From d7b60206d77610d2d0ba62ca280e93470698c94e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 8 Jun 2024 14:55:10 +0200 Subject: [PATCH 01/28] Improve meta manipulation handling a bit. --- Penumbra/Api/Api/TemporaryApi.cs | 4 +- Penumbra/Api/TempModManager.cs | 2 +- .../Meta/Manipulations/EqdpManipulation.cs | 17 +-- .../Meta/Manipulations/EqpManipulation.cs | 17 +-- .../Meta/Manipulations/EstManipulation.cs | 15 +- .../Manipulations/GlobalEqpManipulation.cs | 31 +++- .../Meta/Manipulations/GmpManipulation.cs | 15 +- Penumbra/Meta/Manipulations/MetaDictionary.cs | 140 ++++++++++++++++++ Penumbra/Meta/Manipulations/Rsp.cs | 28 +++- .../Meta/Manipulations/RspManipulation.cs | 27 ++-- Penumbra/Mods/SubMods/DefaultSubMod.cs | 2 +- Penumbra/Mods/SubMods/IModDataContainer.cs | 6 +- Penumbra/Mods/SubMods/OptionSubMod.cs | 2 +- Penumbra/Mods/TemporaryMod.cs | 2 +- 14 files changed, 242 insertions(+), 66 deletions(-) create mode 100644 Penumbra/Meta/Manipulations/MetaDictionary.cs diff --git a/Penumbra/Api/Api/TemporaryApi.cs b/Penumbra/Api/Api/TemporaryApi.cs index 38d080cc..09a9b7c4 100644 --- a/Penumbra/Api/Api/TemporaryApi.cs +++ b/Penumbra/Api/Api/TemporaryApi.cs @@ -160,7 +160,7 @@ private static bool ConvertPaths(IReadOnlyDictionary redirection /// Only returns true if all conversions are successful and distinct. /// private static bool ConvertManips(string manipString, - [NotNullWhen(true)] out HashSet? manips) + [NotNullWhen(true)] out MetaDictionary? manips) { if (manipString.Length == 0) { @@ -174,7 +174,7 @@ private static bool ConvertManips(string manipString, return false; } - manips = new HashSet(manipArray!.Length); + manips = new MetaDictionary(manipArray!.Length); foreach (var manip in manipArray.Where(m => m.Validate())) { if (manips.Add(manip)) diff --git a/Penumbra/Api/TempModManager.cs b/Penumbra/Api/TempModManager.cs index aee2b447..cbb07436 100644 --- a/Penumbra/Api/TempModManager.cs +++ b/Penumbra/Api/TempModManager.cs @@ -43,7 +43,7 @@ public IReadOnlyList ModsForAllCollections => _modsForAllCollections; public RedirectResult Register(string tag, ModCollection? collection, Dictionary dict, - HashSet manips, ModPriority priority) + MetaDictionary manips, ModPriority priority) { var mod = GetOrCreateMod(tag, collection, priority, out var created); Penumbra.Log.Verbose($"{(created ? "Created" : "Changed")} temporary Mod {mod.Name}."); diff --git a/Penumbra/Meta/Manipulations/EqdpManipulation.cs b/Penumbra/Meta/Manipulations/EqdpManipulation.cs index 2c01ce3f..8c5f27e5 100644 --- a/Penumbra/Meta/Manipulations/EqdpManipulation.cs +++ b/Penumbra/Meta/Manipulations/EqdpManipulation.cs @@ -8,11 +8,12 @@ namespace Penumbra.Meta.Manipulations; [StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct EqdpManipulation : IMetaManipulation +public readonly struct EqdpManipulation(EqdpIdentifier identifier, EqdpEntry entry) : IMetaManipulation { [JsonIgnore] - public EqdpIdentifier Identifier { get; private init; } - public EqdpEntry Entry { get; private init; } + public EqdpIdentifier Identifier { get; } = identifier; + + public EqdpEntry Entry { get; } = entry; [JsonConverter(typeof(StringEnumConverter))] public Gender Gender @@ -31,20 +32,18 @@ public EquipSlot Slot [JsonConstructor] public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, PrimaryId setId) - { - Identifier = new EqdpIdentifier(setId, slot, Names.CombinedRace(gender, race)); - Entry = Eqdp.Mask(Slot) & entry; - } + : this(new EqdpIdentifier(setId, slot, Names.CombinedRace(gender, race)), Eqdp.Mask(slot) & entry) + { } public EqdpManipulation Copy(EqdpManipulation entry) { if (entry.Slot != Slot) { var (bit1, bit2) = entry.Entry.ToBits(entry.Slot); - return new EqdpManipulation(Eqdp.FromSlotAndBits(Slot, bit1, bit2), Slot, Gender, Race, SetId); + return new EqdpManipulation(Identifier, Eqdp.FromSlotAndBits(Slot, bit1, bit2)); } - return new EqdpManipulation(entry.Entry, Slot, Gender, Race, SetId); + return new EqdpManipulation(Identifier, entry.Entry); } public EqdpManipulation Copy(EqdpEntry entry) diff --git a/Penumbra/Meta/Manipulations/EqpManipulation.cs b/Penumbra/Meta/Manipulations/EqpManipulation.cs index 3bced096..eef21d12 100644 --- a/Penumbra/Meta/Manipulations/EqpManipulation.cs +++ b/Penumbra/Meta/Manipulations/EqpManipulation.cs @@ -9,12 +9,13 @@ namespace Penumbra.Meta.Manipulations; [StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct EqpManipulation : IMetaManipulation +public readonly struct EqpManipulation(EqpIdentifier identifier, EqpEntry entry) : IMetaManipulation { - [JsonConverter(typeof(ForceNumericFlagEnumConverter))] - public EqpEntry Entry { get; private init; } + [JsonIgnore] + public EqpIdentifier Identifier { get; } = identifier; - public EqpIdentifier Identifier { get; private init; } + [JsonConverter(typeof(ForceNumericFlagEnumConverter))] + public EqpEntry Entry { get; } = entry; public PrimaryId SetId => Identifier.SetId; @@ -25,13 +26,11 @@ public EquipSlot Slot [JsonConstructor] public EqpManipulation(EqpEntry entry, EquipSlot slot, PrimaryId setId) - { - Identifier = new EqpIdentifier(setId, slot); - Entry = Eqp.Mask(slot) & entry; - } + : this(new EqpIdentifier(setId, slot), Eqp.Mask(slot) & entry) + { } public EqpManipulation Copy(EqpEntry entry) - => new(entry, Slot, SetId); + => new(Identifier, entry); public override string ToString() => $"Eqp - {SetId} - {Slot}"; diff --git a/Penumbra/Meta/Manipulations/EstManipulation.cs b/Penumbra/Meta/Manipulations/EstManipulation.cs index c3f9792f..09abbaa5 100644 --- a/Penumbra/Meta/Manipulations/EstManipulation.cs +++ b/Penumbra/Meta/Manipulations/EstManipulation.cs @@ -8,7 +8,7 @@ namespace Penumbra.Meta.Manipulations; [StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct EstManipulation : IMetaManipulation +public readonly struct EstManipulation(EstIdentifier identifier, EstEntry entry) : IMetaManipulation { public static string ToName(EstType type) => type switch @@ -20,8 +20,9 @@ public static string ToName(EstType type) _ => "unk", }; - public EstIdentifier Identifier { get; private init; } - public EstEntry Entry { get; private init; } + [JsonIgnore] + public EstIdentifier Identifier { get; } = identifier; + public EstEntry Entry { get; } = entry; [JsonConverter(typeof(StringEnumConverter))] public Gender Gender @@ -41,13 +42,11 @@ public EstType Slot [JsonConstructor] public EstManipulation(Gender gender, ModelRace race, EstType slot, PrimaryId setId, EstEntry entry) - { - Entry = entry; - Identifier = new EstIdentifier(setId, slot, Names.CombinedRace(gender, race)); - } + : this(new EstIdentifier(setId, slot, Names.CombinedRace(gender, race)), entry) + { } public EstManipulation Copy(EstEntry entry) - => new(Gender, Race, Slot, SetId, entry); + => new(Identifier, entry); public override string ToString() diff --git a/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs b/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs index ada543dc..94c892e2 100644 --- a/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs +++ b/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs @@ -1,9 +1,11 @@ +using Newtonsoft.Json.Linq; +using Penumbra.GameData.Data; using Penumbra.GameData.Structs; using Penumbra.Interop.Structs; namespace Penumbra.Meta.Manipulations; -public readonly struct GlobalEqpManipulation : IMetaManipulation +public readonly struct GlobalEqpManipulation : IMetaManipulation, IMetaIdentifier { public GlobalEqpType Type { get; init; } public PrimaryId Condition { get; init; } @@ -19,6 +21,28 @@ public bool Validate() return Condition != 0; } + public JObject AddToJson(JObject jObj) + { + jObj[nameof(Type)] = Type.ToString(); + jObj[nameof(Condition)] = Condition.Id; + return jObj; + } + + public static GlobalEqpManipulation? FromJson(JObject? jObj) + { + if (jObj == null) + return null; + + var type = jObj[nameof(Type)]?.ToObject() ?? (GlobalEqpType)100; + var condition = jObj[nameof(Condition)]?.ToObject() ?? 0; + var ret = new GlobalEqpManipulation + { + Type = type, + Condition = condition, + }; + return ret.Validate() ? ret : null; + } + public bool Equals(GlobalEqpManipulation other) => Type == other.Type @@ -45,6 +69,9 @@ public override int GetHashCode() public override string ToString() => $"Global EQP - {Type}{(Condition != 0 ? $" - {Condition.Id}" : string.Empty)}"; + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + { } + public MetaIndex FileIndex() - => (MetaIndex)(-1); + => MetaIndex.Eqp; } diff --git a/Penumbra/Meta/Manipulations/GmpManipulation.cs b/Penumbra/Meta/Manipulations/GmpManipulation.cs index 0b2a9f4b..431f6325 100644 --- a/Penumbra/Meta/Manipulations/GmpManipulation.cs +++ b/Penumbra/Meta/Manipulations/GmpManipulation.cs @@ -6,24 +6,23 @@ namespace Penumbra.Meta.Manipulations; [StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct GmpManipulation : IMetaManipulation +public readonly struct GmpManipulation(GmpIdentifier identifier, GmpEntry entry) : IMetaManipulation { - public GmpIdentifier Identifier { get; private init; } + [JsonIgnore] + public GmpIdentifier Identifier { get; } = identifier; - public GmpEntry Entry { get; private init; } + public GmpEntry Entry { get; } = entry; public PrimaryId SetId => Identifier.SetId; [JsonConstructor] public GmpManipulation(GmpEntry entry, PrimaryId setId) - { - Entry = entry; - Identifier = new GmpIdentifier(setId); - } + : this(new GmpIdentifier(setId), entry) + { } public GmpManipulation Copy(GmpEntry entry) - => new(entry, SetId); + => new(Identifier, entry); public override string ToString() => $"Gmp - {SetId}"; diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs new file mode 100644 index 00000000..65252c5d --- /dev/null +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -0,0 +1,140 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Penumbra.GameData.Structs; +using ImcEntry = Penumbra.GameData.Structs.ImcEntry; + +namespace Penumbra.Meta.Manipulations; + +[JsonConverter(typeof(Converter))] +public sealed class MetaDictionary : HashSet +{ + public MetaDictionary() + { } + + public MetaDictionary(int capacity) + : base(capacity) + { } + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, MetaDictionary? value, JsonSerializer serializer) + { + if (value is null) + { + writer.WriteNull(); + return; + } + + writer.WriteStartArray(); + foreach (var item in value) + { + writer.WriteStartObject(); + writer.WritePropertyName("Type"); + writer.WriteValue(item.ManipulationType.ToString()); + writer.WritePropertyName("Manipulation"); + serializer.Serialize(writer, item.Manipulation); + writer.WriteEndObject(); + } + + writer.WriteEndArray(); + } + + public override MetaDictionary ReadJson(JsonReader reader, Type objectType, MetaDictionary? existingValue, bool hasExistingValue, + JsonSerializer serializer) + { + var dict = existingValue ?? []; + dict.Clear(); + var jObj = JArray.Load(reader); + foreach (var item in jObj) + { + var type = item["Type"]?.ToObject() ?? MetaManipulation.Type.Unknown; + if (type is MetaManipulation.Type.Unknown) + { + Penumbra.Log.Warning($"Invalid Meta Manipulation Type {type} encountered."); + continue; + } + + if (item["Manipulation"] is not JObject manip) + { + Penumbra.Log.Warning($"Manipulation of type {type} does not contain manipulation data."); + continue; + } + + switch (type) + { + case MetaManipulation.Type.Imc: + { + var identifier = ImcIdentifier.FromJson(manip); + var entry = manip["Entry"]?.ToObject(); + if (identifier.HasValue && entry.HasValue) + dict.Add(new MetaManipulation(new ImcManipulation(identifier.Value, entry.Value))); + else + Penumbra.Log.Warning("Invalid IMC Manipulation encountered."); + break; + } + case MetaManipulation.Type.Eqdp: + { + var identifier = EqdpIdentifier.FromJson(manip); + var entry = (EqdpEntry?)manip["Entry"]?.ToObject(); + if (identifier.HasValue && entry.HasValue) + dict.Add(new MetaManipulation(new EqdpManipulation(identifier.Value, entry.Value))); + else + Penumbra.Log.Warning("Invalid EQDP Manipulation encountered."); + break; + } + case MetaManipulation.Type.Eqp: + { + var identifier = EqpIdentifier.FromJson(manip); + var entry = (EqpEntry?)manip["Entry"]?.ToObject(); + if (identifier.HasValue && entry.HasValue) + dict.Add(new MetaManipulation(new EqpManipulation(identifier.Value, entry.Value))); + else + Penumbra.Log.Warning("Invalid EQP Manipulation encountered."); + break; + } + case MetaManipulation.Type.Est: + { + var identifier = EstIdentifier.FromJson(manip); + var entry = manip["Entry"]?.ToObject(); + if (identifier.HasValue && entry.HasValue) + dict.Add(new MetaManipulation(new EstManipulation(identifier.Value, entry.Value))); + else + Penumbra.Log.Warning("Invalid EST Manipulation encountered."); + break; + } + case MetaManipulation.Type.Gmp: + { + var identifier = GmpIdentifier.FromJson(manip); + var entry = manip["Entry"]?.ToObject(); + if (identifier.HasValue && entry.HasValue) + dict.Add(new MetaManipulation(new GmpManipulation(identifier.Value, entry.Value))); + else + Penumbra.Log.Warning("Invalid GMP Manipulation encountered."); + break; + } + case MetaManipulation.Type.Rsp: + { + var identifier = RspIdentifier.FromJson(manip); + var entry = manip["Entry"]?.ToObject(); + if (identifier.HasValue && entry.HasValue) + dict.Add(new MetaManipulation(new RspManipulation(identifier.Value, entry.Value))); + else + Penumbra.Log.Warning("Invalid RSP Manipulation encountered."); + break; + } + case MetaManipulation.Type.GlobalEqp: + { + var identifier = GlobalEqpManipulation.FromJson(manip); + if (identifier.HasValue) + dict.Add(new MetaManipulation(identifier.Value)); + else + Penumbra.Log.Warning("Invalid Global EQP Manipulation encountered."); + break; + } + } + } + + return dict; + } + } +} diff --git a/Penumbra/Meta/Manipulations/Rsp.cs b/Penumbra/Meta/Manipulations/Rsp.cs index 29cdfd71..ca7cb1c5 100644 --- a/Penumbra/Meta/Manipulations/Rsp.cs +++ b/Penumbra/Meta/Manipulations/Rsp.cs @@ -1,3 +1,4 @@ +using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.Data; @@ -12,13 +13,31 @@ public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems.TryAdd($"{SubRace.ToName()} {Attribute.ToFullString()}", null); public MetaIndex FileIndex() - => throw new NotImplementedException(); + => MetaIndex.HumanCmp; public bool Validate() - => throw new NotImplementedException(); + => SubRace is not SubRace.Unknown + && Enum.IsDefined(SubRace) + && Attribute is not RspAttribute.NumAttributes + && Enum.IsDefined(Attribute); public JObject AddToJson(JObject jObj) - => throw new NotImplementedException(); + { + jObj["SubRace"] = SubRace.ToString(); + jObj["Attribute"] = Attribute.ToString(); + return jObj; + } + + public static RspIdentifier? FromJson(JObject? jObj) + { + if (jObj == null) + return null; + + var subRace = jObj["SubRace"]?.ToObject() ?? SubRace.Unknown; + var attribute = jObj["Attribute"]?.ToObject() ?? RspAttribute.NumAttributes; + var ret = new RspIdentifier(subRace, attribute); + return ret.Validate() ? ret : null; + } } [JsonConverter(typeof(Converter))] @@ -28,6 +47,9 @@ public readonly record struct RspEntry(float Value) : IComparisonOperators Value is >= MinValue and <= MaxValue; + private class Converter : JsonConverter { public override void WriteJson(JsonWriter writer, RspEntry value, JsonSerializer serializer) diff --git a/Penumbra/Meta/Manipulations/RspManipulation.cs b/Penumbra/Meta/Manipulations/RspManipulation.cs index 04691c9f..e2282c41 100644 --- a/Penumbra/Meta/Manipulations/RspManipulation.cs +++ b/Penumbra/Meta/Manipulations/RspManipulation.cs @@ -7,10 +7,12 @@ namespace Penumbra.Meta.Manipulations; [StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct RspManipulation : IMetaManipulation +public readonly struct RspManipulation(RspIdentifier identifier, RspEntry entry) : IMetaManipulation { - public RspIdentifier Identifier { get; private init; } - public RspEntry Entry { get; private init; } + [JsonIgnore] + public RspIdentifier Identifier { get; } = identifier; + + public RspEntry Entry { get; } = entry; [JsonConverter(typeof(StringEnumConverter))] public SubRace SubRace @@ -22,13 +24,11 @@ public RspAttribute Attribute [JsonConstructor] public RspManipulation(SubRace subRace, RspAttribute attribute, RspEntry entry) - { - Entry = entry; - Identifier = new RspIdentifier(subRace, attribute); - } + : this(new RspIdentifier(subRace, attribute), entry) + { } public RspManipulation Copy(RspEntry entry) - => new(SubRace, Attribute, entry); + => new(Identifier, entry); public override string ToString() => $"Rsp - {SubRace.ToName()} - {Attribute.ToFullString()}"; @@ -63,14 +63,5 @@ public bool Apply(CmpFile file) } public bool Validate() - { - if (SubRace is SubRace.Unknown || !Enum.IsDefined(SubRace)) - return false; - if (!Enum.IsDefined(Attribute)) - return false; - if (Entry.Value is < RspEntry.MinValue or > RspEntry.MaxValue) - return false; - - return true; - } + => Identifier.Validate() && Entry.Validate(); } diff --git a/Penumbra/Mods/SubMods/DefaultSubMod.cs b/Penumbra/Mods/SubMods/DefaultSubMod.cs index 1a234879..5a300a48 100644 --- a/Penumbra/Mods/SubMods/DefaultSubMod.cs +++ b/Penumbra/Mods/SubMods/DefaultSubMod.cs @@ -13,7 +13,7 @@ public class DefaultSubMod(IMod mod) : IModDataContainer public Dictionary Files { get; set; } = []; public Dictionary FileSwaps { get; set; } = []; - public HashSet Manipulations { get; set; } = []; + public MetaDictionary Manipulations { get; set; } = []; IMod IModDataContainer.Mod => Mod; diff --git a/Penumbra/Mods/SubMods/IModDataContainer.cs b/Penumbra/Mods/SubMods/IModDataContainer.cs index 7f7ef4a6..1a89ec17 100644 --- a/Penumbra/Mods/SubMods/IModDataContainer.cs +++ b/Penumbra/Mods/SubMods/IModDataContainer.cs @@ -10,9 +10,9 @@ public interface IModDataContainer public IMod Mod { get; } public IModGroup? Group { get; } - public Dictionary Files { get; set; } - public Dictionary FileSwaps { get; set; } - public HashSet Manipulations { get; set; } + public Dictionary Files { get; set; } + public Dictionary FileSwaps { get; set; } + public MetaDictionary Manipulations { get; set; } public string GetName(); public string GetFullName(); diff --git a/Penumbra/Mods/SubMods/OptionSubMod.cs b/Penumbra/Mods/SubMods/OptionSubMod.cs index 02d86af2..378f6dc8 100644 --- a/Penumbra/Mods/SubMods/OptionSubMod.cs +++ b/Penumbra/Mods/SubMods/OptionSubMod.cs @@ -33,7 +33,7 @@ IModGroup IModOption.Group public Dictionary Files { get; set; } = []; public Dictionary FileSwaps { get; set; } = []; - public HashSet Manipulations { get; set; } = []; + public MetaDictionary Manipulations { get; set; } = []; public void AddDataTo(Dictionary redirections, HashSet manipulations) => SubMod.AddContainerTo(this, redirections, manipulations); diff --git a/Penumbra/Mods/TemporaryMod.cs b/Penumbra/Mods/TemporaryMod.cs index 6e6e72ab..e0a03c92 100644 --- a/Penumbra/Mods/TemporaryMod.cs +++ b/Penumbra/Mods/TemporaryMod.cs @@ -57,7 +57,7 @@ public void SetFile(Utf8GamePath gamePath, FullPath fullPath) public bool SetManipulation(MetaManipulation manip) => Default.Manipulations.Remove(manip) | Default.Manipulations.Add(manip); - public void SetAll(Dictionary dict, HashSet manips) + public void SetAll(Dictionary dict, MetaDictionary manips) { Default.Files = dict; Default.Manipulations = manips; From 94fdd848b718ef2c7e932faff6b108f7ed7287d8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 8 Jun 2024 16:06:37 +0200 Subject: [PATCH 02/28] Expand on MetaDictionary to use separate dictionaries. --- Penumbra/Api/Api/TemporaryApi.cs | 4 +- Penumbra/Meta/Manipulations/MetaDictionary.cs | 244 +++++++++++++++++- Penumbra/Mods/Editor/IMod.cs | 2 +- Penumbra/Mods/Editor/ModMerger.cs | 2 +- Penumbra/Mods/Editor/ModMetaEditor.cs | 2 +- Penumbra/Mods/Groups/IModGroup.cs | 2 +- Penumbra/Mods/Groups/ImcModGroup.cs | 2 +- Penumbra/Mods/Groups/MultiModGroup.cs | 2 +- Penumbra/Mods/Groups/SingleModGroup.cs | 2 +- Penumbra/Mods/ItemSwap/ItemSwapContainer.cs | 21 +- .../Manager/OptionEditor/ModGroupEditor.cs | 2 +- Penumbra/Mods/Mod.cs | 2 +- Penumbra/Mods/SubMods/DefaultSubMod.cs | 2 +- Penumbra/Mods/SubMods/OptionSubMod.cs | 2 +- Penumbra/Mods/SubMods/SubMod.cs | 2 +- 15 files changed, 257 insertions(+), 36 deletions(-) diff --git a/Penumbra/Api/Api/TemporaryApi.cs b/Penumbra/Api/Api/TemporaryApi.cs index 09a9b7c4..995ec388 100644 --- a/Penumbra/Api/Api/TemporaryApi.cs +++ b/Penumbra/Api/Api/TemporaryApi.cs @@ -174,8 +174,8 @@ private static bool ConvertManips(string manipString, return false; } - manips = new MetaDictionary(manipArray!.Length); - foreach (var manip in manipArray.Where(m => m.Validate())) + manips = []; + foreach (var manip in manipArray!.Where(m => m.Validate())) { if (manips.Add(manip)) continue; diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs index 65252c5d..b0b7f011 100644 --- a/Penumbra/Meta/Manipulations/MetaDictionary.cs +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -1,19 +1,237 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.Structs; +using Penumbra.Util; using ImcEntry = Penumbra.GameData.Structs.ImcEntry; namespace Penumbra.Meta.Manipulations; [JsonConverter(typeof(Converter))] -public sealed class MetaDictionary : HashSet +public sealed class MetaDictionary : IEnumerable { - public MetaDictionary() - { } + private readonly Dictionary _imc = []; + private readonly Dictionary _eqp = []; + private readonly Dictionary _eqdp = []; + private readonly Dictionary _est = []; + private readonly Dictionary _rsp = []; + private readonly Dictionary _gmp = []; + private readonly HashSet _globalEqp = []; - public MetaDictionary(int capacity) - : base(capacity) - { } + public int Count { get; private set; } + + public void Clear() + { + _imc.Clear(); + _eqp.Clear(); + _eqdp.Clear(); + _est.Clear(); + _rsp.Clear(); + _gmp.Clear(); + _globalEqp.Clear(); + } + + public IEnumerator GetEnumerator() + => _imc.Select(kvp => new MetaManipulation(new ImcManipulation(kvp.Key, kvp.Value))) + .Concat(_eqp.Select(kvp => new MetaManipulation(new EqpManipulation(kvp.Key, kvp.Value)))) + .Concat(_eqdp.Select(kvp => new MetaManipulation(new EqdpManipulation(kvp.Key, kvp.Value)))) + .Concat(_est.Select(kvp => new MetaManipulation(new EstManipulation(kvp.Key, kvp.Value)))) + .Concat(_rsp.Select(kvp => new MetaManipulation(new RspManipulation(kvp.Key, kvp.Value)))) + .Concat(_gmp.Select(kvp => new MetaManipulation(new GmpManipulation(kvp.Key, kvp.Value)))) + .Concat(_globalEqp.Select(manip => new MetaManipulation(manip))).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public bool Add(MetaManipulation manip) + { + var ret = manip.ManipulationType switch + { + MetaManipulation.Type.Imc => _imc.TryAdd(manip.Imc.Identifier, manip.Imc.Entry), + MetaManipulation.Type.Eqdp => _eqdp.TryAdd(manip.Eqdp.Identifier, manip.Eqdp.Entry), + MetaManipulation.Type.Eqp => _eqp.TryAdd(manip.Eqp.Identifier, manip.Eqp.Entry), + MetaManipulation.Type.Est => _est.TryAdd(manip.Est.Identifier, manip.Est.Entry), + MetaManipulation.Type.Gmp => _gmp.TryAdd(manip.Gmp.Identifier, manip.Gmp.Entry), + MetaManipulation.Type.Rsp => _rsp.TryAdd(manip.Rsp.Identifier, manip.Rsp.Entry), + MetaManipulation.Type.GlobalEqp => _globalEqp.Add(manip.GlobalEqp), + _ => false, + }; + + if (ret) + ++Count; + return ret; + } + + public bool TryAdd(ImcIdentifier identifier, ImcEntry entry) + { + if (!_imc.TryAdd(identifier, entry)) + return false; + + ++Count; + return true; + } + + public bool TryAdd(EqpIdentifier identifier, EqpEntry entry) + { + if (!_eqp.TryAdd(identifier, entry)) + return false; + + ++Count; + return true; + } + + public bool TryAdd(EqdpIdentifier identifier, EqdpEntry entry) + { + if (!_eqdp.TryAdd(identifier, entry)) + return false; + + ++Count; + return true; + } + + public bool TryAdd(EstIdentifier identifier, EstEntry entry) + { + if (!_est.TryAdd(identifier, entry)) + return false; + + ++Count; + return true; + } + + public bool TryAdd(GmpIdentifier identifier, GmpEntry entry) + { + if (!_gmp.TryAdd(identifier, entry)) + return false; + + ++Count; + return true; + } + + public bool TryAdd(RspIdentifier identifier, RspEntry entry) + { + if (!_rsp.TryAdd(identifier, entry)) + return false; + + ++Count; + return true; + } + + public bool TryAdd(GlobalEqpManipulation identifier) + { + if (!_globalEqp.Add(identifier)) + return false; + + ++Count; + return true; + } + + public bool Remove(MetaManipulation manip) + { + var ret = manip.ManipulationType switch + { + MetaManipulation.Type.Imc => _imc.Remove(manip.Imc.Identifier), + MetaManipulation.Type.Eqdp => _eqdp.Remove(manip.Eqdp.Identifier), + MetaManipulation.Type.Eqp => _eqp.Remove(manip.Eqp.Identifier), + MetaManipulation.Type.Est => _est.Remove(manip.Est.Identifier), + MetaManipulation.Type.Gmp => _gmp.Remove(manip.Gmp.Identifier), + MetaManipulation.Type.Rsp => _rsp.Remove(manip.Rsp.Identifier), + MetaManipulation.Type.GlobalEqp => _globalEqp.Remove(manip.GlobalEqp), + _ => false, + }; + if (ret) + --Count; + return ret; + } + + public void UnionWith(IEnumerable manips) + { + foreach (var manip in manips) + Add(manip); + } + + public bool TryGetValue(MetaManipulation identifier, out MetaManipulation oldValue) + { + switch (identifier.ManipulationType) + { + case MetaManipulation.Type.Imc: + if (_imc.TryGetValue(identifier.Imc.Identifier, out var oldImc)) + { + oldValue = new MetaManipulation(new ImcManipulation(identifier.Imc.Identifier, oldImc)); + return true; + } + + break; + case MetaManipulation.Type.Eqdp: + if (_eqp.TryGetValue(identifier.Eqp.Identifier, out var oldEqdp)) + { + oldValue = new MetaManipulation(new EqpManipulation(identifier.Eqp.Identifier, oldEqdp)); + return true; + } + + break; + case MetaManipulation.Type.Eqp: + if (_eqdp.TryGetValue(identifier.Eqdp.Identifier, out var oldEqp)) + { + oldValue = new MetaManipulation(new EqdpManipulation(identifier.Eqdp.Identifier, oldEqp)); + return true; + } + + break; + case MetaManipulation.Type.Est: + if (_est.TryGetValue(identifier.Est.Identifier, out var oldEst)) + { + oldValue = new MetaManipulation(new EstManipulation(identifier.Est.Identifier, oldEst)); + return true; + } + + break; + case MetaManipulation.Type.Gmp: + if (_gmp.TryGetValue(identifier.Gmp.Identifier, out var oldGmp)) + { + oldValue = new MetaManipulation(new GmpManipulation(identifier.Gmp.Identifier, oldGmp)); + return true; + } + + break; + case MetaManipulation.Type.Rsp: + if (_rsp.TryGetValue(identifier.Rsp.Identifier, out var oldRsp)) + { + oldValue = new MetaManipulation(new RspManipulation(identifier.Rsp.Identifier, oldRsp)); + return true; + } + + break; + case MetaManipulation.Type.GlobalEqp: + if (_globalEqp.TryGetValue(identifier.GlobalEqp, out var oldGlobalEqp)) + { + oldValue = new MetaManipulation(oldGlobalEqp); + return true; + } + + break; + } + + oldValue = default; + return false; + } + + public void SetTo(MetaDictionary other) + { + _imc.SetTo(other._imc); + _eqp.SetTo(other._eqp); + _eqdp.SetTo(other._eqdp); + _est.SetTo(other._est); + _rsp.SetTo(other._rsp); + _gmp.SetTo(other._gmp); + _globalEqp.SetTo(other._globalEqp); + Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _globalEqp.Count; + } + + public MetaDictionary Clone() + { + var ret = new MetaDictionary(); + ret.SetTo(this); + return ret; + } private class Converter : JsonConverter { @@ -67,7 +285,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta var identifier = ImcIdentifier.FromJson(manip); var entry = manip["Entry"]?.ToObject(); if (identifier.HasValue && entry.HasValue) - dict.Add(new MetaManipulation(new ImcManipulation(identifier.Value, entry.Value))); + dict.TryAdd(identifier.Value, entry.Value); else Penumbra.Log.Warning("Invalid IMC Manipulation encountered."); break; @@ -77,7 +295,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta var identifier = EqdpIdentifier.FromJson(manip); var entry = (EqdpEntry?)manip["Entry"]?.ToObject(); if (identifier.HasValue && entry.HasValue) - dict.Add(new MetaManipulation(new EqdpManipulation(identifier.Value, entry.Value))); + dict.TryAdd(identifier.Value, entry.Value); else Penumbra.Log.Warning("Invalid EQDP Manipulation encountered."); break; @@ -87,7 +305,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta var identifier = EqpIdentifier.FromJson(manip); var entry = (EqpEntry?)manip["Entry"]?.ToObject(); if (identifier.HasValue && entry.HasValue) - dict.Add(new MetaManipulation(new EqpManipulation(identifier.Value, entry.Value))); + dict.TryAdd(identifier.Value, entry.Value); else Penumbra.Log.Warning("Invalid EQP Manipulation encountered."); break; @@ -97,7 +315,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta var identifier = EstIdentifier.FromJson(manip); var entry = manip["Entry"]?.ToObject(); if (identifier.HasValue && entry.HasValue) - dict.Add(new MetaManipulation(new EstManipulation(identifier.Value, entry.Value))); + dict.TryAdd(identifier.Value, entry.Value); else Penumbra.Log.Warning("Invalid EST Manipulation encountered."); break; @@ -107,7 +325,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta var identifier = GmpIdentifier.FromJson(manip); var entry = manip["Entry"]?.ToObject(); if (identifier.HasValue && entry.HasValue) - dict.Add(new MetaManipulation(new GmpManipulation(identifier.Value, entry.Value))); + dict.TryAdd(identifier.Value, entry.Value); else Penumbra.Log.Warning("Invalid GMP Manipulation encountered."); break; @@ -117,7 +335,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta var identifier = RspIdentifier.FromJson(manip); var entry = manip["Entry"]?.ToObject(); if (identifier.HasValue && entry.HasValue) - dict.Add(new MetaManipulation(new RspManipulation(identifier.Value, entry.Value))); + dict.TryAdd(identifier.Value, entry.Value); else Penumbra.Log.Warning("Invalid RSP Manipulation encountered."); break; @@ -126,7 +344,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta { var identifier = GlobalEqpManipulation.FromJson(manip); if (identifier.HasValue) - dict.Add(new MetaManipulation(identifier.Value)); + dict.TryAdd(identifier.Value); else Penumbra.Log.Warning("Invalid Global EQP Manipulation encountered."); break; diff --git a/Penumbra/Mods/Editor/IMod.cs b/Penumbra/Mods/Editor/IMod.cs index d4c881e9..06c31846 100644 --- a/Penumbra/Mods/Editor/IMod.cs +++ b/Penumbra/Mods/Editor/IMod.cs @@ -8,7 +8,7 @@ namespace Penumbra.Mods.Editor; public record struct AppliedModData( Dictionary FileRedirections, - HashSet Manipulations) + MetaDictionary Manipulations) { public static readonly AppliedModData Empty = new([], []); } diff --git a/Penumbra/Mods/Editor/ModMerger.cs b/Penumbra/Mods/Editor/ModMerger.cs index f5fc9cd7..4faced80 100644 --- a/Penumbra/Mods/Editor/ModMerger.cs +++ b/Penumbra/Mods/Editor/ModMerger.cs @@ -158,7 +158,7 @@ private void MergeIntoOption(IEnumerable mergeOptions, IModDa { var redirections = option.Files.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); var swaps = option.FileSwaps.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - var manips = option.Manipulations.ToHashSet(); + var manips = option.Manipulations.Clone(); foreach (var originalOption in mergeOptions) { diff --git a/Penumbra/Mods/Editor/ModMetaEditor.cs b/Penumbra/Mods/Editor/ModMetaEditor.cs index 86853755..45d9f8a1 100644 --- a/Penumbra/Mods/Editor/ModMetaEditor.cs +++ b/Penumbra/Mods/Editor/ModMetaEditor.cs @@ -145,7 +145,7 @@ public void Apply(IModDataContainer container) if (!Changes) return; - modManager.OptionEditor.SetManipulations(container, Recombine().ToHashSet()); + modManager.OptionEditor.SetManipulations(container, [..Recombine()]); Changes = false; } diff --git a/Penumbra/Mods/Groups/IModGroup.cs b/Penumbra/Mods/Groups/IModGroup.cs index fcc8c093..00f47e25 100644 --- a/Penumbra/Mods/Groups/IModGroup.cs +++ b/Penumbra/Mods/Groups/IModGroup.cs @@ -43,7 +43,7 @@ public interface IModGroup public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer); - public void AddData(Setting setting, Dictionary redirections, HashSet manipulations); + public void AddData(Setting setting, Dictionary redirections, MetaDictionary manipulations); public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems); /// Ensure that a value is valid for a group. diff --git a/Penumbra/Mods/Groups/ImcModGroup.cs b/Penumbra/Mods/Groups/ImcModGroup.cs index b336203d..383bc9fd 100644 --- a/Penumbra/Mods/Groups/ImcModGroup.cs +++ b/Penumbra/Mods/Groups/ImcModGroup.cs @@ -99,7 +99,7 @@ public ImcManipulation GetManip(ushort mask, Variant variant) => new(Identifier.ObjectType, Identifier.BodySlot, Identifier.PrimaryId, Identifier.SecondaryId.Id, variant.Id, Identifier.EquipSlot, DefaultEntry with { AttributeMask = mask }); - public void AddData(Setting setting, Dictionary redirections, HashSet manipulations) + public void AddData(Setting setting, Dictionary redirections, MetaDictionary manipulations) { if (IsDisabled(setting)) return; diff --git a/Penumbra/Mods/Groups/MultiModGroup.cs b/Penumbra/Mods/Groups/MultiModGroup.cs index 7816d628..220d0a7c 100644 --- a/Penumbra/Mods/Groups/MultiModGroup.cs +++ b/Penumbra/Mods/Groups/MultiModGroup.cs @@ -116,7 +116,7 @@ public int GetIndex() public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer) => new MultiModGroupEditDrawer(editDrawer, this); - public void AddData(Setting setting, Dictionary redirections, HashSet manipulations) + public void AddData(Setting setting, Dictionary redirections, MetaDictionary manipulations) { foreach (var (option, index) in OptionData.WithIndex().OrderByDescending(o => o.Value.Priority)) { diff --git a/Penumbra/Mods/Groups/SingleModGroup.cs b/Penumbra/Mods/Groups/SingleModGroup.cs index a6ebd846..a559d609 100644 --- a/Penumbra/Mods/Groups/SingleModGroup.cs +++ b/Penumbra/Mods/Groups/SingleModGroup.cs @@ -101,7 +101,7 @@ public int GetIndex() public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer) => new SingleModGroupEditDrawer(editDrawer, this); - public void AddData(Setting setting, Dictionary redirections, HashSet manipulations) + public void AddData(Setting setting, Dictionary redirections, MetaDictionary manipulations) { if (OptionData.Count == 0) return; diff --git a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs index af5b2d3a..67a5d007 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs @@ -23,7 +23,7 @@ public class ItemSwapContainer public IReadOnlyDictionary ModRedirections => _appliedModData.FileRedirections; - public IReadOnlySet ModManipulations + public MetaDictionary ModManipulations => _appliedModData.Manipulations; public readonly List Swaps = []; @@ -42,9 +42,10 @@ public enum WriteType NoSwaps, } - public bool WriteMod(ModManager manager, Mod mod, IModDataContainer container, WriteType writeType = WriteType.NoSwaps, DirectoryInfo? directory = null) + public bool WriteMod(ModManager manager, Mod mod, IModDataContainer container, WriteType writeType = WriteType.NoSwaps, + DirectoryInfo? directory = null) { - var convertedManips = new HashSet(Swaps.Count); + var convertedManips = new MetaDictionary(); var convertedFiles = new Dictionary(Swaps.Count); var convertedSwaps = new Dictionary(Swaps.Count); directory ??= mod.ModPath; @@ -98,13 +99,9 @@ public void LoadMod(Mod? mod, ModSettings? settings) { Clear(); if (mod == null || mod.Index < 0) - { - _appliedModData = AppliedModData.Empty; - } + _appliedModData = AppliedModData.Empty; else - { _appliedModData = ModSettings.GetResolveData(mod, settings); - } } public ItemSwapContainer(MetaFileManager manager, ObjectIdentification identifier) @@ -121,7 +118,13 @@ private Func PathResolver(ModCollection? collection) private Func MetaResolver(ModCollection? collection) { - var set = collection?.MetaCache?.Manipulations.ToHashSet() ?? _appliedModData.Manipulations; + if (collection?.MetaCache?.Manipulations is { } cache) + { + MetaDictionary dict = [.. cache]; + return m => dict.TryGetValue(m, out var a) ? a : m; + } + + var set = _appliedModData.Manipulations; return m => set.TryGetValue(m, out var a) ? a : m; } diff --git a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs index 55e01015..01092862 100644 --- a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs +++ b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs @@ -142,7 +142,7 @@ public void ChangeOptionDescription(IModOption option, string newDescription) } /// Set the meta manipulations for a given option. Replaces existing manipulations. - public void SetManipulations(IModDataContainer subMod, HashSet manipulations, SaveType saveType = SaveType.Queue) + public void SetManipulations(IModDataContainer subMod, MetaDictionary manipulations, SaveType saveType = SaveType.Queue) { if (subMod.Manipulations.Count == manipulations.Count && subMod.Manipulations.All(m => manipulations.TryGetValue(m, out var old) && old.EntryEquals(m))) diff --git a/Penumbra/Mods/Mod.cs b/Penumbra/Mods/Mod.cs index 783ef3e6..a7f87dcd 100644 --- a/Penumbra/Mods/Mod.cs +++ b/Penumbra/Mods/Mod.cs @@ -71,7 +71,7 @@ public AppliedModData GetData(ModSettings? settings = null) return AppliedModData.Empty; var dictRedirections = new Dictionary(TotalFileCount); - var setManips = new HashSet(TotalManipulations); + var setManips = new MetaDictionary(); foreach (var (group, groupIndex) in Groups.WithIndex().OrderByDescending(g => g.Value.Priority)) { var config = settings.Settings[groupIndex]; diff --git a/Penumbra/Mods/SubMods/DefaultSubMod.cs b/Penumbra/Mods/SubMods/DefaultSubMod.cs index 5a300a48..dcd33610 100644 --- a/Penumbra/Mods/SubMods/DefaultSubMod.cs +++ b/Penumbra/Mods/SubMods/DefaultSubMod.cs @@ -21,7 +21,7 @@ IMod IModDataContainer.Mod IModGroup? IModDataContainer.Group => null; - public void AddTo(Dictionary redirections, HashSet manipulations) + public void AddTo(Dictionary redirections, MetaDictionary manipulations) => SubMod.AddContainerTo(this, redirections, manipulations); public string GetName() diff --git a/Penumbra/Mods/SubMods/OptionSubMod.cs b/Penumbra/Mods/SubMods/OptionSubMod.cs index 378f6dc8..ed7b6ff8 100644 --- a/Penumbra/Mods/SubMods/OptionSubMod.cs +++ b/Penumbra/Mods/SubMods/OptionSubMod.cs @@ -35,7 +35,7 @@ IModGroup IModOption.Group public Dictionary FileSwaps { get; set; } = []; public MetaDictionary Manipulations { get; set; } = []; - public void AddDataTo(Dictionary redirections, HashSet manipulations) + public void AddDataTo(Dictionary redirections, MetaDictionary manipulations) => SubMod.AddContainerTo(this, redirections, manipulations); public string GetName() diff --git a/Penumbra/Mods/SubMods/SubMod.cs b/Penumbra/Mods/SubMods/SubMod.cs index b984b570..06a924c8 100644 --- a/Penumbra/Mods/SubMods/SubMod.cs +++ b/Penumbra/Mods/SubMods/SubMod.cs @@ -21,7 +21,7 @@ public static int GetIndex(IModOption option) /// Add all unique meta manipulations, file redirections and then file swaps from a ModDataContainer to the given sets. Skip any keys that are already contained. [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] public static void AddContainerTo(IModDataContainer container, Dictionary redirections, - HashSet manipulations) + MetaDictionary manipulations) { foreach (var (path, file) in container.Files) redirections.TryAdd(path, file); From 13156a58e92ab64965879f994eb9f1aec77d8f12 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 8 Jun 2024 16:09:34 +0200 Subject: [PATCH 03/28] Remove unused functions. --- Penumbra/Mods/TemporaryMod.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Penumbra/Mods/TemporaryMod.cs b/Penumbra/Mods/TemporaryMod.cs index e0a03c92..a715f786 100644 --- a/Penumbra/Mods/TemporaryMod.cs +++ b/Penumbra/Mods/TemporaryMod.cs @@ -51,12 +51,6 @@ public IReadOnlyList Groups public TemporaryMod() => Default = new DefaultSubMod(this); - public void SetFile(Utf8GamePath gamePath, FullPath fullPath) - => Default.Files[gamePath] = fullPath; - - public bool SetManipulation(MetaManipulation manip) - => Default.Manipulations.Remove(manip) | Default.Manipulations.Add(manip); - public void SetAll(Dictionary dict, MetaDictionary manips) { Default.Files = dict; From e0339160e908276a97963fd498f9e785a88ee690 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 8 Jun 2024 16:25:08 +0200 Subject: [PATCH 04/28] Start removing MetaManipulation functions. --- Penumbra/Meta/Manipulations/MetaDictionary.cs | 40 +++++++++---------- .../Manager/OptionEditor/ModGroupEditor.cs | 3 +- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs index b0b7f011..b9d7990d 100644 --- a/Penumbra/Meta/Manipulations/MetaDictionary.cs +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -124,28 +124,28 @@ public bool TryAdd(GlobalEqpManipulation identifier) return true; } - public bool Remove(MetaManipulation manip) + public void UnionWith(MetaDictionary manips) { - var ret = manip.ManipulationType switch - { - MetaManipulation.Type.Imc => _imc.Remove(manip.Imc.Identifier), - MetaManipulation.Type.Eqdp => _eqdp.Remove(manip.Eqdp.Identifier), - MetaManipulation.Type.Eqp => _eqp.Remove(manip.Eqp.Identifier), - MetaManipulation.Type.Est => _est.Remove(manip.Est.Identifier), - MetaManipulation.Type.Gmp => _gmp.Remove(manip.Gmp.Identifier), - MetaManipulation.Type.Rsp => _rsp.Remove(manip.Rsp.Identifier), - MetaManipulation.Type.GlobalEqp => _globalEqp.Remove(manip.GlobalEqp), - _ => false, - }; - if (ret) - --Count; - return ret; - } + foreach (var (identifier, entry) in manips._imc) + TryAdd(identifier, entry); - public void UnionWith(IEnumerable manips) - { - foreach (var manip in manips) - Add(manip); + foreach (var (identifier, entry) in manips._eqp) + TryAdd(identifier, entry); + + foreach (var (identifier, entry) in manips._eqdp) + TryAdd(identifier, entry); + + foreach (var (identifier, entry) in manips._gmp) + TryAdd(identifier, entry); + + foreach (var (identifier, entry) in manips._rsp) + TryAdd(identifier, entry); + + foreach (var (identifier, entry) in manips._est) + TryAdd(identifier, entry); + + foreach (var identifier in manips._globalEqp) + TryAdd(identifier); } public bool TryGetValue(MetaManipulation identifier, out MetaManipulation oldValue) diff --git a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs index 01092862..594ec9d2 100644 --- a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs +++ b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs @@ -144,8 +144,7 @@ public void ChangeOptionDescription(IModOption option, string newDescription) /// Set the meta manipulations for a given option. Replaces existing manipulations. public void SetManipulations(IModDataContainer subMod, MetaDictionary manipulations, SaveType saveType = SaveType.Queue) { - if (subMod.Manipulations.Count == manipulations.Count - && subMod.Manipulations.All(m => manipulations.TryGetValue(m, out var old) && old.EntryEquals(m))) + if (subMod.Manipulations.Equals(manipulations)) return; communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); From 5ca9e63a2a5f1f44dd9c5859578c3684bc6df4e6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 8 Jun 2024 16:34:51 +0200 Subject: [PATCH 05/28] Use internal entries. --- Penumbra/Meta/Manipulations/Eqdp.cs | 6 +- Penumbra/Meta/Manipulations/MetaDictionary.cs | 70 ++++++++++++------- Penumbra/Mods/ModCreator.cs | 4 +- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/Penumbra/Meta/Manipulations/Eqdp.cs b/Penumbra/Meta/Manipulations/Eqdp.cs index 6d6942e6..a986d475 100644 --- a/Penumbra/Meta/Manipulations/Eqdp.cs +++ b/Penumbra/Meta/Manipulations/Eqdp.cs @@ -71,13 +71,13 @@ public JObject AddToJson(JObject jObj) } } -public readonly record struct InternalEqdpEntry(bool Model, bool Material) +public readonly record struct EqdpEntryInternal(bool Model, bool Material) { - private InternalEqdpEntry((bool, bool) val) + private EqdpEntryInternal((bool, bool) val) : this(val.Item1, val.Item2) { } - public InternalEqdpEntry(EqdpEntry entry, EquipSlot slot) + public EqdpEntryInternal(EqdpEntry entry, EquipSlot slot) : this(entry.ToBits(slot)) { } diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs index b9d7990d..941cdf34 100644 --- a/Penumbra/Meta/Manipulations/MetaDictionary.cs +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -9,13 +9,13 @@ namespace Penumbra.Meta.Manipulations; [JsonConverter(typeof(Converter))] public sealed class MetaDictionary : IEnumerable { - private readonly Dictionary _imc = []; - private readonly Dictionary _eqp = []; - private readonly Dictionary _eqdp = []; - private readonly Dictionary _est = []; - private readonly Dictionary _rsp = []; - private readonly Dictionary _gmp = []; - private readonly HashSet _globalEqp = []; + private readonly Dictionary _imc = []; + private readonly Dictionary _eqp = []; + private readonly Dictionary _eqdp = []; + private readonly Dictionary _est = []; + private readonly Dictionary _rsp = []; + private readonly Dictionary _gmp = []; + private readonly HashSet _globalEqp = []; public int Count { get; private set; } @@ -30,10 +30,20 @@ public void Clear() _globalEqp.Clear(); } + public bool Equals(MetaDictionary other) + => Count == other.Count + && _imc.SetEquals(other._imc) + && _eqp.SetEquals(other._eqp) + && _eqdp.SetEquals(other._eqdp) + && _est.SetEquals(other._est) + && _rsp.SetEquals(other._rsp) + && _gmp.SetEquals(other._gmp) + && _globalEqp.SetEquals(other._globalEqp); + public IEnumerator GetEnumerator() => _imc.Select(kvp => new MetaManipulation(new ImcManipulation(kvp.Key, kvp.Value))) - .Concat(_eqp.Select(kvp => new MetaManipulation(new EqpManipulation(kvp.Key, kvp.Value)))) - .Concat(_eqdp.Select(kvp => new MetaManipulation(new EqdpManipulation(kvp.Key, kvp.Value)))) + .Concat(_eqp.Select(kvp => new MetaManipulation(new EqpManipulation(kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))))) + .Concat(_eqdp.Select(kvp => new MetaManipulation(new EqdpManipulation(kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))))) .Concat(_est.Select(kvp => new MetaManipulation(new EstManipulation(kvp.Key, kvp.Value)))) .Concat(_rsp.Select(kvp => new MetaManipulation(new RspManipulation(kvp.Key, kvp.Value)))) .Concat(_gmp.Select(kvp => new MetaManipulation(new GmpManipulation(kvp.Key, kvp.Value)))) @@ -47,8 +57,8 @@ public bool Add(MetaManipulation manip) var ret = manip.ManipulationType switch { MetaManipulation.Type.Imc => _imc.TryAdd(manip.Imc.Identifier, manip.Imc.Entry), - MetaManipulation.Type.Eqdp => _eqdp.TryAdd(manip.Eqdp.Identifier, manip.Eqdp.Entry), - MetaManipulation.Type.Eqp => _eqp.TryAdd(manip.Eqp.Identifier, manip.Eqp.Entry), + MetaManipulation.Type.Eqdp => _eqdp.TryAdd(manip.Eqdp.Identifier, new EqdpEntryInternal(manip.Eqdp.Entry, manip.Eqdp.Slot)), + MetaManipulation.Type.Eqp => _eqp.TryAdd(manip.Eqp.Identifier, new EqpEntryInternal(manip.Eqp.Entry, manip.Eqp.Slot)), MetaManipulation.Type.Est => _est.TryAdd(manip.Est.Identifier, manip.Est.Entry), MetaManipulation.Type.Gmp => _gmp.TryAdd(manip.Gmp.Identifier, manip.Gmp.Entry), MetaManipulation.Type.Rsp => _rsp.TryAdd(manip.Rsp.Identifier, manip.Rsp.Entry), @@ -71,22 +81,10 @@ public bool TryAdd(ImcIdentifier identifier, ImcEntry entry) } public bool TryAdd(EqpIdentifier identifier, EqpEntry entry) - { - if (!_eqp.TryAdd(identifier, entry)) - return false; - - ++Count; - return true; - } + => TryAdd(identifier, new EqpEntryInternal(entry, identifier.Slot)); public bool TryAdd(EqdpIdentifier identifier, EqdpEntry entry) - { - if (!_eqdp.TryAdd(identifier, entry)) - return false; - - ++Count; - return true; - } + => TryAdd(identifier, new EqdpEntryInternal(entry, identifier.Slot)); public bool TryAdd(EstIdentifier identifier, EstEntry entry) { @@ -163,7 +161,7 @@ public bool TryGetValue(MetaManipulation identifier, out MetaManipulation oldVal case MetaManipulation.Type.Eqdp: if (_eqp.TryGetValue(identifier.Eqp.Identifier, out var oldEqdp)) { - oldValue = new MetaManipulation(new EqpManipulation(identifier.Eqp.Identifier, oldEqdp)); + oldValue = new MetaManipulation(new EqpManipulation(identifier.Eqp.Identifier, oldEqdp.ToEntry(identifier.Eqp.Slot))); return true; } @@ -171,7 +169,7 @@ public bool TryGetValue(MetaManipulation identifier, out MetaManipulation oldVal case MetaManipulation.Type.Eqp: if (_eqdp.TryGetValue(identifier.Eqdp.Identifier, out var oldEqp)) { - oldValue = new MetaManipulation(new EqdpManipulation(identifier.Eqdp.Identifier, oldEqp)); + oldValue = new MetaManipulation(new EqdpManipulation(identifier.Eqdp.Identifier, oldEqp.ToEntry(identifier.Eqdp.Slot))); return true; } @@ -355,4 +353,22 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta return dict; } } + + private bool TryAdd(EqpIdentifier identifier, EqpEntryInternal entry) + { + if (!_eqp.TryAdd(identifier, entry)) + return false; + + ++Count; + return true; + } + + private bool TryAdd(EqdpIdentifier identifier, EqdpEntryInternal entry) + { + if (!_eqdp.TryAdd(identifier, entry)) + return false; + + ++Count; + return true; + } } diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index ed4245c4..0035fd41 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -198,7 +198,7 @@ public void IncorporateAllMetaChanges(Mod mod, bool delete) Penumbra.Log.Verbose( $"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}"); deleteList.Add(file.FullName); - option.Manipulations.UnionWith(meta.MetaManipulations); + option.Manipulations.UnionWith([.. meta.MetaManipulations]); } else if (ext1 == ".rgsp" || ext2 == ".rgsp") { @@ -212,7 +212,7 @@ public void IncorporateAllMetaChanges(Mod mod, bool delete) $"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}"); deleteList.Add(file.FullName); - option.Manipulations.UnionWith(rgsp.MetaManipulations); + option.Manipulations.UnionWith([.. rgsp.MetaManipulations]); } } catch (Exception e) From 0445ed0ef9ed456a45ac100007f1e847ecd2e68e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 8 Jun 2024 19:09:46 +0200 Subject: [PATCH 06/28] Remove TryGetValue(MetaManipulation) from MetaDictionary. --- Penumbra/Meta/Files/EqdpFile.cs | 4 + Penumbra/Meta/Files/EqpGmpFile.cs | 4 + Penumbra/Meta/Files/EstFile.cs | 3 + Penumbra/Meta/Files/ImcFile.cs | 3 + Penumbra/Meta/Manipulations/Eqdp.cs | 5 +- Penumbra/Meta/Manipulations/MetaDictionary.cs | 85 ++++-------- Penumbra/Mods/ItemSwap/EquipmentSwap.cs | 131 +++++++++--------- Penumbra/Mods/ItemSwap/ItemSwap.cs | 21 ++- Penumbra/Mods/ItemSwap/ItemSwapContainer.cs | 23 ++- Penumbra/Mods/ItemSwap/Swaps.cs | 84 +++++++---- Penumbra/UI/AdvancedWindow/ItemSwapTab.cs | 4 +- 11 files changed, 183 insertions(+), 184 deletions(-) diff --git a/Penumbra/Meta/Files/EqdpFile.cs b/Penumbra/Meta/Files/EqdpFile.cs index c76c4efd..e46e82e9 100644 --- a/Penumbra/Meta/Files/EqdpFile.cs +++ b/Penumbra/Meta/Files/EqdpFile.cs @@ -2,6 +2,7 @@ using Penumbra.GameData.Structs; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; +using Penumbra.Meta.Manipulations; using Penumbra.String.Functions; namespace Penumbra.Meta.Files; @@ -126,4 +127,7 @@ public static EqdpEntry GetDefault(byte* data, PrimaryId primaryId) public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, PrimaryId primaryId) => GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], primaryId); + + public static EqdpEntry GetDefault(MetaFileManager manager, EqdpIdentifier identifier) + => GetDefault(manager, CharacterUtility.ReverseIndices[(int)identifier.FileIndex()], identifier.SetId); } diff --git a/Penumbra/Meta/Files/EqpGmpFile.cs b/Penumbra/Meta/Files/EqpGmpFile.cs index 70067c2b..17541c4f 100644 --- a/Penumbra/Meta/Files/EqpGmpFile.cs +++ b/Penumbra/Meta/Files/EqpGmpFile.cs @@ -1,6 +1,7 @@ using Penumbra.GameData.Structs; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; +using Penumbra.Meta.Manipulations; using Penumbra.String.Functions; namespace Penumbra.Meta.Files; @@ -164,6 +165,9 @@ public GmpEntry this[PrimaryId idx] public static GmpEntry GetDefault(MetaFileManager manager, PrimaryId primaryIdx) => new() { Value = GetDefaultInternal(manager, InternalIndex, primaryIdx, GmpEntry.Default.Value) }; + public static GmpEntry GetDefault(MetaFileManager manager, GmpIdentifier identifier) + => new() { Value = GetDefaultInternal(manager, InternalIndex, identifier.SetId, GmpEntry.Default.Value) }; + public void Reset(IEnumerable entries) { foreach (var entry in entries) diff --git a/Penumbra/Meta/Files/EstFile.cs b/Penumbra/Meta/Files/EstFile.cs index ee38ea1e..f3860416 100644 --- a/Penumbra/Meta/Files/EstFile.cs +++ b/Penumbra/Meta/Files/EstFile.cs @@ -184,4 +184,7 @@ public static EstEntry GetDefault(MetaFileManager manager, MetaIndex metaIndex, public static EstEntry GetDefault(MetaFileManager manager, EstType estType, GenderRace genderRace, PrimaryId primaryId) => GetDefault(manager, (MetaIndex)estType, genderRace, primaryId); + + public static EstEntry GetDefault(MetaFileManager manager, EstIdentifier identifier) + => GetDefault(manager, identifier.FileIndex(), identifier.GenderRace, identifier.SetId); } diff --git a/Penumbra/Meta/Files/ImcFile.cs b/Penumbra/Meta/Files/ImcFile.cs index 5d704cf8..892f5b44 100644 --- a/Penumbra/Meta/Files/ImcFile.cs +++ b/Penumbra/Meta/Files/ImcFile.cs @@ -65,6 +65,9 @@ public ImcEntry GetEntry(int partIdx, Variant variantIdx) return ptr == null ? new ImcEntry() : *ptr; } + public ImcEntry GetEntry(EquipSlot slot, Variant variantIdx) + => GetEntry(PartIndex(slot), variantIdx); + public ImcEntry GetEntry(int partIdx, Variant variantIdx, out bool exists) { var ptr = VariantPtr(Data, partIdx, variantIdx); diff --git a/Penumbra/Meta/Manipulations/Eqdp.cs b/Penumbra/Meta/Manipulations/Eqdp.cs index a986d475..6306f419 100644 --- a/Penumbra/Meta/Manipulations/Eqdp.cs +++ b/Penumbra/Meta/Manipulations/Eqdp.cs @@ -71,7 +71,7 @@ public JObject AddToJson(JObject jObj) } } -public readonly record struct EqdpEntryInternal(bool Model, bool Material) +public readonly record struct EqdpEntryInternal(bool Material, bool Model) { private EqdpEntryInternal((bool, bool) val) : this(val.Item1, val.Item2) @@ -81,7 +81,6 @@ public EqdpEntryInternal(EqdpEntry entry, EquipSlot slot) : this(entry.ToBits(slot)) { } - public EqdpEntry ToEntry(EquipSlot slot) - => Eqdp.FromSlotAndBits(slot, Model, Material); + => Eqdp.FromSlotAndBits(slot, Material, Model); } diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs index 941cdf34..51149e3b 100644 --- a/Penumbra/Meta/Manipulations/MetaDictionary.cs +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -52,6 +52,19 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public bool Add(IMetaIdentifier identifier, object entry) + => identifier switch + { + EqdpIdentifier eqdpIdentifier => entry is EqdpEntryInternal e && Add(eqdpIdentifier, e), + EqpIdentifier eqpIdentifier => entry is EqpEntryInternal e && Add(eqpIdentifier, e), + EstIdentifier estIdentifier => entry is EstEntry e && Add(estIdentifier, e), + GlobalEqpManipulation globalEqpManipulation => Add(globalEqpManipulation), + GmpIdentifier gmpIdentifier => entry is GmpEntry e && Add(gmpIdentifier, e), + ImcIdentifier imcIdentifier => entry is ImcEntry e && Add(imcIdentifier, e), + RspIdentifier rspIdentifier => entry is RspEntry e && Add(rspIdentifier, e), + _ => false, + }; + public bool Add(MetaManipulation manip) { var ret = manip.ManipulationType switch @@ -146,71 +159,23 @@ public void UnionWith(MetaDictionary manips) TryAdd(identifier); } - public bool TryGetValue(MetaManipulation identifier, out MetaManipulation oldValue) - { - switch (identifier.ManipulationType) - { - case MetaManipulation.Type.Imc: - if (_imc.TryGetValue(identifier.Imc.Identifier, out var oldImc)) - { - oldValue = new MetaManipulation(new ImcManipulation(identifier.Imc.Identifier, oldImc)); - return true; - } - - break; - case MetaManipulation.Type.Eqdp: - if (_eqp.TryGetValue(identifier.Eqp.Identifier, out var oldEqdp)) - { - oldValue = new MetaManipulation(new EqpManipulation(identifier.Eqp.Identifier, oldEqdp.ToEntry(identifier.Eqp.Slot))); - return true; - } - - break; - case MetaManipulation.Type.Eqp: - if (_eqdp.TryGetValue(identifier.Eqdp.Identifier, out var oldEqp)) - { - oldValue = new MetaManipulation(new EqdpManipulation(identifier.Eqdp.Identifier, oldEqp.ToEntry(identifier.Eqdp.Slot))); - return true; - } - - break; - case MetaManipulation.Type.Est: - if (_est.TryGetValue(identifier.Est.Identifier, out var oldEst)) - { - oldValue = new MetaManipulation(new EstManipulation(identifier.Est.Identifier, oldEst)); - return true; - } + public bool TryGetValue(EstIdentifier identifier, out EstEntry value) + => _est.TryGetValue(identifier, out value); - break; - case MetaManipulation.Type.Gmp: - if (_gmp.TryGetValue(identifier.Gmp.Identifier, out var oldGmp)) - { - oldValue = new MetaManipulation(new GmpManipulation(identifier.Gmp.Identifier, oldGmp)); - return true; - } + public bool TryGetValue(EqpIdentifier identifier, out EqpEntryInternal value) + => _eqp.TryGetValue(identifier, out value); - break; - case MetaManipulation.Type.Rsp: - if (_rsp.TryGetValue(identifier.Rsp.Identifier, out var oldRsp)) - { - oldValue = new MetaManipulation(new RspManipulation(identifier.Rsp.Identifier, oldRsp)); - return true; - } + public bool TryGetValue(EqdpIdentifier identifier, out EqdpEntryInternal value) + => _eqdp.TryGetValue(identifier, out value); - break; - case MetaManipulation.Type.GlobalEqp: - if (_globalEqp.TryGetValue(identifier.GlobalEqp, out var oldGlobalEqp)) - { - oldValue = new MetaManipulation(oldGlobalEqp); - return true; - } + public bool TryGetValue(GmpIdentifier identifier, out GmpEntry value) + => _gmp.TryGetValue(identifier, out value); - break; - } + public bool TryGetValue(RspIdentifier identifier, out RspEntry value) + => _rsp.TryGetValue(identifier, out value); - oldValue = default; - return false; - } + public bool TryGetValue(ImcIdentifier identifier, out ImcEntry value) + => _imc.TryGetValue(identifier, out value); public void SetTo(MetaDictionary other) { diff --git a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs index 3efee857..e42a1d31 100644 --- a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs +++ b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs @@ -1,4 +1,4 @@ -using Penumbra.Api.Enums; +using Penumbra.Api.Enums; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; @@ -16,32 +16,19 @@ public static class EquipmentSwap private static EquipSlot[] ConvertSlots(EquipSlot slot, bool rFinger, bool lFinger) { if (slot != EquipSlot.RFinger) - return new[] - { - slot, - }; + return [slot]; return rFinger ? lFinger - ? new[] - { - EquipSlot.RFinger, - EquipSlot.LFinger, - } - : new[] - { - EquipSlot.RFinger, - } + ? [EquipSlot.RFinger, EquipSlot.LFinger] + : [EquipSlot.RFinger] : lFinger - ? new[] - { - EquipSlot.LFinger, - } - : Array.Empty(); + ? [EquipSlot.LFinger] + : []; } public static EquipItem[] CreateTypeSwap(MetaFileManager manager, ObjectIdentification identifier, List swaps, - Func redirections, Func manips, + Func redirections, MetaDictionary manips, EquipSlot slotFrom, EquipItem itemFrom, EquipSlot slotTo, EquipItem itemTo) { LookupItem(itemFrom, out var actualSlotFrom, out var idFrom, out var variantFrom); @@ -50,11 +37,14 @@ public static EquipItem[] CreateTypeSwap(MetaFileManager manager, ObjectIdentifi throw new ItemSwap.InvalidItemTypeException(); var (imcFileFrom, variants, affectedItems) = GetVariants(manager, identifier, slotFrom, idFrom, idTo, variantFrom); - var imcManip = new ImcManipulation(slotTo, variantTo.Id, idTo.Id, default); - var imcFileTo = new ImcFile(manager, imcManip.Identifier); + var imcIdentifierTo = new ImcIdentifier(slotTo, idTo, variantTo); + var imcFileTo = new ImcFile(manager, imcIdentifierTo); + var imcEntry = manips.TryGetValue(imcIdentifierTo, out var entry) + ? entry + : imcFileTo.GetEntry(imcIdentifierTo.EquipSlot, imcIdentifierTo.Variant); + var mtrlVariantTo = imcEntry.MaterialId; var skipFemale = false; var skipMale = false; - var mtrlVariantTo = manips(imcManip.Copy(imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo.Id))).Imc.Entry.MaterialId; foreach (var gr in Enum.GetValues()) { switch (gr.Split().Item1) @@ -99,7 +89,7 @@ public static EquipItem[] CreateTypeSwap(MetaFileManager manager, ObjectIdentifi } public static EquipItem[] CreateItemSwap(MetaFileManager manager, ObjectIdentification identifier, List swaps, - Func redirections, Func manips, EquipItem itemFrom, + Func redirections, MetaDictionary manips, EquipItem itemFrom, EquipItem itemTo, bool rFinger = true, bool lFinger = true) { // Check actual ids, variants and slots. We only support using the same slot. @@ -120,8 +110,12 @@ public static EquipItem[] CreateItemSwap(MetaFileManager manager, ObjectIdentifi foreach (var slot in ConvertSlots(slotFrom, rFinger, lFinger)) { (var imcFileFrom, var variants, affectedItems) = GetVariants(manager, identifier, slot, idFrom, idTo, variantFrom); - var imcManip = new ImcManipulation(slot, variantTo.Id, idTo, default); - var imcFileTo = new ImcFile(manager, imcManip.Identifier); + var imcIdentifierTo = new ImcIdentifier(slotTo, idTo, variantTo); + var imcFileTo = new ImcFile(manager, imcIdentifierTo); + var imcEntry = manips.TryGetValue(imcIdentifierTo, out var entry) + ? entry + : imcFileTo.GetEntry(imcIdentifierTo.EquipSlot, imcIdentifierTo.Variant); + var mtrlVariantTo = imcEntry.MaterialId; var isAccessory = slot.IsAccessory(); var estType = slot switch @@ -133,7 +127,6 @@ public static EquipItem[] CreateItemSwap(MetaFileManager manager, ObjectIdentifi var skipFemale = false; var skipMale = false; - var mtrlVariantTo = manips(imcManip.Copy(imcFileTo.GetEntry(ImcFile.PartIndex(slot), variantTo))).Imc.Entry.MaterialId; foreach (var gr in Enum.GetValues()) { switch (gr.Split().Item1) @@ -154,7 +147,7 @@ public static EquipItem[] CreateItemSwap(MetaFileManager manager, ObjectIdentifi if (eqdp != null) swaps.Add(eqdp); - var ownMdl = eqdp?.SwapApplied.Eqdp.Entry.ToBits(slot).Item2 ?? false; + var ownMdl = eqdp?.SwapToModdedEntry.Model ?? false; var est = ItemSwap.CreateEst(manager, redirections, manips, estType, gr, idFrom, idTo, ownMdl); if (est != null) swaps.Add(est); @@ -184,22 +177,22 @@ public static EquipItem[] CreateItemSwap(MetaFileManager manager, ObjectIdentifi return affectedItems; } - public static MetaSwap? CreateEqdp(MetaFileManager manager, Func redirections, - Func manips, EquipSlot slot, GenderRace gr, PrimaryId idFrom, - PrimaryId idTo, byte mtrlTo) + public static MetaSwap? CreateEqdp(MetaFileManager manager, Func redirections, + MetaDictionary manips, EquipSlot slot, GenderRace gr, PrimaryId idFrom, PrimaryId idTo, byte mtrlTo) => CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo); - public static MetaSwap? CreateEqdp(MetaFileManager manager, Func redirections, - Func manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, PrimaryId idFrom, + public static MetaSwap? CreateEqdp(MetaFileManager manager, Func redirections, + MetaDictionary manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, PrimaryId idFrom, PrimaryId idTo, byte mtrlTo) { - var (gender, race) = gr.Split(); - var eqdpFrom = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotFrom.IsAccessory(), idFrom), slotFrom, gender, - race, idFrom); - var eqdpTo = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotTo.IsAccessory(), idTo), slotTo, gender, race, - idTo); - var meta = new MetaSwap(manips, eqdpFrom, eqdpTo); - var (ownMtrl, ownMdl) = meta.SwapApplied.Eqdp.Entry.ToBits(slotFrom); + var eqdpFromIdentifier = new EqdpIdentifier(idFrom, slotFrom, gr); + var eqdpToIdentifier = new EqdpIdentifier(idFrom, slotFrom, gr); + var eqdpFromDefault = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(manager, eqdpFromIdentifier), slotFrom); + var eqdpToDefault = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(manager, eqdpToIdentifier), slotTo); + var meta = new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, eqdpFromIdentifier, + eqdpFromDefault, eqdpToIdentifier, + eqdpToDefault); + var (ownMtrl, ownMdl) = meta.SwapToModdedEntry; if (ownMdl) { var mdl = CreateMdl(manager, redirections, slotFrom, slotTo, gr, idFrom, idTo, mtrlTo); @@ -270,38 +263,39 @@ private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager man return (imc, variants, items); } - public static MetaSwap? CreateGmp(MetaFileManager manager, Func manips, EquipSlot slot, PrimaryId idFrom, - PrimaryId idTo) + public static MetaSwap? CreateGmp(MetaFileManager manager, MetaDictionary manips, + EquipSlot slot, PrimaryId idFrom, PrimaryId idTo) { if (slot is not EquipSlot.Head) return null; - var manipFrom = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idFrom), idFrom); - var manipTo = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idTo), idTo); - return new MetaSwap(manips, manipFrom, manipTo); + var manipFromIdentifier = new GmpIdentifier(idFrom); + var manipToIdentifier = new GmpIdentifier(idTo); + var manipFromDefault = ExpandedGmpFile.GetDefault(manager, manipFromIdentifier); + var manipToDefault = ExpandedGmpFile.GetDefault(manager, manipToIdentifier); + return new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault); } - public static MetaSwap CreateImc(MetaFileManager manager, Func redirections, - Func manips, EquipSlot slot, - PrimaryId idFrom, PrimaryId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) + public static MetaSwap CreateImc(MetaFileManager manager, Func redirections, + MetaDictionary manips, EquipSlot slot, PrimaryId idFrom, PrimaryId idTo, Variant variantFrom, Variant variantTo, + ImcFile imcFileFrom, ImcFile imcFileTo) => CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo); - public static MetaSwap CreateImc(MetaFileManager manager, Func redirections, - Func manips, - EquipSlot slotFrom, EquipSlot slotTo, PrimaryId idFrom, PrimaryId idTo, + public static MetaSwap CreateImc(MetaFileManager manager, Func redirections, + MetaDictionary manips, EquipSlot slotFrom, EquipSlot slotTo, PrimaryId idFrom, PrimaryId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) { - var entryFrom = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom); - var entryTo = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo); - var manipulationFrom = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, entryFrom); - var manipulationTo = new ImcManipulation(slotTo, variantTo.Id, idTo, entryTo); - var imc = new MetaSwap(manips, manipulationFrom, manipulationTo); + var manipFromIdentifier = new ImcIdentifier(slotFrom, idFrom, variantFrom); + var manipToIdentifier = new ImcIdentifier(slotTo, idTo, variantTo); + var manipFromDefault = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom); + var manipToDefault = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo); + var imc = new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault); - var decal = CreateDecal(manager, redirections, imc.SwapToModded.Imc.Entry.DecalId); + var decal = CreateDecal(manager, redirections, imc.SwapToModdedEntry.DecalId); if (decal != null) imc.ChildSwaps.Add(decal); - var avfx = CreateAvfx(manager, redirections, idFrom, idTo, imc.SwapToModded.Imc.Entry.VfxId); + var avfx = CreateAvfx(manager, redirections, idFrom, idTo, imc.SwapToModdedEntry.VfxId); if (avfx != null) imc.ChildSwaps.Add(avfx); @@ -322,7 +316,8 @@ public static MetaSwap CreateImc(MetaFileManager manager, Func redirections, PrimaryId idFrom, PrimaryId idTo, byte vfxId) + public static FileSwap? CreateAvfx(MetaFileManager manager, Func redirections, PrimaryId idFrom, PrimaryId idTo, + byte vfxId) { if (vfxId == 0) return null; @@ -340,17 +335,18 @@ public static MetaSwap CreateImc(MetaFileManager manager, Func manips, EquipSlot slot, PrimaryId idFrom, - PrimaryId idTo) + public static MetaSwap? CreateEqp(MetaFileManager manager, MetaDictionary manips, + EquipSlot slot, PrimaryId idFrom, PrimaryId idTo) { if (slot.IsAccessory()) return null; - var eqpValueFrom = ExpandedEqpFile.GetDefault(manager, idFrom); - var eqpValueTo = ExpandedEqpFile.GetDefault(manager, idTo); - var eqpFrom = new EqpManipulation(eqpValueFrom, slot, idFrom); - var eqpTo = new EqpManipulation(eqpValueTo, slot, idFrom); - return new MetaSwap(manips, eqpFrom, eqpTo); + var manipFromIdentifier = new EqpIdentifier(idFrom, slot); + var manipToIdentifier = new EqpIdentifier(idTo, slot); + var manipFromDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idFrom), slot); + var manipToDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idTo), slot); + return new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, + manipFromDefault, manipToIdentifier, manipToDefault); } public static FileSwap? CreateMtrl(MetaFileManager manager, Func redirections, EquipSlot slot, PrimaryId idFrom, @@ -397,7 +393,8 @@ public static MetaSwap CreateImc(MetaFileManager manager, Func redirections, char prefix, PrimaryId idFrom, PrimaryId idTo, + public static FileSwap CreateTex(MetaFileManager manager, Func redirections, char prefix, PrimaryId idFrom, + PrimaryId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged) => CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged); diff --git a/Penumbra/Mods/ItemSwap/ItemSwap.cs b/Penumbra/Mods/ItemSwap/ItemSwap.cs index 7fac52c1..efd8080c 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwap.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwap.cs @@ -147,24 +147,23 @@ public static FileSwap CreateSklb(MetaFileManager manager, Func metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. - public static MetaSwap? CreateEst(MetaFileManager manager, Func redirections, - Func manips, EstType type, - GenderRace genderRace, PrimaryId idFrom, PrimaryId idTo, bool ownMdl) + public static MetaSwap? CreateEst(MetaFileManager manager, Func redirections, + MetaDictionary manips, EstType type, GenderRace genderRace, PrimaryId idFrom, PrimaryId idTo, bool ownMdl) { if (type == 0) return null; - var (gender, race) = genderRace.Split(); - var fromDefault = new EstManipulation(gender, race, type, idFrom, EstFile.GetDefault(manager, type, genderRace, idFrom)); - var toDefault = new EstManipulation(gender, race, type, idTo, EstFile.GetDefault(manager, type, genderRace, idTo)); - var est = new MetaSwap(manips, fromDefault, toDefault); + var manipFromIdentifier = new EstIdentifier(idFrom, type, genderRace); + var manipToIdentifier = new EstIdentifier(idTo, type, genderRace); + var manipFromDefault = EstFile.GetDefault(manager, manipFromIdentifier); + var manipToDefault = EstFile.GetDefault(manager, manipToIdentifier); + var est = new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault); - if (ownMdl && est.SwapApplied.Est.Entry.Value >= 2) + if (ownMdl && est.SwapToModdedEntry.Value >= 2) { - var phyb = CreatePhyb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry); + var phyb = CreatePhyb(manager, redirections, type, genderRace, est.SwapToModdedEntry); est.ChildSwaps.Add(phyb); - var sklb = CreateSklb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry); + var sklb = CreateSklb(manager, redirections, type, genderRace, est.SwapToModdedEntry); est.ChildSwaps.Add(sklb); } else if (est.SwapAppliedIsDefault) diff --git a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs index 67a5d007..021ee665 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs @@ -74,9 +74,9 @@ public bool WriteMod(ModManager manager, Mod mod, IModDataContainer container, W } break; - case MetaSwap meta: + case IMetaSwap meta: if (!meta.SwapAppliedIsDefault) - convertedManips.Add(meta.SwapApplied); + convertedManips.Add(meta.SwapFromIdentifier, meta.SwapToModdedEntry); break; } @@ -116,17 +116,10 @@ private Func PathResolver(ModCollection? collection) ? p => collection.ResolvePath(p) ?? new FullPath(p) : p => ModRedirections.TryGetValue(p, out var path) ? path : new FullPath(p); - private Func MetaResolver(ModCollection? collection) - { - if (collection?.MetaCache?.Manipulations is { } cache) - { - MetaDictionary dict = [.. cache]; - return m => dict.TryGetValue(m, out var a) ? a : m; - } - - var set = _appliedModData.Manipulations; - return m => set.TryGetValue(m, out var a) ? a : m; - } + private MetaDictionary MetaResolver(ModCollection? collection) + => collection?.MetaCache?.Manipulations is { } cache + ? [.. cache] + : _appliedModData.Manipulations; public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true, bool useLeftRing = true) @@ -161,8 +154,8 @@ public bool LoadCustomization(MetaFileManager manager, BodySlot slot, GenderRace _ => (EstType)0, }; - var metaResolver = MetaResolver(collection); - var est = ItemSwap.CreateEst(manager, pathResolver, metaResolver, type, race, from, to, true); + var estResolver = MetaResolver(collection); + var est = ItemSwap.CreateEst(manager, pathResolver, estResolver, type, race, from, to, true); Swaps.Add(mdl); if (est != null) diff --git a/Penumbra/Mods/ItemSwap/Swaps.cs b/Penumbra/Mods/ItemSwap/Swaps.cs index 27935ffb..36c54203 100644 --- a/Penumbra/Mods/ItemSwap/Swaps.cs +++ b/Penumbra/Mods/ItemSwap/Swaps.cs @@ -1,58 +1,91 @@ -using Penumbra.Api.Enums; +using Penumbra.Api.Enums; using Penumbra.GameData.Files; using Penumbra.Meta.Manipulations; using Penumbra.String.Classes; using Penumbra.Meta; using static Penumbra.Mods.ItemSwap.ItemSwap; -using Penumbra.Services; namespace Penumbra.Mods.ItemSwap; public class Swap { /// Any further swaps belonging specifically to this tree of changes. - public readonly List ChildSwaps = new(); + public readonly List ChildSwaps = []; public IEnumerable WithChildren() => ChildSwaps.SelectMany(c => c.WithChildren()).Prepend(this); } -public sealed class MetaSwap : Swap +public interface IMetaSwap { + public IMetaIdentifier SwapFromIdentifier { get; } + public IMetaIdentifier SwapToIdentifier { get; } + + public object SwapFromDefaultEntry { get; } + public object SwapToDefaultEntry { get; } + public object SwapToModdedEntry { get; } + + public bool SwapToIsDefault { get; } + public bool SwapAppliedIsDefault { get; } +} + +public sealed class MetaSwap : Swap, IMetaSwap + where TIdentifier : unmanaged, IMetaIdentifier + where TEntry : unmanaged, IEquatable +{ + public TIdentifier SwapFromIdentifier; + public TIdentifier SwapToIdentifier; + /// The default value of a specific meta manipulation that needs to be redirected. - public MetaManipulation SwapFrom; + public TEntry SwapFromDefaultEntry; /// The default value of the same Meta entry of the redirected item. - public MetaManipulation SwapToDefault; + public TEntry SwapToDefaultEntry; /// The modded value of the same Meta entry of the redirected item, or the same as SwapToDefault if unmodded. - public MetaManipulation SwapToModded; - - /// The modded value applied to the specific meta manipulation target before redirection. - public MetaManipulation SwapApplied; + public TEntry SwapToModdedEntry; - /// Whether SwapToModded equals SwapToDefault. - public bool SwapToIsDefault; + /// Whether SwapToModdedEntry equals SwapToDefaultEntry. + public bool SwapToIsDefault { get; } /// Whether the applied meta manipulation does not change anything against the default. - public bool SwapAppliedIsDefault; + public bool SwapAppliedIsDefault { get; } /// /// Create a new MetaSwap from the original meta identifier and the target meta identifier. /// - /// A function that converts the given manipulation to the modded one. - /// The original meta identifier with its default value. - /// The target meta identifier with its default value. - public MetaSwap(Func manipulations, MetaManipulation manipFrom, MetaManipulation manipTo) + /// A function that obtains a modded meta entry if it exists. + /// The original meta identifier. + /// The default value for the original meta identifier. + /// The target meta identifier. + /// The default value for the target meta identifier. + public MetaSwap(Func manipulations, TIdentifier manipFromIdentifier, TEntry manipFromEntry, + TIdentifier manipToIdentifier, TEntry manipToEntry) { - SwapFrom = manipFrom; - SwapToDefault = manipTo; - - SwapToModded = manipulations(manipTo); - SwapToIsDefault = manipTo.EntryEquals(SwapToModded); - SwapApplied = SwapFrom.WithEntryOf(SwapToModded); - SwapAppliedIsDefault = SwapApplied.EntryEquals(SwapFrom); + SwapFromIdentifier = manipFromIdentifier; + SwapToIdentifier = manipToIdentifier; + SwapFromDefaultEntry = manipFromEntry; + SwapToDefaultEntry = manipToEntry; + + SwapToModdedEntry = manipulations(SwapToIdentifier) ?? SwapToDefaultEntry; + SwapToIsDefault = SwapToModdedEntry.Equals(SwapToDefaultEntry); + SwapAppliedIsDefault = SwapToModdedEntry.Equals(SwapFromDefaultEntry); } + + IMetaIdentifier IMetaSwap.SwapFromIdentifier + => SwapFromIdentifier; + + IMetaIdentifier IMetaSwap.SwapToIdentifier + => SwapToIdentifier; + + object IMetaSwap.SwapFromDefaultEntry + => SwapFromDefaultEntry; + + object IMetaSwap.SwapToDefaultEntry + => SwapToDefaultEntry; + + object IMetaSwap.SwapToModdedEntry + => SwapToModdedEntry; } public sealed class FileSwap : Swap @@ -113,8 +146,7 @@ public string GetNewPath(string newMod) /// A full swap container with the actual file in memory. /// True if everything could be read correctly, false otherwise. public static FileSwap CreateSwap(MetaFileManager manager, ResourceType type, Func redirections, - string swapFromRequest, string swapToRequest, - string? swapFromPreChange = null) + string swapFromRequest, string swapToRequest, string? swapFromPreChange = null) { var swap = new FileSwap { diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index 6010cdaf..62115dd6 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -240,7 +240,7 @@ private static string SwapToString(Swap swap) { return swap switch { - MetaSwap meta => $"{meta.SwapFrom}: {meta.SwapFrom.EntryToString()} -> {meta.SwapApplied.EntryToString()}", + IMetaSwap meta => $"{meta.SwapFromIdentifier}: {meta.SwapFromDefaultEntry} -> {meta.SwapToModdedEntry}", FileSwap file => $"{file.Type}: {file.SwapFromRequestPath} -> {file.SwapToModded.FullName}{(file.DataWasChanged ? " (EDITED)" : string.Empty)}", _ => string.Empty, @@ -410,7 +410,7 @@ private void DrawSwapBar() private ImRaii.IEndObject DrawTab(SwapType newTab) { - using var tab = ImRaii.TabItem(newTab is SwapType.BetweenSlots ? "Between Slots" : newTab.ToString()); + var tab = ImRaii.TabItem(newTab is SwapType.BetweenSlots ? "Between Slots" : newTab.ToString()); if (tab) { _dirty |= _lastTab != newTab; From d9b63320f07b5600bdd71b3420fd740e0f44e6d4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 8 Jun 2024 20:46:29 +0200 Subject: [PATCH 07/28] Some small fixes, parse directly into MetaDictionary. --- Penumbra/Api/Api/TemporaryApi.cs | 24 ++++--------------- Penumbra/Api/IpcTester/TemporaryIpcTester.cs | 3 ++- Penumbra/Meta/Manipulations/MetaDictionary.cs | 16 ++++++------- Penumbra/Mods/ItemSwap/ItemSwapContainer.cs | 2 +- Penumbra/UI/Tabs/Debug/DebugTab.cs | 2 +- 5 files changed, 17 insertions(+), 30 deletions(-) diff --git a/Penumbra/Api/Api/TemporaryApi.cs b/Penumbra/Api/Api/TemporaryApi.cs index 995ec388..49958a0d 100644 --- a/Penumbra/Api/Api/TemporaryApi.cs +++ b/Penumbra/Api/Api/TemporaryApi.cs @@ -159,8 +159,7 @@ private static bool ConvertPaths(IReadOnlyDictionary redirection /// The empty string is treated as an empty set. /// Only returns true if all conversions are successful and distinct. /// - private static bool ConvertManips(string manipString, - [NotNullWhen(true)] out MetaDictionary? manips) + private static bool ConvertManips(string manipString, [NotNullWhen(true)] out MetaDictionary? manips) { if (manipString.Length == 0) { @@ -168,23 +167,10 @@ private static bool ConvertManips(string manipString, return true; } - if (Functions.FromCompressedBase64(manipString, out var manipArray) != MetaManipulation.CurrentVersion) - { - manips = null; - return false; - } - - manips = []; - foreach (var manip in manipArray!.Where(m => m.Validate())) - { - if (manips.Add(manip)) - continue; - - Penumbra.Log.Warning($"Manipulation {manip} {manip.EntryToString()} is invalid and was skipped."); - manips = null; - return false; - } + if (Functions.FromCompressedBase64(manipString, out manips!) == MetaManipulation.CurrentVersion) + return true; - return true; + manips = null; + return false; } } diff --git a/Penumbra/Api/IpcTester/TemporaryIpcTester.cs b/Penumbra/Api/IpcTester/TemporaryIpcTester.cs index a8405eb2..15601867 100644 --- a/Penumbra/Api/IpcTester/TemporaryIpcTester.cs +++ b/Penumbra/Api/IpcTester/TemporaryIpcTester.cs @@ -4,6 +4,7 @@ using OtterGui; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; using Penumbra.Api.Enums; using Penumbra.Api.IpcSubscribers; using Penumbra.Collections.Manager; @@ -49,7 +50,7 @@ public void Draw() ImGui.InputTextWithHint("##tempMod", "Temporary Mod Name...", ref _tempModName, 32); ImGui.InputTextWithHint("##tempGame", "Game Path...", ref _tempGamePath, 256); ImGui.InputTextWithHint("##tempFile", "File Path...", ref _tempFilePath, 256); - ImGui.InputTextWithHint("##tempManip", "Manipulation Base64 String...", ref _tempManipulation, 256); + ImUtf8.InputText("##tempManip"u8, ref _tempManipulation, "Manipulation Base64 String..."u8); ImGui.Checkbox("Force Character Collection Overwrite", ref _forceOverwrite); using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit); diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs index 51149e3b..3ce54afb 100644 --- a/Penumbra/Meta/Manipulations/MetaDictionary.cs +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -52,16 +52,16 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public bool Add(IMetaIdentifier identifier, object entry) + public bool TryAdd(IMetaIdentifier identifier, object entry) => identifier switch { - EqdpIdentifier eqdpIdentifier => entry is EqdpEntryInternal e && Add(eqdpIdentifier, e), - EqpIdentifier eqpIdentifier => entry is EqpEntryInternal e && Add(eqpIdentifier, e), - EstIdentifier estIdentifier => entry is EstEntry e && Add(estIdentifier, e), - GlobalEqpManipulation globalEqpManipulation => Add(globalEqpManipulation), - GmpIdentifier gmpIdentifier => entry is GmpEntry e && Add(gmpIdentifier, e), - ImcIdentifier imcIdentifier => entry is ImcEntry e && Add(imcIdentifier, e), - RspIdentifier rspIdentifier => entry is RspEntry e && Add(rspIdentifier, e), + EqdpIdentifier eqdpIdentifier => entry is EqdpEntryInternal e && TryAdd(eqdpIdentifier, e), + EqpIdentifier eqpIdentifier => entry is EqpEntryInternal e && TryAdd(eqpIdentifier, e), + EstIdentifier estIdentifier => entry is EstEntry e && TryAdd(estIdentifier, e), + GlobalEqpManipulation globalEqpManipulation => TryAdd(globalEqpManipulation), + GmpIdentifier gmpIdentifier => entry is GmpEntry e && TryAdd(gmpIdentifier, e), + ImcIdentifier imcIdentifier => entry is ImcEntry e && TryAdd(imcIdentifier, e), + RspIdentifier rspIdentifier => entry is RspEntry e && TryAdd(rspIdentifier, e), _ => false, }; diff --git a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs index 021ee665..b0b588b4 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs @@ -76,7 +76,7 @@ public bool WriteMod(ModManager manager, Mod mod, IModDataContainer container, W break; case IMetaSwap meta: if (!meta.SwapAppliedIsDefault) - convertedManips.Add(meta.SwapFromIdentifier, meta.SwapToModdedEntry); + convertedManips.TryAdd(meta.SwapFromIdentifier, meta.SwapToModdedEntry); break; } diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index 1813a7e3..396029d5 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -432,7 +432,7 @@ private unsafe void DrawActorsDebug() foreach (var obj in _objects) { - ImGuiUtil.DrawTableColumn($"{((GameObject*)obj.Address)->ObjectIndex}"); + ImGuiUtil.DrawTableColumn(obj.Address == nint.Zero ? $"{((GameObject*)obj.Address)->ObjectIndex}" : "NULL"); ImGuiUtil.DrawTableColumn($"0x{obj.Address:X}"); ImGuiUtil.DrawTableColumn(obj.Address == nint.Zero ? string.Empty From 196ca2ce393ba160c1bc7c29f9375feeb64f1b1f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 8 Jun 2024 21:15:40 +0200 Subject: [PATCH 08/28] Remove all usages of Add(MetaManipulation) --- Penumbra/Meta/Manipulations/MetaDictionary.cs | 135 +++++++++++------- Penumbra/Mods/ItemSwap/ItemSwapContainer.cs | 56 ++++---- Penumbra/Mods/SubMods/SubMod.cs | 6 +- Penumbra/Mods/TemporaryMod.cs | 4 +- 4 files changed, 121 insertions(+), 80 deletions(-) diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs index 3ce54afb..1dc6496e 100644 --- a/Penumbra/Meta/Manipulations/MetaDictionary.cs +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -52,41 +52,19 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public bool TryAdd(IMetaIdentifier identifier, object entry) - => identifier switch - { - EqdpIdentifier eqdpIdentifier => entry is EqdpEntryInternal e && TryAdd(eqdpIdentifier, e), - EqpIdentifier eqpIdentifier => entry is EqpEntryInternal e && TryAdd(eqpIdentifier, e), - EstIdentifier estIdentifier => entry is EstEntry e && TryAdd(estIdentifier, e), - GlobalEqpManipulation globalEqpManipulation => TryAdd(globalEqpManipulation), - GmpIdentifier gmpIdentifier => entry is GmpEntry e && TryAdd(gmpIdentifier, e), - ImcIdentifier imcIdentifier => entry is ImcEntry e && TryAdd(imcIdentifier, e), - RspIdentifier rspIdentifier => entry is RspEntry e && TryAdd(rspIdentifier, e), - _ => false, - }; - - public bool Add(MetaManipulation manip) + public bool TryAdd(ImcIdentifier identifier, ImcEntry entry) { - var ret = manip.ManipulationType switch - { - MetaManipulation.Type.Imc => _imc.TryAdd(manip.Imc.Identifier, manip.Imc.Entry), - MetaManipulation.Type.Eqdp => _eqdp.TryAdd(manip.Eqdp.Identifier, new EqdpEntryInternal(manip.Eqdp.Entry, manip.Eqdp.Slot)), - MetaManipulation.Type.Eqp => _eqp.TryAdd(manip.Eqp.Identifier, new EqpEntryInternal(manip.Eqp.Entry, manip.Eqp.Slot)), - MetaManipulation.Type.Est => _est.TryAdd(manip.Est.Identifier, manip.Est.Entry), - MetaManipulation.Type.Gmp => _gmp.TryAdd(manip.Gmp.Identifier, manip.Gmp.Entry), - MetaManipulation.Type.Rsp => _rsp.TryAdd(manip.Rsp.Identifier, manip.Rsp.Entry), - MetaManipulation.Type.GlobalEqp => _globalEqp.Add(manip.GlobalEqp), - _ => false, - }; - - if (ret) - ++Count; - return ret; + if (!_imc.TryAdd(identifier, entry)) + return false; + + ++Count; + return true; } - public bool TryAdd(ImcIdentifier identifier, ImcEntry entry) + + public bool TryAdd(EqpIdentifier identifier, EqpEntryInternal entry) { - if (!_imc.TryAdd(identifier, entry)) + if (!_eqp.TryAdd(identifier, entry)) return false; ++Count; @@ -96,6 +74,16 @@ public bool TryAdd(ImcIdentifier identifier, ImcEntry entry) public bool TryAdd(EqpIdentifier identifier, EqpEntry entry) => TryAdd(identifier, new EqpEntryInternal(entry, identifier.Slot)); + + public bool TryAdd(EqdpIdentifier identifier, EqdpEntryInternal entry) + { + if (!_eqdp.TryAdd(identifier, entry)) + return false; + + ++Count; + return true; + } + public bool TryAdd(EqdpIdentifier identifier, EqdpEntry entry) => TryAdd(identifier, new EqdpEntryInternal(entry, identifier.Slot)); @@ -159,6 +147,73 @@ public void UnionWith(MetaDictionary manips) TryAdd(identifier); } + /// Try to merge all manipulations from manips into this, and return the first failure, if any. + public bool MergeForced(MetaDictionary manips, out IMetaIdentifier failedIdentifier) + { + foreach (var (identifier, entry) in manips._imc) + { + if (!TryAdd(identifier, entry)) + { + failedIdentifier = identifier; + return false; + } + } + + foreach (var (identifier, entry) in manips._eqp) + { + if (!TryAdd(identifier, entry)) + { + failedIdentifier = identifier; + return false; + } + } + + foreach (var (identifier, entry) in manips._eqdp) + { + if (!TryAdd(identifier, entry)) + { + failedIdentifier = identifier; + return false; + } + } + + foreach (var (identifier, entry) in manips._gmp) + { + if (!TryAdd(identifier, entry)) + { + failedIdentifier = identifier; + return false; + } + } + + foreach (var (identifier, entry) in manips._rsp) + { + if (!TryAdd(identifier, entry)) + { + failedIdentifier = identifier; + return false; + } + } + + foreach (var (identifier, entry) in manips._est) + { + if (!TryAdd(identifier, entry)) + { + failedIdentifier = identifier; + return false; + } + } + + foreach (var identifier in manips._globalEqp) + { + if (!TryAdd(identifier)) + { + failedIdentifier = identifier; + return false; + } + } + } + public bool TryGetValue(EstIdentifier identifier, out EstEntry value) => _est.TryGetValue(identifier, out value); @@ -318,22 +373,4 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta return dict; } } - - private bool TryAdd(EqpIdentifier identifier, EqpEntryInternal entry) - { - if (!_eqp.TryAdd(identifier, entry)) - return false; - - ++Count; - return true; - } - - private bool TryAdd(EqdpIdentifier identifier, EqdpEntryInternal entry) - { - if (!_eqdp.TryAdd(identifier, entry)) - return false; - - ++Count; - return true; - } } diff --git a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs index b0b588b4..72a6005d 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs @@ -53,32 +53,38 @@ public bool WriteMod(ModManager manager, Mod mod, IModDataContainer container, W { foreach (var swap in Swaps.SelectMany(s => s.WithChildren())) { - switch (swap) + if (swap is FileSwap file) { - case FileSwap file: - // Skip, nothing to do - if (file.SwapToModdedEqualsOriginal) - continue; - - if (writeType == WriteType.UseSwaps && file.SwapToModdedExistsInGame && !file.DataWasChanged) - { - convertedSwaps.TryAdd(file.SwapFromRequestPath, file.SwapToModded); - } - else - { - var path = file.GetNewPath(directory.FullName); - var bytes = file.FileData.Write(); - Directory.CreateDirectory(Path.GetDirectoryName(path)!); - _manager.Compactor.WriteAllBytes(path, bytes); - convertedFiles.TryAdd(file.SwapFromRequestPath, new FullPath(path)); - } - - break; - case IMetaSwap meta: - if (!meta.SwapAppliedIsDefault) - convertedManips.TryAdd(meta.SwapFromIdentifier, meta.SwapToModdedEntry); - - break; + // Skip, nothing to do + if (file.SwapToModdedEqualsOriginal) + continue; + + if (writeType == WriteType.UseSwaps && file.SwapToModdedExistsInGame && !file.DataWasChanged) + { + convertedSwaps.TryAdd(file.SwapFromRequestPath, file.SwapToModded); + } + else + { + var path = file.GetNewPath(directory.FullName); + var bytes = file.FileData.Write(); + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + _manager.Compactor.WriteAllBytes(path, bytes); + convertedFiles.TryAdd(file.SwapFromRequestPath, new FullPath(path)); + } + } + else if (swap is IMetaSwap { SwapAppliedIsDefault: false }) + { + // @formatter:off + _ = swap switch + { + MetaSwap meta => convertedManips.TryAdd(meta.SwapFromIdentifier, meta.SwapToModdedEntry), + MetaSwap meta => convertedManips.TryAdd(meta.SwapFromIdentifier, meta.SwapToModdedEntry), + MetaSwap meta => convertedManips.TryAdd(meta.SwapFromIdentifier, meta.SwapToModdedEntry), + MetaSwapmeta => convertedManips.TryAdd(meta.SwapFromIdentifier, meta.SwapToModdedEntry), + MetaSwapmeta => convertedManips.TryAdd(meta.SwapFromIdentifier, meta.SwapToModdedEntry), + _ => false, + }; + // @formatter:on } } diff --git a/Penumbra/Mods/SubMods/SubMod.cs b/Penumbra/Mods/SubMods/SubMod.cs index 06a924c8..40fd2e75 100644 --- a/Penumbra/Mods/SubMods/SubMod.cs +++ b/Penumbra/Mods/SubMods/SubMod.cs @@ -64,11 +64,9 @@ public static void LoadDataContainer(JToken json, IModDataContainer data, Direct data.FileSwaps.TryAdd(p, new FullPath(property.Value.ToObject()!)); } - var manips = json[nameof(data.Manipulations)]; + var manips = json[nameof(data.Manipulations)]?.ToObject(); if (manips != null) - foreach (var s in manips.Children().Select(c => c.ToObject()) - .Where(m => m.Validate())) - data.Manipulations.Add(s); + data.Manipulations.UnionWith(manips); } /// Load the relevant data for a selectable option from a JToken of that option. diff --git a/Penumbra/Mods/TemporaryMod.cs b/Penumbra/Mods/TemporaryMod.cs index a715f786..61ed4528 100644 --- a/Penumbra/Mods/TemporaryMod.cs +++ b/Penumbra/Mods/TemporaryMod.cs @@ -93,8 +93,8 @@ public static void SaveTempCollection(Configuration config, SaveService saveServ } } - foreach (var manip in collection.MetaCache?.Manipulations ?? Array.Empty()) - defaultMod.Manipulations.Add(manip); + MetaDictionary manips = [.. collection.MetaCache?.Manipulations ?? []]; + defaultMod.Manipulations.UnionWith(manips); saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport)); modManager.AddMod(dir); From 361082813b59c125949c3a3c092945a2844626e3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 11 Jun 2024 12:23:08 +0200 Subject: [PATCH 09/28] tmp --- Penumbra/Meta/Manipulations/MetaDictionary.cs | 198 +++++-- .../Meta/Manipulations/MetaManipulation.cs | 142 ++++- Penumbra/Mods/Editor/ModMerger.cs | 9 +- Penumbra/Mods/Editor/ModMetaEditor.cs | 167 +----- Penumbra/Mods/Groups/ImcModGroup.cs | 14 +- Penumbra/Mods/ItemSwap/ItemSwapContainer.cs | 2 +- Penumbra/Mods/ModCreator.cs | 6 +- Penumbra/Mods/SubMods/SubMod.cs | 2 +- Penumbra/Mods/TemporaryMod.cs | 3 +- Penumbra/Services/StaticServiceManager.cs | 1 - .../UI/AdvancedWindow/Meta/ImcMetaDrawer.cs | 353 ++++++++++++ .../UI/AdvancedWindow/ModEditWindow.Meta.cs | 514 ++++++++++-------- Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs | 19 +- .../ModsTab/Groups/ImcModGroupEditDrawer.cs | 11 +- Penumbra/UI/ModsTab/ImcManipulationDrawer.cs | 207 +------ Penumbra/Util/DictionaryExtensions.cs | 12 + 16 files changed, 1004 insertions(+), 656 deletions(-) create mode 100644 Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs index 1dc6496e..236157ae 100644 --- a/Penumbra/Meta/Manipulations/MetaDictionary.cs +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -7,7 +7,7 @@ namespace Penumbra.Meta.Manipulations; [JsonConverter(typeof(Converter))] -public sealed class MetaDictionary : IEnumerable +public class MetaDictionary : IEnumerable { private readonly Dictionary _imc = []; private readonly Dictionary _eqp = []; @@ -17,8 +17,37 @@ public sealed class MetaDictionary : IEnumerable private readonly Dictionary _gmp = []; private readonly HashSet _globalEqp = []; + public IReadOnlyDictionary Imc + => _imc; + public int Count { get; private set; } + public int GetCount(MetaManipulation.Type type) + => type switch + { + MetaManipulation.Type.Imc => _imc.Count, + MetaManipulation.Type.Eqdp => _eqdp.Count, + MetaManipulation.Type.Eqp => _eqp.Count, + MetaManipulation.Type.Est => _est.Count, + MetaManipulation.Type.Gmp => _gmp.Count, + MetaManipulation.Type.Rsp => _rsp.Count, + MetaManipulation.Type.GlobalEqp => _globalEqp.Count, + _ => 0, + }; + + public bool CanAdd(IMetaIdentifier identifier) + => identifier switch + { + EqdpIdentifier eqdpIdentifier => !_eqdp.ContainsKey(eqdpIdentifier), + EqpIdentifier eqpIdentifier => !_eqp.ContainsKey(eqpIdentifier), + EstIdentifier estIdentifier => !_est.ContainsKey(estIdentifier), + GlobalEqpManipulation globalEqpManipulation => !_globalEqp.Contains(globalEqpManipulation), + GmpIdentifier gmpIdentifier => !_gmp.ContainsKey(gmpIdentifier), + ImcIdentifier imcIdentifier => !_imc.ContainsKey(imcIdentifier), + RspIdentifier rspIdentifier => !_rsp.ContainsKey(rspIdentifier), + _ => false, + }; + public void Clear() { _imc.Clear(); @@ -123,6 +152,68 @@ public bool TryAdd(GlobalEqpManipulation identifier) return true; } + public bool Update(ImcIdentifier identifier, ImcEntry entry) + { + if (!_imc.ContainsKey(identifier)) + return false; + + _imc[identifier] = entry; + return true; + } + + + public bool Update(EqpIdentifier identifier, EqpEntryInternal entry) + { + if (!_eqp.ContainsKey(identifier)) + return false; + + _eqp[identifier] = entry; + return true; + } + + public bool Update(EqpIdentifier identifier, EqpEntry entry) + => Update(identifier, new EqpEntryInternal(entry, identifier.Slot)); + + + public bool Update(EqdpIdentifier identifier, EqdpEntryInternal entry) + { + if (!_eqdp.ContainsKey(identifier)) + return false; + + _eqdp[identifier] = entry; + return true; + } + + public bool Update(EqdpIdentifier identifier, EqdpEntry entry) + => Update(identifier, new EqdpEntryInternal(entry, identifier.Slot)); + + public bool Update(EstIdentifier identifier, EstEntry entry) + { + if (!_est.ContainsKey(identifier)) + return false; + + _est[identifier] = entry; + return true; + } + + public bool Update(GmpIdentifier identifier, GmpEntry entry) + { + if (!_gmp.ContainsKey(identifier)) + return false; + + _gmp[identifier] = entry; + return true; + } + + public bool Update(RspIdentifier identifier, RspEntry entry) + { + if (!_rsp.ContainsKey(identifier)) + return false; + + _rsp[identifier] = entry; + return true; + } + public void UnionWith(MetaDictionary manips) { foreach (var (identifier, entry) in manips._imc) @@ -148,70 +239,52 @@ public void UnionWith(MetaDictionary manips) } /// Try to merge all manipulations from manips into this, and return the first failure, if any. - public bool MergeForced(MetaDictionary manips, out IMetaIdentifier failedIdentifier) + public bool MergeForced(MetaDictionary manips, out IMetaIdentifier? failedIdentifier) { - foreach (var (identifier, entry) in manips._imc) + foreach (var (identifier, _) in manips._imc.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { - if (!TryAdd(identifier, entry)) - { - failedIdentifier = identifier; - return false; - } + failedIdentifier = identifier; + return false; } - foreach (var (identifier, entry) in manips._eqp) + foreach (var (identifier, _) in manips._eqp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { - if (!TryAdd(identifier, entry)) - { - failedIdentifier = identifier; - return false; - } + failedIdentifier = identifier; + return false; } - foreach (var (identifier, entry) in manips._eqdp) + foreach (var (identifier, _) in manips._eqdp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { - if (!TryAdd(identifier, entry)) - { - failedIdentifier = identifier; - return false; - } + failedIdentifier = identifier; + return false; } - foreach (var (identifier, entry) in manips._gmp) + foreach (var (identifier, _) in manips._gmp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { - if (!TryAdd(identifier, entry)) - { - failedIdentifier = identifier; - return false; - } + failedIdentifier = identifier; + return false; } - foreach (var (identifier, entry) in manips._rsp) + foreach (var (identifier, _) in manips._rsp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { - if (!TryAdd(identifier, entry)) - { - failedIdentifier = identifier; - return false; - } + failedIdentifier = identifier; + return false; } - foreach (var (identifier, entry) in manips._est) + foreach (var (identifier, _) in manips._est.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { - if (!TryAdd(identifier, entry)) - { - failedIdentifier = identifier; - return false; - } + failedIdentifier = identifier; + return false; } - foreach (var identifier in manips._globalEqp) + foreach (var identifier in manips._globalEqp.Where(identifier => !TryAdd(identifier))) { - if (!TryAdd(identifier)) - { - failedIdentifier = identifier; - return false; - } + failedIdentifier = identifier; + return false; } + + failedIdentifier = default; + return false; } public bool TryGetValue(EstIdentifier identifier, out EstEntry value) @@ -244,6 +317,18 @@ public void SetTo(MetaDictionary other) Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _globalEqp.Count; } + public void UpdateTo(MetaDictionary other) + { + _imc.UpdateTo(other._imc); + _eqp.UpdateTo(other._eqp); + _eqdp.UpdateTo(other._eqdp); + _est.UpdateTo(other._est); + _rsp.UpdateTo(other._rsp); + _gmp.UpdateTo(other._gmp); + _globalEqp.UnionWith(other._globalEqp); + Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _globalEqp.Count; + } + public MetaDictionary Clone() { var ret = new MetaDictionary(); @@ -251,6 +336,31 @@ public MetaDictionary Clone() return ret; } + private static void WriteJson(JsonWriter writer, JsonSerializer serializer, IMetaIdentifier identifier, object entry) + { + var type = identifier switch + { + ImcIdentifier => "Imc", + EqdpIdentifier => "Eqdp", + EqpIdentifier => "Eqp", + EstIdentifier => "Est", + GmpIdentifier => "Gmp", + RspIdentifier => "Rsp", + GlobalEqpManipulation => "GlobalEqp", + _ => string.Empty, + }; + + if (type.Length == 0) + return; + + writer.WriteStartObject(); + writer.WritePropertyName("Type"); + writer.WriteValue(type); + writer.WritePropertyName("Manipulation"); + + writer.WriteEndObject(); + } + private class Converter : JsonConverter { public override void WriteJson(JsonWriter writer, MetaDictionary? value, JsonSerializer serializer) diff --git a/Penumbra/Meta/Manipulations/MetaManipulation.cs b/Penumbra/Meta/Manipulations/MetaManipulation.cs index f22de809..b80681d2 100644 --- a/Penumbra/Meta/Manipulations/MetaManipulation.cs +++ b/Penumbra/Meta/Manipulations/MetaManipulation.cs @@ -1,9 +1,148 @@ +using Dalamud.Interface; +using ImGuiNET; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using OtterGui; +using Penumbra.GameData.Enums; using Penumbra.Interop.Structs; +using Penumbra.Mods.Editor; using Penumbra.String.Functions; +using Penumbra.UI; +using Penumbra.UI.ModsTab; -namespace Penumbra.Meta.Manipulations; +namespace Penumbra.Meta.Manipulations; + +#if false +private static class ImcRow +{ + private static ImcIdentifier _newIdentifier = ImcIdentifier.Default; + + private static float IdWidth + => 80 * UiHelpers.Scale; + + private static float SmallIdWidth + => 45 * UiHelpers.Scale; + + public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) + { + ImGui.TableNextColumn(); + CopyToClipboardButton("Copy all current IMC manipulations to clipboard.", iconSize, + editor.MetaEditor.Imc.Select(m => (MetaManipulation)m)); + ImGui.TableNextColumn(); + var (defaultEntry, fileExists, _) = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true); + var manip = (MetaManipulation)new ImcManipulation(_newIdentifier, defaultEntry); + var canAdd = fileExists && editor.MetaEditor.CanAdd(manip); + var tt = canAdd ? "Stage this edit." : !fileExists ? "This IMC file does not exist." : "This entry is already edited."; + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) + editor.MetaEditor.Add(manip); + + // Identifier + ImGui.TableNextColumn(); + var change = ImcManipulationDrawer.DrawObjectType(ref _newIdentifier); + + ImGui.TableNextColumn(); + change |= ImcManipulationDrawer.DrawPrimaryId(ref _newIdentifier); + 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 (_newIdentifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory) + change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier); + else + change |= ImcManipulationDrawer.DrawSecondaryId(ref _newIdentifier); + + ImGui.TableNextColumn(); + change |= ImcManipulationDrawer.DrawVariant(ref _newIdentifier); + + ImGui.TableNextColumn(); + if (_newIdentifier.ObjectType is ObjectType.DemiHuman) + change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier, 70); + else + ImUtf8.ScaledDummy(new Vector2(70 * UiHelpers.Scale, 0)); + + if (change) + defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true).Entry; + // Values + using var disabled = ImRaii.Disabled(); + ImGui.TableNextColumn(); + ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref defaultEntry, false); + ImGui.SameLine(); + ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref defaultEntry, false); + ImGui.TableNextColumn(); + ImcManipulationDrawer.DrawDecalId(defaultEntry, ref defaultEntry, false); + ImGui.SameLine(); + ImcManipulationDrawer.DrawVfxId(defaultEntry, ref defaultEntry, false); + ImGui.SameLine(); + ImcManipulationDrawer.DrawSoundId(defaultEntry, ref defaultEntry, false); + ImGui.TableNextColumn(); + ImcManipulationDrawer.DrawAttributes(defaultEntry, ref defaultEntry); + } + + public static void Draw(MetaFileManager metaFileManager, ImcManipulation meta, ModEditor editor, Vector2 iconSize) + { + DrawMetaButtons(meta, editor, iconSize); + + // Identifier + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); + ImGui.TextUnformatted(meta.ObjectType.ToName()); + ImGuiUtil.HoverTooltip(ObjectTypeTooltip); + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); + ImGui.TextUnformatted(meta.PrimaryId.ToString()); + ImGuiUtil.HoverTooltip(PrimaryIdTooltipShort); + + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); + if (meta.ObjectType is ObjectType.Equipment or ObjectType.Accessory) + { + ImGui.TextUnformatted(meta.EquipSlot.ToName()); + ImGuiUtil.HoverTooltip(EquipSlotTooltip); + } + else + { + ImGui.TextUnformatted(meta.SecondaryId.ToString()); + ImGuiUtil.HoverTooltip(SecondaryIdTooltip); + } + + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); + ImGui.TextUnformatted(meta.Variant.ToString()); + ImGuiUtil.HoverTooltip(VariantIdTooltip); + + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); + if (meta.ObjectType is ObjectType.DemiHuman) + { + ImGui.TextUnformatted(meta.EquipSlot.ToName()); + ImGuiUtil.HoverTooltip(EquipSlotTooltip); + } + + // Values + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, + new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y)); + ImGui.TableNextColumn(); + var defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(meta.Identifier, true).Entry; + var newEntry = meta.Entry; + var changes = ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref newEntry, true); + ImGui.SameLine(); + changes |= ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref newEntry, true); + ImGui.TableNextColumn(); + changes |= ImcManipulationDrawer.DrawDecalId(defaultEntry, ref newEntry, true); + ImGui.SameLine(); + changes |= ImcManipulationDrawer.DrawVfxId(defaultEntry, ref newEntry, true); + ImGui.SameLine(); + changes |= ImcManipulationDrawer.DrawSoundId(defaultEntry, ref newEntry, true); + ImGui.TableNextColumn(); + changes |= ImcManipulationDrawer.DrawAttributes(defaultEntry, ref newEntry); + + if (changes) + editor.MetaEditor.Change(meta.Copy(newEntry)); + } +} + +#endif public interface IMetaManipulation { @@ -315,3 +454,4 @@ public string EntryToString() public static bool operator >=(MetaManipulation left, MetaManipulation right) => left.CompareTo(right) >= 0; } + diff --git a/Penumbra/Mods/Editor/ModMerger.cs b/Penumbra/Mods/Editor/ModMerger.cs index 4faced80..2df76838 100644 --- a/Penumbra/Mods/Editor/ModMerger.cs +++ b/Penumbra/Mods/Editor/ModMerger.cs @@ -162,12 +162,9 @@ private void MergeIntoOption(IEnumerable mergeOptions, IModDa foreach (var originalOption in mergeOptions) { - foreach (var manip in originalOption.Manipulations) - { - if (!manips.Add(manip)) - throw new Exception( - $"Could not add meta manipulation {manip} from {originalOption.GetFullName()} to {option.GetFullName()} because another manipulation of the same data already exists in this option."); - } + if (!manips.MergeForced(originalOption.Manipulations, out var failed)) + throw new Exception( + $"Could not add meta manipulation {failed} from {originalOption.GetFullName()} to {option.GetFullName()} because another manipulation of the same data already exists in this option."); foreach (var (swapA, swapB) in originalOption.FileSwaps) { diff --git a/Penumbra/Mods/Editor/ModMetaEditor.cs b/Penumbra/Mods/Editor/ModMetaEditor.cs index 45d9f8a1..42171378 100644 --- a/Penumbra/Mods/Editor/ModMetaEditor.cs +++ b/Penumbra/Mods/Editor/ModMetaEditor.cs @@ -1,24 +1,24 @@ using System.Collections.Frozen; +using OtterGui.Services; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Manager; using Penumbra.Mods.SubMods; namespace Penumbra.Mods.Editor; -public class ModMetaEditor(ModManager modManager) +public class ModMetaEditor(ModManager modManager) : MetaDictionary, IService { - private readonly HashSet _imc = []; - private readonly HashSet _eqp = []; - private readonly HashSet _eqdp = []; - private readonly HashSet _gmp = []; - private readonly HashSet _est = []; - private readonly HashSet _rsp = []; - private readonly HashSet _globalEqp = []; - public sealed class OtherOptionData : HashSet { public int TotalCount; + public void Add(string name, int count) + { + if (count > 0) + Add(name); + TotalCount += count; + } + public new void Clear() { TotalCount = 0; @@ -31,91 +31,9 @@ public sealed class OtherOptionData : HashSet public bool Changes { get; private set; } - public IReadOnlySet Imc - => _imc; - - public IReadOnlySet Eqp - => _eqp; - - public IReadOnlySet Eqdp - => _eqdp; - - public IReadOnlySet Gmp - => _gmp; - - public IReadOnlySet Est - => _est; - - public IReadOnlySet Rsp - => _rsp; - - public IReadOnlySet GlobalEqp - => _globalEqp; - - public bool CanAdd(MetaManipulation m) - { - return m.ManipulationType switch - { - MetaManipulation.Type.Imc => !_imc.Contains(m.Imc), - MetaManipulation.Type.Eqdp => !_eqdp.Contains(m.Eqdp), - MetaManipulation.Type.Eqp => !_eqp.Contains(m.Eqp), - MetaManipulation.Type.Est => !_est.Contains(m.Est), - MetaManipulation.Type.Gmp => !_gmp.Contains(m.Gmp), - MetaManipulation.Type.Rsp => !_rsp.Contains(m.Rsp), - MetaManipulation.Type.GlobalEqp => !_globalEqp.Contains(m.GlobalEqp), - _ => false, - }; - } - - public bool Add(MetaManipulation m) - { - var added = m.ManipulationType switch - { - MetaManipulation.Type.Imc => _imc.Add(m.Imc), - MetaManipulation.Type.Eqdp => _eqdp.Add(m.Eqdp), - MetaManipulation.Type.Eqp => _eqp.Add(m.Eqp), - MetaManipulation.Type.Est => _est.Add(m.Est), - MetaManipulation.Type.Gmp => _gmp.Add(m.Gmp), - MetaManipulation.Type.Rsp => _rsp.Add(m.Rsp), - MetaManipulation.Type.GlobalEqp => _globalEqp.Add(m.GlobalEqp), - _ => false, - }; - Changes |= added; - return added; - } - - public bool Delete(MetaManipulation m) + public new void Clear() { - var deleted = m.ManipulationType switch - { - MetaManipulation.Type.Imc => _imc.Remove(m.Imc), - MetaManipulation.Type.Eqdp => _eqdp.Remove(m.Eqdp), - MetaManipulation.Type.Eqp => _eqp.Remove(m.Eqp), - MetaManipulation.Type.Est => _est.Remove(m.Est), - MetaManipulation.Type.Gmp => _gmp.Remove(m.Gmp), - MetaManipulation.Type.Rsp => _rsp.Remove(m.Rsp), - MetaManipulation.Type.GlobalEqp => _globalEqp.Remove(m.GlobalEqp), - _ => false, - }; - Changes |= deleted; - return deleted; - } - - public bool Change(MetaManipulation m) - => Delete(m) && Add(m); - - public bool Set(MetaManipulation m) - => Delete(m) | Add(m); - - public void Clear() - { - _imc.Clear(); - _eqp.Clear(); - _eqdp.Clear(); - _gmp.Clear(); - _est.Clear(); - _rsp.Clear(); - _globalEqp.Clear(); + base.Clear(); Changes = true; } @@ -129,15 +47,19 @@ public void Load(Mod mod, IModDataContainer currentOption) if (option == currentOption) continue; - foreach (var manip in option.Manipulations) - { - var data = OtherData[manip.ManipulationType]; - ++data.TotalCount; - data.Add(option.GetFullName()); - } + var name = option.GetFullName(); + OtherData[MetaManipulation.Type.Imc].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Imc)); + OtherData[MetaManipulation.Type.Eqp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Eqp)); + OtherData[MetaManipulation.Type.Eqdp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Eqdp)); + OtherData[MetaManipulation.Type.Gmp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Gmp)); + OtherData[MetaManipulation.Type.Est].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Est)); + OtherData[MetaManipulation.Type.Rsp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Rsp)); + OtherData[MetaManipulation.Type.GlobalEqp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.GlobalEqp)); } - Split(currentOption.Manipulations); + Clear(); + UnionWith(currentOption.Manipulations); + Changes = false; } public void Apply(IModDataContainer container) @@ -145,50 +67,7 @@ public void Apply(IModDataContainer container) if (!Changes) return; - modManager.OptionEditor.SetManipulations(container, [..Recombine()]); + modManager.OptionEditor.SetManipulations(container, this); Changes = false; } - - private void Split(IEnumerable manips) - { - Clear(); - foreach (var manip in manips) - { - switch (manip.ManipulationType) - { - case MetaManipulation.Type.Imc: - _imc.Add(manip.Imc); - break; - case MetaManipulation.Type.Eqdp: - _eqdp.Add(manip.Eqdp); - break; - case MetaManipulation.Type.Eqp: - _eqp.Add(manip.Eqp); - break; - case MetaManipulation.Type.Est: - _est.Add(manip.Est); - break; - case MetaManipulation.Type.Gmp: - _gmp.Add(manip.Gmp); - break; - case MetaManipulation.Type.Rsp: - _rsp.Add(manip.Rsp); - break; - case MetaManipulation.Type.GlobalEqp: - _globalEqp.Add(manip.GlobalEqp); - break; - } - } - - Changes = false; - } - - public IEnumerable Recombine() - => _imc.Select(m => (MetaManipulation)m) - .Concat(_eqdp.Select(m => (MetaManipulation)m)) - .Concat(_eqp.Select(m => (MetaManipulation)m)) - .Concat(_est.Select(m => (MetaManipulation)m)) - .Concat(_gmp.Select(m => (MetaManipulation)m)) - .Concat(_rsp.Select(m => (MetaManipulation)m)) - .Concat(_globalEqp.Select(m => (MetaManipulation)m)); } diff --git a/Penumbra/Mods/Groups/ImcModGroup.cs b/Penumbra/Mods/Groups/ImcModGroup.cs index 383bc9fd..c52828c0 100644 --- a/Penumbra/Mods/Groups/ImcModGroup.cs +++ b/Penumbra/Mods/Groups/ImcModGroup.cs @@ -95,28 +95,28 @@ public int GetIndex() public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer) => new ImcModGroupEditDrawer(editDrawer, this); - public ImcManipulation GetManip(ushort mask, Variant variant) - => new(Identifier.ObjectType, Identifier.BodySlot, Identifier.PrimaryId, Identifier.SecondaryId.Id, variant.Id, - Identifier.EquipSlot, DefaultEntry with { AttributeMask = mask }); + public ImcEntry GetEntry(ushort mask) + => DefaultEntry with { AttributeMask = mask }; public void AddData(Setting setting, Dictionary redirections, MetaDictionary manipulations) { if (IsDisabled(setting)) return; - var mask = GetCurrentMask(setting); + var mask = GetCurrentMask(setting); + var entry = GetEntry(mask); if (AllVariants) { var count = ImcChecker.GetVariantCount(Identifier); if (count == 0) - manipulations.Add(GetManip(mask, Identifier.Variant)); + manipulations.TryAdd(Identifier, entry); else for (var i = 0; i <= count; ++i) - manipulations.Add(GetManip(mask, (Variant)i)); + manipulations.TryAdd(Identifier with { Variant = (Variant)i }, entry); } else { - manipulations.Add(GetManip(mask, Identifier.Variant)); + manipulations.TryAdd(Identifier, entry); } } diff --git a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs index 72a6005d..8328edea 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs @@ -124,7 +124,7 @@ private Func PathResolver(ModCollection? collection) private MetaDictionary MetaResolver(ModCollection? collection) => collection?.MetaCache?.Manipulations is { } cache - ? [.. cache] + ? [] // [.. cache] TODO : _appliedModData.Manipulations; public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true, diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index 0035fd41..e8ca3199 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -198,7 +198,8 @@ public void IncorporateAllMetaChanges(Mod mod, bool delete) Penumbra.Log.Verbose( $"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}"); deleteList.Add(file.FullName); - option.Manipulations.UnionWith([.. meta.MetaManipulations]); + // TODO + option.Manipulations.UnionWith([]);//[.. meta.MetaManipulations]); } else if (ext1 == ".rgsp" || ext2 == ".rgsp") { @@ -212,7 +213,8 @@ public void IncorporateAllMetaChanges(Mod mod, bool delete) $"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}"); deleteList.Add(file.FullName); - option.Manipulations.UnionWith([.. rgsp.MetaManipulations]); + // TODO + option.Manipulations.UnionWith([]);//[.. rgsp.MetaManipulations]); } } catch (Exception e) diff --git a/Penumbra/Mods/SubMods/SubMod.cs b/Penumbra/Mods/SubMods/SubMod.cs index 40fd2e75..a8c37369 100644 --- a/Penumbra/Mods/SubMods/SubMod.cs +++ b/Penumbra/Mods/SubMods/SubMod.cs @@ -37,7 +37,7 @@ public static void Clone(IModDataContainer from, IModDataContainer to) { to.Files = new Dictionary(from.Files); to.FileSwaps = new Dictionary(from.FileSwaps); - to.Manipulations = [.. from.Manipulations]; + to.Manipulations = from.Manipulations.Clone(); } /// Load all file redirections, file swaps and meta manipulations from a JToken of that option into a data container. diff --git a/Penumbra/Mods/TemporaryMod.cs b/Penumbra/Mods/TemporaryMod.cs index 61ed4528..91c4c5df 100644 --- a/Penumbra/Mods/TemporaryMod.cs +++ b/Penumbra/Mods/TemporaryMod.cs @@ -93,7 +93,8 @@ public static void SaveTempCollection(Configuration config, SaveService saveServ } } - MetaDictionary manips = [.. collection.MetaCache?.Manipulations ?? []]; + // TODO + MetaDictionary manips = []; // [.. collection.MetaCache?.Manipulations ?? []]; defaultMod.Manipulations.UnionWith(manips); saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport)); diff --git a/Penumbra/Services/StaticServiceManager.cs b/Penumbra/Services/StaticServiceManager.cs index 0c6648ba..35e36349 100644 --- a/Penumbra/Services/StaticServiceManager.cs +++ b/Penumbra/Services/StaticServiceManager.cs @@ -188,7 +188,6 @@ private static ServiceManager AddModEditor(this ServiceManager services) .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs new file mode 100644 index 00000000..d9a8c27c --- /dev/null +++ b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs @@ -0,0 +1,353 @@ +using Dalamud.Interface; +using ImGuiNET; +using OtterGui.Raii; +using OtterGui.Services; +using OtterGui.Text; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Penumbra.Meta; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods.Editor; +using Penumbra.UI.Classes; + +namespace Penumbra.UI.AdvancedWindow.Meta; + +public sealed class ImcMetaDrawer(ModEditor editor, MetaFileManager metaFiles) + : MetaDrawer(editor, metaFiles), IService +{ + private bool _fileExists; + + private const string ModelSetIdTooltipShort = "Model Set ID"; + private const string EquipSlotTooltip = "Equip Slot"; + private const string ModelRaceTooltip = "Model Race"; + private const string GenderTooltip = "Gender"; + private const string ObjectTypeTooltip = "Object Type"; + private const string SecondaryIdTooltip = "Secondary ID"; + private const string PrimaryIdTooltipShort = "Primary ID"; + private const string VariantIdTooltip = "Variant ID"; + private const string EstTypeTooltip = "EST Type"; + private const string RacialTribeTooltip = "Racial Tribe"; + private const string ScalingTypeTooltip = "Scaling Type"; + + protected override void Initialize() + { + Identifier = ImcIdentifier.Default; + UpdateEntry(); + } + + private void UpdateEntry() + => (Entry, _fileExists, _) = MetaFiles.ImcChecker.GetDefaultEntry(Identifier, true); + + protected override void DrawNew() + { + ImGui.TableNextColumn(); + // Copy To Clipboard + ImGui.TableNextColumn(); + var canAdd = _fileExists && Editor.MetaEditor.CanAdd(Identifier); + var tt = canAdd ? "Stage this edit."u8 : !_fileExists ? "This IMC file does not exist."u8 : "This entry is already edited."u8; + if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd)) + Editor.MetaEditor.TryAdd(Identifier, Entry); + + if (DrawIdentifier(ref Identifier)) + UpdateEntry(); + + using var disabled = ImRaii.Disabled(); + DrawEntry(Entry, ref Entry, false); + } + + protected override void DrawEntry(ImcIdentifier identifier, ImcEntry entry) + { + const uint frameColor = 0; + // Meta Buttons + + ImGui.TableNextColumn(); + ImUtf8.TextFramed(identifier.ObjectType.ToName(), frameColor); + ImUtf8.HoverTooltip("Object Type"u8); + + ImGui.TableNextColumn(); + ImUtf8.TextFramed($"{identifier.PrimaryId.Id}", frameColor); + ImUtf8.HoverTooltip("Primary ID"); + + ImGui.TableNextColumn(); + if (identifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory) + { + ImUtf8.TextFramed(identifier.EquipSlot.ToName(), frameColor); + ImUtf8.HoverTooltip("Equip Slot"u8); + } + else + { + ImUtf8.TextFramed($"{identifier.SecondaryId.Id}", frameColor); + ImUtf8.HoverTooltip("Secondary ID"u8); + } + + ImGui.TableNextColumn(); + ImUtf8.TextFramed($"{identifier.Variant.Id}", frameColor); + ImUtf8.HoverTooltip("Variant"u8); + + ImGui.TableNextColumn(); + if (identifier.ObjectType is ObjectType.DemiHuman) + { + ImUtf8.TextFramed(identifier.EquipSlot.ToName(), frameColor); + ImUtf8.HoverTooltip("Equip Slot"u8); + } + + var defaultEntry = MetaFiles.ImcChecker.GetDefaultEntry(identifier, true).Entry; + if (DrawEntry(defaultEntry, ref entry, true)) + Editor.MetaEditor.Update(identifier, entry); + } + + private static bool DrawIdentifier(ref ImcIdentifier identifier) + { + ImGui.TableNextColumn(); + var change = DrawObjectType(ref identifier); + + ImGui.TableNextColumn(); + change |= DrawPrimaryId(ref identifier); + + ImGui.TableNextColumn(); + if (identifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory) + change |= DrawSlot(ref identifier); + else + change |= DrawSecondaryId(ref identifier); + + ImGui.TableNextColumn(); + change |= DrawVariant(ref identifier); + + ImGui.TableNextColumn(); + if (identifier.ObjectType is ObjectType.DemiHuman) + change |= DrawSlot(ref identifier, 70f); + else + ImUtf8.ScaledDummy(70f); + return change; + } + + private static bool DrawEntry(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault) + { + ImGui.TableNextColumn(); + var change = DrawMaterialId(defaultEntry, ref entry, addDefault); + ImUtf8.SameLineInner(); + change |= DrawMaterialAnimationId(defaultEntry, ref entry, addDefault); + + ImGui.TableNextColumn(); + change |= DrawDecalId(defaultEntry, ref entry, addDefault); + ImUtf8.SameLineInner(); + change |= DrawVfxId(defaultEntry, ref entry, addDefault); + ImUtf8.SameLineInner(); + change |= DrawSoundId(defaultEntry, ref entry, addDefault); + + ImGui.TableNextColumn(); + change |= DrawAttributes(defaultEntry, ref entry); + return change; + } + + + protected override IEnumerable<(ImcIdentifier, ImcEntry)> Enumerate() + => Editor.MetaEditor.Imc.Select(kvp => (kvp.Key, kvp.Value)); + + public static bool DrawObjectType(ref ImcIdentifier identifier, float width = 110) + { + var ret = Combos.ImcType("##imcType", identifier.ObjectType, out var type, width); + ImUtf8.HoverTooltip("Object Type"u8); + + if (ret) + { + var equipSlot = type switch + { + ObjectType.Equipment => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head, + ObjectType.DemiHuman => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head, + ObjectType.Accessory => identifier.EquipSlot.IsAccessory() ? identifier.EquipSlot : EquipSlot.Ears, + _ => EquipSlot.Unknown, + }; + identifier = identifier with + { + ObjectType = type, + EquipSlot = equipSlot, + SecondaryId = identifier.SecondaryId == 0 ? 1 : identifier.SecondaryId, + }; + } + + return ret; + } + + public static bool DrawPrimaryId(ref ImcIdentifier identifier, float unscaledWidth = 80) + { + var ret = IdInput("##imcPrimaryId"u8, unscaledWidth, identifier.PrimaryId.Id, out var newId, 0, ushort.MaxValue, + identifier.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) + identifier = identifier with { PrimaryId = newId }; + return ret; + } + + public static bool DrawSecondaryId(ref ImcIdentifier identifier, float unscaledWidth = 100) + { + var ret = IdInput("##imcSecondaryId"u8, unscaledWidth, identifier.SecondaryId.Id, out var newId, 0, ushort.MaxValue, false); + ImUtf8.HoverTooltip("Secondary ID"u8); + if (ret) + identifier = identifier with { SecondaryId = newId }; + return ret; + } + + public static bool DrawVariant(ref ImcIdentifier identifier, float unscaledWidth = 45) + { + var ret = IdInput("##imcVariant"u8, unscaledWidth, identifier.Variant.Id, out var newId, 0, byte.MaxValue, false); + ImUtf8.HoverTooltip("Variant ID"u8); + if (ret) + identifier = identifier with { Variant = (byte)newId }; + return ret; + } + + public static bool DrawSlot(ref ImcIdentifier identifier, float unscaledWidth = 100) + { + bool ret; + EquipSlot slot; + switch (identifier.ObjectType) + { + case ObjectType.Equipment: + case ObjectType.DemiHuman: + ret = Combos.EqpEquipSlot("##slot", identifier.EquipSlot, out slot, unscaledWidth); + break; + case ObjectType.Accessory: + ret = Combos.AccessorySlot("##slot", identifier.EquipSlot, out slot, unscaledWidth); + break; + default: return false; + } + + ImUtf8.HoverTooltip("Equip Slot"u8); + if (ret) + identifier = identifier with { EquipSlot = slot }; + return ret; + } + + public static bool DrawMaterialId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45) + { + if (!DragInput("##materialId"u8, "Material ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.MaterialId, defaultEntry.MaterialId, + out var newValue, (byte)1, byte.MaxValue, 0.01f, addDefault)) + return false; + + entry = entry with { MaterialId = newValue }; + return true; + } + + public static bool DrawMaterialAnimationId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45) + { + if (!DragInput("##mAnimId"u8, "Material Animation ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.MaterialAnimationId, + defaultEntry.MaterialAnimationId, out var newValue, (byte)0, byte.MaxValue, 0.01f, addDefault)) + return false; + + entry = entry with { MaterialAnimationId = newValue }; + return true; + } + + public static bool DrawDecalId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45) + { + if (!DragInput("##decalId"u8, "Decal ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.DecalId, defaultEntry.DecalId, out var newValue, + (byte)0, byte.MaxValue, 0.01f, addDefault)) + return false; + + entry = entry with { DecalId = newValue }; + return true; + } + + public static bool DrawVfxId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45) + { + if (!DragInput("##vfxId"u8, "VFX ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.VfxId, defaultEntry.VfxId, out var newValue, (byte)0, + byte.MaxValue, 0.01f, addDefault)) + return false; + + entry = entry with { VfxId = newValue }; + return true; + } + + public static bool DrawSoundId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45) + { + if (!DragInput("##soundId"u8, "Sound ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.SoundId, defaultEntry.SoundId, out var newValue, + (byte)0, byte.MaxValue, 0.01f, addDefault)) + return false; + + entry = entry with { SoundId = newValue }; + return true; + } + + public static bool DrawAttributes(ImcEntry defaultEntry, ref ImcEntry entry) + { + var changes = false; + for (var i = 0; i < ImcEntry.NumAttributes; ++i) + { + using var id = ImRaii.PushId(i); + var flag = 1 << i; + var value = (entry.AttributeMask & flag) != 0; + var def = (defaultEntry.AttributeMask & flag) != 0; + if (Checkmark("##attribute"u8, "ABCDEFGHIJ"u8.Slice(i, 1), value, def, out var newValue)) + { + var newMask = (ushort)(newValue ? entry.AttributeMask | flag : entry.AttributeMask & ~flag); + entry = entry with { AttributeMask = newMask }; + changes = true; + } + + if (i < ImcEntry.NumAttributes - 1) + ImUtf8.SameLineInner(); + } + + return changes; + } + + + /// + /// A number input for ids with an 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; + } + + /// + /// A dragging int input of given width that compares against a default value, shows a tooltip and clamps against min and max. + /// Returns true if newValue changed against currentValue. + /// + private static bool DragInput(ReadOnlySpan label, ReadOnlySpan tooltip, float width, T currentValue, T defaultValue, + out T newValue, T minValue, T maxValue, float speed, bool addDefault) where T : unmanaged, INumber + { + newValue = currentValue; + using var color = ImRaii.PushColor(ImGuiCol.FrameBg, + defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), + defaultValue != currentValue); + ImGui.SetNextItemWidth(width); + if (ImUtf8.DragScalar(label, ref newValue, minValue, maxValue, speed)) + newValue = newValue <= minValue ? minValue : newValue >= maxValue ? maxValue : newValue; + + if (addDefault) + ImUtf8.HoverTooltip($"{tooltip}\nDefault Value: {defaultValue}"); + else + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip); + + return newValue != currentValue; + } + + /// + /// A checkmark that compares against a default value and shows a tooltip. + /// Returns true if newValue is changed against currentValue. + /// + private static bool Checkmark(ReadOnlySpan label, ReadOnlySpan tooltip, bool currentValue, bool defaultValue, + out bool newValue) + { + using var color = ImRaii.PushColor(ImGuiCol.FrameBg, + defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), + defaultValue != currentValue); + newValue = currentValue; + ImUtf8.Checkbox(label, ref newValue); + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip); + return newValue != currentValue; + } +} diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs index b0a74637..50862eec 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs @@ -1,10 +1,11 @@ +using System.Reflection.Emit; using Dalamud.Interface; using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Text; +using OtterGui.Text.EndObjects; using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; using Penumbra.Interop.Structs; using Penumbra.Meta; using Penumbra.Meta.Files; @@ -20,17 +21,7 @@ public partial class ModEditWindow private const string ModelSetIdTooltip = "Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that."; - private const string ModelSetIdTooltipShort = "Model Set ID"; - private const string EquipSlotTooltip = "Equip Slot"; - private const string ModelRaceTooltip = "Model Race"; - private const string GenderTooltip = "Gender"; - private const string ObjectTypeTooltip = "Object Type"; - private const string SecondaryIdTooltip = "Secondary ID"; - private const string PrimaryIdTooltipShort = "Primary ID"; - private const string VariantIdTooltip = "Variant ID"; - private const string EstTypeTooltip = "EST Type"; - private const string RacialTribeTooltip = "Racial Tribe"; - private const string ScalingTypeTooltip = "Scaling Type"; + private void DrawMetaTab() { @@ -56,7 +47,7 @@ private void DrawMetaTab() ImGui.SameLine(); SetFromClipboardButton(); ImGui.SameLine(); - CopyToClipboardButton("Copy all current manipulations to clipboard.", _iconSize, _editor.MetaEditor.Recombine()); + CopyToClipboardButton("Copy all current manipulations to clipboard.", _iconSize, _editor.MetaEditor); ImGui.SameLine(); if (ImGui.Button("Write as TexTools Files")) _metaFileManager.WriteAllTexToolsMeta(Mod!); @@ -65,71 +56,103 @@ private void DrawMetaTab() if (!child) return; - DrawEditHeader(_editor.MetaEditor.Eqp, "Equipment Parameter Edits (EQP)###EQP", 5, EqpRow.Draw, EqpRow.DrawNew, - _editor.MetaEditor.OtherData[MetaManipulation.Type.Eqp]); - DrawEditHeader(_editor.MetaEditor.Eqdp, "Racial Model Edits (EQDP)###EQDP", 7, EqdpRow.Draw, EqdpRow.DrawNew, - _editor.MetaEditor.OtherData[MetaManipulation.Type.Eqdp]); - DrawEditHeader(_editor.MetaEditor.Imc, "Variant Edits (IMC)###IMC", 10, ImcRow.Draw, ImcRow.DrawNew, - _editor.MetaEditor.OtherData[MetaManipulation.Type.Imc]); - DrawEditHeader(_editor.MetaEditor.Est, "Extra Skeleton Parameters (EST)###EST", 7, EstRow.Draw, EstRow.DrawNew, - _editor.MetaEditor.OtherData[MetaManipulation.Type.Est]); - DrawEditHeader(_editor.MetaEditor.Gmp, "Visor/Gimmick Edits (GMP)###GMP", 7, GmpRow.Draw, GmpRow.DrawNew, - _editor.MetaEditor.OtherData[MetaManipulation.Type.Gmp]); - DrawEditHeader(_editor.MetaEditor.Rsp, "Racial Scaling Edits (RSP)###RSP", 5, RspRow.Draw, RspRow.DrawNew, - _editor.MetaEditor.OtherData[MetaManipulation.Type.Rsp]); - DrawEditHeader(_editor.MetaEditor.GlobalEqp, "Global Equipment Parameter Edits (Global EQP)###GEQP", 4, GlobalEqpRow.Draw, - GlobalEqpRow.DrawNew, _editor.MetaEditor.OtherData[MetaManipulation.Type.GlobalEqp]); + DrawEditHeader(MetaManipulation.Type.Eqp); + DrawEditHeader(MetaManipulation.Type.Eqdp); + DrawEditHeader(MetaManipulation.Type.Imc); + DrawEditHeader(MetaManipulation.Type.Est); + DrawEditHeader(MetaManipulation.Type.Gmp); + DrawEditHeader(MetaManipulation.Type.Rsp); + DrawEditHeader(MetaManipulation.Type.GlobalEqp); } + private static ReadOnlySpan Label(MetaManipulation.Type type) + => type switch + { + MetaManipulation.Type.Imc => "Variant Edits (IMC)###IMC"u8, + MetaManipulation.Type.Eqdp => "Racial Model Edits (EQDP)###EQDP"u8, + MetaManipulation.Type.Eqp => "Equipment Parameter Edits (EQP)###EQP"u8, + MetaManipulation.Type.Est => "Extra Skeleton Parameters (EST)###EST"u8, + MetaManipulation.Type.Gmp => "Visor/Gimmick Edits (GMP)###GMP"u8, + MetaManipulation.Type.Rsp => "Racial Scaling Edits (RSP)###RSP"u8, + MetaManipulation.Type.GlobalEqp => "Global Equipment Parameter Edits (Global EQP)###GEQP"u8, + _ => "\0"u8, + }; - /// The headers for the different meta changes all have basically the same structure for different types. - private void DrawEditHeader(IReadOnlyCollection items, string label, int numColumns, - Action draw, Action drawNew, - ModMetaEditor.OtherOptionData otherOptionData) - { - const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersInnerV; + private static int ColumnCount(MetaManipulation.Type type) + => type switch + { + MetaManipulation.Type.Imc => 10, + MetaManipulation.Type.Eqdp => 7, + MetaManipulation.Type.Eqp => 5, + MetaManipulation.Type.Est => 7, + MetaManipulation.Type.Gmp => 7, + MetaManipulation.Type.Rsp => 5, + MetaManipulation.Type.GlobalEqp => 4, + _ => 0, + }; + private void DrawEditHeader(MetaManipulation.Type type) + { var oldPos = ImGui.GetCursorPosY(); - var header = ImGui.CollapsingHeader($"{items.Count} {label}"); - var newPos = ImGui.GetCursorPos(); - if (otherOptionData.TotalCount > 0) + var header = ImUtf8.CollapsingHeader($"{_editor.MetaEditor.GetCount(type)} {Label(type)}"); + DrawOtherOptionData(type, oldPos, ImGui.GetCursorPos()); + if (!header) + return; + + DrawTable(type); + } + + private IMetaDrawer? Drawer(MetaManipulation.Type type) + => type switch { - var text = $"{otherOptionData.TotalCount} Edits in other Options"; - var size = ImGui.CalcTextSize(text).X; - ImGui.SetCursorPos(new Vector2(ImGui.GetContentRegionAvail().X - size, oldPos + ImGui.GetStyle().FramePadding.Y)); - ImGuiUtil.TextColored(ColorId.RedundantAssignment.Value() | 0xFF000000, text); - if (ImGui.IsItemHovered()) - { - using var tt = ImUtf8.Tooltip(); - foreach (var name in otherOptionData) - ImUtf8.Text(name); - } + //MetaManipulation.Type.Imc => expr, + //MetaManipulation.Type.Eqdp => expr, + //MetaManipulation.Type.Eqp => expr, + //MetaManipulation.Type.Est => expr, + //MetaManipulation.Type.Gmp => expr, + //MetaManipulation.Type.Rsp => expr, + //MetaManipulation.Type.GlobalEqp => expr, + _ => null, + }; - ImGui.SetCursorPos(newPos); - } + private void DrawTable(MetaManipulation.Type type) + { + const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersInnerV; + using var table = ImUtf8.Table(Label(type), ColumnCount(type), flags); + if (!table) + return; - if (!header) + if (Drawer(type) is not { } drawer) return; - using (var table = ImRaii.Table(label, numColumns, flags)) + drawer.Draw(); + ImGui.NewLine(); + } + + private void DrawOtherOptionData(MetaManipulation.Type type, float oldPos, Vector2 newPos) + { + var otherOptionData = _editor.MetaEditor.OtherData[type]; + if (otherOptionData.TotalCount <= 0) + return; + + var text = $"{otherOptionData.TotalCount} Edits in other Options"; + var size = ImGui.CalcTextSize(text).X; + ImGui.SetCursorPos(new Vector2(ImGui.GetContentRegionAvail().X - size, oldPos + ImGui.GetStyle().FramePadding.Y)); + ImGuiUtil.TextColored(ColorId.RedundantAssignment.Value() | 0xFF000000, text); + if (ImGui.IsItemHovered()) { - if (table) - { - drawNew(_metaFileManager, _editor, _iconSize); - foreach (var (item, index) in items.ToArray().WithIndex()) - { - using var id = ImRaii.PushId(index); - draw(_metaFileManager, item, _editor, _iconSize); - } - } + using var tt = ImUtf8.Tooltip(); + foreach (var name in otherOptionData) + ImUtf8.Text(name); } - ImGui.NewLine(); + ImGui.SetCursorPos(newPos); } +#if false private static class EqpRow { - private static EqpManipulation _new = new(Eqp.DefaultEntry, EquipSlot.Head, 1); + private static EqpIdentifier _newIdentifier = new(1, EquipSlot.Body); private static float IdWidth => 100 * UiHelpers.Scale; @@ -140,8 +163,8 @@ public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Ve CopyToClipboardButton("Copy all current EQP manipulations to clipboard.", iconSize, editor.MetaEditor.Eqp.Select(m => (MetaManipulation)m)); ImGui.TableNextColumn(); - var canAdd = editor.MetaEditor.CanAdd(_new); - var tt = canAdd ? "Stage this edit." : "This entry is already edited."; + var canAdd = editor.MetaEditor.CanAdd(_new); + var tt = canAdd ? "Stage this edit." : "This entry is already edited."; var defaultEntry = ExpandedEqpFile.GetDefault(metaFileManager, _new.SetId); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) editor.MetaEditor.Add(_new.Copy(defaultEntry)); @@ -197,7 +220,7 @@ public static void Draw(MetaFileManager metaFileManager, EqpManipulation meta, M var idx = 0; foreach (var flag in Eqp.EqpAttributes[meta.Slot]) { - using var id = ImRaii.PushId(idx++); + using var id = ImRaii.PushId(idx++); var defaultValue = defaultEntry.HasFlag(flag); var currentValue = meta.Entry.HasFlag(flag); if (Checkmark("##eqp", flag.ToLocalName(), currentValue, defaultValue, out var value)) @@ -209,8 +232,6 @@ public static void Draw(MetaFileManager metaFileManager, EqpManipulation meta, M ImGui.NewLine(); } } - - private static class EqdpRow { private static EqdpManipulation _new = new(EqdpEntry.Invalid, EquipSlot.Head, Gender.Male, ModelRace.Midlander, 1); @@ -224,9 +245,9 @@ public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Ve CopyToClipboardButton("Copy all current EQDP manipulations to clipboard.", iconSize, editor.MetaEditor.Eqdp.Select(m => (MetaManipulation)m)); ImGui.TableNextColumn(); - var raceCode = Names.CombinedRace(_new.Gender, _new.Race); + var raceCode = Names.CombinedRace(_new.Gender, _new.Race); var validRaceCode = CharacterUtilityData.EqdpIdx(raceCode, false) >= 0; - var canAdd = validRaceCode && editor.MetaEditor.CanAdd(_new); + var canAdd = validRaceCode && editor.MetaEditor.CanAdd(_new); var tt = canAdd ? "Stage this edit." : validRaceCode ? "This entry is already edited." : "This combination of race and gender can not be used."; var defaultEntry = validRaceCode @@ -311,7 +332,7 @@ public static void Draw(MetaFileManager metaFileManager, EqdpManipulation meta, var defaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(meta.Gender, meta.Race), meta.Slot.IsAccessory(), meta.SetId); var (defaultBit1, defaultBit2) = defaultEntry.ToBits(meta.Slot); - var (bit1, bit2) = meta.Entry.ToBits(meta.Slot); + var (bit1, bit2) = meta.Entry.ToBits(meta.Slot); ImGui.TableNextColumn(); if (Checkmark("Material##eqdpCheck1", string.Empty, bit1, defaultBit1, out var newBit1)) editor.MetaEditor.Change(meta.Copy(Eqdp.FromSlotAndBits(meta.Slot, newBit1, bit2))); @@ -321,136 +342,7 @@ public static void Draw(MetaFileManager metaFileManager, EqdpManipulation meta, editor.MetaEditor.Change(meta.Copy(Eqdp.FromSlotAndBits(meta.Slot, bit1, newBit2))); } } - - private static class ImcRow - { - private static ImcIdentifier _newIdentifier = ImcIdentifier.Default; - - private static float IdWidth - => 80 * UiHelpers.Scale; - - private static float SmallIdWidth - => 45 * UiHelpers.Scale; - - public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) - { - ImGui.TableNextColumn(); - CopyToClipboardButton("Copy all current IMC manipulations to clipboard.", iconSize, - editor.MetaEditor.Imc.Select(m => (MetaManipulation)m)); - ImGui.TableNextColumn(); - var (defaultEntry, fileExists, _) = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true); - var manip = (MetaManipulation)new ImcManipulation(_newIdentifier, defaultEntry); - var canAdd = fileExists && editor.MetaEditor.CanAdd(manip); - var tt = canAdd ? "Stage this edit." : !fileExists ? "This IMC file does not exist." : "This entry is already edited."; - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) - editor.MetaEditor.Add(manip); - - // Identifier - ImGui.TableNextColumn(); - var change = ImcManipulationDrawer.DrawObjectType(ref _newIdentifier); - - ImGui.TableNextColumn(); - change |= ImcManipulationDrawer.DrawPrimaryId(ref _newIdentifier); - 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 (_newIdentifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory) - change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier); - else - change |= ImcManipulationDrawer.DrawSecondaryId(ref _newIdentifier); - - ImGui.TableNextColumn(); - change |= ImcManipulationDrawer.DrawVariant(ref _newIdentifier); - - ImGui.TableNextColumn(); - if (_newIdentifier.ObjectType is ObjectType.DemiHuman) - change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier, 70); - else - ImGui.Dummy(new Vector2(70 * UiHelpers.Scale, 0)); - - if (change) - defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true).Entry; - // Values - using var disabled = ImRaii.Disabled(); - ImGui.TableNextColumn(); - ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref defaultEntry, false); - ImGui.SameLine(); - ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref defaultEntry, false); - ImGui.TableNextColumn(); - ImcManipulationDrawer.DrawDecalId(defaultEntry, ref defaultEntry, false); - ImGui.SameLine(); - ImcManipulationDrawer.DrawVfxId(defaultEntry, ref defaultEntry, false); - ImGui.SameLine(); - ImcManipulationDrawer.DrawSoundId(defaultEntry, ref defaultEntry, false); - ImGui.TableNextColumn(); - ImcManipulationDrawer.DrawAttributes(defaultEntry, ref defaultEntry); - } - - public static void Draw(MetaFileManager metaFileManager, ImcManipulation meta, ModEditor editor, Vector2 iconSize) - { - DrawMetaButtons(meta, editor, iconSize); - - // Identifier - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.ObjectType.ToName()); - ImGuiUtil.HoverTooltip(ObjectTypeTooltip); - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.PrimaryId.ToString()); - ImGuiUtil.HoverTooltip(PrimaryIdTooltipShort); - - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - if (meta.ObjectType is ObjectType.Equipment or ObjectType.Accessory) - { - ImGui.TextUnformatted(meta.EquipSlot.ToName()); - ImGuiUtil.HoverTooltip(EquipSlotTooltip); - } - else - { - ImGui.TextUnformatted(meta.SecondaryId.ToString()); - ImGuiUtil.HoverTooltip(SecondaryIdTooltip); - } - - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.Variant.ToString()); - ImGuiUtil.HoverTooltip(VariantIdTooltip); - - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - if (meta.ObjectType is ObjectType.DemiHuman) - { - ImGui.TextUnformatted(meta.EquipSlot.ToName()); - ImGuiUtil.HoverTooltip(EquipSlotTooltip); - } - - // Values - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, - new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y)); - ImGui.TableNextColumn(); - var defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(meta.Identifier, true).Entry; - var newEntry = meta.Entry; - var changes = ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref newEntry, true); - ImGui.SameLine(); - changes |= ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref newEntry, true); - ImGui.TableNextColumn(); - changes |= ImcManipulationDrawer.DrawDecalId(defaultEntry, ref newEntry, true); - ImGui.SameLine(); - changes |= ImcManipulationDrawer.DrawVfxId(defaultEntry, ref newEntry, true); - ImGui.SameLine(); - changes |= ImcManipulationDrawer.DrawSoundId(defaultEntry, ref newEntry, true); - ImGui.TableNextColumn(); - changes |= ImcManipulationDrawer.DrawAttributes(defaultEntry, ref newEntry); - - if (changes) - editor.MetaEditor.Change(meta.Copy(newEntry)); - } - } - + private static class EstRow { private static EstManipulation _new = new(Gender.Male, ModelRace.Midlander, EstType.Body, 1, EstEntry.Zero); @@ -464,8 +356,8 @@ public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Ve CopyToClipboardButton("Copy all current EST manipulations to clipboard.", iconSize, editor.MetaEditor.Est.Select(m => (MetaManipulation)m)); ImGui.TableNextColumn(); - var canAdd = editor.MetaEditor.CanAdd(_new); - var tt = canAdd ? "Stage this edit." : "This entry is already edited."; + var canAdd = editor.MetaEditor.CanAdd(_new); + var tt = canAdd ? "Stage this edit." : "This entry is already edited."; var defaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, _new.Race), _new.SetId); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) editor.MetaEditor.Add(_new.Copy(defaultEntry)); @@ -538,12 +430,11 @@ public static void Draw(MetaFileManager metaFileManager, EstManipulation meta, M // Values var defaultEntry = EstFile.GetDefault(metaFileManager, meta.Slot, Names.CombinedRace(meta.Gender, meta.Race), meta.SetId); ImGui.TableNextColumn(); - if (IntDragInput("##estSkeleton", $"Skeleton Index\nDefault Value: {defaultEntry}", IdWidth, meta.Entry.Value, defaultEntry.Value, + if (IntDragInput("##estSkeleton", $"Skeleton Index\nDefault Value: {defaultEntry}", IdWidth, meta.Entry.Value, defaultEntry.Value, out var entry, 0, ushort.MaxValue, 0.05f)) editor.MetaEditor.Change(meta.Copy(new EstEntry((ushort)entry))); } } - private static class GmpRow { private static GmpManipulation _new = new(GmpEntry.Default, 1); @@ -563,8 +454,8 @@ public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Ve CopyToClipboardButton("Copy all current GMP manipulations to clipboard.", iconSize, editor.MetaEditor.Gmp.Select(m => (MetaManipulation)m)); ImGui.TableNextColumn(); - var canAdd = editor.MetaEditor.CanAdd(_new); - var tt = canAdd ? "Stage this edit." : "This entry is already edited."; + var canAdd = editor.MetaEditor.CanAdd(_new); + var tt = canAdd ? "Stage this edit." : "This entry is already edited."; var defaultEntry = ExpandedGmpFile.GetDefault(metaFileManager, _new.SetId); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) editor.MetaEditor.Add(_new.Copy(defaultEntry)); @@ -643,7 +534,6 @@ public static void Draw(MetaFileManager metaFileManager, GmpManipulation meta, M editor.MetaEditor.Change(meta.Copy(meta.Entry with { UnknownB = (byte)unkB })); } } - private static class RspRow { private static RspManipulation _new = new(SubRace.Midlander, RspAttribute.MaleMinSize, RspEntry.One); @@ -657,8 +547,8 @@ public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Ve CopyToClipboardButton("Copy all current RSP manipulations to clipboard.", iconSize, editor.MetaEditor.Rsp.Select(m => (MetaManipulation)m)); ImGui.TableNextColumn(); - var canAdd = editor.MetaEditor.CanAdd(_new); - var tt = canAdd ? "Stage this edit." : "This entry is already edited."; + var canAdd = editor.MetaEditor.CanAdd(_new); + var tt = canAdd ? "Stage this edit." : "This entry is already edited."; var defaultEntry = CmpFile.GetDefault(metaFileManager, _new.SubRace, _new.Attribute); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) editor.MetaEditor.Add(_new.Copy(defaultEntry)); @@ -700,7 +590,7 @@ public static void Draw(MetaFileManager metaFileManager, RspManipulation meta, M ImGui.TableNextColumn(); // Values - var def = CmpFile.GetDefault(metaFileManager, meta.SubRace, meta.Attribute).Value; + var def = CmpFile.GetDefault(metaFileManager, meta.SubRace, meta.Attribute).Value; var value = meta.Entry.Value; ImGui.SetNextItemWidth(FloatWidth); using var color = ImRaii.PushColor(ImGuiCol.FrameBg, @@ -713,12 +603,11 @@ public static void Draw(MetaFileManager metaFileManager, RspManipulation meta, M ImGuiUtil.HoverTooltip($"Default Value: {def:0.###}"); } } - private static class GlobalEqpRow { private static GlobalEqpManipulation _new = new() { - Type = GlobalEqpType.DoNotHideEarrings, + Type = GlobalEqpType.DoNotHideEarrings, Condition = 1, }; @@ -729,7 +618,7 @@ public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Ve editor.MetaEditor.GlobalEqp.Select(m => (MetaManipulation)m)); ImGui.TableNextColumn(); var canAdd = editor.MetaEditor.CanAdd(_new); - var tt = canAdd ? "Stage this edit." : "This entry is already manipulated."; + var tt = canAdd ? "Stage this edit." : "This entry is already manipulated."; if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) editor.MetaEditor.Add(_new); @@ -744,7 +633,7 @@ public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Ve if (ImUtf8.Selectable(type.ToName(), type == _new.Type)) _new = new GlobalEqpManipulation { - Type = type, + Type = type, Condition = type.HasCondition() ? _new.Type.HasCondition() ? _new.Condition : 1 : 0, }; ImUtf8.HoverTooltip(type.ToDescription()); @@ -777,6 +666,7 @@ public static void Draw(MetaFileManager metaFileManager, GlobalEqpManipulation m } } } +#endif // A number input for ids with a optional max id of given width. // Returns true if newId changed against currentId. @@ -824,7 +714,7 @@ private static bool IntDragInput(string label, string tooltip, float width, int return newValue != currentValue; } - private static void CopyToClipboardButton(string tooltip, Vector2 iconSize, IEnumerable manipulations) + private static void CopyToClipboardButton(string tooltip, Vector2 iconSize, MetaDictionary manipulations) { if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), iconSize, tooltip, false, true)) return; @@ -840,10 +730,9 @@ private void AddFromClipboardButton() { var clipboard = ImGuiUtil.GetClipboardText(); - var version = Functions.FromCompressedBase64(clipboard, out var manips); + var version = Functions.FromCompressedBase64(clipboard, out var manips); if (version == MetaManipulation.CurrentVersion && manips != null) - foreach (var manip in manips.Where(m => m.ManipulationType != MetaManipulation.Type.Unknown)) - _editor.MetaEditor.Set(manip); + _editor.MetaEditor.UpdateTo(manips); } ImGuiUtil.HoverTooltip( @@ -855,13 +744,9 @@ private void SetFromClipboardButton() if (ImGui.Button("Set from Clipboard")) { var clipboard = ImGuiUtil.GetClipboardText(); - var version = Functions.FromCompressedBase64(clipboard, out var manips); + var version = Functions.FromCompressedBase64(clipboard, out var manips); if (version == MetaManipulation.CurrentVersion && manips != null) - { - _editor.MetaEditor.Clear(); - foreach (var manip in manips.Where(m => m.ManipulationType != MetaManipulation.Type.Unknown)) - _editor.MetaEditor.Set(manip); - } + _editor.MetaEditor.SetTo(manips); } ImGuiUtil.HoverTooltip( @@ -870,11 +755,184 @@ private void SetFromClipboardButton() private static void DrawMetaButtons(MetaManipulation meta, ModEditor editor, Vector2 iconSize) { - ImGui.TableNextColumn(); - CopyToClipboardButton("Copy this manipulation to clipboard.", iconSize, Array.Empty().Append(meta)); + //ImGui.TableNextColumn(); + //CopyToClipboardButton("Copy this manipulation to clipboard.", iconSize, Array.Empty().Append(meta)); + // + //ImGui.TableNextColumn(); + //if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta manipulation.", false, true)) + // editor.MetaEditor.Delete(meta); + } +} + + +public interface IMetaDrawer +{ + public void Draw(); +} + - ImGui.TableNextColumn(); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta manipulation.", false, true)) - editor.MetaEditor.Delete(meta); + + +public abstract class MetaDrawer(ModEditor editor, MetaFileManager metaFiles) : IMetaDrawer + where TIdentifier : unmanaged, IMetaIdentifier + where TEntry : unmanaged +{ + protected readonly ModEditor Editor = editor; + protected readonly MetaFileManager MetaFiles = metaFiles; + protected TIdentifier Identifier; + protected TEntry Entry; + private bool _initialized; + + public void Draw() + { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + + DrawNew(); + foreach (var ((identifier, entry), idx) in Enumerate().WithIndex()) + { + using var id = ImUtf8.PushId(idx); + DrawEntry(identifier, entry); + } } + + protected abstract void DrawNew(); + protected abstract void Initialize(); + protected abstract void DrawEntry(TIdentifier identifier, TEntry entry); + + protected abstract IEnumerable<(TIdentifier, TEntry)> Enumerate(); +} + + +#if false +public sealed class GmpMetaDrawer(ModEditor editor) : MetaDrawer, IService +{ + protected override void Initialize() + { + Identifier = new GmpIdentifier(1, EquipSlot.Body); + UpdateEntry(); + } + + private void UpdateEntry() + => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); + + protected override void DrawNew() + { } + + protected override void DrawEntry(GmpIdentifier identifier, GmpEntry entry) + { } + + protected override IEnumerable<(GmpIdentifier, GmpEntry)> Enumerate() + => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); +} + +public sealed class EstMetaDrawer(ModEditor editor) : MetaDrawer, IService +{ + protected override void Initialize() + { + Identifier = new EqpIdentifier(1, EquipSlot.Body); + UpdateEntry(); + } + + private void UpdateEntry() + => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); + + protected override void DrawNew() + { } + + protected override void DrawEntry(EstIdentifier identifier, EstEntry entry) + { } + + protected override IEnumerable<(EstIdentifier, EstEntry)> Enumerate() + => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); +} + +public sealed class EqdpMetaDrawer(ModEditor editor) : MetaDrawer, IService +{ + protected override void Initialize() + { + Identifier = new EqdpIdentifier(1, EquipSlot.Body); + UpdateEntry(); + } + + private void UpdateEntry() + => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); + + protected override void DrawNew() + { } + + protected override void DrawEntry(EqdpIdentifier identifier, EqdpEntry entry) + { } + + protected override IEnumerable<(EqdpIdentifier, EqdpEntry)> Enumerate() + => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); +} + +public sealed class EqpMetaDrawer(ModEditor editor, MetaFileManager metaManager) : MetaDrawer, IService +{ + protected override void Initialize() + { + Identifier = new EqpIdentifier(1, EquipSlot.Body); + UpdateEntry(); + } + + private void UpdateEntry() + => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); + + protected override void DrawNew() + { } + + protected override void DrawEntry(EqpIdentifier identifier, EqpEntry entry) + { } + + protected override IEnumerable<(EqpIdentifier, EqpEntry)> Enumerate() + => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); +} + +public sealed class RspMetaDrawer(ModEditor editor) : MetaDrawer, IService +{ + protected override void Initialize() + { + Identifier = new RspIdentifier(1, EquipSlot.Body); + UpdateEntry(); + } + + private void UpdateEntry() + => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); + + protected override void DrawNew() + { } + + protected override void DrawEntry(RspIdentifier identifier, RspEntry entry) + { } + + protected override IEnumerable<(RspIdentifier, RspEntry)> Enumerate() + => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); +} + + + +public sealed class GlobalEqpMetaDrawer(ModEditor editor) : MetaDrawer, IService +{ + protected override void Initialize() + { + Identifier = new EqpIdentifier(1, EquipSlot.Body); + UpdateEntry(); + } + + private void UpdateEntry() + => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); + + protected override void DrawNew() + { } + + protected override void DrawEntry(GlobalEqpManipulation identifier, byte _) + { } + + protected override IEnumerable<(GlobalEqpManipulation, byte)> Enumerate() + => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); } +#endif diff --git a/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs b/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs index 3ac10cd0..689571f3 100644 --- a/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs @@ -9,6 +9,7 @@ using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Mods.Manager.OptionEditor; +using Penumbra.UI.AdvancedWindow.Meta; using Penumbra.UI.Classes; namespace Penumbra.UI.ModsTab.Groups; @@ -79,29 +80,29 @@ private void DrawMultiGroupButton(Mod mod, Vector2 width) private void DrawImcInput(float width) { - var change = ImcManipulationDrawer.DrawObjectType(ref _imcIdentifier, width); + var change = ImcMetaDrawer.DrawObjectType(ref _imcIdentifier, width); ImUtf8.SameLineInner(); - change |= ImcManipulationDrawer.DrawPrimaryId(ref _imcIdentifier, width); + change |= ImcMetaDrawer.DrawPrimaryId(ref _imcIdentifier, width); if (_imcIdentifier.ObjectType is ObjectType.Weapon or ObjectType.Monster) { - change |= ImcManipulationDrawer.DrawSecondaryId(ref _imcIdentifier, width); + change |= ImcMetaDrawer.DrawSecondaryId(ref _imcIdentifier, width); ImUtf8.SameLineInner(); - change |= ImcManipulationDrawer.DrawVariant(ref _imcIdentifier, width); + change |= ImcMetaDrawer.DrawVariant(ref _imcIdentifier, width); } else if (_imcIdentifier.ObjectType is ObjectType.DemiHuman) { var quarterWidth = (width - ImUtf8.ItemInnerSpacing.X / ImUtf8.GlobalScale) / 2; - change |= ImcManipulationDrawer.DrawSecondaryId(ref _imcIdentifier, width); + change |= ImcMetaDrawer.DrawSecondaryId(ref _imcIdentifier, width); ImUtf8.SameLineInner(); - change |= ImcManipulationDrawer.DrawSlot(ref _imcIdentifier, quarterWidth); + change |= ImcMetaDrawer.DrawSlot(ref _imcIdentifier, quarterWidth); ImUtf8.SameLineInner(); - change |= ImcManipulationDrawer.DrawVariant(ref _imcIdentifier, quarterWidth); + change |= ImcMetaDrawer.DrawVariant(ref _imcIdentifier, quarterWidth); } else { - change |= ImcManipulationDrawer.DrawSlot(ref _imcIdentifier, width); + change |= ImcMetaDrawer.DrawSlot(ref _imcIdentifier, width); ImUtf8.SameLineInner(); - change |= ImcManipulationDrawer.DrawVariant(ref _imcIdentifier, width); + change |= ImcMetaDrawer.DrawVariant(ref _imcIdentifier, width); } if (change) diff --git a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs index 5c8edce6..5d10febd 100644 --- a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs @@ -7,6 +7,7 @@ using Penumbra.Mods.Groups; using Penumbra.Mods.Manager.OptionEditor; using Penumbra.Mods.SubMods; +using Penumbra.UI.AdvancedWindow.Meta; namespace Penumbra.UI.ModsTab.Groups; @@ -37,9 +38,9 @@ public void Draw() ImGui.SameLine(); using (ImUtf8.Group()) { - changes |= ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref entry, true); - changes |= ImcManipulationDrawer.DrawVfxId(defaultEntry, ref entry, true); - changes |= ImcManipulationDrawer.DrawDecalId(defaultEntry, ref entry, true); + changes |= ImcMetaDrawer.DrawMaterialId(defaultEntry, ref entry, true); + changes |= ImcMetaDrawer.DrawVfxId(defaultEntry, ref entry, true); + changes |= ImcMetaDrawer.DrawDecalId(defaultEntry, ref entry, true); } ImGui.SameLine(0, editor.PriorityWidth); @@ -54,8 +55,8 @@ public void Draw() using (ImUtf8.Group()) { - changes |= ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref entry, true); - changes |= ImcManipulationDrawer.DrawSoundId(defaultEntry, ref entry, true); + changes |= ImcMetaDrawer.DrawMaterialAnimationId(defaultEntry, ref entry, true); + changes |= ImcMetaDrawer.DrawSoundId(defaultEntry, ref entry, true); var canBeDisabled = group.CanBeDisabled; if (ImUtf8.Checkbox("##disabled"u8, ref canBeDisabled)) editor.ModManager.OptionEditor.ImcEditor.ChangeCanBeDisabled(group, canBeDisabled); diff --git a/Penumbra/UI/ModsTab/ImcManipulationDrawer.cs b/Penumbra/UI/ModsTab/ImcManipulationDrawer.cs index 694ae11c..1291f568 100644 --- a/Penumbra/UI/ModsTab/ImcManipulationDrawer.cs +++ b/Penumbra/UI/ModsTab/ImcManipulationDrawer.cs @@ -10,210 +10,5 @@ namespace Penumbra.UI.ModsTab; public static class ImcManipulationDrawer { - public static bool DrawObjectType(ref ImcIdentifier identifier, float width = 110) - { - var ret = Combos.ImcType("##imcType", identifier.ObjectType, out var type, width); - ImUtf8.HoverTooltip("Object Type"u8); - - if (ret) - { - var equipSlot = type switch - { - ObjectType.Equipment => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head, - ObjectType.DemiHuman => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head, - ObjectType.Accessory => identifier.EquipSlot.IsAccessory() ? identifier.EquipSlot : EquipSlot.Ears, - _ => EquipSlot.Unknown, - }; - identifier = identifier with - { - ObjectType = type, - EquipSlot = equipSlot, - SecondaryId = identifier.SecondaryId == 0 ? 1 : identifier.SecondaryId, - }; - } - - return ret; - } - - public static bool DrawPrimaryId(ref ImcIdentifier identifier, float unscaledWidth = 80) - { - var ret = IdInput("##imcPrimaryId"u8, unscaledWidth, identifier.PrimaryId.Id, out var newId, 0, ushort.MaxValue, - identifier.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) - identifier = identifier with { PrimaryId = newId }; - return ret; - } - - public static bool DrawSecondaryId(ref ImcIdentifier identifier, float unscaledWidth = 100) - { - var ret = IdInput("##imcSecondaryId"u8, unscaledWidth, identifier.SecondaryId.Id, out var newId, 0, ushort.MaxValue, false); - ImUtf8.HoverTooltip("Secondary ID"u8); - if (ret) - identifier = identifier with { SecondaryId = newId }; - return ret; - } - - public static bool DrawVariant(ref ImcIdentifier identifier, float unscaledWidth = 45) - { - var ret = IdInput("##imcVariant"u8, unscaledWidth, identifier.Variant.Id, out var newId, 0, byte.MaxValue, false); - ImUtf8.HoverTooltip("Variant ID"u8); - if (ret) - identifier = identifier with { Variant = (byte)newId }; - return ret; - } - - public static bool DrawSlot(ref ImcIdentifier identifier, float unscaledWidth = 100) - { - bool ret; - EquipSlot slot; - switch (identifier.ObjectType) - { - case ObjectType.Equipment: - case ObjectType.DemiHuman: - ret = Combos.EqpEquipSlot("##slot", identifier.EquipSlot, out slot, unscaledWidth); - break; - case ObjectType.Accessory: - ret = Combos.AccessorySlot("##slot", identifier.EquipSlot, out slot, unscaledWidth); - break; - default: return false; - } - - ImUtf8.HoverTooltip("Equip Slot"u8); - if (ret) - identifier = identifier with { EquipSlot = slot }; - return ret; - } - - public static bool DrawMaterialId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45) - { - if (!DragInput("##materialId"u8, "Material ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.MaterialId, defaultEntry.MaterialId, - out var newValue, (byte)1, byte.MaxValue, 0.01f, addDefault)) - return false; - - entry = entry with { MaterialId = newValue }; - return true; - } - - public static bool DrawMaterialAnimationId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45) - { - if (!DragInput("##mAnimId"u8, "Material Animation ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.MaterialAnimationId, - defaultEntry.MaterialAnimationId, out var newValue, (byte)0, byte.MaxValue, 0.01f, addDefault)) - return false; - - entry = entry with { MaterialAnimationId = newValue }; - return true; - } - - public static bool DrawDecalId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45) - { - if (!DragInput("##decalId"u8, "Decal ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.DecalId, defaultEntry.DecalId, out var newValue, - (byte)0, byte.MaxValue, 0.01f, addDefault)) - return false; - - entry = entry with { DecalId = newValue }; - return true; - } - - public static bool DrawVfxId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45) - { - if (!DragInput("##vfxId"u8, "VFX ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.VfxId, defaultEntry.VfxId, out var newValue, (byte)0, - byte.MaxValue, 0.01f, addDefault)) - return false; - - entry = entry with { VfxId = newValue }; - return true; - } - - public static bool DrawSoundId(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault, float unscaledWidth = 45) - { - if (!DragInput("##soundId"u8, "Sound ID"u8, unscaledWidth * ImUtf8.GlobalScale, entry.SoundId, defaultEntry.SoundId, out var newValue, - (byte)0, byte.MaxValue, 0.01f, addDefault)) - return false; - - entry = entry with { SoundId = newValue }; - return true; - } - - public static bool DrawAttributes(ImcEntry defaultEntry, ref ImcEntry entry) - { - var changes = false; - for (var i = 0; i < ImcEntry.NumAttributes; ++i) - { - using var id = ImRaii.PushId(i); - var flag = 1 << i; - var value = (entry.AttributeMask & flag) != 0; - var def = (defaultEntry.AttributeMask & flag) != 0; - if (Checkmark("##attribute"u8, "ABCDEFGHIJ"u8.Slice(i, 1), value, def, out var newValue)) - { - var newMask = (ushort)(newValue ? entry.AttributeMask | flag : entry.AttributeMask & ~flag); - entry = entry with { AttributeMask = newMask }; - changes = true; - } - - if (i < ImcEntry.NumAttributes - 1) - ImGui.SameLine(); - } - - return changes; - } - - - /// - /// A number input for ids with an 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; - } - - /// - /// A dragging int input of given width that compares against a default value, shows a tooltip and clamps against min and max. - /// Returns true if newValue changed against currentValue. - /// - private static bool DragInput(ReadOnlySpan label, ReadOnlySpan tooltip, float width, T currentValue, T defaultValue, - out T newValue, T minValue, T maxValue, float speed, bool addDefault) where T : unmanaged, INumber - { - newValue = currentValue; - using var color = ImRaii.PushColor(ImGuiCol.FrameBg, - defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), - defaultValue != currentValue); - ImGui.SetNextItemWidth(width); - if (ImUtf8.DragScalar(label, ref newValue, minValue, maxValue, speed)) - newValue = newValue <= minValue ? minValue : newValue >= maxValue ? maxValue : newValue; - - if (addDefault) - ImUtf8.HoverTooltip($"{tooltip}\nDefault Value: {defaultValue}"); - else - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip); - - return newValue != currentValue; - } - - /// - /// A checkmark that compares against a default value and shows a tooltip. - /// Returns true if newValue is changed against currentValue. - /// - private static bool Checkmark(ReadOnlySpan label, ReadOnlySpan tooltip, bool currentValue, bool defaultValue, - out bool newValue) - { - using var color = ImRaii.PushColor(ImGuiCol.FrameBg, - defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), - defaultValue != currentValue); - newValue = currentValue; - ImUtf8.Checkbox(label, ref newValue); - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip); - return newValue != currentValue; - } + } diff --git a/Penumbra/Util/DictionaryExtensions.cs b/Penumbra/Util/DictionaryExtensions.cs index abf715e6..f7aa5598 100644 --- a/Penumbra/Util/DictionaryExtensions.cs +++ b/Penumbra/Util/DictionaryExtensions.cs @@ -45,6 +45,18 @@ public static void SetTo(this Dictionary lhs, IReadO lhs.Add(key, value); } + /// Set all entries in the right-hand dictionary to the same values in the left-hand dictionary, ensuring capacity beforehand. + public static void UpdateTo(this Dictionary lhs, IReadOnlyDictionary rhs) + where TKey : notnull + { + if (ReferenceEquals(lhs, rhs)) + return; + + lhs.EnsureCapacity(rhs.Count); + foreach (var (key, value) in rhs) + lhs[key] = value; + } + /// Set one set to the other, deleting previous entries and ensuring capacity beforehand. public static void SetTo(this HashSet lhs, IReadOnlySet rhs) { From 3170edfeb659f0362d46f56fde9c000ee54ba0ee Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 14 Jun 2024 13:38:36 +0200 Subject: [PATCH 10/28] Get rid off all MetaManipulation things. --- Penumbra/Api/Api/MetaApi.cs | 28 +- Penumbra/Api/Api/TemporaryApi.cs | 4 +- Penumbra/Api/IpcTester/TemporaryIpcTester.cs | 8 +- Penumbra/Collections/Cache/CmpCache.cs | 56 -- Penumbra/Collections/Cache/CollectionCache.cs | 40 +- .../Collections/Cache/CollectionModData.cs | 12 +- Penumbra/Collections/Cache/EqdpCache.cs | 85 +- Penumbra/Collections/Cache/EqpCache.cs | 77 +- Penumbra/Collections/Cache/EstCache.cs | 132 ++- .../Cache}/GlobalEqpCache.cs | 44 +- Penumbra/Collections/Cache/GmpCache.cs | 74 +- Penumbra/Collections/Cache/IMetaCache.cs | 89 ++ Penumbra/Collections/Cache/ImcCache.cs | 153 ++-- Penumbra/Collections/Cache/MetaCache.cs | 253 ++---- Penumbra/Collections/Cache/RspCache.cs | 81 ++ Penumbra/Import/Models/ModelManager.cs | 23 +- .../Import/TexToolsMeta.Deserialization.cs | 61 +- Penumbra/Import/TexToolsMeta.Export.cs | 322 ++++--- Penumbra/Import/TexToolsMeta.Rgsp.cs | 17 +- Penumbra/Import/TexToolsMeta.cs | 25 +- .../PathResolving/CollectionResolver.cs | 1 - .../ResolveContext.PathResolution.cs | 2 +- Penumbra/Meta/ImcChecker.cs | 4 - Penumbra/Meta/Manipulations/Eqdp.cs | 9 + .../Meta/Manipulations/EqdpManipulation.cs | 110 --- Penumbra/Meta/Manipulations/Eqp.cs | 3 + .../Meta/Manipulations/EqpManipulation.cs | 80 -- Penumbra/Meta/Manipulations/Est.cs | 16 + .../Meta/Manipulations/EstManipulation.cs | 108 --- .../Manipulations/GlobalEqpManipulation.cs | 26 +- Penumbra/Meta/Manipulations/Gmp.cs | 3 + .../Meta/Manipulations/GmpManipulation.cs | 58 -- .../Meta/Manipulations/IMetaIdentifier.cs | 16 + Penumbra/Meta/Manipulations/Imc.cs | 6 +- .../Meta/Manipulations/ImcManipulation.cs | 108 --- Penumbra/Meta/Manipulations/MetaDictionary.cs | 338 +++++-- .../Meta/Manipulations/MetaManipulation.cs | 457 ---------- Penumbra/Meta/Manipulations/Rsp.cs | 3 + .../Meta/Manipulations/RspManipulation.cs | 67 -- Penumbra/Mods/Editor/IMod.cs | 2 +- Penumbra/Mods/Editor/ModMetaEditor.cs | 24 +- Penumbra/Mods/ItemSwap/EquipmentSwap.cs | 41 +- Penumbra/Mods/ItemSwap/ItemSwap.cs | 29 +- Penumbra/Mods/ItemSwap/ItemSwapContainer.cs | 4 +- Penumbra/Mods/ModCreator.cs | 6 +- Penumbra/Mods/SubMods/DefaultSubMod.cs | 2 +- Penumbra/Mods/SubMods/OptionSubMod.cs | 2 +- Penumbra/Mods/TemporaryMod.cs | 3 +- .../UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs | 159 ++++ .../UI/AdvancedWindow/Meta/EqpMetaDrawer.cs | 134 +++ .../UI/AdvancedWindow/Meta/EstMetaDrawer.cs | 147 +++ .../Meta/GlobalEqpMetaDrawer.cs | 111 +++ .../UI/AdvancedWindow/Meta/GmpMetaDrawer.cs | 148 +++ .../UI/AdvancedWindow/Meta/ImcMetaDrawer.cs | 162 ++-- Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs | 154 ++++ .../UI/AdvancedWindow/Meta/MetaDrawers.cs | 35 + .../UI/AdvancedWindow/Meta/RspMetaDrawer.cs | 112 +++ .../UI/AdvancedWindow/ModEditWindow.Meta.cs | 847 +----------------- .../ModEditWindow.Models.MdlTab.cs | 6 +- Penumbra/UI/AdvancedWindow/ModEditWindow.cs | 4 +- Penumbra/UI/ModsTab/ModPanelConflictsTab.cs | 6 +- Penumbra/UI/Tabs/EffectiveTab.cs | 10 +- Penumbra/Util/IdentifierExtensions.cs | 94 +- 63 files changed, 2408 insertions(+), 2833 deletions(-) delete mode 100644 Penumbra/Collections/Cache/CmpCache.cs rename Penumbra/{Meta/Manipulations => Collections/Cache}/GlobalEqpCache.cs (75%) create mode 100644 Penumbra/Collections/Cache/IMetaCache.cs create mode 100644 Penumbra/Collections/Cache/RspCache.cs delete mode 100644 Penumbra/Meta/Manipulations/EqdpManipulation.cs delete mode 100644 Penumbra/Meta/Manipulations/EqpManipulation.cs delete mode 100644 Penumbra/Meta/Manipulations/EstManipulation.cs delete mode 100644 Penumbra/Meta/Manipulations/GmpManipulation.cs delete mode 100644 Penumbra/Meta/Manipulations/ImcManipulation.cs delete mode 100644 Penumbra/Meta/Manipulations/MetaManipulation.cs delete mode 100644 Penumbra/Meta/Manipulations/RspManipulation.cs create mode 100644 Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs create mode 100644 Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs create mode 100644 Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs create mode 100644 Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs create mode 100644 Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs create mode 100644 Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs create mode 100644 Penumbra/UI/AdvancedWindow/Meta/MetaDrawers.cs create mode 100644 Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs diff --git a/Penumbra/Api/Api/MetaApi.cs b/Penumbra/Api/Api/MetaApi.cs index c467df58..ce1a9def 100644 --- a/Penumbra/Api/Api/MetaApi.cs +++ b/Penumbra/Api/Api/MetaApi.cs @@ -1,5 +1,8 @@ +using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Services; +using Penumbra.Collections; +using Penumbra.GameData.Structs; using Penumbra.Interop.PathResolving; using Penumbra.Meta.Manipulations; @@ -7,17 +10,34 @@ namespace Penumbra.Api.Api; public class MetaApi(CollectionResolver collectionResolver, ApiHelpers helpers) : IPenumbraApiMeta, IApiService { + public const int CurrentVersion = 0; + public string GetPlayerMetaManipulations() { var collection = collectionResolver.PlayerCollection(); - var set = collection.MetaCache?.Manipulations.ToArray() ?? []; - return Functions.ToCompressedBase64(set, MetaManipulation.CurrentVersion); + return CompressMetaManipulations(collection); } public string GetMetaManipulations(int gameObjectIdx) { helpers.AssociatedCollection(gameObjectIdx, out var collection); - var set = collection.MetaCache?.Manipulations.ToArray() ?? []; - return Functions.ToCompressedBase64(set, MetaManipulation.CurrentVersion); + return CompressMetaManipulations(collection); + } + + internal static string CompressMetaManipulations(ModCollection collection) + { + var array = new JArray(); + if (collection.MetaCache is { } cache) + { + MetaDictionary.SerializeTo(array, cache.GlobalEqp.Select(kvp => kvp.Key)); + MetaDictionary.SerializeTo(array, cache.Imc.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); + MetaDictionary.SerializeTo(array, cache.Eqp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); + MetaDictionary.SerializeTo(array, cache.Eqdp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); + MetaDictionary.SerializeTo(array, cache.Est.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); + MetaDictionary.SerializeTo(array, cache.Rsp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); + MetaDictionary.SerializeTo(array, cache.Gmp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); + } + + return Functions.ToCompressedBase64(array, CurrentVersion); } } diff --git a/Penumbra/Api/Api/TemporaryApi.cs b/Penumbra/Api/Api/TemporaryApi.cs index 49958a0d..0894a8e5 100644 --- a/Penumbra/Api/Api/TemporaryApi.cs +++ b/Penumbra/Api/Api/TemporaryApi.cs @@ -163,11 +163,11 @@ private static bool ConvertManips(string manipString, [NotNullWhen(true)] out Me { if (manipString.Length == 0) { - manips = []; + manips = new MetaDictionary(); return true; } - if (Functions.FromCompressedBase64(manipString, out manips!) == MetaManipulation.CurrentVersion) + if (Functions.FromCompressedBase64(manipString, out manips!) == MetaApi.CurrentVersion) return true; manips = null; diff --git a/Penumbra/Api/IpcTester/TemporaryIpcTester.cs b/Penumbra/Api/IpcTester/TemporaryIpcTester.cs index 15601867..0aa6821c 100644 --- a/Penumbra/Api/IpcTester/TemporaryIpcTester.cs +++ b/Penumbra/Api/IpcTester/TemporaryIpcTester.cs @@ -5,6 +5,7 @@ using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; +using Penumbra.Api.Api; using Penumbra.Api.Enums; using Penumbra.Api.IpcSubscribers; using Penumbra.Collections.Manager; @@ -102,8 +103,7 @@ public void Draw() && copyCollection is { HasCache: true }) { var files = copyCollection.ResolvedFiles.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.Path.ToString()); - var manips = Functions.ToCompressedBase64(copyCollection.MetaCache?.Manipulations.ToArray() ?? Array.Empty(), - MetaManipulation.CurrentVersion); + var manips = MetaApi.CompressMetaManipulations(copyCollection); _lastTempError = new AddTemporaryMod(pi).Invoke(_tempModName, guid, files, manips, 999); } @@ -188,8 +188,8 @@ void PrintList(string collectionName, IReadOnlyList list) if (ImGui.IsItemHovered()) { using var tt = ImRaii.Tooltip(); - foreach (var manip in mod.Default.Manipulations) - ImGui.TextUnformatted(manip.ToString()); + foreach (var identifier in mod.Default.Manipulations.Identifiers) + ImGui.TextUnformatted(identifier.ToString()); } } } diff --git a/Penumbra/Collections/Cache/CmpCache.cs b/Penumbra/Collections/Cache/CmpCache.cs deleted file mode 100644 index 470cadd4..00000000 --- a/Penumbra/Collections/Cache/CmpCache.cs +++ /dev/null @@ -1,56 +0,0 @@ -using OtterGui.Filesystem; -using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; -using Penumbra.Meta; -using Penumbra.Meta.Files; -using Penumbra.Meta.Manipulations; - -namespace Penumbra.Collections.Cache; - -public struct CmpCache : IDisposable -{ - private CmpFile? _cmpFile = null; - private readonly List _cmpManipulations = new(); - - public CmpCache() - { } - - public void SetFiles(MetaFileManager manager) - => manager.SetFile(_cmpFile, MetaIndex.HumanCmp); - - public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager) - => manager.TemporarilySetFile(_cmpFile, MetaIndex.HumanCmp); - - public void Reset() - { - if (_cmpFile == null) - return; - - _cmpFile.Reset(_cmpManipulations.Select(m => (m.SubRace, m.Attribute))); - _cmpManipulations.Clear(); - } - - public bool ApplyMod(MetaFileManager manager, RspManipulation manip) - { - _cmpManipulations.AddOrReplace(manip); - _cmpFile ??= new CmpFile(manager); - return manip.Apply(_cmpFile); - } - - public bool RevertMod(MetaFileManager manager, RspManipulation manip) - { - if (!_cmpManipulations.Remove(manip)) - return false; - - var def = CmpFile.GetDefault(manager, manip.SubRace, manip.Attribute); - manip = new RspManipulation(manip.SubRace, manip.Attribute, def); - return manip.Apply(_cmpFile!); - } - - public void Dispose() - { - _cmpFile?.Dispose(); - _cmpFile = null; - _cmpManipulations.Clear(); - } -} diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs index 4d8d0b4a..fd801d3b 100644 --- a/Penumbra/Collections/Cache/CollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -36,7 +36,7 @@ public IEnumerable> AllConflicts => ConflictDict.Values; public SingleArray Conflicts(IMod mod) - => ConflictDict.TryGetValue(mod, out var c) ? c : new SingleArray(); + => ConflictDict.TryGetValue(mod, out SingleArray c) ? c : new SingleArray(); private int _changedItemsSaveCounter = -1; @@ -233,8 +233,20 @@ internal void AddModSync(IMod mod, bool addMetaChanges) foreach (var (path, file) in files.FileRedirections) AddFile(path, file, mod); - foreach (var manip in files.Manipulations) - AddManipulation(manip, mod); + foreach (var (identifier, entry) in files.Manipulations.Eqp) + AddManipulation(mod, identifier, entry); + foreach (var (identifier, entry) in files.Manipulations.Eqdp) + AddManipulation(mod, identifier, entry); + foreach (var (identifier, entry) in files.Manipulations.Est) + AddManipulation(mod, identifier, entry); + foreach (var (identifier, entry) in files.Manipulations.Gmp) + AddManipulation(mod, identifier, entry); + foreach (var (identifier, entry) in files.Manipulations.Rsp) + AddManipulation(mod, identifier, entry); + foreach (var (identifier, entry) in files.Manipulations.Imc) + AddManipulation(mod, identifier, entry); + foreach (var identifier in files.Manipulations.GlobalEqp) + AddManipulation(mod, identifier, null!); if (addMetaChanges) { @@ -342,7 +354,7 @@ private bool AddConflict(object data, IMod addedMod, IMod existingMod) foreach (var conflict in tmpConflicts) { if (data is Utf8GamePath path && conflict.Conflicts.RemoveAll(p => p is Utf8GamePath x && x.Equals(path)) > 0 - || data is MetaManipulation meta && conflict.Conflicts.RemoveAll(m => m is MetaManipulation x && x.Equals(meta)) > 0) + || data is IMetaIdentifier meta && conflict.Conflicts.RemoveAll(m => m.Equals(meta)) > 0) AddConflict(data, addedMod, conflict.Mod2); } @@ -374,12 +386,12 @@ private bool AddConflict(object data, IMod addedMod, IMod existingMod) // For different mods, higher mod priority takes precedence before option group priority, // which takes precedence before option priority, which takes precedence before ordering. // Inside the same mod, conflicts are not recorded. - private void AddManipulation(MetaManipulation manip, IMod mod) + private void AddManipulation(IMod mod, IMetaIdentifier identifier, object entry) { - if (!Meta.TryGetValue(manip, out var existingMod)) + if (!Meta.TryGetMod(identifier, out var existingMod)) { - Meta.ApplyMod(manip, mod); - ModData.AddManip(mod, manip); + Meta.ApplyMod(mod, identifier, entry); + ModData.AddManip(mod, identifier); return; } @@ -387,11 +399,11 @@ private void AddManipulation(MetaManipulation manip, IMod mod) if (mod == existingMod) return; - if (AddConflict(manip, mod, existingMod)) + if (AddConflict(identifier, mod, existingMod)) { - ModData.RemoveManip(existingMod, manip); - Meta.ApplyMod(manip, mod); - ModData.AddManip(mod, manip); + ModData.RemoveManip(existingMod, identifier); + Meta.ApplyMod(mod, identifier, entry); + ModData.AddManip(mod, identifier); } } @@ -437,9 +449,9 @@ void AddItems(IMod mod) AddItems(modPath.Mod); } - foreach (var (manip, mod) in Meta) + foreach (var (manip, mod) in Meta.IdentifierSources) { - identifier.MetaChangedItems(items, manip); + manip.AddChangedItems(identifier, items); AddItems(mod); } diff --git a/Penumbra/Collections/Cache/CollectionModData.cs b/Penumbra/Collections/Cache/CollectionModData.cs index d0a3bc76..295191d2 100644 --- a/Penumbra/Collections/Cache/CollectionModData.cs +++ b/Penumbra/Collections/Cache/CollectionModData.cs @@ -9,12 +9,12 @@ namespace Penumbra.Collections.Cache; /// public class CollectionModData { - private readonly Dictionary, HashSet)> _data = new(); + private readonly Dictionary, HashSet)> _data = new(); - public IEnumerable<(IMod, IReadOnlySet, IReadOnlySet)> Data - => _data.Select(kvp => (kvp.Key, (IReadOnlySet)kvp.Value.Item1, (IReadOnlySet)kvp.Value.Item2)); + public IEnumerable<(IMod, IReadOnlySet, IReadOnlySet)> Data + => _data.Select(kvp => (kvp.Key, (IReadOnlySet)kvp.Value.Item1, (IReadOnlySet)kvp.Value.Item2)); - public (IReadOnlyCollection Paths, IReadOnlyCollection Manipulations) RemoveMod(IMod mod) + public (IReadOnlyCollection Paths, IReadOnlyCollection Manipulations) RemoveMod(IMod mod) { if (_data.Remove(mod, out var data)) return data; @@ -35,7 +35,7 @@ public void AddPath(IMod mod, Utf8GamePath path) } } - public void AddManip(IMod mod, MetaManipulation manipulation) + public void AddManip(IMod mod, IMetaIdentifier manipulation) { if (_data.TryGetValue(mod, out var data)) { @@ -54,7 +54,7 @@ public void RemovePath(IMod mod, Utf8GamePath path) _data.Remove(mod); } - public void RemoveManip(IMod mod, MetaManipulation manip) + public void RemoveManip(IMod mod, IMetaIdentifier manip) { if (_data.TryGetValue(mod, out var data) && data.Item2.Remove(manip) && data.Item1.Count == 0 && data.Item2.Count == 0) _data.Remove(mod); diff --git a/Penumbra/Collections/Cache/EqdpCache.cs b/Penumbra/Collections/Cache/EqdpCache.cs index a0f27c23..f3475c7e 100644 --- a/Penumbra/Collections/Cache/EqdpCache.cs +++ b/Penumbra/Collections/Cache/EqdpCache.cs @@ -1,5 +1,5 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using OtterGui; -using OtterGui.Filesystem; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Services; @@ -10,28 +10,38 @@ namespace Penumbra.Collections.Cache; -public readonly struct EqdpCache : IDisposable +public sealed class EqdpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private readonly ExpandedEqdpFile?[] _eqdpFiles = new ExpandedEqdpFile[CharacterUtilityData.EqdpIndices.Length]; // TODO: female Hrothgar - private readonly List _eqdpManipulations = new(); + private readonly ExpandedEqdpFile?[] _eqdpFiles = new ExpandedEqdpFile[CharacterUtilityData.EqdpIndices.Length]; // TODO: female Hrothgar - public EqdpCache() - { } - - public void SetFiles(MetaFileManager manager) + public override void SetFiles() { for (var i = 0; i < CharacterUtilityData.EqdpIndices.Length; ++i) - manager.SetFile(_eqdpFiles[i], CharacterUtilityData.EqdpIndices[i]); + Manager.SetFile(_eqdpFiles[i], CharacterUtilityData.EqdpIndices[i]); } - public void SetFile(MetaFileManager manager, MetaIndex index) + public void SetFile(MetaIndex index) { var i = CharacterUtilityData.EqdpIndices.IndexOf(index); if (i != -1) - manager.SetFile(_eqdpFiles[i], index); + Manager.SetFile(_eqdpFiles[i], index); } - public MetaList.MetaReverter? TemporarilySetFiles(MetaFileManager manager, GenderRace genderRace, bool accessory) + public override void ResetFiles() + => Manager.SetFile(null, MetaIndex.Eqp); + + protected override void IncorporateChangesInternal() + { + foreach (var (identifier, (_, entry)) in this) + Apply(GetFile(identifier)!, identifier, entry); + + Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed EQDP manipulations."); + } + + public ExpandedEqdpFile? EqdpFile(GenderRace race, bool accessory) + => _eqdpFiles[Array.IndexOf(CharacterUtilityData.EqdpIndices, CharacterUtilityData.EqdpIdx(race, accessory))]; // TODO: female Hrothgar + + public MetaList.MetaReverter? TemporarilySetFile(GenderRace genderRace, bool accessory) { var idx = CharacterUtilityData.EqdpIdx(genderRace, accessory); if (idx < 0) @@ -47,44 +57,44 @@ public void SetFile(MetaFileManager manager, MetaIndex index) return null; } - return manager.TemporarilySetFile(_eqdpFiles[i], idx); + return Manager.TemporarilySetFile(_eqdpFiles[i], idx); } - public void Reset() + public override void Reset() { foreach (var file in _eqdpFiles.OfType()) { var relevant = CharacterUtility.RelevantIndices[file.Index.Value]; - file.Reset(_eqdpManipulations.Where(m => m.FileIndex() == relevant).Select(m => (PrimaryId)m.SetId)); + file.Reset(Keys.Where(m => m.FileIndex() == relevant).Select(m => m.SetId)); } - _eqdpManipulations.Clear(); + Clear(); + } + + protected override void ApplyModInternal(EqdpIdentifier identifier, EqdpEntry entry) + { + if (GetFile(identifier) is { } file) + Apply(file, identifier, entry); } - public bool ApplyMod(MetaFileManager manager, EqdpManipulation manip) + protected override void RevertModInternal(EqdpIdentifier identifier) { - _eqdpManipulations.AddOrReplace(manip); - var file = _eqdpFiles[Array.IndexOf(CharacterUtilityData.EqdpIndices, manip.FileIndex())] ??= - new ExpandedEqdpFile(manager, Names.CombinedRace(manip.Gender, manip.Race), manip.Slot.IsAccessory()); // TODO: female Hrothgar - return manip.Apply(file); + if (GetFile(identifier) is { } file) + Apply(file, identifier, ExpandedEqdpFile.GetDefault(Manager, identifier)); } - public bool RevertMod(MetaFileManager manager, EqdpManipulation manip) + public static bool Apply(ExpandedEqdpFile file, EqdpIdentifier identifier, EqdpEntry entry) { - if (!_eqdpManipulations.Remove(manip)) + var origEntry = file[identifier.SetId]; + var mask = Eqdp.Mask(identifier.Slot); + if ((origEntry & mask) == entry) return false; - var def = ExpandedEqdpFile.GetDefault(manager, Names.CombinedRace(manip.Gender, manip.Race), manip.Slot.IsAccessory(), manip.SetId); - var file = _eqdpFiles[Array.IndexOf(CharacterUtilityData.EqdpIndices, manip.FileIndex())]!; - manip = new EqdpManipulation(def, manip.Slot, manip.Gender, manip.Race, manip.SetId); - return manip.Apply(file); + file[identifier.SetId] = (entry & ~mask) | origEntry; + return true; } - public ExpandedEqdpFile? EqdpFile(GenderRace race, bool accessory) - => _eqdpFiles - [Array.IndexOf(CharacterUtilityData.EqdpIndices, CharacterUtilityData.EqdpIdx(race, accessory))]; // TODO: female Hrothgar - - public void Dispose() + protected override void Dispose(bool _) { for (var i = 0; i < _eqdpFiles.Length; ++i) { @@ -92,6 +102,15 @@ public void Dispose() _eqdpFiles[i] = null; } - _eqdpManipulations.Clear(); + Clear(); + } + + private ExpandedEqdpFile? GetFile(EqdpIdentifier identifier) + { + if (!Manager.CharacterUtility.Ready) + return null; + + var index = Array.IndexOf(CharacterUtilityData.EqdpIndices, identifier.FileIndex()); + return _eqdpFiles[index] ??= new ExpandedEqdpFile(Manager, identifier.GenderRace, identifier.Slot.IsAccessory()); } } diff --git a/Penumbra/Collections/Cache/EqpCache.cs b/Penumbra/Collections/Cache/EqpCache.cs index 972ee5a5..599ae588 100644 --- a/Penumbra/Collections/Cache/EqpCache.cs +++ b/Penumbra/Collections/Cache/EqpCache.cs @@ -1,4 +1,4 @@ -using OtterGui.Filesystem; +using Penumbra.GameData.Structs; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; using Penumbra.Meta; @@ -7,54 +7,77 @@ namespace Penumbra.Collections.Cache; -public struct EqpCache : IDisposable +public sealed class EqpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private ExpandedEqpFile? _eqpFile = null; - private readonly List _eqpManipulations = new(); + private ExpandedEqpFile? _eqpFile; - public EqpCache() - { } + public override void SetFiles() + => Manager.SetFile(_eqpFile, MetaIndex.Eqp); - public void SetFiles(MetaFileManager manager) - => manager.SetFile(_eqpFile, MetaIndex.Eqp); + public override void ResetFiles() + => Manager.SetFile(null, MetaIndex.Eqp); - public static void ResetFiles(MetaFileManager manager) - => manager.SetFile(null, MetaIndex.Eqp); + protected override void IncorporateChangesInternal() + { + if (GetFile() is not { } file) + return; - public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager) - => manager.TemporarilySetFile(_eqpFile, MetaIndex.Eqp); + foreach (var (identifier, (_, entry)) in this) + Apply(file, identifier, entry); + + Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed EQP manipulations."); + } - public void Reset() + public MetaList.MetaReverter TemporarilySetFile() + => Manager.TemporarilySetFile(_eqpFile, MetaIndex.Eqp); + + public override void Reset() { if (_eqpFile == null) return; - _eqpFile.Reset(_eqpManipulations.Select(m => m.SetId)); - _eqpManipulations.Clear(); + _eqpFile.Reset(Keys.Select(identifier => identifier.SetId)); + Clear(); } - public bool ApplyMod(MetaFileManager manager, EqpManipulation manip) + protected override void ApplyModInternal(EqpIdentifier identifier, EqpEntry entry) { - _eqpManipulations.AddOrReplace(manip); - _eqpFile ??= new ExpandedEqpFile(manager); - return manip.Apply(_eqpFile); + if (GetFile() is { } file) + Apply(file, identifier, entry); } - public bool RevertMod(MetaFileManager manager, EqpManipulation manip) + protected override void RevertModInternal(EqpIdentifier identifier) { - var idx = _eqpManipulations.FindIndex(manip.Equals); - if (idx < 0) + if (GetFile() is { } file) + Apply(file, identifier, ExpandedEqpFile.GetDefault(Manager, identifier.SetId)); + } + + public static bool Apply(ExpandedEqpFile file, EqpIdentifier identifier, EqpEntry entry) + { + var origEntry = file[identifier.SetId]; + var mask = Eqp.Mask(identifier.Slot); + if ((origEntry & mask) == entry) return false; - var def = ExpandedEqpFile.GetDefault(manager, manip.SetId); - manip = new EqpManipulation(def, manip.Slot, manip.SetId); - return manip.Apply(_eqpFile!); + file[identifier.SetId] = (origEntry & ~mask) | entry; + return true; } - public void Dispose() + protected override void Dispose(bool _) { _eqpFile?.Dispose(); _eqpFile = null; - _eqpManipulations.Clear(); + Clear(); + } + + private ExpandedEqpFile? GetFile() + { + if (_eqpFile != null) + return _eqpFile; + + if (!Manager.CharacterUtility.Ready) + return null; + + return _eqpFile = new ExpandedEqpFile(Manager); } } diff --git a/Penumbra/Collections/Cache/EstCache.cs b/Penumbra/Collections/Cache/EstCache.cs index 3a0b4695..412dd322 100644 --- a/Penumbra/Collections/Cache/EstCache.cs +++ b/Penumbra/Collections/Cache/EstCache.cs @@ -1,6 +1,3 @@ -using OtterGui.Filesystem; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; using Penumbra.Meta; @@ -9,46 +6,41 @@ namespace Penumbra.Collections.Cache; -public struct EstCache : IDisposable +public sealed class EstCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private EstFile? _estFaceFile = null; - private EstFile? _estHairFile = null; - private EstFile? _estBodyFile = null; - private EstFile? _estHeadFile = null; + private EstFile? _estFaceFile; + private EstFile? _estHairFile; + private EstFile? _estBodyFile; + private EstFile? _estHeadFile; - private readonly List _estManipulations = new(); - - public EstCache() - { } - - public void SetFiles(MetaFileManager manager) + public override void SetFiles() { - manager.SetFile(_estFaceFile, MetaIndex.FaceEst); - manager.SetFile(_estHairFile, MetaIndex.HairEst); - manager.SetFile(_estBodyFile, MetaIndex.BodyEst); - manager.SetFile(_estHeadFile, MetaIndex.HeadEst); + Manager.SetFile(_estFaceFile, MetaIndex.FaceEst); + Manager.SetFile(_estHairFile, MetaIndex.HairEst); + Manager.SetFile(_estBodyFile, MetaIndex.BodyEst); + Manager.SetFile(_estHeadFile, MetaIndex.HeadEst); } - public void SetFile(MetaFileManager manager, MetaIndex index) + public void SetFile(MetaIndex index) { switch (index) { case MetaIndex.FaceEst: - manager.SetFile(_estFaceFile, MetaIndex.FaceEst); + Manager.SetFile(_estFaceFile, MetaIndex.FaceEst); break; case MetaIndex.HairEst: - manager.SetFile(_estHairFile, MetaIndex.HairEst); + Manager.SetFile(_estHairFile, MetaIndex.HairEst); break; case MetaIndex.BodyEst: - manager.SetFile(_estBodyFile, MetaIndex.BodyEst); + Manager.SetFile(_estBodyFile, MetaIndex.BodyEst); break; case MetaIndex.HeadEst: - manager.SetFile(_estHeadFile, MetaIndex.HeadEst); + Manager.SetFile(_estHeadFile, MetaIndex.HeadEst); break; } } - public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager, EstType type) + public MetaList.MetaReverter TemporarilySetFiles(EstType type) { var (file, idx) = type switch { @@ -56,74 +48,65 @@ public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager, EstTyp EstType.Hair => (_estHairFile, MetaIndex.HairEst), EstType.Body => (_estBodyFile, MetaIndex.BodyEst), EstType.Head => (_estHeadFile, MetaIndex.HeadEst), - _ => (null, 0), + _ => (null, 0), }; - return manager.TemporarilySetFile(file, idx); + return Manager.TemporarilySetFile(file, idx); } - private readonly EstFile? GetEstFile(EstType type) + public override void ResetFiles() + => Manager.SetFile(null, MetaIndex.Eqp); + + protected override void IncorporateChangesInternal() { - return type switch - { - EstType.Face => _estFaceFile, - EstType.Hair => _estHairFile, - EstType.Body => _estBodyFile, - EstType.Head => _estHeadFile, - _ => null, - }; + if (!Manager.CharacterUtility.Ready) + return; + + foreach (var (identifier, (_, entry)) in this) + Apply(GetFile(identifier)!, identifier, entry); + Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed EST manipulations."); } - internal EstEntry GetEstEntry(MetaFileManager manager, EstType type, GenderRace genderRace, PrimaryId primaryId) + public EstEntry GetEstEntry(EstIdentifier identifier) { - var file = GetEstFile(type); + var file = GetFile(identifier); return file != null - ? file[genderRace, primaryId.Id] - : EstFile.GetDefault(manager, type, genderRace, primaryId); + ? file[identifier.GenderRace, identifier.SetId] + : EstFile.GetDefault(Manager, identifier); } - public void Reset() + public override void Reset() { _estFaceFile?.Reset(); _estHairFile?.Reset(); _estBodyFile?.Reset(); _estHeadFile?.Reset(); - _estManipulations.Clear(); + Clear(); } - public bool ApplyMod(MetaFileManager manager, EstManipulation m) + protected override void ApplyModInternal(EstIdentifier identifier, EstEntry entry) { - _estManipulations.AddOrReplace(m); - var file = m.Slot switch - { - EstType.Hair => _estHairFile ??= new EstFile(manager, EstType.Hair), - EstType.Face => _estFaceFile ??= new EstFile(manager, EstType.Face), - EstType.Body => _estBodyFile ??= new EstFile(manager, EstType.Body), - EstType.Head => _estHeadFile ??= new EstFile(manager, EstType.Head), - _ => throw new ArgumentOutOfRangeException(), - }; - return m.Apply(file); + if (GetFile(identifier) is { } file) + Apply(file, identifier, entry); } - public bool RevertMod(MetaFileManager manager, EstManipulation m) + protected override void RevertModInternal(EstIdentifier identifier) { - if (!_estManipulations.Remove(m)) - return false; + if (GetFile(identifier) is { } file) + Apply(file, identifier, EstFile.GetDefault(Manager, identifier.Slot, identifier.GenderRace, identifier.SetId)); + } - var def = EstFile.GetDefault(manager, m.Slot, Names.CombinedRace(m.Gender, m.Race), m.SetId); - var manip = new EstManipulation(m.Gender, m.Race, m.Slot, m.SetId, def); - var file = m.Slot switch + public static bool Apply(EstFile file, EstIdentifier identifier, EstEntry entry) + => file.SetEntry(identifier.GenderRace, identifier.SetId, entry) switch { - EstType.Hair => _estHairFile!, - EstType.Face => _estFaceFile!, - EstType.Body => _estBodyFile!, - EstType.Head => _estHeadFile!, - _ => throw new ArgumentOutOfRangeException(), + EstFile.EstEntryChange.Unchanged => false, + EstFile.EstEntryChange.Changed => true, + EstFile.EstEntryChange.Added => true, + EstFile.EstEntryChange.Removed => true, + _ => false, }; - return manip.Apply(file); - } - public void Dispose() + protected override void Dispose(bool _) { _estFaceFile?.Dispose(); _estHairFile?.Dispose(); @@ -133,6 +116,21 @@ public void Dispose() _estHairFile = null; _estBodyFile = null; _estHeadFile = null; - _estManipulations.Clear(); + Clear(); + } + + private EstFile? GetFile(EstIdentifier identifier) + { + if (Manager.CharacterUtility.Ready) + return null; + + return identifier.Slot switch + { + EstType.Hair => _estHairFile ??= new EstFile(Manager, EstType.Hair), + EstType.Face => _estFaceFile ??= new EstFile(Manager, EstType.Face), + EstType.Body => _estBodyFile ??= new EstFile(Manager, EstType.Body), + EstType.Head => _estHeadFile ??= new EstFile(Manager, EstType.Head), + _ => null, + }; } } diff --git a/Penumbra/Meta/Manipulations/GlobalEqpCache.cs b/Penumbra/Collections/Cache/GlobalEqpCache.cs similarity index 75% rename from Penumbra/Meta/Manipulations/GlobalEqpCache.cs rename to Penumbra/Collections/Cache/GlobalEqpCache.cs index 26eb1d05..1c80b47d 100644 --- a/Penumbra/Meta/Manipulations/GlobalEqpCache.cs +++ b/Penumbra/Collections/Cache/GlobalEqpCache.cs @@ -1,9 +1,11 @@ using OtterGui.Services; using Penumbra.GameData.Structs; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods.Editor; -namespace Penumbra.Meta.Manipulations; +namespace Penumbra.Collections.Cache; -public struct GlobalEqpCache : IService +public class GlobalEqpCache : Dictionary, IService { private readonly HashSet _doNotHideEarrings = []; private readonly HashSet _doNotHideNecklace = []; @@ -13,11 +15,9 @@ public struct GlobalEqpCache : IService private bool _doNotHideVieraHats; private bool _doNotHideHrothgarHats; - public GlobalEqpCache() - { } - - public void Clear() + public new void Clear() { + base.Clear(); _doNotHideEarrings.Clear(); _doNotHideNecklace.Clear(); _doNotHideBracelets.Clear(); @@ -29,6 +29,9 @@ public void Clear() public unsafe EqpEntry Apply(EqpEntry original, CharacterArmor* armor) { + if (Count == 0) + return original; + if (_doNotHideVieraHats) original |= EqpEntry.HeadShowVieraHat; @@ -52,8 +55,13 @@ public unsafe EqpEntry Apply(EqpEntry original, CharacterArmor* armor) return original; } - public bool Add(GlobalEqpManipulation manipulation) - => manipulation.Type switch + public bool ApplyMod(IMod mod, GlobalEqpManipulation manipulation) + { + if (Remove(manipulation, out var oldMod) && oldMod == mod) + return false; + + this[manipulation] = mod; + _ = manipulation.Type switch { GlobalEqpType.DoNotHideEarrings => _doNotHideEarrings.Add(manipulation.Condition), GlobalEqpType.DoNotHideNecklace => _doNotHideNecklace.Add(manipulation.Condition), @@ -61,12 +69,18 @@ public bool Add(GlobalEqpManipulation manipulation) GlobalEqpType.DoNotHideRingR => _doNotHideRingR.Add(manipulation.Condition), GlobalEqpType.DoNotHideRingL => _doNotHideRingL.Add(manipulation.Condition), GlobalEqpType.DoNotHideHrothgarHats => !_doNotHideHrothgarHats && (_doNotHideHrothgarHats = true), - GlobalEqpType.DoNotHideVieraHats => !_doNotHideVieraHats && (_doNotHideVieraHats = true), - _ => false, + GlobalEqpType.DoNotHideVieraHats => !_doNotHideVieraHats && (_doNotHideVieraHats = true), + _ => false, }; + return true; + } + + public bool RevertMod(GlobalEqpManipulation manipulation, [NotNullWhen(true)] out IMod? mod) + { + if (!Remove(manipulation, out mod)) + return false; - public bool Remove(GlobalEqpManipulation manipulation) - => manipulation.Type switch + _ = manipulation.Type switch { GlobalEqpType.DoNotHideEarrings => _doNotHideEarrings.Remove(manipulation.Condition), GlobalEqpType.DoNotHideNecklace => _doNotHideNecklace.Remove(manipulation.Condition), @@ -74,7 +88,9 @@ public bool Remove(GlobalEqpManipulation manipulation) GlobalEqpType.DoNotHideRingR => _doNotHideRingR.Remove(manipulation.Condition), GlobalEqpType.DoNotHideRingL => _doNotHideRingL.Remove(manipulation.Condition), GlobalEqpType.DoNotHideHrothgarHats => _doNotHideHrothgarHats && !(_doNotHideHrothgarHats = false), - GlobalEqpType.DoNotHideVieraHats => _doNotHideVieraHats && !(_doNotHideVieraHats = false), - _ => false, + GlobalEqpType.DoNotHideVieraHats => _doNotHideVieraHats && !(_doNotHideVieraHats = false), + _ => false, }; + return true; + } } diff --git a/Penumbra/Collections/Cache/GmpCache.cs b/Penumbra/Collections/Cache/GmpCache.cs index 0a713867..1475ffd5 100644 --- a/Penumbra/Collections/Cache/GmpCache.cs +++ b/Penumbra/Collections/Cache/GmpCache.cs @@ -1,4 +1,4 @@ -using OtterGui.Filesystem; +using Penumbra.GameData.Structs; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; using Penumbra.Meta; @@ -7,50 +7,76 @@ namespace Penumbra.Collections.Cache; -public struct GmpCache : IDisposable +public sealed class GmpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private ExpandedGmpFile? _gmpFile = null; - private readonly List _gmpManipulations = new(); + private ExpandedGmpFile? _gmpFile; - public GmpCache() - { } + public override void SetFiles() + => Manager.SetFile(_gmpFile, MetaIndex.Gmp); - public void SetFiles(MetaFileManager manager) - => manager.SetFile(_gmpFile, MetaIndex.Gmp); + public override void ResetFiles() + => Manager.SetFile(null, MetaIndex.Gmp); - public MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager) - => manager.TemporarilySetFile(_gmpFile, MetaIndex.Gmp); + protected override void IncorporateChangesInternal() + { + if (GetFile() is not { } file) + return; - public void Reset() + foreach (var (identifier, (_, entry)) in this) + Apply(file, identifier, entry); + + Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed GMP manipulations."); + } + + public MetaList.MetaReverter TemporarilySetFile() + => Manager.TemporarilySetFile(_gmpFile, MetaIndex.Gmp); + + public override void Reset() { if (_gmpFile == null) return; - _gmpFile.Reset(_gmpManipulations.Select(m => m.SetId)); - _gmpManipulations.Clear(); + _gmpFile.Reset(Keys.Select(identifier => identifier.SetId)); + Clear(); + } + + protected override void ApplyModInternal(GmpIdentifier identifier, GmpEntry entry) + { + if (GetFile() is { } file) + Apply(file, identifier, entry); } - public bool ApplyMod(MetaFileManager manager, GmpManipulation manip) + protected override void RevertModInternal(GmpIdentifier identifier) { - _gmpManipulations.AddOrReplace(manip); - _gmpFile ??= new ExpandedGmpFile(manager); - return manip.Apply(_gmpFile); + if (GetFile() is { } file) + Apply(file, identifier, ExpandedGmpFile.GetDefault(Manager, identifier.SetId)); } - public bool RevertMod(MetaFileManager manager, GmpManipulation manip) + public static bool Apply(ExpandedGmpFile file, GmpIdentifier identifier, GmpEntry entry) { - if (!_gmpManipulations.Remove(manip)) + var origEntry = file[identifier.SetId]; + if (entry == origEntry) return false; - var def = ExpandedGmpFile.GetDefault(manager, manip.SetId); - manip = new GmpManipulation(def, manip.SetId); - return manip.Apply(_gmpFile!); + file[identifier.SetId] = entry; + return true; } - public void Dispose() + protected override void Dispose(bool _) { _gmpFile?.Dispose(); _gmpFile = null; - _gmpManipulations.Clear(); + Clear(); + } + + private ExpandedGmpFile? GetFile() + { + if (_gmpFile != null) + return _gmpFile; + + if (!Manager.CharacterUtility.Ready) + return null; + + return _gmpFile = new ExpandedGmpFile(Manager); } } diff --git a/Penumbra/Collections/Cache/IMetaCache.cs b/Penumbra/Collections/Cache/IMetaCache.cs new file mode 100644 index 00000000..218c1840 --- /dev/null +++ b/Penumbra/Collections/Cache/IMetaCache.cs @@ -0,0 +1,89 @@ +using Penumbra.Meta; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods.Editor; + +namespace Penumbra.Collections.Cache; + +public interface IMetaCache : IDisposable +{ + public void SetFiles(); + public void Reset(); + public void ResetFiles(); + + public int Count { get; } +} + +public abstract class MetaCacheBase + : Dictionary, IMetaCache + where TIdentifier : unmanaged, IMetaIdentifier + where TEntry : unmanaged +{ + public MetaCacheBase(MetaFileManager manager, ModCollection collection) + { + Manager = manager; + Collection = collection; + if (!Manager.CharacterUtility.Ready) + Manager.CharacterUtility.LoadingFinished += IncorporateChanges; + } + + protected readonly MetaFileManager Manager; + protected readonly ModCollection Collection; + + public void Dispose() + { + Manager.CharacterUtility.LoadingFinished -= IncorporateChanges; + Dispose(true); + } + + public abstract void SetFiles(); + public abstract void Reset(); + public abstract void ResetFiles(); + + public bool ApplyMod(IMod source, TIdentifier identifier, TEntry entry) + { + lock (this) + { + if (TryGetValue(identifier, out var pair) && pair.Source == source && EqualityComparer.Default.Equals(pair.Entry, entry)) + return false; + + this[identifier] = (source, entry); + } + + ApplyModInternal(identifier, entry); + return true; + } + + public bool RevertMod(TIdentifier identifier, [NotNullWhen(true)] out IMod? mod) + { + lock (this) + { + if (!Remove(identifier, out var pair)) + { + mod = null; + return false; + } + + mod = pair.Source; + } + + RevertModInternal(identifier); + return true; + } + + private void IncorporateChanges() + { + lock (this) + { + IncorporateChangesInternal(); + } + if (Manager.ActiveCollections.Default == Collection && Manager.Config.EnableMods) + SetFiles(); + } + + protected abstract void ApplyModInternal(TIdentifier identifier, TEntry entry); + protected abstract void RevertModInternal(TIdentifier identifier); + protected abstract void IncorporateChangesInternal(); + + protected virtual void Dispose(bool _) + { } +} diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index 7990122a..3d05e793 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -1,3 +1,6 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; +using Penumbra.Collections.Manager; +using Penumbra.GameData.Structs; using Penumbra.Interop.PathResolving; using Penumbra.Meta; using Penumbra.Meta.Files; @@ -6,116 +9,132 @@ namespace Penumbra.Collections.Cache; -public readonly struct ImcCache : IDisposable +public sealed class ImcCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private readonly Dictionary _imcFiles = []; - private readonly List<(ImcManipulation, ImcFile)> _imcManipulations = []; + private readonly Dictionary)> _imcFiles = []; - public ImcCache() - { } + public override void SetFiles() + => SetFiles(false); - public void SetFiles(ModCollection collection, bool fromFullCompute) + public bool GetFile(Utf8GamePath path, [NotNullWhen(true)] out ImcFile? file) + { + if (!_imcFiles.TryGetValue(path, out var p)) + { + file = null; + return false; + } + + file = p.Item1; + return true; + } + + public void SetFiles(bool fromFullCompute) { if (fromFullCompute) - foreach (var path in _imcFiles.Keys) - collection._cache!.ForceFileSync(path, PathDataHandler.CreateImc(path.Path, collection)); + foreach (var (path, _) in _imcFiles) + Collection._cache!.ForceFileSync(path, PathDataHandler.CreateImc(path.Path, Collection)); else - foreach (var path in _imcFiles.Keys) - collection._cache!.ForceFile(path, PathDataHandler.CreateImc(path.Path, collection)); + foreach (var (path, _) in _imcFiles) + Collection._cache!.ForceFile(path, PathDataHandler.CreateImc(path.Path, Collection)); } - public void Reset(ModCollection collection) + public override void ResetFiles() + { + foreach (var (path, _) in _imcFiles) + Collection._cache!.ForceFile(path, FullPath.Empty); + } + + protected override void IncorporateChangesInternal() + { + if (!Manager.CharacterUtility.Ready) + return; + + foreach (var (identifier, (_, entry)) in this) + ApplyFile(identifier, entry); + + Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed IMC manipulations."); + } + + + public override void Reset() { - foreach (var (path, file) in _imcFiles) + foreach (var (path, (file, set)) in _imcFiles) { - collection._cache!.RemovePath(path); + Collection._cache!.RemovePath(path); file.Reset(); + set.Clear(); } - _imcManipulations.Clear(); + Clear(); } - public bool ApplyMod(MetaFileManager manager, ModCollection collection, ImcManipulation manip) + protected override void ApplyModInternal(ImcIdentifier identifier, ImcEntry entry) { - if (!manip.Validate(true)) - return false; - - var idx = _imcManipulations.FindIndex(p => p.Item1.Equals(manip)); - if (idx < 0) - { - idx = _imcManipulations.Count; - _imcManipulations.Add((manip, null!)); - } + if (Manager.CharacterUtility.Ready) + ApplyFile(identifier, entry); + } - var path = manip.GamePath(); + private void ApplyFile(ImcIdentifier identifier, ImcEntry entry) + { + var path = identifier.GamePath(); try { - if (!_imcFiles.TryGetValue(path, out var file)) - file = new ImcFile(manager, manip.Identifier); + if (!_imcFiles.TryGetValue(path, out var pair)) + pair = (new ImcFile(Manager, identifier), []); - _imcManipulations[idx] = (manip, file); - if (!manip.Apply(file)) - return false; - _imcFiles[path] = file; - var fullPath = PathDataHandler.CreateImc(file.Path.Path, collection); - collection._cache!.ForceFile(path, fullPath); + if (!Apply(pair.Item1, identifier, entry)) + return; - return true; + pair.Item2.Add(identifier); + _imcFiles[path] = pair; + var fullPath = PathDataHandler.CreateImc(pair.Item1.Path.Path, Collection); + Collection._cache!.ForceFile(path, fullPath); } catch (ImcException e) { - manager.ValidityChecker.ImcExceptions.Add(e); + Manager.ValidityChecker.ImcExceptions.Add(e); Penumbra.Log.Error(e.ToString()); } catch (Exception e) { - Penumbra.Log.Error($"Could not apply IMC Manipulation {manip}:\n{e}"); + Penumbra.Log.Error($"Could not apply IMC Manipulation {identifier}:\n{e}"); } - - return false; } - public bool RevertMod(MetaFileManager manager, ModCollection collection, ImcManipulation m) + protected override void RevertModInternal(ImcIdentifier identifier) { - if (!m.Validate(false)) - return false; + var path = identifier.GamePath(); + if (!_imcFiles.TryGetValue(path, out var pair)) + return; - var idx = _imcManipulations.FindIndex(p => p.Item1.Equals(m)); - if (idx < 0) - return false; + if (!pair.Item2.Remove(identifier)) + return; - var (_, file) = _imcManipulations[idx]; - _imcManipulations.RemoveAt(idx); - - if (_imcManipulations.All(p => !ReferenceEquals(p.Item2, file))) + if (pair.Item2.Count == 0) { - _imcFiles.Remove(file.Path); - collection._cache!.ForceFile(file.Path, FullPath.Empty); - file.Dispose(); - return true; + _imcFiles.Remove(path); + Collection._cache!.ForceFile(pair.Item1.Path, FullPath.Empty); + pair.Item1.Dispose(); + return; } - var def = ImcFile.GetDefault(manager, file.Path, m.EquipSlot, m.Variant.Id, out _); - var manip = m.Copy(def); - if (!manip.Apply(file)) - return false; - - var fullPath = PathDataHandler.CreateImc(file.Path.Path, collection); - collection._cache!.ForceFile(file.Path, fullPath); + var def = ImcFile.GetDefault(Manager, pair.Item1.Path, identifier.EquipSlot, identifier.Variant, out _); + if (!Apply(pair.Item1, identifier, def)) + return; - return true; + var fullPath = PathDataHandler.CreateImc(pair.Item1.Path.Path, Collection); + Collection._cache!.ForceFile(pair.Item1.Path, fullPath); } - public void Dispose() + public static bool Apply(ImcFile file, ImcIdentifier identifier, ImcEntry entry) + => file.SetEntry(ImcFile.PartIndex(identifier.EquipSlot), identifier.Variant.Id, entry); + + protected override void Dispose(bool _) { - foreach (var file in _imcFiles.Values) + foreach (var (_, (file, _)) in _imcFiles) file.Dispose(); - + Clear(); _imcFiles.Clear(); - _imcManipulations.Clear(); } - - public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out ImcFile? file) - => _imcFiles.TryGetValue(path, out file); } diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index fbca9c0e..bc6ef34d 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -1,3 +1,4 @@ +using System.IO.Pipes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Services; @@ -9,142 +10,111 @@ namespace Penumbra.Collections.Cache; -public class MetaCache : IDisposable, IEnumerable> +public class MetaCache(MetaFileManager manager, ModCollection collection) { - private readonly MetaFileManager _manager; - private readonly ModCollection _collection; - private readonly Dictionary _manipulations = new(); - private EqpCache _eqpCache = new(); - private readonly EqdpCache _eqdpCache = new(); - private EstCache _estCache = new(); - private GmpCache _gmpCache = new(); - private CmpCache _cmpCache = new(); - private readonly ImcCache _imcCache = new(); - private GlobalEqpCache _globalEqpCache = new(); - - public bool TryGetValue(MetaManipulation manip, [NotNullWhen(true)] out IMod? mod) - { - lock (_manipulations) - { - return _manipulations.TryGetValue(manip, out mod); - } - } + public readonly EqpCache Eqp = new(manager, collection); + public readonly EqdpCache Eqdp = new(manager, collection); + public readonly EstCache Est = new(manager, collection); + public readonly GmpCache Gmp = new(manager, collection); + public readonly RspCache Rsp = new(manager, collection); + public readonly ImcCache Imc = new(manager, collection); + public readonly GlobalEqpCache GlobalEqp = new(); public int Count - => _manipulations.Count; - - public IReadOnlyCollection Manipulations - => _manipulations.Keys; + => Eqp.Count + Eqdp.Count + Est.Count + Gmp.Count + Rsp.Count + Imc.Count + GlobalEqp.Count; - public IEnumerator> GetEnumerator() - => _manipulations.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - - public MetaCache(MetaFileManager manager, ModCollection collection) - { - _manager = manager; - _collection = collection; - if (!_manager.CharacterUtility.Ready) - _manager.CharacterUtility.LoadingFinished += ApplyStoredManipulations; - } + public IEnumerable<(IMetaIdentifier, IMod)> IdentifierSources + => Eqp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source)) + .Concat(Eqdp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))) + .Concat(Est.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))) + .Concat(Gmp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))) + .Concat(Rsp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))) + .Concat(Imc.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))) + .Concat(GlobalEqp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value))); public void SetFiles() { - _eqpCache.SetFiles(_manager); - _eqdpCache.SetFiles(_manager); - _estCache.SetFiles(_manager); - _gmpCache.SetFiles(_manager); - _cmpCache.SetFiles(_manager); - _imcCache.SetFiles(_collection, false); + Eqp.SetFiles(); + Eqdp.SetFiles(); + Est.SetFiles(); + Gmp.SetFiles(); + Rsp.SetFiles(); + Imc.SetFiles(false); } public void Reset() { - _eqpCache.Reset(); - _eqdpCache.Reset(); - _estCache.Reset(); - _gmpCache.Reset(); - _cmpCache.Reset(); - _imcCache.Reset(_collection); - _manipulations.Clear(); - _globalEqpCache.Clear(); + Eqp.Reset(); + Eqdp.Reset(); + Est.Reset(); + Gmp.Reset(); + Rsp.Reset(); + Imc.Reset(); + GlobalEqp.Clear(); } public void Dispose() { - _manager.CharacterUtility.LoadingFinished -= ApplyStoredManipulations; - _eqpCache.Dispose(); - _eqdpCache.Dispose(); - _estCache.Dispose(); - _gmpCache.Dispose(); - _cmpCache.Dispose(); - _imcCache.Dispose(); - _manipulations.Clear(); + Eqp.Dispose(); + Eqdp.Dispose(); + Est.Dispose(); + Gmp.Dispose(); + Rsp.Dispose(); + Imc.Dispose(); } - ~MetaCache() - => Dispose(); - - public bool ApplyMod(MetaManipulation manip, IMod mod) + public bool TryGetMod(IMetaIdentifier identifier, [NotNullWhen(true)] out IMod? mod) { - lock (_manipulations) + mod = null; + return identifier switch { - if (_manipulations.ContainsKey(manip)) - _manipulations.Remove(manip); - - _manipulations[manip] = mod; - } - - if (manip.ManipulationType is MetaManipulation.Type.GlobalEqp) - return _globalEqpCache.Add(manip.GlobalEqp); - - if (!_manager.CharacterUtility.Ready) - return true; + EqdpIdentifier i => Eqdp.TryGetValue(i, out var p) && Convert(p, out mod), + EqpIdentifier i => Eqp.TryGetValue(i, out var p) && Convert(p, out mod), + EstIdentifier i => Est.TryGetValue(i, out var p) && Convert(p, out mod), + GmpIdentifier i => Gmp.TryGetValue(i, out var p) && Convert(p, out mod), + ImcIdentifier i => Imc.TryGetValue(i, out var p) && Convert(p, out mod), + RspIdentifier i => Rsp.TryGetValue(i, out var p) && Convert(p, out mod), + GlobalEqpManipulation i => GlobalEqp.TryGetValue(i, out mod), + _ => false, + }; - // Imc manipulations do not require character utility, - // but they do require the file space to be ready. - return manip.ManipulationType switch + static bool Convert((IMod, T) pair, out IMod mod) { - MetaManipulation.Type.Eqp => _eqpCache.ApplyMod(_manager, manip.Eqp), - MetaManipulation.Type.Eqdp => _eqdpCache.ApplyMod(_manager, manip.Eqdp), - MetaManipulation.Type.Est => _estCache.ApplyMod(_manager, manip.Est), - MetaManipulation.Type.Gmp => _gmpCache.ApplyMod(_manager, manip.Gmp), - MetaManipulation.Type.Rsp => _cmpCache.ApplyMod(_manager, manip.Rsp), - MetaManipulation.Type.Imc => _imcCache.ApplyMod(_manager, _collection, manip.Imc), - MetaManipulation.Type.Unknown => false, - _ => false, - }; + mod = pair.Item1; + return true; + } } - public bool RevertMod(MetaManipulation manip, [NotNullWhen(true)] out IMod? mod) - { - lock (_manipulations) + public bool RevertMod(IMetaIdentifier identifier, [NotNullWhen(true)] out IMod? mod) + => identifier switch { - var ret = _manipulations.Remove(manip, out mod); - - if (manip.ManipulationType is MetaManipulation.Type.GlobalEqp) - return _globalEqpCache.Remove(manip.GlobalEqp); - - if (!_manager.CharacterUtility.Ready) - return ret; - } + EqdpIdentifier i => Eqdp.RevertMod(i, out mod), + EqpIdentifier i => Eqp.RevertMod(i, out mod), + EstIdentifier i => Est.RevertMod(i, out mod), + GmpIdentifier i => Gmp.RevertMod(i, out mod), + ImcIdentifier i => Imc.RevertMod(i, out mod), + RspIdentifier i => Rsp.RevertMod(i, out mod), + GlobalEqpManipulation i => GlobalEqp.RevertMod(i, out mod), + _ => (mod = null) != null, + }; - // Imc manipulations do not require character utility, - // but they do require the file space to be ready. - return manip.ManipulationType switch + public bool ApplyMod(IMod mod, IMetaIdentifier identifier, object entry) + => identifier switch { - MetaManipulation.Type.Eqp => _eqpCache.RevertMod(_manager, manip.Eqp), - MetaManipulation.Type.Eqdp => _eqdpCache.RevertMod(_manager, manip.Eqdp), - MetaManipulation.Type.Est => _estCache.RevertMod(_manager, manip.Est), - MetaManipulation.Type.Gmp => _gmpCache.RevertMod(_manager, manip.Gmp), - MetaManipulation.Type.Rsp => _cmpCache.RevertMod(_manager, manip.Rsp), - MetaManipulation.Type.Imc => _imcCache.RevertMod(_manager, _collection, manip.Imc), - MetaManipulation.Type.Unknown => false, - _ => false, + EqdpIdentifier i when entry is EqdpEntry e => Eqdp.ApplyMod(mod, i, e), + EqdpIdentifier i when entry is EqdpEntryInternal e => Eqdp.ApplyMod(mod, i, e.ToEntry(i.Slot)), + EqpIdentifier i when entry is EqpEntry e => Eqp.ApplyMod(mod, i, e), + EqpIdentifier i when entry is EqpEntryInternal e => Eqp.ApplyMod(mod, i, e.ToEntry(i.Slot)), + EstIdentifier i when entry is EstEntry e => Est.ApplyMod(mod, i, e), + GmpIdentifier i when entry is GmpEntry e => Gmp.ApplyMod(mod, i, e), + ImcIdentifier i when entry is ImcEntry e => Imc.ApplyMod(mod, i, e), + RspIdentifier i when entry is RspEntry e => Rsp.ApplyMod(mod, i, e), + GlobalEqpManipulation i => GlobalEqp.ApplyMod(mod, i), + _ => false, }; - } + + ~MetaCache() + => Dispose(); /// Set a single file. public void SetFile(MetaIndex metaIndex) @@ -152,95 +122,62 @@ public void SetFile(MetaIndex metaIndex) switch (metaIndex) { case MetaIndex.Eqp: - _eqpCache.SetFiles(_manager); + Eqp.SetFiles(); break; case MetaIndex.Gmp: - _gmpCache.SetFiles(_manager); + Gmp.SetFiles(); break; case MetaIndex.HumanCmp: - _cmpCache.SetFiles(_manager); + Rsp.SetFiles(); break; case MetaIndex.FaceEst: case MetaIndex.HairEst: case MetaIndex.HeadEst: case MetaIndex.BodyEst: - _estCache.SetFile(_manager, metaIndex); + Est.SetFile(metaIndex); break; default: - _eqdpCache.SetFile(_manager, metaIndex); + Eqdp.SetFile(metaIndex); break; } } /// Set the currently relevant IMC files for the collection cache. public void SetImcFiles(bool fromFullCompute) - => _imcCache.SetFiles(_collection, fromFullCompute); + => Imc.SetFiles(fromFullCompute); public MetaList.MetaReverter TemporarilySetEqpFile() - => _eqpCache.TemporarilySetFiles(_manager); + => Eqp.TemporarilySetFile(); public MetaList.MetaReverter? TemporarilySetEqdpFile(GenderRace genderRace, bool accessory) - => _eqdpCache.TemporarilySetFiles(_manager, genderRace, accessory); + => Eqdp.TemporarilySetFile(genderRace, accessory); public MetaList.MetaReverter TemporarilySetGmpFile() - => _gmpCache.TemporarilySetFiles(_manager); + => Gmp.TemporarilySetFile(); public MetaList.MetaReverter TemporarilySetCmpFile() - => _cmpCache.TemporarilySetFiles(_manager); + => Rsp.TemporarilySetFile(); public MetaList.MetaReverter TemporarilySetEstFile(EstType type) - => _estCache.TemporarilySetFiles(_manager, type); + => Est.TemporarilySetFiles(type); public unsafe EqpEntry ApplyGlobalEqp(EqpEntry baseEntry, CharacterArmor* armor) - => _globalEqpCache.Apply(baseEntry, armor); + => GlobalEqp.Apply(baseEntry, armor); /// Try to obtain a manipulated IMC file. public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) - => _imcCache.GetImcFile(path, out file); + => Imc.GetFile(path, out file); internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, PrimaryId primaryId) { - var eqdpFile = _eqdpCache.EqdpFile(race, accessory); + var eqdpFile = Eqdp.EqdpFile(race, accessory); if (eqdpFile != null) return primaryId.Id < eqdpFile.Count ? eqdpFile[primaryId] : default; - return Meta.Files.ExpandedEqdpFile.GetDefault(_manager, race, accessory, primaryId); + return Meta.Files.ExpandedEqdpFile.GetDefault(manager, race, accessory, primaryId); } internal EstEntry GetEstEntry(EstType type, GenderRace genderRace, PrimaryId primaryId) - => _estCache.GetEstEntry(_manager, type, genderRace, primaryId); - - /// Use this when CharacterUtility becomes ready. - private void ApplyStoredManipulations() - { - if (!_manager.CharacterUtility.Ready) - return; - - var loaded = 0; - lock (_manipulations) - { - foreach (var manip in Manipulations) - { - loaded += manip.ManipulationType switch - { - MetaManipulation.Type.Eqp => _eqpCache.ApplyMod(_manager, manip.Eqp), - MetaManipulation.Type.Eqdp => _eqdpCache.ApplyMod(_manager, manip.Eqdp), - MetaManipulation.Type.Est => _estCache.ApplyMod(_manager, manip.Est), - MetaManipulation.Type.Gmp => _gmpCache.ApplyMod(_manager, manip.Gmp), - MetaManipulation.Type.Rsp => _cmpCache.ApplyMod(_manager, manip.Rsp), - MetaManipulation.Type.Imc => _imcCache.ApplyMod(_manager, _collection, manip.Imc), - MetaManipulation.Type.GlobalEqp => false, - MetaManipulation.Type.Unknown => false, - _ => false, - } - ? 1 - : 0; - } - } - - _manager.ApplyDefaultFiles(_collection); - _manager.CharacterUtility.LoadingFinished -= ApplyStoredManipulations; - Penumbra.Log.Debug($"{_collection.AnonymizedName}: Loaded {loaded} delayed meta manipulations."); - } + => Est.GetEstEntry(new EstIdentifier(primaryId, type, genderRace)); } diff --git a/Penumbra/Collections/Cache/RspCache.cs b/Penumbra/Collections/Cache/RspCache.cs new file mode 100644 index 00000000..3889d6f1 --- /dev/null +++ b/Penumbra/Collections/Cache/RspCache.cs @@ -0,0 +1,81 @@ +using Penumbra.Interop.Services; +using Penumbra.Interop.Structs; +using Penumbra.Meta; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Collections.Cache; + +public sealed class RspCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) +{ + private CmpFile? _cmpFile; + + public override void SetFiles() + => Manager.SetFile(_cmpFile, MetaIndex.HumanCmp); + + public override void ResetFiles() + => Manager.SetFile(null, MetaIndex.HumanCmp); + + protected override void IncorporateChangesInternal() + { + if (GetFile() is not { } file) + return; + + foreach (var (identifier, (_, entry)) in this) + Apply(file, identifier, entry); + + Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed RSP manipulations."); + } + + public MetaList.MetaReverter TemporarilySetFile() + => Manager.TemporarilySetFile(_cmpFile, MetaIndex.HumanCmp); + + public override void Reset() + { + if (_cmpFile == null) + return; + + _cmpFile.Reset(Keys.Select(identifier => (identifier.SubRace, identifier.Attribute))); + Clear(); + } + + protected override void ApplyModInternal(RspIdentifier identifier, RspEntry entry) + { + if (GetFile() is { } file) + Apply(file, identifier, entry); + } + + protected override void RevertModInternal(RspIdentifier identifier) + { + if (GetFile() is { } file) + Apply(file, identifier, CmpFile.GetDefault(Manager, identifier.SubRace, identifier.Attribute)); + } + + public static bool Apply(CmpFile file, RspIdentifier identifier, RspEntry entry) + { + var value = file[identifier.SubRace, identifier.Attribute]; + if (value == entry) + return false; + + file[identifier.SubRace, identifier.Attribute] = entry; + return true; + } + + protected override void Dispose(bool _) + { + _cmpFile?.Dispose(); + _cmpFile = null; + Clear(); + } + + private CmpFile? GetFile() + { + if (_cmpFile != null) + return _cmpFile; + + if (!Manager.CharacterUtility.Ready) + return null; + + return _cmpFile = new CmpFile(Manager); + } +} diff --git a/Penumbra/Import/Models/ModelManager.cs b/Penumbra/Import/Models/ModelManager.cs index fdd28ef1..9fa77784 100644 --- a/Penumbra/Import/Models/ModelManager.cs +++ b/Penumbra/Import/Models/ModelManager.cs @@ -37,7 +37,8 @@ public void Dispose() _tasks.Clear(); } - public Task ExportToGltf(in ExportConfig config, MdlFile mdl, IEnumerable sklbPaths, Func read, string outputPath) + public Task ExportToGltf(in ExportConfig config, MdlFile mdl, IEnumerable sklbPaths, Func read, + string outputPath) => EnqueueWithResult( new ExportToGltfAction(this, config, mdl, sklbPaths, read, outputPath), action => action.Notifier @@ -52,7 +53,7 @@ public Task ExportToGltf(in ExportConfig config, MdlFile mdl, IEnume /// Try to find the .sklb paths for a .mdl file. /// .mdl file to look up the skeletons for. /// Modified extra skeleton template parameters. - public string[] ResolveSklbsForMdl(string mdlPath, EstManipulation[] estManipulations) + public string[] ResolveSklbsForMdl(string mdlPath, KeyValuePair[] estManipulations) { var info = parser.GetFileInfo(mdlPath); if (info.FileType is not FileType.Model) @@ -81,20 +82,18 @@ ObjectType.Character when info.BodySlot is BodySlot.Face or BodySlot.Ear }; } - private string[] ResolveEstSkeleton(EstType type, GameObjectInfo info, EstManipulation[] estManipulations) + private string[] ResolveEstSkeleton(EstType type, GameObjectInfo info, KeyValuePair[] estManipulations) { // Try to find an EST entry from the manipulations provided. - var (gender, race) = info.GenderRace.Split(); var modEst = estManipulations - .FirstOrNull(est => - est.Gender == gender - && est.Race == race - && est.Slot == type - && est.SetId == info.PrimaryId + .FirstOrNull( + est => est.Key.GenderRace == info.GenderRace + && est.Key.Slot == type + && est.Key.SetId == info.PrimaryId ); // Try to use an entry from provided manipulations, falling back to the current collection. - var targetId = modEst?.Entry + var targetId = modEst?.Value ?? collections.Current.MetaCache?.GetEstEntry(type, info.GenderRace, info.PrimaryId) ?? EstEntry.Zero; @@ -102,7 +101,7 @@ private string[] ResolveEstSkeleton(EstType type, GameObjectInfo info, EstManipu if (targetId == EstEntry.Zero) return []; - return [GamePaths.Skeleton.Sklb.Path(info.GenderRace, EstManipulation.ToName(type), targetId.AsId)]; + return [GamePaths.Skeleton.Sklb.Path(info.GenderRace, type.ToName(), targetId.AsId)]; } /// Try to resolve the absolute path to a .mtrl from the potentially-partial path provided by a model. @@ -250,9 +249,11 @@ Task CreateHavokTask((SklbFile Sklb, int Index) pair) var path = manager.ResolveMtrlPath(relativePath, notifier); if (path == null) return null; + var bytes = read(path); if (bytes == null) return null; + var mtrl = new MtrlFile(bytes); return new MaterialExporter.Material diff --git a/Penumbra/Import/TexToolsMeta.Deserialization.cs b/Penumbra/Import/TexToolsMeta.Deserialization.cs index f6157747..1f970dfe 100644 --- a/Penumbra/Import/TexToolsMeta.Deserialization.cs +++ b/Penumbra/Import/TexToolsMeta.Deserialization.cs @@ -13,15 +13,15 @@ public partial class TexToolsMeta private void DeserializeEqpEntry(MetaFileInfo metaFileInfo, byte[]? data) { // Eqp can only be valid for equipment. - if (data == null || !metaFileInfo.EquipSlot.IsEquipment()) + var mask = Eqp.Mask(metaFileInfo.EquipSlot); + if (data == null || mask == 0) return; - var value = Eqp.FromSlotAndBytes(metaFileInfo.EquipSlot, data); - var def = new EqpManipulation(ExpandedEqpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId), metaFileInfo.EquipSlot, - metaFileInfo.PrimaryId); - var manip = new EqpManipulation(value, metaFileInfo.EquipSlot, metaFileInfo.PrimaryId); - if (_keepDefault || def.Entry != manip.Entry) - MetaManipulations.Add(manip); + var identifier = new EqpIdentifier(metaFileInfo.PrimaryId, metaFileInfo.EquipSlot); + var value = Eqp.FromSlotAndBytes(metaFileInfo.EquipSlot, data) & mask; + var def = ExpandedEqpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId) & mask; + if (_keepDefault || def != value) + MetaManipulations.TryAdd(identifier, value); } // Deserialize and check Eqdp Entries and add them to the list if they are non-default. @@ -40,14 +40,12 @@ private void DeserializeEqdpEntries(MetaFileInfo metaFileInfo, byte[]? data) if (!gr.IsValid() || !metaFileInfo.EquipSlot.IsEquipment() && !metaFileInfo.EquipSlot.IsAccessory()) continue; - var value = Eqdp.FromSlotAndBits(metaFileInfo.EquipSlot, (byteValue & 1) == 1, (byteValue & 2) == 2); - var def = new EqdpManipulation( - ExpandedEqdpFile.GetDefault(_metaFileManager, gr, metaFileInfo.EquipSlot.IsAccessory(), metaFileInfo.PrimaryId), - metaFileInfo.EquipSlot, - gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId); - var manip = new EqdpManipulation(value, metaFileInfo.EquipSlot, gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId); - if (_keepDefault || def.Entry != manip.Entry) - MetaManipulations.Add(manip); + var identifier = new EqdpIdentifier(metaFileInfo.PrimaryId, metaFileInfo.EquipSlot, gr); + var mask = Eqdp.Mask(metaFileInfo.EquipSlot); + var value = Eqdp.FromSlotAndBits(metaFileInfo.EquipSlot, (byteValue & 1) == 1, (byteValue & 2) == 2) & mask; + var def = ExpandedEqdpFile.GetDefault(_metaFileManager, gr, metaFileInfo.EquipSlot.IsAccessory(), metaFileInfo.PrimaryId) & mask; + if (_keepDefault || def != value) + MetaManipulations.TryAdd(identifier, value); } } @@ -57,10 +55,10 @@ private void DeserializeGmpEntry(MetaFileInfo metaFileInfo, byte[]? data) if (data == null) return; - var value = GmpEntry.FromTexToolsMeta(data.AsSpan(0, 5)); - var def = ExpandedGmpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId); + var value = GmpEntry.FromTexToolsMeta(data.AsSpan(0, 5)); + var def = ExpandedGmpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId); if (_keepDefault || value != def) - MetaManipulations.Add(new GmpManipulation(value, metaFileInfo.PrimaryId)); + MetaManipulations.TryAdd(new GmpIdentifier(metaFileInfo.PrimaryId), value); } // Deserialize and check Est Entries and add them to the list if they are non-default. @@ -74,7 +72,7 @@ private void DeserializeEstEntries(MetaFileInfo metaFileInfo, byte[]? data) for (var i = 0; i < num; ++i) { var gr = (GenderRace)reader.ReadUInt16(); - var id = reader.ReadUInt16(); + var id = (PrimaryId)reader.ReadUInt16(); var value = new EstEntry(reader.ReadUInt16()); var type = (metaFileInfo.SecondaryType, metaFileInfo.EquipSlot) switch { @@ -87,9 +85,10 @@ private void DeserializeEstEntries(MetaFileInfo metaFileInfo, byte[]? data) if (!gr.IsValid() || type == 0) continue; - var def = EstFile.GetDefault(_metaFileManager, type, gr, id); + var identifier = new EstIdentifier(id, type, gr); + var def = EstFile.GetDefault(_metaFileManager, type, gr, id); if (_keepDefault || def != value) - MetaManipulations.Add(new EstManipulation(gr.Split().Item1, gr.Split().Item2, type, id, value)); + MetaManipulations.TryAdd(identifier, value); } } @@ -107,20 +106,16 @@ private void DeserializeImcEntries(MetaFileInfo metaFileInfo, byte[]? data) ushort i = 0; try { - var manip = new ImcManipulation(metaFileInfo.PrimaryType, metaFileInfo.SecondaryType, metaFileInfo.PrimaryId, - metaFileInfo.SecondaryId, i, metaFileInfo.EquipSlot, - new ImcEntry()); - var def = new ImcFile(_metaFileManager, manip.Identifier); - var partIdx = ImcFile.PartIndex(manip.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0. + var identifier = new ImcIdentifier(metaFileInfo.PrimaryId, 0, metaFileInfo.PrimaryType, metaFileInfo.SecondaryId, + metaFileInfo.EquipSlot, metaFileInfo.SecondaryType); + var file = new ImcFile(_metaFileManager, identifier); + var partIdx = ImcFile.PartIndex(identifier.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0. foreach (var value in values) { - if (_keepDefault || !value.Equals(def.GetEntry(partIdx, (Variant)i))) - { - var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot, - value); - if (imc.Validate(true)) - MetaManipulations.Add(imc); - } + identifier = identifier with { Variant = (Variant)i }; + var def = file.GetEntry(partIdx, (Variant)i); + if (_keepDefault || def != value && identifier.Validate()) + MetaManipulations.TryAdd(identifier, value); ++i; } diff --git a/Penumbra/Import/TexToolsMeta.Export.cs b/Penumbra/Import/TexToolsMeta.Export.cs index 4fb56df6..9cce60e3 100644 --- a/Penumbra/Import/TexToolsMeta.Export.cs +++ b/Penumbra/Import/TexToolsMeta.Export.cs @@ -1,3 +1,4 @@ +using Penumbra.Collections.Cache; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Meta; @@ -8,7 +9,7 @@ namespace Penumbra.Import; public partial class TexToolsMeta { - public static void WriteTexToolsMeta(MetaFileManager manager, IEnumerable manipulations, DirectoryInfo basePath) + public static void WriteTexToolsMeta(MetaFileManager manager, MetaDictionary manipulations, DirectoryInfo basePath) { var files = ConvertToTexTools(manager, manipulations); @@ -27,49 +28,81 @@ public static void WriteTexToolsMeta(MetaFileManager manager, IEnumerable ConvertToTexTools(MetaFileManager manager, IEnumerable manips) + public static Dictionary ConvertToTexTools(MetaFileManager manager, MetaDictionary manips) { var ret = new Dictionary(); - foreach (var group in manips.GroupBy(ManipToPath)) + foreach (var group in manips.Rsp.GroupBy(ManipToPath)) { if (group.Key.Length == 0) continue; - var bytes = group.Key.EndsWith(".rgsp") - ? WriteRgspFile(manager, group.Key, group) - : WriteMetaFile(manager, group.Key, group); + var bytes = WriteRgspFile(manager, group); if (bytes.Length == 0) continue; ret.Add(group.Key, bytes); } + foreach (var (file, dict) in SplitByFile(manips)) + { + var bytes = WriteMetaFile(manager, file, dict); + if (bytes.Length == 0) + continue; + + ret.Add(file, bytes); + } + return ret; } - private static byte[] WriteRgspFile(MetaFileManager manager, string path, IEnumerable manips) + private static Dictionary SplitByFile(MetaDictionary manips) { - var list = manips.GroupBy(m => m.Rsp.Attribute).ToDictionary(m => m.Key, m => m.Last().Rsp); + var ret = new Dictionary(); + foreach (var (identifier, key) in manips.Imc) + GetDict(ManipToPath(identifier)).TryAdd(identifier, key); + + foreach (var (identifier, key) in manips.Eqp) + GetDict(ManipToPath(identifier)).TryAdd(identifier, key); + + foreach (var (identifier, key) in manips.Eqdp) + GetDict(ManipToPath(identifier)).TryAdd(identifier, key); + + foreach (var (identifier, key) in manips.Est) + GetDict(ManipToPath(identifier)).TryAdd(identifier, key); + + foreach (var (identifier, key) in manips.Gmp) + GetDict(ManipToPath(identifier)).TryAdd(identifier, key); + + ret.Remove(string.Empty); + + return ret; + + MetaDictionary GetDict(string path) + { + if (!ret.TryGetValue(path, out var dict)) + { + dict = new MetaDictionary(); + ret.Add(path, dict); + } + + return dict; + } + } + + private static byte[] WriteRgspFile(MetaFileManager manager, IEnumerable> manips) + { + var list = manips.GroupBy(m => m.Key.Attribute).ToDictionary(g => g.Key, g => g.Last()); using var m = new MemoryStream(45); using var b = new BinaryWriter(m); // Version b.Write(byte.MaxValue); b.Write((ushort)2); - var race = list.First().Value.SubRace; - var gender = list.First().Value.Attribute.ToGender(); + var race = list.First().Value.Key.SubRace; + var gender = list.First().Value.Key.Attribute.ToGender(); b.Write((byte)(race - 1)); // offset by one due to Unknown b.Write((byte)(gender - 1)); // offset by one due to Unknown - void Add(params RspAttribute[] attributes) - { - foreach (var attribute in attributes) - { - var value = list.TryGetValue(attribute, out var tmp) ? tmp.Entry : CmpFile.GetDefault(manager, race, attribute); - b.Write(value.Value); - } - } - if (gender == Gender.Male) { Add(RspAttribute.MaleMinSize, RspAttribute.MaleMaxSize, RspAttribute.MaleMinTail, RspAttribute.MaleMaxTail); @@ -82,12 +115,24 @@ void Add(params RspAttribute[] attributes) } return m.GetBuffer(); + + void Add(params RspAttribute[] attributes) + { + foreach (var attribute in attributes) + { + var value = list.TryGetValue(attribute, out var tmp) ? tmp.Value : CmpFile.GetDefault(manager, race, attribute); + b.Write(value.Value); + } + } } - private static byte[] WriteMetaFile(MetaFileManager manager, string path, IEnumerable manips) + private static byte[] WriteMetaFile(MetaFileManager manager, string path, MetaDictionary manips) { - var filteredManips = manips.GroupBy(m => m.ManipulationType).ToDictionary(p => p.Key, p => p.Select(x => x)); - + var headerCount = (manips.Imc.Count > 0 ? 1 : 0) + + (manips.Eqp.Count > 0 ? 1 : 0) + + (manips.Eqdp.Count > 0 ? 1 : 0) + + (manips.Est.Count > 0 ? 1 : 0) + + (manips.Gmp.Count > 0 ? 1 : 0); using var m = new MemoryStream(); using var b = new BinaryWriter(m); @@ -101,7 +146,7 @@ private static byte[] WriteMetaFile(MetaFileManager manager, string path, IEnume b.Write((byte)0); // Number of Headers - b.Write((uint)filteredManips.Count); + b.Write((uint)headerCount); // Current TT Size of Headers b.Write((uint)12); @@ -109,88 +154,113 @@ private static byte[] WriteMetaFile(MetaFileManager manager, string path, IEnume var headerStart = b.BaseStream.Position + 4; b.Write((uint)headerStart); - var offset = (uint)(b.BaseStream.Position + 12 * filteredManips.Count); - foreach (var (header, data) in filteredManips) + var offset = (uint)(b.BaseStream.Position + 12 * manips.Count); + offset += WriteData(manager, b, offset, manips.Imc); + offset += WriteData(b, offset, manips.Eqdp); + offset += WriteData(b, offset, manips.Eqp); + offset += WriteData(b, offset, manips.Est); + offset += WriteData(b, offset, manips.Gmp); + + return m.ToArray(); + } + + private static uint WriteData(MetaFileManager manager, BinaryWriter b, uint offset, IReadOnlyDictionary manips) + { + if (manips.Count == 0) + return 0; + + b.Write((uint)MetaManipulationType.Imc); + b.Write(offset); + + var oldPos = b.BaseStream.Position; + b.Seek((int)offset, SeekOrigin.Begin); + + var refIdentifier = manips.First().Key; + var baseFile = new ImcFile(manager, refIdentifier); + foreach (var (identifier, entry) in manips) + ImcCache.Apply(baseFile, identifier, entry); + + var partIdx = refIdentifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory + ? ImcFile.PartIndex(refIdentifier.EquipSlot) + : 0; + + for (var i = 0; i <= baseFile.Count; ++i) { - b.Write((uint)header); - b.Write(offset); + var entry = baseFile.GetEntry(partIdx, (Variant)i); + b.Write(entry.MaterialId); + b.Write(entry.DecalId); + b.Write(entry.AttributeAndSound); + b.Write(entry.VfxId); + b.Write(entry.MaterialAnimationId); + } + + var size = b.BaseStream.Position - offset; + b.Seek((int)oldPos, SeekOrigin.Begin); + return (uint)size; + } + + private static uint WriteData(BinaryWriter b, uint offset, IReadOnlyDictionary manips) + { + if (manips.Count == 0) + return 0; + + b.Write((uint)MetaManipulationType.Eqdp); + b.Write(offset); + + var oldPos = b.BaseStream.Position; + b.Seek((int)offset, SeekOrigin.Begin); - var size = WriteData(manager, b, offset, header, data); - b.Write(size); - offset += size; + foreach (var (identifier, entry) in manips) + { + b.Write((uint)identifier.GenderRace); + b.Write(entry.AsByte); } - return m.ToArray(); + var size = b.BaseStream.Position - offset; + b.Seek((int)oldPos, SeekOrigin.Begin); + return (uint)size; + } + + private static uint WriteData(BinaryWriter b, uint offset, + IReadOnlyDictionary manips) + { + if (manips.Count == 0) + return 0; + + b.Write((uint)MetaManipulationType.Imc); + b.Write(offset); + + var oldPos = b.BaseStream.Position; + b.Seek((int)offset, SeekOrigin.Begin); + + foreach (var (identifier, entry) in manips) + { + var numBytes = Eqp.BytesAndOffset(identifier.Slot).Item1; + for (var i = 0; i < numBytes; ++i) + b.Write((byte)(entry.Value >> (8 * i))); + } + + var size = b.BaseStream.Position - offset; + b.Seek((int)oldPos, SeekOrigin.Begin); + return (uint)size; } - private static uint WriteData(MetaFileManager manager, BinaryWriter b, uint offset, MetaManipulation.Type type, - IEnumerable manips) + private static uint WriteData(BinaryWriter b, uint offset, IReadOnlyDictionary manips) { + if (manips.Count == 0) + return 0; + + b.Write((uint)MetaManipulationType.Imc); + b.Write(offset); + var oldPos = b.BaseStream.Position; b.Seek((int)offset, SeekOrigin.Begin); - switch (type) + foreach (var (identifier, entry) in manips) { - case MetaManipulation.Type.Imc: - var allManips = manips.ToList(); - var baseFile = new ImcFile(manager, allManips[0].Imc.Identifier); - foreach (var manip in allManips) - manip.Imc.Apply(baseFile); - - var partIdx = allManips[0].Imc.ObjectType is ObjectType.Equipment or ObjectType.Accessory - ? ImcFile.PartIndex(allManips[0].Imc.EquipSlot) - : 0; - - for (var i = 0; i <= baseFile.Count; ++i) - { - var entry = baseFile.GetEntry(partIdx, (Variant)i); - b.Write(entry.MaterialId); - b.Write(entry.DecalId); - b.Write(entry.AttributeAndSound); - b.Write(entry.VfxId); - b.Write(entry.MaterialAnimationId); - } - - break; - case MetaManipulation.Type.Eqdp: - foreach (var manip in manips) - { - b.Write((uint)Names.CombinedRace(manip.Eqdp.Gender, manip.Eqdp.Race)); - var entry = (byte)(((uint)manip.Eqdp.Entry >> Eqdp.Offset(manip.Eqdp.Slot)) & 0x03); - b.Write(entry); - } - - break; - case MetaManipulation.Type.Eqp: - foreach (var manip in manips) - { - var bytes = BitConverter.GetBytes((ulong)manip.Eqp.Entry); - var (numBytes, byteOffset) = Eqp.BytesAndOffset(manip.Eqp.Slot); - for (var i = byteOffset; i < numBytes + byteOffset; ++i) - b.Write(bytes[i]); - } - - break; - case MetaManipulation.Type.Est: - foreach (var manip in manips) - { - b.Write((ushort)Names.CombinedRace(manip.Est.Gender, manip.Est.Race)); - b.Write(manip.Est.SetId.Id); - b.Write(manip.Est.Entry.Value); - } - - break; - case MetaManipulation.Type.Gmp: - foreach (var manip in manips) - { - b.Write((uint)manip.Gmp.Entry.Value); - b.Write(manip.Gmp.Entry.UnknownTotal); - } - - break; - case MetaManipulation.Type.GlobalEqp: - // Not Supported - break; + b.Write((ushort)identifier.GenderRace); + b.Write(identifier.SetId.Id); + b.Write(entry.Value); } var size = b.BaseStream.Position - offset; @@ -198,19 +268,29 @@ private static uint WriteData(MetaFileManager manager, BinaryWriter b, uint offs return (uint)size; } - private static string ManipToPath(MetaManipulation manip) - => manip.ManipulationType switch + private static uint WriteData(BinaryWriter b, uint offset, IReadOnlyDictionary manips) + { + if (manips.Count == 0) + return 0; + + b.Write((uint)MetaManipulationType.Imc); + b.Write(offset); + + var oldPos = b.BaseStream.Position; + b.Seek((int)offset, SeekOrigin.Begin); + + foreach (var entry in manips.Values) { - MetaManipulation.Type.Imc => ManipToPath(manip.Imc), - MetaManipulation.Type.Eqdp => ManipToPath(manip.Eqdp), - MetaManipulation.Type.Eqp => ManipToPath(manip.Eqp), - MetaManipulation.Type.Est => ManipToPath(manip.Est), - MetaManipulation.Type.Gmp => ManipToPath(manip.Gmp), - MetaManipulation.Type.Rsp => ManipToPath(manip.Rsp), - _ => string.Empty, - }; + b.Write((uint)entry.Value); + b.Write(entry.UnknownTotal); + } + + var size = b.BaseStream.Position - offset; + b.Seek((int)oldPos, SeekOrigin.Begin); + return (uint)size; + } - private static string ManipToPath(ImcManipulation manip) + private static string ManipToPath(ImcIdentifier manip) { var path = manip.GamePath().ToString(); var replacement = manip.ObjectType switch @@ -224,33 +304,33 @@ private static string ManipToPath(ImcManipulation manip) return path.Replace(".imc", replacement); } - private static string ManipToPath(EqdpManipulation manip) + private static string ManipToPath(EqdpIdentifier manip) => manip.Slot.IsAccessory() - ? $"chara/accessory/a{manip.SetId:D4}/a{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta" - : $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta"; + ? $"chara/accessory/a{manip.SetId.Id:D4}/a{manip.SetId.Id:D4}_{manip.Slot.ToSuffix()}.meta" + : $"chara/equipment/e{manip.SetId.Id:D4}/e{manip.SetId.Id:D4}_{manip.Slot.ToSuffix()}.meta"; - private static string ManipToPath(EqpManipulation manip) + private static string ManipToPath(EqpIdentifier manip) => manip.Slot.IsAccessory() - ? $"chara/accessory/a{manip.SetId:D4}/a{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta" - : $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta"; + ? $"chara/accessory/a{manip.SetId.Id:D4}/a{manip.SetId.Id:D4}_{manip.Slot.ToSuffix()}.meta" + : $"chara/equipment/e{manip.SetId.Id:D4}/e{manip.SetId.Id:D4}_{manip.Slot.ToSuffix()}.meta"; - private static string ManipToPath(EstManipulation manip) + private static string ManipToPath(EstIdentifier manip) { var raceCode = Names.CombinedRace(manip.Gender, manip.Race).ToRaceCode(); return manip.Slot switch { - EstType.Hair => $"chara/human/c{raceCode}/obj/hair/h{manip.SetId:D4}/c{raceCode}h{manip.SetId:D4}_hir.meta", - EstType.Face => $"chara/human/c{raceCode}/obj/face/h{manip.SetId:D4}/c{raceCode}f{manip.SetId:D4}_fac.meta", - EstType.Body => $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Body.ToSuffix()}.meta", - EstType.Head => $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Head.ToSuffix()}.meta", - _ => throw new ArgumentOutOfRangeException(), + EstType.Hair => $"chara/human/c{raceCode}/obj/hair/h{manip.SetId.Id:D4}/c{raceCode}h{manip.SetId.Id:D4}_hir.meta", + EstType.Face => $"chara/human/c{raceCode}/obj/face/h{manip.SetId.Id:D4}/c{raceCode}f{manip.SetId.Id:D4}_fac.meta", + EstType.Body => $"chara/equipment/e{manip.SetId.Id:D4}/e{manip.SetId.Id:D4}_{EquipSlot.Body.ToSuffix()}.meta", + EstType.Head => $"chara/equipment/e{manip.SetId.Id:D4}/e{manip.SetId.Id:D4}_{EquipSlot.Head.ToSuffix()}.meta", + _ => throw new ArgumentOutOfRangeException(), }; } - private static string ManipToPath(GmpManipulation manip) - => $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Head.ToSuffix()}.meta"; + private static string ManipToPath(GmpIdentifier manip) + => $"chara/equipment/e{manip.SetId.Id:D4}/e{manip.SetId.Id:D4}_{EquipSlot.Head.ToSuffix()}.meta"; - private static string ManipToPath(RspManipulation manip) - => $"chara/xls/charamake/rgsp/{(int)manip.SubRace - 1}-{(int)manip.Attribute.ToGender() - 1}.rgsp"; + private static string ManipToPath(KeyValuePair manip) + => $"chara/xls/charamake/rgsp/{(int)manip.Key.SubRace - 1}-{(int)manip.Key.Attribute.ToGender() - 1}.rgsp"; } diff --git a/Penumbra/Import/TexToolsMeta.Rgsp.cs b/Penumbra/Import/TexToolsMeta.Rgsp.cs index 71b9165f..7b0bb5a8 100644 --- a/Penumbra/Import/TexToolsMeta.Rgsp.cs +++ b/Penumbra/Import/TexToolsMeta.Rgsp.cs @@ -42,14 +42,6 @@ public static TexToolsMeta FromRgspFile(MetaFileManager manager, string filePath return Invalid; } - // Add the given values to the manipulations if they are not default. - void Add(RspAttribute attribute, float value) - { - var def = CmpFile.GetDefault(manager, subRace, attribute); - if (keepDefault || value != def.Value) - ret.MetaManipulations.Add(new RspManipulation(subRace, attribute, new RspEntry(value))); - } - if (gender == 1) { Add(RspAttribute.FemaleMinSize, br.ReadSingle()); @@ -73,5 +65,14 @@ void Add(RspAttribute attribute, float value) } return ret; + + // Add the given values to the manipulations if they are not default. + void Add(RspAttribute attribute, float value) + { + var identifier = new RspIdentifier(subRace, attribute); + var def = CmpFile.GetDefault(manager, subRace, attribute); + if (keepDefault || value != def.Value) + ret.MetaManipulations.TryAdd(identifier, new RspEntry(value)); + } } } diff --git a/Penumbra/Import/TexToolsMeta.cs b/Penumbra/Import/TexToolsMeta.cs index 25e00bd7..c4a8e81f 100644 --- a/Penumbra/Import/TexToolsMeta.cs +++ b/Penumbra/Import/TexToolsMeta.cs @@ -1,4 +1,3 @@ -using Penumbra.GameData; using Penumbra.GameData.Data; using Penumbra.Import.Structs; using Penumbra.Meta; @@ -22,10 +21,10 @@ public partial class TexToolsMeta public static readonly TexToolsMeta Invalid = new(null!, string.Empty, 0); // The info class determines the files or table locations the changes need to apply to from the filename. - public readonly uint Version; - public readonly string FilePath; - public readonly List MetaManipulations = new(); - private readonly bool _keepDefault = false; + public readonly uint Version; + public readonly string FilePath; + public readonly MetaDictionary MetaManipulations = new(); + private readonly bool _keepDefault; private readonly MetaFileManager _metaFileManager; @@ -44,18 +43,18 @@ public TexToolsMeta(MetaFileManager metaFileManager, GamePathParser parser, byte var headerStart = reader.ReadUInt32(); reader.BaseStream.Seek(headerStart, SeekOrigin.Begin); - List<(MetaManipulation.Type type, uint offset, int size)> entries = []; + List<(MetaManipulationType type, uint offset, int size)> entries = []; for (var i = 0; i < numHeaders; ++i) { var currentOffset = reader.BaseStream.Position; - var type = (MetaManipulation.Type)reader.ReadUInt32(); + var type = (MetaManipulationType)reader.ReadUInt32(); var offset = reader.ReadUInt32(); var size = reader.ReadInt32(); entries.Add((type, offset, size)); reader.BaseStream.Seek(currentOffset + headerSize, SeekOrigin.Begin); } - byte[]? ReadEntry(MetaManipulation.Type type) + byte[]? ReadEntry(MetaManipulationType type) { var idx = entries.FindIndex(t => t.type == type); if (idx < 0) @@ -65,11 +64,11 @@ public TexToolsMeta(MetaFileManager metaFileManager, GamePathParser parser, byte return reader.ReadBytes(entries[idx].size); } - DeserializeEqpEntry(metaInfo, ReadEntry(MetaManipulation.Type.Eqp)); - DeserializeGmpEntry(metaInfo, ReadEntry(MetaManipulation.Type.Gmp)); - DeserializeEqdpEntries(metaInfo, ReadEntry(MetaManipulation.Type.Eqdp)); - DeserializeEstEntries(metaInfo, ReadEntry(MetaManipulation.Type.Est)); - DeserializeImcEntries(metaInfo, ReadEntry(MetaManipulation.Type.Imc)); + DeserializeEqpEntry(metaInfo, ReadEntry(MetaManipulationType.Eqp)); + DeserializeGmpEntry(metaInfo, ReadEntry(MetaManipulationType.Gmp)); + DeserializeEqdpEntries(metaInfo, ReadEntry(MetaManipulationType.Eqdp)); + DeserializeEstEntries(metaInfo, ReadEntry(MetaManipulationType.Est)); + DeserializeImcEntries(metaInfo, ReadEntry(MetaManipulationType.Imc)); } catch (Exception e) { diff --git a/Penumbra/Interop/PathResolving/CollectionResolver.cs b/Penumbra/Interop/PathResolving/CollectionResolver.cs index b42571ac..bc474952 100644 --- a/Penumbra/Interop/PathResolving/CollectionResolver.cs +++ b/Penumbra/Interop/PathResolving/CollectionResolver.cs @@ -1,7 +1,6 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using Microsoft.VisualBasic; using OtterGui.Services; using Penumbra.Collections; using Penumbra.Collections.Manager; diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs index 2b87e688..4dfefd96 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs @@ -273,7 +273,7 @@ private unsafe (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanEqu { var metaCache = Global.Collection.MetaCache; var skeletonSet = metaCache?.GetEstEntry(type, raceCode, primary) ?? default; - return (raceCode, EstManipulation.ToName(type), skeletonSet.AsId); + return (raceCode, type.ToName(), skeletonSet.AsId); } private unsafe Utf8GamePath ResolveSkeletonPathNative(uint partialSkeletonIndex) diff --git a/Penumbra/Meta/ImcChecker.cs b/Penumbra/Meta/ImcChecker.cs index 650919a3..751113a0 100644 --- a/Penumbra/Meta/ImcChecker.cs +++ b/Penumbra/Meta/ImcChecker.cs @@ -51,10 +51,6 @@ public CachedEntry GetDefaultEntry(ImcIdentifier identifier, bool storeCache) return entry; } - public CachedEntry GetDefaultEntry(ImcManipulation imcManip, bool storeCache) - => GetDefaultEntry(new ImcIdentifier(imcManip.PrimaryId, imcManip.Variant, imcManip.ObjectType, imcManip.SecondaryId.Id, - imcManip.EquipSlot, imcManip.BodySlot), storeCache); - private static ImcFile? GetFile(ImcIdentifier identifier) { if (_dataManager == null) diff --git a/Penumbra/Meta/Manipulations/Eqdp.cs b/Penumbra/Meta/Manipulations/Eqdp.cs index 6306f419..3f856bd2 100644 --- a/Penumbra/Meta/Manipulations/Eqdp.cs +++ b/Penumbra/Meta/Manipulations/Eqdp.cs @@ -69,10 +69,16 @@ public JObject AddToJson(JObject jObj) jObj["Slot"] = Slot.ToString(); return jObj; } + + public MetaManipulationType Type + => MetaManipulationType.Eqdp; } public readonly record struct EqdpEntryInternal(bool Material, bool Model) { + public byte AsByte + => (byte)(Material ? Model ? 3 : 1 : Model ? 2 : 0); + private EqdpEntryInternal((bool, bool) val) : this(val.Item1, val.Item2) { } @@ -83,4 +89,7 @@ public EqdpEntryInternal(EqdpEntry entry, EquipSlot slot) public EqdpEntry ToEntry(EquipSlot slot) => Eqdp.FromSlotAndBits(slot, Material, Model); + + public override string ToString() + => $"Material: {Material}, Model: {Model}"; } diff --git a/Penumbra/Meta/Manipulations/EqdpManipulation.cs b/Penumbra/Meta/Manipulations/EqdpManipulation.cs deleted file mode 100644 index 8c5f27e5..00000000 --- a/Penumbra/Meta/Manipulations/EqdpManipulation.cs +++ /dev/null @@ -1,110 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Interop.Structs; -using Penumbra.Meta.Files; - -namespace Penumbra.Meta.Manipulations; - -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct EqdpManipulation(EqdpIdentifier identifier, EqdpEntry entry) : IMetaManipulation -{ - [JsonIgnore] - public EqdpIdentifier Identifier { get; } = identifier; - - public EqdpEntry Entry { get; } = entry; - - [JsonConverter(typeof(StringEnumConverter))] - public Gender Gender - => Identifier.Gender; - - [JsonConverter(typeof(StringEnumConverter))] - public ModelRace Race - => Identifier.Race; - - public PrimaryId SetId - => Identifier.SetId; - - [JsonConverter(typeof(StringEnumConverter))] - public EquipSlot Slot - => Identifier.Slot; - - [JsonConstructor] - public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, PrimaryId setId) - : this(new EqdpIdentifier(setId, slot, Names.CombinedRace(gender, race)), Eqdp.Mask(slot) & entry) - { } - - public EqdpManipulation Copy(EqdpManipulation entry) - { - if (entry.Slot != Slot) - { - var (bit1, bit2) = entry.Entry.ToBits(entry.Slot); - return new EqdpManipulation(Identifier, Eqdp.FromSlotAndBits(Slot, bit1, bit2)); - } - - return new EqdpManipulation(Identifier, entry.Entry); - } - - public EqdpManipulation Copy(EqdpEntry entry) - => new(entry, Slot, Gender, Race, SetId); - - public override string ToString() - => $"Eqdp - {SetId} - {Slot} - {Race.ToName()} - {Gender.ToName()}"; - - public bool Equals(EqdpManipulation other) - => Gender == other.Gender - && Race == other.Race - && SetId == other.SetId - && Slot == other.Slot; - - public override bool Equals(object? obj) - => obj is EqdpManipulation other && Equals(other); - - public override int GetHashCode() - => HashCode.Combine((int)Gender, (int)Race, SetId, (int)Slot); - - public int CompareTo(EqdpManipulation other) - { - var r = Race.CompareTo(other.Race); - if (r != 0) - return r; - - var g = Gender.CompareTo(other.Gender); - if (g != 0) - return g; - - var set = SetId.Id.CompareTo(other.SetId.Id); - return set != 0 ? set : Slot.CompareTo(other.Slot); - } - - public MetaIndex FileIndex() - => CharacterUtilityData.EqdpIdx(Names.CombinedRace(Gender, Race), Slot.IsAccessory()); - - public bool Apply(ExpandedEqdpFile file) - { - var entry = file[SetId]; - var mask = Eqdp.Mask(Slot); - if ((entry & mask) == Entry) - return false; - - file[SetId] = (entry & ~mask) | Entry; - return true; - } - - public bool Validate() - { - var mask = Eqdp.Mask(Slot); - if (mask == 0) - return false; - - if ((mask & Entry) != Entry) - return false; - - if (FileIndex() == (MetaIndex)(-1)) - return false; - - // No check for set id. - return true; - } -} diff --git a/Penumbra/Meta/Manipulations/Eqp.cs b/Penumbra/Meta/Manipulations/Eqp.cs index 572dc203..ec4dd6e7 100644 --- a/Penumbra/Meta/Manipulations/Eqp.cs +++ b/Penumbra/Meta/Manipulations/Eqp.cs @@ -50,6 +50,9 @@ public JObject AddToJson(JObject jObj) jObj["Slot"] = Slot.ToString(); return jObj; } + + public MetaManipulationType Type + => MetaManipulationType.Eqp; } public readonly record struct EqpEntryInternal(uint Value) diff --git a/Penumbra/Meta/Manipulations/EqpManipulation.cs b/Penumbra/Meta/Manipulations/EqpManipulation.cs deleted file mode 100644 index eef21d12..00000000 --- a/Penumbra/Meta/Manipulations/EqpManipulation.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Interop.Structs; -using Penumbra.Meta.Files; -using Penumbra.Util; - -namespace Penumbra.Meta.Manipulations; - -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct EqpManipulation(EqpIdentifier identifier, EqpEntry entry) : IMetaManipulation -{ - [JsonIgnore] - public EqpIdentifier Identifier { get; } = identifier; - - [JsonConverter(typeof(ForceNumericFlagEnumConverter))] - public EqpEntry Entry { get; } = entry; - - public PrimaryId SetId - => Identifier.SetId; - - [JsonConverter(typeof(StringEnumConverter))] - public EquipSlot Slot - => Identifier.Slot; - - [JsonConstructor] - public EqpManipulation(EqpEntry entry, EquipSlot slot, PrimaryId setId) - : this(new EqpIdentifier(setId, slot), Eqp.Mask(slot) & entry) - { } - - public EqpManipulation Copy(EqpEntry entry) - => new(Identifier, entry); - - public override string ToString() - => $"Eqp - {SetId} - {Slot}"; - - public bool Equals(EqpManipulation other) - => Slot == other.Slot - && SetId == other.SetId; - - public override bool Equals(object? obj) - => obj is EqpManipulation other && Equals(other); - - public override int GetHashCode() - => HashCode.Combine((int)Slot, SetId); - - public int CompareTo(EqpManipulation other) - { - var set = SetId.Id.CompareTo(other.SetId.Id); - return set != 0 ? set : Slot.CompareTo(other.Slot); - } - - public MetaIndex FileIndex() - => MetaIndex.Eqp; - - public bool Apply(ExpandedEqpFile file) - { - var entry = file[SetId]; - var mask = Eqp.Mask(Slot); - if ((entry & mask) == Entry) - return false; - - file[SetId] = (entry & ~mask) | Entry; - return true; - } - - public bool Validate() - { - var mask = Eqp.Mask(Slot); - if (mask == 0) - return false; - if ((Entry & mask) != Entry) - return false; - - // No check for set id. - - return true; - } -} diff --git a/Penumbra/Meta/Manipulations/Est.cs b/Penumbra/Meta/Manipulations/Est.cs index 9f878f97..2955dba4 100644 --- a/Penumbra/Meta/Manipulations/Est.cs +++ b/Penumbra/Meta/Manipulations/Est.cs @@ -91,6 +91,9 @@ public JObject AddToJson(JObject jObj) jObj["Slot"] = Slot.ToString(); return jObj; } + + public MetaManipulationType Type + => MetaManipulationType.Est; } [JsonConverter(typeof(Converter))] @@ -111,3 +114,16 @@ public override EstEntry ReadJson(JsonReader reader, Type objectType, EstEntry e => new(serializer.Deserialize(reader)); } } + +public static class EstTypeExtension +{ + public static string ToName(this EstType type) + => type switch + { + EstType.Hair => "hair", + EstType.Face => "face", + EstType.Body => "top", + EstType.Head => "met", + _ => "unk", + }; +} diff --git a/Penumbra/Meta/Manipulations/EstManipulation.cs b/Penumbra/Meta/Manipulations/EstManipulation.cs deleted file mode 100644 index 09abbaa5..00000000 --- a/Penumbra/Meta/Manipulations/EstManipulation.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Interop.Structs; -using Penumbra.Meta.Files; - -namespace Penumbra.Meta.Manipulations; - -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct EstManipulation(EstIdentifier identifier, EstEntry entry) : IMetaManipulation -{ - public static string ToName(EstType type) - => type switch - { - EstType.Hair => "hair", - EstType.Face => "face", - EstType.Body => "top", - EstType.Head => "met", - _ => "unk", - }; - - [JsonIgnore] - public EstIdentifier Identifier { get; } = identifier; - public EstEntry Entry { get; } = entry; - - [JsonConverter(typeof(StringEnumConverter))] - public Gender Gender - => Identifier.Gender; - - [JsonConverter(typeof(StringEnumConverter))] - public ModelRace Race - => Identifier.Race; - - public PrimaryId SetId - => Identifier.SetId; - - [JsonConverter(typeof(StringEnumConverter))] - public EstType Slot - => Identifier.Slot; - - - [JsonConstructor] - public EstManipulation(Gender gender, ModelRace race, EstType slot, PrimaryId setId, EstEntry entry) - : this(new EstIdentifier(setId, slot, Names.CombinedRace(gender, race)), entry) - { } - - public EstManipulation Copy(EstEntry entry) - => new(Identifier, entry); - - - public override string ToString() - => $"Est - {SetId} - {Slot} - {Race.ToName()} {Gender.ToName()}"; - - public bool Equals(EstManipulation other) - => Gender == other.Gender - && Race == other.Race - && SetId == other.SetId - && Slot == other.Slot; - - public override bool Equals(object? obj) - => obj is EstManipulation other && Equals(other); - - public override int GetHashCode() - => HashCode.Combine((int)Gender, (int)Race, SetId, (int)Slot); - - public int CompareTo(EstManipulation other) - { - var r = Race.CompareTo(other.Race); - if (r != 0) - return r; - - var g = Gender.CompareTo(other.Gender); - if (g != 0) - return g; - - var s = Slot.CompareTo(other.Slot); - return s != 0 ? s : SetId.Id.CompareTo(other.SetId.Id); - } - - public MetaIndex FileIndex() - => (MetaIndex)Slot; - - public bool Apply(EstFile file) - { - return file.SetEntry(Names.CombinedRace(Gender, Race), SetId.Id, Entry) switch - { - EstFile.EstEntryChange.Unchanged => false, - EstFile.EstEntryChange.Changed => true, - EstFile.EstEntryChange.Added => true, - EstFile.EstEntryChange.Removed => true, - _ => throw new ArgumentOutOfRangeException(), - }; - } - - public bool Validate() - { - if (!Enum.IsDefined(Slot)) - return false; - if (Names.CombinedRace(Gender, Race) == GenderRace.Unknown) - return false; - - // No known check for set id or entry. - return true; - } -} - - diff --git a/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs b/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs index 94c892e2..2b88d962 100644 --- a/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs +++ b/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs @@ -1,11 +1,12 @@ using Newtonsoft.Json.Linq; using Penumbra.GameData.Data; +using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Structs; namespace Penumbra.Meta.Manipulations; -public readonly struct GlobalEqpManipulation : IMetaManipulation, IMetaIdentifier +public readonly struct GlobalEqpManipulation : IMetaIdentifier { public GlobalEqpType Type { get; init; } public PrimaryId Condition { get; init; } @@ -70,8 +71,29 @@ public override string ToString() => $"Global EQP - {Type}{(Condition != 0 ? $" - {Condition.Id}" : string.Empty)}"; public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) - { } + { + var path = Type switch + { + GlobalEqpType.DoNotHideEarrings => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.Ears), + GlobalEqpType.DoNotHideNecklace => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.Neck), + GlobalEqpType.DoNotHideBracelets => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.Wrists), + GlobalEqpType.DoNotHideRingR => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.RFinger), + GlobalEqpType.DoNotHideRingL => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.LFinger), + GlobalEqpType.DoNotHideHrothgarHats => string.Empty, + GlobalEqpType.DoNotHideVieraHats => string.Empty, + _ => string.Empty, + }; + if (path.Length > 0) + identifier.Identify(changedItems, path); + else if (Type is GlobalEqpType.DoNotHideVieraHats) + changedItems["All Hats for Viera"] = null; + else if (Type is GlobalEqpType.DoNotHideHrothgarHats) + changedItems["All Hats for Hrothgar"] = null; + } public MetaIndex FileIndex() => MetaIndex.Eqp; + + MetaManipulationType IMetaIdentifier.Type + => MetaManipulationType.GlobalEqp; } diff --git a/Penumbra/Meta/Manipulations/Gmp.cs b/Penumbra/Meta/Manipulations/Gmp.cs index 1b7c70ba..a6fcf58b 100644 --- a/Penumbra/Meta/Manipulations/Gmp.cs +++ b/Penumbra/Meta/Manipulations/Gmp.cs @@ -36,4 +36,7 @@ public JObject AddToJson(JObject jObj) jObj["SetId"] = SetId.Id.ToString(); return jObj; } + + public MetaManipulationType Type + => MetaManipulationType.Gmp; } diff --git a/Penumbra/Meta/Manipulations/GmpManipulation.cs b/Penumbra/Meta/Manipulations/GmpManipulation.cs deleted file mode 100644 index 431f6325..00000000 --- a/Penumbra/Meta/Manipulations/GmpManipulation.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Newtonsoft.Json; -using Penumbra.GameData.Structs; -using Penumbra.Interop.Structs; -using Penumbra.Meta.Files; - -namespace Penumbra.Meta.Manipulations; - -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct GmpManipulation(GmpIdentifier identifier, GmpEntry entry) : IMetaManipulation -{ - [JsonIgnore] - public GmpIdentifier Identifier { get; } = identifier; - - public GmpEntry Entry { get; } = entry; - - public PrimaryId SetId - => Identifier.SetId; - - [JsonConstructor] - public GmpManipulation(GmpEntry entry, PrimaryId setId) - : this(new GmpIdentifier(setId), entry) - { } - - public GmpManipulation Copy(GmpEntry entry) - => new(Identifier, entry); - - public override string ToString() - => $"Gmp - {SetId}"; - - public bool Equals(GmpManipulation other) - => SetId == other.SetId; - - public override bool Equals(object? obj) - => obj is GmpManipulation other && Equals(other); - - public override int GetHashCode() - => SetId.GetHashCode(); - - public int CompareTo(GmpManipulation other) - => SetId.Id.CompareTo(other.SetId.Id); - - public MetaIndex FileIndex() - => MetaIndex.Gmp; - - public bool Apply(ExpandedGmpFile file) - { - var entry = file[SetId]; - if (entry == Entry) - return false; - - file[SetId] = Entry; - return true; - } - - public bool Validate() - // No known conditions. - => true; -} diff --git a/Penumbra/Meta/Manipulations/IMetaIdentifier.cs b/Penumbra/Meta/Manipulations/IMetaIdentifier.cs index 4ad6bd3d..5707ffca 100644 --- a/Penumbra/Meta/Manipulations/IMetaIdentifier.cs +++ b/Penumbra/Meta/Manipulations/IMetaIdentifier.cs @@ -4,6 +4,18 @@ namespace Penumbra.Meta.Manipulations; +public enum MetaManipulationType : byte +{ + Unknown = 0, + Imc = 1, + Eqdp = 2, + Eqp = 3, + Est = 4, + Gmp = 5, + Rsp = 6, + GlobalEqp = 7, +} + public interface IMetaIdentifier { public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems); @@ -13,4 +25,8 @@ public interface IMetaIdentifier public bool Validate(); public JObject AddToJson(JObject jObj); + + public MetaManipulationType Type { get; } + + public string ToString(); } diff --git a/Penumbra/Meta/Manipulations/Imc.cs b/Penumbra/Meta/Manipulations/Imc.cs index 2a2f4c03..44c60942 100644 --- a/Penumbra/Meta/Manipulations/Imc.cs +++ b/Penumbra/Meta/Manipulations/Imc.cs @@ -27,9 +27,6 @@ public ImcIdentifier(EquipSlot slot, PrimaryId primaryId, Variant variant) : this(primaryId, variant, slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment, 0, slot, BodySlot.Unknown) { } - public ImcManipulation ToManipulation(ImcEntry entry) - => new(ObjectType, BodySlot, PrimaryId, SecondaryId.Id, Variant.Id, EquipSlot, entry); - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) => AddChangedItems(identifier, changedItems, false); @@ -193,4 +190,7 @@ public JObject AddToJson(JObject jObj) jObj["BodySlot"] = BodySlot.ToString(); return jObj; } + + public MetaManipulationType Type + => MetaManipulationType.Imc; } diff --git a/Penumbra/Meta/Manipulations/ImcManipulation.cs b/Penumbra/Meta/Manipulations/ImcManipulation.cs deleted file mode 100644 index 5065a06e..00000000 --- a/Penumbra/Meta/Manipulations/ImcManipulation.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Interop.Structs; -using Penumbra.Meta.Files; -using Penumbra.String.Classes; - -namespace Penumbra.Meta.Manipulations; - -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct ImcManipulation : IMetaManipulation -{ - [JsonIgnore] - public ImcIdentifier Identifier { get; private init; } - - public ImcEntry Entry { get; private init; } - - - public PrimaryId PrimaryId - => Identifier.PrimaryId; - - public SecondaryId SecondaryId - => Identifier.SecondaryId; - - public Variant Variant - => Identifier.Variant; - - [JsonConverter(typeof(StringEnumConverter))] - public ObjectType ObjectType - => Identifier.ObjectType; - - [JsonConverter(typeof(StringEnumConverter))] - public EquipSlot EquipSlot - => Identifier.EquipSlot; - - [JsonConverter(typeof(StringEnumConverter))] - public BodySlot BodySlot - => Identifier.BodySlot; - - public ImcManipulation(EquipSlot equipSlot, ushort variant, PrimaryId primaryId, ImcEntry entry) - : this(new ImcIdentifier(equipSlot, primaryId, variant), entry) - { } - - public ImcManipulation(ImcIdentifier identifier, ImcEntry entry) - { - Identifier = identifier; - Entry = entry; - } - - - // Variants were initially ushorts but got shortened to bytes. - // There are still some manipulations around that have values > 255 for variant, - // so we change the unused value to something nonsensical in that case, just so they do not compare equal, - // and clamp the variant to 255. - [JsonConstructor] - internal ImcManipulation(ObjectType objectType, BodySlot bodySlot, PrimaryId primaryId, SecondaryId secondaryId, ushort variant, - EquipSlot equipSlot, ImcEntry entry) - { - Entry = entry; - var v = (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue); - Identifier = objectType switch - { - ObjectType.Accessory or ObjectType.Equipment => new ImcIdentifier(primaryId, v, objectType, 0, equipSlot, - variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown), - ObjectType.DemiHuman => new ImcIdentifier(primaryId, v, objectType, secondaryId, equipSlot, variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown), - _ => new ImcIdentifier(primaryId, v, objectType, secondaryId, equipSlot, bodySlot == BodySlot.Unknown ? BodySlot.Body : BodySlot.Unknown), - }; - } - - public ImcManipulation Copy(ImcEntry entry) - => new(Identifier, entry); - - public override string ToString() - => Identifier.ToString(); - - public bool Equals(ImcManipulation other) - => Identifier == other.Identifier; - - public override bool Equals(object? obj) - => obj is ImcManipulation other && Equals(other); - - public override int GetHashCode() - => Identifier.GetHashCode(); - - public int CompareTo(ImcManipulation other) - => Identifier.CompareTo(other.Identifier); - - public MetaIndex FileIndex() - => Identifier.FileIndex(); - - public Utf8GamePath GamePath() - => Identifier.GamePath(); - - public bool Apply(ImcFile file) - => file.SetEntry(ImcFile.PartIndex(EquipSlot), Variant.Id, Entry); - - public bool Validate(bool withMaterial) - { - if (!Identifier.Validate()) - return false; - - if (withMaterial && Entry.MaterialId == 0) - return false; - - return true; - } -} diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs index 236157ae..5a51df83 100644 --- a/Penumbra/Meta/Manipulations/MetaDictionary.cs +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Penumbra.Collections.Cache; using Penumbra.GameData.Structs; using Penumbra.Util; using ImcEntry = Penumbra.GameData.Structs.ImcEntry; @@ -7,7 +8,7 @@ namespace Penumbra.Meta.Manipulations; [JsonConverter(typeof(Converter))] -public class MetaDictionary : IEnumerable +public class MetaDictionary { private readonly Dictionary _imc = []; private readonly Dictionary _eqp = []; @@ -20,32 +21,50 @@ public class MetaDictionary : IEnumerable public IReadOnlyDictionary Imc => _imc; + public IReadOnlyDictionary Eqp + => _eqp; + + public IReadOnlyDictionary Eqdp + => _eqdp; + + public IReadOnlyDictionary Est + => _est; + + public IReadOnlyDictionary Gmp + => _gmp; + + public IReadOnlyDictionary Rsp + => _rsp; + + public IReadOnlySet GlobalEqp + => _globalEqp; + public int Count { get; private set; } - public int GetCount(MetaManipulation.Type type) + public int GetCount(MetaManipulationType type) => type switch { - MetaManipulation.Type.Imc => _imc.Count, - MetaManipulation.Type.Eqdp => _eqdp.Count, - MetaManipulation.Type.Eqp => _eqp.Count, - MetaManipulation.Type.Est => _est.Count, - MetaManipulation.Type.Gmp => _gmp.Count, - MetaManipulation.Type.Rsp => _rsp.Count, - MetaManipulation.Type.GlobalEqp => _globalEqp.Count, - _ => 0, + MetaManipulationType.Imc => _imc.Count, + MetaManipulationType.Eqdp => _eqdp.Count, + MetaManipulationType.Eqp => _eqp.Count, + MetaManipulationType.Est => _est.Count, + MetaManipulationType.Gmp => _gmp.Count, + MetaManipulationType.Rsp => _rsp.Count, + MetaManipulationType.GlobalEqp => _globalEqp.Count, + _ => 0, }; - public bool CanAdd(IMetaIdentifier identifier) + public bool Contains(IMetaIdentifier identifier) => identifier switch { - EqdpIdentifier eqdpIdentifier => !_eqdp.ContainsKey(eqdpIdentifier), - EqpIdentifier eqpIdentifier => !_eqp.ContainsKey(eqpIdentifier), - EstIdentifier estIdentifier => !_est.ContainsKey(estIdentifier), - GlobalEqpManipulation globalEqpManipulation => !_globalEqp.Contains(globalEqpManipulation), - GmpIdentifier gmpIdentifier => !_gmp.ContainsKey(gmpIdentifier), - ImcIdentifier imcIdentifier => !_imc.ContainsKey(imcIdentifier), - RspIdentifier rspIdentifier => !_rsp.ContainsKey(rspIdentifier), - _ => false, + EqdpIdentifier i => _eqdp.ContainsKey(i), + EqpIdentifier i => _eqp.ContainsKey(i), + EstIdentifier i => _est.ContainsKey(i), + GlobalEqpManipulation i => _globalEqp.Contains(i), + GmpIdentifier i => _gmp.ContainsKey(i), + ImcIdentifier i => _imc.ContainsKey(i), + RspIdentifier i => _rsp.ContainsKey(i), + _ => false, }; public void Clear() @@ -69,17 +88,16 @@ public bool Equals(MetaDictionary other) && _gmp.SetEquals(other._gmp) && _globalEqp.SetEquals(other._globalEqp); - public IEnumerator GetEnumerator() - => _imc.Select(kvp => new MetaManipulation(new ImcManipulation(kvp.Key, kvp.Value))) - .Concat(_eqp.Select(kvp => new MetaManipulation(new EqpManipulation(kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))))) - .Concat(_eqdp.Select(kvp => new MetaManipulation(new EqdpManipulation(kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))))) - .Concat(_est.Select(kvp => new MetaManipulation(new EstManipulation(kvp.Key, kvp.Value)))) - .Concat(_rsp.Select(kvp => new MetaManipulation(new RspManipulation(kvp.Key, kvp.Value)))) - .Concat(_gmp.Select(kvp => new MetaManipulation(new GmpManipulation(kvp.Key, kvp.Value)))) - .Concat(_globalEqp.Select(manip => new MetaManipulation(manip))).GetEnumerator(); + public IEnumerable Identifiers + => _imc.Keys.Cast() + .Concat(_eqdp.Keys.Cast()) + .Concat(_eqp.Keys.Cast()) + .Concat(_est.Keys.Cast()) + .Concat(_gmp.Keys.Cast()) + .Concat(_rsp.Keys.Cast()) + .Concat(_globalEqp.Cast()); - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); + #region TryAdd public bool TryAdd(ImcIdentifier identifier, ImcEntry entry) { @@ -90,7 +108,6 @@ public bool TryAdd(ImcIdentifier identifier, ImcEntry entry) return true; } - public bool TryAdd(EqpIdentifier identifier, EqpEntryInternal entry) { if (!_eqp.TryAdd(identifier, entry)) @@ -103,7 +120,6 @@ public bool TryAdd(EqpIdentifier identifier, EqpEntryInternal entry) public bool TryAdd(EqpIdentifier identifier, EqpEntry entry) => TryAdd(identifier, new EqpEntryInternal(entry, identifier.Slot)); - public bool TryAdd(EqdpIdentifier identifier, EqdpEntryInternal entry) { if (!_eqdp.TryAdd(identifier, entry)) @@ -152,6 +168,10 @@ public bool TryAdd(GlobalEqpManipulation identifier) return true; } + #endregion + + #region Update + public bool Update(ImcIdentifier identifier, ImcEntry entry) { if (!_imc.ContainsKey(identifier)) @@ -161,7 +181,6 @@ public bool Update(ImcIdentifier identifier, ImcEntry entry) return true; } - public bool Update(EqpIdentifier identifier, EqpEntryInternal entry) { if (!_eqp.ContainsKey(identifier)) @@ -174,7 +193,6 @@ public bool Update(EqpIdentifier identifier, EqpEntryInternal entry) public bool Update(EqpIdentifier identifier, EqpEntry entry) => Update(identifier, new EqpEntryInternal(entry, identifier.Slot)); - public bool Update(EqdpIdentifier identifier, EqdpEntryInternal entry) { if (!_eqdp.ContainsKey(identifier)) @@ -214,6 +232,50 @@ public bool Update(RspIdentifier identifier, RspEntry entry) return true; } + #endregion + + #region TryGetValue + + public bool TryGetValue(EstIdentifier identifier, out EstEntry value) + => _est.TryGetValue(identifier, out value); + + public bool TryGetValue(EqpIdentifier identifier, out EqpEntryInternal value) + => _eqp.TryGetValue(identifier, out value); + + public bool TryGetValue(EqdpIdentifier identifier, out EqdpEntryInternal value) + => _eqdp.TryGetValue(identifier, out value); + + public bool TryGetValue(GmpIdentifier identifier, out GmpEntry value) + => _gmp.TryGetValue(identifier, out value); + + public bool TryGetValue(RspIdentifier identifier, out RspEntry value) + => _rsp.TryGetValue(identifier, out value); + + public bool TryGetValue(ImcIdentifier identifier, out ImcEntry value) + => _imc.TryGetValue(identifier, out value); + + #endregion + + public bool Remove(IMetaIdentifier identifier) + { + var ret = identifier switch + { + EqdpIdentifier i => _eqdp.Remove(i), + EqpIdentifier i => _eqp.Remove(i), + EstIdentifier i => _est.Remove(i), + GlobalEqpManipulation i => _globalEqp.Remove(i), + GmpIdentifier i => _gmp.Remove(i), + ImcIdentifier i => _imc.Remove(i), + RspIdentifier i => _rsp.Remove(i), + _ => false, + }; + if (ret) + --Count; + return ret; + } + + #region Merging + public void UnionWith(MetaDictionary manips) { foreach (var (identifier, entry) in manips._imc) @@ -287,24 +349,6 @@ public bool MergeForced(MetaDictionary manips, out IMetaIdentifier? failedIdenti return false; } - public bool TryGetValue(EstIdentifier identifier, out EstEntry value) - => _est.TryGetValue(identifier, out value); - - public bool TryGetValue(EqpIdentifier identifier, out EqpEntryInternal value) - => _eqp.TryGetValue(identifier, out value); - - public bool TryGetValue(EqdpIdentifier identifier, out EqdpEntryInternal value) - => _eqdp.TryGetValue(identifier, out value); - - public bool TryGetValue(GmpIdentifier identifier, out GmpEntry value) - => _gmp.TryGetValue(identifier, out value); - - public bool TryGetValue(RspIdentifier identifier, out RspEntry value) - => _rsp.TryGetValue(identifier, out value); - - public bool TryGetValue(ImcIdentifier identifier, out ImcEntry value) - => _imc.TryGetValue(identifier, out value); - public void SetTo(MetaDictionary other) { _imc.SetTo(other._imc); @@ -329,6 +373,8 @@ public void UpdateTo(MetaDictionary other) Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _globalEqp.Count; } + #endregion + public MetaDictionary Clone() { var ret = new MetaDictionary(); @@ -336,29 +382,124 @@ public MetaDictionary Clone() return ret; } - private static void WriteJson(JsonWriter writer, JsonSerializer serializer, IMetaIdentifier identifier, object entry) - { - var type = identifier switch + public static JObject Serialize(EqpIdentifier identifier, EqpEntryInternal entry) + => Serialize(identifier, entry.ToEntry(identifier.Slot)); + + public static JObject Serialize(EqpIdentifier identifier, EqpEntry entry) + => new() { - ImcIdentifier => "Imc", - EqdpIdentifier => "Eqdp", - EqpIdentifier => "Eqp", - EstIdentifier => "Est", - GmpIdentifier => "Gmp", - RspIdentifier => "Rsp", - GlobalEqpManipulation => "GlobalEqp", - _ => string.Empty, + ["Type"] = MetaManipulationType.Eqp.ToString(), + ["Manipulation"] = identifier.AddToJson(new JObject + { + ["Entry"] = (ulong)entry, + }), }; - if (type.Length == 0) - return; + public static JObject Serialize(EqdpIdentifier identifier, EqdpEntryInternal entry) + => Serialize(identifier, entry.ToEntry(identifier.Slot)); + + public static JObject Serialize(EqdpIdentifier identifier, EqdpEntry entry) + => new() + { + ["Type"] = MetaManipulationType.Eqdp.ToString(), + ["Manipulation"] = identifier.AddToJson(new JObject + { + ["Entry"] = (ushort)entry, + }), + }; + + public static JObject Serialize(EstIdentifier identifier, EstEntry entry) + => new() + { + ["Type"] = MetaManipulationType.Est.ToString(), + ["Manipulation"] = identifier.AddToJson(new JObject + { + ["Entry"] = entry.Value, + }), + }; + + public static JObject Serialize(GmpIdentifier identifier, GmpEntry entry) + => new() + { + ["Type"] = MetaManipulationType.Gmp.ToString(), + ["Manipulation"] = identifier.AddToJson(new JObject + { + ["Entry"] = JObject.FromObject(entry), + }), + }; + + public static JObject Serialize(ImcIdentifier identifier, ImcEntry entry) + => new() + { + ["Type"] = MetaManipulationType.Imc.ToString(), + ["Manipulation"] = identifier.AddToJson(new JObject + { + ["Entry"] = JObject.FromObject(entry), + }), + }; - writer.WriteStartObject(); - writer.WritePropertyName("Type"); - writer.WriteValue(type); - writer.WritePropertyName("Manipulation"); + public static JObject Serialize(RspIdentifier identifier, RspEntry entry) + => new() + { + ["Type"] = MetaManipulationType.Rsp.ToString(), + ["Manipulation"] = identifier.AddToJson(new JObject + { + ["Entry"] = entry.Value, + }), + }; + + public static JObject Serialize(GlobalEqpManipulation identifier) + => new() + { + ["Type"] = MetaManipulationType.GlobalEqp.ToString(), + ["Manipulation"] = identifier.AddToJson(new JObject()), + }; + + public static JObject? Serialize(TIdentifier identifier, TEntry entry) + where TIdentifier : unmanaged, IMetaIdentifier + where TEntry : unmanaged + { + if (typeof(TIdentifier) == typeof(EqpIdentifier) && typeof(TEntry) == typeof(EqpEntryInternal)) + return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); + if (typeof(TIdentifier) == typeof(EqpIdentifier) && typeof(TEntry) == typeof(EqpEntry)) + return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); + if (typeof(TIdentifier) == typeof(EqdpIdentifier) && typeof(TEntry) == typeof(EqdpEntryInternal)) + return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); + if (typeof(TIdentifier) == typeof(EqdpIdentifier) && typeof(TEntry) == typeof(EqdpEntry)) + return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); + if (typeof(TIdentifier) == typeof(EstIdentifier) && typeof(TEntry) == typeof(EstEntry)) + return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); + if (typeof(TIdentifier) == typeof(GmpIdentifier) && typeof(TEntry) == typeof(GmpEntry)) + return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); + if (typeof(TIdentifier) == typeof(RspIdentifier) && typeof(TEntry) == typeof(RspEntry)) + return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); + if (typeof(TIdentifier) == typeof(ImcIdentifier) && typeof(TEntry) == typeof(ImcEntry)) + return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); + if (typeof(TIdentifier) == typeof(GlobalEqpManipulation)) + return Serialize(Unsafe.As(ref identifier)); + + return null; + } + + public static JArray SerializeTo(JArray array, IEnumerable> manipulations) + where TIdentifier : unmanaged, IMetaIdentifier + where TEntry : unmanaged + { + foreach (var (identifier, entry) in manipulations) + { + if (Serialize(identifier, entry) is { } jObj) + array.Add(jObj); + } - writer.WriteEndObject(); + return array; + } + + public static JArray SerializeTo(JArray array, IEnumerable manipulations) + { + foreach (var manip in manipulations) + array.Add(Serialize(manip)); + + return array; } private class Converter : JsonConverter @@ -371,30 +512,27 @@ public override void WriteJson(JsonWriter writer, MetaDictionary? value, JsonSer return; } - writer.WriteStartArray(); - foreach (var item in value) - { - writer.WriteStartObject(); - writer.WritePropertyName("Type"); - writer.WriteValue(item.ManipulationType.ToString()); - writer.WritePropertyName("Manipulation"); - serializer.Serialize(writer, item.Manipulation); - writer.WriteEndObject(); - } - - writer.WriteEndArray(); + var array = new JArray(); + SerializeTo(array, value._imc); + SerializeTo(array, value._eqp); + SerializeTo(array, value._eqdp); + SerializeTo(array, value._est); + SerializeTo(array, value._rsp); + SerializeTo(array, value._gmp); + SerializeTo(array, value._globalEqp); + array.WriteTo(writer); } public override MetaDictionary ReadJson(JsonReader reader, Type objectType, MetaDictionary? existingValue, bool hasExistingValue, JsonSerializer serializer) { - var dict = existingValue ?? []; + var dict = existingValue ?? new MetaDictionary(); dict.Clear(); var jObj = JArray.Load(reader); foreach (var item in jObj) { - var type = item["Type"]?.ToObject() ?? MetaManipulation.Type.Unknown; - if (type is MetaManipulation.Type.Unknown) + var type = item["Type"]?.ToObject() ?? MetaManipulationType.Unknown; + if (type is MetaManipulationType.Unknown) { Penumbra.Log.Warning($"Invalid Meta Manipulation Type {type} encountered."); continue; @@ -408,7 +546,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta switch (type) { - case MetaManipulation.Type.Imc: + case MetaManipulationType.Imc: { var identifier = ImcIdentifier.FromJson(manip); var entry = manip["Entry"]?.ToObject(); @@ -418,7 +556,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta Penumbra.Log.Warning("Invalid IMC Manipulation encountered."); break; } - case MetaManipulation.Type.Eqdp: + case MetaManipulationType.Eqdp: { var identifier = EqdpIdentifier.FromJson(manip); var entry = (EqdpEntry?)manip["Entry"]?.ToObject(); @@ -428,7 +566,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta Penumbra.Log.Warning("Invalid EQDP Manipulation encountered."); break; } - case MetaManipulation.Type.Eqp: + case MetaManipulationType.Eqp: { var identifier = EqpIdentifier.FromJson(manip); var entry = (EqpEntry?)manip["Entry"]?.ToObject(); @@ -438,7 +576,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta Penumbra.Log.Warning("Invalid EQP Manipulation encountered."); break; } - case MetaManipulation.Type.Est: + case MetaManipulationType.Est: { var identifier = EstIdentifier.FromJson(manip); var entry = manip["Entry"]?.ToObject(); @@ -448,7 +586,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta Penumbra.Log.Warning("Invalid EST Manipulation encountered."); break; } - case MetaManipulation.Type.Gmp: + case MetaManipulationType.Gmp: { var identifier = GmpIdentifier.FromJson(manip); var entry = manip["Entry"]?.ToObject(); @@ -458,7 +596,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta Penumbra.Log.Warning("Invalid GMP Manipulation encountered."); break; } - case MetaManipulation.Type.Rsp: + case MetaManipulationType.Rsp: { var identifier = RspIdentifier.FromJson(manip); var entry = manip["Entry"]?.ToObject(); @@ -468,7 +606,7 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta Penumbra.Log.Warning("Invalid RSP Manipulation encountered."); break; } - case MetaManipulation.Type.GlobalEqp: + case MetaManipulationType.GlobalEqp: { var identifier = GlobalEqpManipulation.FromJson(manip); if (identifier.HasValue) @@ -483,4 +621,22 @@ public override MetaDictionary ReadJson(JsonReader reader, Type objectType, Meta return dict; } } + + public MetaDictionary() + { } + + public MetaDictionary(MetaCache? cache) + { + if (cache == null) + return; + + _imc = cache.Imc.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); + _eqp = cache.Eqp.ToDictionary(kvp => kvp.Key, kvp => new EqpEntryInternal(kvp.Value.Entry, kvp.Key.Slot)); + _eqdp = cache.Eqdp.ToDictionary(kvp => kvp.Key, kvp => new EqdpEntryInternal(kvp.Value.Entry, kvp.Key.Slot)); + _est = cache.Est.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); + _gmp = cache.Gmp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); + _rsp = cache.Rsp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); + _globalEqp = cache.GlobalEqp.Select(kvp => kvp.Key).ToHashSet(); + Count = cache.Count; + } } diff --git a/Penumbra/Meta/Manipulations/MetaManipulation.cs b/Penumbra/Meta/Manipulations/MetaManipulation.cs deleted file mode 100644 index b80681d2..00000000 --- a/Penumbra/Meta/Manipulations/MetaManipulation.cs +++ /dev/null @@ -1,457 +0,0 @@ -using Dalamud.Interface; -using ImGuiNET; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using OtterGui; -using Penumbra.GameData.Enums; -using Penumbra.Interop.Structs; -using Penumbra.Mods.Editor; -using Penumbra.String.Functions; -using Penumbra.UI; -using Penumbra.UI.ModsTab; - -namespace Penumbra.Meta.Manipulations; - -#if false -private static class ImcRow -{ - private static ImcIdentifier _newIdentifier = ImcIdentifier.Default; - - private static float IdWidth - => 80 * UiHelpers.Scale; - - private static float SmallIdWidth - => 45 * UiHelpers.Scale; - - public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) - { - ImGui.TableNextColumn(); - CopyToClipboardButton("Copy all current IMC manipulations to clipboard.", iconSize, - editor.MetaEditor.Imc.Select(m => (MetaManipulation)m)); - ImGui.TableNextColumn(); - var (defaultEntry, fileExists, _) = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true); - var manip = (MetaManipulation)new ImcManipulation(_newIdentifier, defaultEntry); - var canAdd = fileExists && editor.MetaEditor.CanAdd(manip); - var tt = canAdd ? "Stage this edit." : !fileExists ? "This IMC file does not exist." : "This entry is already edited."; - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) - editor.MetaEditor.Add(manip); - - // Identifier - ImGui.TableNextColumn(); - var change = ImcManipulationDrawer.DrawObjectType(ref _newIdentifier); - - ImGui.TableNextColumn(); - change |= ImcManipulationDrawer.DrawPrimaryId(ref _newIdentifier); - 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 (_newIdentifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory) - change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier); - else - change |= ImcManipulationDrawer.DrawSecondaryId(ref _newIdentifier); - - ImGui.TableNextColumn(); - change |= ImcManipulationDrawer.DrawVariant(ref _newIdentifier); - - ImGui.TableNextColumn(); - if (_newIdentifier.ObjectType is ObjectType.DemiHuman) - change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier, 70); - else - ImUtf8.ScaledDummy(new Vector2(70 * UiHelpers.Scale, 0)); - - if (change) - defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true).Entry; - // Values - using var disabled = ImRaii.Disabled(); - ImGui.TableNextColumn(); - ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref defaultEntry, false); - ImGui.SameLine(); - ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref defaultEntry, false); - ImGui.TableNextColumn(); - ImcManipulationDrawer.DrawDecalId(defaultEntry, ref defaultEntry, false); - ImGui.SameLine(); - ImcManipulationDrawer.DrawVfxId(defaultEntry, ref defaultEntry, false); - ImGui.SameLine(); - ImcManipulationDrawer.DrawSoundId(defaultEntry, ref defaultEntry, false); - ImGui.TableNextColumn(); - ImcManipulationDrawer.DrawAttributes(defaultEntry, ref defaultEntry); - } - - public static void Draw(MetaFileManager metaFileManager, ImcManipulation meta, ModEditor editor, Vector2 iconSize) - { - DrawMetaButtons(meta, editor, iconSize); - - // Identifier - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.ObjectType.ToName()); - ImGuiUtil.HoverTooltip(ObjectTypeTooltip); - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.PrimaryId.ToString()); - ImGuiUtil.HoverTooltip(PrimaryIdTooltipShort); - - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - if (meta.ObjectType is ObjectType.Equipment or ObjectType.Accessory) - { - ImGui.TextUnformatted(meta.EquipSlot.ToName()); - ImGuiUtil.HoverTooltip(EquipSlotTooltip); - } - else - { - ImGui.TextUnformatted(meta.SecondaryId.ToString()); - ImGuiUtil.HoverTooltip(SecondaryIdTooltip); - } - - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.Variant.ToString()); - ImGuiUtil.HoverTooltip(VariantIdTooltip); - - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - if (meta.ObjectType is ObjectType.DemiHuman) - { - ImGui.TextUnformatted(meta.EquipSlot.ToName()); - ImGuiUtil.HoverTooltip(EquipSlotTooltip); - } - - // Values - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, - new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y)); - ImGui.TableNextColumn(); - var defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(meta.Identifier, true).Entry; - var newEntry = meta.Entry; - var changes = ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref newEntry, true); - ImGui.SameLine(); - changes |= ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref newEntry, true); - ImGui.TableNextColumn(); - changes |= ImcManipulationDrawer.DrawDecalId(defaultEntry, ref newEntry, true); - ImGui.SameLine(); - changes |= ImcManipulationDrawer.DrawVfxId(defaultEntry, ref newEntry, true); - ImGui.SameLine(); - changes |= ImcManipulationDrawer.DrawSoundId(defaultEntry, ref newEntry, true); - ImGui.TableNextColumn(); - changes |= ImcManipulationDrawer.DrawAttributes(defaultEntry, ref newEntry); - - if (changes) - editor.MetaEditor.Change(meta.Copy(newEntry)); - } -} - -#endif - -public interface IMetaManipulation -{ - public MetaIndex FileIndex(); -} - -public interface IMetaManipulation - : IMetaManipulation, IComparable, IEquatable where T : struct -{ } - -[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 16)] -public readonly struct MetaManipulation : IEquatable, IComparable -{ - public const int CurrentVersion = 0; - - public enum Type : byte - { - Unknown = 0, - Imc = 1, - Eqdp = 2, - Eqp = 3, - Est = 4, - Gmp = 5, - Rsp = 6, - GlobalEqp = 7, - } - - [FieldOffset(0)] - [JsonIgnore] - public readonly EqpManipulation Eqp = default; - - [FieldOffset(0)] - [JsonIgnore] - public readonly GmpManipulation Gmp = default; - - [FieldOffset(0)] - [JsonIgnore] - public readonly EqdpManipulation Eqdp = default; - - [FieldOffset(0)] - [JsonIgnore] - public readonly EstManipulation Est = default; - - [FieldOffset(0)] - [JsonIgnore] - public readonly RspManipulation Rsp = default; - - [FieldOffset(0)] - [JsonIgnore] - public readonly ImcManipulation Imc = default; - - [FieldOffset(0)] - [JsonIgnore] - public readonly GlobalEqpManipulation GlobalEqp = default; - - [FieldOffset(15)] - [JsonConverter(typeof(StringEnumConverter))] - [JsonProperty("Type")] - public readonly Type ManipulationType; - - public object? Manipulation - { - get => ManipulationType switch - { - Type.Unknown => null, - Type.Imc => Imc, - Type.Eqdp => Eqdp, - Type.Eqp => Eqp, - Type.Est => Est, - Type.Gmp => Gmp, - Type.Rsp => Rsp, - Type.GlobalEqp => GlobalEqp, - _ => null, - }; - init - { - switch (value) - { - case EqpManipulation m: - Eqp = m; - ManipulationType = m.Validate() ? Type.Eqp : Type.Unknown; - return; - case EqdpManipulation m: - Eqdp = m; - ManipulationType = m.Validate() ? Type.Eqdp : Type.Unknown; - return; - case GmpManipulation m: - Gmp = m; - ManipulationType = m.Validate() ? Type.Gmp : Type.Unknown; - return; - case EstManipulation m: - Est = m; - ManipulationType = m.Validate() ? Type.Est : Type.Unknown; - return; - case RspManipulation m: - Rsp = m; - ManipulationType = m.Validate() ? Type.Rsp : Type.Unknown; - return; - case ImcManipulation m: - Imc = m; - ManipulationType = m.Validate(true) ? Type.Imc : Type.Unknown; - return; - case GlobalEqpManipulation m: - GlobalEqp = m; - ManipulationType = m.Validate() ? Type.GlobalEqp : Type.Unknown; - return; - } - } - } - - public bool Validate() - { - return ManipulationType switch - { - Type.Imc => Imc.Validate(true), - Type.Eqdp => Eqdp.Validate(), - Type.Eqp => Eqp.Validate(), - Type.Est => Est.Validate(), - Type.Gmp => Gmp.Validate(), - Type.Rsp => Rsp.Validate(), - Type.GlobalEqp => GlobalEqp.Validate(), - _ => false, - }; - } - - public MetaManipulation(EqpManipulation eqp) - { - Eqp = eqp; - ManipulationType = Type.Eqp; - } - - public MetaManipulation(GmpManipulation gmp) - { - Gmp = gmp; - ManipulationType = Type.Gmp; - } - - public MetaManipulation(EqdpManipulation eqdp) - { - Eqdp = eqdp; - ManipulationType = Type.Eqdp; - } - - public MetaManipulation(EstManipulation est) - { - Est = est; - ManipulationType = Type.Est; - } - - public MetaManipulation(RspManipulation rsp) - { - Rsp = rsp; - ManipulationType = Type.Rsp; - } - - public MetaManipulation(ImcManipulation imc) - { - Imc = imc; - ManipulationType = Type.Imc; - } - - public MetaManipulation(GlobalEqpManipulation eqp) - { - GlobalEqp = eqp; - ManipulationType = Type.GlobalEqp; - } - - public static implicit operator MetaManipulation(EqpManipulation eqp) - => new(eqp); - - public static implicit operator MetaManipulation(GmpManipulation gmp) - => new(gmp); - - public static implicit operator MetaManipulation(EqdpManipulation eqdp) - => new(eqdp); - - public static implicit operator MetaManipulation(EstManipulation est) - => new(est); - - public static implicit operator MetaManipulation(RspManipulation rsp) - => new(rsp); - - public static implicit operator MetaManipulation(ImcManipulation imc) - => new(imc); - - public static implicit operator MetaManipulation(GlobalEqpManipulation eqp) - => new(eqp); - - public bool EntryEquals(MetaManipulation other) - { - if (ManipulationType != other.ManipulationType) - return false; - - return ManipulationType switch - { - Type.Eqp => Eqp.Entry.Equals(other.Eqp.Entry), - Type.Gmp => Gmp.Entry.Equals(other.Gmp.Entry), - Type.Eqdp => Eqdp.Entry.Equals(other.Eqdp.Entry), - Type.Est => Est.Entry.Equals(other.Est.Entry), - Type.Rsp => Rsp.Entry.Equals(other.Rsp.Entry), - Type.Imc => Imc.Entry.Equals(other.Imc.Entry), - Type.GlobalEqp => true, - _ => throw new ArgumentOutOfRangeException(), - }; - } - - public bool Equals(MetaManipulation other) - { - if (ManipulationType != other.ManipulationType) - return false; - - return ManipulationType switch - { - Type.Eqp => Eqp.Equals(other.Eqp), - Type.Gmp => Gmp.Equals(other.Gmp), - Type.Eqdp => Eqdp.Equals(other.Eqdp), - Type.Est => Est.Equals(other.Est), - Type.Rsp => Rsp.Equals(other.Rsp), - Type.Imc => Imc.Equals(other.Imc), - Type.GlobalEqp => GlobalEqp.Equals(other.GlobalEqp), - _ => false, - }; - } - - public MetaManipulation WithEntryOf(MetaManipulation other) - { - if (ManipulationType != other.ManipulationType) - return this; - - return ManipulationType switch - { - Type.Eqp => Eqp.Copy(other.Eqp.Entry), - Type.Gmp => Gmp.Copy(other.Gmp.Entry), - Type.Eqdp => Eqdp.Copy(other.Eqdp), - Type.Est => Est.Copy(other.Est.Entry), - Type.Rsp => Rsp.Copy(other.Rsp.Entry), - Type.Imc => Imc.Copy(other.Imc.Entry), - Type.GlobalEqp => GlobalEqp, - _ => throw new ArgumentOutOfRangeException(), - }; - } - - public override bool Equals(object? obj) - => obj is MetaManipulation other && Equals(other); - - public override int GetHashCode() - => ManipulationType switch - { - Type.Eqp => Eqp.GetHashCode(), - Type.Gmp => Gmp.GetHashCode(), - Type.Eqdp => Eqdp.GetHashCode(), - Type.Est => Est.GetHashCode(), - Type.Rsp => Rsp.GetHashCode(), - Type.Imc => Imc.GetHashCode(), - Type.GlobalEqp => GlobalEqp.GetHashCode(), - _ => 0, - }; - - public unsafe int CompareTo(MetaManipulation other) - { - fixed (MetaManipulation* lhs = &this) - { - return MemoryUtility.MemCmpUnchecked(lhs, &other, sizeof(MetaManipulation)); - } - } - - public override string ToString() - => ManipulationType switch - { - Type.Eqp => Eqp.ToString(), - Type.Gmp => Gmp.ToString(), - Type.Eqdp => Eqdp.ToString(), - Type.Est => Est.ToString(), - Type.Rsp => Rsp.ToString(), - Type.Imc => Imc.ToString(), - Type.GlobalEqp => GlobalEqp.ToString(), - _ => "Invalid", - }; - - public string EntryToString() - => ManipulationType switch - { - Type.Imc => - $"{Imc.Entry.DecalId}-{Imc.Entry.MaterialId}-{Imc.Entry.VfxId}-{Imc.Entry.SoundId}-{Imc.Entry.MaterialAnimationId}-{Imc.Entry.AttributeMask}", - Type.Eqdp => $"{(ushort)Eqdp.Entry:X}", - Type.Eqp => $"{(ulong)Eqp.Entry:X}", - Type.Est => $"{Est.Entry}", - Type.Gmp => $"{Gmp.Entry.Value}", - Type.Rsp => $"{Rsp.Entry}", - Type.GlobalEqp => string.Empty, - _ => string.Empty, - }; - - public static bool operator ==(MetaManipulation left, MetaManipulation right) - => left.Equals(right); - - public static bool operator !=(MetaManipulation left, MetaManipulation right) - => !(left == right); - - public static bool operator <(MetaManipulation left, MetaManipulation right) - => left.CompareTo(right) < 0; - - public static bool operator <=(MetaManipulation left, MetaManipulation right) - => left.CompareTo(right) <= 0; - - public static bool operator >(MetaManipulation left, MetaManipulation right) - => left.CompareTo(right) > 0; - - public static bool operator >=(MetaManipulation left, MetaManipulation right) - => left.CompareTo(right) >= 0; -} - diff --git a/Penumbra/Meta/Manipulations/Rsp.cs b/Penumbra/Meta/Manipulations/Rsp.cs index ca7cb1c5..73d1d7e5 100644 --- a/Penumbra/Meta/Manipulations/Rsp.cs +++ b/Penumbra/Meta/Manipulations/Rsp.cs @@ -38,6 +38,9 @@ public JObject AddToJson(JObject jObj) var ret = new RspIdentifier(subRace, attribute); return ret.Validate() ? ret : null; } + + public MetaManipulationType Type + => MetaManipulationType.Rsp; } [JsonConverter(typeof(Converter))] diff --git a/Penumbra/Meta/Manipulations/RspManipulation.cs b/Penumbra/Meta/Manipulations/RspManipulation.cs deleted file mode 100644 index e2282c41..00000000 --- a/Penumbra/Meta/Manipulations/RspManipulation.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Penumbra.GameData.Enums; -using Penumbra.Interop.Structs; -using Penumbra.Meta.Files; - -namespace Penumbra.Meta.Manipulations; - -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public readonly struct RspManipulation(RspIdentifier identifier, RspEntry entry) : IMetaManipulation -{ - [JsonIgnore] - public RspIdentifier Identifier { get; } = identifier; - - public RspEntry Entry { get; } = entry; - - [JsonConverter(typeof(StringEnumConverter))] - public SubRace SubRace - => Identifier.SubRace; - - [JsonConverter(typeof(StringEnumConverter))] - public RspAttribute Attribute - => Identifier.Attribute; - - [JsonConstructor] - public RspManipulation(SubRace subRace, RspAttribute attribute, RspEntry entry) - : this(new RspIdentifier(subRace, attribute), entry) - { } - - public RspManipulation Copy(RspEntry entry) - => new(Identifier, entry); - - public override string ToString() - => $"Rsp - {SubRace.ToName()} - {Attribute.ToFullString()}"; - - public bool Equals(RspManipulation other) - => SubRace == other.SubRace - && Attribute == other.Attribute; - - public override bool Equals(object? obj) - => obj is RspManipulation other && Equals(other); - - public override int GetHashCode() - => HashCode.Combine((int)SubRace, (int)Attribute); - - public int CompareTo(RspManipulation other) - { - var s = SubRace.CompareTo(other.SubRace); - return s != 0 ? s : Attribute.CompareTo(other.Attribute); - } - - public MetaIndex FileIndex() - => MetaIndex.HumanCmp; - - public bool Apply(CmpFile file) - { - var value = file[SubRace, Attribute]; - if (value == Entry) - return false; - - file[SubRace, Attribute] = Entry; - return true; - } - - public bool Validate() - => Identifier.Validate() && Entry.Validate(); -} diff --git a/Penumbra/Mods/Editor/IMod.cs b/Penumbra/Mods/Editor/IMod.cs index 06c31846..3da38829 100644 --- a/Penumbra/Mods/Editor/IMod.cs +++ b/Penumbra/Mods/Editor/IMod.cs @@ -10,7 +10,7 @@ public record struct AppliedModData( Dictionary FileRedirections, MetaDictionary Manipulations) { - public static readonly AppliedModData Empty = new([], []); + public static readonly AppliedModData Empty = new([], new MetaDictionary()); } public interface IMod diff --git a/Penumbra/Mods/Editor/ModMetaEditor.cs b/Penumbra/Mods/Editor/ModMetaEditor.cs index 42171378..bacf4122 100644 --- a/Penumbra/Mods/Editor/ModMetaEditor.cs +++ b/Penumbra/Mods/Editor/ModMetaEditor.cs @@ -26,20 +26,20 @@ public void Add(string name, int count) } } - public readonly FrozenDictionary OtherData = - Enum.GetValues().ToFrozenDictionary(t => t, _ => new OtherOptionData()); + public readonly FrozenDictionary OtherData = + Enum.GetValues().ToFrozenDictionary(t => t, _ => new OtherOptionData()); - public bool Changes { get; private set; } + public bool Changes { get; set; } public new void Clear() { + Changes = Count > 0; base.Clear(); - Changes = true; } public void Load(Mod mod, IModDataContainer currentOption) { - foreach (var type in Enum.GetValues()) + foreach (var type in Enum.GetValues()) OtherData[type].Clear(); foreach (var option in mod.AllDataContainers) @@ -48,13 +48,13 @@ public void Load(Mod mod, IModDataContainer currentOption) continue; var name = option.GetFullName(); - OtherData[MetaManipulation.Type.Imc].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Imc)); - OtherData[MetaManipulation.Type.Eqp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Eqp)); - OtherData[MetaManipulation.Type.Eqdp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Eqdp)); - OtherData[MetaManipulation.Type.Gmp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Gmp)); - OtherData[MetaManipulation.Type.Est].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Est)); - OtherData[MetaManipulation.Type.Rsp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Rsp)); - OtherData[MetaManipulation.Type.GlobalEqp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.GlobalEqp)); + OtherData[MetaManipulationType.Imc].Add(name, option.Manipulations.GetCount(MetaManipulationType.Imc)); + OtherData[MetaManipulationType.Eqp].Add(name, option.Manipulations.GetCount(MetaManipulationType.Eqp)); + OtherData[MetaManipulationType.Eqdp].Add(name, option.Manipulations.GetCount(MetaManipulationType.Eqdp)); + OtherData[MetaManipulationType.Gmp].Add(name, option.Manipulations.GetCount(MetaManipulationType.Gmp)); + OtherData[MetaManipulationType.Est].Add(name, option.Manipulations.GetCount(MetaManipulationType.Est)); + OtherData[MetaManipulationType.Rsp].Add(name, option.Manipulations.GetCount(MetaManipulationType.Rsp)); + OtherData[MetaManipulationType.GlobalEqp].Add(name, option.Manipulations.GetCount(MetaManipulationType.GlobalEqp)); } Clear(); diff --git a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs index e42a1d31..b7827c47 100644 --- a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs +++ b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs @@ -125,8 +125,8 @@ public static EquipItem[] CreateItemSwap(MetaFileManager manager, ObjectIdentifi _ => (EstType)0, }; - var skipFemale = false; - var skipMale = false; + var skipFemale = false; + var skipMale = false; foreach (var gr in Enum.GetValues()) { switch (gr.Split().Item1) @@ -242,8 +242,8 @@ private static void LookupItem(EquipItem i, out EquipSlot slot, out PrimaryId mo private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, ObjectIdentification identifier, EquipSlot slotFrom, PrimaryId idFrom, PrimaryId idTo, Variant variantFrom) { - var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default); - var imc = new ImcFile(manager, entry.Identifier); + var ident = new ImcIdentifier(slotFrom, idFrom, variantFrom); + var imc = new ImcFile(manager, ident); EquipItem[] items; Variant[] variants; if (idFrom == idTo) @@ -273,7 +273,8 @@ private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager man var manipToIdentifier = new GmpIdentifier(idTo); var manipFromDefault = ExpandedGmpFile.GetDefault(manager, manipFromIdentifier); var manipToDefault = ExpandedGmpFile.GetDefault(manager, manipToIdentifier); - return new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault); + return new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, + manipToIdentifier, manipToDefault); } public static MetaSwap CreateImc(MetaFileManager manager, Func redirections, @@ -286,16 +287,17 @@ public static MetaSwap CreateImc(MetaFileManager manage Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) { var manipFromIdentifier = new ImcIdentifier(slotFrom, idFrom, variantFrom); - var manipToIdentifier = new ImcIdentifier(slotTo, idTo, variantTo); - var manipFromDefault = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom); - var manipToDefault = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo); - var imc = new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault); + var manipToIdentifier = new ImcIdentifier(slotTo, idTo, variantTo); + var manipFromDefault = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom); + var manipToDefault = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo); + var imc = new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, + manipToIdentifier, manipToDefault); var decal = CreateDecal(manager, redirections, imc.SwapToModdedEntry.DecalId); if (decal != null) imc.ChildSwaps.Add(decal); - var avfx = CreateAvfx(manager, redirections, idFrom, idTo, imc.SwapToModdedEntry.VfxId); + var avfx = CreateAvfx(manager, redirections, slotFrom, slotTo, idFrom, idTo, imc.SwapToModdedEntry.VfxId); if (avfx != null) imc.ChildSwaps.Add(avfx); @@ -316,19 +318,21 @@ public static MetaSwap CreateImc(MetaFileManager manage // Example: Abyssos Helm / Body - public static FileSwap? CreateAvfx(MetaFileManager manager, Func redirections, PrimaryId idFrom, PrimaryId idTo, + public static FileSwap? CreateAvfx(MetaFileManager manager, Func redirections, EquipSlot slotFrom, EquipSlot slotTo, + PrimaryId idFrom, PrimaryId idTo, byte vfxId) { if (vfxId == 0) return null; var vfxPathFrom = GamePaths.Equipment.Avfx.Path(idFrom, vfxId); - var vfxPathTo = GamePaths.Equipment.Avfx.Path(idTo, vfxId); - var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo); + vfxPathFrom = ItemSwap.ReplaceType(vfxPathFrom, slotFrom, slotTo, idFrom); + var vfxPathTo = GamePaths.Equipment.Avfx.Path(idTo, vfxId); + var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo); foreach (ref var filePath in avfx.AsAvfx()!.Textures.AsSpan()) { - var atex = CreateAtex(manager, redirections, ref filePath, ref avfx.DataWasChanged); + var atex = CreateAtex(manager, redirections, slotFrom, slotTo, idFrom, ref filePath, ref avfx.DataWasChanged); avfx.ChildSwaps.Add(atex); } @@ -394,8 +398,7 @@ public static MetaSwap CreateImc(MetaFileManager manage } public static FileSwap CreateTex(MetaFileManager manager, Func redirections, char prefix, PrimaryId idFrom, - PrimaryId idTo, - ref MtrlFile.Texture texture, ref bool dataWasChanged) + PrimaryId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged) => CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged); public static FileSwap CreateTex(MetaFileManager manager, Func redirections, char prefix, EquipSlot slotFrom, @@ -404,6 +407,7 @@ public static FileSwap CreateTex(MetaFileManager manager, Func redirections, ref string filePath, - ref bool dataWasChanged) + public static FileSwap CreateAtex(MetaFileManager manager, Func redirections, EquipSlot slotFrom, EquipSlot slotTo, + PrimaryId idFrom, ref string filePath, ref bool dataWasChanged) { var oldPath = filePath; filePath = ItemSwap.AddSuffix(filePath, ".atex", $"_{Path.GetFileName(filePath).GetStableHashCode():x8}"); + filePath = ItemSwap.ReplaceType(filePath, slotFrom, slotTo, idFrom); dataWasChanged = true; return FileSwap.CreateSwap(manager, ResourceType.Atex, redirections, filePath, oldPath, oldPath); diff --git a/Penumbra/Mods/ItemSwap/ItemSwap.cs b/Penumbra/Mods/ItemSwap/ItemSwap.cs index efd8080c..1f4c5e7a 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwap.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwap.cs @@ -136,14 +136,14 @@ public static bool LoadAvfx(MetaFileManager manager, FullPath path, [NotNullWhen public static FileSwap CreatePhyb(MetaFileManager manager, Func redirections, EstType type, GenderRace race, EstEntry estEntry) { - var phybPath = GamePaths.Skeleton.Phyb.Path(race, EstManipulation.ToName(type), estEntry.AsId); + var phybPath = GamePaths.Skeleton.Phyb.Path(race, type.ToName(), estEntry.AsId); return FileSwap.CreateSwap(manager, ResourceType.Phyb, redirections, phybPath, phybPath); } public static FileSwap CreateSklb(MetaFileManager manager, Func redirections, EstType type, GenderRace race, EstEntry estEntry) { - var sklbPath = GamePaths.Skeleton.Sklb.Path(race, EstManipulation.ToName(type), estEntry.AsId); + var sklbPath = GamePaths.Skeleton.Sklb.Path(race, type.ToName(), estEntry.AsId); return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath); } @@ -154,10 +154,11 @@ public static FileSwap CreateSklb(MetaFileManager manager, Func(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault); + var manipToIdentifier = new EstIdentifier(idTo, type, genderRace); + var manipFromDefault = EstFile.GetDefault(manager, manipFromIdentifier); + var manipToDefault = EstFile.GetDefault(manager, manipToIdentifier); + var est = new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, + manipToIdentifier, manipToDefault); if (ownMdl && est.SwapToModdedEntry.Value >= 2) { @@ -215,6 +216,22 @@ public static string ReplaceSlot(string path, EquipSlot from, EquipSlot to, bool ? path.Replace($"_{from.ToSuffix()}_", $"_{to.ToSuffix()}_") : path; + public static string ReplaceType(string path, EquipSlot from, EquipSlot to, PrimaryId idFrom) + { + var isAccessoryFrom = from.IsAccessory(); + if (isAccessoryFrom == to.IsAccessory()) + return path; + + if (isAccessoryFrom) + { + path = path.Replace("accessory/a", "equipment/e"); + return path.Replace($"a{idFrom.Id:D4}", $"e{idFrom.Id:D4}"); + } + + path = path.Replace("equipment/e", "accessory/a"); + return path.Replace($"e{idFrom.Id:D4}", $"a{idFrom.Id:D4}"); + } + public static string ReplaceRace(string path, GenderRace from, GenderRace to, bool condition = true) => ReplaceId(path, 'c', (ushort)from, (ushort)to, condition); diff --git a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs index 8328edea..d2deb9ef 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs @@ -123,8 +123,8 @@ private Func PathResolver(ModCollection? collection) : p => ModRedirections.TryGetValue(p, out var path) ? path : new FullPath(p); private MetaDictionary MetaResolver(ModCollection? collection) - => collection?.MetaCache?.Manipulations is { } cache - ? [] // [.. cache] TODO + => collection?.MetaCache is { } cache + ? new MetaDictionary(cache) : _appliedModData.Manipulations; public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true, diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index e8ca3199..ed4245c4 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -198,8 +198,7 @@ public void IncorporateAllMetaChanges(Mod mod, bool delete) Penumbra.Log.Verbose( $"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}"); deleteList.Add(file.FullName); - // TODO - option.Manipulations.UnionWith([]);//[.. meta.MetaManipulations]); + option.Manipulations.UnionWith(meta.MetaManipulations); } else if (ext1 == ".rgsp" || ext2 == ".rgsp") { @@ -213,8 +212,7 @@ public void IncorporateAllMetaChanges(Mod mod, bool delete) $"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}"); deleteList.Add(file.FullName); - // TODO - option.Manipulations.UnionWith([]);//[.. rgsp.MetaManipulations]); + option.Manipulations.UnionWith(rgsp.MetaManipulations); } } catch (Exception e) diff --git a/Penumbra/Mods/SubMods/DefaultSubMod.cs b/Penumbra/Mods/SubMods/DefaultSubMod.cs index dcd33610..3840468f 100644 --- a/Penumbra/Mods/SubMods/DefaultSubMod.cs +++ b/Penumbra/Mods/SubMods/DefaultSubMod.cs @@ -13,7 +13,7 @@ public class DefaultSubMod(IMod mod) : IModDataContainer public Dictionary Files { get; set; } = []; public Dictionary FileSwaps { get; set; } = []; - public MetaDictionary Manipulations { get; set; } = []; + public MetaDictionary Manipulations { get; set; } = new(); IMod IModDataContainer.Mod => Mod; diff --git a/Penumbra/Mods/SubMods/OptionSubMod.cs b/Penumbra/Mods/SubMods/OptionSubMod.cs index ed7b6ff8..8fac52d8 100644 --- a/Penumbra/Mods/SubMods/OptionSubMod.cs +++ b/Penumbra/Mods/SubMods/OptionSubMod.cs @@ -33,7 +33,7 @@ IModGroup IModOption.Group public Dictionary Files { get; set; } = []; public Dictionary FileSwaps { get; set; } = []; - public MetaDictionary Manipulations { get; set; } = []; + public MetaDictionary Manipulations { get; set; } = new(); public void AddDataTo(Dictionary redirections, MetaDictionary manipulations) => SubMod.AddContainerTo(this, redirections, manipulations); diff --git a/Penumbra/Mods/TemporaryMod.cs b/Penumbra/Mods/TemporaryMod.cs index 91c4c5df..e1cf9f2b 100644 --- a/Penumbra/Mods/TemporaryMod.cs +++ b/Penumbra/Mods/TemporaryMod.cs @@ -93,8 +93,7 @@ public static void SaveTempCollection(Configuration config, SaveService saveServ } } - // TODO - MetaDictionary manips = []; // [.. collection.MetaCache?.Manipulations ?? []]; + var manips = new MetaDictionary(collection.MetaCache); defaultMod.Manipulations.UnionWith(manips); saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport)); diff --git a/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs new file mode 100644 index 00000000..b1ac93f1 --- /dev/null +++ b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs @@ -0,0 +1,159 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility.Raii; +using ImGuiNET; +using OtterGui.Services; +using OtterGui.Text; +using Penumbra.GameData.Enums; +using Penumbra.Interop.Structs; +using Penumbra.Meta; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods.Editor; +using Penumbra.UI.Classes; + +namespace Penumbra.UI.AdvancedWindow.Meta; + +public sealed class EqdpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles) + : MetaDrawer(editor, metaFiles), IService +{ + public override ReadOnlySpan Label + => "Racial Model Edits (EQDP)###EQDP"u8; + + public override int NumColumns + => 7; + + protected override void Initialize() + { + Identifier = new EqdpIdentifier(1, EquipSlot.Head, GenderRace.MidlanderMale); + UpdateEntry(); + } + + private void UpdateEntry() + => Entry = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(MetaFiles, Identifier), Identifier.Slot); + + protected override void DrawNew() + { + ImGui.TableNextColumn(); + CopyToClipboardButton("Copy all current EQDP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Eqdp)); + + ImGui.TableNextColumn(); + var validRaceCode = CharacterUtilityData.EqdpIdx(Identifier.GenderRace, false) >= 0; + var canAdd = validRaceCode && !Editor.Contains(Identifier); + var tt = canAdd ? "Stage this edit."u8 : + validRaceCode ? "This entry is already edited."u8 : "This combination of race and gender can not be used."u8; + if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd)) + Editor.Changes |= Editor.TryAdd(Identifier, Entry); + + if (DrawIdentifierInput(ref Identifier)) + UpdateEntry(); + + DrawEntry(Entry, ref Entry, true); + } + + protected override void DrawEntry(EqdpIdentifier identifier, EqdpEntryInternal entry) + { + DrawMetaButtons(identifier, entry); + DrawIdentifier(identifier); + + var defaultEntry = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(MetaFiles, identifier), identifier.Slot); + if (DrawEntry(defaultEntry, ref entry, false)) + Editor.Changes |= Editor.Update(identifier, entry); + } + + protected override IEnumerable<(EqdpIdentifier, EqdpEntryInternal)> Enumerate() + => Editor.Eqdp.Select(kvp => (kvp.Key, kvp.Value)); + + private static bool DrawIdentifierInput(ref EqdpIdentifier identifier) + { + ImGui.TableNextColumn(); + var changes = DrawPrimaryId(ref identifier); + + ImGui.TableNextColumn(); + changes |= DrawRace(ref identifier); + + ImGui.TableNextColumn(); + changes |= DrawGender(ref identifier); + + ImGui.TableNextColumn(); + changes |= DrawEquipSlot(ref identifier); + return changes; + } + + private static void DrawIdentifier(EqdpIdentifier identifier) + { + ImGui.TableNextColumn(); + ImUtf8.TextFramed($"{identifier.SetId.Id}", FrameColor); + ImUtf8.HoverTooltip("Model Set ID"u8); + + ImGui.TableNextColumn(); + ImUtf8.TextFramed(identifier.Gender.ToName(), FrameColor); + ImUtf8.HoverTooltip("Model Race"u8); + + ImGui.TableNextColumn(); + ImUtf8.TextFramed(identifier.Race.ToName(), FrameColor); + ImUtf8.HoverTooltip("Gender"u8); + + ImGui.TableNextColumn(); + ImUtf8.TextFramed(identifier.Slot.ToName(), FrameColor); + ImUtf8.HoverTooltip("Equip Slot"u8); + } + + private static bool DrawEntry(EqdpEntryInternal defaultEntry, ref EqdpEntryInternal entry, bool disabled) + { + var changes = false; + using var dis = ImRaii.Disabled(disabled); + ImGui.TableNextColumn(); + if (Checkmark("Material##eqdp"u8, "\0"u8, entry.Material, defaultEntry.Material, out var newMaterial)) + { + entry = entry with { Material = newMaterial }; + changes = true; + } + + ImGui.SameLine(); + if (Checkmark("Model##eqdp"u8, "\0"u8, entry.Model, defaultEntry.Model, out var newModel)) + { + entry = entry with { Material = newModel }; + changes = true; + } + + return changes; + } + + public static bool DrawPrimaryId(ref EqdpIdentifier identifier, float unscaledWidth = 100) + { + var ret = IdInput("##eqdpPrimaryId"u8, unscaledWidth, identifier.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1, + identifier.SetId.Id <= 1); + ImUtf8.HoverTooltip( + "Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that."u8); + if (ret) + identifier = identifier with { SetId = setId }; + return ret; + } + + public static bool DrawRace(ref EqdpIdentifier identifier, float unscaledWidth = 100) + { + var ret = Combos.Race("##eqdpRace", identifier.Race, out var race, unscaledWidth); + ImUtf8.HoverTooltip("Model Race"u8); + if (ret) + identifier = identifier with { GenderRace = Names.CombinedRace(identifier.Gender, race) }; + return ret; + } + + public static bool DrawGender(ref EqdpIdentifier identifier, float unscaledWidth = 120) + { + var ret = Combos.Gender("##eqdpGender", identifier.Gender, out var gender, unscaledWidth); + ImUtf8.HoverTooltip("Gender"u8); + if (ret) + identifier = identifier with { GenderRace = Names.CombinedRace(gender, identifier.Race) }; + return ret; + } + + public static bool DrawEquipSlot(ref EqdpIdentifier identifier, float unscaledWidth = 100) + { + var ret = Combos.EqdpEquipSlot("##eqdpSlot", identifier.Slot, out var slot, unscaledWidth); + ImUtf8.HoverTooltip("Equip Slot"u8); + if (ret) + identifier = identifier with { Slot = slot }; + return ret; + } +} diff --git a/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs new file mode 100644 index 00000000..56c06bc9 --- /dev/null +++ b/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs @@ -0,0 +1,134 @@ +using Dalamud.Interface; +using ImGuiNET; +using OtterGui.Raii; +using OtterGui.Services; +using OtterGui.Text; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Penumbra.Meta; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods.Editor; +using Penumbra.UI.Classes; + +namespace Penumbra.UI.AdvancedWindow.Meta; + +public sealed class EqpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles) + : MetaDrawer(editor, metaFiles), IService +{ + public override ReadOnlySpan Label + => "Equipment Parameter Edits (EQP)###EQP"u8; + + public override int NumColumns + => 5; + + protected override void Initialize() + { + Identifier = new EqpIdentifier(1, EquipSlot.Body); + UpdateEntry(); + } + + private void UpdateEntry() + => Entry = new EqpEntryInternal(ExpandedEqpFile.GetDefault(MetaFiles, Identifier.SetId), Identifier.Slot); + + protected override void DrawNew() + { + ImGui.TableNextColumn(); + CopyToClipboardButton("Copy all current EQP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Eqp)); + + ImGui.TableNextColumn(); + var canAdd = !Editor.Contains(Identifier); + var tt = canAdd ? "Stage this edit."u8 : "This entry is already edited."u8; + if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd)) + Editor.Changes |= Editor.TryAdd(Identifier, Entry); + + if (DrawIdentifierInput(ref Identifier)) + UpdateEntry(); + + DrawEntry(Identifier.Slot, Entry, ref Entry, true); + } + + protected override void DrawEntry(EqpIdentifier identifier, EqpEntryInternal entry) + { + DrawMetaButtons(identifier, entry); + DrawIdentifier(identifier); + + var defaultEntry = new EqpEntryInternal(ExpandedEqpFile.GetDefault(MetaFiles, identifier.SetId), identifier.Slot); + if (DrawEntry(identifier.Slot, defaultEntry, ref entry, false)) + Editor.Changes |= Editor.Update(identifier, entry); + } + + protected override IEnumerable<(EqpIdentifier, EqpEntryInternal)> Enumerate() + => Editor.Eqp.Select(kvp => (kvp.Key, kvp.Value)); + + private static bool DrawIdentifierInput(ref EqpIdentifier identifier) + { + ImGui.TableNextColumn(); + var changes = DrawPrimaryId(ref identifier); + + ImGui.TableNextColumn(); + changes |= DrawEquipSlot(ref identifier); + return changes; + } + + private static void DrawIdentifier(EqpIdentifier identifier) + { + ImGui.TableNextColumn(); + ImUtf8.TextFramed($"{identifier.SetId.Id}", FrameColor); + ImUtf8.HoverTooltip("Model Set ID"u8); + + ImGui.TableNextColumn(); + ImUtf8.TextFramed(identifier.Slot.ToName(), FrameColor); + ImUtf8.HoverTooltip("Equip Slot"u8); + } + + private static bool DrawEntry(EquipSlot slot, EqpEntryInternal defaultEntry, ref EqpEntryInternal entry, bool disabled) + { + var changes = false; + using var dis = ImRaii.Disabled(disabled); + ImGui.TableNextColumn(); + var offset = Eqp.OffsetAndMask(slot).Item1; + DrawBox(ref entry, 0); + for (var i = 1; i < Eqp.EqpAttributes[slot].Count; ++i) + { + ImUtf8.SameLineInner(); + DrawBox(ref entry, i); + } + + return changes; + + void DrawBox(ref EqpEntryInternal entry, int i) + { + using var id = ImUtf8.PushId(i); + var flag = 1u << i; + var eqpFlag = (EqpEntry)((ulong)flag << offset); + var defaultValue = (flag & defaultEntry.Value) != 0; + var value = (flag & entry.Value) != 0; + if (Checkmark("##eqp"u8, eqpFlag.ToLocalName(), value, defaultValue, out var newValue)) + { + entry = new EqpEntryInternal(newValue ? entry.Value | flag : entry.Value & ~flag); + changes = true; + } + } + } + + public static bool DrawPrimaryId(ref EqpIdentifier identifier, float unscaledWidth = 100) + { + var ret = IdInput("##eqpPrimaryId"u8, unscaledWidth, identifier.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1, + identifier.SetId.Id <= 1); + ImUtf8.HoverTooltip( + "Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that."u8); + if (ret) + identifier = identifier with { SetId = setId }; + return ret; + } + + public static bool DrawEquipSlot(ref EqpIdentifier identifier, float unscaledWidth = 100) + { + var ret = Combos.EqpEquipSlot("##eqpSlot", identifier.Slot, out var slot, unscaledWidth); + ImUtf8.HoverTooltip("Equip Slot"u8); + if (ret) + identifier = identifier with { Slot = slot }; + return ret; + } +} diff --git a/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs new file mode 100644 index 00000000..5c3c5df5 --- /dev/null +++ b/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs @@ -0,0 +1,147 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility.Raii; +using ImGuiNET; +using OtterGui.Services; +using OtterGui.Text; +using Penumbra.GameData.Enums; +using Penumbra.Meta; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods.Editor; +using Penumbra.UI.Classes; + +namespace Penumbra.UI.AdvancedWindow.Meta; + +public sealed class EstMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles) + : MetaDrawer(editor, metaFiles), IService +{ + public override ReadOnlySpan Label + => "Extra Skeleton Parameters (EST)###EST"u8; + + public override int NumColumns + => 7; + + protected override void Initialize() + { + Identifier = new EstIdentifier(1, EstType.Hair, GenderRace.MidlanderMale); + UpdateEntry(); + } + + private void UpdateEntry() + => Entry = EstFile.GetDefault(MetaFiles, Identifier.Slot, Identifier.GenderRace, Identifier.SetId); + + protected override void DrawNew() + { + ImGui.TableNextColumn(); + CopyToClipboardButton("Copy all current EST manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Est)); + + ImGui.TableNextColumn(); + var canAdd = !Editor.Contains(Identifier); + var tt = canAdd ? "Stage this edit."u8 : "This entry is already edited."u8; + if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd)) + Editor.Changes |= Editor.TryAdd(Identifier, Entry); + + if (DrawIdentifierInput(ref Identifier)) + UpdateEntry(); + + DrawEntry(Entry, ref Entry, true); + } + + protected override void DrawEntry(EstIdentifier identifier, EstEntry entry) + { + DrawMetaButtons(identifier, entry); + DrawIdentifier(identifier); + + var defaultEntry = EstFile.GetDefault(MetaFiles, identifier.Slot, identifier.GenderRace, identifier.SetId); + if (DrawEntry(defaultEntry, ref entry, false)) + Editor.Changes |= Editor.Update(identifier, entry); + } + + protected override IEnumerable<(EstIdentifier, EstEntry)> Enumerate() + => Editor.Est.Select(kvp => (kvp.Key, kvp.Value)); + + private static bool DrawIdentifierInput(ref EstIdentifier identifier) + { + ImGui.TableNextColumn(); + var changes = DrawPrimaryId(ref identifier); + + ImGui.TableNextColumn(); + changes |= DrawRace(ref identifier); + + ImGui.TableNextColumn(); + changes |= DrawGender(ref identifier); + + ImGui.TableNextColumn(); + changes |= DrawSlot(ref identifier); + + return changes; + } + + private static void DrawIdentifier(EstIdentifier identifier) + { + ImGui.TableNextColumn(); + ImUtf8.TextFramed($"{identifier.SetId.Id}", FrameColor); + ImUtf8.HoverTooltip("Model Set ID"u8); + + ImGui.TableNextColumn(); + ImUtf8.TextFramed(identifier.Race.ToName(), FrameColor); + ImUtf8.HoverTooltip("Model Race"u8); + + ImGui.TableNextColumn(); + ImUtf8.TextFramed(identifier.Gender.ToName(), FrameColor); + ImUtf8.HoverTooltip("Gender"u8); + + ImGui.TableNextColumn(); + ImUtf8.TextFramed(identifier.Slot.ToString(), FrameColor); + ImUtf8.HoverTooltip("Extra Skeleton Type"u8); + } + + private static bool DrawEntry(EstEntry defaultEntry, ref EstEntry entry, bool disabled) + { + using var dis = ImRaii.Disabled(disabled); + ImGui.TableNextColumn(); + var ret = DragInput("##estValue"u8, [], 100f * ImUtf8.GlobalScale, entry.Value, defaultEntry.Value, out var newValue, (ushort)0, + ushort.MaxValue, 0.05f, !disabled); + if (ret) + entry = new EstEntry(newValue); + return ret; + } + + public static bool DrawPrimaryId(ref EstIdentifier identifier, float unscaledWidth = 100) + { + var ret = IdInput("##estPrimaryId"u8, unscaledWidth, identifier.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1, + identifier.SetId.Id <= 1); + ImUtf8.HoverTooltip( + "Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that."u8); + if (ret) + identifier = identifier with { SetId = setId }; + return ret; + } + + public static bool DrawRace(ref EstIdentifier identifier, float unscaledWidth = 100) + { + var ret = Combos.Race("##estRace", identifier.Race, out var race, unscaledWidth); + ImUtf8.HoverTooltip("Model Race"u8); + if (ret) + identifier = identifier with { GenderRace = Names.CombinedRace(identifier.Gender, race) }; + return ret; + } + + public static bool DrawGender(ref EstIdentifier identifier, float unscaledWidth = 120) + { + var ret = Combos.Gender("##estGender", identifier.Gender, out var gender, unscaledWidth); + ImUtf8.HoverTooltip("Gender"u8); + if (ret) + identifier = identifier with { GenderRace = Names.CombinedRace(gender, identifier.Race) }; + return ret; + } + + public static bool DrawSlot(ref EstIdentifier identifier, float unscaledWidth = 200) + { + var ret = Combos.EstSlot("##estSlot", identifier.Slot, out var slot, unscaledWidth); + ImUtf8.HoverTooltip("Extra Skeleton Type"u8); + if (ret) + identifier = identifier with { Slot = slot }; + return ret; + } +} diff --git a/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs new file mode 100644 index 00000000..130831a0 --- /dev/null +++ b/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs @@ -0,0 +1,111 @@ +using Dalamud.Interface; +using ImGuiNET; +using OtterGui.Services; +using OtterGui.Text; +using Penumbra.Meta; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods.Editor; + +namespace Penumbra.UI.AdvancedWindow.Meta; + +public sealed class GlobalEqpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles) + : MetaDrawer(editor, metaFiles), IService +{ + public override ReadOnlySpan Label + => "Global Equipment Parameter Edits (Global EQP)###GEQP"u8; + + public override int NumColumns + => 4; + + protected override void Initialize() + { + Identifier = new GlobalEqpManipulation() + { + Condition = 1, + Type = GlobalEqpType.DoNotHideEarrings, + }; + } + + protected override void DrawNew() + { + ImGui.TableNextColumn(); + CopyToClipboardButton("Copy all current global EQP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.GlobalEqp)); + + ImGui.TableNextColumn(); + var canAdd = !Editor.Contains(Identifier); + var tt = canAdd ? "Stage this edit."u8 : "This entry is already edited."u8; + if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd)) + Editor.Changes |= Editor.TryAdd(Identifier); + + DrawIdentifierInput(ref Identifier); + } + + protected override void DrawEntry(GlobalEqpManipulation identifier, byte _) + { + DrawMetaButtons(identifier, 0); + DrawIdentifier(identifier); + } + + protected override IEnumerable<(GlobalEqpManipulation, byte)> Enumerate() + => Editor.GlobalEqp.Select(identifier => (identifier, (byte)0)); + + private static void DrawIdentifierInput(ref GlobalEqpManipulation identifier) + { + ImGui.TableNextColumn(); + DrawType(ref identifier); + + ImGui.TableNextColumn(); + if (identifier.Type.HasCondition()) + DrawCondition(ref identifier); + else + ImUtf8.ScaledDummy(100); + } + + private static void DrawIdentifier(GlobalEqpManipulation identifier) + { + ImGui.TableNextColumn(); + ImUtf8.TextFramed(identifier.Type.ToName(), FrameColor); + ImUtf8.HoverTooltip("Global EQP Type"u8); + + ImGui.TableNextColumn(); + if (identifier.Type.HasCondition()) + { + ImUtf8.TextFramed($"{identifier.Condition.Id}", FrameColor); + ImUtf8.HoverTooltip("Conditional Model ID"u8); + } + } + + public static bool DrawType(ref GlobalEqpManipulation identifier, float unscaledWidth = 250) + { + ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); + using var combo = ImUtf8.Combo("##geqpType"u8, identifier.Type.ToName()); + if (!combo) + return false; + + var ret = false; + foreach (var type in Enum.GetValues()) + { + if (ImUtf8.Selectable(type.ToName(), type == identifier.Type)) + { + identifier = new GlobalEqpManipulation + { + Type = type, + Condition = type.HasCondition() ? identifier.Type.HasCondition() ? identifier.Condition : 1 : 0, + }; + ret = true; + } + + ImUtf8.HoverTooltip(type.ToDescription()); + } + + return ret; + } + + public static void DrawCondition(ref GlobalEqpManipulation identifier, float unscaledWidth = 100) + { + if (IdInput("##geqpCond"u8, unscaledWidth, identifier.Condition.Id, out var newId, 1, ushort.MaxValue, + identifier.Condition.Id <= 1)) + identifier = identifier with { Condition = newId }; + ImUtf8.HoverTooltip("The Model ID for the item that should not be hidden."u8); + } +} diff --git a/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs new file mode 100644 index 00000000..87ed21dc --- /dev/null +++ b/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs @@ -0,0 +1,148 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility.Raii; +using ImGuiNET; +using OtterGui.Services; +using OtterGui.Text; +using Penumbra.GameData.Structs; +using Penumbra.Meta.Files; +using Penumbra.Meta; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods.Editor; + +namespace Penumbra.UI.AdvancedWindow.Meta; + +public sealed class GmpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles) + : MetaDrawer(editor, metaFiles), IService +{ + public override ReadOnlySpan Label + => "Visor/Gimmick Edits (GMP)###GMP"u8; + + public override int NumColumns + => 7; + + protected override void Initialize() + { + Identifier = new GmpIdentifier(1); + UpdateEntry(); + } + + private void UpdateEntry() + => Entry = ExpandedGmpFile.GetDefault(MetaFiles, Identifier.SetId); + + protected override void DrawNew() + { + ImGui.TableNextColumn(); + CopyToClipboardButton("Copy all current Gmp manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Gmp)); + + ImGui.TableNextColumn(); + var canAdd = !Editor.Contains(Identifier); + var tt = canAdd ? "Stage this edit."u8 : "This entry is already edited."u8; + if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd)) + Editor.Changes |= Editor.TryAdd(Identifier, Entry); + + if (DrawIdentifierInput(ref Identifier)) + UpdateEntry(); + + DrawEntry(Entry, ref Entry, true); + } + + protected override void DrawEntry(GmpIdentifier identifier, GmpEntry entry) + { + DrawMetaButtons(identifier, entry); + DrawIdentifier(identifier); + + var defaultEntry = ExpandedGmpFile.GetDefault(MetaFiles, identifier.SetId); + if (DrawEntry(defaultEntry, ref entry, false)) + Editor.Changes |= Editor.Update(identifier, entry); + } + + protected override IEnumerable<(GmpIdentifier, GmpEntry)> Enumerate() + => Editor.Gmp.Select(kvp => (kvp.Key, kvp.Value)); + + private static bool DrawIdentifierInput(ref GmpIdentifier identifier) + { + ImGui.TableNextColumn(); + return DrawPrimaryId(ref identifier); + } + + private static void DrawIdentifier(GmpIdentifier identifier) + { + ImGui.TableNextColumn(); + ImUtf8.TextFramed($"{identifier.SetId.Id}", FrameColor); + ImUtf8.HoverTooltip("Model Set ID"u8); + } + + private static bool DrawEntry(GmpEntry defaultEntry, ref GmpEntry entry, bool disabled) + { + using var dis = ImRaii.Disabled(disabled); + ImGui.TableNextColumn(); + var changes = false; + if (Checkmark("##gmpEnabled"u8, "Gimmick Enabled", entry.Enabled, defaultEntry.Enabled, out var enabled)) + { + entry = entry with { Enabled = enabled }; + changes = true; + } + + ImGui.TableNextColumn(); + if (Checkmark("##gmpAnimated"u8, "Gimmick Animated", entry.Animated, defaultEntry.Animated, out var animated)) + { + entry = entry with { Animated = animated }; + changes = true; + } + + var rotationWidth = 75 * ImUtf8.GlobalScale; + ImGui.TableNextColumn(); + if (DragInput("##gmpRotationA"u8, "Rotation A in Degrees"u8, rotationWidth, entry.RotationA, defaultEntry.RotationA, out var rotationA, + (ushort)0, (ushort)360, 0.05f, !disabled)) + { + entry = entry with { RotationA = rotationA }; + changes = true; + } + + ImUtf8.SameLineInner(); + if (DragInput("##gmpRotationB"u8, "Rotation B in Degrees"u8, rotationWidth, entry.RotationB, defaultEntry.RotationB, out var rotationB, + (ushort)0, (ushort)360, 0.05f, !disabled)) + { + entry = entry with { RotationB = rotationB }; + changes = true; + } + + ImUtf8.SameLineInner(); + if (DragInput("##gmpRotationC"u8, "Rotation C in Degrees"u8, rotationWidth, entry.RotationC, defaultEntry.RotationC, out var rotationC, + (ushort)0, (ushort)360, 0.05f, !disabled)) + { + entry = entry with { RotationC = rotationC }; + changes = true; + } + + var unkWidth = 50 * ImUtf8.GlobalScale; + ImGui.TableNextColumn(); + if (DragInput("##gmpUnkA"u8, "Animation Type A?"u8, unkWidth, entry.UnknownA, defaultEntry.UnknownA, out var unknownA, + (byte)0, (byte)15, 0.01f, !disabled)) + { + entry = entry with { UnknownA = unknownA }; + changes = true; + } + + ImUtf8.SameLineInner(); + if (DragInput("##gmpUnkB"u8, "Animation Type B?"u8, unkWidth, entry.UnknownB, defaultEntry.UnknownB, out var unknownB, + (byte)0, (byte)15, 0.01f, !disabled)) + { + entry = entry with { UnknownB = unknownB }; + changes = true; + } + + return changes; + } + + public static bool DrawPrimaryId(ref GmpIdentifier identifier, float unscaledWidth = 100) + { + var ret = IdInput("##gmpPrimaryId"u8, unscaledWidth, identifier.SetId.Id, out var setId, 1, ExpandedEqpGmpBase.Count - 1, + identifier.SetId.Id <= 1); + ImUtf8.HoverTooltip( + "Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that."u8); + if (ret) + identifier = new GmpIdentifier(setId); + return ret; + } +} diff --git a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs index d9a8c27c..58f626fc 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs @@ -12,22 +12,16 @@ namespace Penumbra.UI.AdvancedWindow.Meta; -public sealed class ImcMetaDrawer(ModEditor editor, MetaFileManager metaFiles) +public sealed class ImcMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles) : MetaDrawer(editor, metaFiles), IService { - private bool _fileExists; + public override ReadOnlySpan Label + => "Variant Edits (IMC)###IMC"u8; + + public override int NumColumns + => 10; - private const string ModelSetIdTooltipShort = "Model Set ID"; - private const string EquipSlotTooltip = "Equip Slot"; - private const string ModelRaceTooltip = "Model Race"; - private const string GenderTooltip = "Gender"; - private const string ObjectTypeTooltip = "Object Type"; - private const string SecondaryIdTooltip = "Secondary ID"; - private const string PrimaryIdTooltipShort = "Primary ID"; - private const string VariantIdTooltip = "Variant ID"; - private const string EstTypeTooltip = "EST Type"; - private const string RacialTribeTooltip = "Racial Tribe"; - private const string ScalingTypeTooltip = "Scaling Type"; + private bool _fileExists; protected override void Initialize() { @@ -41,14 +35,14 @@ private void UpdateEntry() protected override void DrawNew() { ImGui.TableNextColumn(); - // Copy To Clipboard + CopyToClipboardButton("Copy all current IMC manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Imc)); ImGui.TableNextColumn(); - var canAdd = _fileExists && Editor.MetaEditor.CanAdd(Identifier); + var canAdd = _fileExists && !Editor.Contains(Identifier); var tt = canAdd ? "Stage this edit."u8 : !_fileExists ? "This IMC file does not exist."u8 : "This entry is already edited."u8; if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd)) - Editor.MetaEditor.TryAdd(Identifier, Entry); + Editor.Changes |= Editor.TryAdd(Identifier, Entry); - if (DrawIdentifier(ref Identifier)) + if (DrawIdentifierInput(ref Identifier)) UpdateEntry(); using var disabled = ImRaii.Disabled(); @@ -57,68 +51,72 @@ protected override void DrawNew() protected override void DrawEntry(ImcIdentifier identifier, ImcEntry entry) { - const uint frameColor = 0; - // Meta Buttons + DrawMetaButtons(identifier, entry); + DrawIdentifier(identifier); + var defaultEntry = MetaFiles.ImcChecker.GetDefaultEntry(identifier, true).Entry; + if (DrawEntry(defaultEntry, ref entry, true)) + Editor.Changes |= Editor.Update(identifier, entry); + } + + private static bool DrawIdentifierInput(ref ImcIdentifier identifier) + { ImGui.TableNextColumn(); - ImUtf8.TextFramed(identifier.ObjectType.ToName(), frameColor); - ImUtf8.HoverTooltip("Object Type"u8); + var change = DrawObjectType(ref identifier); ImGui.TableNextColumn(); - ImUtf8.TextFramed($"{identifier.PrimaryId.Id}", frameColor); - ImUtf8.HoverTooltip("Primary ID"); + change |= DrawPrimaryId(ref identifier); ImGui.TableNextColumn(); if (identifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory) - { - ImUtf8.TextFramed(identifier.EquipSlot.ToName(), frameColor); - ImUtf8.HoverTooltip("Equip Slot"u8); - } + change |= DrawSlot(ref identifier); else - { - ImUtf8.TextFramed($"{identifier.SecondaryId.Id}", frameColor); - ImUtf8.HoverTooltip("Secondary ID"u8); - } + change |= DrawSecondaryId(ref identifier); ImGui.TableNextColumn(); - ImUtf8.TextFramed($"{identifier.Variant.Id}", frameColor); - ImUtf8.HoverTooltip("Variant"u8); + change |= DrawVariant(ref identifier); ImGui.TableNextColumn(); if (identifier.ObjectType is ObjectType.DemiHuman) - { - ImUtf8.TextFramed(identifier.EquipSlot.ToName(), frameColor); - ImUtf8.HoverTooltip("Equip Slot"u8); - } - - var defaultEntry = MetaFiles.ImcChecker.GetDefaultEntry(identifier, true).Entry; - if (DrawEntry(defaultEntry, ref entry, true)) - Editor.MetaEditor.Update(identifier, entry); + change |= DrawSlot(ref identifier, 70f); + else + ImUtf8.ScaledDummy(70f); + return change; } - private static bool DrawIdentifier(ref ImcIdentifier identifier) + private static void DrawIdentifier(ImcIdentifier identifier) { ImGui.TableNextColumn(); - var change = DrawObjectType(ref identifier); + ImUtf8.TextFramed(identifier.ObjectType.ToName(), FrameColor); + ImUtf8.HoverTooltip("Object Type"u8); ImGui.TableNextColumn(); - change |= DrawPrimaryId(ref identifier); + ImUtf8.TextFramed($"{identifier.PrimaryId.Id}", FrameColor); + ImUtf8.HoverTooltip("Primary ID"); ImGui.TableNextColumn(); if (identifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory) - change |= DrawSlot(ref identifier); + { + ImUtf8.TextFramed(identifier.EquipSlot.ToName(), FrameColor); + ImUtf8.HoverTooltip("Equip Slot"u8); + } else - change |= DrawSecondaryId(ref identifier); + { + ImUtf8.TextFramed($"{identifier.SecondaryId.Id}", FrameColor); + ImUtf8.HoverTooltip("Secondary ID"u8); + } ImGui.TableNextColumn(); - change |= DrawVariant(ref identifier); + ImUtf8.TextFramed($"{identifier.Variant.Id}", FrameColor); + ImUtf8.HoverTooltip("Variant"u8); ImGui.TableNextColumn(); if (identifier.ObjectType is ObjectType.DemiHuman) - change |= DrawSlot(ref identifier, 70f); - else - ImUtf8.ScaledDummy(70f); - return change; + { + ImUtf8.TextFramed(identifier.EquipSlot.ToName(), FrameColor); + ImUtf8.HoverTooltip("Equip Slot"u8); + } + } private static bool DrawEntry(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault) @@ -142,7 +140,7 @@ private static bool DrawEntry(ImcEntry defaultEntry, ref ImcEntry entry, bool ad protected override IEnumerable<(ImcIdentifier, ImcEntry)> Enumerate() - => Editor.MetaEditor.Imc.Select(kvp => (kvp.Key, kvp.Value)); + => Editor.Imc.Select(kvp => (kvp.Key, kvp.Value)); public static bool DrawObjectType(ref ImcIdentifier identifier, float width = 110) { @@ -270,7 +268,7 @@ public static bool DrawSoundId(ImcEntry defaultEntry, ref ImcEntry entry, bool a return true; } - public static bool DrawAttributes(ImcEntry defaultEntry, ref ImcEntry entry) + private static bool DrawAttributes(ImcEntry defaultEntry, ref ImcEntry entry) { var changes = false; for (var i = 0; i < ImcEntry.NumAttributes; ++i) @@ -292,62 +290,4 @@ public static bool DrawAttributes(ImcEntry defaultEntry, ref ImcEntry entry) return changes; } - - - /// - /// A number input for ids with an 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; - } - - /// - /// A dragging int input of given width that compares against a default value, shows a tooltip and clamps against min and max. - /// Returns true if newValue changed against currentValue. - /// - private static bool DragInput(ReadOnlySpan label, ReadOnlySpan tooltip, float width, T currentValue, T defaultValue, - out T newValue, T minValue, T maxValue, float speed, bool addDefault) where T : unmanaged, INumber - { - newValue = currentValue; - using var color = ImRaii.PushColor(ImGuiCol.FrameBg, - defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), - defaultValue != currentValue); - ImGui.SetNextItemWidth(width); - if (ImUtf8.DragScalar(label, ref newValue, minValue, maxValue, speed)) - newValue = newValue <= minValue ? minValue : newValue >= maxValue ? maxValue : newValue; - - if (addDefault) - ImUtf8.HoverTooltip($"{tooltip}\nDefault Value: {defaultValue}"); - else - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip); - - return newValue != currentValue; - } - - /// - /// A checkmark that compares against a default value and shows a tooltip. - /// Returns true if newValue is changed against currentValue. - /// - private static bool Checkmark(ReadOnlySpan label, ReadOnlySpan tooltip, bool currentValue, bool defaultValue, - out bool newValue) - { - using var color = ImRaii.PushColor(ImGuiCol.FrameBg, - defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), - defaultValue != currentValue); - newValue = currentValue; - ImUtf8.Checkbox(label, ref newValue); - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip); - return newValue != currentValue; - } } diff --git a/Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs new file mode 100644 index 00000000..229526c4 --- /dev/null +++ b/Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs @@ -0,0 +1,154 @@ +using Dalamud.Interface; +using ImGuiNET; +using Newtonsoft.Json.Linq; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Text; +using Penumbra.Api.Api; +using Penumbra.Meta; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods.Editor; +using Penumbra.UI.Classes; + +namespace Penumbra.UI.AdvancedWindow.Meta; + +public interface IMetaDrawer +{ + public ReadOnlySpan Label { get; } + public int NumColumns { get; } + public void Draw(); +} + +public abstract class MetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles) : IMetaDrawer + where TIdentifier : unmanaged, IMetaIdentifier + where TEntry : unmanaged +{ + protected const uint FrameColor = 0; + + protected readonly ModMetaEditor Editor = editor; + protected readonly MetaFileManager MetaFiles = metaFiles; + protected TIdentifier Identifier; + protected TEntry Entry; + private bool _initialized; + + public void Draw() + { + if (!_initialized) + { + Initialize(); + _initialized = true; + } + + using var id = ImUtf8.PushId((int)Identifier.Type); + DrawNew(); + foreach (var ((identifier, entry), idx) in Enumerate().WithIndex()) + { + id.Push(idx); + DrawEntry(identifier, entry); + id.Pop(); + } + } + + public abstract ReadOnlySpan Label { get; } + public abstract int NumColumns { get; } + + protected abstract void DrawNew(); + protected abstract void Initialize(); + protected abstract void DrawEntry(TIdentifier identifier, TEntry entry); + + protected abstract IEnumerable<(TIdentifier, TEntry)> Enumerate(); + + + /// + /// A number input for ids with an optional max id of given width. + /// Returns true if newId changed against currentId. + /// + protected 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; + } + + /// + /// A dragging int input of given width that compares against a default value, shows a tooltip and clamps against min and max. + /// Returns true if newValue changed against currentValue. + /// + protected static bool DragInput(ReadOnlySpan label, ReadOnlySpan tooltip, float width, T currentValue, T defaultValue, + out T newValue, T minValue, T maxValue, float speed, bool addDefault) where T : unmanaged, INumber + { + newValue = currentValue; + using var color = ImRaii.PushColor(ImGuiCol.FrameBg, + defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), + defaultValue != currentValue); + ImGui.SetNextItemWidth(width); + if (ImUtf8.DragScalar(label, ref newValue, minValue, maxValue, speed)) + newValue = newValue <= minValue ? minValue : newValue >= maxValue ? maxValue : newValue; + + if (addDefault) + ImUtf8.HoverTooltip($"{tooltip}\nDefault Value: {defaultValue}"); + else + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip); + + return newValue != currentValue; + } + + /// + /// A checkmark that compares against a default value and shows a tooltip. + /// Returns true if newValue is changed against currentValue. + /// + protected static bool Checkmark(ReadOnlySpan label, ReadOnlySpan tooltip, bool currentValue, bool defaultValue, + out bool newValue) + { + using var color = ImRaii.PushColor(ImGuiCol.FrameBg, + defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), + defaultValue != currentValue); + newValue = currentValue; + ImUtf8.Checkbox(label, ref newValue); + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip); + return newValue != currentValue; + } + + /// + /// A checkmark that compares against a default value and shows a tooltip. + /// Returns true if newValue is changed against currentValue. + /// + protected static bool Checkmark(ReadOnlySpan label, ReadOnlySpan tooltip, bool currentValue, bool defaultValue, + out bool newValue) + { + using var color = ImRaii.PushColor(ImGuiCol.FrameBg, + defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), + defaultValue != currentValue); + newValue = currentValue; + ImUtf8.Checkbox(label, ref newValue); + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip); + return newValue != currentValue; + } + + protected void DrawMetaButtons(TIdentifier identifier, TEntry entry) + { + ImGui.TableNextColumn(); + CopyToClipboardButton("Copy this manipulation to clipboard."u8, new JArray { MetaDictionary.Serialize(identifier, entry)! }); + + ImGui.TableNextColumn(); + if (ImUtf8.IconButton(FontAwesomeIcon.Trash, "Delete this meta manipulation."u8)) + Editor.Changes |= Editor.Remove(identifier); + } + + protected void CopyToClipboardButton(ReadOnlySpan tooltip, JToken? manipulations) + { + if (!ImUtf8.IconButton(FontAwesomeIcon.Clipboard, tooltip)) + return; + + var text = Functions.ToCompressedBase64(manipulations, MetaApi.CurrentVersion); + if (text.Length > 0) + ImGui.SetClipboardText(text); + } +} diff --git a/Penumbra/UI/AdvancedWindow/Meta/MetaDrawers.cs b/Penumbra/UI/AdvancedWindow/Meta/MetaDrawers.cs new file mode 100644 index 00000000..b3dd9299 --- /dev/null +++ b/Penumbra/UI/AdvancedWindow/Meta/MetaDrawers.cs @@ -0,0 +1,35 @@ +using OtterGui.Services; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.UI.AdvancedWindow.Meta; + +public class MetaDrawers( + EqdpMetaDrawer eqdp, + EqpMetaDrawer eqp, + EstMetaDrawer est, + GlobalEqpMetaDrawer globalEqp, + GmpMetaDrawer gmp, + ImcMetaDrawer imc, + RspMetaDrawer rsp) : IService +{ + public readonly EqdpMetaDrawer Eqdp = eqdp; + public readonly EqpMetaDrawer Eqp = eqp; + public readonly EstMetaDrawer Est = est; + public readonly GmpMetaDrawer Gmp = gmp; + public readonly RspMetaDrawer Rsp = rsp; + public readonly ImcMetaDrawer Imc = imc; + public readonly GlobalEqpMetaDrawer GlobalEqp = globalEqp; + + public IMetaDrawer? Get(MetaManipulationType type) + => type switch + { + MetaManipulationType.Imc => Imc, + MetaManipulationType.Eqdp => Eqdp, + MetaManipulationType.Eqp => Eqp, + MetaManipulationType.Est => Est, + MetaManipulationType.Gmp => Gmp, + MetaManipulationType.Rsp => Rsp, + MetaManipulationType.GlobalEqp => GlobalEqp, + _ => null, + }; +} diff --git a/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs new file mode 100644 index 00000000..2b7904ce --- /dev/null +++ b/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs @@ -0,0 +1,112 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility.Raii; +using ImGuiNET; +using OtterGui.Services; +using OtterGui.Text; +using Penumbra.GameData.Enums; +using Penumbra.Meta; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods.Editor; +using Penumbra.UI.Classes; + +namespace Penumbra.UI.AdvancedWindow.Meta; + +public sealed class RspMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles) + : MetaDrawer(editor, metaFiles), IService +{ + public override ReadOnlySpan Label + => "Racial Scaling Edits (RSP)###RSP"u8; + + public override int NumColumns + => 5; + + protected override void Initialize() + { + Identifier = new RspIdentifier(SubRace.Midlander, RspAttribute.MaleMinSize); + UpdateEntry(); + } + + private void UpdateEntry() + => Entry = CmpFile.GetDefault(MetaFiles, Identifier.SubRace, Identifier.Attribute); + + protected override void DrawNew() + { + ImGui.TableNextColumn(); + CopyToClipboardButton("Copy all current RSP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Rsp)); + + ImGui.TableNextColumn(); + var canAdd = !Editor.Contains(Identifier); + var tt = canAdd ? "Stage this edit."u8 : "This entry is already edited."u8; + if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd)) + Editor.Changes |= Editor.TryAdd(Identifier, Entry); + + if (DrawIdentifierInput(ref Identifier)) + UpdateEntry(); + + DrawEntry(Entry, ref Entry, true); + } + + protected override void DrawEntry(RspIdentifier identifier, RspEntry entry) + { + DrawMetaButtons(identifier, entry); + DrawIdentifier(identifier); + + var defaultEntry = CmpFile.GetDefault(MetaFiles, identifier.SubRace, identifier.Attribute); + if (DrawEntry(defaultEntry, ref entry, false)) + Editor.Changes |= Editor.Update(identifier, entry); + } + + protected override IEnumerable<(RspIdentifier, RspEntry)> Enumerate() + => Editor.Rsp.Select(kvp => (kvp.Key, kvp.Value)); + + private static bool DrawIdentifierInput(ref RspIdentifier identifier) + { + ImGui.TableNextColumn(); + var changes = DrawSubRace(ref identifier); + + ImGui.TableNextColumn(); + changes |= DrawAttribute(ref identifier); + return changes; + } + + private static void DrawIdentifier(RspIdentifier identifier) + { + ImGui.TableNextColumn(); + ImUtf8.TextFramed(identifier.SubRace.ToName(), FrameColor); + ImUtf8.HoverTooltip("Model Set ID"u8); + + ImGui.TableNextColumn(); + ImUtf8.TextFramed(identifier.Attribute.ToFullString(), FrameColor); + ImUtf8.HoverTooltip("Equip Slot"u8); + } + + private static bool DrawEntry(RspEntry defaultEntry, ref RspEntry entry, bool disabled) + { + using var dis = ImRaii.Disabled(disabled); + ImGui.TableNextColumn(); + var ret = DragInput("##rspValue"u8, [], ImUtf8.GlobalScale * 150, defaultEntry.Value, entry.Value, out var newValue, + RspEntry.MinValue, RspEntry.MaxValue, 0.001f, !disabled); + if (ret) + entry = new RspEntry(newValue); + return ret; + } + + public static bool DrawSubRace(ref RspIdentifier identifier, float unscaledWidth = 150) + { + var ret = Combos.SubRace("##rspSubRace", identifier.SubRace, out var subRace, unscaledWidth); + ImUtf8.HoverTooltip("Racial Clan"u8); + if (ret) + identifier = identifier with { SubRace = subRace }; + return ret; + } + + public static bool DrawAttribute(ref RspIdentifier identifier, float unscaledWidth = 200) + { + var ret = Combos.RspAttribute("##rspAttribute", identifier.Attribute, out var attribute, unscaledWidth); + ImUtf8.HoverTooltip("Scaling Attribute"u8); + if (ret) + identifier = identifier with { Attribute = attribute }; + return ret; + } +} diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs index 50862eec..3ec6a4d5 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs @@ -1,27 +1,18 @@ -using System.Reflection.Emit; using Dalamud.Interface; using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Text; -using OtterGui.Text.EndObjects; -using Penumbra.GameData.Enums; -using Penumbra.Interop.Structs; -using Penumbra.Meta; -using Penumbra.Meta.Files; +using Penumbra.Api.Api; using Penumbra.Meta.Manipulations; -using Penumbra.Mods.Editor; +using Penumbra.UI.AdvancedWindow.Meta; using Penumbra.UI.Classes; -using Penumbra.UI.ModsTab; namespace Penumbra.UI.AdvancedWindow; public partial class ModEditWindow { - private const string ModelSetIdTooltip = - "Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that."; - - + private readonly MetaDrawers _metaDrawers; private void DrawMetaTab() { @@ -56,80 +47,42 @@ private void DrawMetaTab() if (!child) return; - DrawEditHeader(MetaManipulation.Type.Eqp); - DrawEditHeader(MetaManipulation.Type.Eqdp); - DrawEditHeader(MetaManipulation.Type.Imc); - DrawEditHeader(MetaManipulation.Type.Est); - DrawEditHeader(MetaManipulation.Type.Gmp); - DrawEditHeader(MetaManipulation.Type.Rsp); - DrawEditHeader(MetaManipulation.Type.GlobalEqp); + DrawEditHeader(MetaManipulationType.Eqp); + DrawEditHeader(MetaManipulationType.Eqdp); + DrawEditHeader(MetaManipulationType.Imc); + DrawEditHeader(MetaManipulationType.Est); + DrawEditHeader(MetaManipulationType.Gmp); + DrawEditHeader(MetaManipulationType.Rsp); + DrawEditHeader(MetaManipulationType.GlobalEqp); } - private static ReadOnlySpan Label(MetaManipulation.Type type) - => type switch - { - MetaManipulation.Type.Imc => "Variant Edits (IMC)###IMC"u8, - MetaManipulation.Type.Eqdp => "Racial Model Edits (EQDP)###EQDP"u8, - MetaManipulation.Type.Eqp => "Equipment Parameter Edits (EQP)###EQP"u8, - MetaManipulation.Type.Est => "Extra Skeleton Parameters (EST)###EST"u8, - MetaManipulation.Type.Gmp => "Visor/Gimmick Edits (GMP)###GMP"u8, - MetaManipulation.Type.Rsp => "Racial Scaling Edits (RSP)###RSP"u8, - MetaManipulation.Type.GlobalEqp => "Global Equipment Parameter Edits (Global EQP)###GEQP"u8, - _ => "\0"u8, - }; - - private static int ColumnCount(MetaManipulation.Type type) - => type switch - { - MetaManipulation.Type.Imc => 10, - MetaManipulation.Type.Eqdp => 7, - MetaManipulation.Type.Eqp => 5, - MetaManipulation.Type.Est => 7, - MetaManipulation.Type.Gmp => 7, - MetaManipulation.Type.Rsp => 5, - MetaManipulation.Type.GlobalEqp => 4, - _ => 0, - }; - - private void DrawEditHeader(MetaManipulation.Type type) + private void DrawEditHeader(MetaManipulationType type) { + var drawer = _metaDrawers.Get(type); + if (drawer == null) + return; + var oldPos = ImGui.GetCursorPosY(); - var header = ImUtf8.CollapsingHeader($"{_editor.MetaEditor.GetCount(type)} {Label(type)}"); + var header = ImUtf8.CollapsingHeader($"{_editor.MetaEditor.GetCount(type)} {drawer.Label}"); DrawOtherOptionData(type, oldPos, ImGui.GetCursorPos()); if (!header) return; - DrawTable(type); + DrawTable(drawer); } - private IMetaDrawer? Drawer(MetaManipulation.Type type) - => type switch - { - //MetaManipulation.Type.Imc => expr, - //MetaManipulation.Type.Eqdp => expr, - //MetaManipulation.Type.Eqp => expr, - //MetaManipulation.Type.Est => expr, - //MetaManipulation.Type.Gmp => expr, - //MetaManipulation.Type.Rsp => expr, - //MetaManipulation.Type.GlobalEqp => expr, - _ => null, - }; - - private void DrawTable(MetaManipulation.Type type) + private static void DrawTable(IMetaDrawer drawer) { const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersInnerV; - using var table = ImUtf8.Table(Label(type), ColumnCount(type), flags); + using var table = ImUtf8.Table(drawer.Label, drawer.NumColumns, flags); if (!table) return; - if (Drawer(type) is not { } drawer) - return; - drawer.Draw(); ImGui.NewLine(); } - private void DrawOtherOptionData(MetaManipulation.Type type, float oldPos, Vector2 newPos) + private void DrawOtherOptionData(MetaManipulationType type, float oldPos, Vector2 newPos) { var otherOptionData = _editor.MetaEditor.OtherData[type]; if (otherOptionData.TotalCount <= 0) @@ -149,577 +102,12 @@ private void DrawOtherOptionData(MetaManipulation.Type type, float oldPos, Vecto ImGui.SetCursorPos(newPos); } -#if false - private static class EqpRow - { - private static EqpIdentifier _newIdentifier = new(1, EquipSlot.Body); - - private static float IdWidth - => 100 * UiHelpers.Scale; - - public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) - { - ImGui.TableNextColumn(); - CopyToClipboardButton("Copy all current EQP manipulations to clipboard.", iconSize, - editor.MetaEditor.Eqp.Select(m => (MetaManipulation)m)); - ImGui.TableNextColumn(); - var canAdd = editor.MetaEditor.CanAdd(_new); - var tt = canAdd ? "Stage this edit." : "This entry is already edited."; - var defaultEntry = ExpandedEqpFile.GetDefault(metaFileManager, _new.SetId); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) - editor.MetaEditor.Add(_new.Copy(defaultEntry)); - - // Identifier - ImGui.TableNextColumn(); - if (IdInput("##eqpId", IdWidth, _new.SetId.Id, out var setId, 1, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) - _new = new EqpManipulation(ExpandedEqpFile.GetDefault(metaFileManager, setId), _new.Slot, setId); - - ImGuiUtil.HoverTooltip(ModelSetIdTooltip); - - ImGui.TableNextColumn(); - if (Combos.EqpEquipSlot("##eqpSlot", _new.Slot, out var slot)) - _new = new EqpManipulation(ExpandedEqpFile.GetDefault(metaFileManager, setId), slot, _new.SetId); - - ImGuiUtil.HoverTooltip(EquipSlotTooltip); - - // Values - using var disabled = ImRaii.Disabled(); - ImGui.TableNextColumn(); - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, - new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y)); - foreach (var flag in Eqp.EqpAttributes[_new.Slot]) - { - var value = defaultEntry.HasFlag(flag); - Checkmark("##eqp", flag.ToLocalName(), value, value, out _); - ImGui.SameLine(); - } - - ImGui.NewLine(); - } - - public static void Draw(MetaFileManager metaFileManager, EqpManipulation meta, ModEditor editor, Vector2 iconSize) - { - DrawMetaButtons(meta, editor, iconSize); - - // Identifier - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.SetId.ToString()); - ImGuiUtil.HoverTooltip(ModelSetIdTooltipShort); - var defaultEntry = ExpandedEqpFile.GetDefault(metaFileManager, meta.SetId); - - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.Slot.ToName()); - ImGuiUtil.HoverTooltip(EquipSlotTooltip); - - // Values - ImGui.TableNextColumn(); - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, - new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y)); - var idx = 0; - foreach (var flag in Eqp.EqpAttributes[meta.Slot]) - { - using var id = ImRaii.PushId(idx++); - var defaultValue = defaultEntry.HasFlag(flag); - var currentValue = meta.Entry.HasFlag(flag); - if (Checkmark("##eqp", flag.ToLocalName(), currentValue, defaultValue, out var value)) - editor.MetaEditor.Change(meta.Copy(value ? meta.Entry | flag : meta.Entry & ~flag)); - - ImGui.SameLine(); - } - - ImGui.NewLine(); - } - } - private static class EqdpRow - { - private static EqdpManipulation _new = new(EqdpEntry.Invalid, EquipSlot.Head, Gender.Male, ModelRace.Midlander, 1); - - private static float IdWidth - => 100 * UiHelpers.Scale; - - public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) - { - ImGui.TableNextColumn(); - CopyToClipboardButton("Copy all current EQDP manipulations to clipboard.", iconSize, - editor.MetaEditor.Eqdp.Select(m => (MetaManipulation)m)); - ImGui.TableNextColumn(); - var raceCode = Names.CombinedRace(_new.Gender, _new.Race); - var validRaceCode = CharacterUtilityData.EqdpIdx(raceCode, false) >= 0; - var canAdd = validRaceCode && editor.MetaEditor.CanAdd(_new); - var tt = canAdd ? "Stage this edit." : - validRaceCode ? "This entry is already edited." : "This combination of race and gender can not be used."; - var defaultEntry = validRaceCode - ? ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race), _new.Slot.IsAccessory(), _new.SetId) - : 0; - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) - editor.MetaEditor.Add(_new.Copy(defaultEntry)); - - // Identifier - ImGui.TableNextColumn(); - if (IdInput("##eqdpId", IdWidth, _new.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) - { - var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race), - _new.Slot.IsAccessory(), setId); - _new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, _new.Race, setId); - } - - ImGuiUtil.HoverTooltip(ModelSetIdTooltip); - - ImGui.TableNextColumn(); - if (Combos.Race("##eqdpRace", _new.Race, out var race)) - { - var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, race), - _new.Slot.IsAccessory(), _new.SetId); - _new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, race, _new.SetId); - } - - ImGuiUtil.HoverTooltip(ModelRaceTooltip); - - ImGui.TableNextColumn(); - if (Combos.Gender("##eqdpGender", _new.Gender, out var gender)) - { - var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(gender, _new.Race), - _new.Slot.IsAccessory(), _new.SetId); - _new = new EqdpManipulation(newDefaultEntry, _new.Slot, gender, _new.Race, _new.SetId); - } - - ImGuiUtil.HoverTooltip(GenderTooltip); - - ImGui.TableNextColumn(); - if (Combos.EqdpEquipSlot("##eqdpSlot", _new.Slot, out var slot)) - { - var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race), - slot.IsAccessory(), _new.SetId); - _new = new EqdpManipulation(newDefaultEntry, slot, _new.Gender, _new.Race, _new.SetId); - } - - ImGuiUtil.HoverTooltip(EquipSlotTooltip); - - // Values - using var disabled = ImRaii.Disabled(); - ImGui.TableNextColumn(); - var (bit1, bit2) = defaultEntry.ToBits(_new.Slot); - Checkmark("Material##eqdpCheck1", string.Empty, bit1, bit1, out _); - ImGui.SameLine(); - Checkmark("Model##eqdpCheck2", string.Empty, bit2, bit2, out _); - } - - public static void Draw(MetaFileManager metaFileManager, EqdpManipulation meta, ModEditor editor, Vector2 iconSize) - { - DrawMetaButtons(meta, editor, iconSize); - - // Identifier - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.SetId.ToString()); - ImGuiUtil.HoverTooltip(ModelSetIdTooltipShort); - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.Race.ToName()); - ImGuiUtil.HoverTooltip(ModelRaceTooltip); - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.Gender.ToName()); - ImGuiUtil.HoverTooltip(GenderTooltip); - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.Slot.ToName()); - ImGuiUtil.HoverTooltip(EquipSlotTooltip); - - // Values - var defaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(meta.Gender, meta.Race), meta.Slot.IsAccessory(), - meta.SetId); - var (defaultBit1, defaultBit2) = defaultEntry.ToBits(meta.Slot); - var (bit1, bit2) = meta.Entry.ToBits(meta.Slot); - ImGui.TableNextColumn(); - if (Checkmark("Material##eqdpCheck1", string.Empty, bit1, defaultBit1, out var newBit1)) - editor.MetaEditor.Change(meta.Copy(Eqdp.FromSlotAndBits(meta.Slot, newBit1, bit2))); - - ImGui.SameLine(); - if (Checkmark("Model##eqdpCheck2", string.Empty, bit2, defaultBit2, out var newBit2)) - editor.MetaEditor.Change(meta.Copy(Eqdp.FromSlotAndBits(meta.Slot, bit1, newBit2))); - } - } - - private static class EstRow - { - private static EstManipulation _new = new(Gender.Male, ModelRace.Midlander, EstType.Body, 1, EstEntry.Zero); - - private static float IdWidth - => 100 * UiHelpers.Scale; - - public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) - { - ImGui.TableNextColumn(); - CopyToClipboardButton("Copy all current EST manipulations to clipboard.", iconSize, - editor.MetaEditor.Est.Select(m => (MetaManipulation)m)); - ImGui.TableNextColumn(); - var canAdd = editor.MetaEditor.CanAdd(_new); - var tt = canAdd ? "Stage this edit." : "This entry is already edited."; - var defaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, _new.Race), _new.SetId); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) - editor.MetaEditor.Add(_new.Copy(defaultEntry)); - - // Identifier - ImGui.TableNextColumn(); - if (IdInput("##estId", IdWidth, _new.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) - { - var newDefaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, _new.Race), setId); - _new = new EstManipulation(_new.Gender, _new.Race, _new.Slot, setId, newDefaultEntry); - } - - ImGuiUtil.HoverTooltip(ModelSetIdTooltip); - - ImGui.TableNextColumn(); - if (Combos.Race("##estRace", _new.Race, out var race)) - { - var newDefaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, race), _new.SetId); - _new = new EstManipulation(_new.Gender, race, _new.Slot, _new.SetId, newDefaultEntry); - } - - ImGuiUtil.HoverTooltip(ModelRaceTooltip); - - ImGui.TableNextColumn(); - if (Combos.Gender("##estGender", _new.Gender, out var gender)) - { - var newDefaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(gender, _new.Race), _new.SetId); - _new = new EstManipulation(gender, _new.Race, _new.Slot, _new.SetId, newDefaultEntry); - } - - ImGuiUtil.HoverTooltip(GenderTooltip); - - ImGui.TableNextColumn(); - if (Combos.EstSlot("##estSlot", _new.Slot, out var slot)) - { - var newDefaultEntry = EstFile.GetDefault(metaFileManager, slot, Names.CombinedRace(_new.Gender, _new.Race), _new.SetId); - _new = new EstManipulation(_new.Gender, _new.Race, slot, _new.SetId, newDefaultEntry); - } - - ImGuiUtil.HoverTooltip(EstTypeTooltip); - - // Values - using var disabled = ImRaii.Disabled(); - ImGui.TableNextColumn(); - IntDragInput("##estSkeleton", "Skeleton Index", IdWidth, _new.Entry.Value, defaultEntry.Value, out _, 0, ushort.MaxValue, 0.05f); - } - - public static void Draw(MetaFileManager metaFileManager, EstManipulation meta, ModEditor editor, Vector2 iconSize) - { - DrawMetaButtons(meta, editor, iconSize); - - // Identifier - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.SetId.ToString()); - ImGuiUtil.HoverTooltip(ModelSetIdTooltipShort); - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.Race.ToName()); - ImGuiUtil.HoverTooltip(ModelRaceTooltip); - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.Gender.ToName()); - ImGuiUtil.HoverTooltip(GenderTooltip); - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.Slot.ToString()); - ImGuiUtil.HoverTooltip(EstTypeTooltip); - - // Values - var defaultEntry = EstFile.GetDefault(metaFileManager, meta.Slot, Names.CombinedRace(meta.Gender, meta.Race), meta.SetId); - ImGui.TableNextColumn(); - if (IntDragInput("##estSkeleton", $"Skeleton Index\nDefault Value: {defaultEntry}", IdWidth, meta.Entry.Value, defaultEntry.Value, - out var entry, 0, ushort.MaxValue, 0.05f)) - editor.MetaEditor.Change(meta.Copy(new EstEntry((ushort)entry))); - } - } - private static class GmpRow - { - private static GmpManipulation _new = new(GmpEntry.Default, 1); - - private static float RotationWidth - => 75 * UiHelpers.Scale; - - private static float UnkWidth - => 50 * UiHelpers.Scale; - - private static float IdWidth - => 100 * UiHelpers.Scale; - - public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) - { - ImGui.TableNextColumn(); - CopyToClipboardButton("Copy all current GMP manipulations to clipboard.", iconSize, - editor.MetaEditor.Gmp.Select(m => (MetaManipulation)m)); - ImGui.TableNextColumn(); - var canAdd = editor.MetaEditor.CanAdd(_new); - var tt = canAdd ? "Stage this edit." : "This entry is already edited."; - var defaultEntry = ExpandedGmpFile.GetDefault(metaFileManager, _new.SetId); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) - editor.MetaEditor.Add(_new.Copy(defaultEntry)); - - // Identifier - ImGui.TableNextColumn(); - if (IdInput("##gmpId", IdWidth, _new.SetId.Id, out var setId, 1, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) - _new = new GmpManipulation(ExpandedGmpFile.GetDefault(metaFileManager, setId), setId); - - ImGuiUtil.HoverTooltip(ModelSetIdTooltip); - - // Values - using var disabled = ImRaii.Disabled(); - ImGui.TableNextColumn(); - Checkmark("##gmpEnabled", "Gimmick Enabled", defaultEntry.Enabled, defaultEntry.Enabled, out _); - ImGui.TableNextColumn(); - Checkmark("##gmpAnimated", "Gimmick Animated", defaultEntry.Animated, defaultEntry.Animated, out _); - ImGui.TableNextColumn(); - IntDragInput("##gmpRotationA", "Rotation A in Degrees", RotationWidth, defaultEntry.RotationA, defaultEntry.RotationA, out _, 0, - 360, 0f); - ImGui.SameLine(); - IntDragInput("##gmpRotationB", "Rotation B in Degrees", RotationWidth, defaultEntry.RotationB, defaultEntry.RotationB, out _, 0, - 360, 0f); - ImGui.SameLine(); - IntDragInput("##gmpRotationC", "Rotation C in Degrees", RotationWidth, defaultEntry.RotationC, defaultEntry.RotationC, out _, 0, - 360, 0f); - ImGui.TableNextColumn(); - IntDragInput("##gmpUnkA", "Animation Type A?", UnkWidth, defaultEntry.UnknownA, defaultEntry.UnknownA, out _, 0, 15, 0f); - ImGui.SameLine(); - IntDragInput("##gmpUnkB", "Animation Type B?", UnkWidth, defaultEntry.UnknownB, defaultEntry.UnknownB, out _, 0, 15, 0f); - } - - public static void Draw(MetaFileManager metaFileManager, GmpManipulation meta, ModEditor editor, Vector2 iconSize) - { - DrawMetaButtons(meta, editor, iconSize); - - // Identifier - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.SetId.ToString()); - ImGuiUtil.HoverTooltip(ModelSetIdTooltipShort); - - // Values - var defaultEntry = ExpandedGmpFile.GetDefault(metaFileManager, meta.SetId); - ImGui.TableNextColumn(); - if (Checkmark("##gmpEnabled", "Gimmick Enabled", meta.Entry.Enabled, defaultEntry.Enabled, out var enabled)) - editor.MetaEditor.Change(meta.Copy(meta.Entry with { Enabled = enabled })); - - ImGui.TableNextColumn(); - if (Checkmark("##gmpAnimated", "Gimmick Animated", meta.Entry.Animated, defaultEntry.Animated, out var animated)) - editor.MetaEditor.Change(meta.Copy(meta.Entry with { Animated = animated })); - - ImGui.TableNextColumn(); - if (IntDragInput("##gmpRotationA", $"Rotation A in Degrees\nDefault Value: {defaultEntry.RotationA}", RotationWidth, - meta.Entry.RotationA, defaultEntry.RotationA, out var rotationA, 0, 360, 0.05f)) - editor.MetaEditor.Change(meta.Copy(meta.Entry with { RotationA = (ushort)rotationA })); - - ImGui.SameLine(); - if (IntDragInput("##gmpRotationB", $"Rotation B in Degrees\nDefault Value: {defaultEntry.RotationB}", RotationWidth, - meta.Entry.RotationB, defaultEntry.RotationB, out var rotationB, 0, 360, 0.05f)) - editor.MetaEditor.Change(meta.Copy(meta.Entry with { RotationB = (ushort)rotationB })); - - ImGui.SameLine(); - if (IntDragInput("##gmpRotationC", $"Rotation C in Degrees\nDefault Value: {defaultEntry.RotationC}", RotationWidth, - meta.Entry.RotationC, defaultEntry.RotationC, out var rotationC, 0, 360, 0.05f)) - editor.MetaEditor.Change(meta.Copy(meta.Entry with { RotationC = (ushort)rotationC })); - - ImGui.TableNextColumn(); - if (IntDragInput("##gmpUnkA", $"Animation Type A?\nDefault Value: {defaultEntry.UnknownA}", UnkWidth, meta.Entry.UnknownA, - defaultEntry.UnknownA, out var unkA, 0, 15, 0.01f)) - editor.MetaEditor.Change(meta.Copy(meta.Entry with { UnknownA = (byte)unkA })); - - ImGui.SameLine(); - if (IntDragInput("##gmpUnkB", $"Animation Type B?\nDefault Value: {defaultEntry.UnknownB}", UnkWidth, meta.Entry.UnknownB, - defaultEntry.UnknownB, out var unkB, 0, 15, 0.01f)) - editor.MetaEditor.Change(meta.Copy(meta.Entry with { UnknownB = (byte)unkB })); - } - } - private static class RspRow - { - private static RspManipulation _new = new(SubRace.Midlander, RspAttribute.MaleMinSize, RspEntry.One); - - private static float FloatWidth - => 150 * UiHelpers.Scale; - - public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) - { - ImGui.TableNextColumn(); - CopyToClipboardButton("Copy all current RSP manipulations to clipboard.", iconSize, - editor.MetaEditor.Rsp.Select(m => (MetaManipulation)m)); - ImGui.TableNextColumn(); - var canAdd = editor.MetaEditor.CanAdd(_new); - var tt = canAdd ? "Stage this edit." : "This entry is already edited."; - var defaultEntry = CmpFile.GetDefault(metaFileManager, _new.SubRace, _new.Attribute); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) - editor.MetaEditor.Add(_new.Copy(defaultEntry)); - - // Identifier - ImGui.TableNextColumn(); - if (Combos.SubRace("##rspSubRace", _new.SubRace, out var subRace)) - _new = new RspManipulation(subRace, _new.Attribute, CmpFile.GetDefault(metaFileManager, subRace, _new.Attribute)); - - ImGuiUtil.HoverTooltip(RacialTribeTooltip); - - ImGui.TableNextColumn(); - if (Combos.RspAttribute("##rspAttribute", _new.Attribute, out var attribute)) - _new = new RspManipulation(_new.SubRace, attribute, CmpFile.GetDefault(metaFileManager, subRace, attribute)); - - ImGuiUtil.HoverTooltip(ScalingTypeTooltip); - - // Values - using var disabled = ImRaii.Disabled(); - ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(FloatWidth); - var value = defaultEntry.Value; - ImGui.DragFloat("##rspValue", ref value, 0f); - } - - public static void Draw(MetaFileManager metaFileManager, RspManipulation meta, ModEditor editor, Vector2 iconSize) - { - DrawMetaButtons(meta, editor, iconSize); - - // Identifier - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.SubRace.ToName()); - ImGuiUtil.HoverTooltip(RacialTribeTooltip); - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.TextUnformatted(meta.Attribute.ToFullString()); - ImGuiUtil.HoverTooltip(ScalingTypeTooltip); - ImGui.TableNextColumn(); - - // Values - var def = CmpFile.GetDefault(metaFileManager, meta.SubRace, meta.Attribute).Value; - var value = meta.Entry.Value; - ImGui.SetNextItemWidth(FloatWidth); - using var color = ImRaii.PushColor(ImGuiCol.FrameBg, - def < value ? ColorId.IncreasedMetaValue.Value() : ColorId.DecreasedMetaValue.Value(), - def != value); - if (ImGui.DragFloat("##rspValue", ref value, 0.001f, RspEntry.MinValue, RspEntry.MaxValue) - && value is >= RspEntry.MinValue and <= RspEntry.MaxValue) - editor.MetaEditor.Change(meta.Copy(new RspEntry(value))); - - ImGuiUtil.HoverTooltip($"Default Value: {def:0.###}"); - } - } - private static class GlobalEqpRow - { - private static GlobalEqpManipulation _new = new() - { - Type = GlobalEqpType.DoNotHideEarrings, - Condition = 1, - }; - - public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) - { - ImGui.TableNextColumn(); - CopyToClipboardButton("Copy all current global EQP manipulations to clipboard.", iconSize, - editor.MetaEditor.GlobalEqp.Select(m => (MetaManipulation)m)); - ImGui.TableNextColumn(); - var canAdd = editor.MetaEditor.CanAdd(_new); - var tt = canAdd ? "Stage this edit." : "This entry is already manipulated."; - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) - editor.MetaEditor.Add(_new); - - // Identifier - ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(250 * ImUtf8.GlobalScale); - using (var combo = ImUtf8.Combo("##geqpType"u8, _new.Type.ToName())) - { - if (combo) - foreach (var type in Enum.GetValues()) - { - if (ImUtf8.Selectable(type.ToName(), type == _new.Type)) - _new = new GlobalEqpManipulation - { - Type = type, - Condition = type.HasCondition() ? _new.Type.HasCondition() ? _new.Condition : 1 : 0, - }; - ImUtf8.HoverTooltip(type.ToDescription()); - } - } - - ImUtf8.HoverTooltip(_new.Type.ToDescription()); - - ImGui.TableNextColumn(); - if (!_new.Type.HasCondition()) - return; - - if (IdInput("##geqpCond", 100 * ImUtf8.GlobalScale, _new.Condition.Id, out var newId, 1, ushort.MaxValue, _new.Condition.Id <= 1)) - _new = _new with { Condition = newId }; - ImUtf8.HoverTooltip("The Model ID for the item that should not be hidden."u8); - } - - public static void Draw(MetaFileManager metaFileManager, GlobalEqpManipulation meta, ModEditor editor, Vector2 iconSize) - { - DrawMetaButtons(meta, editor, iconSize); - ImGui.TableNextColumn(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImUtf8.Text(meta.Type.ToName()); - ImUtf8.HoverTooltip(meta.Type.ToDescription()); - ImGui.TableNextColumn(); - if (meta.Type.HasCondition()) - { - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImUtf8.Text($"{meta.Condition.Id}"); - } - } - } -#endif - - // A number input for ids with a optional max id of given width. - // Returns true if newId changed against currentId. - private static bool IdInput(string label, float width, ushort currentId, out ushort newId, int minId, int maxId, bool border) - { - int tmp = currentId; - ImGui.SetNextItemWidth(width); - using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, UiHelpers.Scale, border); - using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.RegexWarningBorder, border); - if (ImGui.InputInt(label, ref tmp, 0)) - tmp = Math.Clamp(tmp, minId, maxId); - - newId = (ushort)tmp; - return newId != currentId; - } - - // A checkmark that compares against a default value and shows a tooltip. - // Returns true if newValue is changed against currentValue. - private static bool Checkmark(string label, string tooltip, bool currentValue, bool defaultValue, out bool newValue) - { - using var color = ImRaii.PushColor(ImGuiCol.FrameBg, - defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), - defaultValue != currentValue); - newValue = currentValue; - ImGui.Checkbox(label, ref newValue); - ImGuiUtil.HoverTooltip(tooltip, ImGuiHoveredFlags.AllowWhenDisabled); - return newValue != currentValue; - } - - // A dragging int input of given width that compares against a default value, shows a tooltip and clamps against min and max. - // Returns true if newValue changed against currentValue. - private static bool IntDragInput(string label, string tooltip, float width, int currentValue, int defaultValue, out int newValue, - int minValue, int maxValue, float speed) - { - newValue = currentValue; - using var color = ImRaii.PushColor(ImGuiCol.FrameBg, - defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), - defaultValue != currentValue); - ImGui.SetNextItemWidth(width); - if (ImGui.DragInt(label, ref newValue, speed, minValue, maxValue)) - newValue = Math.Clamp(newValue, minValue, maxValue); - - ImGuiUtil.HoverTooltip(tooltip, ImGuiHoveredFlags.AllowWhenDisabled); - - return newValue != currentValue; - } - private static void CopyToClipboardButton(string tooltip, Vector2 iconSize, MetaDictionary manipulations) { if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), iconSize, tooltip, false, true)) return; - var text = Functions.ToCompressedBase64(manipulations, MetaManipulation.CurrentVersion); + var text = Functions.ToCompressedBase64(manipulations, MetaApi.CurrentVersion); if (text.Length > 0) ImGui.SetClipboardText(text); } @@ -731,8 +119,11 @@ private void AddFromClipboardButton() var clipboard = ImGuiUtil.GetClipboardText(); var version = Functions.FromCompressedBase64(clipboard, out var manips); - if (version == MetaManipulation.CurrentVersion && manips != null) + if (version == MetaApi.CurrentVersion && manips != null) + { _editor.MetaEditor.UpdateTo(manips); + _editor.MetaEditor.Changes = true; + } } ImGuiUtil.HoverTooltip( @@ -745,194 +136,14 @@ private void SetFromClipboardButton() { var clipboard = ImGuiUtil.GetClipboardText(); var version = Functions.FromCompressedBase64(clipboard, out var manips); - if (version == MetaManipulation.CurrentVersion && manips != null) + if (version == MetaApi.CurrentVersion && manips != null) + { _editor.MetaEditor.SetTo(manips); + _editor.MetaEditor.Changes = true; + } } ImGuiUtil.HoverTooltip( "Try to set the current meta manipulations to the set currently stored in the clipboard.\nRemoves all other manipulations."); } - - private static void DrawMetaButtons(MetaManipulation meta, ModEditor editor, Vector2 iconSize) - { - //ImGui.TableNextColumn(); - //CopyToClipboardButton("Copy this manipulation to clipboard.", iconSize, Array.Empty().Append(meta)); - // - //ImGui.TableNextColumn(); - //if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta manipulation.", false, true)) - // editor.MetaEditor.Delete(meta); - } -} - - -public interface IMetaDrawer -{ - public void Draw(); -} - - - - -public abstract class MetaDrawer(ModEditor editor, MetaFileManager metaFiles) : IMetaDrawer - where TIdentifier : unmanaged, IMetaIdentifier - where TEntry : unmanaged -{ - protected readonly ModEditor Editor = editor; - protected readonly MetaFileManager MetaFiles = metaFiles; - protected TIdentifier Identifier; - protected TEntry Entry; - private bool _initialized; - - public void Draw() - { - if (!_initialized) - { - Initialize(); - _initialized = true; - } - - DrawNew(); - foreach (var ((identifier, entry), idx) in Enumerate().WithIndex()) - { - using var id = ImUtf8.PushId(idx); - DrawEntry(identifier, entry); - } - } - - protected abstract void DrawNew(); - protected abstract void Initialize(); - protected abstract void DrawEntry(TIdentifier identifier, TEntry entry); - - protected abstract IEnumerable<(TIdentifier, TEntry)> Enumerate(); -} - - -#if false -public sealed class GmpMetaDrawer(ModEditor editor) : MetaDrawer, IService -{ - protected override void Initialize() - { - Identifier = new GmpIdentifier(1, EquipSlot.Body); - UpdateEntry(); - } - - private void UpdateEntry() - => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); - - protected override void DrawNew() - { } - - protected override void DrawEntry(GmpIdentifier identifier, GmpEntry entry) - { } - - protected override IEnumerable<(GmpIdentifier, GmpEntry)> Enumerate() - => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); -} - -public sealed class EstMetaDrawer(ModEditor editor) : MetaDrawer, IService -{ - protected override void Initialize() - { - Identifier = new EqpIdentifier(1, EquipSlot.Body); - UpdateEntry(); - } - - private void UpdateEntry() - => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); - - protected override void DrawNew() - { } - - protected override void DrawEntry(EstIdentifier identifier, EstEntry entry) - { } - - protected override IEnumerable<(EstIdentifier, EstEntry)> Enumerate() - => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); -} - -public sealed class EqdpMetaDrawer(ModEditor editor) : MetaDrawer, IService -{ - protected override void Initialize() - { - Identifier = new EqdpIdentifier(1, EquipSlot.Body); - UpdateEntry(); - } - - private void UpdateEntry() - => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); - - protected override void DrawNew() - { } - - protected override void DrawEntry(EqdpIdentifier identifier, EqdpEntry entry) - { } - - protected override IEnumerable<(EqdpIdentifier, EqdpEntry)> Enumerate() - => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); -} - -public sealed class EqpMetaDrawer(ModEditor editor, MetaFileManager metaManager) : MetaDrawer, IService -{ - protected override void Initialize() - { - Identifier = new EqpIdentifier(1, EquipSlot.Body); - UpdateEntry(); - } - - private void UpdateEntry() - => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); - - protected override void DrawNew() - { } - - protected override void DrawEntry(EqpIdentifier identifier, EqpEntry entry) - { } - - protected override IEnumerable<(EqpIdentifier, EqpEntry)> Enumerate() - => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); -} - -public sealed class RspMetaDrawer(ModEditor editor) : MetaDrawer, IService -{ - protected override void Initialize() - { - Identifier = new RspIdentifier(1, EquipSlot.Body); - UpdateEntry(); - } - - private void UpdateEntry() - => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); - - protected override void DrawNew() - { } - - protected override void DrawEntry(RspIdentifier identifier, RspEntry entry) - { } - - protected override IEnumerable<(RspIdentifier, RspEntry)> Enumerate() - => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); -} - - - -public sealed class GlobalEqpMetaDrawer(ModEditor editor) : MetaDrawer, IService -{ - protected override void Initialize() - { - Identifier = new EqpIdentifier(1, EquipSlot.Body); - UpdateEntry(); - } - - private void UpdateEntry() - => Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId); - - protected override void DrawNew() - { } - - protected override void DrawEntry(GlobalEqpManipulation identifier, byte _) - { } - - protected override IEnumerable<(GlobalEqpManipulation, byte)> Enumerate() - => editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot))); } -#endif diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs index 1f935eb6..72ab37b2 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs @@ -95,7 +95,7 @@ private void FindGamePaths(string path) task.ContinueWith(t => { GamePaths = FinalizeIo(t); }, TaskScheduler.Default); } - private EstManipulation[] GetCurrentEstManipulations() + private KeyValuePair[] GetCurrentEstManipulations() { var mod = _edit._editor.Mod; var option = _edit._editor.Option; @@ -106,9 +106,7 @@ private EstManipulation[] GetCurrentEstManipulations() return mod.AllDataContainers .Where(subMod => subMod != option) .Prepend(option) - .SelectMany(subMod => subMod.Manipulations) - .Where(manipulation => manipulation.ManipulationType is MetaManipulation.Type.Est) - .Select(manipulation => manipulation.Est) + .SelectMany(subMod => subMod.Manipulations.Est) .ToArray(); } diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs index af01047b..9410b793 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs @@ -25,6 +25,7 @@ using Penumbra.Services; using Penumbra.String; using Penumbra.String.Classes; +using Penumbra.UI.AdvancedWindow.Meta; using Penumbra.UI.Classes; using Penumbra.Util; using MdlMaterialEditor = Penumbra.Mods.Editor.MdlMaterialEditor; @@ -586,7 +587,7 @@ public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialo StainService stainService, ActiveCollections activeCollections, ModMergeTab modMergeTab, CommunicatorService communicator, TextureManager textures, ModelManager models, IDragDropManager dragDropManager, ResourceTreeViewerFactory resourceTreeViewerFactory, ObjectManager objects, IFramework framework, - CharacterBaseDestructor characterBaseDestructor) + CharacterBaseDestructor characterBaseDestructor, MetaDrawers metaDrawers) : base(WindowBaseLabel) { _performance = performance; @@ -606,6 +607,7 @@ public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialo _objects = objects; _framework = framework; _characterBaseDestructor = characterBaseDestructor; + _metaDrawers = metaDrawers; _materialTab = new FileEditor(this, _communicator, gameData, config, _editor.Compactor, _fileDialog, "Materials", ".mtrl", () => PopulateIsOnPlayer(_editor.Files.Mtrl, ResourceType.Mtrl), DrawMaterialPanel, () => Mod?.ModPath.FullName ?? string.Empty, (bytes, path, writable) => new MtrlTab(this, new MtrlFile(bytes), path, writable)); diff --git a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs index 5e3aac48..962d156d 100644 --- a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs @@ -120,9 +120,9 @@ private bool DrawExpandedFiles(ModConflicts conflict) { var _ = data switch { - Utf8GamePath p => ImGuiNative.igSelectable_Bool(p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero) > 0, - MetaManipulation m => ImGui.Selectable(m.Manipulation?.ToString() ?? string.Empty), - _ => false, + Utf8GamePath p => ImGuiNative.igSelectable_Bool(p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero) > 0, + IMetaIdentifier m => ImGui.Selectable(m.ToString()), + _ => false, }; } } diff --git a/Penumbra/UI/Tabs/EffectiveTab.cs b/Penumbra/UI/Tabs/EffectiveTab.cs index 37561000..7076b80f 100644 --- a/Penumbra/UI/Tabs/EffectiveTab.cs +++ b/Penumbra/UI/Tabs/EffectiveTab.cs @@ -98,7 +98,7 @@ private void DrawEffectiveRows(ModCollection active, int skips, float height, bo // Filters mean we can not use the known counts. if (hasFilters) { - var it2 = m.Select(p => (p.Key.ToString(), p.Value.Name)); + var it2 = m.IdentifierSources.Select(p => (p.Item1.ToString(), p.Item2.Name)); if (stop >= 0) { ImGuiClip.DrawEndDummy(stop + it2.Count(CheckFilters), height); @@ -117,7 +117,7 @@ private void DrawEffectiveRows(ModCollection active, int skips, float height, bo } else { - stop = ImGuiClip.ClippedDraw(m, skips, DrawLine, m.Count, ~stop); + stop = ImGuiClip.ClippedDraw(m.IdentifierSources, skips, DrawLine, m.Count, ~stop); ImGuiClip.DrawEndDummy(stop, height); } } @@ -152,11 +152,11 @@ private static void DrawLine((string, LowerString) pair) ImGui.TableNextColumn(); ImGuiUtil.PrintIcon(FontAwesomeIcon.LongArrowAltLeft); ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(name); + ImGuiUtil.CopyOnClickSelectable(name.Text); } /// Draw a line for a unfiltered/unconverted manipulation and mod-index pair. - private static void DrawLine(KeyValuePair pair) + private static void DrawLine((IMetaIdentifier, IMod) pair) { var (manipulation, mod) = pair; ImGui.TableNextColumn(); @@ -165,7 +165,7 @@ private static void DrawLine(KeyValuePair pair) ImGui.TableNextColumn(); ImGuiUtil.PrintIcon(FontAwesomeIcon.LongArrowAltLeft); ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(mod.Name); + ImGuiUtil.CopyOnClickSelectable(mod.Name.Text); } /// Check filters for file replacements. diff --git a/Penumbra/Util/IdentifierExtensions.cs b/Penumbra/Util/IdentifierExtensions.cs index 0647ea8e..cb43ac06 100644 --- a/Penumbra/Util/IdentifierExtensions.cs +++ b/Penumbra/Util/IdentifierExtensions.cs @@ -2,7 +2,6 @@ using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Penumbra.Meta.Manipulations; using Penumbra.Mods.Editor; using Penumbra.Mods.SubMods; @@ -10,103 +9,14 @@ namespace Penumbra.Util; public static class IdentifierExtensions { - /// Compute the items changed by a given meta manipulation and put them into the changedItems dictionary. - public static void MetaChangedItems(this ObjectIdentification identifier, IDictionary changedItems, - MetaManipulation manip) - { - switch (manip.ManipulationType) - { - case MetaManipulation.Type.Imc: - switch (manip.Imc.ObjectType) - { - case ObjectType.Equipment: - case ObjectType.Accessory: - identifier.Identify(changedItems, - GamePaths.Equipment.Mtrl.Path(manip.Imc.PrimaryId, GenderRace.MidlanderMale, manip.Imc.EquipSlot, manip.Imc.Variant, - "a")); - break; - case ObjectType.Weapon: - identifier.Identify(changedItems, - GamePaths.Weapon.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.Variant, "a")); - break; - case ObjectType.DemiHuman: - identifier.Identify(changedItems, - GamePaths.DemiHuman.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.EquipSlot, manip.Imc.Variant, - "a")); - break; - case ObjectType.Monster: - identifier.Identify(changedItems, - GamePaths.Monster.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.Variant, "a")); - break; - } - - break; - case MetaManipulation.Type.Eqdp: - identifier.Identify(changedItems, - GamePaths.Equipment.Mdl.Path(manip.Eqdp.SetId, Names.CombinedRace(manip.Eqdp.Gender, manip.Eqdp.Race), manip.Eqdp.Slot)); - break; - case MetaManipulation.Type.Eqp: - identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(manip.Eqp.SetId, GenderRace.MidlanderMale, manip.Eqp.Slot)); - break; - case MetaManipulation.Type.Est: - switch (manip.Est.Slot) - { - case EstType.Hair: - changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Hair (Hair) {manip.Est.SetId}", null); - break; - case EstType.Face: - changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Face (Face) {manip.Est.SetId}", null); - break; - case EstType.Body: - identifier.Identify(changedItems, - GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race), - EquipSlot.Body)); - break; - case EstType.Head: - identifier.Identify(changedItems, - GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race), - EquipSlot.Head)); - break; - } - - break; - case MetaManipulation.Type.Gmp: - identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(manip.Gmp.SetId, GenderRace.MidlanderMale, EquipSlot.Head)); - break; - case MetaManipulation.Type.Rsp: - changedItems.TryAdd($"{manip.Rsp.SubRace.ToName()} {manip.Rsp.Attribute.ToFullString()}", null); - break; - case MetaManipulation.Type.GlobalEqp: - var path = manip.GlobalEqp.Type switch - { - GlobalEqpType.DoNotHideEarrings => GamePaths.Accessory.Mdl.Path(manip.GlobalEqp.Condition, GenderRace.MidlanderMale, - EquipSlot.Ears), - GlobalEqpType.DoNotHideNecklace => GamePaths.Accessory.Mdl.Path(manip.GlobalEqp.Condition, GenderRace.MidlanderMale, - EquipSlot.Neck), - GlobalEqpType.DoNotHideBracelets => GamePaths.Accessory.Mdl.Path(manip.GlobalEqp.Condition, GenderRace.MidlanderMale, - EquipSlot.Wrists), - GlobalEqpType.DoNotHideRingR => GamePaths.Accessory.Mdl.Path(manip.GlobalEqp.Condition, GenderRace.MidlanderMale, - EquipSlot.RFinger), - GlobalEqpType.DoNotHideRingL => GamePaths.Accessory.Mdl.Path(manip.GlobalEqp.Condition, GenderRace.MidlanderMale, - EquipSlot.LFinger), - GlobalEqpType.DoNotHideHrothgarHats => string.Empty, - GlobalEqpType.DoNotHideVieraHats => string.Empty, - _ => string.Empty, - }; - if (path.Length > 0) - identifier.Identify(changedItems, path); - break; - } - } - public static void AddChangedItems(this ObjectIdentification identifier, IModDataContainer container, IDictionary changedItems) { foreach (var gamePath in container.Files.Keys.Concat(container.FileSwaps.Keys)) identifier.Identify(changedItems, gamePath.ToString()); - foreach (var manip in container.Manipulations) - MetaChangedItems(identifier, changedItems, manip); + foreach (var manip in container.Manipulations.Identifiers) + manip.AddChangedItems(identifier, changedItems); } public static void RemoveMachinistOffhands(this SortedList changedItems) From 4ca49598f8ffff55728e98e108183c88d679c2e8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 14 Jun 2024 17:40:47 +0200 Subject: [PATCH 11/28] Small improvement. --- Penumbra/UI/ModsTab/ModPanelConflictsTab.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs index 962d156d..c1a3c1eb 100644 --- a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs @@ -3,6 +3,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Text; using OtterGui.Widgets; using Penumbra.Collections.Cache; using Penumbra.Collections.Manager; @@ -116,15 +117,12 @@ private bool DrawExpandedFiles(ModConflicts conflict) using var indent = ImRaii.PushIndent(30f); foreach (var data in conflict.Conflicts) { - unsafe + _ = data switch { - var _ = data switch - { - Utf8GamePath p => ImGuiNative.igSelectable_Bool(p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero) > 0, - IMetaIdentifier m => ImGui.Selectable(m.ToString()), - _ => false, - }; - } + Utf8GamePath p => ImUtf8.Selectable(p.Path.Span, false), + IMetaIdentifier m => ImUtf8.Selectable(m.ToString(), false), + _ => false, + }; } return true; From e33512cf7ff3dcc887b0516188e79d6d9fe09aa1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 14 Jun 2024 23:09:38 +0200 Subject: [PATCH 12/28] Fix issue, remove IMetaCache. --- Penumbra/Collections/Cache/IMetaCache.cs | 14 +++----------- Penumbra/Meta/Manipulations/MetaDictionary.cs | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Penumbra/Collections/Cache/IMetaCache.cs b/Penumbra/Collections/Cache/IMetaCache.cs index 218c1840..eacbdcc2 100644 --- a/Penumbra/Collections/Cache/IMetaCache.cs +++ b/Penumbra/Collections/Cache/IMetaCache.cs @@ -4,21 +4,12 @@ namespace Penumbra.Collections.Cache; -public interface IMetaCache : IDisposable -{ - public void SetFiles(); - public void Reset(); - public void ResetFiles(); - - public int Count { get; } -} - public abstract class MetaCacheBase - : Dictionary, IMetaCache + : Dictionary where TIdentifier : unmanaged, IMetaIdentifier where TEntry : unmanaged { - public MetaCacheBase(MetaFileManager manager, ModCollection collection) + protected MetaCacheBase(MetaFileManager manager, ModCollection collection) { Manager = manager; Collection = collection; @@ -76,6 +67,7 @@ private void IncorporateChanges() { IncorporateChangesInternal(); } + if (Manager.ActiveCollections.Default == Collection && Manager.Config.EnableMods) SetFiles(); } diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs index 5a51df83..1093c6c5 100644 --- a/Penumbra/Meta/Manipulations/MetaDictionary.cs +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -346,7 +346,7 @@ public bool MergeForced(MetaDictionary manips, out IMetaIdentifier? failedIdenti } failedIdentifier = default; - return false; + return true; } public void SetTo(MetaDictionary other) From ad0c64d4ac9e3b2d8ba89b7920b2f96a72480709 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 15 Jun 2024 11:37:39 +0200 Subject: [PATCH 13/28] Change Eqp hook to not need eqp files anymore. --- Penumbra/Collections/Cache/EqpCache.cs | 12 ++++++++++++ Penumbra/Collections/Cache/MetaCache.cs | 8 -------- Penumbra/Collections/ModCollection.Cache.Access.cs | 7 ------- Penumbra/Interop/Hooks/Meta/EqpHook.cs | 7 +++---- Penumbra/Interop/PathResolving/MetaState.cs | 5 ++--- Penumbra/UI/ConfigWindow.cs | 3 ++- 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Penumbra/Collections/Cache/EqpCache.cs b/Penumbra/Collections/Cache/EqpCache.cs index 599ae588..8dde9aba 100644 --- a/Penumbra/Collections/Cache/EqpCache.cs +++ b/Penumbra/Collections/Cache/EqpCache.cs @@ -1,3 +1,4 @@ +using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; @@ -28,6 +29,17 @@ protected override void IncorporateChangesInternal() Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed EQP manipulations."); } + public unsafe EqpEntry GetValues(CharacterArmor* armor) + => GetSingleValue(armor[0].Set, EquipSlot.Head) + | GetSingleValue(armor[1].Set, EquipSlot.Body) + | GetSingleValue(armor[2].Set, EquipSlot.Hands) + | GetSingleValue(armor[3].Set, EquipSlot.Legs) + | GetSingleValue(armor[4].Set, EquipSlot.Feet); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private EqpEntry GetSingleValue(PrimaryId id, EquipSlot slot) + => TryGetValue(new EqpIdentifier(id, slot), out var pair) ? pair.Entry : ExpandedEqpFile.GetDefault(manager, id) & Eqp.Mask(slot); + public MetaList.MetaReverter TemporarilySetFile() => Manager.TemporarilySetFile(_eqpFile, MetaIndex.Eqp); diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index bc6ef34d..45a85d0f 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -1,4 +1,3 @@ -using System.IO.Pipes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Services; @@ -146,9 +145,6 @@ public void SetFile(MetaIndex metaIndex) public void SetImcFiles(bool fromFullCompute) => Imc.SetFiles(fromFullCompute); - public MetaList.MetaReverter TemporarilySetEqpFile() - => Eqp.TemporarilySetFile(); - public MetaList.MetaReverter? TemporarilySetEqdpFile(GenderRace genderRace, bool accessory) => Eqdp.TemporarilySetFile(genderRace, accessory); @@ -161,10 +157,6 @@ public MetaList.MetaReverter TemporarilySetCmpFile() public MetaList.MetaReverter TemporarilySetEstFile(EstType type) => Est.TemporarilySetFiles(type); - public unsafe EqpEntry ApplyGlobalEqp(EqpEntry baseEntry, CharacterArmor* armor) - => GlobalEqp.Apply(baseEntry, armor); - - /// Try to obtain a manipulated IMC file. public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) => Imc.GetFile(path, out file); diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 484d4dd2..3e3386ea 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -100,10 +100,6 @@ public void SetMetaFile(CharacterUtility utility, MetaIndex idx) return idx >= 0 ? utility.TemporarilyResetResource(idx) : null; } - public MetaList.MetaReverter TemporarilySetEqpFile(CharacterUtility utility) - => _cache?.Meta.TemporarilySetEqpFile() - ?? utility.TemporarilyResetResource(MetaIndex.Eqp); - public MetaList.MetaReverter TemporarilySetGmpFile(CharacterUtility utility) => _cache?.Meta.TemporarilySetGmpFile() ?? utility.TemporarilyResetResource(MetaIndex.Gmp); @@ -115,7 +111,4 @@ public MetaList.MetaReverter TemporarilySetCmpFile(CharacterUtility utility) public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstType type) => _cache?.Meta.TemporarilySetEstFile(type) ?? utility.TemporarilyResetResource((MetaIndex)type); - - public unsafe EqpEntry ApplyGlobalEqp(EqpEntry baseEntry, CharacterArmor* armor) - => _cache?.Meta.ApplyGlobalEqp(baseEntry, armor) ?? baseEntry; } diff --git a/Penumbra/Interop/Hooks/Meta/EqpHook.cs b/Penumbra/Interop/Hooks/Meta/EqpHook.cs index 6663c211..448605c1 100644 --- a/Penumbra/Interop/Hooks/Meta/EqpHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EqpHook.cs @@ -19,11 +19,10 @@ public EqpHook(HookManager hooks, MetaState metaState) private void Detour(CharacterUtility* utility, EqpEntry* flags, CharacterArmor* armor) { - if (_metaState.EqpCollection.Valid) + if (_metaState.EqpCollection is { Valid: true, ModCollection.MetaCache: { } cache }) { - using var eqp = _metaState.ResolveEqpData(_metaState.EqpCollection.ModCollection); - Task.Result.Original(utility, flags, armor); - *flags = _metaState.EqpCollection.ModCollection.ApplyGlobalEqp(*flags, armor); + *flags = cache.Eqp.GetValues(armor); + *flags = cache.GlobalEqp.Apply(*flags, armor); } else { diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index 6fa5c263..de7912e0 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -47,6 +47,8 @@ public sealed unsafe class MetaState : IDisposable public ResolveData CustomizeChangeCollection = ResolveData.Invalid; public ResolveData EqpCollection = ResolveData.Invalid; + public ResolveData GmpCollection = ResolveData.Invalid; + public PrimaryId UndividedGmpId = 0; private ResolveData _lastCreatedCollection = ResolveData.Invalid; private DisposableContainer _characterBaseCreateMetaChanges = DisposableContainer.Empty; @@ -93,9 +95,6 @@ public DisposableContainer ResolveEqdpData(ModCollection collection, GenderRace _ => DisposableContainer.Empty, }; - public MetaList.MetaReverter ResolveEqpData(ModCollection collection) - => collection.TemporarilySetEqpFile(_characterUtility); - public MetaList.MetaReverter ResolveGmpData(ModCollection collection) => collection.TemporarilySetGmpFile(_characterUtility); diff --git a/Penumbra/UI/ConfigWindow.cs b/Penumbra/UI/ConfigWindow.cs index 9ae11fc3..0ae16f6d 100644 --- a/Penumbra/UI/ConfigWindow.cs +++ b/Penumbra/UI/ConfigWindow.cs @@ -4,6 +4,7 @@ using OtterGui; using OtterGui.Custom; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.Api.Enums; using Penumbra.Services; using Penumbra.UI.Classes; @@ -144,7 +145,7 @@ private void DrawProblemWindow(string text) using var color = ImRaii.PushColor(ImGuiCol.Text, Colors.RegexWarningBorder); ImGui.NewLine(); ImGui.NewLine(); - ImGuiUtil.TextWrapped(text); + ImUtf8.TextWrapped(text); color.Pop(); ImGui.NewLine(); From 30b32fdcd21e82d9f9e1176793f6b01082a79528 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 15 Jun 2024 12:09:41 +0200 Subject: [PATCH 14/28] Fix EQDP bug. --- Penumbra/Collections/Cache/EqpCache.cs | 2 +- Penumbra/Meta/Files/EqpGmpFile.cs | 12 ++---------- Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Penumbra/Collections/Cache/EqpCache.cs b/Penumbra/Collections/Cache/EqpCache.cs index 8dde9aba..32c4c0ae 100644 --- a/Penumbra/Collections/Cache/EqpCache.cs +++ b/Penumbra/Collections/Cache/EqpCache.cs @@ -38,7 +38,7 @@ public unsafe EqpEntry GetValues(CharacterArmor* armor) [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private EqpEntry GetSingleValue(PrimaryId id, EquipSlot slot) - => TryGetValue(new EqpIdentifier(id, slot), out var pair) ? pair.Entry : ExpandedEqpFile.GetDefault(manager, id) & Eqp.Mask(slot); + => TryGetValue(new EqpIdentifier(id, slot), out var pair) ? pair.Entry : ExpandedEqpFile.GetDefault(Manager, id) & Eqp.Mask(slot); public MetaList.MetaReverter TemporarilySetFile() => Manager.TemporarilySetFile(_eqpFile, MetaIndex.Eqp); diff --git a/Penumbra/Meta/Files/EqpGmpFile.cs b/Penumbra/Meta/Files/EqpGmpFile.cs index 17541c4f..a7470b75 100644 --- a/Penumbra/Meta/Files/EqpGmpFile.cs +++ b/Penumbra/Meta/Files/EqpGmpFile.cs @@ -104,15 +104,11 @@ protected static ulong GetDefaultInternal(MetaFileManager manager, CharacterUtil } } -public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable +public sealed class ExpandedEqpFile(MetaFileManager manager) : ExpandedEqpGmpBase(manager, false), IEnumerable { public static readonly CharacterUtility.InternalIndex InternalIndex = CharacterUtility.ReverseIndices[(int)MetaIndex.Eqp]; - public ExpandedEqpFile(MetaFileManager manager) - : base(manager, false) - { } - public EqpEntry this[PrimaryId idx] { get => (EqpEntry)GetInternal(idx); @@ -147,15 +143,11 @@ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } -public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable +public sealed class ExpandedGmpFile(MetaFileManager manager) : ExpandedEqpGmpBase(manager, true), IEnumerable { public static readonly CharacterUtility.InternalIndex InternalIndex = CharacterUtility.ReverseIndices[(int)MetaIndex.Gmp]; - public ExpandedGmpFile(MetaFileManager manager) - : base(manager, true) - { } - public GmpEntry this[PrimaryId idx] { get => new() { Value = GetInternal(idx) }; diff --git a/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs index b1ac93f1..970b70cb 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs @@ -112,7 +112,7 @@ private static bool DrawEntry(EqdpEntryInternal defaultEntry, ref EqdpEntryInter ImGui.SameLine(); if (Checkmark("Model##eqdp"u8, "\0"u8, entry.Model, defaultEntry.Model, out var newModel)) { - entry = entry with { Material = newModel }; + entry = entry with { Model = newModel }; changes = true; } From a61a96f1ef5bcc9e0c96210595c07a52c0ac8115 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 15 Jun 2024 14:04:16 +0200 Subject: [PATCH 15/28] Make GmpEntry readonly. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 0a2e2650..cf1ff07e 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 0a2e2650d693d1bba267498f96112682cc09dbab +Subproject commit cf1ff07e900e2f93ab628a1fa535fc2b103794a5 From a7b90639c6deb2859adeac608521bd664ac2cbca Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 15 Jun 2024 14:46:42 +0200 Subject: [PATCH 16/28] Some fixes. --- Penumbra/Collections/Cache/EqdpCache.cs | 10 ++++++---- Penumbra/Collections/Cache/EstCache.cs | 7 ++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Penumbra/Collections/Cache/EqdpCache.cs b/Penumbra/Collections/Cache/EqdpCache.cs index f3475c7e..7139bb72 100644 --- a/Penumbra/Collections/Cache/EqdpCache.cs +++ b/Penumbra/Collections/Cache/EqdpCache.cs @@ -1,4 +1,3 @@ -using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using OtterGui; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -28,7 +27,10 @@ public void SetFile(MetaIndex index) } public override void ResetFiles() - => Manager.SetFile(null, MetaIndex.Eqp); + { + foreach (var t in CharacterUtilityData.EqdpIndices) + Manager.SetFile(null, t); + } protected override void IncorporateChangesInternal() { @@ -89,8 +91,8 @@ public static bool Apply(ExpandedEqdpFile file, EqdpIdentifier identifier, EqdpE var mask = Eqdp.Mask(identifier.Slot); if ((origEntry & mask) == entry) return false; - - file[identifier.SetId] = (entry & ~mask) | origEntry; + + file[identifier.SetId] = (origEntry & ~mask) | entry; return true; } diff --git a/Penumbra/Collections/Cache/EstCache.cs b/Penumbra/Collections/Cache/EstCache.cs index 412dd322..8ee530cc 100644 --- a/Penumbra/Collections/Cache/EstCache.cs +++ b/Penumbra/Collections/Cache/EstCache.cs @@ -55,7 +55,12 @@ public MetaList.MetaReverter TemporarilySetFiles(EstType type) } public override void ResetFiles() - => Manager.SetFile(null, MetaIndex.Eqp); + { + Manager.SetFile(null, MetaIndex.FaceEst); + Manager.SetFile(null, MetaIndex.HairEst); + Manager.SetFile(null, MetaIndex.BodyEst); + Manager.SetFile(null, MetaIndex.HeadEst); + } protected override void IncorporateChangesInternal() { From c53f29c257003bb8131441cc5bcf7086d808ad73 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 15 Jun 2024 15:19:48 +0200 Subject: [PATCH 17/28] Fix unnecessary EST file creations. --- Penumbra/Collections/Cache/EstCache.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Penumbra/Collections/Cache/EstCache.cs b/Penumbra/Collections/Cache/EstCache.cs index 8ee530cc..845ff128 100644 --- a/Penumbra/Collections/Cache/EstCache.cs +++ b/Penumbra/Collections/Cache/EstCache.cs @@ -74,7 +74,7 @@ protected override void IncorporateChangesInternal() public EstEntry GetEstEntry(EstIdentifier identifier) { - var file = GetFile(identifier); + var file = GetCurrentFile(identifier); return file != null ? file[identifier.GenderRace, identifier.SetId] : EstFile.GetDefault(Manager, identifier); @@ -124,9 +124,19 @@ protected override void Dispose(bool _) Clear(); } + private EstFile? GetCurrentFile(EstIdentifier identifier) + => identifier.Slot switch + { + EstType.Hair => _estHairFile, + EstType.Face => _estFaceFile, + EstType.Body => _estBodyFile, + EstType.Head => _estHeadFile, + _ => null, + }; + private EstFile? GetFile(EstIdentifier identifier) { - if (Manager.CharacterUtility.Ready) + if (!Manager.CharacterUtility.Ready) return null; return identifier.Slot switch From 943207cae8923720fc8dbe56d763ddc3d1034bd4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 15 Jun 2024 15:55:51 +0200 Subject: [PATCH 18/28] Make GMP independent of file, cleanup unused functions. --- Penumbra/Collections/Cache/EqdpCache.cs | 4 +- Penumbra/Collections/Cache/EqpCache.cs | 59 ++--------------- Penumbra/Collections/Cache/EstCache.cs | 4 +- Penumbra/Collections/Cache/GmpCache.cs | 59 ++--------------- Penumbra/Collections/Cache/IMetaCache.cs | 2 - Penumbra/Collections/Cache/ImcCache.cs | 4 +- Penumbra/Collections/Cache/MetaCache.cs | 3 - Penumbra/Collections/Cache/RspCache.cs | 5 +- .../Collections/ModCollection.Cache.Access.cs | 4 -- Penumbra/Interop/Hooks/Meta/GmpHook.cs | 64 +++++++++++++++++++ Penumbra/Interop/Hooks/Meta/SetupVisor.cs | 24 +++---- Penumbra/Interop/PathResolving/MetaState.cs | 3 - Penumbra/Interop/Services/MetaList.cs | 23 ++----- Penumbra/Meta/Files/EqpGmpFile.cs | 8 +-- 14 files changed, 108 insertions(+), 158 deletions(-) create mode 100644 Penumbra/Interop/Hooks/Meta/GmpHook.cs diff --git a/Penumbra/Collections/Cache/EqdpCache.cs b/Penumbra/Collections/Cache/EqdpCache.cs index 7139bb72..c63403ae 100644 --- a/Penumbra/Collections/Cache/EqdpCache.cs +++ b/Penumbra/Collections/Cache/EqdpCache.cs @@ -26,7 +26,7 @@ public void SetFile(MetaIndex index) Manager.SetFile(_eqdpFiles[i], index); } - public override void ResetFiles() + public void ResetFiles() { foreach (var t in CharacterUtilityData.EqdpIndices) Manager.SetFile(null, t); @@ -62,7 +62,7 @@ protected override void IncorporateChangesInternal() return Manager.TemporarilySetFile(_eqdpFiles[i], idx); } - public override void Reset() + public void Reset() { foreach (var file in _eqdpFiles.OfType()) { diff --git a/Penumbra/Collections/Cache/EqpCache.cs b/Penumbra/Collections/Cache/EqpCache.cs index 32c4c0ae..b1e03943 100644 --- a/Penumbra/Collections/Cache/EqpCache.cs +++ b/Penumbra/Collections/Cache/EqpCache.cs @@ -1,7 +1,5 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -10,24 +8,11 @@ namespace Penumbra.Collections.Cache; public sealed class EqpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private ExpandedEqpFile? _eqpFile; - public override void SetFiles() - => Manager.SetFile(_eqpFile, MetaIndex.Eqp); - - public override void ResetFiles() - => Manager.SetFile(null, MetaIndex.Eqp); + { } protected override void IncorporateChangesInternal() - { - if (GetFile() is not { } file) - return; - - foreach (var (identifier, (_, entry)) in this) - Apply(file, identifier, entry); - - Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed EQP manipulations."); - } + { } public unsafe EqpEntry GetValues(CharacterArmor* armor) => GetSingleValue(armor[0].Set, EquipSlot.Head) @@ -40,29 +25,14 @@ public unsafe EqpEntry GetValues(CharacterArmor* armor) private EqpEntry GetSingleValue(PrimaryId id, EquipSlot slot) => TryGetValue(new EqpIdentifier(id, slot), out var pair) ? pair.Entry : ExpandedEqpFile.GetDefault(Manager, id) & Eqp.Mask(slot); - public MetaList.MetaReverter TemporarilySetFile() - => Manager.TemporarilySetFile(_eqpFile, MetaIndex.Eqp); - - public override void Reset() - { - if (_eqpFile == null) - return; - - _eqpFile.Reset(Keys.Select(identifier => identifier.SetId)); - Clear(); - } + public void Reset() + => Clear(); protected override void ApplyModInternal(EqpIdentifier identifier, EqpEntry entry) - { - if (GetFile() is { } file) - Apply(file, identifier, entry); - } + { } protected override void RevertModInternal(EqpIdentifier identifier) - { - if (GetFile() is { } file) - Apply(file, identifier, ExpandedEqpFile.GetDefault(Manager, identifier.SetId)); - } + { } public static bool Apply(ExpandedEqpFile file, EqpIdentifier identifier, EqpEntry entry) { @@ -76,20 +46,5 @@ public static bool Apply(ExpandedEqpFile file, EqpIdentifier identifier, EqpEntr } protected override void Dispose(bool _) - { - _eqpFile?.Dispose(); - _eqpFile = null; - Clear(); - } - - private ExpandedEqpFile? GetFile() - { - if (_eqpFile != null) - return _eqpFile; - - if (!Manager.CharacterUtility.Ready) - return null; - - return _eqpFile = new ExpandedEqpFile(Manager); - } + => Clear(); } diff --git a/Penumbra/Collections/Cache/EstCache.cs b/Penumbra/Collections/Cache/EstCache.cs index 845ff128..6a9fa909 100644 --- a/Penumbra/Collections/Cache/EstCache.cs +++ b/Penumbra/Collections/Cache/EstCache.cs @@ -54,7 +54,7 @@ public MetaList.MetaReverter TemporarilySetFiles(EstType type) return Manager.TemporarilySetFile(file, idx); } - public override void ResetFiles() + public void ResetFiles() { Manager.SetFile(null, MetaIndex.FaceEst); Manager.SetFile(null, MetaIndex.HairEst); @@ -80,7 +80,7 @@ public EstEntry GetEstEntry(EstIdentifier identifier) : EstFile.GetDefault(Manager, identifier); } - public override void Reset() + public void Reset() { _estFaceFile?.Reset(); _estHairFile?.Reset(); diff --git a/Penumbra/Collections/Cache/GmpCache.cs b/Penumbra/Collections/Cache/GmpCache.cs index 1475ffd5..0beb51d8 100644 --- a/Penumbra/Collections/Cache/GmpCache.cs +++ b/Penumbra/Collections/Cache/GmpCache.cs @@ -1,6 +1,4 @@ using Penumbra.GameData.Structs; -using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -9,48 +7,20 @@ namespace Penumbra.Collections.Cache; public sealed class GmpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private ExpandedGmpFile? _gmpFile; - public override void SetFiles() - => Manager.SetFile(_gmpFile, MetaIndex.Gmp); - - public override void ResetFiles() - => Manager.SetFile(null, MetaIndex.Gmp); + { } protected override void IncorporateChangesInternal() - { - if (GetFile() is not { } file) - return; - - foreach (var (identifier, (_, entry)) in this) - Apply(file, identifier, entry); - - Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed GMP manipulations."); - } + { } - public MetaList.MetaReverter TemporarilySetFile() - => Manager.TemporarilySetFile(_gmpFile, MetaIndex.Gmp); - - public override void Reset() - { - if (_gmpFile == null) - return; - - _gmpFile.Reset(Keys.Select(identifier => identifier.SetId)); - Clear(); - } + public void Reset() + => Clear(); protected override void ApplyModInternal(GmpIdentifier identifier, GmpEntry entry) - { - if (GetFile() is { } file) - Apply(file, identifier, entry); - } + { } protected override void RevertModInternal(GmpIdentifier identifier) - { - if (GetFile() is { } file) - Apply(file, identifier, ExpandedGmpFile.GetDefault(Manager, identifier.SetId)); - } + { } public static bool Apply(ExpandedGmpFile file, GmpIdentifier identifier, GmpEntry entry) { @@ -63,20 +33,5 @@ public static bool Apply(ExpandedGmpFile file, GmpIdentifier identifier, GmpEntr } protected override void Dispose(bool _) - { - _gmpFile?.Dispose(); - _gmpFile = null; - Clear(); - } - - private ExpandedGmpFile? GetFile() - { - if (_gmpFile != null) - return _gmpFile; - - if (!Manager.CharacterUtility.Ready) - return null; - - return _gmpFile = new ExpandedGmpFile(Manager); - } + => Clear(); } diff --git a/Penumbra/Collections/Cache/IMetaCache.cs b/Penumbra/Collections/Cache/IMetaCache.cs index eacbdcc2..dd218b48 100644 --- a/Penumbra/Collections/Cache/IMetaCache.cs +++ b/Penumbra/Collections/Cache/IMetaCache.cs @@ -27,8 +27,6 @@ public void Dispose() } public abstract void SetFiles(); - public abstract void Reset(); - public abstract void ResetFiles(); public bool ApplyMod(IMod source, TIdentifier identifier, TEntry entry) { diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index 3d05e793..a9daf795 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -38,7 +38,7 @@ public void SetFiles(bool fromFullCompute) Collection._cache!.ForceFile(path, PathDataHandler.CreateImc(path.Path, Collection)); } - public override void ResetFiles() + public void ResetFiles() { foreach (var (path, _) in _imcFiles) Collection._cache!.ForceFile(path, FullPath.Empty); @@ -56,7 +56,7 @@ protected override void IncorporateChangesInternal() } - public override void Reset() + public void Reset() { foreach (var (path, (file, set)) in _imcFiles) { diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 45a85d0f..c8a116eb 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -148,9 +148,6 @@ public void SetImcFiles(bool fromFullCompute) public MetaList.MetaReverter? TemporarilySetEqdpFile(GenderRace genderRace, bool accessory) => Eqdp.TemporarilySetFile(genderRace, accessory); - public MetaList.MetaReverter TemporarilySetGmpFile() - => Gmp.TemporarilySetFile(); - public MetaList.MetaReverter TemporarilySetCmpFile() => Rsp.TemporarilySetFile(); diff --git a/Penumbra/Collections/Cache/RspCache.cs b/Penumbra/Collections/Cache/RspCache.cs index 3889d6f1..8a5fe97d 100644 --- a/Penumbra/Collections/Cache/RspCache.cs +++ b/Penumbra/Collections/Cache/RspCache.cs @@ -13,9 +13,6 @@ public sealed class RspCache(MetaFileManager manager, ModCollection collection) public override void SetFiles() => Manager.SetFile(_cmpFile, MetaIndex.HumanCmp); - public override void ResetFiles() - => Manager.SetFile(null, MetaIndex.HumanCmp); - protected override void IncorporateChangesInternal() { if (GetFile() is not { } file) @@ -30,7 +27,7 @@ protected override void IncorporateChangesInternal() public MetaList.MetaReverter TemporarilySetFile() => Manager.TemporarilySetFile(_cmpFile, MetaIndex.HumanCmp); - public override void Reset() + public void Reset() { if (_cmpFile == null) return; diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 3e3386ea..8701e3bb 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -100,10 +100,6 @@ public void SetMetaFile(CharacterUtility utility, MetaIndex idx) return idx >= 0 ? utility.TemporarilyResetResource(idx) : null; } - public MetaList.MetaReverter TemporarilySetGmpFile(CharacterUtility utility) - => _cache?.Meta.TemporarilySetGmpFile() - ?? utility.TemporarilyResetResource(MetaIndex.Gmp); - public MetaList.MetaReverter TemporarilySetCmpFile(CharacterUtility utility) => _cache?.Meta.TemporarilySetCmpFile() ?? utility.TemporarilyResetResource(MetaIndex.HumanCmp); diff --git a/Penumbra/Interop/Hooks/Meta/GmpHook.cs b/Penumbra/Interop/Hooks/Meta/GmpHook.cs new file mode 100644 index 00000000..60966fb7 --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/GmpHook.cs @@ -0,0 +1,64 @@ +using OtterGui.Services; +using Penumbra.Interop.PathResolving; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Interop.Hooks.Meta; + +public unsafe class GmpHook : FastHook +{ + public delegate nint Delegate(nint gmpResource, uint dividedHeadId); + + private readonly MetaState _metaState; + + private static readonly Finalizer StablePointer = new(); + + public GmpHook(HookManager hooks, MetaState metaState) + { + _metaState = metaState; + Task = hooks.CreateHook("GetGmpEntry", "E8 ?? ?? ?? ?? 48 85 C0 74 ?? 43 8D 0C", Detour, true); + } + + /// + /// This function returns a pointer to the correct block in the GMP file, if it exists - cf. . + /// To work around this, we just have a single stable ulong accessible and offset the pointer to this by the required distance, + /// which is defined by the modulo of the original ID and the block size, if we return our own custom gmp entry. + /// + private nint Detour(nint gmpResource, uint dividedHeadId) + { + nint ret; + if (_metaState.GmpCollection is { Valid: true, ModCollection.MetaCache: { } cache } + && cache.Gmp.TryGetValue(new GmpIdentifier(_metaState.UndividedGmpId), out var entry)) + { + if (entry.Entry.Enabled) + { + *StablePointer.Pointer = entry.Entry.Value; + // This function already gets the original ID divided by the block size, so we can compute the modulo with a single multiplication and addition. + // We then go backwards from our pointer because this gets added by the calling functions. + ret = (nint)(StablePointer.Pointer - (_metaState.UndividedGmpId.Id - dividedHeadId * ExpandedEqpGmpBase.BlockSize)); + } + else + { + ret = nint.Zero; + } + } + else + { + ret = Task.Result.Original(gmpResource, dividedHeadId); + } + + Penumbra.Log.Excessive($"[GetGmpFlags] Invoked on 0x{gmpResource:X} with {dividedHeadId}, returned {ret:X10}."); + return ret; + } + + /// Allocate and clean up our single stable ulong pointer. + private class Finalizer + { + public readonly ulong* Pointer = (ulong*)Marshal.AllocHGlobal(8); + + ~Finalizer() + { + Marshal.FreeHGlobal((nint)Pointer); + } + } +} diff --git a/Penumbra/Interop/Hooks/Meta/SetupVisor.cs b/Penumbra/Interop/Hooks/Meta/SetupVisor.cs index e451f118..a3e56d7f 100644 --- a/Penumbra/Interop/Hooks/Meta/SetupVisor.cs +++ b/Penumbra/Interop/Hooks/Meta/SetupVisor.cs @@ -1,10 +1,11 @@ -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using OtterGui.Services; -using Penumbra.GameData; -using Penumbra.Interop.PathResolving; - -namespace Penumbra.Interop.Hooks.Meta; - +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +using Penumbra.Collections; +using Penumbra.GameData; +using Penumbra.Interop.PathResolving; + +namespace Penumbra.Interop.Hooks.Meta; + /// /// GMP. This gets called every time when changing visor state, and it accesses the gmp file itself, /// but it only applies a changed gmp file after a redraw for some reason. @@ -26,10 +27,11 @@ public SetupVisor(HookManager hooks, CollectionResolver collectionResolver, Meta [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private byte Detour(DrawObject* drawObject, ushort modelId, byte visorState) { - var collection = _collectionResolver.IdentifyCollection(drawObject, true); - using var gmp = _metaState.ResolveGmpData(collection.ModCollection); - var ret = Task.Result.Original.Invoke(drawObject, modelId, visorState); - Penumbra.Log.Excessive($"[Setup Visor] Invoked on {(nint)drawObject:X} with {modelId}, {visorState} -> {ret}."); + _metaState.GmpCollection = _collectionResolver.IdentifyCollection(drawObject, true); + _metaState.UndividedGmpId = modelId; + var ret = Task.Result.Original.Invoke(drawObject, modelId, visorState); + Penumbra.Log.Information($"[Setup Visor] Invoked on {(nint)drawObject:X} with {modelId}, {visorState} -> {ret}."); + _metaState.GmpCollection = ResolveData.Invalid; return ret; } } diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index de7912e0..c8ebe18f 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -95,9 +95,6 @@ public DisposableContainer ResolveEqdpData(ModCollection collection, GenderRace _ => DisposableContainer.Empty, }; - public MetaList.MetaReverter ResolveGmpData(ModCollection collection) - => collection.TemporarilySetGmpFile(_characterUtility); - public MetaList.MetaReverter ResolveRspData(ModCollection collection) => collection.TemporarilySetCmpFile(_characterUtility); diff --git a/Penumbra/Interop/Services/MetaList.cs b/Penumbra/Interop/Services/MetaList.cs index e956040b..dc472b8e 100644 --- a/Penumbra/Interop/Services/MetaList.cs +++ b/Penumbra/Interop/Services/MetaList.cs @@ -102,30 +102,19 @@ public void Dispose() ResetResourceInternal(); } - public sealed class MetaReverter : IDisposable + public sealed class MetaReverter(MetaList metaList, nint data, int length) : IDisposable { public static readonly MetaReverter Disabled = new(null!) { Disposed = true }; - public readonly MetaList MetaList; - public readonly nint Data; - public readonly int Length; + public readonly MetaList MetaList = metaList; + public readonly nint Data = data; + public readonly int Length = length; public readonly bool Resetter; public bool Disposed; - public MetaReverter(MetaList metaList, nint data, int length) - { - MetaList = metaList; - Data = data; - Length = length; - } - public MetaReverter(MetaList metaList) - { - MetaList = metaList; - Data = nint.Zero; - Length = 0; - Resetter = true; - } + : this(metaList, nint.Zero, 0) + => Resetter = true; public void Dispose() { diff --git a/Penumbra/Meta/Files/EqpGmpFile.cs b/Penumbra/Meta/Files/EqpGmpFile.cs index a7470b75..c47c84ef 100644 --- a/Penumbra/Meta/Files/EqpGmpFile.cs +++ b/Penumbra/Meta/Files/EqpGmpFile.cs @@ -15,10 +15,10 @@ namespace Penumbra.Meta.Files; /// public unsafe class ExpandedEqpGmpBase : MetaBaseFile { - protected const int BlockSize = 160; - protected const int NumBlocks = 64; - protected const int EntrySize = 8; - protected const int MaxSize = BlockSize * NumBlocks * EntrySize; + public const int BlockSize = 160; + public const int NumBlocks = 64; + public const int EntrySize = 8; + public const int MaxSize = BlockSize * NumBlocks * EntrySize; public const int Count = BlockSize * NumBlocks; From ebef4ff650c19aee26306135ab125a451210bab9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 15 Jun 2024 16:17:44 +0200 Subject: [PATCH 19/28] No EST files anymore. --- Penumbra/Collections/Cache/EqpCache.cs | 11 -- Penumbra/Collections/Cache/EstCache.cs | 136 ++---------------- Penumbra/Collections/Cache/GmpCache.cs | 11 -- Penumbra/Collections/Cache/MetaCache.cs | 6 - .../Collections/ModCollection.Cache.Access.cs | 4 - Penumbra/Interop/Hooks/Meta/EstHook.cs | 49 +++++++ .../Hooks/Resources/ResolvePathHooksBase.cs | 29 ++-- Penumbra/Interop/PathResolving/MetaState.cs | 5 +- 8 files changed, 68 insertions(+), 183 deletions(-) create mode 100644 Penumbra/Interop/Hooks/Meta/EstHook.cs diff --git a/Penumbra/Collections/Cache/EqpCache.cs b/Penumbra/Collections/Cache/EqpCache.cs index b1e03943..7ba0c489 100644 --- a/Penumbra/Collections/Cache/EqpCache.cs +++ b/Penumbra/Collections/Cache/EqpCache.cs @@ -34,17 +34,6 @@ protected override void ApplyModInternal(EqpIdentifier identifier, EqpEntry entr protected override void RevertModInternal(EqpIdentifier identifier) { } - public static bool Apply(ExpandedEqpFile file, EqpIdentifier identifier, EqpEntry entry) - { - var origEntry = file[identifier.SetId]; - var mask = Eqp.Mask(identifier.Slot); - if ((origEntry & mask) == entry) - return false; - - file[identifier.SetId] = (origEntry & ~mask) | entry; - return true; - } - protected override void Dispose(bool _) => Clear(); } diff --git a/Penumbra/Collections/Cache/EstCache.cs b/Penumbra/Collections/Cache/EstCache.cs index 6a9fa909..ff94324e 100644 --- a/Penumbra/Collections/Cache/EstCache.cs +++ b/Penumbra/Collections/Cache/EstCache.cs @@ -1,5 +1,3 @@ -using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -8,144 +6,26 @@ namespace Penumbra.Collections.Cache; public sealed class EstCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private EstFile? _estFaceFile; - private EstFile? _estHairFile; - private EstFile? _estBodyFile; - private EstFile? _estHeadFile; - public override void SetFiles() - { - Manager.SetFile(_estFaceFile, MetaIndex.FaceEst); - Manager.SetFile(_estHairFile, MetaIndex.HairEst); - Manager.SetFile(_estBodyFile, MetaIndex.BodyEst); - Manager.SetFile(_estHeadFile, MetaIndex.HeadEst); - } - - public void SetFile(MetaIndex index) - { - switch (index) - { - case MetaIndex.FaceEst: - Manager.SetFile(_estFaceFile, MetaIndex.FaceEst); - break; - case MetaIndex.HairEst: - Manager.SetFile(_estHairFile, MetaIndex.HairEst); - break; - case MetaIndex.BodyEst: - Manager.SetFile(_estBodyFile, MetaIndex.BodyEst); - break; - case MetaIndex.HeadEst: - Manager.SetFile(_estHeadFile, MetaIndex.HeadEst); - break; - } - } - - public MetaList.MetaReverter TemporarilySetFiles(EstType type) - { - var (file, idx) = type switch - { - EstType.Face => (_estFaceFile, MetaIndex.FaceEst), - EstType.Hair => (_estHairFile, MetaIndex.HairEst), - EstType.Body => (_estBodyFile, MetaIndex.BodyEst), - EstType.Head => (_estHeadFile, MetaIndex.HeadEst), - _ => (null, 0), - }; - - return Manager.TemporarilySetFile(file, idx); - } - - public void ResetFiles() - { - Manager.SetFile(null, MetaIndex.FaceEst); - Manager.SetFile(null, MetaIndex.HairEst); - Manager.SetFile(null, MetaIndex.BodyEst); - Manager.SetFile(null, MetaIndex.HeadEst); - } + { } protected override void IncorporateChangesInternal() - { - if (!Manager.CharacterUtility.Ready) - return; - - foreach (var (identifier, (_, entry)) in this) - Apply(GetFile(identifier)!, identifier, entry); - Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed EST manipulations."); - } + { } public EstEntry GetEstEntry(EstIdentifier identifier) - { - var file = GetCurrentFile(identifier); - return file != null - ? file[identifier.GenderRace, identifier.SetId] + => TryGetValue(identifier, out var entry) + ? entry.Entry : EstFile.GetDefault(Manager, identifier); - } public void Reset() - { - _estFaceFile?.Reset(); - _estHairFile?.Reset(); - _estBodyFile?.Reset(); - _estHeadFile?.Reset(); - Clear(); - } + => Clear(); protected override void ApplyModInternal(EstIdentifier identifier, EstEntry entry) - { - if (GetFile(identifier) is { } file) - Apply(file, identifier, entry); - } + { } protected override void RevertModInternal(EstIdentifier identifier) - { - if (GetFile(identifier) is { } file) - Apply(file, identifier, EstFile.GetDefault(Manager, identifier.Slot, identifier.GenderRace, identifier.SetId)); - } - - public static bool Apply(EstFile file, EstIdentifier identifier, EstEntry entry) - => file.SetEntry(identifier.GenderRace, identifier.SetId, entry) switch - { - EstFile.EstEntryChange.Unchanged => false, - EstFile.EstEntryChange.Changed => true, - EstFile.EstEntryChange.Added => true, - EstFile.EstEntryChange.Removed => true, - _ => false, - }; + { } protected override void Dispose(bool _) - { - _estFaceFile?.Dispose(); - _estHairFile?.Dispose(); - _estBodyFile?.Dispose(); - _estHeadFile?.Dispose(); - _estFaceFile = null; - _estHairFile = null; - _estBodyFile = null; - _estHeadFile = null; - Clear(); - } - - private EstFile? GetCurrentFile(EstIdentifier identifier) - => identifier.Slot switch - { - EstType.Hair => _estHairFile, - EstType.Face => _estFaceFile, - EstType.Body => _estBodyFile, - EstType.Head => _estHeadFile, - _ => null, - }; - - private EstFile? GetFile(EstIdentifier identifier) - { - if (!Manager.CharacterUtility.Ready) - return null; - - return identifier.Slot switch - { - EstType.Hair => _estHairFile ??= new EstFile(Manager, EstType.Hair), - EstType.Face => _estFaceFile ??= new EstFile(Manager, EstType.Face), - EstType.Body => _estBodyFile ??= new EstFile(Manager, EstType.Body), - EstType.Head => _estHeadFile ??= new EstFile(Manager, EstType.Head), - _ => null, - }; - } + => Clear(); } diff --git a/Penumbra/Collections/Cache/GmpCache.cs b/Penumbra/Collections/Cache/GmpCache.cs index 0beb51d8..541b424d 100644 --- a/Penumbra/Collections/Cache/GmpCache.cs +++ b/Penumbra/Collections/Cache/GmpCache.cs @@ -1,6 +1,5 @@ using Penumbra.GameData.Structs; using Penumbra.Meta; -using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; namespace Penumbra.Collections.Cache; @@ -22,16 +21,6 @@ protected override void ApplyModInternal(GmpIdentifier identifier, GmpEntry entr protected override void RevertModInternal(GmpIdentifier identifier) { } - public static bool Apply(ExpandedGmpFile file, GmpIdentifier identifier, GmpEntry entry) - { - var origEntry = file[identifier.SetId]; - if (entry == origEntry) - return false; - - file[identifier.SetId] = entry; - return true; - } - protected override void Dispose(bool _) => Clear(); } diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index c8a116eb..014c7552 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -121,10 +121,8 @@ public void SetFile(MetaIndex metaIndex) switch (metaIndex) { case MetaIndex.Eqp: - Eqp.SetFiles(); break; case MetaIndex.Gmp: - Gmp.SetFiles(); break; case MetaIndex.HumanCmp: Rsp.SetFiles(); @@ -133,7 +131,6 @@ public void SetFile(MetaIndex metaIndex) case MetaIndex.HairEst: case MetaIndex.HeadEst: case MetaIndex.BodyEst: - Est.SetFile(metaIndex); break; default: Eqdp.SetFile(metaIndex); @@ -151,9 +148,6 @@ public void SetImcFiles(bool fromFullCompute) public MetaList.MetaReverter TemporarilySetCmpFile() => Rsp.TemporarilySetFile(); - public MetaList.MetaReverter TemporarilySetEstFile(EstType type) - => Est.TemporarilySetFiles(type); - /// Try to obtain a manipulated IMC file. public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) => Imc.GetFile(path, out file); diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 8701e3bb..dba971c6 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -103,8 +103,4 @@ public void SetMetaFile(CharacterUtility utility, MetaIndex idx) public MetaList.MetaReverter TemporarilySetCmpFile(CharacterUtility utility) => _cache?.Meta.TemporarilySetCmpFile() ?? utility.TemporarilyResetResource(MetaIndex.HumanCmp); - - public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstType type) - => _cache?.Meta.TemporarilySetEstFile(type) - ?? utility.TemporarilyResetResource((MetaIndex)type); } diff --git a/Penumbra/Interop/Hooks/Meta/EstHook.cs b/Penumbra/Interop/Hooks/Meta/EstHook.cs new file mode 100644 index 00000000..3fab1434 --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/EstHook.cs @@ -0,0 +1,49 @@ +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Penumbra.Interop.PathResolving; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Interop.Hooks.Meta; + +public class EstHook : FastHook +{ + public delegate EstEntry Delegate(uint id, int estType, uint genderRace); + + private readonly MetaState _metaState; + + public EstHook(HookManager hooks, MetaState metaState) + { + _metaState = metaState; + Task = hooks.CreateHook("GetEstEntry", "44 8B C9 83 EA ?? 74", Detour, true); + } + + private EstEntry Detour(uint genderRace, int estType, uint id) + { + EstEntry ret; + if (_metaState.EstCollection is { Valid: true, ModCollection.MetaCache: { } cache } + && cache.Est.TryGetValue(Convert(genderRace, estType, id), out var entry)) + ret = entry.Entry; + else + ret = Task.Result.Original(genderRace, estType, id); + + Penumbra.Log.Information($"[GetEstEntry] Invoked with {genderRace}, {estType}, {id}, returned {ret.Value}."); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static EstIdentifier Convert(uint genderRace, int estType, uint id) + { + var i = new PrimaryId((ushort)id); + var gr = (GenderRace)genderRace; + var type = estType switch + { + 1 => EstType.Face, + 2 => EstType.Hair, + 3 => EstType.Head, + 4 => EstType.Body, + _ => (EstType)0, + }; + return new EstIdentifier(i, type, gr); + } +} diff --git a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs index 9a68160b..6c9c1b7d 100644 --- a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs +++ b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs @@ -158,26 +158,26 @@ private nint ResolveMdlHuman(nint drawObject, nint pathBuffer, nint pathBufferSi private nint ResolvePapHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint unkAnimationIndex, nint animationName) { - using var est = GetEstChanges(drawObject, out var data); - return ResolvePath(data, _resolvePapPathHook.Original(drawObject, pathBuffer, pathBufferSize, unkAnimationIndex, animationName)); + _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); + return ResolvePath(_parent.MetaState.EstCollection, _resolvePapPathHook.Original(drawObject, pathBuffer, pathBufferSize, unkAnimationIndex, animationName)); } private nint ResolvePhybHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex) { - using var est = GetEstChanges(drawObject, out var data); - return ResolvePath(data, _resolvePhybPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); + return ResolvePath(_parent.MetaState.EstCollection, _resolvePhybPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); } private nint ResolveSklbHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex) { - using var est = GetEstChanges(drawObject, out var data); - return ResolvePath(data, _resolveSklbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); + return ResolvePath(_parent.MetaState.EstCollection, _resolveSklbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); } private nint ResolveSkpHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex) { - using var est = GetEstChanges(drawObject, out var data); - return ResolvePath(data, _resolveSkpPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); + return ResolvePath(_parent.MetaState.EstCollection, _resolveSkpPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); } private nint ResolveVfxHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex, nint unkOutParam) @@ -206,19 +206,6 @@ private nint ResolveVfxHuman(nint drawObject, nint pathBuffer, nint pathBufferSi return ResolvePath(drawObject, pathBuffer); } - private DisposableContainer GetEstChanges(nint drawObject, out ResolveData data) - { - data = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - if (_parent.InInternalResolve) - return DisposableContainer.Empty; - - return new DisposableContainer(data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstType.Face), - data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstType.Body), - data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstType.Hair), - data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstType.Head)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private static Hook Create(string name, HookManager hooks, nint address, Type type, T other, T human) where T : Delegate { diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index c8ebe18f..8fa09232 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -18,7 +18,7 @@ namespace Penumbra.Interop.PathResolving; // GetSlotEqpData seems to be the only function using the EQP table. // It is only called by CheckSlotsForUnload (called by UpdateModels), // SetupModelAttributes (called by UpdateModels and OnModelLoadComplete) -// and a unnamed function called by UpdateRender. +// and an unnamed function called by UpdateRender. // It seems to be enough to change the EQP entries for UpdateModels. // GetEqdpDataFor[Adults|Children|Other] seem to be the only functions using the EQDP tables. @@ -35,7 +35,7 @@ namespace Penumbra.Interop.PathResolving; // they all are called by many functions, but the most relevant seem to be Human.SetupFromCharacterData, which is only called by CharacterBase.Create, // ChangeCustomize and RspSetupCharacter, which is hooked here, as well as Character.CalculateHeight. -// GMP Entries seem to be only used by "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", which has a DrawObject as its first parameter. +// GMP Entries seem to be only used by "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", which is SetupVisor. public sealed unsafe class MetaState : IDisposable { private readonly Configuration _config; @@ -48,6 +48,7 @@ public sealed unsafe class MetaState : IDisposable public ResolveData CustomizeChangeCollection = ResolveData.Invalid; public ResolveData EqpCollection = ResolveData.Invalid; public ResolveData GmpCollection = ResolveData.Invalid; + public ResolveData EstCollection = ResolveData.Invalid; public PrimaryId UndividedGmpId = 0; private ResolveData _lastCreatedCollection = ResolveData.Invalid; From 9ecc4ab46d1b04e1bea0a6816d3e8ab06b862812 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 15 Jun 2024 20:47:08 +0200 Subject: [PATCH 20/28] Remove CMP file. --- Penumbra/Collections/Cache/MetaCache.cs | 3 - Penumbra/Collections/Cache/RspCache.cs | 64 ++--------------- .../Collections/ModCollection.Cache.Access.cs | 4 -- .../Interop/Hooks/Meta/CalculateHeight.cs | 7 +- .../Interop/Hooks/Meta/ChangeCustomize.cs | 3 +- Penumbra/Interop/Hooks/Meta/EstHook.cs | 2 +- Penumbra/Interop/Hooks/Meta/RspBustHook.cs | 66 ++++++++++++++++++ Penumbra/Interop/Hooks/Meta/RspHeightHook.cs | 68 +++++++++++++++++++ .../Interop/Hooks/Meta/RspSetupCharacter.cs | 5 +- Penumbra/Interop/Hooks/Meta/RspTailHook.cs | 68 +++++++++++++++++++ Penumbra/Interop/Hooks/Meta/SetupVisor.cs | 2 +- .../Hooks/Resources/ResolvePathHooksBase.cs | 16 +++-- Penumbra/Interop/PathResolving/MetaState.cs | 9 ++- Penumbra/Meta/Files/CmpFile.cs | 8 +++ .../UI/AdvancedWindow/Meta/RspMetaDrawer.cs | 2 +- 15 files changed, 244 insertions(+), 83 deletions(-) create mode 100644 Penumbra/Interop/Hooks/Meta/RspBustHook.cs create mode 100644 Penumbra/Interop/Hooks/Meta/RspHeightHook.cs create mode 100644 Penumbra/Interop/Hooks/Meta/RspTailHook.cs diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 014c7552..e6083351 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -145,9 +145,6 @@ public void SetImcFiles(bool fromFullCompute) public MetaList.MetaReverter? TemporarilySetEqdpFile(GenderRace genderRace, bool accessory) => Eqdp.TemporarilySetFile(genderRace, accessory); - public MetaList.MetaReverter TemporarilySetCmpFile() - => Rsp.TemporarilySetFile(); - /// Try to obtain a manipulated IMC file. public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) => Imc.GetFile(path, out file); diff --git a/Penumbra/Collections/Cache/RspCache.cs b/Penumbra/Collections/Cache/RspCache.cs index 8a5fe97d..8a983c6c 100644 --- a/Penumbra/Collections/Cache/RspCache.cs +++ b/Penumbra/Collections/Cache/RspCache.cs @@ -1,78 +1,26 @@ -using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; using Penumbra.Meta; -using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; namespace Penumbra.Collections.Cache; public sealed class RspCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private CmpFile? _cmpFile; - public override void SetFiles() - => Manager.SetFile(_cmpFile, MetaIndex.HumanCmp); + { } protected override void IncorporateChangesInternal() - { - if (GetFile() is not { } file) - return; - - foreach (var (identifier, (_, entry)) in this) - Apply(file, identifier, entry); - - Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed RSP manipulations."); - } - - public MetaList.MetaReverter TemporarilySetFile() - => Manager.TemporarilySetFile(_cmpFile, MetaIndex.HumanCmp); + { } public void Reset() - { - if (_cmpFile == null) - return; - - _cmpFile.Reset(Keys.Select(identifier => (identifier.SubRace, identifier.Attribute))); - Clear(); - } + => Clear(); protected override void ApplyModInternal(RspIdentifier identifier, RspEntry entry) - { - if (GetFile() is { } file) - Apply(file, identifier, entry); - } + { } protected override void RevertModInternal(RspIdentifier identifier) - { - if (GetFile() is { } file) - Apply(file, identifier, CmpFile.GetDefault(Manager, identifier.SubRace, identifier.Attribute)); - } + { } - public static bool Apply(CmpFile file, RspIdentifier identifier, RspEntry entry) - { - var value = file[identifier.SubRace, identifier.Attribute]; - if (value == entry) - return false; - - file[identifier.SubRace, identifier.Attribute] = entry; - return true; - } protected override void Dispose(bool _) - { - _cmpFile?.Dispose(); - _cmpFile = null; - Clear(); - } - - private CmpFile? GetFile() - { - if (_cmpFile != null) - return _cmpFile; - - if (!Manager.CharacterUtility.Ready) - return null; - - return _cmpFile = new CmpFile(Manager); - } + => Clear(); } diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index dba971c6..d93a0f53 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -99,8 +99,4 @@ public void SetMetaFile(CharacterUtility utility, MetaIndex idx) var idx = CharacterUtilityData.EqdpIdx(genderRace, accessory); return idx >= 0 ? utility.TemporarilyResetResource(idx) : null; } - - public MetaList.MetaReverter TemporarilySetCmpFile(CharacterUtility utility) - => _cache?.Meta.TemporarilySetCmpFile() - ?? utility.TemporarilyResetResource(MetaIndex.HumanCmp); } diff --git a/Penumbra/Interop/Hooks/Meta/CalculateHeight.cs b/Penumbra/Interop/Hooks/Meta/CalculateHeight.cs index 2fd87f6e..5a207491 100644 --- a/Penumbra/Interop/Hooks/Meta/CalculateHeight.cs +++ b/Penumbra/Interop/Hooks/Meta/CalculateHeight.cs @@ -1,5 +1,6 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; using OtterGui.Services; +using Penumbra.Collections; using Penumbra.Interop.PathResolving; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; @@ -22,10 +23,10 @@ public CalculateHeight(HookManager hooks, CollectionResolver collectionResolver, [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private ulong Detour(Character* character) { - var collection = _collectionResolver.IdentifyCollection((GameObject*)character, true); - using var cmp = _metaState.ResolveRspData(collection.ModCollection); - var ret = Task.Result.Original.Invoke(character); + _metaState.RspCollection = _collectionResolver.IdentifyCollection((GameObject*)character, true); + var ret = Task.Result.Original.Invoke(character); Penumbra.Log.Excessive($"[Calculate Height] Invoked on {(nint)character:X} -> {ret}."); + _metaState.RspCollection = ResolveData.Invalid; return ret; } } diff --git a/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs b/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs index 2f717491..4e0a5744 100644 --- a/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs +++ b/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs @@ -25,12 +25,13 @@ public ChangeCustomize(HookManager hooks, CollectionResolver collectionResolver, private bool Detour(Human* human, CustomizeArray* data, byte skipEquipment) { _metaState.CustomizeChangeCollection = _collectionResolver.IdentifyCollection((DrawObject*)human, true); - using var cmp = _metaState.ResolveRspData(_metaState.CustomizeChangeCollection.ModCollection); + _metaState.RspCollection = _metaState.CustomizeChangeCollection; using var decal1 = _metaState.ResolveDecal(_metaState.CustomizeChangeCollection, true); using var decal2 = _metaState.ResolveDecal(_metaState.CustomizeChangeCollection, false); var ret = Task.Result.Original.Invoke(human, data, skipEquipment); Penumbra.Log.Excessive($"[Change Customize] Invoked on {(nint)human:X} with {(nint)data:X}, {skipEquipment} -> {ret}."); _metaState.CustomizeChangeCollection = ResolveData.Invalid; + _metaState.RspCollection = ResolveData.Invalid; return ret; } } diff --git a/Penumbra/Interop/Hooks/Meta/EstHook.cs b/Penumbra/Interop/Hooks/Meta/EstHook.cs index 3fab1434..34935edb 100644 --- a/Penumbra/Interop/Hooks/Meta/EstHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EstHook.cs @@ -27,7 +27,7 @@ private EstEntry Detour(uint genderRace, int estType, uint id) else ret = Task.Result.Original(genderRace, estType, id); - Penumbra.Log.Information($"[GetEstEntry] Invoked with {genderRace}, {estType}, {id}, returned {ret.Value}."); + Penumbra.Log.Excessive($"[GetEstEntry] Invoked with {genderRace}, {estType}, {id}, returned {ret.Value}."); return ret; } diff --git a/Penumbra/Interop/Hooks/Meta/RspBustHook.cs b/Penumbra/Interop/Hooks/Meta/RspBustHook.cs new file mode 100644 index 00000000..fc1d743a --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/RspBustHook.cs @@ -0,0 +1,66 @@ +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.Interop.PathResolving; +using Penumbra.Meta; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Interop.Hooks.Meta; + +public unsafe class RspBustHook : FastHook +{ + public delegate float* Delegate(nint cmpResource, float* storage, Race race, byte gender, byte isSecondSubRace, byte bodyType, + byte bustSize); + + private readonly MetaState _metaState; + private readonly MetaFileManager _metaFileManager; + + public RspBustHook(HookManager hooks, MetaState metaState, MetaFileManager metaFileManager) + { + _metaState = metaState; + _metaFileManager = metaFileManager; + Task = hooks.CreateHook("GetRspBust", "E8 ?? ?? ?? ?? F2 0F 10 44 24 ?? 8B 44 24", Detour, true); + } + + private float* Detour(nint cmpResource, float* storage, Race race, byte gender, byte isSecondSubRace, byte bodyType, byte bustSize) + { + if (gender == 0) + { + storage[0] = 1f; + storage[1] = 1f; + storage[2] = 1f; + return storage; + } + + var ret = storage; + if (bodyType < 2 && _metaState.RspCollection is { Valid: true, ModCollection.MetaCache: { } cache }) + { + var bustScale = bustSize / 100f; + var clan = (SubRace)(((int)race - 1) * 2 + 1 + isSecondSubRace); + var ptr = CmpFile.GetDefaults(_metaFileManager, clan, RspAttribute.BustMinX); + storage[0] = GetValue(0, RspAttribute.BustMinX, RspAttribute.BustMaxX); + storage[1] = GetValue(1, RspAttribute.BustMinY, RspAttribute.BustMaxY); + storage[2] = GetValue(2, RspAttribute.BustMinZ, RspAttribute.BustMaxZ); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + float GetValue(int dimension, RspAttribute min, RspAttribute max) + { + var minValue = cache.Rsp.TryGetValue(new RspIdentifier(clan, min), out var minEntry) + ? minEntry.Entry.Value + : (ptr + dimension)->Value; + var maxValue = cache.Rsp.TryGetValue(new RspIdentifier(clan, max), out var maxEntry) + ? maxEntry.Entry.Value + : (ptr + 3 + dimension)->Value; + return (maxValue - minValue) * bustScale + minValue; + } + } + else + { + ret = Task.Result.Original(cmpResource, storage, race, gender, isSecondSubRace, bodyType, bustSize); + } + + Penumbra.Log.Information( + $"[GetRspBust] Invoked on 0x{cmpResource:X} with {race}, {(Gender)(gender + 1)}, {isSecondSubRace == 1}, {bodyType}, {bustSize}, returned {storage[0]}, {storage[1]}, {storage[2]}."); + return ret; + } +} diff --git a/Penumbra/Interop/Hooks/Meta/RspHeightHook.cs b/Penumbra/Interop/Hooks/Meta/RspHeightHook.cs new file mode 100644 index 00000000..883f5fc6 --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/RspHeightHook.cs @@ -0,0 +1,68 @@ +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.Interop.PathResolving; +using Penumbra.Meta; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Interop.Hooks.Meta; + +public class RspHeightHook : FastHook +{ + public delegate float Delegate(nint cmpResource, Race clan, byte gender, byte isSecondSubRace, byte bodyType, byte height); + + private readonly MetaState _metaState; + private readonly MetaFileManager _metaFileManager; + + public RspHeightHook(HookManager hooks, MetaState metaState, MetaFileManager metaFileManager) + { + _metaState = metaState; + _metaFileManager = metaFileManager; + Task = hooks.CreateHook("GetRspHeight", "E8 ?? ?? ?? ?? 48 8B 8E ?? ?? ?? ?? 44 8B CF", Detour, true); + } + + private unsafe float Detour(nint cmpResource, Race race, byte gender, byte isSecondSubRace, byte bodyType, byte height) + { + float scale; + if (bodyType < 2 && _metaState.RspCollection is { Valid: true, ModCollection.MetaCache: { } cache }) + { + var clan = (SubRace)(((int)race - 1) * 2 + 1 + isSecondSubRace); + var (minIdent, maxIdent) = gender == 0 + ? (new RspIdentifier(clan, RspAttribute.MaleMinSize), new RspIdentifier(clan, RspAttribute.MaleMaxSize)) + : (new RspIdentifier(clan, RspAttribute.FemaleMinSize), new RspIdentifier(clan, RspAttribute.FemaleMaxSize)); + + float minEntry, maxEntry; + if (cache.Rsp.TryGetValue(minIdent, out var min)) + { + minEntry = min.Entry.Value; + maxEntry = cache.Rsp.TryGetValue(maxIdent, out var max) + ? max.Entry.Value + : CmpFile.GetDefault(_metaFileManager, minIdent.SubRace, maxIdent.Attribute).Value; + } + else + { + var ptr = CmpFile.GetDefaults(_metaFileManager, minIdent.SubRace, minIdent.Attribute); + if (cache.Rsp.TryGetValue(maxIdent, out var max)) + { + minEntry = ptr->Value; + maxEntry = max.Entry.Value; + } + else + { + minEntry = ptr[0].Value; + maxEntry = ptr[1].Value; + } + } + + scale = (maxEntry - minEntry) * height / 100f + minEntry; + } + else + { + scale = Task.Result.Original(cmpResource, race, gender, isSecondSubRace, bodyType, height); + } + + Penumbra.Log.Excessive( + $"[GetRspHeight] Invoked on 0x{cmpResource:X} with {race}, {(Gender)(gender + 1)}, {isSecondSubRace == 1}, {bodyType}, {height}, returned {scale}."); + return scale; + } +} diff --git a/Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs b/Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs index 8f8f1d78..831c99bb 100644 --- a/Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs +++ b/Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs @@ -1,5 +1,6 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Services; +using Penumbra.Collections; using Penumbra.GameData; using Penumbra.Interop.PathResolving; @@ -30,8 +31,8 @@ private void Detour(DrawObject* drawObject, nint unk2, float unk3, nint unk4, by return; } - var collection = _collectionResolver.IdentifyCollection(drawObject, true); - using var cmp = _metaState.ResolveRspData(collection.ModCollection); + _metaState.RspCollection = _collectionResolver.IdentifyCollection(drawObject, true); Task.Result.Original.Invoke(drawObject, unk2, unk3, unk4, unk5); + _metaState.RspCollection = ResolveData.Invalid; } } diff --git a/Penumbra/Interop/Hooks/Meta/RspTailHook.cs b/Penumbra/Interop/Hooks/Meta/RspTailHook.cs new file mode 100644 index 00000000..c81e97fa --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/RspTailHook.cs @@ -0,0 +1,68 @@ +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.Interop.PathResolving; +using Penumbra.Meta; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Interop.Hooks.Meta; + +public class RspTailHook : FastHook +{ + public delegate float Delegate(nint cmpResource, Race clan, byte gender, byte isSecondSubRace, byte bodyType, byte height); + + private readonly MetaState _metaState; + private readonly MetaFileManager _metaFileManager; + + public RspTailHook(HookManager hooks, MetaState metaState, MetaFileManager metaFileManager) + { + _metaState = metaState; + _metaFileManager = metaFileManager; + Task = hooks.CreateHook("GetRspTail", "E8 ?? ?? ?? ?? 0F 28 F0 48 8B 05", Detour, true); + } + + private unsafe float Detour(nint cmpResource, Race race, byte gender, byte isSecondSubRace, byte bodyType, byte tailLength) + { + float scale; + if (bodyType < 2 && _metaState.RspCollection is { Valid: true, ModCollection.MetaCache: { } cache }) + { + var clan = (SubRace)(((int)race - 1) * 2 + 1 + isSecondSubRace); + var (minIdent, maxIdent) = gender == 0 + ? (new RspIdentifier(clan, RspAttribute.MaleMinTail), new RspIdentifier(clan, RspAttribute.MaleMaxTail)) + : (new RspIdentifier(clan, RspAttribute.FemaleMinTail), new RspIdentifier(clan, RspAttribute.FemaleMaxTail)); + + float minEntry, maxEntry; + if (cache.Rsp.TryGetValue(minIdent, out var min)) + { + minEntry = min.Entry.Value; + maxEntry = cache.Rsp.TryGetValue(maxIdent, out var max) + ? max.Entry.Value + : CmpFile.GetDefault(_metaFileManager, minIdent.SubRace, maxIdent.Attribute).Value; + } + else + { + var ptr = CmpFile.GetDefaults(_metaFileManager, minIdent.SubRace, minIdent.Attribute); + if (cache.Rsp.TryGetValue(maxIdent, out var max)) + { + minEntry = ptr->Value; + maxEntry = max.Entry.Value; + } + else + { + minEntry = ptr[0].Value; + maxEntry = ptr[1].Value; + } + } + + scale = (maxEntry - minEntry) * tailLength / 100f + minEntry; + } + else + { + scale = Task.Result.Original(cmpResource, race, gender, isSecondSubRace, bodyType, tailLength); + } + + Penumbra.Log.Excessive( + $"[GetRspTail] Invoked on 0x{cmpResource:X} with {race}, {(Gender)(gender + 1)}, {isSecondSubRace == 1}, {bodyType}, {tailLength}, returned {scale}."); + return scale; + } +} diff --git a/Penumbra/Interop/Hooks/Meta/SetupVisor.cs b/Penumbra/Interop/Hooks/Meta/SetupVisor.cs index a3e56d7f..8479968f 100644 --- a/Penumbra/Interop/Hooks/Meta/SetupVisor.cs +++ b/Penumbra/Interop/Hooks/Meta/SetupVisor.cs @@ -30,7 +30,7 @@ private byte Detour(DrawObject* drawObject, ushort modelId, byte visorState) _metaState.GmpCollection = _collectionResolver.IdentifyCollection(drawObject, true); _metaState.UndividedGmpId = modelId; var ret = Task.Result.Original.Invoke(drawObject, modelId, visorState); - Penumbra.Log.Information($"[Setup Visor] Invoked on {(nint)drawObject:X} with {modelId}, {visorState} -> {ret}."); + Penumbra.Log.Excessive($"[Setup Visor] Invoked on {(nint)drawObject:X} with {modelId}, {visorState} -> {ret}."); _metaState.GmpCollection = ResolveData.Invalid; return ret; } diff --git a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs index 6c9c1b7d..17cfa3f6 100644 --- a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs +++ b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs @@ -159,25 +159,33 @@ private nint ResolveMdlHuman(nint drawObject, nint pathBuffer, nint pathBufferSi private nint ResolvePapHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint unkAnimationIndex, nint animationName) { _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - return ResolvePath(_parent.MetaState.EstCollection, _resolvePapPathHook.Original(drawObject, pathBuffer, pathBufferSize, unkAnimationIndex, animationName)); + var ret = ResolvePath(_parent.MetaState.EstCollection, _resolvePapPathHook.Original(drawObject, pathBuffer, pathBufferSize, unkAnimationIndex, animationName)); + _parent.MetaState.EstCollection = ResolveData.Invalid; + return ret; } private nint ResolvePhybHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex) { _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - return ResolvePath(_parent.MetaState.EstCollection, _resolvePhybPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + var ret = ResolvePath(_parent.MetaState.EstCollection, _resolvePhybPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + _parent.MetaState.EstCollection = ResolveData.Invalid; + return ret; } private nint ResolveSklbHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex) { _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - return ResolvePath(_parent.MetaState.EstCollection, _resolveSklbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + var ret = ResolvePath(_parent.MetaState.EstCollection, _resolveSklbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + _parent.MetaState.EstCollection = ResolveData.Invalid; + return ret; } private nint ResolveSkpHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex) { _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - return ResolvePath(_parent.MetaState.EstCollection, _resolveSkpPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + var ret = ResolvePath(_parent.MetaState.EstCollection, _resolveSkpPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + _parent.MetaState.EstCollection = ResolveData.Invalid; + return ret; } private nint ResolveVfxHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex, nint unkOutParam) diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index 8fa09232..3da94ce3 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -49,6 +49,7 @@ public sealed unsafe class MetaState : IDisposable public ResolveData EqpCollection = ResolveData.Invalid; public ResolveData GmpCollection = ResolveData.Invalid; public ResolveData EstCollection = ResolveData.Invalid; + public ResolveData RspCollection = ResolveData.Invalid; public PrimaryId UndividedGmpId = 0; private ResolveData _lastCreatedCollection = ResolveData.Invalid; @@ -96,9 +97,6 @@ public DisposableContainer ResolveEqdpData(ModCollection collection, GenderRace _ => DisposableContainer.Empty, }; - public MetaList.MetaReverter ResolveRspData(ModCollection collection) - => collection.TemporarilySetCmpFile(_characterUtility); - public DecalReverter ResolveDecal(ResolveData resolve, bool which) => new(_config, _characterUtility, _resources, resolve, which); @@ -132,9 +130,9 @@ private void OnCreatingCharacterBase(ModelCharaId* modelCharaId, CustomizeArray* var decal = new DecalReverter(_config, _characterUtility, _resources, _lastCreatedCollection, UsesDecal(*(uint*)modelCharaId, (nint)customize)); - var cmp = _lastCreatedCollection.ModCollection.TemporarilySetCmpFile(_characterUtility); + RspCollection = _lastCreatedCollection; _characterBaseCreateMetaChanges.Dispose(); // Should always be empty. - _characterBaseCreateMetaChanges = new DisposableContainer(decal, cmp); + _characterBaseCreateMetaChanges = new DisposableContainer(decal); } private void OnCharacterBaseCreated(ModelCharaId _1, CustomizeArray* _2, CharacterArmor* _3, CharacterBase* drawObject) @@ -144,6 +142,7 @@ private void OnCharacterBaseCreated(ModelCharaId _1, CustomizeArray* _2, Charact if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero && drawObject != null) _communicator.CreatedCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject, _lastCreatedCollection.ModCollection, (nint)drawObject); + RspCollection = ResolveData.Invalid; _lastCreatedCollection = ResolveData.Invalid; } diff --git a/Penumbra/Meta/Files/CmpFile.cs b/Penumbra/Meta/Files/CmpFile.cs index 96cda496..8ca7cb80 100644 --- a/Penumbra/Meta/Files/CmpFile.cs +++ b/Penumbra/Meta/Files/CmpFile.cs @@ -46,6 +46,14 @@ public static RspEntry GetDefault(MetaFileManager manager, SubRace subRace, RspA return *(RspEntry*)(data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4); } + public static RspEntry* GetDefaults(MetaFileManager manager, SubRace subRace, RspAttribute attribute) + { + { + var data = (byte*)manager.CharacterUtility.DefaultResource(InternalIndex).Address; + return (RspEntry*)(data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4); + } + } + private static int ToRspIndex(SubRace subRace) => subRace switch { diff --git a/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs index 2b7904ce..be02e321 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs @@ -85,7 +85,7 @@ private static bool DrawEntry(RspEntry defaultEntry, ref RspEntry entry, bool di { using var dis = ImRaii.Disabled(disabled); ImGui.TableNextColumn(); - var ret = DragInput("##rspValue"u8, [], ImUtf8.GlobalScale * 150, defaultEntry.Value, entry.Value, out var newValue, + var ret = DragInput("##rspValue"u8, [], ImUtf8.GlobalScale * 150, entry.Value, defaultEntry.Value, out var newValue, RspEntry.MinValue, RspEntry.MaxValue, 0.001f, !disabled); if (ret) entry = new RspEntry(newValue); From 600fd2ecd36faa72292a7a8e2e8ce8982f6c708e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 16 Jun 2024 01:02:42 +0200 Subject: [PATCH 21/28] Get rid off EQDP files --- Penumbra/Collections/Cache/EqdpCache.cs | 115 +++++------------- Penumbra/Collections/Cache/MetaCache.cs | 38 +----- .../Collections/ModCollection.Cache.Access.cs | 36 ------ .../Interop/Hooks/Meta/CalculateHeight.cs | 5 +- .../Interop/Hooks/Meta/ChangeCustomize.cs | 23 ++-- .../Interop/Hooks/Meta/EqdpAccessoryHook.cs | 33 +++++ Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs | 32 +++++ Penumbra/Interop/Hooks/Meta/EqpHook.cs | 2 +- Penumbra/Interop/Hooks/Meta/EstHook.cs | 2 +- Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs | 5 +- .../Interop/Hooks/Meta/GetEqpIndirect2.cs | 23 ++-- Penumbra/Interop/Hooks/Meta/GmpHook.cs | 6 +- .../Interop/Hooks/Meta/ModelLoadComplete.cs | 9 +- Penumbra/Interop/Hooks/Meta/RspBustHook.cs | 2 +- Penumbra/Interop/Hooks/Meta/RspHeightHook.cs | 3 +- .../Interop/Hooks/Meta/RspSetupCharacter.cs | 5 +- Penumbra/Interop/Hooks/Meta/RspTailHook.cs | 2 +- Penumbra/Interop/Hooks/Meta/SetupVisor.cs | 6 +- Penumbra/Interop/Hooks/Meta/UpdateModel.cs | 9 +- .../Hooks/Resources/ResolvePathHooksBase.cs | 45 ++++--- Penumbra/Interop/PathResolving/MetaState.cs | 33 ++--- Penumbra/Interop/Services/MetaList.cs | 2 +- Penumbra/Penumbra.cs | 2 - 23 files changed, 192 insertions(+), 246 deletions(-) create mode 100644 Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs create mode 100644 Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs diff --git a/Penumbra/Collections/Cache/EqdpCache.cs b/Penumbra/Collections/Cache/EqdpCache.cs index c63403ae..5bfe2dbf 100644 --- a/Penumbra/Collections/Cache/EqdpCache.cs +++ b/Penumbra/Collections/Cache/EqdpCache.cs @@ -1,8 +1,5 @@ -using OtterGui; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -11,108 +8,60 @@ namespace Penumbra.Collections.Cache; public sealed class EqdpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private readonly ExpandedEqdpFile?[] _eqdpFiles = new ExpandedEqdpFile[CharacterUtilityData.EqdpIndices.Length]; // TODO: female Hrothgar + private readonly Dictionary<(PrimaryId Id, GenderRace GenderRace, bool Accessory), EqdpEntry> _fullEntries = []; public override void SetFiles() - { - for (var i = 0; i < CharacterUtilityData.EqdpIndices.Length; ++i) - Manager.SetFile(_eqdpFiles[i], CharacterUtilityData.EqdpIndices[i]); - } + { } - public void SetFile(MetaIndex index) - { - var i = CharacterUtilityData.EqdpIndices.IndexOf(index); - if (i != -1) - Manager.SetFile(_eqdpFiles[i], index); - } - - public void ResetFiles() - { - foreach (var t in CharacterUtilityData.EqdpIndices) - Manager.SetFile(null, t); - } + public bool TryGetFullEntry(PrimaryId id, GenderRace genderRace, bool accessory, out EqdpEntry entry) + => _fullEntries.TryGetValue((id, genderRace, accessory), out entry); protected override void IncorporateChangesInternal() - { - foreach (var (identifier, (_, entry)) in this) - Apply(GetFile(identifier)!, identifier, entry); - - Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed EQDP manipulations."); - } - - public ExpandedEqdpFile? EqdpFile(GenderRace race, bool accessory) - => _eqdpFiles[Array.IndexOf(CharacterUtilityData.EqdpIndices, CharacterUtilityData.EqdpIdx(race, accessory))]; // TODO: female Hrothgar - - public MetaList.MetaReverter? TemporarilySetFile(GenderRace genderRace, bool accessory) - { - var idx = CharacterUtilityData.EqdpIdx(genderRace, accessory); - if (idx < 0) - { - Penumbra.Log.Warning($"Invalid Gender, Race or Accessory for EQDP file {genderRace}, {accessory}."); - return null; - } - - var i = CharacterUtilityData.EqdpIndices.IndexOf(idx); - if (i < 0) - { - Penumbra.Log.Warning($"Invalid Gender, Race or Accessory for EQDP file {genderRace}, {accessory}."); - return null; - } - - return Manager.TemporarilySetFile(_eqdpFiles[i], idx); - } + { } public void Reset() { - foreach (var file in _eqdpFiles.OfType()) - { - var relevant = CharacterUtility.RelevantIndices[file.Index.Value]; - file.Reset(Keys.Where(m => m.FileIndex() == relevant).Select(m => m.SetId)); - } - Clear(); + _fullEntries.Clear(); } protected override void ApplyModInternal(EqdpIdentifier identifier, EqdpEntry entry) { - if (GetFile(identifier) is { } file) - Apply(file, identifier, entry); + var tuple = (identifier.SetId, identifier.GenderRace, identifier.Slot.IsAccessory()); + var mask = Eqdp.Mask(identifier.Slot); + if (!_fullEntries.TryGetValue(tuple, out var currentEntry)) + currentEntry = ExpandedEqdpFile.GetDefault(Manager, identifier); + + _fullEntries[tuple] = (currentEntry & ~mask) | (entry & mask); } protected override void RevertModInternal(EqdpIdentifier identifier) { - if (GetFile(identifier) is { } file) - Apply(file, identifier, ExpandedEqdpFile.GetDefault(Manager, identifier)); - } + var tuple = (identifier.SetId, identifier.GenderRace, identifier.Slot.IsAccessory()); + var mask = Eqdp.Mask(identifier.Slot); - public static bool Apply(ExpandedEqdpFile file, EqdpIdentifier identifier, EqdpEntry entry) - { - var origEntry = file[identifier.SetId]; - var mask = Eqdp.Mask(identifier.Slot); - if ((origEntry & mask) == entry) - return false; - - file[identifier.SetId] = (origEntry & ~mask) | entry; - return true; - } - - protected override void Dispose(bool _) - { - for (var i = 0; i < _eqdpFiles.Length; ++i) + if (_fullEntries.TryGetValue(tuple, out var currentEntry)) { - _eqdpFiles[i]?.Dispose(); - _eqdpFiles[i] = null; + var def = ExpandedEqdpFile.GetDefault(Manager, identifier); + var newEntry = (currentEntry & ~mask) | (def & mask); + if (currentEntry != newEntry) + { + _fullEntries[tuple] = newEntry; + } + else + { + var slots = tuple.Item3 ? EquipSlotExtensions.AccessorySlots : EquipSlotExtensions.EquipmentSlots; + if (slots.All(s => !ContainsKey(identifier with { Slot = s }))) + _fullEntries.Remove(tuple); + else + _fullEntries[tuple] = newEntry; + } } - - Clear(); } - private ExpandedEqdpFile? GetFile(EqdpIdentifier identifier) + protected override void Dispose(bool _) { - if (!Manager.CharacterUtility.Ready) - return null; - - var index = Array.IndexOf(CharacterUtilityData.EqdpIndices, identifier.FileIndex()); - return _eqdpFiles[index] ??= new ExpandedEqdpFile(Manager, identifier.GenderRace, identifier.Slot.IsAccessory()); + Clear(); + _fullEntries.Clear(); } } diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index e6083351..614a5a2c 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -1,7 +1,5 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; using Penumbra.Meta; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Editor; @@ -115,48 +113,18 @@ public bool ApplyMod(IMod mod, IMetaIdentifier identifier, object entry) ~MetaCache() => Dispose(); - /// Set a single file. - public void SetFile(MetaIndex metaIndex) - { - switch (metaIndex) - { - case MetaIndex.Eqp: - break; - case MetaIndex.Gmp: - break; - case MetaIndex.HumanCmp: - Rsp.SetFiles(); - break; - case MetaIndex.FaceEst: - case MetaIndex.HairEst: - case MetaIndex.HeadEst: - case MetaIndex.BodyEst: - break; - default: - Eqdp.SetFile(metaIndex); - break; - } - } - /// Set the currently relevant IMC files for the collection cache. public void SetImcFiles(bool fromFullCompute) => Imc.SetFiles(fromFullCompute); - public MetaList.MetaReverter? TemporarilySetEqdpFile(GenderRace genderRace, bool accessory) - => Eqdp.TemporarilySetFile(genderRace, accessory); - /// Try to obtain a manipulated IMC file. public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) => Imc.GetFile(path, out file); internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, PrimaryId primaryId) - { - var eqdpFile = Eqdp.EqdpFile(race, accessory); - if (eqdpFile != null) - return primaryId.Id < eqdpFile.Count ? eqdpFile[primaryId] : default; - - return Meta.Files.ExpandedEqdpFile.GetDefault(manager, race, accessory, primaryId); - } + => Eqdp.TryGetFullEntry(primaryId, race, accessory, out var entry) + ? entry + : Meta.Files.ExpandedEqdpFile.GetDefault(manager, race, accessory, primaryId); internal EstEntry GetEstEntry(EstType type, GenderRace genderRace, PrimaryId primaryId) => Est.GetEstEntry(new EstIdentifier(primaryId, type, genderRace)); diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index d93a0f53..81751128 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -1,14 +1,9 @@ using OtterGui.Classes; -using Penumbra.GameData.Enums; using Penumbra.Mods; -using Penumbra.Interop.Structs; using Penumbra.Meta.Files; -using Penumbra.Meta.Manipulations; using Penumbra.String.Classes; using Penumbra.Collections.Cache; -using Penumbra.Interop.Services; using Penumbra.Mods.Editor; -using Penumbra.GameData.Structs; namespace Penumbra.Collections; @@ -68,35 +63,4 @@ internal IEnumerable> AllConflicts internal SingleArray Conflicts(Mod mod) => _cache?.Conflicts(mod) ?? new SingleArray(); - - public void SetFiles(CharacterUtility utility) - { - if (_cache == null) - { - utility.ResetAll(); - } - else - { - _cache.Meta.SetFiles(); - Penumbra.Log.Debug($"Set CharacterUtility resources for collection {Identifier}."); - } - } - - public void SetMetaFile(CharacterUtility utility, MetaIndex idx) - { - if (_cache == null) - utility.ResetResource(idx); - else - _cache.Meta.SetFile(idx); - } - - // Used for short periods of changed files. - public MetaList.MetaReverter? TemporarilySetEqdpFile(CharacterUtility utility, GenderRace genderRace, bool accessory) - { - if (_cache != null) - return _cache?.Meta.TemporarilySetEqdpFile(genderRace, accessory); - - var idx = CharacterUtilityData.EqdpIdx(genderRace, accessory); - return idx >= 0 ? utility.TemporarilyResetResource(idx) : null; - } } diff --git a/Penumbra/Interop/Hooks/Meta/CalculateHeight.cs b/Penumbra/Interop/Hooks/Meta/CalculateHeight.cs index 5a207491..7936b831 100644 --- a/Penumbra/Interop/Hooks/Meta/CalculateHeight.cs +++ b/Penumbra/Interop/Hooks/Meta/CalculateHeight.cs @@ -23,10 +23,11 @@ public CalculateHeight(HookManager hooks, CollectionResolver collectionResolver, [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private ulong Detour(Character* character) { - _metaState.RspCollection = _collectionResolver.IdentifyCollection((GameObject*)character, true); + var collection = _collectionResolver.IdentifyCollection((GameObject*)character, true); + _metaState.RspCollection.Push(collection); var ret = Task.Result.Original.Invoke(character); Penumbra.Log.Excessive($"[Calculate Height] Invoked on {(nint)character:X} -> {ret}."); - _metaState.RspCollection = ResolveData.Invalid; + _metaState.RspCollection.Pop(); return ret; } } diff --git a/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs b/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs index 4e0a5744..f589cf4e 100644 --- a/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs +++ b/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs @@ -1,12 +1,12 @@ -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Services; using Penumbra.Collections; -using Penumbra.GameData; -using Penumbra.GameData.Structs; -using Penumbra.Interop.PathResolving; - -namespace Penumbra.Interop.Hooks.Meta; - +using Penumbra.GameData; +using Penumbra.GameData.Structs; +using Penumbra.Interop.PathResolving; + +namespace Penumbra.Interop.Hooks.Meta; + public sealed unsafe class ChangeCustomize : FastHook { private readonly CollectionResolver _collectionResolver; @@ -24,14 +24,15 @@ public ChangeCustomize(HookManager hooks, CollectionResolver collectionResolver, [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private bool Detour(Human* human, CustomizeArray* data, byte skipEquipment) { - _metaState.CustomizeChangeCollection = _collectionResolver.IdentifyCollection((DrawObject*)human, true); - _metaState.RspCollection = _metaState.CustomizeChangeCollection; + var collection = _collectionResolver.IdentifyCollection((DrawObject*)human, true); + _metaState.CustomizeChangeCollection = collection; + _metaState.RspCollection.Push(collection); using var decal1 = _metaState.ResolveDecal(_metaState.CustomizeChangeCollection, true); using var decal2 = _metaState.ResolveDecal(_metaState.CustomizeChangeCollection, false); var ret = Task.Result.Original.Invoke(human, data, skipEquipment); Penumbra.Log.Excessive($"[Change Customize] Invoked on {(nint)human:X} with {(nint)data:X}, {skipEquipment} -> {ret}."); _metaState.CustomizeChangeCollection = ResolveData.Invalid; - _metaState.RspCollection = ResolveData.Invalid; + _metaState.RspCollection.Pop(); return ret; } -} +} diff --git a/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs b/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs new file mode 100644 index 00000000..475e1eb7 --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs @@ -0,0 +1,33 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Penumbra.Interop.PathResolving; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Interop.Hooks.Meta; + +public unsafe class EqdpAccessoryHook : FastHook +{ + public delegate void Delegate(CharacterUtility* utility, EqdpEntry* entry, uint id, uint raceCode); + + private readonly MetaState _metaState; + + public EqdpAccessoryHook(HookManager hooks, MetaState metaState) + { + _metaState = metaState; + Task = hooks.CreateHook("GetEqdpAccessoryEntry", "E8 ?? ?? ?? ?? 41 BF ?? ?? ?? ?? 83 FB", Detour, true); + } + + private void Detour(CharacterUtility* utility, EqdpEntry* entry, uint setId, uint raceCode) + { + if (_metaState.EqdpCollection.TryPeek(out var collection) + && collection is { Valid: true, ModCollection.MetaCache: { } cache } + && cache.Eqdp.TryGetFullEntry(new PrimaryId((ushort)setId), (GenderRace)raceCode, true, out var newEntry)) + *entry = newEntry; + else + Task.Result.Original(utility, entry, setId, raceCode); + Penumbra.Log.Information( + $"[GetEqdpAccessoryEntry] Invoked on 0x{(ulong)utility:X} with {setId}, {(GenderRace)raceCode}, returned {(ushort)*entry:B10}."); + } +} diff --git a/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs b/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs new file mode 100644 index 00000000..9b911710 --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs @@ -0,0 +1,32 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Penumbra.Interop.PathResolving; + +namespace Penumbra.Interop.Hooks.Meta; + +public unsafe class EqdpEquipHook : FastHook +{ + public delegate void Delegate(CharacterUtility* utility, EqdpEntry* entry, uint id, uint raceCode); + + private readonly MetaState _metaState; + + public EqdpEquipHook(HookManager hooks, MetaState metaState) + { + _metaState = metaState; + Task = hooks.CreateHook("GetEqdpEquipEntry", "E8 ?? ?? ?? ?? 85 DB 75 ?? F6 45", Detour, true); + } + + private void Detour(CharacterUtility* utility, EqdpEntry* entry, uint setId, uint raceCode) + { + if (_metaState.EqdpCollection.TryPeek(out var collection) + && collection is { Valid: true, ModCollection.MetaCache: { } cache } + && cache.Eqdp.TryGetFullEntry(new PrimaryId((ushort)setId), (GenderRace)raceCode, false, out var newEntry)) + *entry = newEntry; + else + Task.Result.Original(utility, entry, setId, raceCode); + Penumbra.Log.Information( + $"[GetEqdpEquipEntry] Invoked on 0x{(ulong)utility:X} with {setId}, {(GenderRace)raceCode}, returned {(ushort)*entry:B10}."); + } +} diff --git a/Penumbra/Interop/Hooks/Meta/EqpHook.cs b/Penumbra/Interop/Hooks/Meta/EqpHook.cs index 448605c1..7107e26b 100644 --- a/Penumbra/Interop/Hooks/Meta/EqpHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EqpHook.cs @@ -19,7 +19,7 @@ public EqpHook(HookManager hooks, MetaState metaState) private void Detour(CharacterUtility* utility, EqpEntry* flags, CharacterArmor* armor) { - if (_metaState.EqpCollection is { Valid: true, ModCollection.MetaCache: { } cache }) + if (_metaState.EqpCollection.TryPeek(out var collection) && collection is { Valid: true, ModCollection.MetaCache: { } cache }) { *flags = cache.Eqp.GetValues(armor); *flags = cache.GlobalEqp.Apply(*flags, armor); diff --git a/Penumbra/Interop/Hooks/Meta/EstHook.cs b/Penumbra/Interop/Hooks/Meta/EstHook.cs index 34935edb..23931182 100644 --- a/Penumbra/Interop/Hooks/Meta/EstHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EstHook.cs @@ -21,7 +21,7 @@ public EstHook(HookManager hooks, MetaState metaState) private EstEntry Detour(uint genderRace, int estType, uint id) { EstEntry ret; - if (_metaState.EstCollection is { Valid: true, ModCollection.MetaCache: { } cache } + if (_metaState.EstCollection.TryPeek(out var collection) && collection is { Valid: true, ModCollection.MetaCache: { } cache } && cache.Est.TryGetValue(Convert(genderRace, estType, id), out var entry)) ret = entry.Entry; else diff --git a/Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs b/Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs index beae6acc..a10b511a 100644 --- a/Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs +++ b/Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs @@ -29,8 +29,9 @@ private void Detour(DrawObject* drawObject) return; Penumbra.Log.Excessive($"[Get EQP Indirect] Invoked on {(nint)drawObject:X}."); - _metaState.EqpCollection = _collectionResolver.IdentifyCollection(drawObject, true); + var collection = _collectionResolver.IdentifyCollection(drawObject, true); + _metaState.EqpCollection.Push(collection); Task.Result.Original(drawObject); - _metaState.EqpCollection = ResolveData.Invalid; + _metaState.EqpCollection.Pop(); } } diff --git a/Penumbra/Interop/Hooks/Meta/GetEqpIndirect2.cs b/Penumbra/Interop/Hooks/Meta/GetEqpIndirect2.cs index 89aaa9b0..30ec2597 100644 --- a/Penumbra/Interop/Hooks/Meta/GetEqpIndirect2.cs +++ b/Penumbra/Interop/Hooks/Meta/GetEqpIndirect2.cs @@ -1,11 +1,11 @@ -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using OtterGui.Services; -using Penumbra.Collections; -using Penumbra.GameData; -using Penumbra.Interop.PathResolving; - -namespace Penumbra.Interop.Hooks.Meta; - +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +using Penumbra.Collections; +using Penumbra.GameData; +using Penumbra.Interop.PathResolving; + +namespace Penumbra.Interop.Hooks.Meta; + public sealed unsafe class GetEqpIndirect2 : FastHook { private readonly CollectionResolver _collectionResolver; @@ -29,8 +29,9 @@ private void Detour(DrawObject* drawObject) return; Penumbra.Log.Excessive($"[Get EQP Indirect 2] Invoked on {(nint)drawObject:X}."); - _metaState.EqpCollection = _collectionResolver.IdentifyCollection(drawObject, true); + var collection = _collectionResolver.IdentifyCollection(drawObject, true); + _metaState.EqpCollection.Push(collection); Task.Result.Original(drawObject); - _metaState.EqpCollection = ResolveData.Invalid; + _metaState.EqpCollection.Pop(); } -} +} diff --git a/Penumbra/Interop/Hooks/Meta/GmpHook.cs b/Penumbra/Interop/Hooks/Meta/GmpHook.cs index 60966fb7..256d8702 100644 --- a/Penumbra/Interop/Hooks/Meta/GmpHook.cs +++ b/Penumbra/Interop/Hooks/Meta/GmpHook.cs @@ -27,15 +27,15 @@ public GmpHook(HookManager hooks, MetaState metaState) private nint Detour(nint gmpResource, uint dividedHeadId) { nint ret; - if (_metaState.GmpCollection is { Valid: true, ModCollection.MetaCache: { } cache } - && cache.Gmp.TryGetValue(new GmpIdentifier(_metaState.UndividedGmpId), out var entry)) + if (_metaState.GmpCollection.TryPeek(out var collection) && collection.Collection is { Valid: true, ModCollection.MetaCache: { } cache } + && cache.Gmp.TryGetValue(new GmpIdentifier(collection.Id), out var entry)) { if (entry.Entry.Enabled) { *StablePointer.Pointer = entry.Entry.Value; // This function already gets the original ID divided by the block size, so we can compute the modulo with a single multiplication and addition. // We then go backwards from our pointer because this gets added by the calling functions. - ret = (nint)(StablePointer.Pointer - (_metaState.UndividedGmpId.Id - dividedHeadId * ExpandedEqpGmpBase.BlockSize)); + ret = (nint)(StablePointer.Pointer - (collection.Id.Id - dividedHeadId * ExpandedEqpGmpBase.BlockSize)); } else { diff --git a/Penumbra/Interop/Hooks/Meta/ModelLoadComplete.cs b/Penumbra/Interop/Hooks/Meta/ModelLoadComplete.cs index 10c12594..2c17362d 100644 --- a/Penumbra/Interop/Hooks/Meta/ModelLoadComplete.cs +++ b/Penumbra/Interop/Hooks/Meta/ModelLoadComplete.cs @@ -23,10 +23,11 @@ public ModelLoadComplete(HookManager hooks, CollectionResolver collectionResolve private void Detour(DrawObject* drawObject) { Penumbra.Log.Excessive($"[Model Load Complete] Invoked on {(nint)drawObject:X}."); - var collection = _collectionResolver.IdentifyCollection(drawObject, true); - using var eqdp = _metaState.ResolveEqdpData(collection.ModCollection, MetaState.GetDrawObjectGenderRace((nint)drawObject), true, true); - _metaState.EqpCollection = collection; + var collection = _collectionResolver.IdentifyCollection(drawObject, true); + _metaState.EqpCollection.Push(collection); + _metaState.EqdpCollection.Push(collection); Task.Result.Original(drawObject); - _metaState.EqpCollection = ResolveData.Invalid; + _metaState.EqpCollection.Pop(); + _metaState.EqdpCollection.Pop(); } } diff --git a/Penumbra/Interop/Hooks/Meta/RspBustHook.cs b/Penumbra/Interop/Hooks/Meta/RspBustHook.cs index fc1d743a..13a5410d 100644 --- a/Penumbra/Interop/Hooks/Meta/RspBustHook.cs +++ b/Penumbra/Interop/Hooks/Meta/RspBustHook.cs @@ -33,7 +33,7 @@ public RspBustHook(HookManager hooks, MetaState metaState, MetaFileManager metaF } var ret = storage; - if (bodyType < 2 && _metaState.RspCollection is { Valid: true, ModCollection.MetaCache: { } cache }) + if (bodyType < 2 && _metaState.RspCollection.TryPeek(out var collection) && collection is { Valid: true, ModCollection.MetaCache: { } cache }) { var bustScale = bustSize / 100f; var clan = (SubRace)(((int)race - 1) * 2 + 1 + isSecondSubRace); diff --git a/Penumbra/Interop/Hooks/Meta/RspHeightHook.cs b/Penumbra/Interop/Hooks/Meta/RspHeightHook.cs index 883f5fc6..cf88c34a 100644 --- a/Penumbra/Interop/Hooks/Meta/RspHeightHook.cs +++ b/Penumbra/Interop/Hooks/Meta/RspHeightHook.cs @@ -1,3 +1,4 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.Interop.PathResolving; @@ -24,7 +25,7 @@ public RspHeightHook(HookManager hooks, MetaState metaState, MetaFileManager met private unsafe float Detour(nint cmpResource, Race race, byte gender, byte isSecondSubRace, byte bodyType, byte height) { float scale; - if (bodyType < 2 && _metaState.RspCollection is { Valid: true, ModCollection.MetaCache: { } cache }) + if (bodyType < 2 && _metaState.RspCollection.TryPeek(out var collection) && collection is { Valid: true, ModCollection.MetaCache: { } cache }) { var clan = (SubRace)(((int)race - 1) * 2 + 1 + isSecondSubRace); var (minIdent, maxIdent) = gender == 0 diff --git a/Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs b/Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs index 831c99bb..58856f52 100644 --- a/Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs +++ b/Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs @@ -31,8 +31,9 @@ private void Detour(DrawObject* drawObject, nint unk2, float unk3, nint unk4, by return; } - _metaState.RspCollection = _collectionResolver.IdentifyCollection(drawObject, true); + var collection = _collectionResolver.IdentifyCollection(drawObject, true); + _metaState.RspCollection.Push(collection); Task.Result.Original.Invoke(drawObject, unk2, unk3, unk4, unk5); - _metaState.RspCollection = ResolveData.Invalid; + _metaState.RspCollection.Pop(); } } diff --git a/Penumbra/Interop/Hooks/Meta/RspTailHook.cs b/Penumbra/Interop/Hooks/Meta/RspTailHook.cs index c81e97fa..e40f0161 100644 --- a/Penumbra/Interop/Hooks/Meta/RspTailHook.cs +++ b/Penumbra/Interop/Hooks/Meta/RspTailHook.cs @@ -24,7 +24,7 @@ public RspTailHook(HookManager hooks, MetaState metaState, MetaFileManager metaF private unsafe float Detour(nint cmpResource, Race race, byte gender, byte isSecondSubRace, byte bodyType, byte tailLength) { float scale; - if (bodyType < 2 && _metaState.RspCollection is { Valid: true, ModCollection.MetaCache: { } cache }) + if (bodyType < 2 && _metaState.RspCollection.TryPeek(out var collection) && collection is { Valid: true, ModCollection.MetaCache: { } cache }) { var clan = (SubRace)(((int)race - 1) * 2 + 1 + isSecondSubRace); var (minIdent, maxIdent) = gender == 0 diff --git a/Penumbra/Interop/Hooks/Meta/SetupVisor.cs b/Penumbra/Interop/Hooks/Meta/SetupVisor.cs index 8479968f..82b24dc4 100644 --- a/Penumbra/Interop/Hooks/Meta/SetupVisor.cs +++ b/Penumbra/Interop/Hooks/Meta/SetupVisor.cs @@ -27,11 +27,11 @@ public SetupVisor(HookManager hooks, CollectionResolver collectionResolver, Meta [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private byte Detour(DrawObject* drawObject, ushort modelId, byte visorState) { - _metaState.GmpCollection = _collectionResolver.IdentifyCollection(drawObject, true); - _metaState.UndividedGmpId = modelId; + var collection = _collectionResolver.IdentifyCollection(drawObject, true); + _metaState.GmpCollection.Push((collection, modelId)); var ret = Task.Result.Original.Invoke(drawObject, modelId, visorState); Penumbra.Log.Excessive($"[Setup Visor] Invoked on {(nint)drawObject:X} with {modelId}, {visorState} -> {ret}."); - _metaState.GmpCollection = ResolveData.Invalid; + _metaState.GmpCollection.Pop(); return ret; } } diff --git a/Penumbra/Interop/Hooks/Meta/UpdateModel.cs b/Penumbra/Interop/Hooks/Meta/UpdateModel.cs index b0298ac7..76854bca 100644 --- a/Penumbra/Interop/Hooks/Meta/UpdateModel.cs +++ b/Penumbra/Interop/Hooks/Meta/UpdateModel.cs @@ -29,10 +29,11 @@ private void Detour(DrawObject* drawObject) return; Penumbra.Log.Excessive($"[Update Model] Invoked on {(nint)drawObject:X}."); - var collection = _collectionResolver.IdentifyCollection(drawObject, true); - using var eqdp = _metaState.ResolveEqdpData(collection.ModCollection, MetaState.GetDrawObjectGenderRace((nint)drawObject), true, true); - _metaState.EqpCollection = collection; + var collection = _collectionResolver.IdentifyCollection(drawObject, true); + _metaState.EqpCollection.Push(collection); + _metaState.EqdpCollection.Push(collection); Task.Result.Original(drawObject); - _metaState.EqpCollection = ResolveData.Invalid; + _metaState.EqpCollection.Pop(); + _metaState.EqdpCollection.Pop(); } } diff --git a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs index 17cfa3f6..5941773f 100644 --- a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs +++ b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs @@ -1,11 +1,9 @@ using System.Text.Unicode; using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using OtterGui.Classes; using OtterGui.Services; using Penumbra.Collections; using Penumbra.Interop.PathResolving; -using Penumbra.Meta.Manipulations; namespace Penumbra.Interop.Hooks.Resources; @@ -149,42 +147,51 @@ private nint ResolveVfx(nint drawObject, nint pathBuffer, nint pathBufferSize, u private nint ResolveMdlHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex) { - var data = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - using var eqdp = slotIndex > 9 || _parent.InInternalResolve - ? DisposableContainer.Empty - : _parent.MetaState.ResolveEqdpData(data.ModCollection, MetaState.GetHumanGenderRace(drawObject), slotIndex < 5, slotIndex > 4); - return ResolvePath(data, _resolveMdlPathHook.Original(drawObject, pathBuffer, pathBufferSize, slotIndex)); + var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); + if (slotIndex < 10) + _parent.MetaState.EqdpCollection.Push(collection); + + var ret = ResolvePath(collection, _resolveMdlPathHook.Original(drawObject, pathBuffer, pathBufferSize, slotIndex)); + if (slotIndex < 10) + _parent.MetaState.EqdpCollection.Pop(); + + return ret; } private nint ResolvePapHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint unkAnimationIndex, nint animationName) { - _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - var ret = ResolvePath(_parent.MetaState.EstCollection, _resolvePapPathHook.Original(drawObject, pathBuffer, pathBufferSize, unkAnimationIndex, animationName)); - _parent.MetaState.EstCollection = ResolveData.Invalid; + var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); + _parent.MetaState.EstCollection.Push(collection); + var ret = ResolvePath(collection, + _resolvePapPathHook.Original(drawObject, pathBuffer, pathBufferSize, unkAnimationIndex, animationName)); + _parent.MetaState.EstCollection.Pop(); return ret; } private nint ResolvePhybHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex) { - _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - var ret = ResolvePath(_parent.MetaState.EstCollection, _resolvePhybPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); - _parent.MetaState.EstCollection = ResolveData.Invalid; + var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); + _parent.MetaState.EstCollection.Push(collection); + var ret = ResolvePath(collection, _resolvePhybPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + _parent.MetaState.EstCollection.Pop(); return ret; } private nint ResolveSklbHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex) { - _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - var ret = ResolvePath(_parent.MetaState.EstCollection, _resolveSklbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); - _parent.MetaState.EstCollection = ResolveData.Invalid; + var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); + _parent.MetaState.EstCollection.Push(collection); + var ret = ResolvePath(collection, _resolveSklbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + _parent.MetaState.EstCollection.Pop(); return ret; } private nint ResolveSkpHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex) { - _parent.MetaState.EstCollection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - var ret = ResolvePath(_parent.MetaState.EstCollection, _resolveSkpPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); - _parent.MetaState.EstCollection = ResolveData.Invalid; + var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); + _parent.MetaState.EstCollection.Push(collection); + var ret = ResolvePath(collection, _resolveSkpPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex)); + _parent.MetaState.EstCollection.Pop(); return ret; } diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index 3da94ce3..4bd23cf8 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -45,12 +45,14 @@ public sealed unsafe class MetaState : IDisposable private readonly CharacterUtility _characterUtility; private readonly CreateCharacterBase _createCharacterBase; - public ResolveData CustomizeChangeCollection = ResolveData.Invalid; - public ResolveData EqpCollection = ResolveData.Invalid; - public ResolveData GmpCollection = ResolveData.Invalid; - public ResolveData EstCollection = ResolveData.Invalid; - public ResolveData RspCollection = ResolveData.Invalid; - public PrimaryId UndividedGmpId = 0; + public ResolveData CustomizeChangeCollection = ResolveData.Invalid; + public readonly Stack EqpCollection = []; + public readonly Stack EqdpCollection = []; + public readonly Stack EstCollection = []; + public readonly Stack RspCollection = []; + + public readonly Stack<(ResolveData Collection, PrimaryId Id)> GmpCollection = []; + private ResolveData _lastCreatedCollection = ResolveData.Invalid; private DisposableContainer _characterBaseCreateMetaChanges = DisposableContainer.Empty; @@ -82,21 +84,6 @@ public bool HandleDecalFile(ResourceType type, Utf8GamePath gamePath, out Resolv return false; } - public DisposableContainer ResolveEqdpData(ModCollection collection, GenderRace race, bool equipment, bool accessory) - => (equipment, accessory) switch - { - (true, true) => new DisposableContainer(race.Dependencies().SelectMany(r => new[] - { - collection.TemporarilySetEqdpFile(_characterUtility, r, false), - collection.TemporarilySetEqdpFile(_characterUtility, r, true), - })), - (true, false) => new DisposableContainer(race.Dependencies() - .Select(r => collection.TemporarilySetEqdpFile(_characterUtility, r, false))), - (false, true) => new DisposableContainer(race.Dependencies() - .Select(r => collection.TemporarilySetEqdpFile(_characterUtility, r, true))), - _ => DisposableContainer.Empty, - }; - public DecalReverter ResolveDecal(ResolveData resolve, bool which) => new(_config, _characterUtility, _resources, resolve, which); @@ -130,7 +117,7 @@ private void OnCreatingCharacterBase(ModelCharaId* modelCharaId, CustomizeArray* var decal = new DecalReverter(_config, _characterUtility, _resources, _lastCreatedCollection, UsesDecal(*(uint*)modelCharaId, (nint)customize)); - RspCollection = _lastCreatedCollection; + RspCollection.Push(_lastCreatedCollection); _characterBaseCreateMetaChanges.Dispose(); // Should always be empty. _characterBaseCreateMetaChanges = new DisposableContainer(decal); } @@ -142,7 +129,7 @@ private void OnCharacterBaseCreated(ModelCharaId _1, CustomizeArray* _2, Charact if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero && drawObject != null) _communicator.CreatedCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject, _lastCreatedCollection.ModCollection, (nint)drawObject); - RspCollection = ResolveData.Invalid; + RspCollection.Pop(); _lastCreatedCollection = ResolveData.Invalid; } diff --git a/Penumbra/Interop/Services/MetaList.cs b/Penumbra/Interop/Services/MetaList.cs index dc472b8e..24d3f088 100644 --- a/Penumbra/Interop/Services/MetaList.cs +++ b/Penumbra/Interop/Services/MetaList.cs @@ -87,7 +87,7 @@ private void ResetResourceInternal() => SetResourceInternal(_defaultResourceData, _defaultResourceSize); private void SetResourceToDefaultCollection() - => _utility.Active.Default.SetMetaFile(_utility, GlobalMetaIndex); + {} public void Dispose() { diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 3bbfdf65..905b998d 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -144,7 +144,6 @@ public bool SetEnabled(bool enabled) { if (_characterUtility.Ready) { - _collectionManager.Active.Default.SetFiles(_characterUtility); _residentResources.Reload(); _redrawService.RedrawAll(RedrawType.Redraw); } @@ -153,7 +152,6 @@ public bool SetEnabled(bool enabled) { if (_characterUtility.Ready) { - _characterUtility.ResetAll(); _residentResources.Reload(); _redrawService.RedrawAll(RedrawType.Redraw); } From 91d9e465ede9850fd7d5d0a457870e068cfd0cc8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 16 Jun 2024 12:30:47 +0200 Subject: [PATCH 22/28] Improve eqdp. --- Penumbra.GameData | 2 +- Penumbra/Collections/Cache/EqdpCache.cs | 49 ++++++++----------- Penumbra/Collections/Cache/MetaCache.cs | 4 +- .../Interop/Hooks/Meta/EqdpAccessoryHook.cs | 9 ++-- Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs | 8 ++- Penumbra/Interop/PathResolving/MetaState.cs | 17 ------- .../Services/ShaderReplacementFixer.cs | 4 +- 7 files changed, 31 insertions(+), 62 deletions(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index cf1ff07e..3fbc7045 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit cf1ff07e900e2f93ab628a1fa535fc2b103794a5 +Subproject commit 3fbc704515b7b5fa9be02fb2a44719fc333747c1 diff --git a/Penumbra/Collections/Cache/EqdpCache.cs b/Penumbra/Collections/Cache/EqdpCache.cs index 5bfe2dbf..6047736b 100644 --- a/Penumbra/Collections/Cache/EqdpCache.cs +++ b/Penumbra/Collections/Cache/EqdpCache.cs @@ -1,20 +1,22 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Meta; -using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; namespace Penumbra.Collections.Cache; public sealed class EqdpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private readonly Dictionary<(PrimaryId Id, GenderRace GenderRace, bool Accessory), EqdpEntry> _fullEntries = []; + private readonly Dictionary<(PrimaryId Id, GenderRace GenderRace, bool Accessory), (EqdpEntry Entry, EqdpEntry InverseMask)> _fullEntries = + []; public override void SetFiles() { } - public bool TryGetFullEntry(PrimaryId id, GenderRace genderRace, bool accessory, out EqdpEntry entry) - => _fullEntries.TryGetValue((id, genderRace, accessory), out entry); + public EqdpEntry ApplyFullEntry(PrimaryId id, GenderRace genderRace, bool accessory, EqdpEntry originalEntry) + => _fullEntries.TryGetValue((id, genderRace, accessory), out var pair) + ? (originalEntry & pair.InverseMask) | pair.Entry + : originalEntry; protected override void IncorporateChangesInternal() { } @@ -27,36 +29,27 @@ public void Reset() protected override void ApplyModInternal(EqdpIdentifier identifier, EqdpEntry entry) { - var tuple = (identifier.SetId, identifier.GenderRace, identifier.Slot.IsAccessory()); - var mask = Eqdp.Mask(identifier.Slot); - if (!_fullEntries.TryGetValue(tuple, out var currentEntry)) - currentEntry = ExpandedEqdpFile.GetDefault(Manager, identifier); - - _fullEntries[tuple] = (currentEntry & ~mask) | (entry & mask); + var tuple = (identifier.SetId, identifier.GenderRace, identifier.Slot.IsAccessory()); + var mask = Eqdp.Mask(identifier.Slot); + var inverseMask = ~mask; + if (_fullEntries.TryGetValue(tuple, out var pair)) + pair = ((pair.Entry & inverseMask) | (entry & mask), pair.InverseMask & inverseMask); + else + pair = (entry & mask, inverseMask); + _fullEntries[tuple] = pair; } protected override void RevertModInternal(EqdpIdentifier identifier) { var tuple = (identifier.SetId, identifier.GenderRace, identifier.Slot.IsAccessory()); - var mask = Eqdp.Mask(identifier.Slot); - if (_fullEntries.TryGetValue(tuple, out var currentEntry)) - { - var def = ExpandedEqdpFile.GetDefault(Manager, identifier); - var newEntry = (currentEntry & ~mask) | (def & mask); - if (currentEntry != newEntry) - { - _fullEntries[tuple] = newEntry; - } - else - { - var slots = tuple.Item3 ? EquipSlotExtensions.AccessorySlots : EquipSlotExtensions.EquipmentSlots; - if (slots.All(s => !ContainsKey(identifier with { Slot = s }))) - _fullEntries.Remove(tuple); - else - _fullEntries[tuple] = newEntry; - } - } + if (!_fullEntries.Remove(tuple, out var pair)) + return; + + var mask = Eqdp.Mask(identifier.Slot); + var newMask = pair.InverseMask | mask; + if (newMask is not EqdpEntry.FullMask) + _fullEntries[tuple] = (pair.Entry & ~mask, newMask); } protected override void Dispose(bool _) diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 614a5a2c..92a445dd 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -122,9 +122,7 @@ public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.Imc => Imc.GetFile(path, out file); internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, PrimaryId primaryId) - => Eqdp.TryGetFullEntry(primaryId, race, accessory, out var entry) - ? entry - : Meta.Files.ExpandedEqdpFile.GetDefault(manager, race, accessory, primaryId); + => Eqdp.ApplyFullEntry(primaryId, race, accessory, Meta.Files.ExpandedEqdpFile.GetDefault(manager, race, accessory, primaryId)); internal EstEntry GetEstEntry(EstType type, GenderRace genderRace, PrimaryId primaryId) => Est.GetEstEntry(new EstIdentifier(primaryId, type, genderRace)); diff --git a/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs b/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs index 475e1eb7..f7390ea3 100644 --- a/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs @@ -3,7 +3,6 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.PathResolving; -using Penumbra.Meta.Manipulations; namespace Penumbra.Interop.Hooks.Meta; @@ -21,12 +20,10 @@ public EqdpAccessoryHook(HookManager hooks, MetaState metaState) private void Detour(CharacterUtility* utility, EqdpEntry* entry, uint setId, uint raceCode) { + Task.Result.Original(utility, entry, setId, raceCode); if (_metaState.EqdpCollection.TryPeek(out var collection) - && collection is { Valid: true, ModCollection.MetaCache: { } cache } - && cache.Eqdp.TryGetFullEntry(new PrimaryId((ushort)setId), (GenderRace)raceCode, true, out var newEntry)) - *entry = newEntry; - else - Task.Result.Original(utility, entry, setId, raceCode); + && collection is { Valid: true, ModCollection.MetaCache: { } cache }) + *entry = cache.Eqdp.ApplyFullEntry(new PrimaryId((ushort)setId), (GenderRace)raceCode, true, *entry); Penumbra.Log.Information( $"[GetEqdpAccessoryEntry] Invoked on 0x{(ulong)utility:X} with {setId}, {(GenderRace)raceCode}, returned {(ushort)*entry:B10}."); } diff --git a/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs b/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs index 9b911710..9b635b1f 100644 --- a/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs @@ -20,12 +20,10 @@ public EqdpEquipHook(HookManager hooks, MetaState metaState) private void Detour(CharacterUtility* utility, EqdpEntry* entry, uint setId, uint raceCode) { + Task.Result.Original(utility, entry, setId, raceCode); if (_metaState.EqdpCollection.TryPeek(out var collection) - && collection is { Valid: true, ModCollection.MetaCache: { } cache } - && cache.Eqdp.TryGetFullEntry(new PrimaryId((ushort)setId), (GenderRace)raceCode, false, out var newEntry)) - *entry = newEntry; - else - Task.Result.Original(utility, entry, setId, raceCode); + && collection is { Valid: true, ModCollection.MetaCache: { } cache }) + *entry = cache.Eqdp.ApplyFullEntry(new PrimaryId((ushort)setId), (GenderRace)raceCode, false, *entry); Penumbra.Log.Information( $"[GetEqdpEquipEntry] Invoked on 0x{(ulong)utility:X} with {setId}, {(GenderRace)raceCode}, returned {(ushort)*entry:B10}."); } diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index 4bd23cf8..7f820b4e 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -2,13 +2,11 @@ using OtterGui.Classes; using Penumbra.Collections; using Penumbra.Api.Enums; -using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.ResourceLoading; using Penumbra.Interop.Services; using Penumbra.Services; using Penumbra.String.Classes; -using ObjectType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType; using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; using Penumbra.Interop.Hooks.Objects; @@ -87,21 +85,6 @@ public bool HandleDecalFile(ResourceType type, Utf8GamePath gamePath, out Resolv public DecalReverter ResolveDecal(ResolveData resolve, bool which) => new(_config, _characterUtility, _resources, resolve, which); - public static GenderRace GetHumanGenderRace(nint human) - => (GenderRace)((Human*)human)->RaceSexId; - - public static GenderRace GetDrawObjectGenderRace(nint drawObject) - { - var draw = (DrawObject*)drawObject; - if (draw->Object.GetObjectType() != ObjectType.CharacterBase) - return GenderRace.Unknown; - - var c = (CharacterBase*)drawObject; - return c->GetModelType() == CharacterBase.ModelType.Human - ? GetHumanGenderRace(drawObject) - : GenderRace.Unknown; - } - public void Dispose() { _createCharacterBase.Unsubscribe(OnCreatingCharacterBase); diff --git a/Penumbra/Interop/Services/ShaderReplacementFixer.cs b/Penumbra/Interop/Services/ShaderReplacementFixer.cs index 3809ecbd..95e70b45 100644 --- a/Penumbra/Interop/Services/ShaderReplacementFixer.cs +++ b/Penumbra/Interop/Services/ShaderReplacementFixer.cs @@ -139,9 +139,9 @@ private nint OnRenderHumanMaterial(CharacterBase* human, CSModelRenderer.OnRende // Performance considerations: // - This function is called from several threads simultaneously, hence the need for synchronization in the swapping path ; - // - Function is called each frame for each material on screen, after culling, i. e. up to thousands of times a frame in crowded areas ; + // - Function is called each frame for each material on screen, after culling, i.e. up to thousands of times a frame in crowded areas ; // - Swapping path is taken up to hundreds of times a frame. - // At the time of writing, the lock doesn't seem to have a noticeable impact in either framerate or CPU usage, but the swapping path shall still be avoided as much as possible. + // At the time of writing, the lock doesn't seem to have a noticeable impact in either frame rate or CPU usage, but the swapping path shall still be avoided as much as possible. lock (_skinLock) { try From be729afd4b382e618c91558373e013f34959ba02 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 17 Jun 2024 16:39:10 +0200 Subject: [PATCH 23/28] Some cleanup --- Penumbra/Collections/Cache/ImcCache.cs | 2 -- Penumbra/Communication/CreatingCharacterBase.cs | 3 +-- Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs | 2 +- Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs | 2 +- Penumbra/Interop/Hooks/Meta/RspBustHook.cs | 2 +- Penumbra/Mods/Manager/ModManager.cs | 2 +- 6 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index a9daf795..c6bb0330 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -1,5 +1,3 @@ -using FFXIVClientStructs.FFXIV.Client.Graphics.Render; -using Penumbra.Collections.Manager; using Penumbra.GameData.Structs; using Penumbra.Interop.PathResolving; using Penumbra.Meta; diff --git a/Penumbra/Communication/CreatingCharacterBase.cs b/Penumbra/Communication/CreatingCharacterBase.cs index 8a906ca0..51d55868 100644 --- a/Penumbra/Communication/CreatingCharacterBase.cs +++ b/Penumbra/Communication/CreatingCharacterBase.cs @@ -1,5 +1,4 @@ using OtterGui.Classes; -using Penumbra.Api; using Penumbra.Api.Api; using Penumbra.Services; @@ -19,7 +18,7 @@ public sealed class CreatingCharacterBase() { public enum Priority { - /// + /// Api = 0, /// diff --git a/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs b/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs index f7390ea3..aaaaccd4 100644 --- a/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs @@ -24,7 +24,7 @@ private void Detour(CharacterUtility* utility, EqdpEntry* entry, uint setId, uin if (_metaState.EqdpCollection.TryPeek(out var collection) && collection is { Valid: true, ModCollection.MetaCache: { } cache }) *entry = cache.Eqdp.ApplyFullEntry(new PrimaryId((ushort)setId), (GenderRace)raceCode, true, *entry); - Penumbra.Log.Information( + Penumbra.Log.Excessive( $"[GetEqdpAccessoryEntry] Invoked on 0x{(ulong)utility:X} with {setId}, {(GenderRace)raceCode}, returned {(ushort)*entry:B10}."); } } diff --git a/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs b/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs index 9b635b1f..2711f195 100644 --- a/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs @@ -24,7 +24,7 @@ private void Detour(CharacterUtility* utility, EqdpEntry* entry, uint setId, uin if (_metaState.EqdpCollection.TryPeek(out var collection) && collection is { Valid: true, ModCollection.MetaCache: { } cache }) *entry = cache.Eqdp.ApplyFullEntry(new PrimaryId((ushort)setId), (GenderRace)raceCode, false, *entry); - Penumbra.Log.Information( + Penumbra.Log.Excessive( $"[GetEqdpEquipEntry] Invoked on 0x{(ulong)utility:X} with {setId}, {(GenderRace)raceCode}, returned {(ushort)*entry:B10}."); } } diff --git a/Penumbra/Interop/Hooks/Meta/RspBustHook.cs b/Penumbra/Interop/Hooks/Meta/RspBustHook.cs index 13a5410d..86759460 100644 --- a/Penumbra/Interop/Hooks/Meta/RspBustHook.cs +++ b/Penumbra/Interop/Hooks/Meta/RspBustHook.cs @@ -59,7 +59,7 @@ float GetValue(int dimension, RspAttribute min, RspAttribute max) ret = Task.Result.Original(cmpResource, storage, race, gender, isSecondSubRace, bodyType, bustSize); } - Penumbra.Log.Information( + Penumbra.Log.Excessive( $"[GetRspBust] Invoked on 0x{cmpResource:X} with {race}, {(Gender)(gender + 1)}, {isSecondSubRace == 1}, {bodyType}, {bustSize}, returned {storage[0]}, {storage[1]}, {storage[2]}."); return ret; } diff --git a/Penumbra/Mods/Manager/ModManager.cs b/Penumbra/Mods/Manager/ModManager.cs index 62b54865..42082383 100644 --- a/Penumbra/Mods/Manager/ModManager.cs +++ b/Penumbra/Mods/Manager/ModManager.cs @@ -261,7 +261,7 @@ public void Dispose() /// /// Set the mod base directory. - /// If its not the first time, check if it is the same directory as before. + /// If it's not the first time, check if it is the same directory as before. /// Also checks if the directory is available and tries to create it if it is not. /// private void SetBaseDirectory(string newPath, bool firstTime, out string resultNewDir) From d7a8c9415bb57f8fe13e3cfeed067e6d4a579470 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 17 Jun 2024 23:11:42 +0200 Subject: [PATCH 24/28] Use specific counter for Imc. --- Penumbra/Collections/Cache/ImcCache.cs | 2 ++ Penumbra/Collections/ModCollection.cs | 2 ++ Penumbra/Interop/PathResolving/PathDataHandler.cs | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index c6bb0330..786463bc 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -68,6 +68,7 @@ public void Reset() protected override void ApplyModInternal(ImcIdentifier identifier, ImcEntry entry) { + ++Collection.ImcChangeCounter; if (Manager.CharacterUtility.Ready) ApplyFile(identifier, entry); } @@ -102,6 +103,7 @@ private void ApplyFile(ImcIdentifier identifier, ImcEntry entry) protected override void RevertModInternal(ImcIdentifier identifier) { + ++Collection.ImcChangeCounter; var path = identifier.GamePath(); if (!_imcFiles.TryGetValue(path, out var pair)) return; diff --git a/Penumbra/Collections/ModCollection.cs b/Penumbra/Collections/ModCollection.cs index 9286d459..eb5ab46a 100644 --- a/Penumbra/Collections/ModCollection.cs +++ b/Penumbra/Collections/ModCollection.cs @@ -56,6 +56,8 @@ public string AnonymizedName /// public int ChangeCounter { get; private set; } + public uint ImcChangeCounter { get; set; } + /// Increment the number of changes in the effective file list. public int IncrementCounter() => ++ChangeCounter; diff --git a/Penumbra/Interop/PathResolving/PathDataHandler.cs b/Penumbra/Interop/PathResolving/PathDataHandler.cs index 5627e015..a8be97c8 100644 --- a/Penumbra/Interop/PathResolving/PathDataHandler.cs +++ b/Penumbra/Interop/PathResolving/PathDataHandler.cs @@ -32,7 +32,7 @@ public bool Valid /// Create the encoding path for an IMC file. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static FullPath CreateImc(ByteString path, ModCollection collection) - => CreateBase(path, collection); + => new($"|{collection.LocalId.Id}_{collection.ImcChangeCounter}_{DiscriminatorString}|{path}"); /// Create the encoding path for a TMB file. [MethodImpl(MethodImplOptions.AggressiveInlining)] From 03d3c38ad577756d3a4fb20c1bd0417344538727 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 18 Jun 2024 17:52:34 +0200 Subject: [PATCH 25/28] Improve Imc Handling. --- Penumbra/Collections/Cache/CollectionCache.cs | 16 +--- .../Cache/CollectionCacheManager.cs | 2 - Penumbra/Collections/Cache/ImcCache.cs | 58 ++++---------- Penumbra/Collections/Cache/MetaCache.cs | 8 +- .../Interop/PathResolving/PathResolver.cs | 50 +++---------- .../Interop/PathResolving/SubfileHelper.cs | 17 +++-- .../Interop/ResourceLoading/ResourceLoader.cs | 69 +++++++++++++++++ Penumbra/Interop/Services/CharacterUtility.cs | 35 +-------- Penumbra/Meta/Files/CmpFile.cs | 2 +- Penumbra/Meta/Files/EqdpFile.cs | 2 +- Penumbra/Meta/Files/EqpGmpFile.cs | 2 +- Penumbra/Meta/Files/EstFile.cs | 2 +- Penumbra/Meta/Files/EvpFile.cs | 6 +- Penumbra/Meta/Files/ImcFile.cs | 30 ++++---- Penumbra/Meta/Files/MetaBaseFile.cs | 75 ++++++++++++++++--- Penumbra/Meta/MetaFileManager.cs | 54 +------------ 16 files changed, 196 insertions(+), 232 deletions(-) diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs index fd801d3b..4755840e 100644 --- a/Penumbra/Collections/Cache/CollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -36,7 +36,7 @@ public IEnumerable> AllConflicts => ConflictDict.Values; public SingleArray Conflicts(IMod mod) - => ConflictDict.TryGetValue(mod, out SingleArray c) ? c : new SingleArray(); + => ConflictDict.TryGetValue(mod, out var c) ? c : new SingleArray(); private int _changedItemsSaveCounter = -1; @@ -125,12 +125,6 @@ public HashSet[] ReverseResolvePaths(IReadOnlyCollection f return ret; } - public void ForceFile(Utf8GamePath path, FullPath fullPath) - => _manager.AddChange(ChangeData.ForcedFile(this, path, fullPath)); - - public void RemovePath(Utf8GamePath path) - => _manager.AddChange(ChangeData.ForcedFile(this, path, FullPath.Empty)); - public void ReloadMod(IMod mod, bool addMetaChanges) => _manager.AddChange(ChangeData.ModReload(this, mod, addMetaChanges)); @@ -251,9 +245,6 @@ internal void AddModSync(IMod mod, bool addMetaChanges) if (addMetaChanges) { _collection.IncrementCounter(); - if (mod.TotalManipulations > 0) - AddMetaFiles(false); - _manager.MetaFileManager.ApplyDefaultFiles(_collection); } } @@ -408,11 +399,6 @@ private void AddManipulation(IMod mod, IMetaIdentifier identifier, object entry) } - // Add all necessary meta file redirects. - public void AddMetaFiles(bool fromFullCompute) - => Meta.SetImcFiles(fromFullCompute); - - // Identify and record all manipulated objects for this entire collection. private void SetChangedItems() { diff --git a/Penumbra/Collections/Cache/CollectionCacheManager.cs b/Penumbra/Collections/Cache/CollectionCacheManager.cs index ae424b94..02c9c8a9 100644 --- a/Penumbra/Collections/Cache/CollectionCacheManager.cs +++ b/Penumbra/Collections/Cache/CollectionCacheManager.cs @@ -180,8 +180,6 @@ private void FullRecalculation(ModCollection collection) foreach (var mod in _modStorage) cache.AddModSync(mod, false); - cache.AddMetaFiles(true); - collection.IncrementCounter(); MetaFileManager.ApplyDefaultFiles(collection); diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index 786463bc..e7eedc04 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -1,20 +1,22 @@ using Penumbra.GameData.Structs; -using Penumbra.Interop.PathResolving; using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; -using Penumbra.String.Classes; +using Penumbra.String; namespace Penumbra.Collections.Cache; public sealed class ImcCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private readonly Dictionary)> _imcFiles = []; + private readonly Dictionary)> _imcFiles = []; public override void SetFiles() - => SetFiles(false); + { } - public bool GetFile(Utf8GamePath path, [NotNullWhen(true)] out ImcFile? file) + public bool HasFile(ByteString path) + => _imcFiles.ContainsKey(path); + + public bool GetFile(ByteString path, [NotNullWhen(true)] out ImcFile? file) { if (!_imcFiles.TryGetValue(path, out var p)) { @@ -26,56 +28,31 @@ public bool GetFile(Utf8GamePath path, [NotNullWhen(true)] out ImcFile? file) return true; } - public void SetFiles(bool fromFullCompute) - { - if (fromFullCompute) - foreach (var (path, _) in _imcFiles) - Collection._cache!.ForceFileSync(path, PathDataHandler.CreateImc(path.Path, Collection)); - else - foreach (var (path, _) in _imcFiles) - Collection._cache!.ForceFile(path, PathDataHandler.CreateImc(path.Path, Collection)); - } - - public void ResetFiles() - { - foreach (var (path, _) in _imcFiles) - Collection._cache!.ForceFile(path, FullPath.Empty); - } - protected override void IncorporateChangesInternal() - { - if (!Manager.CharacterUtility.Ready) - return; - - foreach (var (identifier, (_, entry)) in this) - ApplyFile(identifier, entry); - - Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed IMC manipulations."); - } + { } public void Reset() { - foreach (var (path, (file, set)) in _imcFiles) + foreach (var (_, (file, set)) in _imcFiles) { - Collection._cache!.RemovePath(path); file.Reset(); set.Clear(); } + _imcFiles.Clear(); Clear(); } protected override void ApplyModInternal(ImcIdentifier identifier, ImcEntry entry) { ++Collection.ImcChangeCounter; - if (Manager.CharacterUtility.Ready) - ApplyFile(identifier, entry); + ApplyFile(identifier, entry); } private void ApplyFile(ImcIdentifier identifier, ImcEntry entry) { - var path = identifier.GamePath(); + var path = identifier.GamePath().Path; try { if (!_imcFiles.TryGetValue(path, out var pair)) @@ -87,8 +64,6 @@ private void ApplyFile(ImcIdentifier identifier, ImcEntry entry) pair.Item2.Add(identifier); _imcFiles[path] = pair; - var fullPath = PathDataHandler.CreateImc(pair.Item1.Path.Path, Collection); - Collection._cache!.ForceFile(path, fullPath); } catch (ImcException e) { @@ -104,7 +79,7 @@ private void ApplyFile(ImcIdentifier identifier, ImcEntry entry) protected override void RevertModInternal(ImcIdentifier identifier) { ++Collection.ImcChangeCounter; - var path = identifier.GamePath(); + var path = identifier.GamePath().Path; if (!_imcFiles.TryGetValue(path, out var pair)) return; @@ -114,17 +89,12 @@ protected override void RevertModInternal(ImcIdentifier identifier) if (pair.Item2.Count == 0) { _imcFiles.Remove(path); - Collection._cache!.ForceFile(pair.Item1.Path, FullPath.Empty); pair.Item1.Dispose(); return; } var def = ImcFile.GetDefault(Manager, pair.Item1.Path, identifier.EquipSlot, identifier.Variant, out _); - if (!Apply(pair.Item1, identifier, def)) - return; - - var fullPath = PathDataHandler.CreateImc(pair.Item1.Path.Path, Collection); - Collection._cache!.ForceFile(pair.Item1.Path, fullPath); + Apply(pair.Item1, identifier, def); } public static bool Apply(ImcFile file, ImcIdentifier identifier, ImcEntry entry) diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 92a445dd..253f3c7f 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -36,7 +36,7 @@ public void SetFiles() Est.SetFiles(); Gmp.SetFiles(); Rsp.SetFiles(); - Imc.SetFiles(false); + Imc.SetFiles(); } public void Reset() @@ -113,13 +113,9 @@ public bool ApplyMod(IMod mod, IMetaIdentifier identifier, object entry) ~MetaCache() => Dispose(); - /// Set the currently relevant IMC files for the collection cache. - public void SetImcFiles(bool fromFullCompute) - => Imc.SetFiles(fromFullCompute); - /// Try to obtain a manipulated IMC file. public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) - => Imc.GetFile(path, out file); + => Imc.GetFile(path.Path, out file); internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, PrimaryId primaryId) => Eqdp.ApplyFullEntry(primaryId, race, accessory, Meta.Files.ExpandedEqdpFile.GetDefault(manager, race, accessory, primaryId)); diff --git a/Penumbra/Interop/PathResolving/PathResolver.cs b/Penumbra/Interop/PathResolving/PathResolver.cs index e5c75327..e069e3ea 100644 --- a/Penumbra/Interop/PathResolving/PathResolver.cs +++ b/Penumbra/Interop/PathResolving/PathResolver.cs @@ -1,11 +1,8 @@ -using System.Runtime; using FFXIVClientStructs.FFXIV.Client.System.Resource; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.Interop.ResourceLoading; -using Penumbra.Interop.Structs; -using Penumbra.String; using Penumbra.String.Classes; using Penumbra.Util; @@ -27,24 +24,16 @@ public class PathResolver : IDisposable public unsafe PathResolver(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ResourceLoader loader, SubfileHelper subfileHelper, PathState pathState, MetaState metaState, CollectionResolver collectionResolver, GameState gameState) { - _performance = performance; - _config = config; - _collectionManager = collectionManager; - _subfileHelper = subfileHelper; - _pathState = pathState; - _metaState = metaState; - _gameState = gameState; - _collectionResolver = collectionResolver; - _loader = loader; - _loader.ResolvePath = ResolvePath; - _loader.FileLoaded += ImcLoadResource; - } - - /// Obtain a temporary or permanent collection by local ID. - public bool CollectionByLocalId(LocalCollectionId id, out ModCollection collection) - { - collection = _collectionManager.Storage.ByLocalId(id); - return collection != ModCollection.Empty; + _performance = performance; + _config = config; + _collectionManager = collectionManager; + _subfileHelper = subfileHelper; + _pathState = pathState; + _metaState = metaState; + _gameState = gameState; + _collectionResolver = collectionResolver; + _loader = loader; + _loader.ResolvePath = ResolvePath; } /// Try to resolve the given game path to the replaced path. @@ -120,7 +109,6 @@ public bool CollectionByLocalId(LocalCollectionId id, out ModCollection collecti public unsafe void Dispose() { _loader.ResetResolvePath(); - _loader.FileLoaded -= ImcLoadResource; } /// Use the default method of path replacement. @@ -130,24 +118,6 @@ public unsafe void Dispose() return (resolved, _collectionManager.Active.Default.ToResolveData()); } - /// After loading an IMC file, replace its contents with the modded IMC file. - private unsafe void ImcLoadResource(ResourceHandle* resource, ByteString path, bool returnValue, bool custom, - ReadOnlySpan additionalData) - { - if (resource->FileType != ResourceType.Imc - || !PathDataHandler.Read(additionalData, out var data) - || data.Discriminator != PathDataHandler.Discriminator - || !Utf8GamePath.FromByteString(path, out var gamePath) - || !CollectionByLocalId(data.Collection, out var collection) - || !collection.HasCache - || !collection.GetImcFile(gamePath, out var file)) - return; - - file.Replace(resource); - Penumbra.Log.Verbose( - $"[ResourceLoader] Loaded {gamePath} from file and replaced with IMC from collection {collection.AnonymizedName}."); - } - /// Resolve a path from the interface collection. private (FullPath?, ResolveData) ResolveUi(Utf8GamePath path) => (_collectionManager.Active.Interface.ResolvePath(path), diff --git a/Penumbra/Interop/PathResolving/SubfileHelper.cs b/Penumbra/Interop/PathResolving/SubfileHelper.cs index 793ea20b..b9631cf2 100644 --- a/Penumbra/Interop/PathResolving/SubfileHelper.cs +++ b/Penumbra/Interop/PathResolving/SubfileHelper.cs @@ -70,14 +70,15 @@ public bool HandleSubFiles(ResourceType type, out ResolveData collection) public static void HandleCollection(ResolveData resolveData, ByteString path, bool nonDefault, ResourceType type, FullPath? resolved, Utf8GamePath originalPath, out (FullPath?, ResolveData) data) { - if (nonDefault) - resolved = type switch - { - ResourceType.Mtrl => PathDataHandler.CreateMtrl(path, resolveData.ModCollection, originalPath), - ResourceType.Avfx => PathDataHandler.CreateAvfx(path, resolveData.ModCollection), - ResourceType.Tmb => PathDataHandler.CreateTmb(path, resolveData.ModCollection), - _ => resolved, - }; + resolved = type switch + { + ResourceType.Mtrl when nonDefault => PathDataHandler.CreateMtrl(path, resolveData.ModCollection, originalPath), + ResourceType.Avfx when nonDefault => PathDataHandler.CreateAvfx(path, resolveData.ModCollection), + ResourceType.Tmb when nonDefault => PathDataHandler.CreateTmb(path, resolveData.ModCollection), + ResourceType.Imc when resolveData.ModCollection.MetaCache?.Imc.HasFile(path) ?? false => PathDataHandler.CreateImc(path, + resolveData.ModCollection), + _ => resolved, + }; data = (resolved, resolveData); } diff --git a/Penumbra/Interop/ResourceLoading/ResourceLoader.cs b/Penumbra/Interop/ResourceLoading/ResourceLoader.cs index 7b49beab..fae38907 100644 --- a/Penumbra/Interop/ResourceLoading/ResourceLoader.cs +++ b/Penumbra/Interop/ResourceLoading/ResourceLoader.cs @@ -1,6 +1,9 @@ +using System.Collections.Frozen; using FFXIVClientStructs.FFXIV.Client.System.Resource; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections; +using Penumbra.Collections.Manager; using Penumbra.Interop.PathResolving; using Penumbra.Interop.SafeHandles; using Penumbra.Interop.Structs; @@ -10,6 +13,72 @@ namespace Penumbra.Interop.ResourceLoading; +public interface IFilePostProcessor : IService +{ + public ResourceType Type { get; } + public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan additionalData); +} + +public sealed class MaterialFilePostProcessor : IFilePostProcessor +{ + public ResourceType Type + => ResourceType.Mtrl; + + public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan additionalData) + { + if (!PathDataHandler.ReadMtrl(additionalData, out var data)) + return; + } +} + +public sealed class ImcFilePostProcessor(CollectionStorage collections) : IFilePostProcessor +{ + public ResourceType Type + => ResourceType.Imc; + + public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan additionalData) + { + if (!PathDataHandler.Read(additionalData, out var data) || data.Discriminator != PathDataHandler.Discriminator) + return; + + var collection = collections.ByLocalId(data.Collection); + if (collection.MetaCache is not { } cache) + return; + + if (!cache.Imc.GetFile(originalGamePath, out var file)) + return; + + file.Replace(resource); + Penumbra.Log.Information( + $"[ResourceLoader] Loaded {originalGamePath} from file and replaced with IMC from collection {collection.AnonymizedName}."); + } +} + +public unsafe class FilePostProcessService : IRequiredService, IDisposable +{ + private readonly ResourceLoader _resourceLoader; + private readonly FrozenDictionary _processors; + + public FilePostProcessService(ResourceLoader resourceLoader, ServiceManager services) + { + _resourceLoader = resourceLoader; + _processors = services.GetServicesImplementing().ToFrozenDictionary(s => s.Type, s => s); + _resourceLoader.FileLoaded += OnFileLoaded; + } + + public void Dispose() + { + _resourceLoader.FileLoaded -= OnFileLoaded; + } + + private void OnFileLoaded(ResourceHandle* resource, ByteString path, bool returnValue, bool custom, + ReadOnlySpan additionalData) + { + if (_processors.TryGetValue(resource->FileType, out var processor)) + processor.PostProcess(resource, path, additionalData); + } +} + public unsafe class ResourceLoader : IDisposable { private readonly ResourceService _resources; diff --git a/Penumbra/Interop/Services/CharacterUtility.cs b/Penumbra/Interop/Services/CharacterUtility.cs index da04bf90..9459df06 100644 --- a/Penumbra/Interop/Services/CharacterUtility.cs +++ b/Penumbra/Interop/Services/CharacterUtility.cs @@ -1,6 +1,5 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; -using Penumbra.Collections.Manager; using Penumbra.GameData; using Penumbra.Interop.Structs; @@ -53,17 +52,15 @@ public IReadOnlyList Lists public (nint Address, int Size) DefaultResource(InternalIndex idx) => _lists[idx.Value].DefaultResource; - private readonly IFramework _framework; - public readonly ActiveCollectionData Active; + private readonly IFramework _framework; - public CharacterUtility(IFramework framework, IGameInteropProvider interop, ActiveCollectionData active) + public CharacterUtility(IFramework framework, IGameInteropProvider interop) { interop.InitializeFromAttributes(this); _lists = Enumerable.Range(0, RelevantIndices.Length) .Select(idx => new MetaList(this, new InternalIndex(idx))) .ToArray(); _framework = framework; - Active = active; LoadingFinished += () => Penumbra.Log.Debug("Loading of CharacterUtility finished."); LoadDefaultResources(null!); if (!Ready) @@ -121,34 +118,6 @@ private void LoadDefaultResources(object _) LoadingFinished.Invoke(); } - public void SetResource(MetaIndex resourceIdx, nint data, int length) - { - var idx = ReverseIndices[(int)resourceIdx]; - var list = _lists[idx.Value]; - list.SetResource(data, length); - } - - public void ResetResource(MetaIndex resourceIdx) - { - var idx = ReverseIndices[(int)resourceIdx]; - var list = _lists[idx.Value]; - list.ResetResource(); - } - - public MetaList.MetaReverter TemporarilySetResource(MetaIndex resourceIdx, nint data, int length) - { - var idx = ReverseIndices[(int)resourceIdx]; - var list = _lists[idx.Value]; - return list.TemporarilySetResource(data, length); - } - - public MetaList.MetaReverter TemporarilyResetResource(MetaIndex resourceIdx) - { - var idx = ReverseIndices[(int)resourceIdx]; - var list = _lists[idx.Value]; - return list.TemporarilyResetResource(); - } - /// Return all relevant resources to the default resource. public void ResetAll() { diff --git a/Penumbra/Meta/Files/CmpFile.cs b/Penumbra/Meta/Files/CmpFile.cs index 8ca7cb80..5028a3de 100644 --- a/Penumbra/Meta/Files/CmpFile.cs +++ b/Penumbra/Meta/Files/CmpFile.cs @@ -34,7 +34,7 @@ public void Reset(IEnumerable<(SubRace, RspAttribute)> entries) } public CmpFile(MetaFileManager manager) - : base(manager, MetaIndex.HumanCmp) + : base(manager, manager.MarshalAllocator, MetaIndex.HumanCmp) { AllocateData(DefaultData.Length); Reset(); diff --git a/Penumbra/Meta/Files/EqdpFile.cs b/Penumbra/Meta/Files/EqdpFile.cs index e46e82e9..34b4f25b 100644 --- a/Penumbra/Meta/Files/EqdpFile.cs +++ b/Penumbra/Meta/Files/EqdpFile.cs @@ -87,7 +87,7 @@ public void Reset(IEnumerable entries) } public ExpandedEqdpFile(MetaFileManager manager, GenderRace raceCode, bool accessory) - : base(manager, CharacterUtilityData.EqdpIdx(raceCode, accessory)) + : base(manager, manager.MarshalAllocator, CharacterUtilityData.EqdpIdx(raceCode, accessory)) { var def = (byte*)DefaultData.Data; var blockSize = *(ushort*)(def + IdentifierSize); diff --git a/Penumbra/Meta/Files/EqpGmpFile.cs b/Penumbra/Meta/Files/EqpGmpFile.cs index c47c84ef..a7540f4b 100644 --- a/Penumbra/Meta/Files/EqpGmpFile.cs +++ b/Penumbra/Meta/Files/EqpGmpFile.cs @@ -76,7 +76,7 @@ public sealed override void Reset() } public ExpandedEqpGmpBase(MetaFileManager manager, bool gmp) - : base(manager, gmp ? MetaIndex.Gmp : MetaIndex.Eqp) + : base(manager, manager.MarshalAllocator, gmp ? MetaIndex.Gmp : MetaIndex.Eqp) { AllocateData(MaxSize); Reset(); diff --git a/Penumbra/Meta/Files/EstFile.cs b/Penumbra/Meta/Files/EstFile.cs index f3860416..ba38d6d9 100644 --- a/Penumbra/Meta/Files/EstFile.cs +++ b/Penumbra/Meta/Files/EstFile.cs @@ -157,7 +157,7 @@ public override void Reset() } public EstFile(MetaFileManager manager, EstType estType) - : base(manager, (MetaIndex)estType) + : base(manager, manager.MarshalAllocator, (MetaIndex)estType) { var length = DefaultData.Length; AllocateData(length + IncreaseSize); diff --git a/Penumbra/Meta/Files/EvpFile.cs b/Penumbra/Meta/Files/EvpFile.cs index 3d0b4dbe..6ab1591c 100644 --- a/Penumbra/Meta/Files/EvpFile.cs +++ b/Penumbra/Meta/Files/EvpFile.cs @@ -12,7 +12,7 @@ namespace Penumbra.Meta.Files; /// Containing Flags in each byte, 0x01 set for Body, 0x02 set for Helmet. /// Each flag corresponds to a mount row from the Mounts table and determines whether the mount disables the effect. /// -public unsafe class EvpFile : MetaBaseFile +public unsafe class EvpFile(MetaFileManager manager) : MetaBaseFile(manager, manager.MarshalAllocator, (MetaIndex)1) { public const int FlagArraySize = 512; @@ -57,8 +57,4 @@ public EvpFlag Flag(ushort modelSet, int arrayIndex) return EvpFlag.None; } - - public EvpFile(MetaFileManager manager) - : base(manager, (MetaIndex)1) // TODO: Name - { } } diff --git a/Penumbra/Meta/Files/ImcFile.cs b/Penumbra/Meta/Files/ImcFile.cs index 892f5b44..01ef3f16 100644 --- a/Penumbra/Meta/Files/ImcFile.cs +++ b/Penumbra/Meta/Files/ImcFile.cs @@ -7,16 +7,10 @@ namespace Penumbra.Meta.Files; -public class ImcException : Exception +public class ImcException(ImcIdentifier identifier, Utf8GamePath path) : Exception { - public readonly ImcIdentifier Identifier; - public readonly string GamePath; - - public ImcException(ImcIdentifier identifier, Utf8GamePath path) - { - Identifier = identifier; - GamePath = path.ToString(); - } + public readonly ImcIdentifier Identifier = identifier; + public readonly string GamePath = path.ToString(); public override string Message => "Could not obtain default Imc File.\n" @@ -146,7 +140,11 @@ public override void Reset() } public ImcFile(MetaFileManager manager, ImcIdentifier identifier) - : base(manager, 0) + : this(manager, manager.MarshalAllocator, identifier) + { } + + public ImcFile(MetaFileManager manager, IFileAllocator alloc, ImcIdentifier identifier) + : base(manager, alloc, 0) { var path = identifier.GamePathString(); Path = Utf8GamePath.FromString(path, out var p) ? p : Utf8GamePath.Empty; @@ -194,7 +192,13 @@ public static ImcEntry GetEntry(ReadOnlySpan imcFileData, EquipSlot slot, public void Replace(ResourceHandle* resource) { var (data, length) = resource->GetData(); - var newData = Manager.AllocateDefaultMemory(ActualLength, 8); + if (length == ActualLength) + { + MemoryUtility.MemCpyUnchecked((byte*)data, Data, ActualLength); + return; + } + + var newData = Manager.XivAllocator.Allocate(ActualLength, 8); if (newData == null) { Penumbra.Log.Error($"Could not replace loaded IMC data at 0x{(ulong)resource:X}, allocation failed."); @@ -203,7 +207,7 @@ public void Replace(ResourceHandle* resource) MemoryUtility.MemCpyUnchecked(newData, Data, ActualLength); - Manager.Free(data, length); - resource->SetData((IntPtr)newData, ActualLength); + Manager.XivAllocator.Release((void*)data, length); + resource->SetData((nint)newData, ActualLength); } } diff --git a/Penumbra/Meta/Files/MetaBaseFile.cs b/Penumbra/Meta/Files/MetaBaseFile.cs index ab08efc2..86a55101 100644 --- a/Penumbra/Meta/Files/MetaBaseFile.cs +++ b/Penumbra/Meta/Files/MetaBaseFile.cs @@ -1,23 +1,75 @@ using Dalamud.Memory; +using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.System.Memory; +using OtterGui.Services; +using Penumbra.GameData; using Penumbra.Interop.Structs; using Penumbra.String.Functions; using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; namespace Penumbra.Meta.Files; -public unsafe class MetaBaseFile : IDisposable +public unsafe interface IFileAllocator { - protected readonly MetaFileManager Manager; + public T* Allocate(int length, int alignment = 1) where T : unmanaged; + public void Release(ref T* pointer, int length) where T : unmanaged; - public byte* Data { get; private set; } - public int Length { get; private set; } - public CharacterUtility.InternalIndex Index { get; } + public void Release(void* pointer, int length) + { + var tmp = (byte*)pointer; + Release(ref tmp, length); + } + + public byte* Allocate(int length, int alignment = 1) + => Allocate(length, alignment); +} - public MetaBaseFile(MetaFileManager manager, MetaIndex idx) +public sealed class MarshalAllocator : IFileAllocator +{ + public unsafe T* Allocate(int length, int alignment = 1) where T : unmanaged + => (T*)Marshal.AllocHGlobal(length * sizeof(T)); + + public unsafe void Release(ref T* pointer, int length) where T : unmanaged { - Manager = manager; - Index = CharacterUtility.ReverseIndices[(int)idx]; + Marshal.FreeHGlobal((nint)pointer); + pointer = null; } +} + +public sealed unsafe class XivFileAllocator : IFileAllocator, IService +{ + /// + /// Allocate in the games space for file storage. + /// We only need this if using any meta file. + /// + [Signature(Sigs.GetFileSpace)] + private readonly nint _getFileSpaceAddress = nint.Zero; + + public XivFileAllocator(IGameInteropProvider provider) + => provider.InitializeFromAttributes(this); + + public IMemorySpace* GetFileSpace() + => ((delegate* unmanaged)_getFileSpaceAddress)(); + + public T* Allocate(int length, int alignment = 1) where T : unmanaged + => (T*)GetFileSpace()->Malloc((ulong)(length * sizeof(T)), (ulong)alignment); + + public void Release(ref T* pointer, int length) where T : unmanaged + { + IMemorySpace.Free(pointer, (ulong)(length * sizeof(T))); + pointer = null; + } +} + +public unsafe class MetaBaseFile(MetaFileManager manager, IFileAllocator alloc, MetaIndex idx) : IDisposable +{ + protected readonly MetaFileManager Manager = manager; + protected readonly IFileAllocator Allocator = alloc; + + public byte* Data { get; private set; } + public int Length { get; private set; } + public CharacterUtility.InternalIndex Index { get; } = CharacterUtility.ReverseIndices[(int)idx]; protected (IntPtr Data, int Length) DefaultData => Manager.CharacterUtility.DefaultResource(Index); @@ -30,7 +82,7 @@ public virtual void Reset() protected void AllocateData(int length) { Length = length; - Data = (byte*)Manager.AllocateFileMemory(length); + Data = Allocator.Allocate(length); if (length > 0) GC.AddMemoryPressure(length); } @@ -38,8 +90,7 @@ protected void AllocateData(int length) /// Free memory. protected void ReleaseUnmanagedResources() { - var ptr = (IntPtr)Data; - MemoryHelper.GameFree(ref ptr, (ulong)Length); + Allocator.Release(Data, Length); if (Length > 0) GC.RemoveMemoryPressure(Length); @@ -53,7 +104,7 @@ protected void ResizeResources(int newLength) if (newLength == Length) return; - var data = (byte*)Manager.AllocateFileMemory((ulong)newLength); + var data = Allocator.Allocate(newLength); if (newLength > Length) { MemoryUtility.MemCpyUnchecked(data, Data, Length); diff --git a/Penumbra/Meta/MetaFileManager.cs b/Penumbra/Meta/MetaFileManager.cs index 40fceb07..81c0fa3e 100644 --- a/Penumbra/Meta/MetaFileManager.cs +++ b/Penumbra/Meta/MetaFileManager.cs @@ -1,14 +1,10 @@ using Dalamud.Plugin.Services; -using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.System.Memory; using OtterGui.Compression; using Penumbra.Collections; using Penumbra.Collections.Manager; -using Penumbra.GameData; using Penumbra.GameData.Data; using Penumbra.Import; using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; using Penumbra.Meta.Files; using Penumbra.Mods; using Penumbra.Mods.Groups; @@ -28,6 +24,9 @@ public unsafe class MetaFileManager internal readonly ObjectIdentification Identifier; internal readonly FileCompactor Compactor; internal readonly ImcChecker ImcChecker; + internal readonly IFileAllocator MarshalAllocator = new MarshalAllocator(); + internal readonly IFileAllocator XivAllocator; + public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, IDataManager gameData, ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, ObjectIdentification identifier, @@ -42,6 +41,7 @@ public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManage Identifier = identifier; Compactor = compactor; ImcChecker = new ImcChecker(this); + XivAllocator = new XivFileAllocator(interop); interop.InitializeFromAttributes(this); } @@ -76,57 +76,11 @@ public void WriteAllTexToolsMeta(Mod mod) } } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public void SetFile(MetaBaseFile? file, MetaIndex metaIndex) - { - if (file == null || !Config.EnableMods) - CharacterUtility.ResetResource(metaIndex); - else - CharacterUtility.SetResource(metaIndex, (nint)file.Data, file.Length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public MetaList.MetaReverter TemporarilySetFile(MetaBaseFile? file, MetaIndex metaIndex) - => Config.EnableMods - ? file == null - ? CharacterUtility.TemporarilyResetResource(metaIndex) - : CharacterUtility.TemporarilySetResource(metaIndex, (nint)file.Data, file.Length) - : MetaList.MetaReverter.Disabled; - public void ApplyDefaultFiles(ModCollection? collection) { if (ActiveCollections.Default != collection || !CharacterUtility.Ready || !Config.EnableMods) return; ResidentResources.Reload(); - if (collection._cache == null) - CharacterUtility.ResetAll(); - else - collection._cache.Meta.SetFiles(); } - - /// - /// Allocate in the games space for file storage. - /// We only need this if using any meta file. - /// - [Signature(Sigs.GetFileSpace)] - private readonly nint _getFileSpaceAddress = nint.Zero; - - public IMemorySpace* GetFileSpace() - => ((delegate* unmanaged)_getFileSpaceAddress)(); - - public void* AllocateFileMemory(ulong length, ulong alignment = 0) - => GetFileSpace()->Malloc(length, alignment); - - public void* AllocateFileMemory(int length, int alignment = 0) - => AllocateFileMemory((ulong)length, (ulong)alignment); - - public void* AllocateDefaultMemory(ulong length, ulong alignment = 0) - => GetFileSpace()->Malloc(length, alignment); - - public void* AllocateDefaultMemory(int length, int alignment = 0) - => IMemorySpace.GetDefaultSpace()->Malloc((ulong)length, (ulong)alignment); - - public void Free(nint ptr, int length) - => IMemorySpace.Free((void*)ptr, (ulong)length); } From f9c45a2f3f9bae991a67076622c6ef7d701c94ea Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 18 Jun 2024 17:57:12 +0200 Subject: [PATCH 26/28] Clean unused functions. --- Penumbra/Collections/Cache/EqdpCache.cs | 10 ++----- Penumbra/Collections/Cache/EqpCache.cs | 12 --------- Penumbra/Collections/Cache/EstCache.cs | 12 --------- Penumbra/Collections/Cache/GmpCache.cs | 12 --------- Penumbra/Collections/Cache/IMetaCache.cs | 33 +++++------------------- Penumbra/Collections/Cache/ImcCache.cs | 7 ----- Penumbra/Collections/Cache/MetaCache.cs | 10 ------- Penumbra/Collections/Cache/RspCache.cs | 13 ---------- 8 files changed, 9 insertions(+), 100 deletions(-) diff --git a/Penumbra/Collections/Cache/EqdpCache.cs b/Penumbra/Collections/Cache/EqdpCache.cs index 6047736b..5e0626cf 100644 --- a/Penumbra/Collections/Cache/EqdpCache.cs +++ b/Penumbra/Collections/Cache/EqdpCache.cs @@ -10,17 +10,11 @@ public sealed class EqdpCache(MetaFileManager manager, ModCollection collection) private readonly Dictionary<(PrimaryId Id, GenderRace GenderRace, bool Accessory), (EqdpEntry Entry, EqdpEntry InverseMask)> _fullEntries = []; - public override void SetFiles() - { } - public EqdpEntry ApplyFullEntry(PrimaryId id, GenderRace genderRace, bool accessory, EqdpEntry originalEntry) => _fullEntries.TryGetValue((id, genderRace, accessory), out var pair) ? (originalEntry & pair.InverseMask) | pair.Entry : originalEntry; - protected override void IncorporateChangesInternal() - { } - public void Reset() { Clear(); @@ -46,8 +40,8 @@ protected override void RevertModInternal(EqdpIdentifier identifier) if (!_fullEntries.Remove(tuple, out var pair)) return; - var mask = Eqdp.Mask(identifier.Slot); - var newMask = pair.InverseMask | mask; + var mask = Eqdp.Mask(identifier.Slot); + var newMask = pair.InverseMask | mask; if (newMask is not EqdpEntry.FullMask) _fullEntries[tuple] = (pair.Entry & ~mask, newMask); } diff --git a/Penumbra/Collections/Cache/EqpCache.cs b/Penumbra/Collections/Cache/EqpCache.cs index 7ba0c489..60e38aef 100644 --- a/Penumbra/Collections/Cache/EqpCache.cs +++ b/Penumbra/Collections/Cache/EqpCache.cs @@ -8,12 +8,6 @@ namespace Penumbra.Collections.Cache; public sealed class EqpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - public override void SetFiles() - { } - - protected override void IncorporateChangesInternal() - { } - public unsafe EqpEntry GetValues(CharacterArmor* armor) => GetSingleValue(armor[0].Set, EquipSlot.Head) | GetSingleValue(armor[1].Set, EquipSlot.Body) @@ -28,12 +22,6 @@ private EqpEntry GetSingleValue(PrimaryId id, EquipSlot slot) public void Reset() => Clear(); - protected override void ApplyModInternal(EqpIdentifier identifier, EqpEntry entry) - { } - - protected override void RevertModInternal(EqpIdentifier identifier) - { } - protected override void Dispose(bool _) => Clear(); } diff --git a/Penumbra/Collections/Cache/EstCache.cs b/Penumbra/Collections/Cache/EstCache.cs index ff94324e..aff8beef 100644 --- a/Penumbra/Collections/Cache/EstCache.cs +++ b/Penumbra/Collections/Cache/EstCache.cs @@ -6,12 +6,6 @@ namespace Penumbra.Collections.Cache; public sealed class EstCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - public override void SetFiles() - { } - - protected override void IncorporateChangesInternal() - { } - public EstEntry GetEstEntry(EstIdentifier identifier) => TryGetValue(identifier, out var entry) ? entry.Entry @@ -20,12 +14,6 @@ public EstEntry GetEstEntry(EstIdentifier identifier) public void Reset() => Clear(); - protected override void ApplyModInternal(EstIdentifier identifier, EstEntry entry) - { } - - protected override void RevertModInternal(EstIdentifier identifier) - { } - protected override void Dispose(bool _) => Clear(); } diff --git a/Penumbra/Collections/Cache/GmpCache.cs b/Penumbra/Collections/Cache/GmpCache.cs index 541b424d..9170b871 100644 --- a/Penumbra/Collections/Cache/GmpCache.cs +++ b/Penumbra/Collections/Cache/GmpCache.cs @@ -6,21 +6,9 @@ namespace Penumbra.Collections.Cache; public sealed class GmpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - public override void SetFiles() - { } - - protected override void IncorporateChangesInternal() - { } - public void Reset() => Clear(); - protected override void ApplyModInternal(GmpIdentifier identifier, GmpEntry entry) - { } - - protected override void RevertModInternal(GmpIdentifier identifier) - { } - protected override void Dispose(bool _) => Clear(); } diff --git a/Penumbra/Collections/Cache/IMetaCache.cs b/Penumbra/Collections/Cache/IMetaCache.cs index dd218b48..fecc6f50 100644 --- a/Penumbra/Collections/Cache/IMetaCache.cs +++ b/Penumbra/Collections/Cache/IMetaCache.cs @@ -4,30 +4,19 @@ namespace Penumbra.Collections.Cache; -public abstract class MetaCacheBase +public abstract class MetaCacheBase(MetaFileManager manager, ModCollection collection) : Dictionary where TIdentifier : unmanaged, IMetaIdentifier where TEntry : unmanaged { - protected MetaCacheBase(MetaFileManager manager, ModCollection collection) - { - Manager = manager; - Collection = collection; - if (!Manager.CharacterUtility.Ready) - Manager.CharacterUtility.LoadingFinished += IncorporateChanges; - } - - protected readonly MetaFileManager Manager; - protected readonly ModCollection Collection; + protected readonly MetaFileManager Manager = manager; + protected readonly ModCollection Collection = collection; public void Dispose() { - Manager.CharacterUtility.LoadingFinished -= IncorporateChanges; Dispose(true); } - public abstract void SetFiles(); - public bool ApplyMod(IMod source, TIdentifier identifier, TEntry entry) { lock (this) @@ -59,20 +48,12 @@ public bool RevertMod(TIdentifier identifier, [NotNullWhen(true)] out IMod? mod) return true; } - private void IncorporateChanges() - { - lock (this) - { - IncorporateChangesInternal(); - } - if (Manager.ActiveCollections.Default == Collection && Manager.Config.EnableMods) - SetFiles(); - } + protected virtual void ApplyModInternal(TIdentifier identifier, TEntry entry) + { } - protected abstract void ApplyModInternal(TIdentifier identifier, TEntry entry); - protected abstract void RevertModInternal(TIdentifier identifier); - protected abstract void IncorporateChangesInternal(); + protected virtual void RevertModInternal(TIdentifier identifier) + { } protected virtual void Dispose(bool _) { } diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index e7eedc04..40c3d2c7 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -10,9 +10,6 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection) { private readonly Dictionary)> _imcFiles = []; - public override void SetFiles() - { } - public bool HasFile(ByteString path) => _imcFiles.ContainsKey(path); @@ -28,10 +25,6 @@ public bool GetFile(ByteString path, [NotNullWhen(true)] out ImcFile? file) return true; } - protected override void IncorporateChangesInternal() - { } - - public void Reset() { foreach (var (_, (file, set)) in _imcFiles) diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 253f3c7f..02056fad 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -29,16 +29,6 @@ public int Count .Concat(Imc.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))) .Concat(GlobalEqp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value))); - public void SetFiles() - { - Eqp.SetFiles(); - Eqdp.SetFiles(); - Est.SetFiles(); - Gmp.SetFiles(); - Rsp.SetFiles(); - Imc.SetFiles(); - } - public void Reset() { Eqp.Reset(); diff --git a/Penumbra/Collections/Cache/RspCache.cs b/Penumbra/Collections/Cache/RspCache.cs index 8a983c6c..064b1f44 100644 --- a/Penumbra/Collections/Cache/RspCache.cs +++ b/Penumbra/Collections/Cache/RspCache.cs @@ -5,22 +5,9 @@ namespace Penumbra.Collections.Cache; public sealed class RspCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - public override void SetFiles() - { } - - protected override void IncorporateChangesInternal() - { } - public void Reset() => Clear(); - protected override void ApplyModInternal(RspIdentifier identifier, RspEntry entry) - { } - - protected override void RevertModInternal(RspIdentifier identifier) - { } - - protected override void Dispose(bool _) => Clear(); } From cf1dcfcb7cb27e6928810b75c63f1f352da4ecd2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 18 Jun 2024 18:33:37 +0200 Subject: [PATCH 27/28] Improve Path preprocessing. --- .../Interop/PathResolving/PathResolver.cs | 23 ++-- .../Interop/PathResolving/SubfileHelper.cs | 16 --- .../Processing/AvfxPathPreProcessor.cs | 16 +++ .../Processing/FilePostProcessService.cs | 39 ++++++ .../Processing/GamePathPreProcessService.cs | 37 +++++ .../Processing/ImcFilePostProcessor.cs | 30 ++++ .../Interop/Processing/ImcPathPreProcessor.cs | 18 +++ .../Processing/MaterialFilePostProcessor.cs | 18 +++ .../Processing/MtrlPathPreProcessor.cs | 16 +++ .../Interop/Processing/TmbPathPreProcessor.cs | 16 +++ .../Interop/ResourceLoading/ResourceLoader.cs | 69 ---------- Penumbra/Interop/Services/CharacterUtility.cs | 8 +- Penumbra/Interop/Services/MetaList.cs | 130 +----------------- Penumbra/UI/Tabs/Debug/DebugTab.cs | 19 --- 14 files changed, 209 insertions(+), 246 deletions(-) create mode 100644 Penumbra/Interop/Processing/AvfxPathPreProcessor.cs create mode 100644 Penumbra/Interop/Processing/FilePostProcessService.cs create mode 100644 Penumbra/Interop/Processing/GamePathPreProcessService.cs create mode 100644 Penumbra/Interop/Processing/ImcFilePostProcessor.cs create mode 100644 Penumbra/Interop/Processing/ImcPathPreProcessor.cs create mode 100644 Penumbra/Interop/Processing/MaterialFilePostProcessor.cs create mode 100644 Penumbra/Interop/Processing/MtrlPathPreProcessor.cs create mode 100644 Penumbra/Interop/Processing/TmbPathPreProcessor.cs diff --git a/Penumbra/Interop/PathResolving/PathResolver.cs b/Penumbra/Interop/PathResolving/PathResolver.cs index e069e3ea..f31b3323 100644 --- a/Penumbra/Interop/PathResolving/PathResolver.cs +++ b/Penumbra/Interop/PathResolving/PathResolver.cs @@ -2,6 +2,7 @@ using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; +using Penumbra.Interop.Processing; using Penumbra.Interop.ResourceLoading; using Penumbra.String.Classes; using Penumbra.Util; @@ -15,14 +16,16 @@ public class PathResolver : IDisposable private readonly CollectionManager _collectionManager; private readonly ResourceLoader _loader; - private readonly SubfileHelper _subfileHelper; - private readonly PathState _pathState; - private readonly MetaState _metaState; - private readonly GameState _gameState; - private readonly CollectionResolver _collectionResolver; + private readonly SubfileHelper _subfileHelper; + private readonly PathState _pathState; + private readonly MetaState _metaState; + private readonly GameState _gameState; + private readonly CollectionResolver _collectionResolver; + private readonly GamePathPreProcessService _preprocessor; - public unsafe PathResolver(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ResourceLoader loader, - SubfileHelper subfileHelper, PathState pathState, MetaState metaState, CollectionResolver collectionResolver, GameState gameState) + public PathResolver(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ResourceLoader loader, + SubfileHelper subfileHelper, PathState pathState, MetaState metaState, CollectionResolver collectionResolver, GameState gameState, + GamePathPreProcessService preprocessor) { _performance = performance; _config = config; @@ -31,6 +34,7 @@ public unsafe PathResolver(PerformanceTracker performance, Configuration config, _pathState = pathState; _metaState = metaState; _gameState = gameState; + _preprocessor = preprocessor; _collectionResolver = collectionResolver; _loader = loader; _loader.ResolvePath = ResolvePath; @@ -102,11 +106,10 @@ public unsafe PathResolver(PerformanceTracker performance, Configuration config, // so that the functions loading tex and shpk can find that path and use its collection. // We also need to handle defaulted materials against a non-default collection. var path = resolved == null ? gamePath.Path : resolved.Value.InternalName; - SubfileHelper.HandleCollection(resolveData, path, nonDefault, type, resolved, gamePath, out var pair); - return pair; + return _preprocessor.PreProcess(resolveData, path, nonDefault, type, resolved, gamePath); } - public unsafe void Dispose() + public void Dispose() { _loader.ResetResolvePath(); } diff --git a/Penumbra/Interop/PathResolving/SubfileHelper.cs b/Penumbra/Interop/PathResolving/SubfileHelper.cs index b9631cf2..3cefd98d 100644 --- a/Penumbra/Interop/PathResolving/SubfileHelper.cs +++ b/Penumbra/Interop/PathResolving/SubfileHelper.cs @@ -66,22 +66,6 @@ public bool HandleSubFiles(ResourceType type, out ResolveData collection) return false; } - /// Materials, TMB, and AVFX need to be set per collection, so they can load their sub files independently of each other. - public static void HandleCollection(ResolveData resolveData, ByteString path, bool nonDefault, ResourceType type, FullPath? resolved, - Utf8GamePath originalPath, out (FullPath?, ResolveData) data) - { - resolved = type switch - { - ResourceType.Mtrl when nonDefault => PathDataHandler.CreateMtrl(path, resolveData.ModCollection, originalPath), - ResourceType.Avfx when nonDefault => PathDataHandler.CreateAvfx(path, resolveData.ModCollection), - ResourceType.Tmb when nonDefault => PathDataHandler.CreateTmb(path, resolveData.ModCollection), - ResourceType.Imc when resolveData.ModCollection.MetaCache?.Imc.HasFile(path) ?? false => PathDataHandler.CreateImc(path, - resolveData.ModCollection), - _ => resolved, - }; - data = (resolved, resolveData); - } - public void Dispose() { _loader.ResourceLoaded -= SubfileContainerRequested; diff --git a/Penumbra/Interop/Processing/AvfxPathPreProcessor.cs b/Penumbra/Interop/Processing/AvfxPathPreProcessor.cs new file mode 100644 index 00000000..56f693e6 --- /dev/null +++ b/Penumbra/Interop/Processing/AvfxPathPreProcessor.cs @@ -0,0 +1,16 @@ +using Penumbra.Api.Enums; +using Penumbra.Collections; +using Penumbra.Interop.PathResolving; +using Penumbra.String; +using Penumbra.String.Classes; + +namespace Penumbra.Interop.Processing; + +public sealed class AvfxPathPreProcessor : IPathPreProcessor +{ + public ResourceType Type + => ResourceType.Avfx; + + public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath _, bool nonDefault, FullPath? resolved) + => nonDefault ? PathDataHandler.CreateAvfx(path, resolveData.ModCollection) : resolved; +} diff --git a/Penumbra/Interop/Processing/FilePostProcessService.cs b/Penumbra/Interop/Processing/FilePostProcessService.cs new file mode 100644 index 00000000..0dc62b3d --- /dev/null +++ b/Penumbra/Interop/Processing/FilePostProcessService.cs @@ -0,0 +1,39 @@ +using System.Collections.Frozen; +using OtterGui.Services; +using Penumbra.Api.Enums; +using Penumbra.Interop.ResourceLoading; +using Penumbra.Interop.Structs; +using Penumbra.String; + +namespace Penumbra.Interop.Processing; + +public interface IFilePostProcessor : IService +{ + public ResourceType Type { get; } + public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan additionalData); +} + +public unsafe class FilePostProcessService : IRequiredService, IDisposable +{ + private readonly ResourceLoader _resourceLoader; + private readonly FrozenDictionary _processors; + + public FilePostProcessService(ResourceLoader resourceLoader, ServiceManager services) + { + _resourceLoader = resourceLoader; + _processors = services.GetServicesImplementing().ToFrozenDictionary(s => s.Type, s => s); + _resourceLoader.FileLoaded += OnFileLoaded; + } + + public void Dispose() + { + _resourceLoader.FileLoaded -= OnFileLoaded; + } + + private void OnFileLoaded(ResourceHandle* resource, ByteString path, bool returnValue, bool custom, + ReadOnlySpan additionalData) + { + if (_processors.TryGetValue(resource->FileType, out var processor)) + processor.PostProcess(resource, path, additionalData); + } +} diff --git a/Penumbra/Interop/Processing/GamePathPreProcessService.cs b/Penumbra/Interop/Processing/GamePathPreProcessService.cs new file mode 100644 index 00000000..004b7168 --- /dev/null +++ b/Penumbra/Interop/Processing/GamePathPreProcessService.cs @@ -0,0 +1,37 @@ +using System.Collections.Frozen; +using OtterGui.Services; +using Penumbra.Api.Enums; +using Penumbra.Collections; +using Penumbra.String; +using Penumbra.String.Classes; + +namespace Penumbra.Interop.Processing; + +public interface IPathPreProcessor : IService +{ + public ResourceType Type { get; } + + public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath originalGamePath, bool nonDefault, FullPath? resolved); +} + +public class GamePathPreProcessService : IService +{ + private readonly FrozenDictionary _processors; + + public GamePathPreProcessService(ServiceManager services) + { + _processors = services.GetServicesImplementing().ToFrozenDictionary(s => s.Type, s => s); + } + + + public (FullPath? Path, ResolveData Data) PreProcess(ResolveData resolveData, ByteString path, bool nonDefault, ResourceType type, + FullPath? resolved, + Utf8GamePath originalPath) + { + if (!_processors.TryGetValue(type, out var processor)) + return (resolved, resolveData); + + resolved = processor.PreProcess(resolveData, path, originalPath, nonDefault, resolved); + return (resolved, resolveData); + } +} diff --git a/Penumbra/Interop/Processing/ImcFilePostProcessor.cs b/Penumbra/Interop/Processing/ImcFilePostProcessor.cs new file mode 100644 index 00000000..4a0ebe22 --- /dev/null +++ b/Penumbra/Interop/Processing/ImcFilePostProcessor.cs @@ -0,0 +1,30 @@ +using Penumbra.Api.Enums; +using Penumbra.Collections.Manager; +using Penumbra.Interop.PathResolving; +using Penumbra.Interop.Structs; +using Penumbra.String; + +namespace Penumbra.Interop.Processing; + +public sealed class ImcFilePostProcessor(CollectionStorage collections) : IFilePostProcessor +{ + public ResourceType Type + => ResourceType.Imc; + + public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan additionalData) + { + if (!PathDataHandler.Read(additionalData, out var data) || data.Discriminator != PathDataHandler.Discriminator) + return; + + var collection = collections.ByLocalId(data.Collection); + if (collection.MetaCache is not { } cache) + return; + + if (!cache.Imc.GetFile(originalGamePath, out var file)) + return; + + file.Replace(resource); + Penumbra.Log.Information( + $"[ResourceLoader] Loaded {originalGamePath} from file and replaced with IMC from collection {collection.AnonymizedName}."); + } +} diff --git a/Penumbra/Interop/Processing/ImcPathPreProcessor.cs b/Penumbra/Interop/Processing/ImcPathPreProcessor.cs new file mode 100644 index 00000000..907d7587 --- /dev/null +++ b/Penumbra/Interop/Processing/ImcPathPreProcessor.cs @@ -0,0 +1,18 @@ +using Penumbra.Api.Enums; +using Penumbra.Collections; +using Penumbra.Interop.PathResolving; +using Penumbra.String; +using Penumbra.String.Classes; + +namespace Penumbra.Interop.Processing; + +public sealed class ImcPathPreProcessor : IPathPreProcessor +{ + public ResourceType Type + => ResourceType.Imc; + + public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath originalGamePath, bool _, FullPath? resolved) + => resolveData.ModCollection.MetaCache?.Imc.HasFile(originalGamePath.Path) ?? false + ? PathDataHandler.CreateImc(path, resolveData.ModCollection) + : resolved; +} diff --git a/Penumbra/Interop/Processing/MaterialFilePostProcessor.cs b/Penumbra/Interop/Processing/MaterialFilePostProcessor.cs new file mode 100644 index 00000000..02b5d46c --- /dev/null +++ b/Penumbra/Interop/Processing/MaterialFilePostProcessor.cs @@ -0,0 +1,18 @@ +using Penumbra.Api.Enums; +using Penumbra.Interop.PathResolving; +using Penumbra.Interop.Structs; +using Penumbra.String; + +namespace Penumbra.Interop.Processing; + +public sealed class MaterialFilePostProcessor //: IFilePostProcessor +{ + public ResourceType Type + => ResourceType.Mtrl; + + public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan additionalData) + { + if (!PathDataHandler.ReadMtrl(additionalData, out var data)) + return; + } +} diff --git a/Penumbra/Interop/Processing/MtrlPathPreProcessor.cs b/Penumbra/Interop/Processing/MtrlPathPreProcessor.cs new file mode 100644 index 00000000..8fb2400b --- /dev/null +++ b/Penumbra/Interop/Processing/MtrlPathPreProcessor.cs @@ -0,0 +1,16 @@ +using Penumbra.Api.Enums; +using Penumbra.Collections; +using Penumbra.Interop.PathResolving; +using Penumbra.String; +using Penumbra.String.Classes; + +namespace Penumbra.Interop.Processing; + +public sealed class MtrlPathPreProcessor : IPathPreProcessor +{ + public ResourceType Type + => ResourceType.Mtrl; + + public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath originalGamePath, bool nonDefault, FullPath? resolved) + => nonDefault ? PathDataHandler.CreateMtrl(path, resolveData.ModCollection, originalGamePath) : resolved; +} diff --git a/Penumbra/Interop/Processing/TmbPathPreProcessor.cs b/Penumbra/Interop/Processing/TmbPathPreProcessor.cs new file mode 100644 index 00000000..dd887819 --- /dev/null +++ b/Penumbra/Interop/Processing/TmbPathPreProcessor.cs @@ -0,0 +1,16 @@ +using Penumbra.Api.Enums; +using Penumbra.Collections; +using Penumbra.Interop.PathResolving; +using Penumbra.String; +using Penumbra.String.Classes; + +namespace Penumbra.Interop.Processing; + +public sealed class TmbPathPreProcessor : IPathPreProcessor +{ + public ResourceType Type + => ResourceType.Tmb; + + public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath _, bool nonDefault, FullPath? resolved) + => nonDefault ? PathDataHandler.CreateTmb(path, resolveData.ModCollection) : resolved; +} diff --git a/Penumbra/Interop/ResourceLoading/ResourceLoader.cs b/Penumbra/Interop/ResourceLoading/ResourceLoader.cs index fae38907..7b49beab 100644 --- a/Penumbra/Interop/ResourceLoading/ResourceLoader.cs +++ b/Penumbra/Interop/ResourceLoading/ResourceLoader.cs @@ -1,9 +1,6 @@ -using System.Collections.Frozen; using FFXIVClientStructs.FFXIV.Client.System.Resource; -using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections; -using Penumbra.Collections.Manager; using Penumbra.Interop.PathResolving; using Penumbra.Interop.SafeHandles; using Penumbra.Interop.Structs; @@ -13,72 +10,6 @@ namespace Penumbra.Interop.ResourceLoading; -public interface IFilePostProcessor : IService -{ - public ResourceType Type { get; } - public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan additionalData); -} - -public sealed class MaterialFilePostProcessor : IFilePostProcessor -{ - public ResourceType Type - => ResourceType.Mtrl; - - public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan additionalData) - { - if (!PathDataHandler.ReadMtrl(additionalData, out var data)) - return; - } -} - -public sealed class ImcFilePostProcessor(CollectionStorage collections) : IFilePostProcessor -{ - public ResourceType Type - => ResourceType.Imc; - - public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan additionalData) - { - if (!PathDataHandler.Read(additionalData, out var data) || data.Discriminator != PathDataHandler.Discriminator) - return; - - var collection = collections.ByLocalId(data.Collection); - if (collection.MetaCache is not { } cache) - return; - - if (!cache.Imc.GetFile(originalGamePath, out var file)) - return; - - file.Replace(resource); - Penumbra.Log.Information( - $"[ResourceLoader] Loaded {originalGamePath} from file and replaced with IMC from collection {collection.AnonymizedName}."); - } -} - -public unsafe class FilePostProcessService : IRequiredService, IDisposable -{ - private readonly ResourceLoader _resourceLoader; - private readonly FrozenDictionary _processors; - - public FilePostProcessService(ResourceLoader resourceLoader, ServiceManager services) - { - _resourceLoader = resourceLoader; - _processors = services.GetServicesImplementing().ToFrozenDictionary(s => s.Type, s => s); - _resourceLoader.FileLoaded += OnFileLoaded; - } - - public void Dispose() - { - _resourceLoader.FileLoaded -= OnFileLoaded; - } - - private void OnFileLoaded(ResourceHandle* resource, ByteString path, bool returnValue, bool custom, - ReadOnlySpan additionalData) - { - if (_processors.TryGetValue(resource->FileType, out var processor)) - processor.PostProcess(resource, path, additionalData); - } -} - public unsafe class ResourceLoader : IDisposable { private readonly ResourceService _resources; diff --git a/Penumbra/Interop/Services/CharacterUtility.cs b/Penumbra/Interop/Services/CharacterUtility.cs index 9459df06..0877d221 100644 --- a/Penumbra/Interop/Services/CharacterUtility.cs +++ b/Penumbra/Interop/Services/CharacterUtility.cs @@ -46,9 +46,6 @@ public static readonly InternalIndex[] ReverseIndices private readonly MetaList[] _lists; - public IReadOnlyList Lists - => _lists; - public (nint Address, int Size) DefaultResource(InternalIndex idx) => _lists[idx.Value].DefaultResource; @@ -58,7 +55,7 @@ public CharacterUtility(IFramework framework, IGameInteropProvider interop) { interop.InitializeFromAttributes(this); _lists = Enumerable.Range(0, RelevantIndices.Length) - .Select(idx => new MetaList(this, new InternalIndex(idx))) + .Select(idx => new MetaList(new InternalIndex(idx))) .ToArray(); _framework = framework; LoadingFinished += () => Penumbra.Log.Debug("Loading of CharacterUtility finished."); @@ -124,9 +121,6 @@ public void ResetAll() if (!Ready) return; - foreach (var list in _lists) - list.Dispose(); - Address->HumanPbdResource = (ResourceHandle*)DefaultHumanPbdResource; Address->TransparentTexResource = (TextureResourceHandle*)DefaultTransparentResource; Address->DecalTexResource = (TextureResourceHandle*)DefaultDecalResource; diff --git a/Penumbra/Interop/Services/MetaList.cs b/Penumbra/Interop/Services/MetaList.cs index 24d3f088..839c289e 100644 --- a/Penumbra/Interop/Services/MetaList.cs +++ b/Penumbra/Interop/Services/MetaList.cs @@ -2,26 +2,14 @@ namespace Penumbra.Interop.Services; -public unsafe class MetaList : IDisposable +public class MetaList(CharacterUtility.InternalIndex index) { - private readonly CharacterUtility _utility; - private readonly LinkedList _entries = new(); - public readonly CharacterUtility.InternalIndex Index; - public readonly MetaIndex GlobalMetaIndex; - - public IReadOnlyCollection Entries - => _entries; + public readonly CharacterUtility.InternalIndex Index = index; + public readonly MetaIndex GlobalMetaIndex = CharacterUtility.RelevantIndices[index.Value]; private nint _defaultResourceData = nint.Zero; - private int _defaultResourceSize = 0; - public bool Ready { get; private set; } = false; - - public MetaList(CharacterUtility utility, CharacterUtility.InternalIndex index) - { - _utility = utility; - Index = index; - GlobalMetaIndex = CharacterUtility.RelevantIndices[index.Value]; - } + private int _defaultResourceSize; + public bool Ready { get; private set; } public void SetDefaultResource(nint data, int size) { @@ -31,116 +19,8 @@ public void SetDefaultResource(nint data, int size) _defaultResourceData = data; _defaultResourceSize = size; Ready = _defaultResourceData != nint.Zero && size != 0; - if (_entries.Count <= 0) - return; - - var first = _entries.First!.Value; - SetResource(first.Data, first.Length); } public (nint Address, int Size) DefaultResource => (_defaultResourceData, _defaultResourceSize); - - public MetaReverter TemporarilySetResource(nint data, int length) - { - Penumbra.Log.Excessive($"Temporarily set resource {GlobalMetaIndex} to 0x{(ulong)data:X} ({length} bytes)."); - var reverter = new MetaReverter(this, data, length); - _entries.AddFirst(reverter); - SetResourceInternal(data, length); - return reverter; - } - - public MetaReverter TemporarilyResetResource() - { - Penumbra.Log.Excessive( - $"Temporarily reset resource {GlobalMetaIndex} to default at 0x{_defaultResourceData:X} ({_defaultResourceSize} bytes)."); - var reverter = new MetaReverter(this); - _entries.AddFirst(reverter); - ResetResourceInternal(); - return reverter; - } - - public void SetResource(nint data, int length) - { - Penumbra.Log.Excessive($"Set resource {GlobalMetaIndex} to 0x{(ulong)data:X} ({length} bytes)."); - SetResourceInternal(data, length); - } - - public void ResetResource() - { - Penumbra.Log.Excessive($"Reset resource {GlobalMetaIndex} to default at 0x{_defaultResourceData:X} ({_defaultResourceSize} bytes)."); - ResetResourceInternal(); - } - - /// Set the currently stored data of this resource to new values. - private void SetResourceInternal(nint data, int length) - { - if (!Ready) - return; - - var resource = _utility.Address->Resource(GlobalMetaIndex); - resource->SetData(data, length); - } - - /// Reset the currently stored data of this resource to its default values. - private void ResetResourceInternal() - => SetResourceInternal(_defaultResourceData, _defaultResourceSize); - - private void SetResourceToDefaultCollection() - {} - - public void Dispose() - { - if (_entries.Count > 0) - { - foreach (var entry in _entries) - entry.Disposed = true; - - _entries.Clear(); - } - - ResetResourceInternal(); - } - - public sealed class MetaReverter(MetaList metaList, nint data, int length) : IDisposable - { - public static readonly MetaReverter Disabled = new(null!) { Disposed = true }; - - public readonly MetaList MetaList = metaList; - public readonly nint Data = data; - public readonly int Length = length; - public readonly bool Resetter; - public bool Disposed; - - public MetaReverter(MetaList metaList) - : this(metaList, nint.Zero, 0) - => Resetter = true; - - public void Dispose() - { - if (Disposed) - return; - - var list = MetaList._entries; - var wasCurrent = ReferenceEquals(this, list.First?.Value); - list.Remove(this); - if (!wasCurrent) - return; - - if (list.Count == 0) - { - MetaList.SetResourceToDefaultCollection(); - } - else - { - var next = list.First!.Value; - if (next.Resetter) - MetaList.ResetResourceInternal(); - else - MetaList.SetResourceInternal(next.Data, next.Length); - } - - Disposed = true; - } - } } diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index 396029d5..1519ebf0 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -179,8 +179,6 @@ public void DrawContent() ImGui.NewLine(); DrawData(); ImGui.NewLine(); - DrawDebugTabMetaLists(); - ImGui.NewLine(); DrawResourceProblems(); ImGui.NewLine(); DrawPlayerModelInfo(); @@ -788,23 +786,6 @@ private unsafe void DrawDebugCharacterUtility() } } - private void DrawDebugTabMetaLists() - { - if (!ImGui.CollapsingHeader("Metadata Changes")) - return; - - using var table = Table("##DebugMetaTable", 3, ImGuiTableFlags.SizingFixedFit); - if (!table) - return; - - foreach (var list in _characterUtility.Lists) - { - ImGuiUtil.DrawTableColumn(list.GlobalMetaIndex.ToString()); - ImGuiUtil.DrawTableColumn(list.Entries.Count.ToString()); - ImGuiUtil.DrawTableColumn(string.Join(", ", list.Entries.Select(e => $"0x{e.Data:X}"))); - } - } - /// Draw information about the resident resource files. private unsafe void DrawDebugResidentResources() { From e05dbe988570d949f6af81f65b3073c875fb9fb9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 18 Jun 2024 21:59:04 +0200 Subject: [PATCH 28/28] Make everything services. --- OtterGui | 2 +- Penumbra/Api/TempModManager.cs | 3 +- .../Cache/CollectionCacheManager.cs | 3 +- .../Collections/Manager/ActiveCollections.cs | 5 +- .../Collections/Manager/CollectionEditor.cs | 3 +- .../Collections/Manager/CollectionManager.cs | 3 +- .../Collections/Manager/CollectionStorage.cs | 5 +- .../Collections/Manager/InheritanceManager.cs | 14 +- .../Manager/TempCollectionManager.cs | 3 +- Penumbra/CommandHandler.cs | 4 +- Penumbra/Configuration.cs | 3 +- Penumbra/EphemeralConfig.cs | 3 +- Penumbra/Import/Models/ModelManager.cs | 4 +- Penumbra/Import/Textures/TextureManager.cs | 26 ++- .../Interop/PathResolving/CutsceneService.cs | 3 +- .../IdentifiedCollectionCache.cs | 4 +- Penumbra/Interop/PathResolving/MetaState.cs | 3 +- .../Interop/PathResolving/PathResolver.cs | 3 +- Penumbra/Interop/PathResolving/PathState.cs | 3 +- .../Interop/PathResolving/SubfileHelper.cs | 4 +- .../ResourceLoading/FileReadService.cs | 3 +- .../Interop/ResourceLoading/ResourceLoader.cs | 5 +- .../ResourceLoading/ResourceManagerService.cs | 3 +- .../ResourceLoading/ResourceService.cs | 3 +- .../Interop/ResourceLoading/TexMdlService.cs | 3 +- .../ResourceTree/ResourceTreeFactory.cs | 3 +- Penumbra/Interop/Services/CharacterUtility.cs | 3 +- Penumbra/Interop/Services/FontReloader.cs | 3 +- Penumbra/Interop/Services/ModelRenderer.cs | 9 +- Penumbra/Interop/Services/RedrawService.cs | 11 +- .../Services/ResidentResourceManager.cs | 5 +- Penumbra/Meta/MetaFileManager.cs | 3 +- Penumbra/Mods/Editor/DuplicateManager.cs | 3 +- Penumbra/Mods/Editor/MdlMaterialEditor.cs | 3 +- Penumbra/Mods/Editor/ModEditor.cs | 4 +- Penumbra/Mods/Editor/ModFileCollection.cs | 3 +- Penumbra/Mods/Editor/ModFileEditor.cs | 3 +- Penumbra/Mods/Editor/ModMerger.cs | 3 +- Penumbra/Mods/Editor/ModNormalizer.cs | 4 +- Penumbra/Mods/Editor/ModSwapEditor.cs | 4 +- Penumbra/Mods/Manager/ModCacheManager.cs | 3 +- Penumbra/Mods/Manager/ModDataEditor.cs | 5 +- Penumbra/Mods/Manager/ModExportManager.cs | 3 +- Penumbra/Mods/Manager/ModFileSystem.cs | 5 +- Penumbra/Mods/Manager/ModImportManager.cs | 6 +- Penumbra/Mods/Manager/ModManager.cs | 3 +- Penumbra/Mods/ModCreator.cs | 3 +- Penumbra/Services/StaticServiceManager.cs | 154 +----------------- Penumbra/UI/AdvancedWindow/ItemSwapTab.cs | 3 +- Penumbra/UI/AdvancedWindow/ModEditWindow.cs | 3 +- Penumbra/UI/AdvancedWindow/ModMergeTab.cs | 5 +- Penumbra/UI/ChangedItemDrawer.cs | 5 +- Penumbra/UI/Changelog.cs | 3 +- Penumbra/UI/Classes/CollectionSelectHeader.cs | 3 +- Penumbra/UI/ConfigWindow.cs | 3 +- Penumbra/UI/FileDialogService.cs | 3 +- Penumbra/UI/ImportPopup.cs | 3 +- Penumbra/UI/LaunchButton.cs | 3 +- Penumbra/UI/ModsTab/ModFileSystemSelector.cs | 9 +- Penumbra/UI/ModsTab/ModPanel.cs | 3 +- .../UI/ModsTab/ModPanelChangedItemsTab.cs | 28 +--- Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs | 18 +- Penumbra/UI/ModsTab/ModPanelConflictsTab.cs | 3 +- Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs | 3 +- Penumbra/UI/ModsTab/ModPanelEditTab.cs | 4 +- Penumbra/UI/ModsTab/ModPanelTabBar.cs | 11 +- Penumbra/UI/ModsTab/MultiModPanel.cs | 7 +- Penumbra/UI/PredefinedTagManager.cs | 4 +- .../UI/ResourceWatcher/ResourceWatcher.cs | 3 +- Penumbra/UI/Tabs/ChangedItemsTab.cs | 3 +- Penumbra/UI/Tabs/CollectionsTab.cs | 3 +- Penumbra/UI/Tabs/ConfigTabBar.cs | 3 +- Penumbra/UI/Tabs/Debug/DebugTab.cs | 6 +- Penumbra/UI/Tabs/EffectiveTab.cs | 3 +- Penumbra/UI/Tabs/MessagesTab.cs | 3 +- Penumbra/UI/Tabs/ModsTab.cs | 3 +- Penumbra/UI/Tabs/OnScreenTab.cs | 10 +- Penumbra/UI/Tabs/ResourceTab.cs | 3 +- Penumbra/UI/Tabs/SettingsTab.cs | 3 +- Penumbra/UI/TutorialService.cs | 3 +- Penumbra/UI/WindowSystem.cs | 3 +- 81 files changed, 220 insertions(+), 317 deletions(-) diff --git a/OtterGui b/OtterGui index e95c0f04..caa9e9b9 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit e95c0f04edc7e85aea67498fd8bf495a7fe6d3c8 +Subproject commit caa9e9b9a5dc3928eba10b315cf6a0f6f1d84b65 diff --git a/Penumbra/Api/TempModManager.cs b/Penumbra/Api/TempModManager.cs index cbb07436..0b52e64a 100644 --- a/Penumbra/Api/TempModManager.cs +++ b/Penumbra/Api/TempModManager.cs @@ -1,3 +1,4 @@ +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Meta.Manipulations; @@ -18,7 +19,7 @@ public enum RedirectResult FilteredGamePath = 3, } -public class TempModManager : IDisposable +public class TempModManager : IDisposable, IService { private readonly CommunicatorService _communicator; diff --git a/Penumbra/Collections/Cache/CollectionCacheManager.cs b/Penumbra/Collections/Cache/CollectionCacheManager.cs index 02c9c8a9..44c12856 100644 --- a/Penumbra/Collections/Cache/CollectionCacheManager.cs +++ b/Penumbra/Collections/Cache/CollectionCacheManager.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin.Services; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Api; using Penumbra.Api.Enums; using Penumbra.Collections.Manager; @@ -17,7 +18,7 @@ namespace Penumbra.Collections.Cache; -public class CollectionCacheManager : IDisposable +public class CollectionCacheManager : IDisposable, IService { private readonly FrameworkManager _framework; private readonly CommunicatorService _communicator; diff --git a/Penumbra/Collections/Manager/ActiveCollections.cs b/Penumbra/Collections/Manager/ActiveCollections.cs index 4e8ebe36..6d48f74b 100644 --- a/Penumbra/Collections/Manager/ActiveCollections.cs +++ b/Penumbra/Collections/Manager/ActiveCollections.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Communication; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; @@ -11,7 +12,7 @@ namespace Penumbra.Collections.Manager; -public class ActiveCollectionData +public class ActiveCollectionData : IService { public ModCollection Current { get; internal set; } = ModCollection.Empty; public ModCollection Default { get; internal set; } = ModCollection.Empty; @@ -20,7 +21,7 @@ public class ActiveCollectionData public readonly ModCollection?[] SpecialCollections = new ModCollection?[Enum.GetValues().Length - 3]; } -public class ActiveCollections : ISavable, IDisposable +public class ActiveCollections : ISavable, IDisposable, IService { public const int Version = 2; diff --git a/Penumbra/Collections/Manager/CollectionEditor.cs b/Penumbra/Collections/Manager/CollectionEditor.cs index 0243de1e..caff2c86 100644 --- a/Penumbra/Collections/Manager/CollectionEditor.cs +++ b/Penumbra/Collections/Manager/CollectionEditor.cs @@ -1,4 +1,5 @@ using OtterGui; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Mods; using Penumbra.Mods.Manager; @@ -7,7 +8,7 @@ namespace Penumbra.Collections.Manager; -public class CollectionEditor(SaveService saveService, CommunicatorService communicator, ModStorage modStorage) +public class CollectionEditor(SaveService saveService, CommunicatorService communicator, ModStorage modStorage) : IService { /// Enable or disable the mod inheritance of mod idx. public bool SetModInheritance(ModCollection collection, Mod mod, bool inherit) diff --git a/Penumbra/Collections/Manager/CollectionManager.cs b/Penumbra/Collections/Manager/CollectionManager.cs index e95617b1..85f5b957 100644 --- a/Penumbra/Collections/Manager/CollectionManager.cs +++ b/Penumbra/Collections/Manager/CollectionManager.cs @@ -1,3 +1,4 @@ +using OtterGui.Services; using Penumbra.Collections.Cache; namespace Penumbra.Collections.Manager; @@ -8,7 +9,7 @@ public class CollectionManager( InheritanceManager inheritances, CollectionCacheManager caches, TempCollectionManager temp, - CollectionEditor editor) + CollectionEditor editor) : IService { public readonly CollectionStorage Storage = storage; public readonly ActiveCollections Active = active; diff --git a/Penumbra/Collections/Manager/CollectionStorage.cs b/Penumbra/Collections/Manager/CollectionStorage.cs index 67de3a03..cd680d36 100644 --- a/Penumbra/Collections/Manager/CollectionStorage.cs +++ b/Penumbra/Collections/Manager/CollectionStorage.cs @@ -1,7 +1,7 @@ -using System; using Dalamud.Interface.Internal.Notifications; using OtterGui; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Communication; using Penumbra.Mods; using Penumbra.Mods.Editor; @@ -11,7 +11,6 @@ using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; using Penumbra.Services; -using Penumbra.UI.CollectionTab; namespace Penumbra.Collections.Manager; @@ -24,7 +23,7 @@ public readonly record struct LocalCollectionId(int Id) : IAdditionOperators new(left.Id + right); } -public class CollectionStorage : IReadOnlyList, IDisposable +public class CollectionStorage : IReadOnlyList, IDisposable, IService { private readonly CommunicatorService _communicator; private readonly SaveService _saveService; diff --git a/Penumbra/Collections/Manager/InheritanceManager.cs b/Penumbra/Collections/Manager/InheritanceManager.cs index 6003b5f9..f3482cdf 100644 --- a/Penumbra/Collections/Manager/InheritanceManager.cs +++ b/Penumbra/Collections/Manager/InheritanceManager.cs @@ -2,11 +2,10 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Filesystem; +using OtterGui.Services; using Penumbra.Communication; using Penumbra.Mods.Manager; using Penumbra.Services; -using Penumbra.UI.CollectionTab; -using Penumbra.Util; namespace Penumbra.Collections.Manager; @@ -15,7 +14,7 @@ namespace Penumbra.Collections.Manager; /// This is transitive, so a collection A inheriting from B also inherits from everything B inherits. /// Circular dependencies are resolved by distinctness. /// -public class InheritanceManager : IDisposable +public class InheritanceManager : IDisposable, IService { public enum ValidInheritance { @@ -144,7 +143,8 @@ private void ApplyInheritances() continue; changes = true; - Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.", NotificationType.Warning); + Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.", + NotificationType.Warning); } else if (_storage.ByName(subCollectionName, out subCollection)) { @@ -153,12 +153,14 @@ private void ApplyInheritances() if (AddInheritance(collection, subCollection, false)) continue; - Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.", NotificationType.Warning); + Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.", + NotificationType.Warning); } else { Penumbra.Messager.NotificationMessage( - $"Inherited collection {subCollectionName} for {collection.AnonymizedName} does not exist, it was removed.", NotificationType.Warning); + $"Inherited collection {subCollectionName} for {collection.AnonymizedName} does not exist, it was removed.", + NotificationType.Warning); changes = true; } } diff --git a/Penumbra/Collections/Manager/TempCollectionManager.cs b/Penumbra/Collections/Manager/TempCollectionManager.cs index ce438a6b..5c893232 100644 --- a/Penumbra/Collections/Manager/TempCollectionManager.cs +++ b/Penumbra/Collections/Manager/TempCollectionManager.cs @@ -1,4 +1,5 @@ using OtterGui; +using OtterGui.Services; using Penumbra.Api; using Penumbra.Communication; using Penumbra.GameData.Actors; @@ -8,7 +9,7 @@ namespace Penumbra.Collections.Manager; -public class TempCollectionManager : IDisposable +public class TempCollectionManager : IDisposable, IService { public int GlobalChangeCounter { get; private set; } public readonly IndividualCollections Collections; diff --git a/Penumbra/CommandHandler.cs b/Penumbra/CommandHandler.cs index 4e1d6453..484dd954 100644 --- a/Penumbra/CommandHandler.cs +++ b/Penumbra/CommandHandler.cs @@ -3,6 +3,7 @@ using Dalamud.Plugin.Services; using ImGuiNET; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; @@ -10,12 +11,11 @@ using Penumbra.Interop.Services; using Penumbra.Mods; using Penumbra.Mods.Manager; -using Penumbra.Services; using Penumbra.UI; namespace Penumbra; -public class CommandHandler : IDisposable +public class CommandHandler : IDisposable, IApiService { private const string CommandName = "/penumbra"; diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 02286cc7..f6100b62 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -4,6 +4,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Filesystem; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Import.Structs; using Penumbra.Interop.Services; @@ -18,7 +19,7 @@ namespace Penumbra; [Serializable] -public class Configuration : IPluginConfiguration, ISavable +public class Configuration : IPluginConfiguration, ISavable, IService { [JsonIgnore] private readonly SaveService _saveService; diff --git a/Penumbra/EphemeralConfig.cs b/Penumbra/EphemeralConfig.cs index 0a542d04..52e276c7 100644 --- a/Penumbra/EphemeralConfig.cs +++ b/Penumbra/EphemeralConfig.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.Internal.Notifications; using Newtonsoft.Json; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Communication; using Penumbra.Enums; @@ -14,7 +15,7 @@ namespace Penumbra; -public class EphemeralConfig : ISavable, IDisposable +public class EphemeralConfig : ISavable, IDisposable, IService { [JsonIgnore] private readonly SaveService _saveService; diff --git a/Penumbra/Import/Models/ModelManager.cs b/Penumbra/Import/Models/ModelManager.cs index 9fa77784..01396cfb 100644 --- a/Penumbra/Import/Models/ModelManager.cs +++ b/Penumbra/Import/Models/ModelManager.cs @@ -1,6 +1,7 @@ using Dalamud.Plugin.Services; using Lumina.Data.Parsing; using OtterGui; +using OtterGui.Services; using OtterGui.Tasks; using Penumbra.Collections.Manager; using Penumbra.GameData; @@ -21,7 +22,8 @@ namespace Penumbra.Import.Models; using Schema2 = SharpGLTF.Schema2; using LuminaMaterial = Lumina.Models.Materials.Material; -public sealed class ModelManager(IFramework framework, ActiveCollections collections, GamePathParser parser) : SingleTaskQueue, IDisposable +public sealed class ModelManager(IFramework framework, ActiveCollections collections, GamePathParser parser) + : SingleTaskQueue, IDisposable, IService { private readonly IFramework _framework = framework; diff --git a/Penumbra/Import/Textures/TextureManager.cs b/Penumbra/Import/Textures/TextureManager.cs index 976bc179..4aa64209 100644 --- a/Penumbra/Import/Textures/TextureManager.cs +++ b/Penumbra/Import/Textures/TextureManager.cs @@ -3,6 +3,7 @@ using Dalamud.Plugin.Services; using Lumina.Data.Files; using OtterGui.Log; +using OtterGui.Services; using OtterGui.Tasks; using OtterTex; using SixLabors.ImageSharp; @@ -12,22 +13,14 @@ namespace Penumbra.Import.Textures; -public sealed class TextureManager : SingleTaskQueue, IDisposable +public sealed class TextureManager(UiBuilder uiBuilder, IDataManager gameData, Logger logger) + : SingleTaskQueue, IDisposable, IService { - private readonly Logger _logger; - private readonly UiBuilder _uiBuilder; - private readonly IDataManager _gameData; + private readonly Logger _logger = logger; - private readonly ConcurrentDictionary _tasks = new(); + private readonly ConcurrentDictionary _tasks = new(); private bool _disposed; - public TextureManager(UiBuilder uiBuilder, IDataManager gameData, Logger logger) - { - _uiBuilder = uiBuilder; - _gameData = gameData; - _logger = logger; - } - public IReadOnlyDictionary Tasks => _tasks; @@ -64,7 +57,8 @@ private Task Enqueue(IAction action) { var token = new CancellationTokenSource(); var task = Enqueue(a, token.Token); - task.ContinueWith(_ => _tasks.TryRemove(a, out var unused), CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); + task.ContinueWith(_ => _tasks.TryRemove(a, out var unused), CancellationToken.None, TaskContinuationOptions.None, + TaskScheduler.Default); return (task, token); }).Item1; } @@ -217,7 +211,7 @@ public IDalamudTextureWrap LoadTextureWrap(BaseImage image, byte[]? rgba = null, /// Load a texture wrap for a given image. public IDalamudTextureWrap LoadTextureWrap(byte[] rgba, int width, int height) - => _uiBuilder.LoadImageRaw(rgba, width, height, 4); + => uiBuilder.LoadImageRaw(rgba, width, height, 4); /// Load any supported file from game data or drive depending on extension and if the path is rooted. public (BaseImage, TextureType) Load(string path) @@ -326,7 +320,7 @@ public static BaseImage ConvertToDds(byte[] rgba, int width, int height) } public bool GameFileExists(string path) - => _gameData.FileExists(path); + => gameData.FileExists(path); /// Add up to 13 mip maps to the input if mip maps is true, otherwise return input. public static ScratchImage AddMipMaps(ScratchImage input, bool mipMaps) @@ -382,7 +376,7 @@ private Stream OpenTexStream(string path) if (Path.IsPathRooted(path)) return File.OpenRead(path); - var file = _gameData.GetFile(path); + var file = gameData.GetFile(path); return file != null ? new MemoryStream(file.Data) : throw new Exception($"Unable to obtain \"{path}\" from game files."); } diff --git a/Penumbra/Interop/PathResolving/CutsceneService.cs b/Penumbra/Interop/PathResolving/CutsceneService.cs index 93fee11e..feb27341 100644 --- a/Penumbra/Interop/PathResolving/CutsceneService.cs +++ b/Penumbra/Interop/PathResolving/CutsceneService.cs @@ -1,6 +1,5 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Game.Object; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -9,7 +8,7 @@ namespace Penumbra.Interop.PathResolving; -public sealed class CutsceneService : IService, IDisposable +public sealed class CutsceneService : IRequiredService, IDisposable { public const int CutsceneStartIdx = (int)ScreenActor.CutsceneStart; public const int CutsceneEndIdx = (int)ScreenActor.CutsceneEnd; diff --git a/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs b/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs index 32090f7c..eeff7eee 100644 --- a/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs +++ b/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs @@ -1,6 +1,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; +using OtterGui.Services; using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.Communication; @@ -10,7 +11,8 @@ namespace Penumbra.Interop.PathResolving; -public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(nint Address, ActorIdentifier Identifier, ModCollection Collection)> +public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(nint Address, ActorIdentifier Identifier, ModCollection Collection)>, + IService { private readonly CommunicatorService _communicator; private readonly CharacterDestructor _characterDestructor; diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index 7f820b4e..f7dcfc07 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -1,5 +1,6 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Collections; using Penumbra.Api.Enums; using Penumbra.GameData.Structs; @@ -34,7 +35,7 @@ namespace Penumbra.Interop.PathResolving; // ChangeCustomize and RspSetupCharacter, which is hooked here, as well as Character.CalculateHeight. // GMP Entries seem to be only used by "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", which is SetupVisor. -public sealed unsafe class MetaState : IDisposable +public sealed unsafe class MetaState : IDisposable, IService { private readonly Configuration _config; private readonly CommunicatorService _communicator; diff --git a/Penumbra/Interop/PathResolving/PathResolver.cs b/Penumbra/Interop/PathResolving/PathResolver.cs index f31b3323..cc3e0e9b 100644 --- a/Penumbra/Interop/PathResolving/PathResolver.cs +++ b/Penumbra/Interop/PathResolving/PathResolver.cs @@ -1,4 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; @@ -9,7 +10,7 @@ namespace Penumbra.Interop.PathResolving; -public class PathResolver : IDisposable +public class PathResolver : IDisposable, IService { private readonly PerformanceTracker _performance; private readonly Configuration _config; diff --git a/Penumbra/Interop/PathResolving/PathState.cs b/Penumbra/Interop/PathResolving/PathState.cs index f4218e9c..bf9d1e25 100644 --- a/Penumbra/Interop/PathResolving/PathState.cs +++ b/Penumbra/Interop/PathResolving/PathState.cs @@ -1,3 +1,4 @@ +using OtterGui.Services; using Penumbra.Collections; using Penumbra.Interop.Services; using Penumbra.String; @@ -5,7 +6,7 @@ namespace Penumbra.Interop.PathResolving; public sealed class PathState(CollectionResolver collectionResolver, MetaState metaState, CharacterUtility characterUtility) - : IDisposable + : IDisposable, IService { public readonly CollectionResolver CollectionResolver = collectionResolver; public readonly MetaState MetaState = metaState; diff --git a/Penumbra/Interop/PathResolving/SubfileHelper.cs b/Penumbra/Interop/PathResolving/SubfileHelper.cs index 3cefd98d..44a152f0 100644 --- a/Penumbra/Interop/PathResolving/SubfileHelper.cs +++ b/Penumbra/Interop/PathResolving/SubfileHelper.cs @@ -1,9 +1,9 @@ +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Interop.Hooks.Resources; using Penumbra.Interop.ResourceLoading; using Penumbra.Interop.Structs; -using Penumbra.String; using Penumbra.String.Classes; namespace Penumbra.Interop.PathResolving; @@ -13,7 +13,7 @@ namespace Penumbra.Interop.PathResolving; /// Those are loaded synchronously. /// Thus, we need to ensure the correct files are loaded when a material is loaded. /// -public sealed unsafe class SubfileHelper : IDisposable, IReadOnlyCollection> +public sealed unsafe class SubfileHelper : IDisposable, IReadOnlyCollection>, IService { private readonly GameState _gameState; private readonly ResourceLoader _loader; diff --git a/Penumbra/Interop/ResourceLoading/FileReadService.cs b/Penumbra/Interop/ResourceLoading/FileReadService.cs index 64442771..f1d7fe24 100644 --- a/Penumbra/Interop/ResourceLoading/FileReadService.cs +++ b/Penumbra/Interop/ResourceLoading/FileReadService.cs @@ -1,13 +1,14 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; +using OtterGui.Services; using Penumbra.GameData; using Penumbra.Interop.Structs; using Penumbra.Util; namespace Penumbra.Interop.ResourceLoading; -public unsafe class FileReadService : IDisposable +public unsafe class FileReadService : IDisposable, IRequiredService { public FileReadService(PerformanceTracker performance, ResourceManagerService resourceManager, IGameInteropProvider interop) { diff --git a/Penumbra/Interop/ResourceLoading/ResourceLoader.cs b/Penumbra/Interop/ResourceLoading/ResourceLoader.cs index 7b49beab..4a423993 100644 --- a/Penumbra/Interop/ResourceLoading/ResourceLoader.cs +++ b/Penumbra/Interop/ResourceLoading/ResourceLoader.cs @@ -1,4 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Interop.PathResolving; @@ -10,7 +11,7 @@ namespace Penumbra.Interop.ResourceLoading; -public unsafe class ResourceLoader : IDisposable +public unsafe class ResourceLoader : IDisposable, IService { private readonly ResourceService _resources; private readonly FileReadService _fileReadService; @@ -212,7 +213,7 @@ private void IncRefProtection(ResourceHandle* handle, ref nint? returnValue) /// /// Catch weird errors with invalid decrements of the reference count. /// - private void DecRefProtection(ResourceHandle* handle, ref byte? returnValue) + private static void DecRefProtection(ResourceHandle* handle, ref byte? returnValue) { if (handle->RefCount != 0) return; diff --git a/Penumbra/Interop/ResourceLoading/ResourceManagerService.cs b/Penumbra/Interop/ResourceLoading/ResourceManagerService.cs index a087a659..c885c317 100644 --- a/Penumbra/Interop/ResourceLoading/ResourceManagerService.cs +++ b/Penumbra/Interop/ResourceLoading/ResourceManagerService.cs @@ -4,12 +4,13 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using FFXIVClientStructs.STD; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.GameData; namespace Penumbra.Interop.ResourceLoading; -public unsafe class ResourceManagerService +public unsafe class ResourceManagerService : IRequiredService { public ResourceManagerService(IGameInteropProvider interop) => interop.InitializeFromAttributes(this); diff --git a/Penumbra/Interop/ResourceLoading/ResourceService.cs b/Penumbra/Interop/ResourceLoading/ResourceService.cs index e3338e6c..54c86777 100644 --- a/Penumbra/Interop/ResourceLoading/ResourceService.cs +++ b/Penumbra/Interop/ResourceLoading/ResourceService.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.GameData; using Penumbra.Interop.SafeHandles; @@ -13,7 +14,7 @@ namespace Penumbra.Interop.ResourceLoading; -public unsafe class ResourceService : IDisposable +public unsafe class ResourceService : IDisposable, IRequiredService { private readonly PerformanceTracker _performance; private readonly ResourceManagerService _resourceManager; diff --git a/Penumbra/Interop/ResourceLoading/TexMdlService.cs b/Penumbra/Interop/ResourceLoading/TexMdlService.cs index b9279f54..e617673e 100644 --- a/Penumbra/Interop/ResourceLoading/TexMdlService.cs +++ b/Penumbra/Interop/ResourceLoading/TexMdlService.cs @@ -2,13 +2,14 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.GameData; using Penumbra.String.Classes; namespace Penumbra.Interop.ResourceLoading; -public unsafe class TexMdlService : IDisposable +public unsafe class TexMdlService : IDisposable, IRequiredService { /// Custom ulong flag to signal our files as opposed to SE files. public static readonly IntPtr CustomFileFlag = new(0xDEADBEEF); diff --git a/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs b/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs index 5a190e52..e26c1436 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Object; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.GameData.Actors; using Penumbra.GameData.Data; @@ -17,7 +18,7 @@ public class ResourceTreeFactory( ObjectIdentification identifier, Configuration config, ActorManager actors, - PathState pathState) + PathState pathState) : IService { private TreeBuildCache CreateTreeBuildCache() => new(objects, gameData, actors); diff --git a/Penumbra/Interop/Services/CharacterUtility.cs b/Penumbra/Interop/Services/CharacterUtility.cs index 0877d221..532dc823 100644 --- a/Penumbra/Interop/Services/CharacterUtility.cs +++ b/Penumbra/Interop/Services/CharacterUtility.cs @@ -1,11 +1,12 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; +using OtterGui.Services; using Penumbra.GameData; using Penumbra.Interop.Structs; namespace Penumbra.Interop.Services; -public unsafe class CharacterUtility : IDisposable +public unsafe class CharacterUtility : IDisposable, IRequiredService { public record struct InternalIndex(int Value); diff --git a/Penumbra/Interop/Services/FontReloader.cs b/Penumbra/Interop/Services/FontReloader.cs index 2f4a3cfd..259fdd10 100644 --- a/Penumbra/Interop/Services/FontReloader.cs +++ b/Penumbra/Interop/Services/FontReloader.cs @@ -1,6 +1,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Component.GUI; +using OtterGui.Services; using Penumbra.GameData; namespace Penumbra.Interop.Services; @@ -9,7 +10,7 @@ namespace Penumbra.Interop.Services; /// Handle font reloading via game functions. /// May cause a interface flicker while reloading. /// -public unsafe class FontReloader +public unsafe class FontReloader : IService { public bool Valid => _reloadFontsFunc != null; diff --git a/Penumbra/Interop/Services/ModelRenderer.cs b/Penumbra/Interop/Services/ModelRenderer.cs index 7df83cf7..b268b395 100644 --- a/Penumbra/Interop/Services/ModelRenderer.cs +++ b/Penumbra/Interop/Services/ModelRenderer.cs @@ -1,10 +1,11 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using OtterGui.Services; namespace Penumbra.Interop.Services; -public unsafe class ModelRenderer : IDisposable +public unsafe class ModelRenderer : IDisposable, IRequiredService { public bool Ready { get; private set; } @@ -37,14 +38,14 @@ private void LoadDefaultResources(object _) if (DefaultCharacterGlassShaderPackage == null) { - DefaultCharacterGlassShaderPackage = *CharacterGlassShaderPackage; - anyMissing |= DefaultCharacterGlassShaderPackage == null; + DefaultCharacterGlassShaderPackage = *CharacterGlassShaderPackage; + anyMissing |= DefaultCharacterGlassShaderPackage == null; } if (anyMissing) return; - Ready = true; + Ready = true; _framework.Update -= LoadDefaultResources; } diff --git a/Penumbra/Interop/Services/RedrawService.cs b/Penumbra/Interop/Services/RedrawService.cs index 21ecfd4f..61d7b90c 100644 --- a/Penumbra/Interop/Services/RedrawService.cs +++ b/Penumbra/Interop/Services/RedrawService.cs @@ -6,6 +6,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Housing; using FFXIVClientStructs.Interop; +using OtterGui.Services; using Penumbra.Api; using Penumbra.Api.Enums; using Penumbra.Communication; @@ -20,7 +21,7 @@ namespace Penumbra.Interop.Services; -public unsafe partial class RedrawService +public unsafe partial class RedrawService : IService { public const int GPosePlayerIdx = 201; public const int GPoseSlots = 42; @@ -171,7 +172,8 @@ private void WriteInvisible(GameObject? actor) if (gPose) DisableDraw(actor!); - if (actor is PlayerCharacter && _objects.GetDalamudObject(tableIndex + 1) is { ObjectKind: ObjectKind.MountType or ObjectKind.Ornament } mountOrOrnament) + if (actor is PlayerCharacter + && _objects.GetDalamudObject(tableIndex + 1) is { ObjectKind: ObjectKind.MountType or ObjectKind.Ornament } mountOrOrnament) { *ActorDrawState(mountOrOrnament) |= DrawState.Invisibility; if (gPose) @@ -190,7 +192,8 @@ private void WriteVisible(GameObject? actor) if (gPose) EnableDraw(actor!); - if (actor is PlayerCharacter && _objects.GetDalamudObject(tableIndex + 1) is { ObjectKind: ObjectKind.MountType or ObjectKind.Ornament } mountOrOrnament) + if (actor is PlayerCharacter + && _objects.GetDalamudObject(tableIndex + 1) is { ObjectKind: ObjectKind.MountType or ObjectKind.Ornament } mountOrOrnament) { *ActorDrawState(mountOrOrnament) &= ~DrawState.Invisibility; if (gPose) @@ -380,7 +383,7 @@ public bool GetName(string lowerName, out GameObject? actor) if (!ret && lowerName.Length > 1 && lowerName[0] == '#' && ushort.TryParse(lowerName[1..], out var objectIndex)) { ret = true; - actor = _objects.GetDalamudObject((int) objectIndex); + actor = _objects.GetDalamudObject((int)objectIndex); } return ret; diff --git a/Penumbra/Interop/Services/ResidentResourceManager.cs b/Penumbra/Interop/Services/ResidentResourceManager.cs index 72697185..4f430aa1 100644 --- a/Penumbra/Interop/Services/ResidentResourceManager.cs +++ b/Penumbra/Interop/Services/ResidentResourceManager.cs @@ -1,10 +1,11 @@ -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; +using OtterGui.Services; using Penumbra.GameData; namespace Penumbra.Interop.Services; -public unsafe class ResidentResourceManager +public unsafe class ResidentResourceManager : IService { // A static pointer to the resident resource manager address. [Signature(Sigs.ResidentResourceManager, ScanType = ScanType.StaticAddress)] diff --git a/Penumbra/Meta/MetaFileManager.cs b/Penumbra/Meta/MetaFileManager.cs index 81c0fa3e..3755afa2 100644 --- a/Penumbra/Meta/MetaFileManager.cs +++ b/Penumbra/Meta/MetaFileManager.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin.Services; using OtterGui.Compression; +using OtterGui.Services; using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.GameData.Data; @@ -13,7 +14,7 @@ namespace Penumbra.Meta; -public unsafe class MetaFileManager +public class MetaFileManager : IService { internal readonly Configuration Config; internal readonly CharacterUtility CharacterUtility; diff --git a/Penumbra/Mods/Editor/DuplicateManager.cs b/Penumbra/Mods/Editor/DuplicateManager.cs index 47aa18dc..bcecf264 100644 --- a/Penumbra/Mods/Editor/DuplicateManager.cs +++ b/Penumbra/Mods/Editor/DuplicateManager.cs @@ -1,4 +1,5 @@ using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Mods.Groups; using Penumbra.Mods.Manager; using Penumbra.Mods.SubMods; @@ -7,7 +8,7 @@ namespace Penumbra.Mods.Editor; -public class DuplicateManager(ModManager modManager, SaveService saveService, Configuration config) +public class DuplicateManager(ModManager modManager, SaveService saveService, Configuration config) : IService { private readonly SHA256 _hasher = SHA256.Create(); private readonly List<(FullPath[] Paths, long Size, byte[] Hash)> _duplicates = []; diff --git a/Penumbra/Mods/Editor/MdlMaterialEditor.cs b/Penumbra/Mods/Editor/MdlMaterialEditor.cs index 738e606e..2a23ffad 100644 --- a/Penumbra/Mods/Editor/MdlMaterialEditor.cs +++ b/Penumbra/Mods/Editor/MdlMaterialEditor.cs @@ -1,11 +1,12 @@ using OtterGui; using OtterGui.Compression; +using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; namespace Penumbra.Mods.Editor; -public partial class MdlMaterialEditor(ModFileCollection files) +public partial class MdlMaterialEditor(ModFileCollection files) : IService { [GeneratedRegex(@"/mt_c(?'RaceCode'\d{4})b0001_(?'Suffix'.*?)\.mtrl", RegexOptions.ExplicitCapture | RegexOptions.NonBacktracking)] private static partial Regex MaterialRegex(); diff --git a/Penumbra/Mods/Editor/ModEditor.cs b/Penumbra/Mods/Editor/ModEditor.cs index 37524da1..cacb7f88 100644 --- a/Penumbra/Mods/Editor/ModEditor.cs +++ b/Penumbra/Mods/Editor/ModEditor.cs @@ -1,5 +1,5 @@ -using OtterGui; using OtterGui.Compression; +using OtterGui.Services; using Penumbra.Mods.Groups; using Penumbra.Mods.SubMods; @@ -14,7 +14,7 @@ public class ModEditor( ModSwapEditor swapEditor, MdlMaterialEditor mdlMaterialEditor, FileCompactor compactor) - : IDisposable + : IDisposable, IService { public readonly ModNormalizer ModNormalizer = modNormalizer; public readonly ModMetaEditor MetaEditor = metaEditor; diff --git a/Penumbra/Mods/Editor/ModFileCollection.cs b/Penumbra/Mods/Editor/ModFileCollection.cs index 551d04cf..241f5b3b 100644 --- a/Penumbra/Mods/Editor/ModFileCollection.cs +++ b/Penumbra/Mods/Editor/ModFileCollection.cs @@ -1,10 +1,11 @@ using OtterGui; +using OtterGui.Services; using Penumbra.Mods.SubMods; using Penumbra.String.Classes; namespace Penumbra.Mods.Editor; -public class ModFileCollection : IDisposable +public class ModFileCollection : IDisposable, IService { private readonly List _available = []; private readonly List _mtrl = []; diff --git a/Penumbra/Mods/Editor/ModFileEditor.cs b/Penumbra/Mods/Editor/ModFileEditor.cs index e2c0b726..55e0e94e 100644 --- a/Penumbra/Mods/Editor/ModFileEditor.cs +++ b/Penumbra/Mods/Editor/ModFileEditor.cs @@ -1,3 +1,4 @@ +using OtterGui.Services; using Penumbra.Mods.Manager; using Penumbra.Mods.SubMods; using Penumbra.Services; @@ -5,7 +6,7 @@ namespace Penumbra.Mods.Editor; -public class ModFileEditor(ModFileCollection files, ModManager modManager, CommunicatorService communicator) +public class ModFileEditor(ModFileCollection files, ModManager modManager, CommunicatorService communicator) : IService { public bool Changes { get; private set; } diff --git a/Penumbra/Mods/Editor/ModMerger.cs b/Penumbra/Mods/Editor/ModMerger.cs index 2df76838..9d31664b 100644 --- a/Penumbra/Mods/Editor/ModMerger.cs +++ b/Penumbra/Mods/Editor/ModMerger.cs @@ -2,6 +2,7 @@ using Dalamud.Utility; using OtterGui; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Communication; using Penumbra.Mods.Manager; @@ -13,7 +14,7 @@ namespace Penumbra.Mods.Editor; -public class ModMerger : IDisposable +public class ModMerger : IDisposable, IService { private readonly Configuration _config; private readonly CommunicatorService _communicator; diff --git a/Penumbra/Mods/Editor/ModNormalizer.cs b/Penumbra/Mods/Editor/ModNormalizer.cs index 58e4fc08..c6bc4939 100644 --- a/Penumbra/Mods/Editor/ModNormalizer.cs +++ b/Penumbra/Mods/Editor/ModNormalizer.cs @@ -1,15 +1,15 @@ using Dalamud.Interface.Internal.Notifications; using OtterGui; using OtterGui.Classes; +using OtterGui.Services; using OtterGui.Tasks; -using Penumbra.Mods.Groups; using Penumbra.Mods.Manager; using Penumbra.Mods.SubMods; using Penumbra.String.Classes; namespace Penumbra.Mods.Editor; -public class ModNormalizer(ModManager _modManager, Configuration _config) +public class ModNormalizer(ModManager _modManager, Configuration _config) : IService { private readonly List>> _redirections = []; diff --git a/Penumbra/Mods/Editor/ModSwapEditor.cs b/Penumbra/Mods/Editor/ModSwapEditor.cs index 0250efae..1a8ff2eb 100644 --- a/Penumbra/Mods/Editor/ModSwapEditor.cs +++ b/Penumbra/Mods/Editor/ModSwapEditor.cs @@ -1,10 +1,10 @@ -using Penumbra.Mods; +using OtterGui.Services; using Penumbra.Mods.Manager; using Penumbra.Mods.SubMods; using Penumbra.String.Classes; using Penumbra.Util; -public class ModSwapEditor(ModManager modManager) +public class ModSwapEditor(ModManager modManager) : IService { private readonly Dictionary _swaps = []; diff --git a/Penumbra/Mods/Manager/ModCacheManager.cs b/Penumbra/Mods/Manager/ModCacheManager.cs index 8ab8cf33..38d98d7c 100644 --- a/Penumbra/Mods/Manager/ModCacheManager.cs +++ b/Penumbra/Mods/Manager/ModCacheManager.cs @@ -1,3 +1,4 @@ +using OtterGui.Services; using Penumbra.Communication; using Penumbra.GameData.Data; using Penumbra.Mods.Groups; @@ -8,7 +9,7 @@ namespace Penumbra.Mods.Manager; -public class ModCacheManager : IDisposable +public class ModCacheManager : IDisposable, IService { private readonly Configuration _config; private readonly CommunicatorService _communicator; diff --git a/Penumbra/Mods/Manager/ModDataEditor.cs b/Penumbra/Mods/Manager/ModDataEditor.cs index c7c7c2cc..4ab9deb1 100644 --- a/Penumbra/Mods/Manager/ModDataEditor.cs +++ b/Penumbra/Mods/Manager/ModDataEditor.cs @@ -1,6 +1,7 @@ using Dalamud.Utility; using Newtonsoft.Json.Linq; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Services; namespace Penumbra.Mods.Manager; @@ -23,7 +24,7 @@ public enum ModDataChangeType : ushort Note = 0x0800, } -public class ModDataEditor(SaveService saveService, CommunicatorService communicatorService) +public class ModDataEditor(SaveService saveService, CommunicatorService communicatorService) : IService { /// Create the file containing the meta information about a mod from scratch. public void CreateMeta(DirectoryInfo directory, string? name, string? author, string? description, string? version, @@ -49,7 +50,6 @@ public ModDataChangeType LoadLocalData(Mod mod) var save = true; if (File.Exists(dataFile)) - { try { var text = File.ReadAllText(dataFile); @@ -65,7 +65,6 @@ public ModDataChangeType LoadLocalData(Mod mod) { Penumbra.Log.Error($"Could not load local mod data:\n{e}"); } - } if (importDate == 0) importDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); diff --git a/Penumbra/Mods/Manager/ModExportManager.cs b/Penumbra/Mods/Manager/ModExportManager.cs index 676018be..38b9c0fd 100644 --- a/Penumbra/Mods/Manager/ModExportManager.cs +++ b/Penumbra/Mods/Manager/ModExportManager.cs @@ -1,10 +1,11 @@ +using OtterGui.Services; using Penumbra.Communication; using Penumbra.Mods.Editor; using Penumbra.Services; namespace Penumbra.Mods.Manager; -public class ModExportManager : IDisposable +public class ModExportManager : IDisposable, IService { private readonly Configuration _config; private readonly CommunicatorService _communicator; diff --git a/Penumbra/Mods/Manager/ModFileSystem.cs b/Penumbra/Mods/Manager/ModFileSystem.cs index c8a0a5db..e32fec0c 100644 --- a/Penumbra/Mods/Manager/ModFileSystem.cs +++ b/Penumbra/Mods/Manager/ModFileSystem.cs @@ -1,12 +1,11 @@ -using Newtonsoft.Json.Linq; -using OtterGui.Classes; using OtterGui.Filesystem; +using OtterGui.Services; using Penumbra.Communication; using Penumbra.Services; namespace Penumbra.Mods.Manager; -public sealed class ModFileSystem : FileSystem, IDisposable, ISavable +public sealed class ModFileSystem : FileSystem, IDisposable, ISavable, IService { private readonly ModManager _modManager; private readonly CommunicatorService _communicator; diff --git a/Penumbra/Mods/Manager/ModImportManager.cs b/Penumbra/Mods/Manager/ModImportManager.cs index c99b7d0e..ff39b021 100644 --- a/Penumbra/Mods/Manager/ModImportManager.cs +++ b/Penumbra/Mods/Manager/ModImportManager.cs @@ -1,11 +1,12 @@ using Dalamud.Interface.Internal.Notifications; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Import; using Penumbra.Mods.Editor; namespace Penumbra.Mods.Manager; -public class ModImportManager(ModManager modManager, Configuration config, ModEditor modEditor) : IDisposable +public class ModImportManager(ModManager modManager, Configuration config, ModEditor modEditor) : IDisposable, IService { private readonly ConcurrentQueue _modsToUnpack = new(); @@ -32,7 +33,8 @@ public void TryUnpacking() if (File.Exists(s)) return true; - Penumbra.Messager.NotificationMessage($"Failed to import queued mod at {s}, the file does not exist.", NotificationType.Warning, false); + Penumbra.Messager.NotificationMessage($"Failed to import queued mod at {s}, the file does not exist.", NotificationType.Warning, + false); return false; }).Select(s => new FileInfo(s)).ToArray(); diff --git a/Penumbra/Mods/Manager/ModManager.cs b/Penumbra/Mods/Manager/ModManager.cs index 42082383..4b19ea4c 100644 --- a/Penumbra/Mods/Manager/ModManager.cs +++ b/Penumbra/Mods/Manager/ModManager.cs @@ -1,3 +1,4 @@ +using OtterGui.Services; using Penumbra.Communication; using Penumbra.Mods.Editor; using Penumbra.Mods.Manager.OptionEditor; @@ -27,7 +28,7 @@ public enum ModPathChangeType StartingReload, } -public sealed class ModManager : ModStorage, IDisposable +public sealed class ModManager : ModStorage, IDisposable, IService { private readonly Configuration _config; private readonly CommunicatorService _communicator; diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index ed4245c4..0e66367a 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -4,6 +4,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Filesystem; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.GameData.Data; using Penumbra.Import; @@ -23,7 +24,7 @@ public partial class ModCreator( Configuration config, ModDataEditor _dataEditor, MetaFileManager _metaFileManager, - GamePathParser _gamePathParser) + GamePathParser _gamePathParser) : IService { public readonly Configuration Config = config; diff --git a/Penumbra/Services/StaticServiceManager.cs b/Penumbra/Services/StaticServiceManager.cs index 35e36349..3279da96 100644 --- a/Penumbra/Services/StaticServiceManager.cs +++ b/Penumbra/Services/StaticServiceManager.cs @@ -5,36 +5,15 @@ using Dalamud.Plugin.Services; using Microsoft.Extensions.DependencyInjection; using OtterGui; -using OtterGui.Classes; using OtterGui.Log; using OtterGui.Services; -using Penumbra.Api; using Penumbra.Api.Api; -using Penumbra.Collections.Cache; -using Penumbra.Collections.Manager; using Penumbra.GameData.Actors; -using Penumbra.Import.Models; using Penumbra.GameData.Structs; -using Penumbra.Import.Textures; using Penumbra.Interop.PathResolving; -using Penumbra.Interop.ResourceLoading; -using Penumbra.Interop.ResourceTree; -using Penumbra.Interop.Services; using Penumbra.Meta; -using Penumbra.Mods; -using Penumbra.Mods.Editor; using Penumbra.Mods.Manager; -using Penumbra.UI; -using Penumbra.UI.AdvancedWindow; -using Penumbra.UI.Classes; -using Penumbra.UI.ModsTab; -using Penumbra.UI.ResourceWatcher; -using Penumbra.UI.Tabs; -using Penumbra.UI.Tabs.Debug; using IPenumbraApi = Penumbra.Api.Api.IPenumbraApi; -using MdlMaterialEditor = Penumbra.Mods.Editor.MdlMaterialEditor; -using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager; -using Penumbra.Api.IpcTester; namespace Penumbra.Services; @@ -45,19 +24,18 @@ public static ServiceManager CreateProvider(Penumbra penumbra, DalamudPluginInte var services = new ServiceManager(log) .AddDalamudServices(pi) .AddExistingService(log) - .AddExistingService(penumbra) - .AddInterop() - .AddConfiguration() - .AddCollections() - .AddMods() - .AddResources() - .AddResolvers() - .AddInterface() - .AddModEditor() - .AddApi(); + .AddExistingService(penumbra); services.AddIServices(typeof(EquipItem).Assembly); services.AddIServices(typeof(Penumbra).Assembly); services.AddIServices(typeof(ImGuiUtil).Assembly); + services.AddSingleton(p => + { + var cutsceneService = p.GetRequiredService(); + return new CutsceneResolver(cutsceneService.GetParentIndex); + }) + .AddSingleton(p => p.GetRequiredService().ImcChecker) + .AddSingleton(s => (ModStorage)s.GetRequiredService()) + .AddSingleton(x => x.GetRequiredService()); services.CreateProvider(); return services; } @@ -83,118 +61,4 @@ private static ServiceManager AddDalamudServices(this ServiceManager services, D .AddDalamudService(pi) .AddDalamudService(pi) .AddDalamudService(pi); - - private static ServiceManager AddInterop(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton(p => - { - var cutsceneService = p.GetRequiredService(); - return new CutsceneResolver(cutsceneService.GetParentIndex); - }) - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(p => p.GetRequiredService().ImcChecker); - - private static ServiceManager AddConfiguration(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddCollections(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddMods(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(s => (ModStorage)s.GetRequiredService()); - - private static ServiceManager AddResources(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddResolvers(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddInterface(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(p => new Diagnostics(p)); - - private static ServiceManager AddModEditor(this ServiceManager services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - private static ServiceManager AddApi(this ServiceManager services) - => services.AddSingleton(x => x.GetRequiredService()); } diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index 62115dd6..652f928d 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -3,6 +3,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Api.Enums; using Penumbra.Collections; @@ -24,7 +25,7 @@ namespace Penumbra.UI.AdvancedWindow; -public class ItemSwapTab : IDisposable, ITab +public class ItemSwapTab : IDisposable, ITab, IUiService { private readonly Configuration _config; private readonly CommunicatorService _communicator; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs index 9410b793..90fdc48e 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs @@ -7,6 +7,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections.Manager; using Penumbra.Communication; @@ -32,7 +33,7 @@ namespace Penumbra.UI.AdvancedWindow; -public partial class ModEditWindow : Window, IDisposable +public partial class ModEditWindow : Window, IDisposable, IUiService { private const string WindowBaseLabel = "###SubModEdit"; diff --git a/Penumbra/UI/AdvancedWindow/ModMergeTab.cs b/Penumbra/UI/AdvancedWindow/ModMergeTab.cs index 5dad66b4..b5f0255c 100644 --- a/Penumbra/UI/AdvancedWindow/ModMergeTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModMergeTab.cs @@ -2,6 +2,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Services; using Penumbra.Mods.Editor; using Penumbra.Mods.Manager; using Penumbra.Mods.SubMods; @@ -9,7 +10,7 @@ namespace Penumbra.UI.AdvancedWindow; -public class ModMergeTab(ModMerger modMerger) +public class ModMergeTab(ModMerger modMerger) : IUiService { private readonly ModCombo _modCombo = new(() => modMerger.ModsWithoutCurrent.ToList()); private string _newModName = string.Empty; @@ -183,7 +184,7 @@ private void DrawOptionTable(float size) else { ImGuiUtil.DrawTableColumn(option.GetName()); - + ImGui.TableNextColumn(); ImGui.Selectable(group.Name, false); if (ImGui.BeginPopupContextItem("##groupContext")) diff --git a/Penumbra/UI/ChangedItemDrawer.cs b/Penumbra/UI/ChangedItemDrawer.cs index 29a1f291..0afeeeeb 100644 --- a/Penumbra/UI/ChangedItemDrawer.cs +++ b/Penumbra/UI/ChangedItemDrawer.cs @@ -10,6 +10,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -19,7 +20,7 @@ namespace Penumbra.UI; -public class ChangedItemDrawer : IDisposable +public class ChangedItemDrawer : IDisposable, IUiService { [Flags] public enum ChangedItemIcon : uint @@ -99,8 +100,10 @@ public static bool TryParsePartial(string lowerInput, out ChangedItemIcon slot) slot = 0; foreach (var (item, flag) in LowerNames.Zip(Order)) + { if (item.Contains(lowerInput, StringComparison.Ordinal)) slot |= flag; + } return slot != 0; } diff --git a/Penumbra/UI/Changelog.cs b/Penumbra/UI/Changelog.cs index 184633f2..f4cedf7d 100644 --- a/Penumbra/UI/Changelog.cs +++ b/Penumbra/UI/Changelog.cs @@ -1,8 +1,9 @@ +using OtterGui.Services; using OtterGui.Widgets; namespace Penumbra.UI; -public class PenumbraChangelog +public class PenumbraChangelog : IUiService { public const int LastChangelogVersion = 0; diff --git a/Penumbra/UI/Classes/CollectionSelectHeader.cs b/Penumbra/UI/Classes/CollectionSelectHeader.cs index bff0092a..6c8bbf64 100644 --- a/Penumbra/UI/Classes/CollectionSelectHeader.cs +++ b/Penumbra/UI/Classes/CollectionSelectHeader.cs @@ -1,6 +1,7 @@ using ImGuiNET; using OtterGui.Raii; using OtterGui; +using OtterGui.Services; using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.Interop.PathResolving; @@ -9,7 +10,7 @@ namespace Penumbra.UI.Classes; -public class CollectionSelectHeader +public class CollectionSelectHeader : IUiService { private readonly CollectionCombo _collectionCombo; private readonly ActiveCollections _activeCollections; diff --git a/Penumbra/UI/ConfigWindow.cs b/Penumbra/UI/ConfigWindow.cs index 0ae16f6d..67b0a50c 100644 --- a/Penumbra/UI/ConfigWindow.cs +++ b/Penumbra/UI/ConfigWindow.cs @@ -4,6 +4,7 @@ using OtterGui; using OtterGui.Custom; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Text; using Penumbra.Api.Enums; using Penumbra.Services; @@ -13,7 +14,7 @@ namespace Penumbra.UI; -public sealed class ConfigWindow : Window +public sealed class ConfigWindow : Window, IUiService { private readonly DalamudPluginInterface _pluginInterface; private readonly Configuration _config; diff --git a/Penumbra/UI/FileDialogService.cs b/Penumbra/UI/FileDialogService.cs index 88c0b00f..cc2a7f6a 100644 --- a/Penumbra/UI/FileDialogService.cs +++ b/Penumbra/UI/FileDialogService.cs @@ -3,12 +3,13 @@ using Dalamud.Utility; using ImGuiNET; using OtterGui; +using OtterGui.Services; using Penumbra.Communication; using Penumbra.Services; namespace Penumbra.UI; -public class FileDialogService : IDisposable +public class FileDialogService : IDisposable, IUiService { private readonly CommunicatorService _communicator; private readonly FileDialogManager _manager; diff --git a/Penumbra/UI/ImportPopup.cs b/Penumbra/UI/ImportPopup.cs index 71164d1d..fb2028b5 100644 --- a/Penumbra/UI/ImportPopup.cs +++ b/Penumbra/UI/ImportPopup.cs @@ -1,13 +1,14 @@ using Dalamud.Interface.Windowing; using ImGuiNET; using OtterGui.Raii; +using OtterGui.Services; using Penumbra.Import.Structs; using Penumbra.Mods.Manager; namespace Penumbra.UI; /// Draw the progress information for import. -public sealed class ImportPopup : Window +public sealed class ImportPopup : Window, IUiService { public const string WindowLabel = "Penumbra Import Status"; diff --git a/Penumbra/UI/LaunchButton.cs b/Penumbra/UI/LaunchButton.cs index 9650ccf8..14e16432 100644 --- a/Penumbra/UI/LaunchButton.cs +++ b/Penumbra/UI/LaunchButton.cs @@ -2,6 +2,7 @@ using Dalamud.Interface.Internal; using Dalamud.Plugin; using Dalamud.Plugin.Services; +using OtterGui.Services; namespace Penumbra.UI; @@ -9,7 +10,7 @@ namespace Penumbra.UI; /// A Launch Button used in the title screen of the game, /// using the Dalamud-provided collapsible submenu. /// -public class LaunchButton : IDisposable +public class LaunchButton : IDisposable, IUiService { private readonly ConfigWindow _configWindow; private readonly UiBuilder _uiBuilder; diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs index 58f0b615..0ca4d40c 100644 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs @@ -8,6 +8,7 @@ using OtterGui.Filesystem; using OtterGui.FileSystem.Selector; using OtterGui.Raii; +using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; @@ -21,7 +22,7 @@ namespace Penumbra.UI.ModsTab; -public sealed class ModFileSystemSelector : FileSystemSelector +public sealed class ModFileSystemSelector : FileSystemSelector, IUiService { private readonly CommunicatorService _communicator; private readonly MessageService _messager; @@ -33,9 +34,9 @@ public sealed class ModFileSystemSelector : FileSystemSelector().Aggregate((a, b) => a | b); - public ReadOnlySpan Label => "Changed Items"u8; - public ModPanelChangedItemsTab(ModFileSystemSelector selector, ChangedItemDrawer drawer) - { - _selector = selector; - _drawer = drawer; - } - public bool IsVisible - => _selector.Selected!.ChangedItems.Count > 0; + => selector.Selected!.ChangedItems.Count > 0; public void DrawContent() { - _drawer.DrawTypeFilter(); + drawer.DrawTypeFilter(); ImGui.Separator(); using var table = ImRaii.Table("##changedItems", 1, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(ImGui.GetContentRegionAvail().X, -1)); if (!table) return; - var zipList = ZipList.FromSortedList((SortedList)_selector.Selected!.ChangedItems); + var zipList = ZipList.FromSortedList((SortedList)selector.Selected!.ChangedItems); var height = ImGui.GetFrameHeightWithSpacing(); ImGui.TableNextColumn(); var skips = ImGuiClip.GetNecessarySkips(height); @@ -43,14 +33,14 @@ public void DrawContent() } private bool CheckFilter((string Name, object? Data) kvp) - => _drawer.FilterChangedItem(kvp.Name, kvp.Data, LowerString.Empty); + => drawer.FilterChangedItem(kvp.Name, kvp.Data, LowerString.Empty); private void DrawChangedItem((string Name, object? Data) kvp) { ImGui.TableNextColumn(); - _drawer.DrawCategoryIcon(kvp.Name, kvp.Data); + drawer.DrawCategoryIcon(kvp.Name, kvp.Data); ImGui.SameLine(); - _drawer.DrawChangedItem(kvp.Name, kvp.Data); - _drawer.DrawModelData(kvp.Data); + drawer.DrawChangedItem(kvp.Name, kvp.Data); + drawer.DrawModelData(kvp.Data); } } diff --git a/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs b/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs index aa598557..9f37f847 100644 --- a/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs @@ -2,6 +2,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Collections; using Penumbra.Collections.Manager; @@ -10,25 +11,16 @@ namespace Penumbra.UI.ModsTab; -public class ModPanelCollectionsTab : ITab +public class ModPanelCollectionsTab(CollectionStorage storage, ModFileSystemSelector selector) : ITab, IUiService { - private readonly ModFileSystemSelector _selector; - private readonly CollectionStorage _collections; - - private readonly List<(ModCollection, ModCollection, uint, string)> _cache = new(); - - public ModPanelCollectionsTab(CollectionStorage storage, ModFileSystemSelector selector) - { - _collections = storage; - _selector = selector; - } + private readonly List<(ModCollection, ModCollection, uint, string)> _cache = []; public ReadOnlySpan Label => "Collections"u8; public void DrawContent() { - var (direct, inherited) = CountUsage(_selector.Selected!); + var (direct, inherited) = CountUsage(selector.Selected!); ImGui.NewLine(); if (direct == 1) ImGui.TextUnformatted("This Mod is directly configured in 1 collection."); @@ -80,7 +72,7 @@ public void DrawContent() var disInherited = ColorId.InheritedDisabledMod.Value(); var directCount = 0; var inheritedCount = 0; - foreach (var collection in _collections) + foreach (var collection in storage) { var (settings, parent) = collection[mod.Index]; var (color, text) = settings == null diff --git a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs index c1a3c1eb..bee48068 100644 --- a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs @@ -3,6 +3,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Text; using OtterGui.Widgets; using Penumbra.Collections.Cache; @@ -16,7 +17,7 @@ namespace Penumbra.UI.ModsTab; -public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSystemSelector selector) : ITab +public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSystemSelector selector) : ITab, IUiService { private int? _currentPriority; diff --git a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs index ed6340ab..6fe3e4c6 100644 --- a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs @@ -2,6 +2,7 @@ using ImGuiNET; using OtterGui.Raii; using OtterGui; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Mods.Manager; @@ -12,7 +13,7 @@ public class ModPanelDescriptionTab( TutorialService tutorial, ModManager modManager, PredefinedTagManager predefinedTagsConfig) - : ITab + : ITab, IUiService { private readonly TagButtons _localTags = new(); private readonly TagButtons _modTags = new(); diff --git a/Penumbra/UI/ModsTab/ModPanelEditTab.cs b/Penumbra/UI/ModsTab/ModPanelEditTab.cs index 468e97b9..1e371065 100644 --- a/Penumbra/UI/ModsTab/ModPanelEditTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelEditTab.cs @@ -6,13 +6,13 @@ using OtterGui.Raii; using OtterGui.Widgets; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Mods; using Penumbra.Mods.Editor; using Penumbra.Mods.Manager; using Penumbra.Services; using Penumbra.UI.AdvancedWindow; using Penumbra.Mods.Settings; -using Penumbra.Mods.Manager.OptionEditor; using Penumbra.UI.ModsTab.Groups; namespace Penumbra.UI.ModsTab; @@ -31,7 +31,7 @@ public class ModPanelEditTab( ModGroupEditDrawer groupEditDrawer, DescriptionEditPopup descriptionPopup, AddGroupDrawer addGroupDrawer) - : ITab + : ITab, IUiService { private readonly TagButtons _modTags = new(); diff --git a/Penumbra/UI/ModsTab/ModPanelTabBar.cs b/Penumbra/UI/ModsTab/ModPanelTabBar.cs index 8b09d8b9..639118f5 100644 --- a/Penumbra/UI/ModsTab/ModPanelTabBar.cs +++ b/Penumbra/UI/ModsTab/ModPanelTabBar.cs @@ -2,6 +2,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Mods; using Penumbra.Mods.Manager; @@ -9,7 +10,7 @@ namespace Penumbra.UI.ModsTab; -public class ModPanelTabBar +public class ModPanelTabBar : IUiService { private enum ModPanelTabType { @@ -33,7 +34,7 @@ private enum ModPanelTabType public readonly ITab[] Tabs; private ModPanelTabType _preferredTab = ModPanelTabType.Settings; - private Mod? _lastMod = null; + private Mod? _lastMod; public ModPanelTabBar(ModEditWindow modEditWindow, ModPanelSettingsTab settings, ModPanelDescriptionTab description, ModPanelConflictsTab conflicts, ModPanelChangedItemsTab changedItems, ModPanelEditTab edit, ModManager modManager, @@ -49,15 +50,15 @@ public ModPanelTabBar(ModEditWindow modEditWindow, ModPanelSettingsTab settings, _tutorial = tutorial; Collections = collections; - Tabs = new ITab[] - { + Tabs = + [ Settings, Description, Conflicts, ChangedItems, Collections, Edit, - }; + ]; } public void Draw(Mod mod) diff --git a/Penumbra/UI/ModsTab/MultiModPanel.cs b/Penumbra/UI/ModsTab/MultiModPanel.cs index 595240f4..4079748e 100644 --- a/Penumbra/UI/ModsTab/MultiModPanel.cs +++ b/Penumbra/UI/ModsTab/MultiModPanel.cs @@ -3,12 +3,13 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Services; using Penumbra.Mods; using Penumbra.Mods.Manager; namespace Penumbra.UI.ModsTab; -public class MultiModPanel(ModFileSystemSelector _selector, ModDataEditor _editor) +public class MultiModPanel(ModFileSystemSelector _selector, ModDataEditor _editor) : IUiService { public void Draw() { @@ -65,8 +66,8 @@ private void DrawModList() } private string _tag = string.Empty; - private readonly List _addMods = []; - private readonly List<(Mod, int)> _removeMods = []; + private readonly List _addMods = []; + private readonly List<(Mod, int)> _removeMods = []; private void DrawMultiTagger() { diff --git a/Penumbra/UI/PredefinedTagManager.cs b/Penumbra/UI/PredefinedTagManager.cs index 0e5377d6..d531b1a2 100644 --- a/Penumbra/UI/PredefinedTagManager.cs +++ b/Penumbra/UI/PredefinedTagManager.cs @@ -6,14 +6,14 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Services; using Penumbra.Mods.Manager; using Penumbra.Services; using Penumbra.UI.Classes; -using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; namespace Penumbra.UI; -public sealed class PredefinedTagManager : ISavable, IReadOnlyList +public sealed class PredefinedTagManager : ISavable, IReadOnlyList, IService { public const int Version = 1; diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs index 65a8fe76..a7d1a8c6 100644 --- a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs +++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs @@ -2,6 +2,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource; using ImGuiNET; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Api.Enums; using Penumbra.Collections; @@ -15,7 +16,7 @@ namespace Penumbra.UI.ResourceWatcher; -public sealed class ResourceWatcher : IDisposable, ITab +public sealed class ResourceWatcher : IDisposable, ITab, IUiService { public const int DefaultMaxEntries = 1024; public const RecordType AllRecords = RecordType.Request | RecordType.ResourceLoad | RecordType.FileLoad | RecordType.Destruction; diff --git a/Penumbra/UI/Tabs/ChangedItemsTab.cs b/Penumbra/UI/Tabs/ChangedItemsTab.cs index ab0badf4..2aeaaea0 100644 --- a/Penumbra/UI/Tabs/ChangedItemsTab.cs +++ b/Penumbra/UI/Tabs/ChangedItemsTab.cs @@ -2,6 +2,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Api.Enums; using Penumbra.Collections.Manager; @@ -17,7 +18,7 @@ public class ChangedItemsTab( CollectionSelectHeader collectionHeader, ChangedItemDrawer drawer, CommunicatorService communicator) - : ITab + : ITab, IUiService { public ReadOnlySpan Label => "Changed Items"u8; diff --git a/Penumbra/UI/Tabs/CollectionsTab.cs b/Penumbra/UI/Tabs/CollectionsTab.cs index fabf7561..34e2cbcf 100644 --- a/Penumbra/UI/Tabs/CollectionsTab.cs +++ b/Penumbra/UI/Tabs/CollectionsTab.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin; using ImGuiNET; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Collections.Manager; using Penumbra.GameData.Actors; @@ -11,7 +12,7 @@ namespace Penumbra.UI.Tabs; -public sealed class CollectionsTab : IDisposable, ITab +public sealed class CollectionsTab : IDisposable, ITab, IUiService { private readonly EphemeralConfig _config; private readonly CollectionSelector _selector; diff --git a/Penumbra/UI/Tabs/ConfigTabBar.cs b/Penumbra/UI/Tabs/ConfigTabBar.cs index 9fd07f27..28827ad9 100644 --- a/Penumbra/UI/Tabs/ConfigTabBar.cs +++ b/Penumbra/UI/Tabs/ConfigTabBar.cs @@ -1,4 +1,5 @@ using ImGuiNET; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Api.Enums; using Penumbra.Mods; @@ -8,7 +9,7 @@ namespace Penumbra.UI.Tabs; -public class ConfigTabBar : IDisposable +public class ConfigTabBar : IDisposable, IUiService { private readonly CommunicatorService _communicator; diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index 1519ebf0..0122a6f5 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -44,7 +44,7 @@ namespace Penumbra.UI.Tabs.Debug; -public class Diagnostics(IServiceProvider provider) +public class Diagnostics(ServiceManager provider) : IUiService { public void DrawDiagnostics() { @@ -55,7 +55,7 @@ public void DrawDiagnostics() foreach (var type in typeof(ActorManager).Assembly.GetTypes() .Where(t => t is { IsAbstract: false, IsInterface: false } && t.IsAssignableTo(typeof(IAsyncDataContainer)))) { - var container = (IAsyncDataContainer)provider.GetRequiredService(type); + var container = (IAsyncDataContainer)provider.Provider!.GetRequiredService(type); ImGuiUtil.DrawTableColumn(container.Name); ImGuiUtil.DrawTableColumn(container.Time.ToString()); ImGuiUtil.DrawTableColumn(Functions.HumanReadableSize(container.Memory)); @@ -64,7 +64,7 @@ public void DrawDiagnostics() } } -public class DebugTab : Window, ITab +public class DebugTab : Window, ITab, IUiService { private readonly PerformanceTracker _performance; private readonly Configuration _config; diff --git a/Penumbra/UI/Tabs/EffectiveTab.cs b/Penumbra/UI/Tabs/EffectiveTab.cs index 7076b80f..1b9af75c 100644 --- a/Penumbra/UI/Tabs/EffectiveTab.cs +++ b/Penumbra/UI/Tabs/EffectiveTab.cs @@ -3,6 +3,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Collections; using Penumbra.Collections.Cache; @@ -15,7 +16,7 @@ namespace Penumbra.UI.Tabs; public class EffectiveTab(CollectionManager collectionManager, CollectionSelectHeader collectionHeader) - : ITab + : ITab, IUiService { public ReadOnlySpan Label => "Effective Changes"u8; diff --git a/Penumbra/UI/Tabs/MessagesTab.cs b/Penumbra/UI/Tabs/MessagesTab.cs index abaf2ba6..190f9407 100644 --- a/Penumbra/UI/Tabs/MessagesTab.cs +++ b/Penumbra/UI/Tabs/MessagesTab.cs @@ -1,9 +1,10 @@ +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Services; namespace Penumbra.UI.Tabs; -public class MessagesTab(MessageService messages) : ITab +public class MessagesTab(MessageService messages) : ITab, IUiService { public ReadOnlySpan Label => "Messages"u8; diff --git a/Penumbra/UI/Tabs/ModsTab.cs b/Penumbra/UI/Tabs/ModsTab.cs index e4d94bb5..7faa3da8 100644 --- a/Penumbra/UI/Tabs/ModsTab.cs +++ b/Penumbra/UI/Tabs/ModsTab.cs @@ -6,6 +6,7 @@ using Dalamud.Interface; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Housing; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Api.Enums; using Penumbra.Interop.Services; @@ -30,7 +31,7 @@ public class ModsTab( CollectionSelectHeader collectionHeader, ITargetManager targets, ObjectManager objects) - : ITab + : ITab, IUiService { private readonly ActiveCollections _activeCollections = collectionManager.Active; diff --git a/Penumbra/UI/Tabs/OnScreenTab.cs b/Penumbra/UI/Tabs/OnScreenTab.cs index 787e07a1..fa33f702 100644 --- a/Penumbra/UI/Tabs/OnScreenTab.cs +++ b/Penumbra/UI/Tabs/OnScreenTab.cs @@ -1,16 +1,12 @@ +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.UI.AdvancedWindow; namespace Penumbra.UI.Tabs; -public class OnScreenTab : ITab +public class OnScreenTab(ResourceTreeViewerFactory resourceTreeViewerFactory) : ITab, IUiService { - private readonly ResourceTreeViewer _viewer; - - public OnScreenTab(ResourceTreeViewerFactory resourceTreeViewerFactory) - { - _viewer = resourceTreeViewerFactory.Create(0, delegate { }, delegate { }); - } + private readonly ResourceTreeViewer _viewer = resourceTreeViewerFactory.Create(0, delegate { }, delegate { }); public ReadOnlySpan Label => "On-Screen"u8; diff --git a/Penumbra/UI/Tabs/ResourceTab.cs b/Penumbra/UI/Tabs/ResourceTab.cs index bbb0561b..0b54c5e2 100644 --- a/Penumbra/UI/Tabs/ResourceTab.cs +++ b/Penumbra/UI/Tabs/ResourceTab.cs @@ -6,6 +6,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Interop.ResourceLoading; using Penumbra.String.Classes; @@ -13,7 +14,7 @@ namespace Penumbra.UI.Tabs; public class ResourceTab(Configuration config, ResourceManagerService resourceManager, ISigScanner sigScanner) - : ITab + : ITab, IUiService { public ReadOnlySpan Label => "Resource Manager"u8; diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs index 0de4f790..17db21c9 100644 --- a/Penumbra/UI/Tabs/SettingsTab.cs +++ b/Penumbra/UI/Tabs/SettingsTab.cs @@ -9,6 +9,7 @@ using OtterGui.Compression; using OtterGui.Custom; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Api; using Penumbra.Interop.Services; @@ -19,7 +20,7 @@ namespace Penumbra.UI.Tabs; -public class SettingsTab : ITab +public class SettingsTab : ITab, IUiService { public const int RootDirectoryMaxLength = 64; diff --git a/Penumbra/UI/TutorialService.cs b/Penumbra/UI/TutorialService.cs index d87df19e..7d2a0d2a 100644 --- a/Penumbra/UI/TutorialService.cs +++ b/Penumbra/UI/TutorialService.cs @@ -1,3 +1,4 @@ +using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Collections; using Penumbra.Collections.Manager; @@ -40,7 +41,7 @@ public enum BasicTutorialSteps } /// Service for the in-game tutorial. -public class TutorialService +public class TutorialService : IUiService { public const string SelectedCollection = "Selected Collection"; public const string DefaultCollection = "Base Collection"; diff --git a/Penumbra/UI/WindowSystem.cs b/Penumbra/UI/WindowSystem.cs index c5418eb3..99819fce 100644 --- a/Penumbra/UI/WindowSystem.cs +++ b/Penumbra/UI/WindowSystem.cs @@ -1,12 +1,13 @@ using Dalamud.Interface; using Dalamud.Interface.Windowing; using Dalamud.Plugin; +using OtterGui.Services; using Penumbra.UI.AdvancedWindow; using Penumbra.UI.Tabs.Debug; namespace Penumbra.UI; -public class PenumbraWindowSystem : IDisposable +public class PenumbraWindowSystem : IDisposable, IUiService { private readonly UiBuilder _uiBuilder; private readonly WindowSystem _windowSystem;