Add support for Global EQP Changes.

This commit is contained in:
Ottermandias 2024-05-26 13:30:35 +02:00
parent f9527970cb
commit ed083f2a4c
15 changed files with 471 additions and 133 deletions

@ -1 +1 @@
Subproject commit 539d138700543e7c2c6c918f9f68e33228111e4d
Subproject commit 07cc26f196984a44711b3bc4c412947d863288bd

View file

@ -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;

View file

@ -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<KeyValuePair<MetaManipulation,
{
private readonly MetaFileManager _manager;
private readonly ModCollection _collection;
private readonly Dictionary<MetaManipulation, IMod> _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<MetaManipulation, IMod> _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<KeyValuePair<MetaManipulation,
_cmpCache.Reset();
_imcCache.Reset(_collection);
_manipulations.Clear();
_globalEqpCache.Clear();
}
public void Dispose()
@ -96,6 +97,9 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
_manipulations[manip] = mod;
}
if (manip.ManipulationType is MetaManipulation.Type.GlobalEqp)
return _globalEqpCache.Add(manip.GlobalEqp);
if (!_manager.CharacterUtility.Ready)
return true;
@ -119,6 +123,10 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
lock (_manipulations)
{
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;
}
@ -183,6 +191,9 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
public MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type)
=> _estCache.TemporarilySetFiles(_manager, type);
public unsafe EqpEntry ApplyGlobalEqp(EqpEntry baseEntry, CharacterArmor* armor)
=> _globalEqpCache.Apply(baseEntry, armor);
/// <summary> Try to obtain a manipulated IMC file. </summary>
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file)
@ -193,8 +204,8 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
var eqdpFile = _eqdpCache.EqdpFile(race, accessory);
if (eqdpFile != null)
return primaryId.Id < eqdpFile.Count ? eqdpFile[primaryId] : default;
else
return Meta.Files.ExpandedEqdpFile.GetDefault(_manager, race, accessory, primaryId);
return Meta.Files.ExpandedEqdpFile.GetDefault(_manager, race, accessory, primaryId);
}
internal ushort GetEstEntry(EstManipulation.EstType type, GenderRace genderRace, PrimaryId primaryId)
@ -213,14 +224,15 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
{
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.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;

View file

@ -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;
}

View file

@ -187,6 +187,9 @@ public partial class TexToolsMeta
b.Write(manip.Gmp.Entry.UnknownTotal);
}
break;
case MetaManipulation.Type.GlobalEqp:
// Not Supported
break;
}

View file

@ -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;

View file

@ -23,6 +23,7 @@ public unsafe class EqpHook : FastHook<EqpHook.Delegate>
{
using var eqp = _metaState.ResolveEqpData(_metaState.EqpCollection.ModCollection);
Task.Result.Original(utility, flags, armor);
*flags = _metaState.EqpCollection.ModCollection.ApplyGlobalEqp(*flags, armor);
}
else
{

View file

@ -0,0 +1,80 @@
using OtterGui.Services;
using Penumbra.GameData.Structs;
namespace Penumbra.Meta.Manipulations;
public struct GlobalEqpCache : IService
{
private readonly HashSet<PrimaryId> _doNotHideEarrings = [];
private readonly HashSet<PrimaryId> _doNotHideNecklace = [];
private readonly HashSet<PrimaryId> _doNotHideBracelets = [];
private readonly HashSet<PrimaryId> _doNotHideRingL = [];
private readonly HashSet<PrimaryId> _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,
};
}

View file

@ -0,0 +1,50 @@
using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs;
namespace Penumbra.Meta.Manipulations;
public readonly struct GlobalEqpManipulation : IMetaManipulation<GlobalEqpManipulation>
{
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);
}

View file

@ -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<byte> 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<byte> 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,
};
}

View file

@ -21,13 +21,14 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, 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<MetaManipulation>, 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<MetaManipulation>, 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<MetaManipulation>, 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<MetaManipulation>, 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<MetaManipulation>, 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<MetaManipulation>, 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<MetaManipulation>, 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<MetaManipulation>, 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<MetaManipulation>, 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<MetaManipulation>, 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<MetaManipulation>, 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<MetaManipulation>, 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);

View file

@ -6,19 +6,21 @@ namespace Penumbra.Mods.Editor;
public class ModMetaEditor(ModManager modManager)
{
private readonly HashSet<ImcManipulation> _imc = [];
private readonly HashSet<EqpManipulation> _eqp = [];
private readonly HashSet<EqdpManipulation> _eqdp = [];
private readonly HashSet<GmpManipulation> _gmp = [];
private readonly HashSet<EstManipulation> _est = [];
private readonly HashSet<RspManipulation> _rsp = [];
private readonly HashSet<ImcManipulation> _imc = [];
private readonly HashSet<EqpManipulation> _eqp = [];
private readonly HashSet<EqdpManipulation> _eqdp = [];
private readonly HashSet<GmpManipulation> _gmp = [];
private readonly HashSet<EstManipulation> _est = [];
private readonly HashSet<RspManipulation> _rsp = [];
private readonly HashSet<GlobalEqpManipulation> _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<RspManipulation> Rsp
=> _rsp;
public IReadOnlySet<GlobalEqpManipulation> 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));
}

View file

@ -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<Utf8GamePath, FullPath> Files { get; set; }
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; }
public HashSet<MetaManipulation> Manipulations { get; set; }
public Dictionary<Utf8GamePath, FullPath> Files { get; set; }
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; }
public HashSet<MetaManipulation> Manipulations { get; set; }
public string GetName();
public string GetFullName();
public string GetName();
public string GetFullName();
public (int GroupIndex, int DataIndex) GetDataIndices();
}

View file

@ -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<GlobalEqpType>())
{
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)

View file

@ -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;
}
}