mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-21 23:37:47 +01:00
Get rid off all MetaManipulation things.
This commit is contained in:
parent
361082813b
commit
3170edfeb6
63 changed files with 2422 additions and 2847 deletions
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
96
Penumbra/Collections/Cache/GlobalEqpCache.cs
Normal file
96
Penumbra/Collections/Cache/GlobalEqpCache.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
89
Penumbra/Collections/Cache/IMetaCache.cs
Normal file
89
Penumbra/Collections/Cache/IMetaCache.cs
Normal 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 _)
|
||||
{ }
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
81
Penumbra/Collections/Cache/RspCache.cs
Normal file
81
Penumbra/Collections/Cache/RspCache.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue