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.Communication;
using Penumbra.Mods.Editor; using Penumbra.Mods.Editor;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.Mods.Manager;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Collections.Cache; namespace Penumbra.Collections.Cache;

View file

@ -4,7 +4,6 @@ using Penumbra.Interop.Services;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
using Penumbra.Mods.Editor; using Penumbra.Mods.Editor;
using Penumbra.String.Classes; using Penumbra.String.Classes;
@ -21,6 +20,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
private GmpCache _gmpCache = new(); private GmpCache _gmpCache = new();
private CmpCache _cmpCache = new(); private CmpCache _cmpCache = new();
private readonly ImcCache _imcCache = new(); private readonly ImcCache _imcCache = new();
private GlobalEqpCache _globalEqpCache = new();
public bool TryGetValue(MetaManipulation manip, [NotNullWhen(true)] out IMod? mod) public bool TryGetValue(MetaManipulation manip, [NotNullWhen(true)] out IMod? mod)
{ {
@ -69,6 +69,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
_cmpCache.Reset(); _cmpCache.Reset();
_imcCache.Reset(_collection); _imcCache.Reset(_collection);
_manipulations.Clear(); _manipulations.Clear();
_globalEqpCache.Clear();
} }
public void Dispose() public void Dispose()
@ -96,6 +97,9 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
_manipulations[manip] = mod; _manipulations[manip] = mod;
} }
if (manip.ManipulationType is MetaManipulation.Type.GlobalEqp)
return _globalEqpCache.Add(manip.GlobalEqp);
if (!_manager.CharacterUtility.Ready) if (!_manager.CharacterUtility.Ready)
return true; return true;
@ -119,6 +123,10 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
lock (_manipulations) lock (_manipulations)
{ {
var ret = _manipulations.Remove(manip, out mod); var ret = _manipulations.Remove(manip, out mod);
if (manip.ManipulationType is MetaManipulation.Type.GlobalEqp)
return _globalEqpCache.Remove(manip.GlobalEqp);
if (!_manager.CharacterUtility.Ready) if (!_manager.CharacterUtility.Ready)
return ret; return ret;
} }
@ -183,6 +191,9 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
public MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type) public MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type)
=> _estCache.TemporarilySetFiles(_manager, 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> /// <summary> Try to obtain a manipulated IMC file. </summary>
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file)
@ -193,7 +204,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
var eqdpFile = _eqdpCache.EqdpFile(race, accessory); var eqdpFile = _eqdpCache.EqdpFile(race, accessory);
if (eqdpFile != null) if (eqdpFile != null)
return primaryId.Id < eqdpFile.Count ? eqdpFile[primaryId] : default; 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);
} }
@ -219,6 +230,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
MetaManipulation.Type.Gmp => _gmpCache.ApplyMod(_manager, manip.Gmp), MetaManipulation.Type.Gmp => _gmpCache.ApplyMod(_manager, manip.Gmp),
MetaManipulation.Type.Rsp => _cmpCache.ApplyMod(_manager, manip.Rsp), MetaManipulation.Type.Rsp => _cmpCache.ApplyMod(_manager, manip.Rsp),
MetaManipulation.Type.Imc => _imcCache.ApplyMod(_manager, _collection, manip.Imc), MetaManipulation.Type.Imc => _imcCache.ApplyMod(_manager, _collection, manip.Imc),
MetaManipulation.Type.GlobalEqp => false,
MetaManipulation.Type.Unknown => false, MetaManipulation.Type.Unknown => false,
_ => false, _ => false,
} }

View file

@ -8,6 +8,7 @@ using Penumbra.String.Classes;
using Penumbra.Collections.Cache; using Penumbra.Collections.Cache;
using Penumbra.Interop.Services; using Penumbra.Interop.Services;
using Penumbra.Mods.Editor; using Penumbra.Mods.Editor;
using Penumbra.GameData.Structs;
namespace Penumbra.Collections; namespace Penumbra.Collections;
@ -114,4 +115,7 @@ public partial class ModCollection
public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstManipulation.EstType type) public MetaList.MetaReverter TemporarilySetEstFile(CharacterUtility utility, EstManipulation.EstType type)
=> _cache?.Meta.TemporarilySetEstFile(type) => _cache?.Meta.TemporarilySetEstFile(type)
?? utility.TemporarilyResetResource((MetaIndex)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); b.Write(manip.Gmp.Entry.UnknownTotal);
} }
break;
case MetaManipulation.Type.GlobalEqp:
// Not Supported
break; break;
} }

View file

@ -44,7 +44,7 @@ public partial class TexToolsMeta
var headerStart = reader.ReadUInt32(); var headerStart = reader.ReadUInt32();
reader.BaseStream.Seek(headerStart, SeekOrigin.Begin); 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) for (var i = 0; i < numHeaders; ++i)
{ {
var currentOffset = reader.BaseStream.Position; 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); using var eqp = _metaState.ResolveEqpData(_metaState.EqpCollection.ModCollection);
Task.Result.Original(utility, flags, armor); Task.Result.Original(utility, flags, armor);
*flags = _metaState.EqpCollection.ModCollection.ApplyGlobalEqp(*flags, armor);
} }
else 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

@ -28,6 +28,7 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
Est = 4, Est = 4,
Gmp = 5, Gmp = 5,
Rsp = 6, Rsp = 6,
GlobalEqp = 7,
} }
[FieldOffset(0)] [FieldOffset(0)]
@ -54,6 +55,10 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
[JsonIgnore] [JsonIgnore]
public readonly ImcManipulation Imc = default; public readonly ImcManipulation Imc = default;
[FieldOffset(0)]
[JsonIgnore]
public readonly GlobalEqpManipulation GlobalEqp = default;
[FieldOffset(15)] [FieldOffset(15)]
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
[JsonProperty("Type")] [JsonProperty("Type")]
@ -70,6 +75,7 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
Type.Est => Est, Type.Est => Est,
Type.Gmp => Gmp, Type.Gmp => Gmp,
Type.Rsp => Rsp, Type.Rsp => Rsp,
Type.GlobalEqp => GlobalEqp,
_ => null, _ => null,
}; };
init init
@ -100,6 +106,10 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
Imc = m; Imc = m;
ManipulationType = m.Validate(true) ? Type.Imc : Type.Unknown; ManipulationType = m.Validate(true) ? Type.Imc : Type.Unknown;
return; return;
case GlobalEqpManipulation m:
GlobalEqp = m;
ManipulationType = m.Validate() ? Type.GlobalEqp : Type.Unknown;
return;
} }
} }
} }
@ -114,6 +124,7 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
Type.Est => Est.Validate(), Type.Est => Est.Validate(),
Type.Gmp => Gmp.Validate(), Type.Gmp => Gmp.Validate(),
Type.Rsp => Rsp.Validate(), Type.Rsp => Rsp.Validate(),
Type.GlobalEqp => GlobalEqp.Validate(),
_ => false, _ => false,
}; };
} }
@ -154,6 +165,12 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
ManipulationType = Type.Imc; ManipulationType = Type.Imc;
} }
public MetaManipulation(GlobalEqpManipulation eqp)
{
GlobalEqp = eqp;
ManipulationType = Type.GlobalEqp;
}
public static implicit operator MetaManipulation(EqpManipulation eqp) public static implicit operator MetaManipulation(EqpManipulation eqp)
=> new(eqp); => new(eqp);
@ -172,6 +189,9 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
public static implicit operator MetaManipulation(ImcManipulation imc) public static implicit operator MetaManipulation(ImcManipulation imc)
=> new(imc); => new(imc);
public static implicit operator MetaManipulation(GlobalEqpManipulation eqp)
=> new(eqp);
public bool EntryEquals(MetaManipulation other) public bool EntryEquals(MetaManipulation other)
{ {
if (ManipulationType != other.ManipulationType) if (ManipulationType != other.ManipulationType)
@ -185,6 +205,7 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
Type.Est => Est.Entry.Equals(other.Est.Entry), Type.Est => Est.Entry.Equals(other.Est.Entry),
Type.Rsp => Rsp.Entry.Equals(other.Rsp.Entry), Type.Rsp => Rsp.Entry.Equals(other.Rsp.Entry),
Type.Imc => Imc.Entry.Equals(other.Imc.Entry), Type.Imc => Imc.Entry.Equals(other.Imc.Entry),
Type.GlobalEqp => true,
_ => throw new ArgumentOutOfRangeException(), _ => throw new ArgumentOutOfRangeException(),
}; };
} }
@ -202,6 +223,7 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
Type.Est => Est.Equals(other.Est), Type.Est => Est.Equals(other.Est),
Type.Rsp => Rsp.Equals(other.Rsp), Type.Rsp => Rsp.Equals(other.Rsp),
Type.Imc => Imc.Equals(other.Imc), Type.Imc => Imc.Equals(other.Imc),
Type.GlobalEqp => GlobalEqp.Equals(other.GlobalEqp),
_ => false, _ => false,
}; };
} }
@ -219,6 +241,7 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
Type.Est => Est.Copy(other.Est.Entry), Type.Est => Est.Copy(other.Est.Entry),
Type.Rsp => Rsp.Copy(other.Rsp.Entry), Type.Rsp => Rsp.Copy(other.Rsp.Entry),
Type.Imc => Imc.Copy(other.Imc.Entry), Type.Imc => Imc.Copy(other.Imc.Entry),
Type.GlobalEqp => GlobalEqp,
_ => throw new ArgumentOutOfRangeException(), _ => throw new ArgumentOutOfRangeException(),
}; };
} }
@ -235,6 +258,7 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
Type.Est => Est.GetHashCode(), Type.Est => Est.GetHashCode(),
Type.Rsp => Rsp.GetHashCode(), Type.Rsp => Rsp.GetHashCode(),
Type.Imc => Imc.GetHashCode(), Type.Imc => Imc.GetHashCode(),
Type.GlobalEqp => GlobalEqp.GetHashCode(),
_ => 0, _ => 0,
}; };
@ -255,6 +279,7 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
Type.Est => Est.ToString(), Type.Est => Est.ToString(),
Type.Rsp => Rsp.ToString(), Type.Rsp => Rsp.ToString(),
Type.Imc => Imc.ToString(), Type.Imc => Imc.ToString(),
Type.GlobalEqp => GlobalEqp.ToString(),
_ => "Invalid", _ => "Invalid",
}; };
@ -268,6 +293,7 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
Type.Est => $"{Est.Entry}", Type.Est => $"{Est.Entry}",
Type.Gmp => $"{Gmp.Entry.Value}", Type.Gmp => $"{Gmp.Entry.Value}",
Type.Rsp => $"{Rsp.Entry}", Type.Rsp => $"{Rsp.Entry}",
Type.GlobalEqp => string.Empty,
_ => string.Empty, _ => string.Empty,
}; };

View file

@ -12,6 +12,7 @@ public class ModMetaEditor(ModManager modManager)
private readonly HashSet<GmpManipulation> _gmp = []; private readonly HashSet<GmpManipulation> _gmp = [];
private readonly HashSet<EstManipulation> _est = []; private readonly HashSet<EstManipulation> _est = [];
private readonly HashSet<RspManipulation> _rsp = []; private readonly HashSet<RspManipulation> _rsp = [];
private readonly HashSet<GlobalEqpManipulation> _globalEqp = [];
public int OtherImcCount { get; private set; } public int OtherImcCount { get; private set; }
public int OtherEqpCount { get; private set; } public int OtherEqpCount { get; private set; }
@ -19,6 +20,7 @@ public class ModMetaEditor(ModManager modManager)
public int OtherGmpCount { get; private set; } public int OtherGmpCount { get; private set; }
public int OtherEstCount { get; private set; } public int OtherEstCount { get; private set; }
public int OtherRspCount { get; private set; } public int OtherRspCount { get; private set; }
public int OtherGlobalEqpCount { get; private set; }
public bool Changes { get; private set; } public bool Changes { get; private set; }
@ -40,6 +42,9 @@ public class ModMetaEditor(ModManager modManager)
public IReadOnlySet<RspManipulation> Rsp public IReadOnlySet<RspManipulation> Rsp
=> _rsp; => _rsp;
public IReadOnlySet<GlobalEqpManipulation> GlobalEqp
=> _globalEqp;
public bool CanAdd(MetaManipulation m) public bool CanAdd(MetaManipulation m)
{ {
return m.ManipulationType switch return m.ManipulationType switch
@ -50,6 +55,7 @@ public class ModMetaEditor(ModManager modManager)
MetaManipulation.Type.Est => !_est.Contains(m.Est), MetaManipulation.Type.Est => !_est.Contains(m.Est),
MetaManipulation.Type.Gmp => !_gmp.Contains(m.Gmp), MetaManipulation.Type.Gmp => !_gmp.Contains(m.Gmp),
MetaManipulation.Type.Rsp => !_rsp.Contains(m.Rsp), MetaManipulation.Type.Rsp => !_rsp.Contains(m.Rsp),
MetaManipulation.Type.GlobalEqp => !_globalEqp.Contains(m.GlobalEqp),
_ => false, _ => false,
}; };
} }
@ -64,6 +70,7 @@ public class ModMetaEditor(ModManager modManager)
MetaManipulation.Type.Est => _est.Add(m.Est), MetaManipulation.Type.Est => _est.Add(m.Est),
MetaManipulation.Type.Gmp => _gmp.Add(m.Gmp), MetaManipulation.Type.Gmp => _gmp.Add(m.Gmp),
MetaManipulation.Type.Rsp => _rsp.Add(m.Rsp), MetaManipulation.Type.Rsp => _rsp.Add(m.Rsp),
MetaManipulation.Type.GlobalEqp => _globalEqp.Add(m.GlobalEqp),
_ => false, _ => false,
}; };
Changes |= added; Changes |= added;
@ -80,6 +87,7 @@ public class ModMetaEditor(ModManager modManager)
MetaManipulation.Type.Est => _est.Remove(m.Est), MetaManipulation.Type.Est => _est.Remove(m.Est),
MetaManipulation.Type.Gmp => _gmp.Remove(m.Gmp), MetaManipulation.Type.Gmp => _gmp.Remove(m.Gmp),
MetaManipulation.Type.Rsp => _rsp.Remove(m.Rsp), MetaManipulation.Type.Rsp => _rsp.Remove(m.Rsp),
MetaManipulation.Type.GlobalEqp => _globalEqp.Remove(m.GlobalEqp),
_ => false, _ => false,
}; };
Changes |= deleted; Changes |= deleted;
@ -100,6 +108,7 @@ public class ModMetaEditor(ModManager modManager)
_gmp.Clear(); _gmp.Clear();
_est.Clear(); _est.Clear();
_rsp.Clear(); _rsp.Clear();
_globalEqp.Clear();
Changes = true; Changes = true;
} }
@ -111,6 +120,7 @@ public class ModMetaEditor(ModManager modManager)
OtherGmpCount = 0; OtherGmpCount = 0;
OtherEstCount = 0; OtherEstCount = 0;
OtherRspCount = 0; OtherRspCount = 0;
OtherGlobalEqpCount = 0;
foreach (var option in mod.AllDataContainers) foreach (var option in mod.AllDataContainers)
{ {
if (option == currentOption) if (option == currentOption)
@ -138,6 +148,9 @@ public class ModMetaEditor(ModManager modManager)
case MetaManipulation.Type.Rsp: case MetaManipulation.Type.Rsp:
++OtherRspCount; ++OtherRspCount;
break; break;
case MetaManipulation.Type.GlobalEqp:
++OtherGlobalEqpCount;
break;
} }
} }
} }
@ -179,6 +192,9 @@ public class ModMetaEditor(ModManager modManager)
case MetaManipulation.Type.Rsp: case MetaManipulation.Type.Rsp:
_rsp.Add(manip.Rsp); _rsp.Add(manip.Rsp);
break; 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(_eqp.Select(m => (MetaManipulation)m))
.Concat(_est.Select(m => (MetaManipulation)m)) .Concat(_est.Select(m => (MetaManipulation)m))
.Concat(_gmp.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,7 +5,6 @@ using Penumbra.String.Classes;
namespace Penumbra.Mods.SubMods; namespace Penumbra.Mods.SubMods;
public interface IModDataContainer public interface IModDataContainer
{ {
public IMod Mod { get; } public IMod Mod { get; }

View file

@ -2,6 +2,7 @@ using Dalamud.Interface;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
@ -75,6 +76,8 @@ public partial class ModEditWindow
_editor.MetaEditor.OtherGmpCount); _editor.MetaEditor.OtherGmpCount);
DrawEditHeader(_editor.MetaEditor.Rsp, "Racial Scaling Edits (RSP)###RSP", 5, RspRow.Draw, RspRow.DrawNew, DrawEditHeader(_editor.MetaEditor.Rsp, "Racial Scaling Edits (RSP)###RSP", 5, RspRow.Draw, RspRow.DrawNew,
_editor.MetaEditor.OtherRspCount); _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. // A number input for ids with a optional max id of given width.
// Returns true if newId changed against currentId. // 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) 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: case MetaManipulation.Type.Rsp:
changedItems.TryAdd($"{manip.Rsp.SubRace.ToName()} {manip.Rsp.Attribute.ToFullString()}", null); changedItems.TryAdd($"{manip.Rsp.SubRace.ToName()} {manip.Rsp.Attribute.ToFullString()}", null);
break; 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;
} }
} }