Get rid off all MetaManipulation things.

This commit is contained in:
Ottermandias 2024-06-14 13:38:36 +02:00
parent 361082813b
commit 3170edfeb6
63 changed files with 2422 additions and 2847 deletions

View file

@ -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<RspManipulation> _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();
}
}

View file

@ -36,7 +36,7 @@ public sealed class CollectionCache : IDisposable
=> ConflictDict.Values;
public SingleArray<ModConflicts> Conflicts(IMod mod)
=> ConflictDict.TryGetValue(mod, out var c) ? c : new SingleArray<ModConflicts>();
=> ConflictDict.TryGetValue(mod, out SingleArray<ModConflicts> c) ? c : new SingleArray<ModConflicts>();
private int _changedItemsSaveCounter = -1;
@ -233,8 +233,20 @@ public sealed class CollectionCache : IDisposable
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 @@ public sealed class CollectionCache : IDisposable
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 @@ public sealed class CollectionCache : IDisposable
// 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 @@ public sealed class CollectionCache : IDisposable
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 @@ public sealed class CollectionCache : IDisposable
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);
}

View file

@ -9,12 +9,12 @@ namespace Penumbra.Collections.Cache;
/// </summary>
public class CollectionModData
{
private readonly Dictionary<IMod, (HashSet<Utf8GamePath>, HashSet<MetaManipulation>)> _data = new();
private readonly Dictionary<IMod, (HashSet<Utf8GamePath>, HashSet<IMetaIdentifier>)> _data = new();
public IEnumerable<(IMod, IReadOnlySet<Utf8GamePath>, IReadOnlySet<MetaManipulation>)> Data
=> _data.Select(kvp => (kvp.Key, (IReadOnlySet<Utf8GamePath>)kvp.Value.Item1, (IReadOnlySet<MetaManipulation>)kvp.Value.Item2));
public IEnumerable<(IMod, IReadOnlySet<Utf8GamePath>, IReadOnlySet<IMetaIdentifier>)> Data
=> _data.Select(kvp => (kvp.Key, (IReadOnlySet<Utf8GamePath>)kvp.Value.Item1, (IReadOnlySet<IMetaIdentifier>)kvp.Value.Item2));
public (IReadOnlyCollection<Utf8GamePath> Paths, IReadOnlyCollection<MetaManipulation> Manipulations) RemoveMod(IMod mod)
public (IReadOnlyCollection<Utf8GamePath> Paths, IReadOnlyCollection<IMetaIdentifier> Manipulations) RemoveMod(IMod mod)
{
if (_data.Remove(mod, out var data))
return data;
@ -35,7 +35,7 @@ public class CollectionModData
}
}
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 class CollectionModData
_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);

View file

@ -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 @@ using Penumbra.Meta.Manipulations;
namespace Penumbra.Collections.Cache;
public readonly struct EqdpCache : IDisposable
public sealed class EqdpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<EqdpIdentifier, EqdpEntry>(manager, collection)
{
private readonly ExpandedEqdpFile?[] _eqdpFiles = new ExpandedEqdpFile[CharacterUtilityData.EqdpIndices.Length]; // TODO: female Hrothgar
private readonly List<EqdpManipulation> _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 readonly struct EqdpCache : IDisposable
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<ExpandedEqdpFile>())
{
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();
}
public bool ApplyMod(MetaFileManager manager, EqdpManipulation manip)
protected override void ApplyModInternal(EqdpIdentifier identifier, EqdpEntry entry)
{
_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, entry);
}
public bool RevertMod(MetaFileManager manager, EqdpManipulation manip)
protected override void RevertModInternal(EqdpIdentifier identifier)
{
if (!_eqdpManipulations.Remove(manip))
if (GetFile(identifier) is { } file)
Apply(file, identifier, ExpandedEqdpFile.GetDefault(Manager, identifier));
}
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;
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 readonly struct EqdpCache : IDisposable
_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());
}
}

View file

@ -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 @@ using Penumbra.Meta.Manipulations;
namespace Penumbra.Collections.Cache;
public struct EqpCache : IDisposable
public sealed class EqpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<EqpIdentifier, EqpEntry>(manager, collection)
{
private ExpandedEqpFile? _eqpFile = null;
private readonly List<EqpManipulation> _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);
public void Reset()
Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed EQP manipulations.");
}
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);
}
}

View file

@ -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 @@ using Penumbra.Meta.Manipulations;
namespace Penumbra.Collections.Cache;
public struct EstCache : IDisposable
public sealed class EstCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<EstIdentifier, EstEntry>(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<EstManipulation> _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 struct EstCache : IDisposable
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;
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
{
EstType.Hair => _estHairFile!,
EstType.Face => _estFaceFile!,
EstType.Body => _estBodyFile!,
EstType.Head => _estHeadFile!,
_ => throw new ArgumentOutOfRangeException(),
};
return manip.Apply(file);
if (GetFile(identifier) is { } file)
Apply(file, identifier, EstFile.GetDefault(Manager, identifier.Slot, identifier.GenderRace, identifier.SetId));
}
public void Dispose()
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();
@ -133,6 +116,21 @@ public struct EstCache : IDisposable
_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,
};
}
}

View file

@ -0,0 +1,96 @@
using OtterGui.Services;
using Penumbra.GameData.Structs;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor;
namespace Penumbra.Collections.Cache;
public class GlobalEqpCache : Dictionary<GlobalEqpManipulation, IMod>, 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 new void Clear()
{
base.Clear();
_doNotHideEarrings.Clear();
_doNotHideNecklace.Clear();
_doNotHideBracelets.Clear();
_doNotHideRingL.Clear();
_doNotHideRingR.Clear();
_doNotHideHrothgarHats = false;
_doNotHideVieraHats = false;
}
public unsafe EqpEntry Apply(EqpEntry original, CharacterArmor* armor)
{
if (Count == 0)
return original;
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 (_doNotHideRingR.Contains(armor[8].Set))
original |= EqpEntry.HandShowRingR;
if (_doNotHideRingL.Contains(armor[9].Set))
original |= EqpEntry.HandShowRingL;
return original;
}
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),
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,
};
return true;
}
public bool RevertMod(GlobalEqpManipulation manipulation, [NotNullWhen(true)] out IMod? mod)
{
if (!Remove(manipulation, out mod))
return false;
_ = 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,
};
return true;
}
}

View file

@ -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 @@ using Penumbra.Meta.Manipulations;
namespace Penumbra.Collections.Cache;
public struct GmpCache : IDisposable
public sealed class GmpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<GmpIdentifier, GmpEntry>(manager, collection)
{
private ExpandedGmpFile? _gmpFile = null;
private readonly List<GmpManipulation> _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();
}
public bool ApplyMod(MetaFileManager manager, GmpManipulation manip)
protected override void ApplyModInternal(GmpIdentifier identifier, GmpEntry entry)
{
_gmpManipulations.AddOrReplace(manip);
_gmpFile ??= new ExpandedGmpFile(manager);
return manip.Apply(_gmpFile);
if (GetFile() is { } file)
Apply(file, identifier, entry);
}
public bool RevertMod(MetaFileManager manager, GmpManipulation manip)
protected override void RevertModInternal(GmpIdentifier identifier)
{
if (!_gmpManipulations.Remove(manip))
if (GetFile() is { } file)
Apply(file, identifier, ExpandedGmpFile.GetDefault(Manager, identifier.SetId));
}
public static bool Apply(ExpandedGmpFile file, GmpIdentifier identifier, GmpEntry entry)
{
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);
}
}

View file

@ -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<TIdentifier, TEntry>
: Dictionary<TIdentifier, (IMod Source, TEntry Entry)>, 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<TEntry>.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 _)
{ }
}

View file

@ -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 @@ using Penumbra.String.Classes;
namespace Penumbra.Collections.Cache;
public readonly struct ImcCache : IDisposable
public sealed class ImcCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<ImcIdentifier, ImcEntry>(manager, collection)
{
private readonly Dictionary<Utf8GamePath, ImcFile> _imcFiles = [];
private readonly List<(ImcManipulation, ImcFile)> _imcManipulations = [];
private readonly Dictionary<Utf8GamePath, (ImcFile, HashSet<ImcIdentifier>)> _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, file) in _imcFiles)
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, 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;
if (Manager.CharacterUtility.Ready)
ApplyFile(identifier, entry);
}
var idx = _imcManipulations.FindIndex(p => p.Item1.Equals(manip));
if (idx < 0)
{
idx = _imcManipulations.Count;
_imcManipulations.Add((manip, null!));
}
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 def = ImcFile.GetDefault(Manager, pair.Item1.Path, identifier.EquipSlot, identifier.Variant, out _);
if (!Apply(pair.Item1, identifier, def))
return;
var fullPath = PathDataHandler.CreateImc(file.Path.Path, collection);
collection._cache!.ForceFile(file.Path, fullPath);
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);
}

View file

@ -1,3 +1,4 @@
using System.IO.Pipes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Interop.Services;
@ -9,238 +10,174 @@ using Penumbra.String.Classes;
namespace Penumbra.Collections.Cache;
public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation, IMod>>
public class MetaCache(MetaFileManager manager, ModCollection collection)
{
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 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;
=> Eqp.Count + Eqdp.Count + Est.Count + Gmp.Count + Rsp.Count + Imc.Count + GlobalEqp.Count;
public IReadOnlyCollection<MetaManipulation> Manipulations
=> _manipulations.Keys;
public IEnumerator<KeyValuePair<MetaManipulation, IMod>> 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();
}
public bool TryGetMod(IMetaIdentifier identifier, [NotNullWhen(true)] out IMod? mod)
{
mod = null;
return identifier switch
{
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,
};
static bool Convert<T>((IMod, T) pair, out IMod mod)
{
mod = pair.Item1;
return true;
}
}
public bool RevertMod(IMetaIdentifier identifier, [NotNullWhen(true)] out IMod? mod)
=> identifier switch
{
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,
};
public bool ApplyMod(IMod mod, IMetaIdentifier identifier, object entry)
=> identifier switch
{
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();
public bool ApplyMod(MetaManipulation manip, IMod mod)
{
lock (_manipulations)
{
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;
// Imc manipulations do not require character utility,
// but they do require the file space to be ready.
return 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,
};
}
public bool RevertMod(MetaManipulation manip, [NotNullWhen(true)] out IMod? mod)
{
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;
}
// Imc manipulations do not require character utility,
// but they do require the file space to be ready.
return manip.ManipulationType 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,
};
}
/// <summary> Set a single file. </summary>
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;
}
}
/// <summary> Set the currently relevant IMC files for the collection cache. </summary>
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);
/// <summary> Try to obtain a manipulated IMC file. </summary>
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);
/// <summary> Use this when CharacterUtility becomes ready. </summary>
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));
}

View file

@ -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<RspIdentifier, RspEntry>(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);
}
}