diff --git a/Penumbra.GameData b/Penumbra.GameData index 539d1387..07cc26f1 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 539d138700543e7c2c6c918f9f68e33228111e4d +Subproject commit 07cc26f196984a44711b3bc4c412947d863288bd diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs index e2f20b46..4d8d0b4a 100644 --- a/Penumbra/Collections/Cache/CollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -5,7 +5,6 @@ using Penumbra.Mods; using Penumbra.Communication; using Penumbra.Mods.Editor; using Penumbra.String.Classes; -using Penumbra.Mods.Manager; using Penumbra.Util; namespace Penumbra.Collections.Cache; diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 4c147c3c..f42b72fc 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -4,7 +4,6 @@ using Penumbra.Interop.Services; using Penumbra.Interop.Structs; using Penumbra.Meta; using Penumbra.Meta.Manipulations; -using Penumbra.Mods; using Penumbra.Mods.Editor; using Penumbra.String.Classes; @@ -14,13 +13,14 @@ public class MetaCache : IDisposable, IEnumerable _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 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) { @@ -69,6 +69,7 @@ public class MetaCache : IDisposable, IEnumerable _estCache.TemporarilySetFiles(_manager, type); + public unsafe EqpEntry ApplyGlobalEqp(EqpEntry baseEntry, CharacterArmor* armor) + => _globalEqpCache.Apply(baseEntry, armor); + /// Try to obtain a manipulated IMC file. public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) @@ -193,8 +204,8 @@ public class MetaCache : IDisposable, IEnumerable _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, + 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; diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index b073e731..3f3733e0 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -8,6 +8,7 @@ using Penumbra.String.Classes; using Penumbra.Collections.Cache; using Penumbra.Interop.Services; using Penumbra.Mods.Editor; +using Penumbra.GameData.Structs; namespace Penumbra.Collections; @@ -114,4 +115,7 @@ public partial class ModCollection public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstManipulation.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/Import/TexToolsMeta.Export.cs b/Penumbra/Import/TexToolsMeta.Export.cs index 90ffaf60..03bdbd90 100644 --- a/Penumbra/Import/TexToolsMeta.Export.cs +++ b/Penumbra/Import/TexToolsMeta.Export.cs @@ -187,6 +187,9 @@ public partial class TexToolsMeta b.Write(manip.Gmp.Entry.UnknownTotal); } + break; + case MetaManipulation.Type.GlobalEqp: + // Not Supported break; } diff --git a/Penumbra/Import/TexToolsMeta.cs b/Penumbra/Import/TexToolsMeta.cs index 83b430fb..25e00bd7 100644 --- a/Penumbra/Import/TexToolsMeta.cs +++ b/Penumbra/Import/TexToolsMeta.cs @@ -44,7 +44,7 @@ public partial class TexToolsMeta var headerStart = reader.ReadUInt32(); reader.BaseStream.Seek(headerStart, SeekOrigin.Begin); - List<(MetaManipulation.Type type, uint offset, int size)> entries = new(); + List<(MetaManipulation.Type type, uint offset, int size)> entries = []; for (var i = 0; i < numHeaders; ++i) { var currentOffset = reader.BaseStream.Position; diff --git a/Penumbra/Interop/Hooks/Meta/EqpHook.cs b/Penumbra/Interop/Hooks/Meta/EqpHook.cs index 457b9428..6663c211 100644 --- a/Penumbra/Interop/Hooks/Meta/EqpHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EqpHook.cs @@ -23,6 +23,7 @@ public unsafe class EqpHook : FastHook { using var eqp = _metaState.ResolveEqpData(_metaState.EqpCollection.ModCollection); Task.Result.Original(utility, flags, armor); + *flags = _metaState.EqpCollection.ModCollection.ApplyGlobalEqp(*flags, armor); } else { diff --git a/Penumbra/Meta/Manipulations/GlobalEqpCache.cs b/Penumbra/Meta/Manipulations/GlobalEqpCache.cs new file mode 100644 index 00000000..48ffb308 --- /dev/null +++ b/Penumbra/Meta/Manipulations/GlobalEqpCache.cs @@ -0,0 +1,80 @@ +using OtterGui.Services; +using Penumbra.GameData.Structs; + +namespace Penumbra.Meta.Manipulations; + +public struct GlobalEqpCache : IService +{ + private readonly HashSet _doNotHideEarrings = []; + private readonly HashSet _doNotHideNecklace = []; + private readonly HashSet _doNotHideBracelets = []; + private readonly HashSet _doNotHideRingL = []; + private readonly HashSet _doNotHideRingR = []; + private bool _doNotHideVieraHats; + private bool _doNotHideHrothgarHats; + + public GlobalEqpCache() + { } + + public void Clear() + { + _doNotHideEarrings.Clear(); + _doNotHideNecklace.Clear(); + _doNotHideBracelets.Clear(); + _doNotHideRingL.Clear(); + _doNotHideRingR.Clear(); + _doNotHideHrothgarHats = false; + _doNotHideVieraHats = false; + } + + public unsafe EqpEntry Apply(EqpEntry original, CharacterArmor* armor) + { + if (_doNotHideVieraHats) + original |= EqpEntry.HeadShowVieraHat; + + if (_doNotHideHrothgarHats) + original |= EqpEntry.HeadShowHrothgarHat; + + if (_doNotHideEarrings.Contains(armor[5].Set)) + original |= EqpEntry.HeadShowEarrings | EqpEntry.HeadShowEarringsAura | EqpEntry.HeadShowEarringsHuman; + + if (_doNotHideNecklace.Contains(armor[6].Set)) + original |= EqpEntry.BodyShowNecklace | EqpEntry.HeadShowNecklace; + + if (_doNotHideBracelets.Contains(armor[7].Set)) + original |= EqpEntry.BodyShowBracelet | EqpEntry.HandShowBracelet; + + if (_doNotHideBracelets.Contains(armor[8].Set)) + original |= EqpEntry.HandShowRingR; + + if (_doNotHideBracelets.Contains(armor[9].Set)) + original |= EqpEntry.HandShowRingL; + return original; + } + + public bool Add(GlobalEqpManipulation manipulation) + => manipulation.Type switch + { + GlobalEqpType.DoNotHideEarrings => _doNotHideEarrings.Add(manipulation.Condition), + GlobalEqpType.DoNotHideNecklace => _doNotHideNecklace.Add(manipulation.Condition), + GlobalEqpType.DoNotHideBracelets => _doNotHideBracelets.Add(manipulation.Condition), + GlobalEqpType.DoNotHideRingR => _doNotHideRingR.Add(manipulation.Condition), + GlobalEqpType.DoNotHideRingL => _doNotHideRingL.Add(manipulation.Condition), + GlobalEqpType.DoNotHideHrothgarHats => !_doNotHideHrothgarHats && (_doNotHideHrothgarHats = true), + GlobalEqpType.DoNotHideVieraHats => !_doNotHideVieraHats && (_doNotHideVieraHats = true), + _ => false, + }; + + public bool Remove(GlobalEqpManipulation manipulation) + => manipulation.Type switch + { + GlobalEqpType.DoNotHideEarrings => _doNotHideEarrings.Remove(manipulation.Condition), + GlobalEqpType.DoNotHideNecklace => _doNotHideNecklace.Remove(manipulation.Condition), + GlobalEqpType.DoNotHideBracelets => _doNotHideBracelets.Remove(manipulation.Condition), + GlobalEqpType.DoNotHideRingR => _doNotHideRingR.Remove(manipulation.Condition), + GlobalEqpType.DoNotHideRingL => _doNotHideRingL.Remove(manipulation.Condition), + GlobalEqpType.DoNotHideHrothgarHats => _doNotHideHrothgarHats && !(_doNotHideHrothgarHats = false), + GlobalEqpType.DoNotHideVieraHats => _doNotHideVieraHats && !(_doNotHideVieraHats = false), + _ => false, + }; +} diff --git a/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs b/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs new file mode 100644 index 00000000..ada543dc --- /dev/null +++ b/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs @@ -0,0 +1,50 @@ +using Penumbra.GameData.Structs; +using Penumbra.Interop.Structs; + +namespace Penumbra.Meta.Manipulations; + +public readonly struct GlobalEqpManipulation : IMetaManipulation +{ + public GlobalEqpType Type { get; init; } + public PrimaryId Condition { get; init; } + + public bool Validate() + { + if (!Enum.IsDefined(Type)) + return false; + + if (Type is GlobalEqpType.DoNotHideVieraHats or GlobalEqpType.DoNotHideHrothgarHats) + return Condition == 0; + + return Condition != 0; + } + + + public bool Equals(GlobalEqpManipulation other) + => Type == other.Type + && Condition.Equals(other.Condition); + + public int CompareTo(GlobalEqpManipulation other) + { + var typeComp = Type.CompareTo(other); + return typeComp != 0 ? typeComp : Condition.Id.CompareTo(other.Condition.Id); + } + + public override bool Equals(object? obj) + => obj is GlobalEqpManipulation other && Equals(other); + + public override int GetHashCode() + => HashCode.Combine((int)Type, Condition); + + public static bool operator ==(GlobalEqpManipulation left, GlobalEqpManipulation right) + => left.Equals(right); + + public static bool operator !=(GlobalEqpManipulation left, GlobalEqpManipulation right) + => !left.Equals(right); + + public override string ToString() + => $"Global EQP - {Type}{(Condition != 0 ? $" - {Condition.Id}" : string.Empty)}"; + + public MetaIndex FileIndex() + => (MetaIndex)(-1); +} diff --git a/Penumbra/Meta/Manipulations/GlobalEqpType.cs b/Penumbra/Meta/Manipulations/GlobalEqpType.cs new file mode 100644 index 00000000..57d99d56 --- /dev/null +++ b/Penumbra/Meta/Manipulations/GlobalEqpType.cs @@ -0,0 +1,61 @@ +namespace Penumbra.Meta.Manipulations; + +public enum GlobalEqpType +{ + DoNotHideEarrings, + DoNotHideNecklace, + DoNotHideBracelets, + DoNotHideRingR, + DoNotHideRingL, + DoNotHideHrothgarHats, + DoNotHideVieraHats, +} + +public static class GlobalEqpExtensions +{ + public static bool HasCondition(this GlobalEqpType type) + => type switch + { + GlobalEqpType.DoNotHideEarrings => true, + GlobalEqpType.DoNotHideNecklace => true, + GlobalEqpType.DoNotHideBracelets => true, + GlobalEqpType.DoNotHideRingR => true, + GlobalEqpType.DoNotHideRingL => true, + GlobalEqpType.DoNotHideHrothgarHats => false, + GlobalEqpType.DoNotHideVieraHats => false, + _ => false, + }; + + + public static ReadOnlySpan ToName(this GlobalEqpType type) + => type switch + { + GlobalEqpType.DoNotHideEarrings => "Do Not Hide Earrings"u8, + GlobalEqpType.DoNotHideNecklace => "Do Not Hide Necklaces"u8, + GlobalEqpType.DoNotHideBracelets => "Do Not Hide Bracelets"u8, + GlobalEqpType.DoNotHideRingR => "Do Not Hide Rings (Right Finger)"u8, + GlobalEqpType.DoNotHideRingL => "Do Not Hide Rings (Left Finger)"u8, + GlobalEqpType.DoNotHideHrothgarHats => "Do Not Hide Hats for Hrothgar"u8, + GlobalEqpType.DoNotHideVieraHats => "Do Not Hide Hats for Viera"u8, + _ => "\0"u8, + }; + + public static ReadOnlySpan ToDescription(this GlobalEqpType type) + => type switch + { + GlobalEqpType.DoNotHideEarrings => "Prevents the game from hiding earrings through other models when a specific earring is worn."u8, + GlobalEqpType.DoNotHideNecklace => + "Prevents the game from hiding necklaces through other models when a specific necklace is worn."u8, + GlobalEqpType.DoNotHideBracelets => + "Prevents the game from hiding bracelets through other models when a specific bracelet is worn."u8, + GlobalEqpType.DoNotHideRingR => + "Prevents the game from hiding rings worn on the right finger through other models when a specific ring is worn on the right finger."u8, + GlobalEqpType.DoNotHideRingL => + "Prevents the game from hiding rings worn on the left finger through other models when a specific ring is worn on the left finger."u8, + GlobalEqpType.DoNotHideHrothgarHats => + "Prevents the game from hiding any hats for Hrothgar that are normally flagged to not display on them."u8, + GlobalEqpType.DoNotHideVieraHats => + "Prevents the game from hiding any hats for Viera that are normally flagged to not display on them."u8, + _ => "\0"u8, + }; +} diff --git a/Penumbra/Meta/Manipulations/MetaManipulation.cs b/Penumbra/Meta/Manipulations/MetaManipulation.cs index ed184d52..f22de809 100644 --- a/Penumbra/Meta/Manipulations/MetaManipulation.cs +++ b/Penumbra/Meta/Manipulations/MetaManipulation.cs @@ -21,13 +21,14 @@ public readonly struct MetaManipulation : IEquatable, ICompara public enum Type : byte { - Unknown = 0, - Imc = 1, - Eqdp = 2, - Eqp = 3, - Est = 4, - Gmp = 5, - Rsp = 6, + Unknown = 0, + Imc = 1, + Eqdp = 2, + Eqp = 3, + Est = 4, + Gmp = 5, + Rsp = 6, + GlobalEqp = 7, } [FieldOffset(0)] @@ -54,6 +55,10 @@ public readonly struct MetaManipulation : IEquatable, ICompara [JsonIgnore] public readonly ImcManipulation Imc = default; + [FieldOffset(0)] + [JsonIgnore] + public readonly GlobalEqpManipulation GlobalEqp = default; + [FieldOffset(15)] [JsonConverter(typeof(StringEnumConverter))] [JsonProperty("Type")] @@ -63,14 +68,15 @@ public readonly struct MetaManipulation : IEquatable, ICompara { get => ManipulationType switch { - Type.Unknown => null, - Type.Imc => Imc, - Type.Eqdp => Eqdp, - Type.Eqp => Eqp, - Type.Est => Est, - Type.Gmp => Gmp, - Type.Rsp => Rsp, - _ => null, + 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 { @@ -100,6 +106,10 @@ public readonly struct MetaManipulation : IEquatable, ICompara Imc = m; ManipulationType = m.Validate(true) ? Type.Imc : Type.Unknown; return; + case GlobalEqpManipulation m: + GlobalEqp = m; + ManipulationType = m.Validate() ? Type.GlobalEqp : Type.Unknown; + return; } } } @@ -108,13 +118,14 @@ public readonly struct MetaManipulation : IEquatable, ICompara { 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(), - _ => false, + 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, }; } @@ -154,6 +165,12 @@ public readonly struct MetaManipulation : IEquatable, ICompara ManipulationType = Type.Imc; } + public MetaManipulation(GlobalEqpManipulation eqp) + { + GlobalEqp = eqp; + ManipulationType = Type.GlobalEqp; + } + public static implicit operator MetaManipulation(EqpManipulation eqp) => new(eqp); @@ -172,6 +189,9 @@ public readonly struct MetaManipulation : IEquatable, ICompara 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) @@ -179,13 +199,14 @@ public readonly struct MetaManipulation : IEquatable, ICompara 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), - _ => throw new ArgumentOutOfRangeException(), + 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(), }; } @@ -196,13 +217,14 @@ public readonly struct MetaManipulation : IEquatable, ICompara 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), - _ => false, + 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, }; } @@ -213,13 +235,14 @@ public readonly struct MetaManipulation : IEquatable, ICompara 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), - _ => throw new ArgumentOutOfRangeException(), + 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(), }; } @@ -229,13 +252,14 @@ public readonly struct MetaManipulation : IEquatable, ICompara 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(), - _ => 0, + 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) @@ -249,13 +273,14 @@ public readonly struct MetaManipulation : IEquatable, ICompara 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(), - _ => "Invalid", + 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() @@ -263,14 +288,15 @@ public readonly struct MetaManipulation : IEquatable, ICompara { 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}", - _ => string.Empty, - }; - + 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); diff --git a/Penumbra/Mods/Editor/ModMetaEditor.cs b/Penumbra/Mods/Editor/ModMetaEditor.cs index 2f7fd04c..829161f5 100644 --- a/Penumbra/Mods/Editor/ModMetaEditor.cs +++ b/Penumbra/Mods/Editor/ModMetaEditor.cs @@ -6,19 +6,21 @@ namespace Penumbra.Mods.Editor; public class ModMetaEditor(ModManager modManager) { - 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 _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 int OtherImcCount { get; private set; } - public int OtherEqpCount { get; private set; } - public int OtherEqdpCount { get; private set; } - public int OtherGmpCount { get; private set; } - public int OtherEstCount { get; private set; } - public int OtherRspCount { get; private set; } + public int OtherImcCount { get; private set; } + public int OtherEqpCount { get; private set; } + public int OtherEqdpCount { get; private set; } + public int OtherGmpCount { get; private set; } + public int OtherEstCount { get; private set; } + public int OtherRspCount { get; private set; } + public int OtherGlobalEqpCount { get; private set; } public bool Changes { get; private set; } @@ -40,17 +42,21 @@ public class ModMetaEditor(ModManager modManager) 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), - _ => false, + 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, }; } @@ -58,13 +64,14 @@ public class ModMetaEditor(ModManager modManager) { 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), - _ => false, + 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; @@ -74,13 +81,14 @@ public class ModMetaEditor(ModManager modManager) { 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), - _ => false, + 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; @@ -100,17 +108,19 @@ public class ModMetaEditor(ModManager modManager) _gmp.Clear(); _est.Clear(); _rsp.Clear(); + _globalEqp.Clear(); Changes = true; } public void Load(Mod mod, IModDataContainer currentOption) { - OtherImcCount = 0; - OtherEqpCount = 0; - OtherEqdpCount = 0; - OtherGmpCount = 0; - OtherEstCount = 0; - OtherRspCount = 0; + OtherImcCount = 0; + OtherEqpCount = 0; + OtherEqdpCount = 0; + OtherGmpCount = 0; + OtherEstCount = 0; + OtherRspCount = 0; + OtherGlobalEqpCount = 0; foreach (var option in mod.AllDataContainers) { if (option == currentOption) @@ -138,6 +148,9 @@ public class ModMetaEditor(ModManager modManager) case MetaManipulation.Type.Rsp: ++OtherRspCount; break; + case MetaManipulation.Type.GlobalEqp: + ++OtherGlobalEqpCount; + break; } } } @@ -179,6 +192,9 @@ public class ModMetaEditor(ModManager modManager) case MetaManipulation.Type.Rsp: _rsp.Add(manip.Rsp); break; + case MetaManipulation.Type.GlobalEqp: + _globalEqp.Add(manip.GlobalEqp); + break; } } @@ -191,5 +207,6 @@ public class ModMetaEditor(ModManager modManager) .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(_rsp.Select(m => (MetaManipulation)m)) + .Concat(_globalEqp.Select(m => (MetaManipulation)m)); } diff --git a/Penumbra/Mods/SubMods/IModDataContainer.cs b/Penumbra/Mods/SubMods/IModDataContainer.cs index a6ab491f..7f7ef4a6 100644 --- a/Penumbra/Mods/SubMods/IModDataContainer.cs +++ b/Penumbra/Mods/SubMods/IModDataContainer.cs @@ -5,17 +5,16 @@ using Penumbra.String.Classes; namespace Penumbra.Mods.SubMods; - public interface IModDataContainer { - public IMod Mod { get; } + 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 HashSet Manipulations { get; set; } - public string GetName(); - public string GetFullName(); + public string GetName(); + public string GetFullName(); public (int GroupIndex, int DataIndex) GetDataIndices(); } diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs index 68933c9e..7cf75c03 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs @@ -2,6 +2,7 @@ using Dalamud.Interface; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Structs; @@ -75,6 +76,8 @@ public partial class ModEditWindow _editor.MetaEditor.OtherGmpCount); DrawEditHeader(_editor.MetaEditor.Rsp, "Racial Scaling Edits (RSP)###RSP", 5, RspRow.Draw, RspRow.DrawNew, _editor.MetaEditor.OtherRspCount); + DrawEditHeader(_editor.MetaEditor.GlobalEqp, "Global Equipment Parameter Edits Edits (Global EQP)###GEQP", 4, GlobalEqpRow.Draw, + GlobalEqpRow.DrawNew, _editor.MetaEditor.OtherGlobalEqpCount); } @@ -702,6 +705,69 @@ public partial class ModEditWindow } } + 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 }; + } + + 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}"); + } + } + } + // 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) diff --git a/Penumbra/Util/IdentifierExtensions.cs b/Penumbra/Util/IdentifierExtensions.cs index 392a5aba..7368c7c8 100644 --- a/Penumbra/Util/IdentifierExtensions.cs +++ b/Penumbra/Util/IdentifierExtensions.cs @@ -76,6 +76,26 @@ public static class IdentifierExtensions 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; } }