mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +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,5 +1,8 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
|
|
@ -7,17 +10,34 @@ namespace Penumbra.Api.Api;
|
|||
|
||||
public class MetaApi(CollectionResolver collectionResolver, ApiHelpers helpers) : IPenumbraApiMeta, IApiService
|
||||
{
|
||||
public const int CurrentVersion = 0;
|
||||
|
||||
public string GetPlayerMetaManipulations()
|
||||
{
|
||||
var collection = collectionResolver.PlayerCollection();
|
||||
var set = collection.MetaCache?.Manipulations.ToArray() ?? [];
|
||||
return Functions.ToCompressedBase64(set, MetaManipulation.CurrentVersion);
|
||||
return CompressMetaManipulations(collection);
|
||||
}
|
||||
|
||||
public string GetMetaManipulations(int gameObjectIdx)
|
||||
{
|
||||
helpers.AssociatedCollection(gameObjectIdx, out var collection);
|
||||
var set = collection.MetaCache?.Manipulations.ToArray() ?? [];
|
||||
return Functions.ToCompressedBase64(set, MetaManipulation.CurrentVersion);
|
||||
return CompressMetaManipulations(collection);
|
||||
}
|
||||
|
||||
internal static string CompressMetaManipulations(ModCollection collection)
|
||||
{
|
||||
var array = new JArray();
|
||||
if (collection.MetaCache is { } cache)
|
||||
{
|
||||
MetaDictionary.SerializeTo(array, cache.GlobalEqp.Select(kvp => kvp.Key));
|
||||
MetaDictionary.SerializeTo(array, cache.Imc.Select(kvp => new KeyValuePair<ImcIdentifier, ImcEntry>(kvp.Key, kvp.Value.Entry)));
|
||||
MetaDictionary.SerializeTo(array, cache.Eqp.Select(kvp => new KeyValuePair<EqpIdentifier, EqpEntry>(kvp.Key, kvp.Value.Entry)));
|
||||
MetaDictionary.SerializeTo(array, cache.Eqdp.Select(kvp => new KeyValuePair<EqdpIdentifier, EqdpEntry>(kvp.Key, kvp.Value.Entry)));
|
||||
MetaDictionary.SerializeTo(array, cache.Est.Select(kvp => new KeyValuePair<EstIdentifier, EstEntry>(kvp.Key, kvp.Value.Entry)));
|
||||
MetaDictionary.SerializeTo(array, cache.Rsp.Select(kvp => new KeyValuePair<RspIdentifier, RspEntry>(kvp.Key, kvp.Value.Entry)));
|
||||
MetaDictionary.SerializeTo(array, cache.Gmp.Select(kvp => new KeyValuePair<GmpIdentifier, GmpEntry>(kvp.Key, kvp.Value.Entry)));
|
||||
}
|
||||
|
||||
return Functions.ToCompressedBase64(array, CurrentVersion);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,11 +163,11 @@ public class TemporaryApi(
|
|||
{
|
||||
if (manipString.Length == 0)
|
||||
{
|
||||
manips = [];
|
||||
manips = new MetaDictionary();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Functions.FromCompressedBase64(manipString, out manips!) == MetaManipulation.CurrentVersion)
|
||||
if (Functions.FromCompressedBase64(manipString, out manips!) == MetaApi.CurrentVersion)
|
||||
return true;
|
||||
|
||||
manips = null;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using OtterGui;
|
|||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
using Penumbra.Collections.Manager;
|
||||
|
|
@ -102,8 +103,7 @@ public class TemporaryIpcTester(
|
|||
&& copyCollection is { HasCache: true })
|
||||
{
|
||||
var files = copyCollection.ResolvedFiles.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.Path.ToString());
|
||||
var manips = Functions.ToCompressedBase64(copyCollection.MetaCache?.Manipulations.ToArray() ?? Array.Empty<MetaManipulation>(),
|
||||
MetaManipulation.CurrentVersion);
|
||||
var manips = MetaApi.CompressMetaManipulations(copyCollection);
|
||||
_lastTempError = new AddTemporaryMod(pi).Invoke(_tempModName, guid, files, manips, 999);
|
||||
}
|
||||
|
||||
|
|
@ -188,8 +188,8 @@ public class TemporaryIpcTester(
|
|||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
using var tt = ImRaii.Tooltip();
|
||||
foreach (var manip in mod.Default.Manipulations)
|
||||
ImGui.TextUnformatted(manip.ToString());
|
||||
foreach (var identifier in mod.Default.Manipulations.Identifiers)
|
||||
ImGui.TextUnformatted(identifier.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
namespace Penumbra.Collections.Cache;
|
||||
|
||||
public struct GlobalEqpCache : IService
|
||||
public class GlobalEqpCache : Dictionary<GlobalEqpManipulation, IMod>, IService
|
||||
{
|
||||
private readonly HashSet<PrimaryId> _doNotHideEarrings = [];
|
||||
private readonly HashSet<PrimaryId> _doNotHideNecklace = [];
|
||||
|
|
@ -13,11 +15,9 @@ public struct GlobalEqpCache : IService
|
|||
private bool _doNotHideVieraHats;
|
||||
private bool _doNotHideHrothgarHats;
|
||||
|
||||
public GlobalEqpCache()
|
||||
{ }
|
||||
|
||||
public void Clear()
|
||||
public new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_doNotHideEarrings.Clear();
|
||||
_doNotHideNecklace.Clear();
|
||||
_doNotHideBracelets.Clear();
|
||||
|
|
@ -29,6 +29,9 @@ public struct GlobalEqpCache : IService
|
|||
|
||||
public unsafe EqpEntry Apply(EqpEntry original, CharacterArmor* armor)
|
||||
{
|
||||
if (Count == 0)
|
||||
return original;
|
||||
|
||||
if (_doNotHideVieraHats)
|
||||
original |= EqpEntry.HeadShowVieraHat;
|
||||
|
||||
|
|
@ -52,8 +55,13 @@ public struct GlobalEqpCache : IService
|
|||
return original;
|
||||
}
|
||||
|
||||
public bool Add(GlobalEqpManipulation manipulation)
|
||||
=> manipulation.Type switch
|
||||
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),
|
||||
|
|
@ -61,12 +69,18 @@ public struct GlobalEqpCache : IService
|
|||
GlobalEqpType.DoNotHideRingR => _doNotHideRingR.Add(manipulation.Condition),
|
||||
GlobalEqpType.DoNotHideRingL => _doNotHideRingL.Add(manipulation.Condition),
|
||||
GlobalEqpType.DoNotHideHrothgarHats => !_doNotHideHrothgarHats && (_doNotHideHrothgarHats = true),
|
||||
GlobalEqpType.DoNotHideVieraHats => !_doNotHideVieraHats && (_doNotHideVieraHats = true),
|
||||
_ => false,
|
||||
GlobalEqpType.DoNotHideVieraHats => !_doNotHideVieraHats && (_doNotHideVieraHats = true),
|
||||
_ => false,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Remove(GlobalEqpManipulation manipulation)
|
||||
=> manipulation.Type switch
|
||||
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),
|
||||
|
|
@ -74,7 +88,9 @@ public struct GlobalEqpCache : IService
|
|||
GlobalEqpType.DoNotHideRingR => _doNotHideRingR.Remove(manipulation.Condition),
|
||||
GlobalEqpType.DoNotHideRingL => _doNotHideRingL.Remove(manipulation.Condition),
|
||||
GlobalEqpType.DoNotHideHrothgarHats => _doNotHideHrothgarHats && !(_doNotHideHrothgarHats = false),
|
||||
GlobalEqpType.DoNotHideVieraHats => _doNotHideVieraHats && !(_doNotHideVieraHats = false),
|
||||
_ => 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,8 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
|
|||
_tasks.Clear();
|
||||
}
|
||||
|
||||
public Task<IoNotifier> ExportToGltf(in ExportConfig config, MdlFile mdl, IEnumerable<string> sklbPaths, Func<string, byte[]?> read, string outputPath)
|
||||
public Task<IoNotifier> ExportToGltf(in ExportConfig config, MdlFile mdl, IEnumerable<string> sklbPaths, Func<string, byte[]?> read,
|
||||
string outputPath)
|
||||
=> EnqueueWithResult(
|
||||
new ExportToGltfAction(this, config, mdl, sklbPaths, read, outputPath),
|
||||
action => action.Notifier
|
||||
|
|
@ -52,7 +53,7 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
|
|||
/// <summary> Try to find the .sklb paths for a .mdl file. </summary>
|
||||
/// <param name="mdlPath"> .mdl file to look up the skeletons for. </param>
|
||||
/// <param name="estManipulations"> Modified extra skeleton template parameters. </param>
|
||||
public string[] ResolveSklbsForMdl(string mdlPath, EstManipulation[] estManipulations)
|
||||
public string[] ResolveSklbsForMdl(string mdlPath, KeyValuePair<EstIdentifier, EstEntry>[] estManipulations)
|
||||
{
|
||||
var info = parser.GetFileInfo(mdlPath);
|
||||
if (info.FileType is not FileType.Model)
|
||||
|
|
@ -81,20 +82,18 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
|
|||
};
|
||||
}
|
||||
|
||||
private string[] ResolveEstSkeleton(EstType type, GameObjectInfo info, EstManipulation[] estManipulations)
|
||||
private string[] ResolveEstSkeleton(EstType type, GameObjectInfo info, KeyValuePair<EstIdentifier, EstEntry>[] estManipulations)
|
||||
{
|
||||
// Try to find an EST entry from the manipulations provided.
|
||||
var (gender, race) = info.GenderRace.Split();
|
||||
var modEst = estManipulations
|
||||
.FirstOrNull(est =>
|
||||
est.Gender == gender
|
||||
&& est.Race == race
|
||||
&& est.Slot == type
|
||||
&& est.SetId == info.PrimaryId
|
||||
.FirstOrNull(
|
||||
est => est.Key.GenderRace == info.GenderRace
|
||||
&& est.Key.Slot == type
|
||||
&& est.Key.SetId == info.PrimaryId
|
||||
);
|
||||
|
||||
// Try to use an entry from provided manipulations, falling back to the current collection.
|
||||
var targetId = modEst?.Entry
|
||||
var targetId = modEst?.Value
|
||||
?? collections.Current.MetaCache?.GetEstEntry(type, info.GenderRace, info.PrimaryId)
|
||||
?? EstEntry.Zero;
|
||||
|
||||
|
|
@ -102,7 +101,7 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
|
|||
if (targetId == EstEntry.Zero)
|
||||
return [];
|
||||
|
||||
return [GamePaths.Skeleton.Sklb.Path(info.GenderRace, EstManipulation.ToName(type), targetId.AsId)];
|
||||
return [GamePaths.Skeleton.Sklb.Path(info.GenderRace, type.ToName(), targetId.AsId)];
|
||||
}
|
||||
|
||||
/// <summary> Try to resolve the absolute path to a .mtrl from the potentially-partial path provided by a model. </summary>
|
||||
|
|
@ -250,9 +249,11 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
|
|||
var path = manager.ResolveMtrlPath(relativePath, notifier);
|
||||
if (path == null)
|
||||
return null;
|
||||
|
||||
var bytes = read(path);
|
||||
if (bytes == null)
|
||||
return null;
|
||||
|
||||
var mtrl = new MtrlFile(bytes);
|
||||
|
||||
return new MaterialExporter.Material
|
||||
|
|
|
|||
|
|
@ -13,15 +13,15 @@ public partial class TexToolsMeta
|
|||
private void DeserializeEqpEntry(MetaFileInfo metaFileInfo, byte[]? data)
|
||||
{
|
||||
// Eqp can only be valid for equipment.
|
||||
if (data == null || !metaFileInfo.EquipSlot.IsEquipment())
|
||||
var mask = Eqp.Mask(metaFileInfo.EquipSlot);
|
||||
if (data == null || mask == 0)
|
||||
return;
|
||||
|
||||
var value = Eqp.FromSlotAndBytes(metaFileInfo.EquipSlot, data);
|
||||
var def = new EqpManipulation(ExpandedEqpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId), metaFileInfo.EquipSlot,
|
||||
metaFileInfo.PrimaryId);
|
||||
var manip = new EqpManipulation(value, metaFileInfo.EquipSlot, metaFileInfo.PrimaryId);
|
||||
if (_keepDefault || def.Entry != manip.Entry)
|
||||
MetaManipulations.Add(manip);
|
||||
var identifier = new EqpIdentifier(metaFileInfo.PrimaryId, metaFileInfo.EquipSlot);
|
||||
var value = Eqp.FromSlotAndBytes(metaFileInfo.EquipSlot, data) & mask;
|
||||
var def = ExpandedEqpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId) & mask;
|
||||
if (_keepDefault || def != value)
|
||||
MetaManipulations.TryAdd(identifier, value);
|
||||
}
|
||||
|
||||
// Deserialize and check Eqdp Entries and add them to the list if they are non-default.
|
||||
|
|
@ -40,14 +40,12 @@ public partial class TexToolsMeta
|
|||
if (!gr.IsValid() || !metaFileInfo.EquipSlot.IsEquipment() && !metaFileInfo.EquipSlot.IsAccessory())
|
||||
continue;
|
||||
|
||||
var value = Eqdp.FromSlotAndBits(metaFileInfo.EquipSlot, (byteValue & 1) == 1, (byteValue & 2) == 2);
|
||||
var def = new EqdpManipulation(
|
||||
ExpandedEqdpFile.GetDefault(_metaFileManager, gr, metaFileInfo.EquipSlot.IsAccessory(), metaFileInfo.PrimaryId),
|
||||
metaFileInfo.EquipSlot,
|
||||
gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId);
|
||||
var manip = new EqdpManipulation(value, metaFileInfo.EquipSlot, gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId);
|
||||
if (_keepDefault || def.Entry != manip.Entry)
|
||||
MetaManipulations.Add(manip);
|
||||
var identifier = new EqdpIdentifier(metaFileInfo.PrimaryId, metaFileInfo.EquipSlot, gr);
|
||||
var mask = Eqdp.Mask(metaFileInfo.EquipSlot);
|
||||
var value = Eqdp.FromSlotAndBits(metaFileInfo.EquipSlot, (byteValue & 1) == 1, (byteValue & 2) == 2) & mask;
|
||||
var def = ExpandedEqdpFile.GetDefault(_metaFileManager, gr, metaFileInfo.EquipSlot.IsAccessory(), metaFileInfo.PrimaryId) & mask;
|
||||
if (_keepDefault || def != value)
|
||||
MetaManipulations.TryAdd(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,10 +55,10 @@ public partial class TexToolsMeta
|
|||
if (data == null)
|
||||
return;
|
||||
|
||||
var value = GmpEntry.FromTexToolsMeta(data.AsSpan(0, 5));
|
||||
var def = ExpandedGmpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId);
|
||||
var value = GmpEntry.FromTexToolsMeta(data.AsSpan(0, 5));
|
||||
var def = ExpandedGmpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId);
|
||||
if (_keepDefault || value != def)
|
||||
MetaManipulations.Add(new GmpManipulation(value, metaFileInfo.PrimaryId));
|
||||
MetaManipulations.TryAdd(new GmpIdentifier(metaFileInfo.PrimaryId), value);
|
||||
}
|
||||
|
||||
// Deserialize and check Est Entries and add them to the list if they are non-default.
|
||||
|
|
@ -74,7 +72,7 @@ public partial class TexToolsMeta
|
|||
for (var i = 0; i < num; ++i)
|
||||
{
|
||||
var gr = (GenderRace)reader.ReadUInt16();
|
||||
var id = reader.ReadUInt16();
|
||||
var id = (PrimaryId)reader.ReadUInt16();
|
||||
var value = new EstEntry(reader.ReadUInt16());
|
||||
var type = (metaFileInfo.SecondaryType, metaFileInfo.EquipSlot) switch
|
||||
{
|
||||
|
|
@ -87,9 +85,10 @@ public partial class TexToolsMeta
|
|||
if (!gr.IsValid() || type == 0)
|
||||
continue;
|
||||
|
||||
var def = EstFile.GetDefault(_metaFileManager, type, gr, id);
|
||||
var identifier = new EstIdentifier(id, type, gr);
|
||||
var def = EstFile.GetDefault(_metaFileManager, type, gr, id);
|
||||
if (_keepDefault || def != value)
|
||||
MetaManipulations.Add(new EstManipulation(gr.Split().Item1, gr.Split().Item2, type, id, value));
|
||||
MetaManipulations.TryAdd(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,20 +106,16 @@ public partial class TexToolsMeta
|
|||
ushort i = 0;
|
||||
try
|
||||
{
|
||||
var manip = new ImcManipulation(metaFileInfo.PrimaryType, metaFileInfo.SecondaryType, metaFileInfo.PrimaryId,
|
||||
metaFileInfo.SecondaryId, i, metaFileInfo.EquipSlot,
|
||||
new ImcEntry());
|
||||
var def = new ImcFile(_metaFileManager, manip.Identifier);
|
||||
var partIdx = ImcFile.PartIndex(manip.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0.
|
||||
var identifier = new ImcIdentifier(metaFileInfo.PrimaryId, 0, metaFileInfo.PrimaryType, metaFileInfo.SecondaryId,
|
||||
metaFileInfo.EquipSlot, metaFileInfo.SecondaryType);
|
||||
var file = new ImcFile(_metaFileManager, identifier);
|
||||
var partIdx = ImcFile.PartIndex(identifier.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0.
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (_keepDefault || !value.Equals(def.GetEntry(partIdx, (Variant)i)))
|
||||
{
|
||||
var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot,
|
||||
value);
|
||||
if (imc.Validate(true))
|
||||
MetaManipulations.Add(imc);
|
||||
}
|
||||
identifier = identifier with { Variant = (Variant)i };
|
||||
var def = file.GetEntry(partIdx, (Variant)i);
|
||||
if (_keepDefault || def != value && identifier.Validate())
|
||||
MetaManipulations.TryAdd(identifier, value);
|
||||
|
||||
++i;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Penumbra.Collections.Cache;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Meta;
|
||||
|
|
@ -8,7 +9,7 @@ namespace Penumbra.Import;
|
|||
|
||||
public partial class TexToolsMeta
|
||||
{
|
||||
public static void WriteTexToolsMeta(MetaFileManager manager, IEnumerable<MetaManipulation> manipulations, DirectoryInfo basePath)
|
||||
public static void WriteTexToolsMeta(MetaFileManager manager, MetaDictionary manipulations, DirectoryInfo basePath)
|
||||
{
|
||||
var files = ConvertToTexTools(manager, manipulations);
|
||||
|
||||
|
|
@ -27,49 +28,81 @@ public partial class TexToolsMeta
|
|||
}
|
||||
}
|
||||
|
||||
public static Dictionary<string, byte[]> ConvertToTexTools(MetaFileManager manager, IEnumerable<MetaManipulation> manips)
|
||||
public static Dictionary<string, byte[]> ConvertToTexTools(MetaFileManager manager, MetaDictionary manips)
|
||||
{
|
||||
var ret = new Dictionary<string, byte[]>();
|
||||
foreach (var group in manips.GroupBy(ManipToPath))
|
||||
foreach (var group in manips.Rsp.GroupBy(ManipToPath))
|
||||
{
|
||||
if (group.Key.Length == 0)
|
||||
continue;
|
||||
|
||||
var bytes = group.Key.EndsWith(".rgsp")
|
||||
? WriteRgspFile(manager, group.Key, group)
|
||||
: WriteMetaFile(manager, group.Key, group);
|
||||
var bytes = WriteRgspFile(manager, group);
|
||||
if (bytes.Length == 0)
|
||||
continue;
|
||||
|
||||
ret.Add(group.Key, bytes);
|
||||
}
|
||||
|
||||
foreach (var (file, dict) in SplitByFile(manips))
|
||||
{
|
||||
var bytes = WriteMetaFile(manager, file, dict);
|
||||
if (bytes.Length == 0)
|
||||
continue;
|
||||
|
||||
ret.Add(file, bytes);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static byte[] WriteRgspFile(MetaFileManager manager, string path, IEnumerable<MetaManipulation> manips)
|
||||
private static Dictionary<string, MetaDictionary> SplitByFile(MetaDictionary manips)
|
||||
{
|
||||
var list = manips.GroupBy(m => m.Rsp.Attribute).ToDictionary(m => m.Key, m => m.Last().Rsp);
|
||||
var ret = new Dictionary<string, MetaDictionary>();
|
||||
foreach (var (identifier, key) in manips.Imc)
|
||||
GetDict(ManipToPath(identifier)).TryAdd(identifier, key);
|
||||
|
||||
foreach (var (identifier, key) in manips.Eqp)
|
||||
GetDict(ManipToPath(identifier)).TryAdd(identifier, key);
|
||||
|
||||
foreach (var (identifier, key) in manips.Eqdp)
|
||||
GetDict(ManipToPath(identifier)).TryAdd(identifier, key);
|
||||
|
||||
foreach (var (identifier, key) in manips.Est)
|
||||
GetDict(ManipToPath(identifier)).TryAdd(identifier, key);
|
||||
|
||||
foreach (var (identifier, key) in manips.Gmp)
|
||||
GetDict(ManipToPath(identifier)).TryAdd(identifier, key);
|
||||
|
||||
ret.Remove(string.Empty);
|
||||
|
||||
return ret;
|
||||
|
||||
MetaDictionary GetDict(string path)
|
||||
{
|
||||
if (!ret.TryGetValue(path, out var dict))
|
||||
{
|
||||
dict = new MetaDictionary();
|
||||
ret.Add(path, dict);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] WriteRgspFile(MetaFileManager manager, IEnumerable<KeyValuePair<RspIdentifier, RspEntry>> manips)
|
||||
{
|
||||
var list = manips.GroupBy(m => m.Key.Attribute).ToDictionary(g => g.Key, g => g.Last());
|
||||
using var m = new MemoryStream(45);
|
||||
using var b = new BinaryWriter(m);
|
||||
// Version
|
||||
b.Write(byte.MaxValue);
|
||||
b.Write((ushort)2);
|
||||
|
||||
var race = list.First().Value.SubRace;
|
||||
var gender = list.First().Value.Attribute.ToGender();
|
||||
var race = list.First().Value.Key.SubRace;
|
||||
var gender = list.First().Value.Key.Attribute.ToGender();
|
||||
b.Write((byte)(race - 1)); // offset by one due to Unknown
|
||||
b.Write((byte)(gender - 1)); // offset by one due to Unknown
|
||||
|
||||
void Add(params RspAttribute[] attributes)
|
||||
{
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
var value = list.TryGetValue(attribute, out var tmp) ? tmp.Entry : CmpFile.GetDefault(manager, race, attribute);
|
||||
b.Write(value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (gender == Gender.Male)
|
||||
{
|
||||
Add(RspAttribute.MaleMinSize, RspAttribute.MaleMaxSize, RspAttribute.MaleMinTail, RspAttribute.MaleMaxTail);
|
||||
|
|
@ -82,12 +115,24 @@ public partial class TexToolsMeta
|
|||
}
|
||||
|
||||
return m.GetBuffer();
|
||||
|
||||
void Add(params RspAttribute[] attributes)
|
||||
{
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
var value = list.TryGetValue(attribute, out var tmp) ? tmp.Value : CmpFile.GetDefault(manager, race, attribute);
|
||||
b.Write(value.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] WriteMetaFile(MetaFileManager manager, string path, IEnumerable<MetaManipulation> manips)
|
||||
private static byte[] WriteMetaFile(MetaFileManager manager, string path, MetaDictionary manips)
|
||||
{
|
||||
var filteredManips = manips.GroupBy(m => m.ManipulationType).ToDictionary(p => p.Key, p => p.Select(x => x));
|
||||
|
||||
var headerCount = (manips.Imc.Count > 0 ? 1 : 0)
|
||||
+ (manips.Eqp.Count > 0 ? 1 : 0)
|
||||
+ (manips.Eqdp.Count > 0 ? 1 : 0)
|
||||
+ (manips.Est.Count > 0 ? 1 : 0)
|
||||
+ (manips.Gmp.Count > 0 ? 1 : 0);
|
||||
using var m = new MemoryStream();
|
||||
using var b = new BinaryWriter(m);
|
||||
|
||||
|
|
@ -101,7 +146,7 @@ public partial class TexToolsMeta
|
|||
b.Write((byte)0);
|
||||
|
||||
// Number of Headers
|
||||
b.Write((uint)filteredManips.Count);
|
||||
b.Write((uint)headerCount);
|
||||
// Current TT Size of Headers
|
||||
b.Write((uint)12);
|
||||
|
||||
|
|
@ -109,88 +154,44 @@ public partial class TexToolsMeta
|
|||
var headerStart = b.BaseStream.Position + 4;
|
||||
b.Write((uint)headerStart);
|
||||
|
||||
var offset = (uint)(b.BaseStream.Position + 12 * filteredManips.Count);
|
||||
foreach (var (header, data) in filteredManips)
|
||||
{
|
||||
b.Write((uint)header);
|
||||
b.Write(offset);
|
||||
|
||||
var size = WriteData(manager, b, offset, header, data);
|
||||
b.Write(size);
|
||||
offset += size;
|
||||
}
|
||||
var offset = (uint)(b.BaseStream.Position + 12 * manips.Count);
|
||||
offset += WriteData(manager, b, offset, manips.Imc);
|
||||
offset += WriteData(b, offset, manips.Eqdp);
|
||||
offset += WriteData(b, offset, manips.Eqp);
|
||||
offset += WriteData(b, offset, manips.Est);
|
||||
offset += WriteData(b, offset, manips.Gmp);
|
||||
|
||||
return m.ToArray();
|
||||
}
|
||||
|
||||
private static uint WriteData(MetaFileManager manager, BinaryWriter b, uint offset, MetaManipulation.Type type,
|
||||
IEnumerable<MetaManipulation> manips)
|
||||
private static uint WriteData(MetaFileManager manager, BinaryWriter b, uint offset, IReadOnlyDictionary<ImcIdentifier, ImcEntry> manips)
|
||||
{
|
||||
if (manips.Count == 0)
|
||||
return 0;
|
||||
|
||||
b.Write((uint)MetaManipulationType.Imc);
|
||||
b.Write(offset);
|
||||
|
||||
var oldPos = b.BaseStream.Position;
|
||||
b.Seek((int)offset, SeekOrigin.Begin);
|
||||
|
||||
switch (type)
|
||||
var refIdentifier = manips.First().Key;
|
||||
var baseFile = new ImcFile(manager, refIdentifier);
|
||||
foreach (var (identifier, entry) in manips)
|
||||
ImcCache.Apply(baseFile, identifier, entry);
|
||||
|
||||
var partIdx = refIdentifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory
|
||||
? ImcFile.PartIndex(refIdentifier.EquipSlot)
|
||||
: 0;
|
||||
|
||||
for (var i = 0; i <= baseFile.Count; ++i)
|
||||
{
|
||||
case MetaManipulation.Type.Imc:
|
||||
var allManips = manips.ToList();
|
||||
var baseFile = new ImcFile(manager, allManips[0].Imc.Identifier);
|
||||
foreach (var manip in allManips)
|
||||
manip.Imc.Apply(baseFile);
|
||||
|
||||
var partIdx = allManips[0].Imc.ObjectType is ObjectType.Equipment or ObjectType.Accessory
|
||||
? ImcFile.PartIndex(allManips[0].Imc.EquipSlot)
|
||||
: 0;
|
||||
|
||||
for (var i = 0; i <= baseFile.Count; ++i)
|
||||
{
|
||||
var entry = baseFile.GetEntry(partIdx, (Variant)i);
|
||||
b.Write(entry.MaterialId);
|
||||
b.Write(entry.DecalId);
|
||||
b.Write(entry.AttributeAndSound);
|
||||
b.Write(entry.VfxId);
|
||||
b.Write(entry.MaterialAnimationId);
|
||||
}
|
||||
|
||||
break;
|
||||
case MetaManipulation.Type.Eqdp:
|
||||
foreach (var manip in manips)
|
||||
{
|
||||
b.Write((uint)Names.CombinedRace(manip.Eqdp.Gender, manip.Eqdp.Race));
|
||||
var entry = (byte)(((uint)manip.Eqdp.Entry >> Eqdp.Offset(manip.Eqdp.Slot)) & 0x03);
|
||||
b.Write(entry);
|
||||
}
|
||||
|
||||
break;
|
||||
case MetaManipulation.Type.Eqp:
|
||||
foreach (var manip in manips)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes((ulong)manip.Eqp.Entry);
|
||||
var (numBytes, byteOffset) = Eqp.BytesAndOffset(manip.Eqp.Slot);
|
||||
for (var i = byteOffset; i < numBytes + byteOffset; ++i)
|
||||
b.Write(bytes[i]);
|
||||
}
|
||||
|
||||
break;
|
||||
case MetaManipulation.Type.Est:
|
||||
foreach (var manip in manips)
|
||||
{
|
||||
b.Write((ushort)Names.CombinedRace(manip.Est.Gender, manip.Est.Race));
|
||||
b.Write(manip.Est.SetId.Id);
|
||||
b.Write(manip.Est.Entry.Value);
|
||||
}
|
||||
|
||||
break;
|
||||
case MetaManipulation.Type.Gmp:
|
||||
foreach (var manip in manips)
|
||||
{
|
||||
b.Write((uint)manip.Gmp.Entry.Value);
|
||||
b.Write(manip.Gmp.Entry.UnknownTotal);
|
||||
}
|
||||
|
||||
break;
|
||||
case MetaManipulation.Type.GlobalEqp:
|
||||
// Not Supported
|
||||
break;
|
||||
var entry = baseFile.GetEntry(partIdx, (Variant)i);
|
||||
b.Write(entry.MaterialId);
|
||||
b.Write(entry.DecalId);
|
||||
b.Write(entry.AttributeAndSound);
|
||||
b.Write(entry.VfxId);
|
||||
b.Write(entry.MaterialAnimationId);
|
||||
}
|
||||
|
||||
var size = b.BaseStream.Position - offset;
|
||||
|
|
@ -198,19 +199,98 @@ public partial class TexToolsMeta
|
|||
return (uint)size;
|
||||
}
|
||||
|
||||
private static string ManipToPath(MetaManipulation manip)
|
||||
=> manip.ManipulationType switch
|
||||
{
|
||||
MetaManipulation.Type.Imc => ManipToPath(manip.Imc),
|
||||
MetaManipulation.Type.Eqdp => ManipToPath(manip.Eqdp),
|
||||
MetaManipulation.Type.Eqp => ManipToPath(manip.Eqp),
|
||||
MetaManipulation.Type.Est => ManipToPath(manip.Est),
|
||||
MetaManipulation.Type.Gmp => ManipToPath(manip.Gmp),
|
||||
MetaManipulation.Type.Rsp => ManipToPath(manip.Rsp),
|
||||
_ => string.Empty,
|
||||
};
|
||||
private static uint WriteData(BinaryWriter b, uint offset, IReadOnlyDictionary<EqdpIdentifier, EqdpEntryInternal> manips)
|
||||
{
|
||||
if (manips.Count == 0)
|
||||
return 0;
|
||||
|
||||
private static string ManipToPath(ImcManipulation manip)
|
||||
b.Write((uint)MetaManipulationType.Eqdp);
|
||||
b.Write(offset);
|
||||
|
||||
var oldPos = b.BaseStream.Position;
|
||||
b.Seek((int)offset, SeekOrigin.Begin);
|
||||
|
||||
foreach (var (identifier, entry) in manips)
|
||||
{
|
||||
b.Write((uint)identifier.GenderRace);
|
||||
b.Write(entry.AsByte);
|
||||
}
|
||||
|
||||
var size = b.BaseStream.Position - offset;
|
||||
b.Seek((int)oldPos, SeekOrigin.Begin);
|
||||
return (uint)size;
|
||||
}
|
||||
|
||||
private static uint WriteData(BinaryWriter b, uint offset,
|
||||
IReadOnlyDictionary<EqpIdentifier, EqpEntryInternal> manips)
|
||||
{
|
||||
if (manips.Count == 0)
|
||||
return 0;
|
||||
|
||||
b.Write((uint)MetaManipulationType.Imc);
|
||||
b.Write(offset);
|
||||
|
||||
var oldPos = b.BaseStream.Position;
|
||||
b.Seek((int)offset, SeekOrigin.Begin);
|
||||
|
||||
foreach (var (identifier, entry) in manips)
|
||||
{
|
||||
var numBytes = Eqp.BytesAndOffset(identifier.Slot).Item1;
|
||||
for (var i = 0; i < numBytes; ++i)
|
||||
b.Write((byte)(entry.Value >> (8 * i)));
|
||||
}
|
||||
|
||||
var size = b.BaseStream.Position - offset;
|
||||
b.Seek((int)oldPos, SeekOrigin.Begin);
|
||||
return (uint)size;
|
||||
}
|
||||
|
||||
private static uint WriteData(BinaryWriter b, uint offset, IReadOnlyDictionary<EstIdentifier, EstEntry> manips)
|
||||
{
|
||||
if (manips.Count == 0)
|
||||
return 0;
|
||||
|
||||
b.Write((uint)MetaManipulationType.Imc);
|
||||
b.Write(offset);
|
||||
|
||||
var oldPos = b.BaseStream.Position;
|
||||
b.Seek((int)offset, SeekOrigin.Begin);
|
||||
|
||||
foreach (var (identifier, entry) in manips)
|
||||
{
|
||||
b.Write((ushort)identifier.GenderRace);
|
||||
b.Write(identifier.SetId.Id);
|
||||
b.Write(entry.Value);
|
||||
}
|
||||
|
||||
var size = b.BaseStream.Position - offset;
|
||||
b.Seek((int)oldPos, SeekOrigin.Begin);
|
||||
return (uint)size;
|
||||
}
|
||||
|
||||
private static uint WriteData(BinaryWriter b, uint offset, IReadOnlyDictionary<GmpIdentifier, GmpEntry> manips)
|
||||
{
|
||||
if (manips.Count == 0)
|
||||
return 0;
|
||||
|
||||
b.Write((uint)MetaManipulationType.Imc);
|
||||
b.Write(offset);
|
||||
|
||||
var oldPos = b.BaseStream.Position;
|
||||
b.Seek((int)offset, SeekOrigin.Begin);
|
||||
|
||||
foreach (var entry in manips.Values)
|
||||
{
|
||||
b.Write((uint)entry.Value);
|
||||
b.Write(entry.UnknownTotal);
|
||||
}
|
||||
|
||||
var size = b.BaseStream.Position - offset;
|
||||
b.Seek((int)oldPos, SeekOrigin.Begin);
|
||||
return (uint)size;
|
||||
}
|
||||
|
||||
private static string ManipToPath(ImcIdentifier manip)
|
||||
{
|
||||
var path = manip.GamePath().ToString();
|
||||
var replacement = manip.ObjectType switch
|
||||
|
|
@ -224,33 +304,33 @@ public partial class TexToolsMeta
|
|||
return path.Replace(".imc", replacement);
|
||||
}
|
||||
|
||||
private static string ManipToPath(EqdpManipulation manip)
|
||||
private static string ManipToPath(EqdpIdentifier manip)
|
||||
=> manip.Slot.IsAccessory()
|
||||
? $"chara/accessory/a{manip.SetId:D4}/a{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta"
|
||||
: $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta";
|
||||
? $"chara/accessory/a{manip.SetId.Id:D4}/a{manip.SetId.Id:D4}_{manip.Slot.ToSuffix()}.meta"
|
||||
: $"chara/equipment/e{manip.SetId.Id:D4}/e{manip.SetId.Id:D4}_{manip.Slot.ToSuffix()}.meta";
|
||||
|
||||
private static string ManipToPath(EqpManipulation manip)
|
||||
private static string ManipToPath(EqpIdentifier manip)
|
||||
=> manip.Slot.IsAccessory()
|
||||
? $"chara/accessory/a{manip.SetId:D4}/a{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta"
|
||||
: $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta";
|
||||
? $"chara/accessory/a{manip.SetId.Id:D4}/a{manip.SetId.Id:D4}_{manip.Slot.ToSuffix()}.meta"
|
||||
: $"chara/equipment/e{manip.SetId.Id:D4}/e{manip.SetId.Id:D4}_{manip.Slot.ToSuffix()}.meta";
|
||||
|
||||
private static string ManipToPath(EstManipulation manip)
|
||||
private static string ManipToPath(EstIdentifier manip)
|
||||
{
|
||||
var raceCode = Names.CombinedRace(manip.Gender, manip.Race).ToRaceCode();
|
||||
return manip.Slot switch
|
||||
{
|
||||
EstType.Hair => $"chara/human/c{raceCode}/obj/hair/h{manip.SetId:D4}/c{raceCode}h{manip.SetId:D4}_hir.meta",
|
||||
EstType.Face => $"chara/human/c{raceCode}/obj/face/h{manip.SetId:D4}/c{raceCode}f{manip.SetId:D4}_fac.meta",
|
||||
EstType.Body => $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Body.ToSuffix()}.meta",
|
||||
EstType.Head => $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Head.ToSuffix()}.meta",
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
EstType.Hair => $"chara/human/c{raceCode}/obj/hair/h{manip.SetId.Id:D4}/c{raceCode}h{manip.SetId.Id:D4}_hir.meta",
|
||||
EstType.Face => $"chara/human/c{raceCode}/obj/face/h{manip.SetId.Id:D4}/c{raceCode}f{manip.SetId.Id:D4}_fac.meta",
|
||||
EstType.Body => $"chara/equipment/e{manip.SetId.Id:D4}/e{manip.SetId.Id:D4}_{EquipSlot.Body.ToSuffix()}.meta",
|
||||
EstType.Head => $"chara/equipment/e{manip.SetId.Id:D4}/e{manip.SetId.Id:D4}_{EquipSlot.Head.ToSuffix()}.meta",
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
|
||||
private static string ManipToPath(GmpManipulation manip)
|
||||
=> $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Head.ToSuffix()}.meta";
|
||||
private static string ManipToPath(GmpIdentifier manip)
|
||||
=> $"chara/equipment/e{manip.SetId.Id:D4}/e{manip.SetId.Id:D4}_{EquipSlot.Head.ToSuffix()}.meta";
|
||||
|
||||
|
||||
private static string ManipToPath(RspManipulation manip)
|
||||
=> $"chara/xls/charamake/rgsp/{(int)manip.SubRace - 1}-{(int)manip.Attribute.ToGender() - 1}.rgsp";
|
||||
private static string ManipToPath(KeyValuePair<RspIdentifier, RspEntry> manip)
|
||||
=> $"chara/xls/charamake/rgsp/{(int)manip.Key.SubRace - 1}-{(int)manip.Key.Attribute.ToGender() - 1}.rgsp";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,14 +42,6 @@ public partial class TexToolsMeta
|
|||
return Invalid;
|
||||
}
|
||||
|
||||
// Add the given values to the manipulations if they are not default.
|
||||
void Add(RspAttribute attribute, float value)
|
||||
{
|
||||
var def = CmpFile.GetDefault(manager, subRace, attribute);
|
||||
if (keepDefault || value != def.Value)
|
||||
ret.MetaManipulations.Add(new RspManipulation(subRace, attribute, new RspEntry(value)));
|
||||
}
|
||||
|
||||
if (gender == 1)
|
||||
{
|
||||
Add(RspAttribute.FemaleMinSize, br.ReadSingle());
|
||||
|
|
@ -73,5 +65,14 @@ public partial class TexToolsMeta
|
|||
}
|
||||
|
||||
return ret;
|
||||
|
||||
// Add the given values to the manipulations if they are not default.
|
||||
void Add(RspAttribute attribute, float value)
|
||||
{
|
||||
var identifier = new RspIdentifier(subRace, attribute);
|
||||
var def = CmpFile.GetDefault(manager, subRace, attribute);
|
||||
if (keepDefault || value != def.Value)
|
||||
ret.MetaManipulations.TryAdd(identifier, new RspEntry(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Meta;
|
||||
|
|
@ -22,10 +21,10 @@ public partial class TexToolsMeta
|
|||
public static readonly TexToolsMeta Invalid = new(null!, string.Empty, 0);
|
||||
|
||||
// The info class determines the files or table locations the changes need to apply to from the filename.
|
||||
public readonly uint Version;
|
||||
public readonly string FilePath;
|
||||
public readonly List<MetaManipulation> MetaManipulations = new();
|
||||
private readonly bool _keepDefault = false;
|
||||
public readonly uint Version;
|
||||
public readonly string FilePath;
|
||||
public readonly MetaDictionary MetaManipulations = new();
|
||||
private readonly bool _keepDefault;
|
||||
|
||||
private readonly MetaFileManager _metaFileManager;
|
||||
|
||||
|
|
@ -44,18 +43,18 @@ public partial class TexToolsMeta
|
|||
var headerStart = reader.ReadUInt32();
|
||||
reader.BaseStream.Seek(headerStart, SeekOrigin.Begin);
|
||||
|
||||
List<(MetaManipulation.Type type, uint offset, int size)> entries = [];
|
||||
List<(MetaManipulationType type, uint offset, int size)> entries = [];
|
||||
for (var i = 0; i < numHeaders; ++i)
|
||||
{
|
||||
var currentOffset = reader.BaseStream.Position;
|
||||
var type = (MetaManipulation.Type)reader.ReadUInt32();
|
||||
var type = (MetaManipulationType)reader.ReadUInt32();
|
||||
var offset = reader.ReadUInt32();
|
||||
var size = reader.ReadInt32();
|
||||
entries.Add((type, offset, size));
|
||||
reader.BaseStream.Seek(currentOffset + headerSize, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
byte[]? ReadEntry(MetaManipulation.Type type)
|
||||
byte[]? ReadEntry(MetaManipulationType type)
|
||||
{
|
||||
var idx = entries.FindIndex(t => t.type == type);
|
||||
if (idx < 0)
|
||||
|
|
@ -65,11 +64,11 @@ public partial class TexToolsMeta
|
|||
return reader.ReadBytes(entries[idx].size);
|
||||
}
|
||||
|
||||
DeserializeEqpEntry(metaInfo, ReadEntry(MetaManipulation.Type.Eqp));
|
||||
DeserializeGmpEntry(metaInfo, ReadEntry(MetaManipulation.Type.Gmp));
|
||||
DeserializeEqdpEntries(metaInfo, ReadEntry(MetaManipulation.Type.Eqdp));
|
||||
DeserializeEstEntries(metaInfo, ReadEntry(MetaManipulation.Type.Est));
|
||||
DeserializeImcEntries(metaInfo, ReadEntry(MetaManipulation.Type.Imc));
|
||||
DeserializeEqpEntry(metaInfo, ReadEntry(MetaManipulationType.Eqp));
|
||||
DeserializeGmpEntry(metaInfo, ReadEntry(MetaManipulationType.Gmp));
|
||||
DeserializeEqdpEntries(metaInfo, ReadEntry(MetaManipulationType.Eqdp));
|
||||
DeserializeEstEntries(metaInfo, ReadEntry(MetaManipulationType.Est));
|
||||
DeserializeImcEntries(metaInfo, ReadEntry(MetaManipulationType.Imc));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using Microsoft.VisualBasic;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ internal partial record ResolveContext
|
|||
{
|
||||
var metaCache = Global.Collection.MetaCache;
|
||||
var skeletonSet = metaCache?.GetEstEntry(type, raceCode, primary) ?? default;
|
||||
return (raceCode, EstManipulation.ToName(type), skeletonSet.AsId);
|
||||
return (raceCode, type.ToName(), skeletonSet.AsId);
|
||||
}
|
||||
|
||||
private unsafe Utf8GamePath ResolveSkeletonPathNative(uint partialSkeletonIndex)
|
||||
|
|
|
|||
|
|
@ -51,10 +51,6 @@ public class ImcChecker
|
|||
return entry;
|
||||
}
|
||||
|
||||
public CachedEntry GetDefaultEntry(ImcManipulation imcManip, bool storeCache)
|
||||
=> GetDefaultEntry(new ImcIdentifier(imcManip.PrimaryId, imcManip.Variant, imcManip.ObjectType, imcManip.SecondaryId.Id,
|
||||
imcManip.EquipSlot, imcManip.BodySlot), storeCache);
|
||||
|
||||
private static ImcFile? GetFile(ImcIdentifier identifier)
|
||||
{
|
||||
if (_dataManager == null)
|
||||
|
|
|
|||
|
|
@ -69,10 +69,16 @@ public readonly record struct EqdpIdentifier(PrimaryId SetId, EquipSlot Slot, Ge
|
|||
jObj["Slot"] = Slot.ToString();
|
||||
return jObj;
|
||||
}
|
||||
|
||||
public MetaManipulationType Type
|
||||
=> MetaManipulationType.Eqdp;
|
||||
}
|
||||
|
||||
public readonly record struct EqdpEntryInternal(bool Material, bool Model)
|
||||
{
|
||||
public byte AsByte
|
||||
=> (byte)(Material ? Model ? 3 : 1 : Model ? 2 : 0);
|
||||
|
||||
private EqdpEntryInternal((bool, bool) val)
|
||||
: this(val.Item1, val.Item2)
|
||||
{ }
|
||||
|
|
@ -83,4 +89,7 @@ public readonly record struct EqdpEntryInternal(bool Material, bool Model)
|
|||
|
||||
public EqdpEntry ToEntry(EquipSlot slot)
|
||||
=> Eqdp.FromSlotAndBits(slot, Material, Model);
|
||||
|
||||
public override string ToString()
|
||||
=> $"Material: {Material}, Model: {Model}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,110 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct EqdpManipulation(EqdpIdentifier identifier, EqdpEntry entry) : IMetaManipulation<EqdpManipulation>
|
||||
{
|
||||
[JsonIgnore]
|
||||
public EqdpIdentifier Identifier { get; } = identifier;
|
||||
|
||||
public EqdpEntry Entry { get; } = entry;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public Gender Gender
|
||||
=> Identifier.Gender;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ModelRace Race
|
||||
=> Identifier.Race;
|
||||
|
||||
public PrimaryId SetId
|
||||
=> Identifier.SetId;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public EquipSlot Slot
|
||||
=> Identifier.Slot;
|
||||
|
||||
[JsonConstructor]
|
||||
public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, PrimaryId setId)
|
||||
: this(new EqdpIdentifier(setId, slot, Names.CombinedRace(gender, race)), Eqdp.Mask(slot) & entry)
|
||||
{ }
|
||||
|
||||
public EqdpManipulation Copy(EqdpManipulation entry)
|
||||
{
|
||||
if (entry.Slot != Slot)
|
||||
{
|
||||
var (bit1, bit2) = entry.Entry.ToBits(entry.Slot);
|
||||
return new EqdpManipulation(Identifier, Eqdp.FromSlotAndBits(Slot, bit1, bit2));
|
||||
}
|
||||
|
||||
return new EqdpManipulation(Identifier, entry.Entry);
|
||||
}
|
||||
|
||||
public EqdpManipulation Copy(EqdpEntry entry)
|
||||
=> new(entry, Slot, Gender, Race, SetId);
|
||||
|
||||
public override string ToString()
|
||||
=> $"Eqdp - {SetId} - {Slot} - {Race.ToName()} - {Gender.ToName()}";
|
||||
|
||||
public bool Equals(EqdpManipulation other)
|
||||
=> Gender == other.Gender
|
||||
&& Race == other.Race
|
||||
&& SetId == other.SetId
|
||||
&& Slot == other.Slot;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is EqdpManipulation other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine((int)Gender, (int)Race, SetId, (int)Slot);
|
||||
|
||||
public int CompareTo(EqdpManipulation other)
|
||||
{
|
||||
var r = Race.CompareTo(other.Race);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
var g = Gender.CompareTo(other.Gender);
|
||||
if (g != 0)
|
||||
return g;
|
||||
|
||||
var set = SetId.Id.CompareTo(other.SetId.Id);
|
||||
return set != 0 ? set : Slot.CompareTo(other.Slot);
|
||||
}
|
||||
|
||||
public MetaIndex FileIndex()
|
||||
=> CharacterUtilityData.EqdpIdx(Names.CombinedRace(Gender, Race), Slot.IsAccessory());
|
||||
|
||||
public bool Apply(ExpandedEqdpFile file)
|
||||
{
|
||||
var entry = file[SetId];
|
||||
var mask = Eqdp.Mask(Slot);
|
||||
if ((entry & mask) == Entry)
|
||||
return false;
|
||||
|
||||
file[SetId] = (entry & ~mask) | Entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Validate()
|
||||
{
|
||||
var mask = Eqdp.Mask(Slot);
|
||||
if (mask == 0)
|
||||
return false;
|
||||
|
||||
if ((mask & Entry) != Entry)
|
||||
return false;
|
||||
|
||||
if (FileIndex() == (MetaIndex)(-1))
|
||||
return false;
|
||||
|
||||
// No check for set id.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,9 @@ public readonly record struct EqpIdentifier(PrimaryId SetId, EquipSlot Slot) : I
|
|||
jObj["Slot"] = Slot.ToString();
|
||||
return jObj;
|
||||
}
|
||||
|
||||
public MetaManipulationType Type
|
||||
=> MetaManipulationType.Eqp;
|
||||
}
|
||||
|
||||
public readonly record struct EqpEntryInternal(uint Value)
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct EqpManipulation(EqpIdentifier identifier, EqpEntry entry) : IMetaManipulation<EqpManipulation>
|
||||
{
|
||||
[JsonIgnore]
|
||||
public EqpIdentifier Identifier { get; } = identifier;
|
||||
|
||||
[JsonConverter(typeof(ForceNumericFlagEnumConverter))]
|
||||
public EqpEntry Entry { get; } = entry;
|
||||
|
||||
public PrimaryId SetId
|
||||
=> Identifier.SetId;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public EquipSlot Slot
|
||||
=> Identifier.Slot;
|
||||
|
||||
[JsonConstructor]
|
||||
public EqpManipulation(EqpEntry entry, EquipSlot slot, PrimaryId setId)
|
||||
: this(new EqpIdentifier(setId, slot), Eqp.Mask(slot) & entry)
|
||||
{ }
|
||||
|
||||
public EqpManipulation Copy(EqpEntry entry)
|
||||
=> new(Identifier, entry);
|
||||
|
||||
public override string ToString()
|
||||
=> $"Eqp - {SetId} - {Slot}";
|
||||
|
||||
public bool Equals(EqpManipulation other)
|
||||
=> Slot == other.Slot
|
||||
&& SetId == other.SetId;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is EqpManipulation other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine((int)Slot, SetId);
|
||||
|
||||
public int CompareTo(EqpManipulation other)
|
||||
{
|
||||
var set = SetId.Id.CompareTo(other.SetId.Id);
|
||||
return set != 0 ? set : Slot.CompareTo(other.Slot);
|
||||
}
|
||||
|
||||
public MetaIndex FileIndex()
|
||||
=> MetaIndex.Eqp;
|
||||
|
||||
public bool Apply(ExpandedEqpFile file)
|
||||
{
|
||||
var entry = file[SetId];
|
||||
var mask = Eqp.Mask(Slot);
|
||||
if ((entry & mask) == Entry)
|
||||
return false;
|
||||
|
||||
file[SetId] = (entry & ~mask) | Entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Validate()
|
||||
{
|
||||
var mask = Eqp.Mask(Slot);
|
||||
if (mask == 0)
|
||||
return false;
|
||||
if ((Entry & mask) != Entry)
|
||||
return false;
|
||||
|
||||
// No check for set id.
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -91,6 +91,9 @@ public readonly record struct EstIdentifier(PrimaryId SetId, EstType Slot, Gende
|
|||
jObj["Slot"] = Slot.ToString();
|
||||
return jObj;
|
||||
}
|
||||
|
||||
public MetaManipulationType Type
|
||||
=> MetaManipulationType.Est;
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(Converter))]
|
||||
|
|
@ -111,3 +114,16 @@ public readonly record struct EstEntry(ushort Value)
|
|||
=> new(serializer.Deserialize<ushort>(reader));
|
||||
}
|
||||
}
|
||||
|
||||
public static class EstTypeExtension
|
||||
{
|
||||
public static string ToName(this EstType type)
|
||||
=> type switch
|
||||
{
|
||||
EstType.Hair => "hair",
|
||||
EstType.Face => "face",
|
||||
EstType.Body => "top",
|
||||
EstType.Head => "met",
|
||||
_ => "unk",
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct EstManipulation(EstIdentifier identifier, EstEntry entry) : IMetaManipulation<EstManipulation>
|
||||
{
|
||||
public static string ToName(EstType type)
|
||||
=> type switch
|
||||
{
|
||||
EstType.Hair => "hair",
|
||||
EstType.Face => "face",
|
||||
EstType.Body => "top",
|
||||
EstType.Head => "met",
|
||||
_ => "unk",
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
public EstIdentifier Identifier { get; } = identifier;
|
||||
public EstEntry Entry { get; } = entry;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public Gender Gender
|
||||
=> Identifier.Gender;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ModelRace Race
|
||||
=> Identifier.Race;
|
||||
|
||||
public PrimaryId SetId
|
||||
=> Identifier.SetId;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public EstType Slot
|
||||
=> Identifier.Slot;
|
||||
|
||||
|
||||
[JsonConstructor]
|
||||
public EstManipulation(Gender gender, ModelRace race, EstType slot, PrimaryId setId, EstEntry entry)
|
||||
: this(new EstIdentifier(setId, slot, Names.CombinedRace(gender, race)), entry)
|
||||
{ }
|
||||
|
||||
public EstManipulation Copy(EstEntry entry)
|
||||
=> new(Identifier, entry);
|
||||
|
||||
|
||||
public override string ToString()
|
||||
=> $"Est - {SetId} - {Slot} - {Race.ToName()} {Gender.ToName()}";
|
||||
|
||||
public bool Equals(EstManipulation other)
|
||||
=> Gender == other.Gender
|
||||
&& Race == other.Race
|
||||
&& SetId == other.SetId
|
||||
&& Slot == other.Slot;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is EstManipulation other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine((int)Gender, (int)Race, SetId, (int)Slot);
|
||||
|
||||
public int CompareTo(EstManipulation other)
|
||||
{
|
||||
var r = Race.CompareTo(other.Race);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
var g = Gender.CompareTo(other.Gender);
|
||||
if (g != 0)
|
||||
return g;
|
||||
|
||||
var s = Slot.CompareTo(other.Slot);
|
||||
return s != 0 ? s : SetId.Id.CompareTo(other.SetId.Id);
|
||||
}
|
||||
|
||||
public MetaIndex FileIndex()
|
||||
=> (MetaIndex)Slot;
|
||||
|
||||
public bool Apply(EstFile file)
|
||||
{
|
||||
return file.SetEntry(Names.CombinedRace(Gender, Race), SetId.Id, Entry) switch
|
||||
{
|
||||
EstFile.EstEntryChange.Unchanged => false,
|
||||
EstFile.EstEntryChange.Changed => true,
|
||||
EstFile.EstEntryChange.Added => true,
|
||||
EstFile.EstEntryChange.Removed => true,
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
|
||||
public bool Validate()
|
||||
{
|
||||
if (!Enum.IsDefined(Slot))
|
||||
return false;
|
||||
if (Names.CombinedRace(Gender, Race) == GenderRace.Unknown)
|
||||
return false;
|
||||
|
||||
// No known check for set id or entry.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
public readonly struct GlobalEqpManipulation : IMetaManipulation<GlobalEqpManipulation>, IMetaIdentifier
|
||||
public readonly struct GlobalEqpManipulation : IMetaIdentifier
|
||||
{
|
||||
public GlobalEqpType Type { get; init; }
|
||||
public PrimaryId Condition { get; init; }
|
||||
|
|
@ -70,8 +71,29 @@ public readonly struct GlobalEqpManipulation : IMetaManipulation<GlobalEqpManipu
|
|||
=> $"Global EQP - {Type}{(Condition != 0 ? $" - {Condition.Id}" : string.Empty)}";
|
||||
|
||||
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
|
||||
{ }
|
||||
{
|
||||
var path = Type switch
|
||||
{
|
||||
GlobalEqpType.DoNotHideEarrings => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.Ears),
|
||||
GlobalEqpType.DoNotHideNecklace => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.Neck),
|
||||
GlobalEqpType.DoNotHideBracelets => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.Wrists),
|
||||
GlobalEqpType.DoNotHideRingR => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.RFinger),
|
||||
GlobalEqpType.DoNotHideRingL => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.LFinger),
|
||||
GlobalEqpType.DoNotHideHrothgarHats => string.Empty,
|
||||
GlobalEqpType.DoNotHideVieraHats => string.Empty,
|
||||
_ => string.Empty,
|
||||
};
|
||||
if (path.Length > 0)
|
||||
identifier.Identify(changedItems, path);
|
||||
else if (Type is GlobalEqpType.DoNotHideVieraHats)
|
||||
changedItems["All Hats for Viera"] = null;
|
||||
else if (Type is GlobalEqpType.DoNotHideHrothgarHats)
|
||||
changedItems["All Hats for Hrothgar"] = null;
|
||||
}
|
||||
|
||||
public MetaIndex FileIndex()
|
||||
=> MetaIndex.Eqp;
|
||||
|
||||
MetaManipulationType IMetaIdentifier.Type
|
||||
=> MetaManipulationType.GlobalEqp;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,4 +36,7 @@ public readonly record struct GmpIdentifier(PrimaryId SetId) : IMetaIdentifier,
|
|||
jObj["SetId"] = SetId.Id.ToString();
|
||||
return jObj;
|
||||
}
|
||||
|
||||
public MetaManipulationType Type
|
||||
=> MetaManipulationType.Gmp;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct GmpManipulation(GmpIdentifier identifier, GmpEntry entry) : IMetaManipulation<GmpManipulation>
|
||||
{
|
||||
[JsonIgnore]
|
||||
public GmpIdentifier Identifier { get; } = identifier;
|
||||
|
||||
public GmpEntry Entry { get; } = entry;
|
||||
|
||||
public PrimaryId SetId
|
||||
=> Identifier.SetId;
|
||||
|
||||
[JsonConstructor]
|
||||
public GmpManipulation(GmpEntry entry, PrimaryId setId)
|
||||
: this(new GmpIdentifier(setId), entry)
|
||||
{ }
|
||||
|
||||
public GmpManipulation Copy(GmpEntry entry)
|
||||
=> new(Identifier, entry);
|
||||
|
||||
public override string ToString()
|
||||
=> $"Gmp - {SetId}";
|
||||
|
||||
public bool Equals(GmpManipulation other)
|
||||
=> SetId == other.SetId;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is GmpManipulation other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> SetId.GetHashCode();
|
||||
|
||||
public int CompareTo(GmpManipulation other)
|
||||
=> SetId.Id.CompareTo(other.SetId.Id);
|
||||
|
||||
public MetaIndex FileIndex()
|
||||
=> MetaIndex.Gmp;
|
||||
|
||||
public bool Apply(ExpandedGmpFile file)
|
||||
{
|
||||
var entry = file[SetId];
|
||||
if (entry == Entry)
|
||||
return false;
|
||||
|
||||
file[SetId] = Entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Validate()
|
||||
// No known conditions.
|
||||
=> true;
|
||||
}
|
||||
|
|
@ -4,6 +4,18 @@ using Penumbra.Interop.Structs;
|
|||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
public enum MetaManipulationType : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
Imc = 1,
|
||||
Eqdp = 2,
|
||||
Eqp = 3,
|
||||
Est = 4,
|
||||
Gmp = 5,
|
||||
Rsp = 6,
|
||||
GlobalEqp = 7,
|
||||
}
|
||||
|
||||
public interface IMetaIdentifier
|
||||
{
|
||||
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems);
|
||||
|
|
@ -13,4 +25,8 @@ public interface IMetaIdentifier
|
|||
public bool Validate();
|
||||
|
||||
public JObject AddToJson(JObject jObj);
|
||||
|
||||
public MetaManipulationType Type { get; }
|
||||
|
||||
public string ToString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,6 @@ public readonly record struct ImcIdentifier(
|
|||
: this(primaryId, variant, slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment, 0, slot, BodySlot.Unknown)
|
||||
{ }
|
||||
|
||||
public ImcManipulation ToManipulation(ImcEntry entry)
|
||||
=> new(ObjectType, BodySlot, PrimaryId, SecondaryId.Id, Variant.Id, EquipSlot, entry);
|
||||
|
||||
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
|
||||
=> AddChangedItems(identifier, changedItems, false);
|
||||
|
||||
|
|
@ -193,4 +190,7 @@ public readonly record struct ImcIdentifier(
|
|||
jObj["BodySlot"] = BodySlot.ToString();
|
||||
return jObj;
|
||||
}
|
||||
|
||||
public MetaManipulationType Type
|
||||
=> MetaManipulationType.Imc;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
|
||||
{
|
||||
[JsonIgnore]
|
||||
public ImcIdentifier Identifier { get; private init; }
|
||||
|
||||
public ImcEntry Entry { get; private init; }
|
||||
|
||||
|
||||
public PrimaryId PrimaryId
|
||||
=> Identifier.PrimaryId;
|
||||
|
||||
public SecondaryId SecondaryId
|
||||
=> Identifier.SecondaryId;
|
||||
|
||||
public Variant Variant
|
||||
=> Identifier.Variant;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ObjectType ObjectType
|
||||
=> Identifier.ObjectType;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public EquipSlot EquipSlot
|
||||
=> Identifier.EquipSlot;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public BodySlot BodySlot
|
||||
=> Identifier.BodySlot;
|
||||
|
||||
public ImcManipulation(EquipSlot equipSlot, ushort variant, PrimaryId primaryId, ImcEntry entry)
|
||||
: this(new ImcIdentifier(equipSlot, primaryId, variant), entry)
|
||||
{ }
|
||||
|
||||
public ImcManipulation(ImcIdentifier identifier, ImcEntry entry)
|
||||
{
|
||||
Identifier = identifier;
|
||||
Entry = entry;
|
||||
}
|
||||
|
||||
|
||||
// Variants were initially ushorts but got shortened to bytes.
|
||||
// There are still some manipulations around that have values > 255 for variant,
|
||||
// so we change the unused value to something nonsensical in that case, just so they do not compare equal,
|
||||
// and clamp the variant to 255.
|
||||
[JsonConstructor]
|
||||
internal ImcManipulation(ObjectType objectType, BodySlot bodySlot, PrimaryId primaryId, SecondaryId secondaryId, ushort variant,
|
||||
EquipSlot equipSlot, ImcEntry entry)
|
||||
{
|
||||
Entry = entry;
|
||||
var v = (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue);
|
||||
Identifier = objectType switch
|
||||
{
|
||||
ObjectType.Accessory or ObjectType.Equipment => new ImcIdentifier(primaryId, v, objectType, 0, equipSlot,
|
||||
variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown),
|
||||
ObjectType.DemiHuman => new ImcIdentifier(primaryId, v, objectType, secondaryId, equipSlot, variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown),
|
||||
_ => new ImcIdentifier(primaryId, v, objectType, secondaryId, equipSlot, bodySlot == BodySlot.Unknown ? BodySlot.Body : BodySlot.Unknown),
|
||||
};
|
||||
}
|
||||
|
||||
public ImcManipulation Copy(ImcEntry entry)
|
||||
=> new(Identifier, entry);
|
||||
|
||||
public override string ToString()
|
||||
=> Identifier.ToString();
|
||||
|
||||
public bool Equals(ImcManipulation other)
|
||||
=> Identifier == other.Identifier;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is ImcManipulation other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Identifier.GetHashCode();
|
||||
|
||||
public int CompareTo(ImcManipulation other)
|
||||
=> Identifier.CompareTo(other.Identifier);
|
||||
|
||||
public MetaIndex FileIndex()
|
||||
=> Identifier.FileIndex();
|
||||
|
||||
public Utf8GamePath GamePath()
|
||||
=> Identifier.GamePath();
|
||||
|
||||
public bool Apply(ImcFile file)
|
||||
=> file.SetEntry(ImcFile.PartIndex(EquipSlot), Variant.Id, Entry);
|
||||
|
||||
public bool Validate(bool withMaterial)
|
||||
{
|
||||
if (!Identifier.Validate())
|
||||
return false;
|
||||
|
||||
if (withMaterial && Entry.MaterialId == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.Collections.Cache;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Util;
|
||||
using ImcEntry = Penumbra.GameData.Structs.ImcEntry;
|
||||
|
|
@ -7,7 +8,7 @@ using ImcEntry = Penumbra.GameData.Structs.ImcEntry;
|
|||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
[JsonConverter(typeof(Converter))]
|
||||
public class MetaDictionary : IEnumerable<MetaManipulation>
|
||||
public class MetaDictionary
|
||||
{
|
||||
private readonly Dictionary<ImcIdentifier, ImcEntry> _imc = [];
|
||||
private readonly Dictionary<EqpIdentifier, EqpEntryInternal> _eqp = [];
|
||||
|
|
@ -20,32 +21,50 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
public IReadOnlyDictionary<ImcIdentifier, ImcEntry> Imc
|
||||
=> _imc;
|
||||
|
||||
public IReadOnlyDictionary<EqpIdentifier, EqpEntryInternal> Eqp
|
||||
=> _eqp;
|
||||
|
||||
public IReadOnlyDictionary<EqdpIdentifier, EqdpEntryInternal> Eqdp
|
||||
=> _eqdp;
|
||||
|
||||
public IReadOnlyDictionary<EstIdentifier, EstEntry> Est
|
||||
=> _est;
|
||||
|
||||
public IReadOnlyDictionary<GmpIdentifier, GmpEntry> Gmp
|
||||
=> _gmp;
|
||||
|
||||
public IReadOnlyDictionary<RspIdentifier, RspEntry> Rsp
|
||||
=> _rsp;
|
||||
|
||||
public IReadOnlySet<GlobalEqpManipulation> GlobalEqp
|
||||
=> _globalEqp;
|
||||
|
||||
public int Count { get; private set; }
|
||||
|
||||
public int GetCount(MetaManipulation.Type type)
|
||||
public int GetCount(MetaManipulationType type)
|
||||
=> type switch
|
||||
{
|
||||
MetaManipulation.Type.Imc => _imc.Count,
|
||||
MetaManipulation.Type.Eqdp => _eqdp.Count,
|
||||
MetaManipulation.Type.Eqp => _eqp.Count,
|
||||
MetaManipulation.Type.Est => _est.Count,
|
||||
MetaManipulation.Type.Gmp => _gmp.Count,
|
||||
MetaManipulation.Type.Rsp => _rsp.Count,
|
||||
MetaManipulation.Type.GlobalEqp => _globalEqp.Count,
|
||||
_ => 0,
|
||||
MetaManipulationType.Imc => _imc.Count,
|
||||
MetaManipulationType.Eqdp => _eqdp.Count,
|
||||
MetaManipulationType.Eqp => _eqp.Count,
|
||||
MetaManipulationType.Est => _est.Count,
|
||||
MetaManipulationType.Gmp => _gmp.Count,
|
||||
MetaManipulationType.Rsp => _rsp.Count,
|
||||
MetaManipulationType.GlobalEqp => _globalEqp.Count,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public bool CanAdd(IMetaIdentifier identifier)
|
||||
public bool Contains(IMetaIdentifier identifier)
|
||||
=> identifier switch
|
||||
{
|
||||
EqdpIdentifier eqdpIdentifier => !_eqdp.ContainsKey(eqdpIdentifier),
|
||||
EqpIdentifier eqpIdentifier => !_eqp.ContainsKey(eqpIdentifier),
|
||||
EstIdentifier estIdentifier => !_est.ContainsKey(estIdentifier),
|
||||
GlobalEqpManipulation globalEqpManipulation => !_globalEqp.Contains(globalEqpManipulation),
|
||||
GmpIdentifier gmpIdentifier => !_gmp.ContainsKey(gmpIdentifier),
|
||||
ImcIdentifier imcIdentifier => !_imc.ContainsKey(imcIdentifier),
|
||||
RspIdentifier rspIdentifier => !_rsp.ContainsKey(rspIdentifier),
|
||||
_ => false,
|
||||
EqdpIdentifier i => _eqdp.ContainsKey(i),
|
||||
EqpIdentifier i => _eqp.ContainsKey(i),
|
||||
EstIdentifier i => _est.ContainsKey(i),
|
||||
GlobalEqpManipulation i => _globalEqp.Contains(i),
|
||||
GmpIdentifier i => _gmp.ContainsKey(i),
|
||||
ImcIdentifier i => _imc.ContainsKey(i),
|
||||
RspIdentifier i => _rsp.ContainsKey(i),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public void Clear()
|
||||
|
|
@ -69,17 +88,16 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
&& _gmp.SetEquals(other._gmp)
|
||||
&& _globalEqp.SetEquals(other._globalEqp);
|
||||
|
||||
public IEnumerator<MetaManipulation> GetEnumerator()
|
||||
=> _imc.Select(kvp => new MetaManipulation(new ImcManipulation(kvp.Key, kvp.Value)))
|
||||
.Concat(_eqp.Select(kvp => new MetaManipulation(new EqpManipulation(kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)))))
|
||||
.Concat(_eqdp.Select(kvp => new MetaManipulation(new EqdpManipulation(kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)))))
|
||||
.Concat(_est.Select(kvp => new MetaManipulation(new EstManipulation(kvp.Key, kvp.Value))))
|
||||
.Concat(_rsp.Select(kvp => new MetaManipulation(new RspManipulation(kvp.Key, kvp.Value))))
|
||||
.Concat(_gmp.Select(kvp => new MetaManipulation(new GmpManipulation(kvp.Key, kvp.Value))))
|
||||
.Concat(_globalEqp.Select(manip => new MetaManipulation(manip))).GetEnumerator();
|
||||
public IEnumerable<IMetaIdentifier> Identifiers
|
||||
=> _imc.Keys.Cast<IMetaIdentifier>()
|
||||
.Concat(_eqdp.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_eqp.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_est.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_gmp.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_rsp.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_globalEqp.Cast<IMetaIdentifier>());
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
#region TryAdd
|
||||
|
||||
public bool TryAdd(ImcIdentifier identifier, ImcEntry entry)
|
||||
{
|
||||
|
|
@ -90,7 +108,6 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool TryAdd(EqpIdentifier identifier, EqpEntryInternal entry)
|
||||
{
|
||||
if (!_eqp.TryAdd(identifier, entry))
|
||||
|
|
@ -103,7 +120,6 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
public bool TryAdd(EqpIdentifier identifier, EqpEntry entry)
|
||||
=> TryAdd(identifier, new EqpEntryInternal(entry, identifier.Slot));
|
||||
|
||||
|
||||
public bool TryAdd(EqdpIdentifier identifier, EqdpEntryInternal entry)
|
||||
{
|
||||
if (!_eqdp.TryAdd(identifier, entry))
|
||||
|
|
@ -152,6 +168,10 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update
|
||||
|
||||
public bool Update(ImcIdentifier identifier, ImcEntry entry)
|
||||
{
|
||||
if (!_imc.ContainsKey(identifier))
|
||||
|
|
@ -161,7 +181,6 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool Update(EqpIdentifier identifier, EqpEntryInternal entry)
|
||||
{
|
||||
if (!_eqp.ContainsKey(identifier))
|
||||
|
|
@ -174,7 +193,6 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
public bool Update(EqpIdentifier identifier, EqpEntry entry)
|
||||
=> Update(identifier, new EqpEntryInternal(entry, identifier.Slot));
|
||||
|
||||
|
||||
public bool Update(EqdpIdentifier identifier, EqdpEntryInternal entry)
|
||||
{
|
||||
if (!_eqdp.ContainsKey(identifier))
|
||||
|
|
@ -214,6 +232,50 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TryGetValue
|
||||
|
||||
public bool TryGetValue(EstIdentifier identifier, out EstEntry value)
|
||||
=> _est.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(EqpIdentifier identifier, out EqpEntryInternal value)
|
||||
=> _eqp.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(EqdpIdentifier identifier, out EqdpEntryInternal value)
|
||||
=> _eqdp.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(GmpIdentifier identifier, out GmpEntry value)
|
||||
=> _gmp.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(RspIdentifier identifier, out RspEntry value)
|
||||
=> _rsp.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(ImcIdentifier identifier, out ImcEntry value)
|
||||
=> _imc.TryGetValue(identifier, out value);
|
||||
|
||||
#endregion
|
||||
|
||||
public bool Remove(IMetaIdentifier identifier)
|
||||
{
|
||||
var ret = identifier switch
|
||||
{
|
||||
EqdpIdentifier i => _eqdp.Remove(i),
|
||||
EqpIdentifier i => _eqp.Remove(i),
|
||||
EstIdentifier i => _est.Remove(i),
|
||||
GlobalEqpManipulation i => _globalEqp.Remove(i),
|
||||
GmpIdentifier i => _gmp.Remove(i),
|
||||
ImcIdentifier i => _imc.Remove(i),
|
||||
RspIdentifier i => _rsp.Remove(i),
|
||||
_ => false,
|
||||
};
|
||||
if (ret)
|
||||
--Count;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#region Merging
|
||||
|
||||
public void UnionWith(MetaDictionary manips)
|
||||
{
|
||||
foreach (var (identifier, entry) in manips._imc)
|
||||
|
|
@ -287,24 +349,6 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetValue(EstIdentifier identifier, out EstEntry value)
|
||||
=> _est.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(EqpIdentifier identifier, out EqpEntryInternal value)
|
||||
=> _eqp.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(EqdpIdentifier identifier, out EqdpEntryInternal value)
|
||||
=> _eqdp.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(GmpIdentifier identifier, out GmpEntry value)
|
||||
=> _gmp.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(RspIdentifier identifier, out RspEntry value)
|
||||
=> _rsp.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(ImcIdentifier identifier, out ImcEntry value)
|
||||
=> _imc.TryGetValue(identifier, out value);
|
||||
|
||||
public void SetTo(MetaDictionary other)
|
||||
{
|
||||
_imc.SetTo(other._imc);
|
||||
|
|
@ -329,6 +373,8 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _globalEqp.Count;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public MetaDictionary Clone()
|
||||
{
|
||||
var ret = new MetaDictionary();
|
||||
|
|
@ -336,29 +382,124 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
return ret;
|
||||
}
|
||||
|
||||
private static void WriteJson(JsonWriter writer, JsonSerializer serializer, IMetaIdentifier identifier, object entry)
|
||||
{
|
||||
var type = identifier switch
|
||||
public static JObject Serialize(EqpIdentifier identifier, EqpEntryInternal entry)
|
||||
=> Serialize(identifier, entry.ToEntry(identifier.Slot));
|
||||
|
||||
public static JObject Serialize(EqpIdentifier identifier, EqpEntry entry)
|
||||
=> new()
|
||||
{
|
||||
ImcIdentifier => "Imc",
|
||||
EqdpIdentifier => "Eqdp",
|
||||
EqpIdentifier => "Eqp",
|
||||
EstIdentifier => "Est",
|
||||
GmpIdentifier => "Gmp",
|
||||
RspIdentifier => "Rsp",
|
||||
GlobalEqpManipulation => "GlobalEqp",
|
||||
_ => string.Empty,
|
||||
["Type"] = MetaManipulationType.Eqp.ToString(),
|
||||
["Manipulation"] = identifier.AddToJson(new JObject
|
||||
{
|
||||
["Entry"] = (ulong)entry,
|
||||
}),
|
||||
};
|
||||
|
||||
if (type.Length == 0)
|
||||
return;
|
||||
public static JObject Serialize(EqdpIdentifier identifier, EqdpEntryInternal entry)
|
||||
=> Serialize(identifier, entry.ToEntry(identifier.Slot));
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("Type");
|
||||
writer.WriteValue(type);
|
||||
writer.WritePropertyName("Manipulation");
|
||||
public static JObject Serialize(EqdpIdentifier identifier, EqdpEntry entry)
|
||||
=> new()
|
||||
{
|
||||
["Type"] = MetaManipulationType.Eqdp.ToString(),
|
||||
["Manipulation"] = identifier.AddToJson(new JObject
|
||||
{
|
||||
["Entry"] = (ushort)entry,
|
||||
}),
|
||||
};
|
||||
|
||||
writer.WriteEndObject();
|
||||
public static JObject Serialize(EstIdentifier identifier, EstEntry entry)
|
||||
=> new()
|
||||
{
|
||||
["Type"] = MetaManipulationType.Est.ToString(),
|
||||
["Manipulation"] = identifier.AddToJson(new JObject
|
||||
{
|
||||
["Entry"] = entry.Value,
|
||||
}),
|
||||
};
|
||||
|
||||
public static JObject Serialize(GmpIdentifier identifier, GmpEntry entry)
|
||||
=> new()
|
||||
{
|
||||
["Type"] = MetaManipulationType.Gmp.ToString(),
|
||||
["Manipulation"] = identifier.AddToJson(new JObject
|
||||
{
|
||||
["Entry"] = JObject.FromObject(entry),
|
||||
}),
|
||||
};
|
||||
|
||||
public static JObject Serialize(ImcIdentifier identifier, ImcEntry entry)
|
||||
=> new()
|
||||
{
|
||||
["Type"] = MetaManipulationType.Imc.ToString(),
|
||||
["Manipulation"] = identifier.AddToJson(new JObject
|
||||
{
|
||||
["Entry"] = JObject.FromObject(entry),
|
||||
}),
|
||||
};
|
||||
|
||||
public static JObject Serialize(RspIdentifier identifier, RspEntry entry)
|
||||
=> new()
|
||||
{
|
||||
["Type"] = MetaManipulationType.Rsp.ToString(),
|
||||
["Manipulation"] = identifier.AddToJson(new JObject
|
||||
{
|
||||
["Entry"] = entry.Value,
|
||||
}),
|
||||
};
|
||||
|
||||
public static JObject Serialize(GlobalEqpManipulation identifier)
|
||||
=> new()
|
||||
{
|
||||
["Type"] = MetaManipulationType.GlobalEqp.ToString(),
|
||||
["Manipulation"] = identifier.AddToJson(new JObject()),
|
||||
};
|
||||
|
||||
public static JObject? Serialize<TIdentifier, TEntry>(TIdentifier identifier, TEntry entry)
|
||||
where TIdentifier : unmanaged, IMetaIdentifier
|
||||
where TEntry : unmanaged
|
||||
{
|
||||
if (typeof(TIdentifier) == typeof(EqpIdentifier) && typeof(TEntry) == typeof(EqpEntryInternal))
|
||||
return Serialize(Unsafe.As<TIdentifier, EqpIdentifier>(ref identifier), Unsafe.As<TEntry, EqpEntryInternal>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(EqpIdentifier) && typeof(TEntry) == typeof(EqpEntry))
|
||||
return Serialize(Unsafe.As<TIdentifier, EqpIdentifier>(ref identifier), Unsafe.As<TEntry, EqpEntry>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(EqdpIdentifier) && typeof(TEntry) == typeof(EqdpEntryInternal))
|
||||
return Serialize(Unsafe.As<TIdentifier, EqdpIdentifier>(ref identifier), Unsafe.As<TEntry, EqdpEntryInternal>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(EqdpIdentifier) && typeof(TEntry) == typeof(EqdpEntry))
|
||||
return Serialize(Unsafe.As<TIdentifier, EqdpIdentifier>(ref identifier), Unsafe.As<TEntry, EqdpEntry>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(EstIdentifier) && typeof(TEntry) == typeof(EstEntry))
|
||||
return Serialize(Unsafe.As<TIdentifier, EstIdentifier>(ref identifier), Unsafe.As<TEntry, EstEntry>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(GmpIdentifier) && typeof(TEntry) == typeof(GmpEntry))
|
||||
return Serialize(Unsafe.As<TIdentifier, GmpIdentifier>(ref identifier), Unsafe.As<TEntry, GmpEntry>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(RspIdentifier) && typeof(TEntry) == typeof(RspEntry))
|
||||
return Serialize(Unsafe.As<TIdentifier, RspIdentifier>(ref identifier), Unsafe.As<TEntry, RspEntry>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(ImcIdentifier) && typeof(TEntry) == typeof(ImcEntry))
|
||||
return Serialize(Unsafe.As<TIdentifier, ImcIdentifier>(ref identifier), Unsafe.As<TEntry, ImcEntry>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(GlobalEqpManipulation))
|
||||
return Serialize(Unsafe.As<TIdentifier, GlobalEqpManipulation>(ref identifier));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static JArray SerializeTo<TIdentifier, TEntry>(JArray array, IEnumerable<KeyValuePair<TIdentifier, TEntry>> manipulations)
|
||||
where TIdentifier : unmanaged, IMetaIdentifier
|
||||
where TEntry : unmanaged
|
||||
{
|
||||
foreach (var (identifier, entry) in manipulations)
|
||||
{
|
||||
if (Serialize(identifier, entry) is { } jObj)
|
||||
array.Add(jObj);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public static JArray SerializeTo(JArray array, IEnumerable<GlobalEqpManipulation> manipulations)
|
||||
{
|
||||
foreach (var manip in manipulations)
|
||||
array.Add(Serialize(manip));
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private class Converter : JsonConverter<MetaDictionary>
|
||||
|
|
@ -371,30 +512,27 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
return;
|
||||
}
|
||||
|
||||
writer.WriteStartArray();
|
||||
foreach (var item in value)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName("Type");
|
||||
writer.WriteValue(item.ManipulationType.ToString());
|
||||
writer.WritePropertyName("Manipulation");
|
||||
serializer.Serialize(writer, item.Manipulation);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
var array = new JArray();
|
||||
SerializeTo(array, value._imc);
|
||||
SerializeTo(array, value._eqp);
|
||||
SerializeTo(array, value._eqdp);
|
||||
SerializeTo(array, value._est);
|
||||
SerializeTo(array, value._rsp);
|
||||
SerializeTo(array, value._gmp);
|
||||
SerializeTo(array, value._globalEqp);
|
||||
array.WriteTo(writer);
|
||||
}
|
||||
|
||||
public override MetaDictionary ReadJson(JsonReader reader, Type objectType, MetaDictionary? existingValue, bool hasExistingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
var dict = existingValue ?? [];
|
||||
var dict = existingValue ?? new MetaDictionary();
|
||||
dict.Clear();
|
||||
var jObj = JArray.Load(reader);
|
||||
foreach (var item in jObj)
|
||||
{
|
||||
var type = item["Type"]?.ToObject<MetaManipulation.Type>() ?? MetaManipulation.Type.Unknown;
|
||||
if (type is MetaManipulation.Type.Unknown)
|
||||
var type = item["Type"]?.ToObject<MetaManipulationType>() ?? MetaManipulationType.Unknown;
|
||||
if (type is MetaManipulationType.Unknown)
|
||||
{
|
||||
Penumbra.Log.Warning($"Invalid Meta Manipulation Type {type} encountered.");
|
||||
continue;
|
||||
|
|
@ -408,7 +546,7 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
|
||||
switch (type)
|
||||
{
|
||||
case MetaManipulation.Type.Imc:
|
||||
case MetaManipulationType.Imc:
|
||||
{
|
||||
var identifier = ImcIdentifier.FromJson(manip);
|
||||
var entry = manip["Entry"]?.ToObject<ImcEntry>();
|
||||
|
|
@ -418,7 +556,7 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
Penumbra.Log.Warning("Invalid IMC Manipulation encountered.");
|
||||
break;
|
||||
}
|
||||
case MetaManipulation.Type.Eqdp:
|
||||
case MetaManipulationType.Eqdp:
|
||||
{
|
||||
var identifier = EqdpIdentifier.FromJson(manip);
|
||||
var entry = (EqdpEntry?)manip["Entry"]?.ToObject<ushort>();
|
||||
|
|
@ -428,7 +566,7 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
Penumbra.Log.Warning("Invalid EQDP Manipulation encountered.");
|
||||
break;
|
||||
}
|
||||
case MetaManipulation.Type.Eqp:
|
||||
case MetaManipulationType.Eqp:
|
||||
{
|
||||
var identifier = EqpIdentifier.FromJson(manip);
|
||||
var entry = (EqpEntry?)manip["Entry"]?.ToObject<ulong>();
|
||||
|
|
@ -438,7 +576,7 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
Penumbra.Log.Warning("Invalid EQP Manipulation encountered.");
|
||||
break;
|
||||
}
|
||||
case MetaManipulation.Type.Est:
|
||||
case MetaManipulationType.Est:
|
||||
{
|
||||
var identifier = EstIdentifier.FromJson(manip);
|
||||
var entry = manip["Entry"]?.ToObject<EstEntry>();
|
||||
|
|
@ -448,7 +586,7 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
Penumbra.Log.Warning("Invalid EST Manipulation encountered.");
|
||||
break;
|
||||
}
|
||||
case MetaManipulation.Type.Gmp:
|
||||
case MetaManipulationType.Gmp:
|
||||
{
|
||||
var identifier = GmpIdentifier.FromJson(manip);
|
||||
var entry = manip["Entry"]?.ToObject<GmpEntry>();
|
||||
|
|
@ -458,7 +596,7 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
Penumbra.Log.Warning("Invalid GMP Manipulation encountered.");
|
||||
break;
|
||||
}
|
||||
case MetaManipulation.Type.Rsp:
|
||||
case MetaManipulationType.Rsp:
|
||||
{
|
||||
var identifier = RspIdentifier.FromJson(manip);
|
||||
var entry = manip["Entry"]?.ToObject<RspEntry>();
|
||||
|
|
@ -468,7 +606,7 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
Penumbra.Log.Warning("Invalid RSP Manipulation encountered.");
|
||||
break;
|
||||
}
|
||||
case MetaManipulation.Type.GlobalEqp:
|
||||
case MetaManipulationType.GlobalEqp:
|
||||
{
|
||||
var identifier = GlobalEqpManipulation.FromJson(manip);
|
||||
if (identifier.HasValue)
|
||||
|
|
@ -483,4 +621,22 @@ public class MetaDictionary : IEnumerable<MetaManipulation>
|
|||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
public MetaDictionary()
|
||||
{ }
|
||||
|
||||
public MetaDictionary(MetaCache? cache)
|
||||
{
|
||||
if (cache == null)
|
||||
return;
|
||||
|
||||
_imc = cache.Imc.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry);
|
||||
_eqp = cache.Eqp.ToDictionary(kvp => kvp.Key, kvp => new EqpEntryInternal(kvp.Value.Entry, kvp.Key.Slot));
|
||||
_eqdp = cache.Eqdp.ToDictionary(kvp => kvp.Key, kvp => new EqdpEntryInternal(kvp.Value.Entry, kvp.Key.Slot));
|
||||
_est = cache.Est.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry);
|
||||
_gmp = cache.Gmp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry);
|
||||
_rsp = cache.Rsp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry);
|
||||
_globalEqp = cache.GlobalEqp.Select(kvp => kvp.Key).ToHashSet();
|
||||
Count = cache.Count;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,457 +0,0 @@
|
|||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.String.Functions;
|
||||
using Penumbra.UI;
|
||||
using Penumbra.UI.ModsTab;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
#if false
|
||||
private static class ImcRow
|
||||
{
|
||||
private static ImcIdentifier _newIdentifier = ImcIdentifier.Default;
|
||||
|
||||
private static float IdWidth
|
||||
=> 80 * UiHelpers.Scale;
|
||||
|
||||
private static float SmallIdWidth
|
||||
=> 45 * UiHelpers.Scale;
|
||||
|
||||
public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current IMC manipulations to clipboard.", iconSize,
|
||||
editor.MetaEditor.Imc.Select(m => (MetaManipulation)m));
|
||||
ImGui.TableNextColumn();
|
||||
var (defaultEntry, fileExists, _) = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true);
|
||||
var manip = (MetaManipulation)new ImcManipulation(_newIdentifier, defaultEntry);
|
||||
var canAdd = fileExists && editor.MetaEditor.CanAdd(manip);
|
||||
var tt = canAdd ? "Stage this edit." : !fileExists ? "This IMC file does not exist." : "This entry is already edited.";
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
|
||||
editor.MetaEditor.Add(manip);
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
var change = ImcManipulationDrawer.DrawObjectType(ref _newIdentifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
change |= ImcManipulationDrawer.DrawPrimaryId(ref _newIdentifier);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
|
||||
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
// Equipment and accessories are slightly different imcs than other types.
|
||||
if (_newIdentifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
|
||||
change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier);
|
||||
else
|
||||
change |= ImcManipulationDrawer.DrawSecondaryId(ref _newIdentifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
change |= ImcManipulationDrawer.DrawVariant(ref _newIdentifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (_newIdentifier.ObjectType is ObjectType.DemiHuman)
|
||||
change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier, 70);
|
||||
else
|
||||
ImUtf8.ScaledDummy(new Vector2(70 * UiHelpers.Scale, 0));
|
||||
|
||||
if (change)
|
||||
defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true).Entry;
|
||||
// Values
|
||||
using var disabled = ImRaii.Disabled();
|
||||
ImGui.TableNextColumn();
|
||||
ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref defaultEntry, false);
|
||||
ImGui.SameLine();
|
||||
ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref defaultEntry, false);
|
||||
ImGui.TableNextColumn();
|
||||
ImcManipulationDrawer.DrawDecalId(defaultEntry, ref defaultEntry, false);
|
||||
ImGui.SameLine();
|
||||
ImcManipulationDrawer.DrawVfxId(defaultEntry, ref defaultEntry, false);
|
||||
ImGui.SameLine();
|
||||
ImcManipulationDrawer.DrawSoundId(defaultEntry, ref defaultEntry, false);
|
||||
ImGui.TableNextColumn();
|
||||
ImcManipulationDrawer.DrawAttributes(defaultEntry, ref defaultEntry);
|
||||
}
|
||||
|
||||
public static void Draw(MetaFileManager metaFileManager, ImcManipulation meta, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
DrawMetaButtons(meta, editor, iconSize);
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.ObjectType.ToName());
|
||||
ImGuiUtil.HoverTooltip(ObjectTypeTooltip);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.PrimaryId.ToString());
|
||||
ImGuiUtil.HoverTooltip(PrimaryIdTooltipShort);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
if (meta.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
|
||||
{
|
||||
ImGui.TextUnformatted(meta.EquipSlot.ToName());
|
||||
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted(meta.SecondaryId.ToString());
|
||||
ImGuiUtil.HoverTooltip(SecondaryIdTooltip);
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.Variant.ToString());
|
||||
ImGuiUtil.HoverTooltip(VariantIdTooltip);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
if (meta.ObjectType is ObjectType.DemiHuman)
|
||||
{
|
||||
ImGui.TextUnformatted(meta.EquipSlot.ToName());
|
||||
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
|
||||
}
|
||||
|
||||
// Values
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
|
||||
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
|
||||
ImGui.TableNextColumn();
|
||||
var defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(meta.Identifier, true).Entry;
|
||||
var newEntry = meta.Entry;
|
||||
var changes = ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref newEntry, true);
|
||||
ImGui.SameLine();
|
||||
changes |= ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref newEntry, true);
|
||||
ImGui.TableNextColumn();
|
||||
changes |= ImcManipulationDrawer.DrawDecalId(defaultEntry, ref newEntry, true);
|
||||
ImGui.SameLine();
|
||||
changes |= ImcManipulationDrawer.DrawVfxId(defaultEntry, ref newEntry, true);
|
||||
ImGui.SameLine();
|
||||
changes |= ImcManipulationDrawer.DrawSoundId(defaultEntry, ref newEntry, true);
|
||||
ImGui.TableNextColumn();
|
||||
changes |= ImcManipulationDrawer.DrawAttributes(defaultEntry, ref newEntry);
|
||||
|
||||
if (changes)
|
||||
editor.MetaEditor.Change(meta.Copy(newEntry));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public interface IMetaManipulation
|
||||
{
|
||||
public MetaIndex FileIndex();
|
||||
}
|
||||
|
||||
public interface IMetaManipulation<T>
|
||||
: IMetaManipulation, IComparable<T>, IEquatable<T> where T : struct
|
||||
{ }
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 16)]
|
||||
public readonly struct MetaManipulation : IEquatable<MetaManipulation>, IComparable<MetaManipulation>
|
||||
{
|
||||
public const int CurrentVersion = 0;
|
||||
|
||||
public enum Type : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
Imc = 1,
|
||||
Eqdp = 2,
|
||||
Eqp = 3,
|
||||
Est = 4,
|
||||
Gmp = 5,
|
||||
Rsp = 6,
|
||||
GlobalEqp = 7,
|
||||
}
|
||||
|
||||
[FieldOffset(0)]
|
||||
[JsonIgnore]
|
||||
public readonly EqpManipulation Eqp = default;
|
||||
|
||||
[FieldOffset(0)]
|
||||
[JsonIgnore]
|
||||
public readonly GmpManipulation Gmp = default;
|
||||
|
||||
[FieldOffset(0)]
|
||||
[JsonIgnore]
|
||||
public readonly EqdpManipulation Eqdp = default;
|
||||
|
||||
[FieldOffset(0)]
|
||||
[JsonIgnore]
|
||||
public readonly EstManipulation Est = default;
|
||||
|
||||
[FieldOffset(0)]
|
||||
[JsonIgnore]
|
||||
public readonly RspManipulation Rsp = default;
|
||||
|
||||
[FieldOffset(0)]
|
||||
[JsonIgnore]
|
||||
public readonly ImcManipulation Imc = default;
|
||||
|
||||
[FieldOffset(0)]
|
||||
[JsonIgnore]
|
||||
public readonly GlobalEqpManipulation GlobalEqp = default;
|
||||
|
||||
[FieldOffset(15)]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
[JsonProperty("Type")]
|
||||
public readonly Type ManipulationType;
|
||||
|
||||
public object? Manipulation
|
||||
{
|
||||
get => ManipulationType switch
|
||||
{
|
||||
Type.Unknown => null,
|
||||
Type.Imc => Imc,
|
||||
Type.Eqdp => Eqdp,
|
||||
Type.Eqp => Eqp,
|
||||
Type.Est => Est,
|
||||
Type.Gmp => Gmp,
|
||||
Type.Rsp => Rsp,
|
||||
Type.GlobalEqp => GlobalEqp,
|
||||
_ => null,
|
||||
};
|
||||
init
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case EqpManipulation m:
|
||||
Eqp = m;
|
||||
ManipulationType = m.Validate() ? Type.Eqp : Type.Unknown;
|
||||
return;
|
||||
case EqdpManipulation m:
|
||||
Eqdp = m;
|
||||
ManipulationType = m.Validate() ? Type.Eqdp : Type.Unknown;
|
||||
return;
|
||||
case GmpManipulation m:
|
||||
Gmp = m;
|
||||
ManipulationType = m.Validate() ? Type.Gmp : Type.Unknown;
|
||||
return;
|
||||
case EstManipulation m:
|
||||
Est = m;
|
||||
ManipulationType = m.Validate() ? Type.Est : Type.Unknown;
|
||||
return;
|
||||
case RspManipulation m:
|
||||
Rsp = m;
|
||||
ManipulationType = m.Validate() ? Type.Rsp : Type.Unknown;
|
||||
return;
|
||||
case ImcManipulation m:
|
||||
Imc = m;
|
||||
ManipulationType = m.Validate(true) ? Type.Imc : Type.Unknown;
|
||||
return;
|
||||
case GlobalEqpManipulation m:
|
||||
GlobalEqp = m;
|
||||
ManipulationType = m.Validate() ? Type.GlobalEqp : Type.Unknown;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Validate()
|
||||
{
|
||||
return ManipulationType switch
|
||||
{
|
||||
Type.Imc => Imc.Validate(true),
|
||||
Type.Eqdp => Eqdp.Validate(),
|
||||
Type.Eqp => Eqp.Validate(),
|
||||
Type.Est => Est.Validate(),
|
||||
Type.Gmp => Gmp.Validate(),
|
||||
Type.Rsp => Rsp.Validate(),
|
||||
Type.GlobalEqp => GlobalEqp.Validate(),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public MetaManipulation(EqpManipulation eqp)
|
||||
{
|
||||
Eqp = eqp;
|
||||
ManipulationType = Type.Eqp;
|
||||
}
|
||||
|
||||
public MetaManipulation(GmpManipulation gmp)
|
||||
{
|
||||
Gmp = gmp;
|
||||
ManipulationType = Type.Gmp;
|
||||
}
|
||||
|
||||
public MetaManipulation(EqdpManipulation eqdp)
|
||||
{
|
||||
Eqdp = eqdp;
|
||||
ManipulationType = Type.Eqdp;
|
||||
}
|
||||
|
||||
public MetaManipulation(EstManipulation est)
|
||||
{
|
||||
Est = est;
|
||||
ManipulationType = Type.Est;
|
||||
}
|
||||
|
||||
public MetaManipulation(RspManipulation rsp)
|
||||
{
|
||||
Rsp = rsp;
|
||||
ManipulationType = Type.Rsp;
|
||||
}
|
||||
|
||||
public MetaManipulation(ImcManipulation imc)
|
||||
{
|
||||
Imc = imc;
|
||||
ManipulationType = Type.Imc;
|
||||
}
|
||||
|
||||
public MetaManipulation(GlobalEqpManipulation eqp)
|
||||
{
|
||||
GlobalEqp = eqp;
|
||||
ManipulationType = Type.GlobalEqp;
|
||||
}
|
||||
|
||||
public static implicit operator MetaManipulation(EqpManipulation eqp)
|
||||
=> new(eqp);
|
||||
|
||||
public static implicit operator MetaManipulation(GmpManipulation gmp)
|
||||
=> new(gmp);
|
||||
|
||||
public static implicit operator MetaManipulation(EqdpManipulation eqdp)
|
||||
=> new(eqdp);
|
||||
|
||||
public static implicit operator MetaManipulation(EstManipulation est)
|
||||
=> new(est);
|
||||
|
||||
public static implicit operator MetaManipulation(RspManipulation rsp)
|
||||
=> new(rsp);
|
||||
|
||||
public static implicit operator MetaManipulation(ImcManipulation imc)
|
||||
=> new(imc);
|
||||
|
||||
public static implicit operator MetaManipulation(GlobalEqpManipulation eqp)
|
||||
=> new(eqp);
|
||||
|
||||
public bool EntryEquals(MetaManipulation other)
|
||||
{
|
||||
if (ManipulationType != other.ManipulationType)
|
||||
return false;
|
||||
|
||||
return ManipulationType switch
|
||||
{
|
||||
Type.Eqp => Eqp.Entry.Equals(other.Eqp.Entry),
|
||||
Type.Gmp => Gmp.Entry.Equals(other.Gmp.Entry),
|
||||
Type.Eqdp => Eqdp.Entry.Equals(other.Eqdp.Entry),
|
||||
Type.Est => Est.Entry.Equals(other.Est.Entry),
|
||||
Type.Rsp => Rsp.Entry.Equals(other.Rsp.Entry),
|
||||
Type.Imc => Imc.Entry.Equals(other.Imc.Entry),
|
||||
Type.GlobalEqp => true,
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
|
||||
public bool Equals(MetaManipulation other)
|
||||
{
|
||||
if (ManipulationType != other.ManipulationType)
|
||||
return false;
|
||||
|
||||
return ManipulationType switch
|
||||
{
|
||||
Type.Eqp => Eqp.Equals(other.Eqp),
|
||||
Type.Gmp => Gmp.Equals(other.Gmp),
|
||||
Type.Eqdp => Eqdp.Equals(other.Eqdp),
|
||||
Type.Est => Est.Equals(other.Est),
|
||||
Type.Rsp => Rsp.Equals(other.Rsp),
|
||||
Type.Imc => Imc.Equals(other.Imc),
|
||||
Type.GlobalEqp => GlobalEqp.Equals(other.GlobalEqp),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public MetaManipulation WithEntryOf(MetaManipulation other)
|
||||
{
|
||||
if (ManipulationType != other.ManipulationType)
|
||||
return this;
|
||||
|
||||
return ManipulationType switch
|
||||
{
|
||||
Type.Eqp => Eqp.Copy(other.Eqp.Entry),
|
||||
Type.Gmp => Gmp.Copy(other.Gmp.Entry),
|
||||
Type.Eqdp => Eqdp.Copy(other.Eqdp),
|
||||
Type.Est => Est.Copy(other.Est.Entry),
|
||||
Type.Rsp => Rsp.Copy(other.Rsp.Entry),
|
||||
Type.Imc => Imc.Copy(other.Imc.Entry),
|
||||
Type.GlobalEqp => GlobalEqp,
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is MetaManipulation other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> ManipulationType switch
|
||||
{
|
||||
Type.Eqp => Eqp.GetHashCode(),
|
||||
Type.Gmp => Gmp.GetHashCode(),
|
||||
Type.Eqdp => Eqdp.GetHashCode(),
|
||||
Type.Est => Est.GetHashCode(),
|
||||
Type.Rsp => Rsp.GetHashCode(),
|
||||
Type.Imc => Imc.GetHashCode(),
|
||||
Type.GlobalEqp => GlobalEqp.GetHashCode(),
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public unsafe int CompareTo(MetaManipulation other)
|
||||
{
|
||||
fixed (MetaManipulation* lhs = &this)
|
||||
{
|
||||
return MemoryUtility.MemCmpUnchecked(lhs, &other, sizeof(MetaManipulation));
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> ManipulationType switch
|
||||
{
|
||||
Type.Eqp => Eqp.ToString(),
|
||||
Type.Gmp => Gmp.ToString(),
|
||||
Type.Eqdp => Eqdp.ToString(),
|
||||
Type.Est => Est.ToString(),
|
||||
Type.Rsp => Rsp.ToString(),
|
||||
Type.Imc => Imc.ToString(),
|
||||
Type.GlobalEqp => GlobalEqp.ToString(),
|
||||
_ => "Invalid",
|
||||
};
|
||||
|
||||
public string EntryToString()
|
||||
=> ManipulationType switch
|
||||
{
|
||||
Type.Imc =>
|
||||
$"{Imc.Entry.DecalId}-{Imc.Entry.MaterialId}-{Imc.Entry.VfxId}-{Imc.Entry.SoundId}-{Imc.Entry.MaterialAnimationId}-{Imc.Entry.AttributeMask}",
|
||||
Type.Eqdp => $"{(ushort)Eqdp.Entry:X}",
|
||||
Type.Eqp => $"{(ulong)Eqp.Entry:X}",
|
||||
Type.Est => $"{Est.Entry}",
|
||||
Type.Gmp => $"{Gmp.Entry.Value}",
|
||||
Type.Rsp => $"{Rsp.Entry}",
|
||||
Type.GlobalEqp => string.Empty,
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
public static bool operator ==(MetaManipulation left, MetaManipulation right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(MetaManipulation left, MetaManipulation right)
|
||||
=> !(left == right);
|
||||
|
||||
public static bool operator <(MetaManipulation left, MetaManipulation right)
|
||||
=> left.CompareTo(right) < 0;
|
||||
|
||||
public static bool operator <=(MetaManipulation left, MetaManipulation right)
|
||||
=> left.CompareTo(right) <= 0;
|
||||
|
||||
public static bool operator >(MetaManipulation left, MetaManipulation right)
|
||||
=> left.CompareTo(right) > 0;
|
||||
|
||||
public static bool operator >=(MetaManipulation left, MetaManipulation right)
|
||||
=> left.CompareTo(right) >= 0;
|
||||
}
|
||||
|
||||
|
|
@ -38,6 +38,9 @@ public readonly record struct RspIdentifier(SubRace SubRace, RspAttribute Attrib
|
|||
var ret = new RspIdentifier(subRace, attribute);
|
||||
return ret.Validate() ? ret : null;
|
||||
}
|
||||
|
||||
public MetaManipulationType Type
|
||||
=> MetaManipulationType.Rsp;
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(Converter))]
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct RspManipulation(RspIdentifier identifier, RspEntry entry) : IMetaManipulation<RspManipulation>
|
||||
{
|
||||
[JsonIgnore]
|
||||
public RspIdentifier Identifier { get; } = identifier;
|
||||
|
||||
public RspEntry Entry { get; } = entry;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public SubRace SubRace
|
||||
=> Identifier.SubRace;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public RspAttribute Attribute
|
||||
=> Identifier.Attribute;
|
||||
|
||||
[JsonConstructor]
|
||||
public RspManipulation(SubRace subRace, RspAttribute attribute, RspEntry entry)
|
||||
: this(new RspIdentifier(subRace, attribute), entry)
|
||||
{ }
|
||||
|
||||
public RspManipulation Copy(RspEntry entry)
|
||||
=> new(Identifier, entry);
|
||||
|
||||
public override string ToString()
|
||||
=> $"Rsp - {SubRace.ToName()} - {Attribute.ToFullString()}";
|
||||
|
||||
public bool Equals(RspManipulation other)
|
||||
=> SubRace == other.SubRace
|
||||
&& Attribute == other.Attribute;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is RspManipulation other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine((int)SubRace, (int)Attribute);
|
||||
|
||||
public int CompareTo(RspManipulation other)
|
||||
{
|
||||
var s = SubRace.CompareTo(other.SubRace);
|
||||
return s != 0 ? s : Attribute.CompareTo(other.Attribute);
|
||||
}
|
||||
|
||||
public MetaIndex FileIndex()
|
||||
=> MetaIndex.HumanCmp;
|
||||
|
||||
public bool Apply(CmpFile file)
|
||||
{
|
||||
var value = file[SubRace, Attribute];
|
||||
if (value == Entry)
|
||||
return false;
|
||||
|
||||
file[SubRace, Attribute] = Entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Validate()
|
||||
=> Identifier.Validate() && Entry.Validate();
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ public record struct AppliedModData(
|
|||
Dictionary<Utf8GamePath, FullPath> FileRedirections,
|
||||
MetaDictionary Manipulations)
|
||||
{
|
||||
public static readonly AppliedModData Empty = new([], []);
|
||||
public static readonly AppliedModData Empty = new([], new MetaDictionary());
|
||||
}
|
||||
|
||||
public interface IMod
|
||||
|
|
|
|||
|
|
@ -26,20 +26,20 @@ public class ModMetaEditor(ModManager modManager) : MetaDictionary, IService
|
|||
}
|
||||
}
|
||||
|
||||
public readonly FrozenDictionary<MetaManipulation.Type, OtherOptionData> OtherData =
|
||||
Enum.GetValues<MetaManipulation.Type>().ToFrozenDictionary(t => t, _ => new OtherOptionData());
|
||||
public readonly FrozenDictionary<MetaManipulationType, OtherOptionData> OtherData =
|
||||
Enum.GetValues<MetaManipulationType>().ToFrozenDictionary(t => t, _ => new OtherOptionData());
|
||||
|
||||
public bool Changes { get; private set; }
|
||||
public bool Changes { get; set; }
|
||||
|
||||
public new void Clear()
|
||||
{
|
||||
Changes = Count > 0;
|
||||
base.Clear();
|
||||
Changes = true;
|
||||
}
|
||||
|
||||
public void Load(Mod mod, IModDataContainer currentOption)
|
||||
{
|
||||
foreach (var type in Enum.GetValues<MetaManipulation.Type>())
|
||||
foreach (var type in Enum.GetValues<MetaManipulationType>())
|
||||
OtherData[type].Clear();
|
||||
|
||||
foreach (var option in mod.AllDataContainers)
|
||||
|
|
@ -48,13 +48,13 @@ public class ModMetaEditor(ModManager modManager) : MetaDictionary, IService
|
|||
continue;
|
||||
|
||||
var name = option.GetFullName();
|
||||
OtherData[MetaManipulation.Type.Imc].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Imc));
|
||||
OtherData[MetaManipulation.Type.Eqp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Eqp));
|
||||
OtherData[MetaManipulation.Type.Eqdp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Eqdp));
|
||||
OtherData[MetaManipulation.Type.Gmp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Gmp));
|
||||
OtherData[MetaManipulation.Type.Est].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Est));
|
||||
OtherData[MetaManipulation.Type.Rsp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.Rsp));
|
||||
OtherData[MetaManipulation.Type.GlobalEqp].Add(name, option.Manipulations.GetCount(MetaManipulation.Type.GlobalEqp));
|
||||
OtherData[MetaManipulationType.Imc].Add(name, option.Manipulations.GetCount(MetaManipulationType.Imc));
|
||||
OtherData[MetaManipulationType.Eqp].Add(name, option.Manipulations.GetCount(MetaManipulationType.Eqp));
|
||||
OtherData[MetaManipulationType.Eqdp].Add(name, option.Manipulations.GetCount(MetaManipulationType.Eqdp));
|
||||
OtherData[MetaManipulationType.Gmp].Add(name, option.Manipulations.GetCount(MetaManipulationType.Gmp));
|
||||
OtherData[MetaManipulationType.Est].Add(name, option.Manipulations.GetCount(MetaManipulationType.Est));
|
||||
OtherData[MetaManipulationType.Rsp].Add(name, option.Manipulations.GetCount(MetaManipulationType.Rsp));
|
||||
OtherData[MetaManipulationType.GlobalEqp].Add(name, option.Manipulations.GetCount(MetaManipulationType.GlobalEqp));
|
||||
}
|
||||
|
||||
Clear();
|
||||
|
|
|
|||
|
|
@ -125,8 +125,8 @@ public static class EquipmentSwap
|
|||
_ => (EstType)0,
|
||||
};
|
||||
|
||||
var skipFemale = false;
|
||||
var skipMale = false;
|
||||
var skipFemale = false;
|
||||
var skipMale = false;
|
||||
foreach (var gr in Enum.GetValues<GenderRace>())
|
||||
{
|
||||
switch (gr.Split().Item1)
|
||||
|
|
@ -242,8 +242,8 @@ public static class EquipmentSwap
|
|||
private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, ObjectIdentification identifier, EquipSlot slotFrom,
|
||||
PrimaryId idFrom, PrimaryId idTo, Variant variantFrom)
|
||||
{
|
||||
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
|
||||
var imc = new ImcFile(manager, entry.Identifier);
|
||||
var ident = new ImcIdentifier(slotFrom, idFrom, variantFrom);
|
||||
var imc = new ImcFile(manager, ident);
|
||||
EquipItem[] items;
|
||||
Variant[] variants;
|
||||
if (idFrom == idTo)
|
||||
|
|
@ -273,7 +273,8 @@ public static class EquipmentSwap
|
|||
var manipToIdentifier = new GmpIdentifier(idTo);
|
||||
var manipFromDefault = ExpandedGmpFile.GetDefault(manager, manipFromIdentifier);
|
||||
var manipToDefault = ExpandedGmpFile.GetDefault(manager, manipToIdentifier);
|
||||
return new MetaSwap<GmpIdentifier, GmpEntry>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault);
|
||||
return new MetaSwap<GmpIdentifier, GmpEntry>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault,
|
||||
manipToIdentifier, manipToDefault);
|
||||
}
|
||||
|
||||
public static MetaSwap<ImcIdentifier, ImcEntry> CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||
|
|
@ -286,16 +287,17 @@ public static class EquipmentSwap
|
|||
Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
|
||||
{
|
||||
var manipFromIdentifier = new ImcIdentifier(slotFrom, idFrom, variantFrom);
|
||||
var manipToIdentifier = new ImcIdentifier(slotTo, idTo, variantTo);
|
||||
var manipFromDefault = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom);
|
||||
var manipToDefault = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo);
|
||||
var imc = new MetaSwap<ImcIdentifier, ImcEntry>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault);
|
||||
var manipToIdentifier = new ImcIdentifier(slotTo, idTo, variantTo);
|
||||
var manipFromDefault = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom);
|
||||
var manipToDefault = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo);
|
||||
var imc = new MetaSwap<ImcIdentifier, ImcEntry>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault,
|
||||
manipToIdentifier, manipToDefault);
|
||||
|
||||
var decal = CreateDecal(manager, redirections, imc.SwapToModdedEntry.DecalId);
|
||||
if (decal != null)
|
||||
imc.ChildSwaps.Add(decal);
|
||||
|
||||
var avfx = CreateAvfx(manager, redirections, idFrom, idTo, imc.SwapToModdedEntry.VfxId);
|
||||
var avfx = CreateAvfx(manager, redirections, slotFrom, slotTo, idFrom, idTo, imc.SwapToModdedEntry.VfxId);
|
||||
if (avfx != null)
|
||||
imc.ChildSwaps.Add(avfx);
|
||||
|
||||
|
|
@ -316,19 +318,21 @@ public static class EquipmentSwap
|
|||
|
||||
|
||||
// Example: Abyssos Helm / Body
|
||||
public static FileSwap? CreateAvfx(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, PrimaryId idFrom, PrimaryId idTo,
|
||||
public static FileSwap? CreateAvfx(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slotFrom, EquipSlot slotTo,
|
||||
PrimaryId idFrom, PrimaryId idTo,
|
||||
byte vfxId)
|
||||
{
|
||||
if (vfxId == 0)
|
||||
return null;
|
||||
|
||||
var vfxPathFrom = GamePaths.Equipment.Avfx.Path(idFrom, vfxId);
|
||||
var vfxPathTo = GamePaths.Equipment.Avfx.Path(idTo, vfxId);
|
||||
var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo);
|
||||
vfxPathFrom = ItemSwap.ReplaceType(vfxPathFrom, slotFrom, slotTo, idFrom);
|
||||
var vfxPathTo = GamePaths.Equipment.Avfx.Path(idTo, vfxId);
|
||||
var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo);
|
||||
|
||||
foreach (ref var filePath in avfx.AsAvfx()!.Textures.AsSpan())
|
||||
{
|
||||
var atex = CreateAtex(manager, redirections, ref filePath, ref avfx.DataWasChanged);
|
||||
var atex = CreateAtex(manager, redirections, slotFrom, slotTo, idFrom, ref filePath, ref avfx.DataWasChanged);
|
||||
avfx.ChildSwaps.Add(atex);
|
||||
}
|
||||
|
||||
|
|
@ -394,8 +398,7 @@ public static class EquipmentSwap
|
|||
}
|
||||
|
||||
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, PrimaryId idFrom,
|
||||
PrimaryId idTo,
|
||||
ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
||||
PrimaryId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
||||
=> CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged);
|
||||
|
||||
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, EquipSlot slotFrom,
|
||||
|
|
@ -404,6 +407,7 @@ public static class EquipmentSwap
|
|||
var addedDashes = GamePaths.Tex.HandleDx11Path(texture, out var path);
|
||||
var newPath = ItemSwap.ReplaceAnyId(path, prefix, idFrom);
|
||||
newPath = ItemSwap.ReplaceSlot(newPath, slotTo, slotFrom, slotTo != slotFrom);
|
||||
newPath = ItemSwap.ReplaceType(newPath, slotFrom, slotTo, idFrom);
|
||||
newPath = ItemSwap.AddSuffix(newPath, ".tex", $"_{Path.GetFileName(texture.Path).GetStableHashCode():x8}");
|
||||
if (newPath != path)
|
||||
{
|
||||
|
|
@ -421,11 +425,12 @@ public static class EquipmentSwap
|
|||
return FileSwap.CreateSwap(manager, ResourceType.Shpk, redirections, path, path);
|
||||
}
|
||||
|
||||
public static FileSwap CreateAtex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string filePath,
|
||||
ref bool dataWasChanged)
|
||||
public static FileSwap CreateAtex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slotFrom, EquipSlot slotTo,
|
||||
PrimaryId idFrom, ref string filePath, ref bool dataWasChanged)
|
||||
{
|
||||
var oldPath = filePath;
|
||||
filePath = ItemSwap.AddSuffix(filePath, ".atex", $"_{Path.GetFileName(filePath).GetStableHashCode():x8}");
|
||||
filePath = ItemSwap.ReplaceType(filePath, slotFrom, slotTo, idFrom);
|
||||
dataWasChanged = true;
|
||||
|
||||
return FileSwap.CreateSwap(manager, ResourceType.Atex, redirections, filePath, oldPath, oldPath);
|
||||
|
|
|
|||
|
|
@ -136,14 +136,14 @@ public static class ItemSwap
|
|||
public static FileSwap CreatePhyb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstType type,
|
||||
GenderRace race, EstEntry estEntry)
|
||||
{
|
||||
var phybPath = GamePaths.Skeleton.Phyb.Path(race, EstManipulation.ToName(type), estEntry.AsId);
|
||||
var phybPath = GamePaths.Skeleton.Phyb.Path(race, type.ToName(), estEntry.AsId);
|
||||
return FileSwap.CreateSwap(manager, ResourceType.Phyb, redirections, phybPath, phybPath);
|
||||
}
|
||||
|
||||
public static FileSwap CreateSklb(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EstType type,
|
||||
GenderRace race, EstEntry estEntry)
|
||||
{
|
||||
var sklbPath = GamePaths.Skeleton.Sklb.Path(race, EstManipulation.ToName(type), estEntry.AsId);
|
||||
var sklbPath = GamePaths.Skeleton.Sklb.Path(race, type.ToName(), estEntry.AsId);
|
||||
return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath);
|
||||
}
|
||||
|
||||
|
|
@ -154,10 +154,11 @@ public static class ItemSwap
|
|||
return null;
|
||||
|
||||
var manipFromIdentifier = new EstIdentifier(idFrom, type, genderRace);
|
||||
var manipToIdentifier = new EstIdentifier(idTo, type, genderRace);
|
||||
var manipFromDefault = EstFile.GetDefault(manager, manipFromIdentifier);
|
||||
var manipToDefault = EstFile.GetDefault(manager, manipToIdentifier);
|
||||
var est = new MetaSwap<EstIdentifier, EstEntry>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault);
|
||||
var manipToIdentifier = new EstIdentifier(idTo, type, genderRace);
|
||||
var manipFromDefault = EstFile.GetDefault(manager, manipFromIdentifier);
|
||||
var manipToDefault = EstFile.GetDefault(manager, manipToIdentifier);
|
||||
var est = new MetaSwap<EstIdentifier, EstEntry>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault,
|
||||
manipToIdentifier, manipToDefault);
|
||||
|
||||
if (ownMdl && est.SwapToModdedEntry.Value >= 2)
|
||||
{
|
||||
|
|
@ -215,6 +216,22 @@ public static class ItemSwap
|
|||
? path.Replace($"_{from.ToSuffix()}_", $"_{to.ToSuffix()}_")
|
||||
: path;
|
||||
|
||||
public static string ReplaceType(string path, EquipSlot from, EquipSlot to, PrimaryId idFrom)
|
||||
{
|
||||
var isAccessoryFrom = from.IsAccessory();
|
||||
if (isAccessoryFrom == to.IsAccessory())
|
||||
return path;
|
||||
|
||||
if (isAccessoryFrom)
|
||||
{
|
||||
path = path.Replace("accessory/a", "equipment/e");
|
||||
return path.Replace($"a{idFrom.Id:D4}", $"e{idFrom.Id:D4}");
|
||||
}
|
||||
|
||||
path = path.Replace("equipment/e", "accessory/a");
|
||||
return path.Replace($"e{idFrom.Id:D4}", $"a{idFrom.Id:D4}");
|
||||
}
|
||||
|
||||
public static string ReplaceRace(string path, GenderRace from, GenderRace to, bool condition = true)
|
||||
=> ReplaceId(path, 'c', (ushort)from, (ushort)to, condition);
|
||||
|
||||
|
|
|
|||
|
|
@ -123,8 +123,8 @@ public class ItemSwapContainer
|
|||
: p => ModRedirections.TryGetValue(p, out var path) ? path : new FullPath(p);
|
||||
|
||||
private MetaDictionary MetaResolver(ModCollection? collection)
|
||||
=> collection?.MetaCache?.Manipulations is { } cache
|
||||
? [] // [.. cache] TODO
|
||||
=> collection?.MetaCache is { } cache
|
||||
? new MetaDictionary(cache)
|
||||
: _appliedModData.Manipulations;
|
||||
|
||||
public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true,
|
||||
|
|
|
|||
|
|
@ -198,8 +198,7 @@ public partial class ModCreator(
|
|||
Penumbra.Log.Verbose(
|
||||
$"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}");
|
||||
deleteList.Add(file.FullName);
|
||||
// TODO
|
||||
option.Manipulations.UnionWith([]);//[.. meta.MetaManipulations]);
|
||||
option.Manipulations.UnionWith(meta.MetaManipulations);
|
||||
}
|
||||
else if (ext1 == ".rgsp" || ext2 == ".rgsp")
|
||||
{
|
||||
|
|
@ -213,8 +212,7 @@ public partial class ModCreator(
|
|||
$"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}");
|
||||
deleteList.Add(file.FullName);
|
||||
|
||||
// TODO
|
||||
option.Manipulations.UnionWith([]);//[.. rgsp.MetaManipulations]);
|
||||
option.Manipulations.UnionWith(rgsp.MetaManipulations);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public class DefaultSubMod(IMod mod) : IModDataContainer
|
|||
|
||||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
|
||||
public MetaDictionary Manipulations { get; set; } = [];
|
||||
public MetaDictionary Manipulations { get; set; } = new();
|
||||
|
||||
IMod IModDataContainer.Mod
|
||||
=> Mod;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public abstract class OptionSubMod(IModGroup group) : IModOption, IModDataContai
|
|||
|
||||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
|
||||
public MetaDictionary Manipulations { get; set; } = [];
|
||||
public MetaDictionary Manipulations { get; set; } = new();
|
||||
|
||||
public void AddDataTo(Dictionary<Utf8GamePath, FullPath> redirections, MetaDictionary manipulations)
|
||||
=> SubMod.AddContainerTo(this, redirections, manipulations);
|
||||
|
|
|
|||
|
|
@ -93,8 +93,7 @@ public class TemporaryMod : IMod
|
|||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
MetaDictionary manips = []; // [.. collection.MetaCache?.Manipulations ?? []];
|
||||
var manips = new MetaDictionary(collection.MetaCache);
|
||||
defaultMod.Manipulations.UnionWith(manips);
|
||||
|
||||
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport));
|
||||
|
|
|
|||
159
Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs
Normal file
159
Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow.Meta;
|
||||
|
||||
public sealed class EqdpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles)
|
||||
: MetaDrawer<EqdpIdentifier, EqdpEntryInternal>(editor, metaFiles), IService
|
||||
{
|
||||
public override ReadOnlySpan<byte> Label
|
||||
=> "Racial Model Edits (EQDP)###EQDP"u8;
|
||||
|
||||
public override int NumColumns
|
||||
=> 7;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new EqdpIdentifier(1, EquipSlot.Head, GenderRace.MidlanderMale);
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(MetaFiles, Identifier), Identifier.Slot);
|
||||
|
||||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current EQDP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Eqdp));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var validRaceCode = CharacterUtilityData.EqdpIdx(Identifier.GenderRace, false) >= 0;
|
||||
var canAdd = validRaceCode && !Editor.Contains(Identifier);
|
||||
var tt = canAdd ? "Stage this edit."u8 :
|
||||
validRaceCode ? "This entry is already edited."u8 : "This combination of race and gender can not be used."u8;
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd))
|
||||
Editor.Changes |= Editor.TryAdd(Identifier, Entry);
|
||||
|
||||
if (DrawIdentifierInput(ref Identifier))
|
||||
UpdateEntry();
|
||||
|
||||
DrawEntry(Entry, ref Entry, true);
|
||||
}
|
||||
|
||||
protected override void DrawEntry(EqdpIdentifier identifier, EqdpEntryInternal entry)
|
||||
{
|
||||
DrawMetaButtons(identifier, entry);
|
||||
DrawIdentifier(identifier);
|
||||
|
||||
var defaultEntry = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(MetaFiles, identifier), identifier.Slot);
|
||||
if (DrawEntry(defaultEntry, ref entry, false))
|
||||
Editor.Changes |= Editor.Update(identifier, entry);
|
||||
}
|
||||
|
||||
protected override IEnumerable<(EqdpIdentifier, EqdpEntryInternal)> Enumerate()
|
||||
=> Editor.Eqdp.Select(kvp => (kvp.Key, kvp.Value));
|
||||
|
||||
private static bool DrawIdentifierInput(ref EqdpIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
var changes = DrawPrimaryId(ref identifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
changes |= DrawRace(ref identifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
changes |= DrawGender(ref identifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
changes |= DrawEquipSlot(ref identifier);
|
||||
return changes;
|
||||
}
|
||||
|
||||
private static void DrawIdentifier(EqdpIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed($"{identifier.SetId.Id}", FrameColor);
|
||||
ImUtf8.HoverTooltip("Model Set ID"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.Gender.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Model Race"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.Race.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Gender"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.Slot.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Equip Slot"u8);
|
||||
}
|
||||
|
||||
private static bool DrawEntry(EqdpEntryInternal defaultEntry, ref EqdpEntryInternal entry, bool disabled)
|
||||
{
|
||||
var changes = false;
|
||||
using var dis = ImRaii.Disabled(disabled);
|
||||
ImGui.TableNextColumn();
|
||||
if (Checkmark("Material##eqdp"u8, "\0"u8, entry.Material, defaultEntry.Material, out var newMaterial))
|
||||
{
|
||||
entry = entry with { Material = newMaterial };
|
||||
changes = true;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (Checkmark("Model##eqdp"u8, "\0"u8, entry.Model, defaultEntry.Model, out var newModel))
|
||||
{
|
||||
entry = entry with { Material = newModel };
|
||||
changes = true;
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
public static bool DrawPrimaryId(ref EqdpIdentifier identifier, float unscaledWidth = 100)
|
||||
{
|
||||
var ret = IdInput("##eqdpPrimaryId"u8, unscaledWidth, identifier.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1,
|
||||
identifier.SetId.Id <= 1);
|
||||
ImUtf8.HoverTooltip(
|
||||
"Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that."u8);
|
||||
if (ret)
|
||||
identifier = identifier with { SetId = setId };
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool DrawRace(ref EqdpIdentifier identifier, float unscaledWidth = 100)
|
||||
{
|
||||
var ret = Combos.Race("##eqdpRace", identifier.Race, out var race, unscaledWidth);
|
||||
ImUtf8.HoverTooltip("Model Race"u8);
|
||||
if (ret)
|
||||
identifier = identifier with { GenderRace = Names.CombinedRace(identifier.Gender, race) };
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool DrawGender(ref EqdpIdentifier identifier, float unscaledWidth = 120)
|
||||
{
|
||||
var ret = Combos.Gender("##eqdpGender", identifier.Gender, out var gender, unscaledWidth);
|
||||
ImUtf8.HoverTooltip("Gender"u8);
|
||||
if (ret)
|
||||
identifier = identifier with { GenderRace = Names.CombinedRace(gender, identifier.Race) };
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool DrawEquipSlot(ref EqdpIdentifier identifier, float unscaledWidth = 100)
|
||||
{
|
||||
var ret = Combos.EqdpEquipSlot("##eqdpSlot", identifier.Slot, out var slot, unscaledWidth);
|
||||
ImUtf8.HoverTooltip("Equip Slot"u8);
|
||||
if (ret)
|
||||
identifier = identifier with { Slot = slot };
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
134
Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs
Normal file
134
Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow.Meta;
|
||||
|
||||
public sealed class EqpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles)
|
||||
: MetaDrawer<EqpIdentifier, EqpEntryInternal>(editor, metaFiles), IService
|
||||
{
|
||||
public override ReadOnlySpan<byte> Label
|
||||
=> "Equipment Parameter Edits (EQP)###EQP"u8;
|
||||
|
||||
public override int NumColumns
|
||||
=> 5;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new EqpIdentifier(1, EquipSlot.Body);
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = new EqpEntryInternal(ExpandedEqpFile.GetDefault(MetaFiles, Identifier.SetId), Identifier.Slot);
|
||||
|
||||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current EQP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Eqp));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = !Editor.Contains(Identifier);
|
||||
var tt = canAdd ? "Stage this edit."u8 : "This entry is already edited."u8;
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd))
|
||||
Editor.Changes |= Editor.TryAdd(Identifier, Entry);
|
||||
|
||||
if (DrawIdentifierInput(ref Identifier))
|
||||
UpdateEntry();
|
||||
|
||||
DrawEntry(Identifier.Slot, Entry, ref Entry, true);
|
||||
}
|
||||
|
||||
protected override void DrawEntry(EqpIdentifier identifier, EqpEntryInternal entry)
|
||||
{
|
||||
DrawMetaButtons(identifier, entry);
|
||||
DrawIdentifier(identifier);
|
||||
|
||||
var defaultEntry = new EqpEntryInternal(ExpandedEqpFile.GetDefault(MetaFiles, identifier.SetId), identifier.Slot);
|
||||
if (DrawEntry(identifier.Slot, defaultEntry, ref entry, false))
|
||||
Editor.Changes |= Editor.Update(identifier, entry);
|
||||
}
|
||||
|
||||
protected override IEnumerable<(EqpIdentifier, EqpEntryInternal)> Enumerate()
|
||||
=> Editor.Eqp.Select(kvp => (kvp.Key, kvp.Value));
|
||||
|
||||
private static bool DrawIdentifierInput(ref EqpIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
var changes = DrawPrimaryId(ref identifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
changes |= DrawEquipSlot(ref identifier);
|
||||
return changes;
|
||||
}
|
||||
|
||||
private static void DrawIdentifier(EqpIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed($"{identifier.SetId.Id}", FrameColor);
|
||||
ImUtf8.HoverTooltip("Model Set ID"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.Slot.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Equip Slot"u8);
|
||||
}
|
||||
|
||||
private static bool DrawEntry(EquipSlot slot, EqpEntryInternal defaultEntry, ref EqpEntryInternal entry, bool disabled)
|
||||
{
|
||||
var changes = false;
|
||||
using var dis = ImRaii.Disabled(disabled);
|
||||
ImGui.TableNextColumn();
|
||||
var offset = Eqp.OffsetAndMask(slot).Item1;
|
||||
DrawBox(ref entry, 0);
|
||||
for (var i = 1; i < Eqp.EqpAttributes[slot].Count; ++i)
|
||||
{
|
||||
ImUtf8.SameLineInner();
|
||||
DrawBox(ref entry, i);
|
||||
}
|
||||
|
||||
return changes;
|
||||
|
||||
void DrawBox(ref EqpEntryInternal entry, int i)
|
||||
{
|
||||
using var id = ImUtf8.PushId(i);
|
||||
var flag = 1u << i;
|
||||
var eqpFlag = (EqpEntry)((ulong)flag << offset);
|
||||
var defaultValue = (flag & defaultEntry.Value) != 0;
|
||||
var value = (flag & entry.Value) != 0;
|
||||
if (Checkmark("##eqp"u8, eqpFlag.ToLocalName(), value, defaultValue, out var newValue))
|
||||
{
|
||||
entry = new EqpEntryInternal(newValue ? entry.Value | flag : entry.Value & ~flag);
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DrawPrimaryId(ref EqpIdentifier identifier, float unscaledWidth = 100)
|
||||
{
|
||||
var ret = IdInput("##eqpPrimaryId"u8, unscaledWidth, identifier.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1,
|
||||
identifier.SetId.Id <= 1);
|
||||
ImUtf8.HoverTooltip(
|
||||
"Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that."u8);
|
||||
if (ret)
|
||||
identifier = identifier with { SetId = setId };
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool DrawEquipSlot(ref EqpIdentifier identifier, float unscaledWidth = 100)
|
||||
{
|
||||
var ret = Combos.EqpEquipSlot("##eqpSlot", identifier.Slot, out var slot, unscaledWidth);
|
||||
ImUtf8.HoverTooltip("Equip Slot"u8);
|
||||
if (ret)
|
||||
identifier = identifier with { Slot = slot };
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
147
Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs
Normal file
147
Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow.Meta;
|
||||
|
||||
public sealed class EstMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles)
|
||||
: MetaDrawer<EstIdentifier, EstEntry>(editor, metaFiles), IService
|
||||
{
|
||||
public override ReadOnlySpan<byte> Label
|
||||
=> "Extra Skeleton Parameters (EST)###EST"u8;
|
||||
|
||||
public override int NumColumns
|
||||
=> 7;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new EstIdentifier(1, EstType.Hair, GenderRace.MidlanderMale);
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = EstFile.GetDefault(MetaFiles, Identifier.Slot, Identifier.GenderRace, Identifier.SetId);
|
||||
|
||||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current EST manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Est));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = !Editor.Contains(Identifier);
|
||||
var tt = canAdd ? "Stage this edit."u8 : "This entry is already edited."u8;
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd))
|
||||
Editor.Changes |= Editor.TryAdd(Identifier, Entry);
|
||||
|
||||
if (DrawIdentifierInput(ref Identifier))
|
||||
UpdateEntry();
|
||||
|
||||
DrawEntry(Entry, ref Entry, true);
|
||||
}
|
||||
|
||||
protected override void DrawEntry(EstIdentifier identifier, EstEntry entry)
|
||||
{
|
||||
DrawMetaButtons(identifier, entry);
|
||||
DrawIdentifier(identifier);
|
||||
|
||||
var defaultEntry = EstFile.GetDefault(MetaFiles, identifier.Slot, identifier.GenderRace, identifier.SetId);
|
||||
if (DrawEntry(defaultEntry, ref entry, false))
|
||||
Editor.Changes |= Editor.Update(identifier, entry);
|
||||
}
|
||||
|
||||
protected override IEnumerable<(EstIdentifier, EstEntry)> Enumerate()
|
||||
=> Editor.Est.Select(kvp => (kvp.Key, kvp.Value));
|
||||
|
||||
private static bool DrawIdentifierInput(ref EstIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
var changes = DrawPrimaryId(ref identifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
changes |= DrawRace(ref identifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
changes |= DrawGender(ref identifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
changes |= DrawSlot(ref identifier);
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private static void DrawIdentifier(EstIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed($"{identifier.SetId.Id}", FrameColor);
|
||||
ImUtf8.HoverTooltip("Model Set ID"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.Race.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Model Race"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.Gender.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Gender"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.Slot.ToString(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Extra Skeleton Type"u8);
|
||||
}
|
||||
|
||||
private static bool DrawEntry(EstEntry defaultEntry, ref EstEntry entry, bool disabled)
|
||||
{
|
||||
using var dis = ImRaii.Disabled(disabled);
|
||||
ImGui.TableNextColumn();
|
||||
var ret = DragInput("##estValue"u8, [], 100f * ImUtf8.GlobalScale, entry.Value, defaultEntry.Value, out var newValue, (ushort)0,
|
||||
ushort.MaxValue, 0.05f, !disabled);
|
||||
if (ret)
|
||||
entry = new EstEntry(newValue);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool DrawPrimaryId(ref EstIdentifier identifier, float unscaledWidth = 100)
|
||||
{
|
||||
var ret = IdInput("##estPrimaryId"u8, unscaledWidth, identifier.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1,
|
||||
identifier.SetId.Id <= 1);
|
||||
ImUtf8.HoverTooltip(
|
||||
"Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that."u8);
|
||||
if (ret)
|
||||
identifier = identifier with { SetId = setId };
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool DrawRace(ref EstIdentifier identifier, float unscaledWidth = 100)
|
||||
{
|
||||
var ret = Combos.Race("##estRace", identifier.Race, out var race, unscaledWidth);
|
||||
ImUtf8.HoverTooltip("Model Race"u8);
|
||||
if (ret)
|
||||
identifier = identifier with { GenderRace = Names.CombinedRace(identifier.Gender, race) };
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool DrawGender(ref EstIdentifier identifier, float unscaledWidth = 120)
|
||||
{
|
||||
var ret = Combos.Gender("##estGender", identifier.Gender, out var gender, unscaledWidth);
|
||||
ImUtf8.HoverTooltip("Gender"u8);
|
||||
if (ret)
|
||||
identifier = identifier with { GenderRace = Names.CombinedRace(gender, identifier.Race) };
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool DrawSlot(ref EstIdentifier identifier, float unscaledWidth = 200)
|
||||
{
|
||||
var ret = Combos.EstSlot("##estSlot", identifier.Slot, out var slot, unscaledWidth);
|
||||
ImUtf8.HoverTooltip("Extra Skeleton Type"u8);
|
||||
if (ret)
|
||||
identifier = identifier with { Slot = slot };
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
111
Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs
Normal file
111
Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow.Meta;
|
||||
|
||||
public sealed class GlobalEqpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles)
|
||||
: MetaDrawer<GlobalEqpManipulation, byte>(editor, metaFiles), IService
|
||||
{
|
||||
public override ReadOnlySpan<byte> Label
|
||||
=> "Global Equipment Parameter Edits (Global EQP)###GEQP"u8;
|
||||
|
||||
public override int NumColumns
|
||||
=> 4;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new GlobalEqpManipulation()
|
||||
{
|
||||
Condition = 1,
|
||||
Type = GlobalEqpType.DoNotHideEarrings,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current global EQP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.GlobalEqp));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = !Editor.Contains(Identifier);
|
||||
var tt = canAdd ? "Stage this edit."u8 : "This entry is already edited."u8;
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd))
|
||||
Editor.Changes |= Editor.TryAdd(Identifier);
|
||||
|
||||
DrawIdentifierInput(ref Identifier);
|
||||
}
|
||||
|
||||
protected override void DrawEntry(GlobalEqpManipulation identifier, byte _)
|
||||
{
|
||||
DrawMetaButtons(identifier, 0);
|
||||
DrawIdentifier(identifier);
|
||||
}
|
||||
|
||||
protected override IEnumerable<(GlobalEqpManipulation, byte)> Enumerate()
|
||||
=> Editor.GlobalEqp.Select(identifier => (identifier, (byte)0));
|
||||
|
||||
private static void DrawIdentifierInput(ref GlobalEqpManipulation identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
DrawType(ref identifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (identifier.Type.HasCondition())
|
||||
DrawCondition(ref identifier);
|
||||
else
|
||||
ImUtf8.ScaledDummy(100);
|
||||
}
|
||||
|
||||
private static void DrawIdentifier(GlobalEqpManipulation identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.Type.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Global EQP Type"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (identifier.Type.HasCondition())
|
||||
{
|
||||
ImUtf8.TextFramed($"{identifier.Condition.Id}", FrameColor);
|
||||
ImUtf8.HoverTooltip("Conditional Model ID"u8);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DrawType(ref GlobalEqpManipulation identifier, float unscaledWidth = 250)
|
||||
{
|
||||
ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale);
|
||||
using var combo = ImUtf8.Combo("##geqpType"u8, identifier.Type.ToName());
|
||||
if (!combo)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
foreach (var type in Enum.GetValues<GlobalEqpType>())
|
||||
{
|
||||
if (ImUtf8.Selectable(type.ToName(), type == identifier.Type))
|
||||
{
|
||||
identifier = new GlobalEqpManipulation
|
||||
{
|
||||
Type = type,
|
||||
Condition = type.HasCondition() ? identifier.Type.HasCondition() ? identifier.Condition : 1 : 0,
|
||||
};
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImUtf8.HoverTooltip(type.ToDescription());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void DrawCondition(ref GlobalEqpManipulation identifier, float unscaledWidth = 100)
|
||||
{
|
||||
if (IdInput("##geqpCond"u8, unscaledWidth, identifier.Condition.Id, out var newId, 1, ushort.MaxValue,
|
||||
identifier.Condition.Id <= 1))
|
||||
identifier = identifier with { Condition = newId };
|
||||
ImUtf8.HoverTooltip("The Model ID for the item that should not be hidden."u8);
|
||||
}
|
||||
}
|
||||
148
Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs
Normal file
148
Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow.Meta;
|
||||
|
||||
public sealed class GmpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles)
|
||||
: MetaDrawer<GmpIdentifier, GmpEntry>(editor, metaFiles), IService
|
||||
{
|
||||
public override ReadOnlySpan<byte> Label
|
||||
=> "Visor/Gimmick Edits (GMP)###GMP"u8;
|
||||
|
||||
public override int NumColumns
|
||||
=> 7;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new GmpIdentifier(1);
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = ExpandedGmpFile.GetDefault(MetaFiles, Identifier.SetId);
|
||||
|
||||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current Gmp manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Gmp));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = !Editor.Contains(Identifier);
|
||||
var tt = canAdd ? "Stage this edit."u8 : "This entry is already edited."u8;
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd))
|
||||
Editor.Changes |= Editor.TryAdd(Identifier, Entry);
|
||||
|
||||
if (DrawIdentifierInput(ref Identifier))
|
||||
UpdateEntry();
|
||||
|
||||
DrawEntry(Entry, ref Entry, true);
|
||||
}
|
||||
|
||||
protected override void DrawEntry(GmpIdentifier identifier, GmpEntry entry)
|
||||
{
|
||||
DrawMetaButtons(identifier, entry);
|
||||
DrawIdentifier(identifier);
|
||||
|
||||
var defaultEntry = ExpandedGmpFile.GetDefault(MetaFiles, identifier.SetId);
|
||||
if (DrawEntry(defaultEntry, ref entry, false))
|
||||
Editor.Changes |= Editor.Update(identifier, entry);
|
||||
}
|
||||
|
||||
protected override IEnumerable<(GmpIdentifier, GmpEntry)> Enumerate()
|
||||
=> Editor.Gmp.Select(kvp => (kvp.Key, kvp.Value));
|
||||
|
||||
private static bool DrawIdentifierInput(ref GmpIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
return DrawPrimaryId(ref identifier);
|
||||
}
|
||||
|
||||
private static void DrawIdentifier(GmpIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed($"{identifier.SetId.Id}", FrameColor);
|
||||
ImUtf8.HoverTooltip("Model Set ID"u8);
|
||||
}
|
||||
|
||||
private static bool DrawEntry(GmpEntry defaultEntry, ref GmpEntry entry, bool disabled)
|
||||
{
|
||||
using var dis = ImRaii.Disabled(disabled);
|
||||
ImGui.TableNextColumn();
|
||||
var changes = false;
|
||||
if (Checkmark("##gmpEnabled"u8, "Gimmick Enabled", entry.Enabled, defaultEntry.Enabled, out var enabled))
|
||||
{
|
||||
entry = entry with { Enabled = enabled };
|
||||
changes = true;
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (Checkmark("##gmpAnimated"u8, "Gimmick Animated", entry.Animated, defaultEntry.Animated, out var animated))
|
||||
{
|
||||
entry = entry with { Animated = animated };
|
||||
changes = true;
|
||||
}
|
||||
|
||||
var rotationWidth = 75 * ImUtf8.GlobalScale;
|
||||
ImGui.TableNextColumn();
|
||||
if (DragInput("##gmpRotationA"u8, "Rotation A in Degrees"u8, rotationWidth, entry.RotationA, defaultEntry.RotationA, out var rotationA,
|
||||
(ushort)0, (ushort)360, 0.05f, !disabled))
|
||||
{
|
||||
entry = entry with { RotationA = rotationA };
|
||||
changes = true;
|
||||
}
|
||||
|
||||
ImUtf8.SameLineInner();
|
||||
if (DragInput("##gmpRotationB"u8, "Rotation B in Degrees"u8, rotationWidth, entry.RotationB, defaultEntry.RotationB, out var rotationB,
|
||||
(ushort)0, (ushort)360, 0.05f, !disabled))
|
||||
{
|
||||
entry = entry with { RotationB = rotationB };
|
||||
changes = true;
|
||||
}
|
||||
|
||||
ImUtf8.SameLineInner();
|
||||
if (DragInput("##gmpRotationC"u8, "Rotation C in Degrees"u8, rotationWidth, entry.RotationC, defaultEntry.RotationC, out var rotationC,
|
||||
(ushort)0, (ushort)360, 0.05f, !disabled))
|
||||
{
|
||||
entry = entry with { RotationC = rotationC };
|
||||
changes = true;
|
||||
}
|
||||
|
||||
var unkWidth = 50 * ImUtf8.GlobalScale;
|
||||
ImGui.TableNextColumn();
|
||||
if (DragInput("##gmpUnkA"u8, "Animation Type A?"u8, unkWidth, entry.UnknownA, defaultEntry.UnknownA, out var unknownA,
|
||||
(byte)0, (byte)15, 0.01f, !disabled))
|
||||
{
|
||||
entry = entry with { UnknownA = unknownA };
|
||||
changes = true;
|
||||
}
|
||||
|
||||
ImUtf8.SameLineInner();
|
||||
if (DragInput("##gmpUnkB"u8, "Animation Type B?"u8, unkWidth, entry.UnknownB, defaultEntry.UnknownB, out var unknownB,
|
||||
(byte)0, (byte)15, 0.01f, !disabled))
|
||||
{
|
||||
entry = entry with { UnknownB = unknownB };
|
||||
changes = true;
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
public static bool DrawPrimaryId(ref GmpIdentifier identifier, float unscaledWidth = 100)
|
||||
{
|
||||
var ret = IdInput("##gmpPrimaryId"u8, unscaledWidth, identifier.SetId.Id, out var setId, 1, ExpandedEqpGmpBase.Count - 1,
|
||||
identifier.SetId.Id <= 1);
|
||||
ImUtf8.HoverTooltip(
|
||||
"Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that."u8);
|
||||
if (ret)
|
||||
identifier = new GmpIdentifier(setId);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,22 +12,16 @@ using Penumbra.UI.Classes;
|
|||
|
||||
namespace Penumbra.UI.AdvancedWindow.Meta;
|
||||
|
||||
public sealed class ImcMetaDrawer(ModEditor editor, MetaFileManager metaFiles)
|
||||
public sealed class ImcMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles)
|
||||
: MetaDrawer<ImcIdentifier, ImcEntry>(editor, metaFiles), IService
|
||||
{
|
||||
private bool _fileExists;
|
||||
public override ReadOnlySpan<byte> Label
|
||||
=> "Variant Edits (IMC)###IMC"u8;
|
||||
|
||||
private const string ModelSetIdTooltipShort = "Model Set ID";
|
||||
private const string EquipSlotTooltip = "Equip Slot";
|
||||
private const string ModelRaceTooltip = "Model Race";
|
||||
private const string GenderTooltip = "Gender";
|
||||
private const string ObjectTypeTooltip = "Object Type";
|
||||
private const string SecondaryIdTooltip = "Secondary ID";
|
||||
private const string PrimaryIdTooltipShort = "Primary ID";
|
||||
private const string VariantIdTooltip = "Variant ID";
|
||||
private const string EstTypeTooltip = "EST Type";
|
||||
private const string RacialTribeTooltip = "Racial Tribe";
|
||||
private const string ScalingTypeTooltip = "Scaling Type";
|
||||
public override int NumColumns
|
||||
=> 10;
|
||||
|
||||
private bool _fileExists;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
|
|
@ -41,14 +35,14 @@ public sealed class ImcMetaDrawer(ModEditor editor, MetaFileManager metaFiles)
|
|||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
// Copy To Clipboard
|
||||
CopyToClipboardButton("Copy all current IMC manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Imc));
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = _fileExists && Editor.MetaEditor.CanAdd(Identifier);
|
||||
var canAdd = _fileExists && !Editor.Contains(Identifier);
|
||||
var tt = canAdd ? "Stage this edit."u8 : !_fileExists ? "This IMC file does not exist."u8 : "This entry is already edited."u8;
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd))
|
||||
Editor.MetaEditor.TryAdd(Identifier, Entry);
|
||||
Editor.Changes |= Editor.TryAdd(Identifier, Entry);
|
||||
|
||||
if (DrawIdentifier(ref Identifier))
|
||||
if (DrawIdentifierInput(ref Identifier))
|
||||
UpdateEntry();
|
||||
|
||||
using var disabled = ImRaii.Disabled();
|
||||
|
|
@ -57,46 +51,15 @@ public sealed class ImcMetaDrawer(ModEditor editor, MetaFileManager metaFiles)
|
|||
|
||||
protected override void DrawEntry(ImcIdentifier identifier, ImcEntry entry)
|
||||
{
|
||||
const uint frameColor = 0;
|
||||
// Meta Buttons
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.ObjectType.ToName(), frameColor);
|
||||
ImUtf8.HoverTooltip("Object Type"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed($"{identifier.PrimaryId.Id}", frameColor);
|
||||
ImUtf8.HoverTooltip("Primary ID");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (identifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
|
||||
{
|
||||
ImUtf8.TextFramed(identifier.EquipSlot.ToName(), frameColor);
|
||||
ImUtf8.HoverTooltip("Equip Slot"u8);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImUtf8.TextFramed($"{identifier.SecondaryId.Id}", frameColor);
|
||||
ImUtf8.HoverTooltip("Secondary ID"u8);
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed($"{identifier.Variant.Id}", frameColor);
|
||||
ImUtf8.HoverTooltip("Variant"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (identifier.ObjectType is ObjectType.DemiHuman)
|
||||
{
|
||||
ImUtf8.TextFramed(identifier.EquipSlot.ToName(), frameColor);
|
||||
ImUtf8.HoverTooltip("Equip Slot"u8);
|
||||
}
|
||||
DrawMetaButtons(identifier, entry);
|
||||
DrawIdentifier(identifier);
|
||||
|
||||
var defaultEntry = MetaFiles.ImcChecker.GetDefaultEntry(identifier, true).Entry;
|
||||
if (DrawEntry(defaultEntry, ref entry, true))
|
||||
Editor.MetaEditor.Update(identifier, entry);
|
||||
Editor.Changes |= Editor.Update(identifier, entry);
|
||||
}
|
||||
|
||||
private static bool DrawIdentifier(ref ImcIdentifier identifier)
|
||||
private static bool DrawIdentifierInput(ref ImcIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
var change = DrawObjectType(ref identifier);
|
||||
|
|
@ -121,6 +84,41 @@ public sealed class ImcMetaDrawer(ModEditor editor, MetaFileManager metaFiles)
|
|||
return change;
|
||||
}
|
||||
|
||||
private static void DrawIdentifier(ImcIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.ObjectType.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Object Type"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed($"{identifier.PrimaryId.Id}", FrameColor);
|
||||
ImUtf8.HoverTooltip("Primary ID");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (identifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
|
||||
{
|
||||
ImUtf8.TextFramed(identifier.EquipSlot.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Equip Slot"u8);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImUtf8.TextFramed($"{identifier.SecondaryId.Id}", FrameColor);
|
||||
ImUtf8.HoverTooltip("Secondary ID"u8);
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed($"{identifier.Variant.Id}", FrameColor);
|
||||
ImUtf8.HoverTooltip("Variant"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (identifier.ObjectType is ObjectType.DemiHuman)
|
||||
{
|
||||
ImUtf8.TextFramed(identifier.EquipSlot.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Equip Slot"u8);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static bool DrawEntry(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -142,7 +140,7 @@ public sealed class ImcMetaDrawer(ModEditor editor, MetaFileManager metaFiles)
|
|||
|
||||
|
||||
protected override IEnumerable<(ImcIdentifier, ImcEntry)> Enumerate()
|
||||
=> Editor.MetaEditor.Imc.Select(kvp => (kvp.Key, kvp.Value));
|
||||
=> Editor.Imc.Select(kvp => (kvp.Key, kvp.Value));
|
||||
|
||||
public static bool DrawObjectType(ref ImcIdentifier identifier, float width = 110)
|
||||
{
|
||||
|
|
@ -270,7 +268,7 @@ public sealed class ImcMetaDrawer(ModEditor editor, MetaFileManager metaFiles)
|
|||
return true;
|
||||
}
|
||||
|
||||
public static bool DrawAttributes(ImcEntry defaultEntry, ref ImcEntry entry)
|
||||
private static bool DrawAttributes(ImcEntry defaultEntry, ref ImcEntry entry)
|
||||
{
|
||||
var changes = false;
|
||||
for (var i = 0; i < ImcEntry.NumAttributes; ++i)
|
||||
|
|
@ -292,62 +290,4 @@ public sealed class ImcMetaDrawer(ModEditor editor, MetaFileManager metaFiles)
|
|||
|
||||
return changes;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A number input for ids with an optional max id of given width.
|
||||
/// Returns true if newId changed against currentId.
|
||||
/// </summary>
|
||||
private static bool IdInput(ReadOnlySpan<byte> label, float unscaledWidth, ushort currentId, out ushort newId, int minId, int maxId,
|
||||
bool border)
|
||||
{
|
||||
int tmp = currentId;
|
||||
ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, UiHelpers.Scale, border);
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.RegexWarningBorder, border);
|
||||
if (ImUtf8.InputScalar(label, ref tmp))
|
||||
tmp = Math.Clamp(tmp, minId, maxId);
|
||||
|
||||
newId = (ushort)tmp;
|
||||
return newId != currentId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A dragging int input of given width that compares against a default value, shows a tooltip and clamps against min and max.
|
||||
/// Returns true if newValue changed against currentValue.
|
||||
/// </summary>
|
||||
private static bool DragInput<T>(ReadOnlySpan<byte> label, ReadOnlySpan<byte> tooltip, float width, T currentValue, T defaultValue,
|
||||
out T newValue, T minValue, T maxValue, float speed, bool addDefault) where T : unmanaged, INumber<T>
|
||||
{
|
||||
newValue = currentValue;
|
||||
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
||||
defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
|
||||
defaultValue != currentValue);
|
||||
ImGui.SetNextItemWidth(width);
|
||||
if (ImUtf8.DragScalar(label, ref newValue, minValue, maxValue, speed))
|
||||
newValue = newValue <= minValue ? minValue : newValue >= maxValue ? maxValue : newValue;
|
||||
|
||||
if (addDefault)
|
||||
ImUtf8.HoverTooltip($"{tooltip}\nDefault Value: {defaultValue}");
|
||||
else
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip);
|
||||
|
||||
return newValue != currentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A checkmark that compares against a default value and shows a tooltip.
|
||||
/// Returns true if newValue is changed against currentValue.
|
||||
/// </summary>
|
||||
private static bool Checkmark(ReadOnlySpan<byte> label, ReadOnlySpan<byte> tooltip, bool currentValue, bool defaultValue,
|
||||
out bool newValue)
|
||||
{
|
||||
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
||||
defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
|
||||
defaultValue != currentValue);
|
||||
newValue = currentValue;
|
||||
ImUtf8.Checkbox(label, ref newValue);
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip);
|
||||
return newValue != currentValue;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
154
Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs
Normal file
154
Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow.Meta;
|
||||
|
||||
public interface IMetaDrawer
|
||||
{
|
||||
public ReadOnlySpan<byte> Label { get; }
|
||||
public int NumColumns { get; }
|
||||
public void Draw();
|
||||
}
|
||||
|
||||
public abstract class MetaDrawer<TIdentifier, TEntry>(ModMetaEditor editor, MetaFileManager metaFiles) : IMetaDrawer
|
||||
where TIdentifier : unmanaged, IMetaIdentifier
|
||||
where TEntry : unmanaged
|
||||
{
|
||||
protected const uint FrameColor = 0;
|
||||
|
||||
protected readonly ModMetaEditor Editor = editor;
|
||||
protected readonly MetaFileManager MetaFiles = metaFiles;
|
||||
protected TIdentifier Identifier;
|
||||
protected TEntry Entry;
|
||||
private bool _initialized;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
Initialize();
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
using var id = ImUtf8.PushId((int)Identifier.Type);
|
||||
DrawNew();
|
||||
foreach (var ((identifier, entry), idx) in Enumerate().WithIndex())
|
||||
{
|
||||
id.Push(idx);
|
||||
DrawEntry(identifier, entry);
|
||||
id.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract ReadOnlySpan<byte> Label { get; }
|
||||
public abstract int NumColumns { get; }
|
||||
|
||||
protected abstract void DrawNew();
|
||||
protected abstract void Initialize();
|
||||
protected abstract void DrawEntry(TIdentifier identifier, TEntry entry);
|
||||
|
||||
protected abstract IEnumerable<(TIdentifier, TEntry)> Enumerate();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A number input for ids with an optional max id of given width.
|
||||
/// Returns true if newId changed against currentId.
|
||||
/// </summary>
|
||||
protected static bool IdInput(ReadOnlySpan<byte> label, float unscaledWidth, ushort currentId, out ushort newId, int minId, int maxId,
|
||||
bool border)
|
||||
{
|
||||
int tmp = currentId;
|
||||
ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, UiHelpers.Scale, border);
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.RegexWarningBorder, border);
|
||||
if (ImUtf8.InputScalar(label, ref tmp))
|
||||
tmp = Math.Clamp(tmp, minId, maxId);
|
||||
|
||||
newId = (ushort)tmp;
|
||||
return newId != currentId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A dragging int input of given width that compares against a default value, shows a tooltip and clamps against min and max.
|
||||
/// Returns true if newValue changed against currentValue.
|
||||
/// </summary>
|
||||
protected static bool DragInput<T>(ReadOnlySpan<byte> label, ReadOnlySpan<byte> tooltip, float width, T currentValue, T defaultValue,
|
||||
out T newValue, T minValue, T maxValue, float speed, bool addDefault) where T : unmanaged, INumber<T>
|
||||
{
|
||||
newValue = currentValue;
|
||||
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
||||
defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
|
||||
defaultValue != currentValue);
|
||||
ImGui.SetNextItemWidth(width);
|
||||
if (ImUtf8.DragScalar(label, ref newValue, minValue, maxValue, speed))
|
||||
newValue = newValue <= minValue ? minValue : newValue >= maxValue ? maxValue : newValue;
|
||||
|
||||
if (addDefault)
|
||||
ImUtf8.HoverTooltip($"{tooltip}\nDefault Value: {defaultValue}");
|
||||
else
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip);
|
||||
|
||||
return newValue != currentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A checkmark that compares against a default value and shows a tooltip.
|
||||
/// Returns true if newValue is changed against currentValue.
|
||||
/// </summary>
|
||||
protected static bool Checkmark(ReadOnlySpan<byte> label, ReadOnlySpan<byte> tooltip, bool currentValue, bool defaultValue,
|
||||
out bool newValue)
|
||||
{
|
||||
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
||||
defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
|
||||
defaultValue != currentValue);
|
||||
newValue = currentValue;
|
||||
ImUtf8.Checkbox(label, ref newValue);
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip);
|
||||
return newValue != currentValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A checkmark that compares against a default value and shows a tooltip.
|
||||
/// Returns true if newValue is changed against currentValue.
|
||||
/// </summary>
|
||||
protected static bool Checkmark(ReadOnlySpan<byte> label, ReadOnlySpan<char> tooltip, bool currentValue, bool defaultValue,
|
||||
out bool newValue)
|
||||
{
|
||||
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
||||
defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
|
||||
defaultValue != currentValue);
|
||||
newValue = currentValue;
|
||||
ImUtf8.Checkbox(label, ref newValue);
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, tooltip);
|
||||
return newValue != currentValue;
|
||||
}
|
||||
|
||||
protected void DrawMetaButtons(TIdentifier identifier, TEntry entry)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy this manipulation to clipboard."u8, new JArray { MetaDictionary.Serialize(identifier, entry)! });
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Trash, "Delete this meta manipulation."u8))
|
||||
Editor.Changes |= Editor.Remove(identifier);
|
||||
}
|
||||
|
||||
protected void CopyToClipboardButton(ReadOnlySpan<byte> tooltip, JToken? manipulations)
|
||||
{
|
||||
if (!ImUtf8.IconButton(FontAwesomeIcon.Clipboard, tooltip))
|
||||
return;
|
||||
|
||||
var text = Functions.ToCompressedBase64(manipulations, MetaApi.CurrentVersion);
|
||||
if (text.Length > 0)
|
||||
ImGui.SetClipboardText(text);
|
||||
}
|
||||
}
|
||||
35
Penumbra/UI/AdvancedWindow/Meta/MetaDrawers.cs
Normal file
35
Penumbra/UI/AdvancedWindow/Meta/MetaDrawers.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using OtterGui.Services;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow.Meta;
|
||||
|
||||
public class MetaDrawers(
|
||||
EqdpMetaDrawer eqdp,
|
||||
EqpMetaDrawer eqp,
|
||||
EstMetaDrawer est,
|
||||
GlobalEqpMetaDrawer globalEqp,
|
||||
GmpMetaDrawer gmp,
|
||||
ImcMetaDrawer imc,
|
||||
RspMetaDrawer rsp) : IService
|
||||
{
|
||||
public readonly EqdpMetaDrawer Eqdp = eqdp;
|
||||
public readonly EqpMetaDrawer Eqp = eqp;
|
||||
public readonly EstMetaDrawer Est = est;
|
||||
public readonly GmpMetaDrawer Gmp = gmp;
|
||||
public readonly RspMetaDrawer Rsp = rsp;
|
||||
public readonly ImcMetaDrawer Imc = imc;
|
||||
public readonly GlobalEqpMetaDrawer GlobalEqp = globalEqp;
|
||||
|
||||
public IMetaDrawer? Get(MetaManipulationType type)
|
||||
=> type switch
|
||||
{
|
||||
MetaManipulationType.Imc => Imc,
|
||||
MetaManipulationType.Eqdp => Eqdp,
|
||||
MetaManipulationType.Eqp => Eqp,
|
||||
MetaManipulationType.Est => Est,
|
||||
MetaManipulationType.Gmp => Gmp,
|
||||
MetaManipulationType.Rsp => Rsp,
|
||||
MetaManipulationType.GlobalEqp => GlobalEqp,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
112
Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs
Normal file
112
Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow.Meta;
|
||||
|
||||
public sealed class RspMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles)
|
||||
: MetaDrawer<RspIdentifier, RspEntry>(editor, metaFiles), IService
|
||||
{
|
||||
public override ReadOnlySpan<byte> Label
|
||||
=> "Racial Scaling Edits (RSP)###RSP"u8;
|
||||
|
||||
public override int NumColumns
|
||||
=> 5;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new RspIdentifier(SubRace.Midlander, RspAttribute.MaleMinSize);
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = CmpFile.GetDefault(MetaFiles, Identifier.SubRace, Identifier.Attribute);
|
||||
|
||||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current RSP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Rsp));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = !Editor.Contains(Identifier);
|
||||
var tt = canAdd ? "Stage this edit."u8 : "This entry is already edited."u8;
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd))
|
||||
Editor.Changes |= Editor.TryAdd(Identifier, Entry);
|
||||
|
||||
if (DrawIdentifierInput(ref Identifier))
|
||||
UpdateEntry();
|
||||
|
||||
DrawEntry(Entry, ref Entry, true);
|
||||
}
|
||||
|
||||
protected override void DrawEntry(RspIdentifier identifier, RspEntry entry)
|
||||
{
|
||||
DrawMetaButtons(identifier, entry);
|
||||
DrawIdentifier(identifier);
|
||||
|
||||
var defaultEntry = CmpFile.GetDefault(MetaFiles, identifier.SubRace, identifier.Attribute);
|
||||
if (DrawEntry(defaultEntry, ref entry, false))
|
||||
Editor.Changes |= Editor.Update(identifier, entry);
|
||||
}
|
||||
|
||||
protected override IEnumerable<(RspIdentifier, RspEntry)> Enumerate()
|
||||
=> Editor.Rsp.Select(kvp => (kvp.Key, kvp.Value));
|
||||
|
||||
private static bool DrawIdentifierInput(ref RspIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
var changes = DrawSubRace(ref identifier);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
changes |= DrawAttribute(ref identifier);
|
||||
return changes;
|
||||
}
|
||||
|
||||
private static void DrawIdentifier(RspIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.SubRace.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Model Set ID"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.Attribute.ToFullString(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Equip Slot"u8);
|
||||
}
|
||||
|
||||
private static bool DrawEntry(RspEntry defaultEntry, ref RspEntry entry, bool disabled)
|
||||
{
|
||||
using var dis = ImRaii.Disabled(disabled);
|
||||
ImGui.TableNextColumn();
|
||||
var ret = DragInput("##rspValue"u8, [], ImUtf8.GlobalScale * 150, defaultEntry.Value, entry.Value, out var newValue,
|
||||
RspEntry.MinValue, RspEntry.MaxValue, 0.001f, !disabled);
|
||||
if (ret)
|
||||
entry = new RspEntry(newValue);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool DrawSubRace(ref RspIdentifier identifier, float unscaledWidth = 150)
|
||||
{
|
||||
var ret = Combos.SubRace("##rspSubRace", identifier.SubRace, out var subRace, unscaledWidth);
|
||||
ImUtf8.HoverTooltip("Racial Clan"u8);
|
||||
if (ret)
|
||||
identifier = identifier with { SubRace = subRace };
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool DrawAttribute(ref RspIdentifier identifier, float unscaledWidth = 200)
|
||||
{
|
||||
var ret = Combos.RspAttribute("##rspAttribute", identifier.Attribute, out var attribute, unscaledWidth);
|
||||
ImUtf8.HoverTooltip("Scaling Attribute"u8);
|
||||
if (ret)
|
||||
identifier = identifier with { Attribute = attribute };
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,18 @@
|
|||
using System.Reflection.Emit;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Text;
|
||||
using OtterGui.Text.EndObjects;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.UI.AdvancedWindow.Meta;
|
||||
using Penumbra.UI.Classes;
|
||||
using Penumbra.UI.ModsTab;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
||||
public partial class ModEditWindow
|
||||
{
|
||||
private const string ModelSetIdTooltip =
|
||||
"Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that.";
|
||||
|
||||
|
||||
private readonly MetaDrawers _metaDrawers;
|
||||
|
||||
private void DrawMetaTab()
|
||||
{
|
||||
|
|
@ -56,80 +47,42 @@ public partial class ModEditWindow
|
|||
if (!child)
|
||||
return;
|
||||
|
||||
DrawEditHeader(MetaManipulation.Type.Eqp);
|
||||
DrawEditHeader(MetaManipulation.Type.Eqdp);
|
||||
DrawEditHeader(MetaManipulation.Type.Imc);
|
||||
DrawEditHeader(MetaManipulation.Type.Est);
|
||||
DrawEditHeader(MetaManipulation.Type.Gmp);
|
||||
DrawEditHeader(MetaManipulation.Type.Rsp);
|
||||
DrawEditHeader(MetaManipulation.Type.GlobalEqp);
|
||||
DrawEditHeader(MetaManipulationType.Eqp);
|
||||
DrawEditHeader(MetaManipulationType.Eqdp);
|
||||
DrawEditHeader(MetaManipulationType.Imc);
|
||||
DrawEditHeader(MetaManipulationType.Est);
|
||||
DrawEditHeader(MetaManipulationType.Gmp);
|
||||
DrawEditHeader(MetaManipulationType.Rsp);
|
||||
DrawEditHeader(MetaManipulationType.GlobalEqp);
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> Label(MetaManipulation.Type type)
|
||||
=> type switch
|
||||
{
|
||||
MetaManipulation.Type.Imc => "Variant Edits (IMC)###IMC"u8,
|
||||
MetaManipulation.Type.Eqdp => "Racial Model Edits (EQDP)###EQDP"u8,
|
||||
MetaManipulation.Type.Eqp => "Equipment Parameter Edits (EQP)###EQP"u8,
|
||||
MetaManipulation.Type.Est => "Extra Skeleton Parameters (EST)###EST"u8,
|
||||
MetaManipulation.Type.Gmp => "Visor/Gimmick Edits (GMP)###GMP"u8,
|
||||
MetaManipulation.Type.Rsp => "Racial Scaling Edits (RSP)###RSP"u8,
|
||||
MetaManipulation.Type.GlobalEqp => "Global Equipment Parameter Edits (Global EQP)###GEQP"u8,
|
||||
_ => "\0"u8,
|
||||
};
|
||||
|
||||
private static int ColumnCount(MetaManipulation.Type type)
|
||||
=> type switch
|
||||
{
|
||||
MetaManipulation.Type.Imc => 10,
|
||||
MetaManipulation.Type.Eqdp => 7,
|
||||
MetaManipulation.Type.Eqp => 5,
|
||||
MetaManipulation.Type.Est => 7,
|
||||
MetaManipulation.Type.Gmp => 7,
|
||||
MetaManipulation.Type.Rsp => 5,
|
||||
MetaManipulation.Type.GlobalEqp => 4,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
private void DrawEditHeader(MetaManipulation.Type type)
|
||||
private void DrawEditHeader(MetaManipulationType type)
|
||||
{
|
||||
var drawer = _metaDrawers.Get(type);
|
||||
if (drawer == null)
|
||||
return;
|
||||
|
||||
var oldPos = ImGui.GetCursorPosY();
|
||||
var header = ImUtf8.CollapsingHeader($"{_editor.MetaEditor.GetCount(type)} {Label(type)}");
|
||||
var header = ImUtf8.CollapsingHeader($"{_editor.MetaEditor.GetCount(type)} {drawer.Label}");
|
||||
DrawOtherOptionData(type, oldPos, ImGui.GetCursorPos());
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
DrawTable(type);
|
||||
DrawTable(drawer);
|
||||
}
|
||||
|
||||
private IMetaDrawer? Drawer(MetaManipulation.Type type)
|
||||
=> type switch
|
||||
{
|
||||
//MetaManipulation.Type.Imc => expr,
|
||||
//MetaManipulation.Type.Eqdp => expr,
|
||||
//MetaManipulation.Type.Eqp => expr,
|
||||
//MetaManipulation.Type.Est => expr,
|
||||
//MetaManipulation.Type.Gmp => expr,
|
||||
//MetaManipulation.Type.Rsp => expr,
|
||||
//MetaManipulation.Type.GlobalEqp => expr,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
private void DrawTable(MetaManipulation.Type type)
|
||||
private static void DrawTable(IMetaDrawer drawer)
|
||||
{
|
||||
const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersInnerV;
|
||||
using var table = ImUtf8.Table(Label(type), ColumnCount(type), flags);
|
||||
using var table = ImUtf8.Table(drawer.Label, drawer.NumColumns, flags);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
if (Drawer(type) is not { } drawer)
|
||||
return;
|
||||
|
||||
drawer.Draw();
|
||||
ImGui.NewLine();
|
||||
}
|
||||
|
||||
private void DrawOtherOptionData(MetaManipulation.Type type, float oldPos, Vector2 newPos)
|
||||
private void DrawOtherOptionData(MetaManipulationType type, float oldPos, Vector2 newPos)
|
||||
{
|
||||
var otherOptionData = _editor.MetaEditor.OtherData[type];
|
||||
if (otherOptionData.TotalCount <= 0)
|
||||
|
|
@ -149,577 +102,12 @@ public partial class ModEditWindow
|
|||
ImGui.SetCursorPos(newPos);
|
||||
}
|
||||
|
||||
#if false
|
||||
private static class EqpRow
|
||||
{
|
||||
private static EqpIdentifier _newIdentifier = new(1, EquipSlot.Body);
|
||||
|
||||
private static float IdWidth
|
||||
=> 100 * UiHelpers.Scale;
|
||||
|
||||
public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current EQP manipulations to clipboard.", iconSize,
|
||||
editor.MetaEditor.Eqp.Select(m => (MetaManipulation)m));
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = editor.MetaEditor.CanAdd(_new);
|
||||
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
|
||||
var defaultEntry = ExpandedEqpFile.GetDefault(metaFileManager, _new.SetId);
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
|
||||
editor.MetaEditor.Add(_new.Copy(defaultEntry));
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
if (IdInput("##eqpId", IdWidth, _new.SetId.Id, out var setId, 1, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1))
|
||||
_new = new EqpManipulation(ExpandedEqpFile.GetDefault(metaFileManager, setId), _new.Slot, setId);
|
||||
|
||||
ImGuiUtil.HoverTooltip(ModelSetIdTooltip);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (Combos.EqpEquipSlot("##eqpSlot", _new.Slot, out var slot))
|
||||
_new = new EqpManipulation(ExpandedEqpFile.GetDefault(metaFileManager, setId), slot, _new.SetId);
|
||||
|
||||
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
|
||||
|
||||
// Values
|
||||
using var disabled = ImRaii.Disabled();
|
||||
ImGui.TableNextColumn();
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
|
||||
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
|
||||
foreach (var flag in Eqp.EqpAttributes[_new.Slot])
|
||||
{
|
||||
var value = defaultEntry.HasFlag(flag);
|
||||
Checkmark("##eqp", flag.ToLocalName(), value, value, out _);
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.NewLine();
|
||||
}
|
||||
|
||||
public static void Draw(MetaFileManager metaFileManager, EqpManipulation meta, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
DrawMetaButtons(meta, editor, iconSize);
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.SetId.ToString());
|
||||
ImGuiUtil.HoverTooltip(ModelSetIdTooltipShort);
|
||||
var defaultEntry = ExpandedEqpFile.GetDefault(metaFileManager, meta.SetId);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.Slot.ToName());
|
||||
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
|
||||
|
||||
// Values
|
||||
ImGui.TableNextColumn();
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
|
||||
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
|
||||
var idx = 0;
|
||||
foreach (var flag in Eqp.EqpAttributes[meta.Slot])
|
||||
{
|
||||
using var id = ImRaii.PushId(idx++);
|
||||
var defaultValue = defaultEntry.HasFlag(flag);
|
||||
var currentValue = meta.Entry.HasFlag(flag);
|
||||
if (Checkmark("##eqp", flag.ToLocalName(), currentValue, defaultValue, out var value))
|
||||
editor.MetaEditor.Change(meta.Copy(value ? meta.Entry | flag : meta.Entry & ~flag));
|
||||
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.NewLine();
|
||||
}
|
||||
}
|
||||
private static class EqdpRow
|
||||
{
|
||||
private static EqdpManipulation _new = new(EqdpEntry.Invalid, EquipSlot.Head, Gender.Male, ModelRace.Midlander, 1);
|
||||
|
||||
private static float IdWidth
|
||||
=> 100 * UiHelpers.Scale;
|
||||
|
||||
public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current EQDP manipulations to clipboard.", iconSize,
|
||||
editor.MetaEditor.Eqdp.Select(m => (MetaManipulation)m));
|
||||
ImGui.TableNextColumn();
|
||||
var raceCode = Names.CombinedRace(_new.Gender, _new.Race);
|
||||
var validRaceCode = CharacterUtilityData.EqdpIdx(raceCode, false) >= 0;
|
||||
var canAdd = validRaceCode && editor.MetaEditor.CanAdd(_new);
|
||||
var tt = canAdd ? "Stage this edit." :
|
||||
validRaceCode ? "This entry is already edited." : "This combination of race and gender can not be used.";
|
||||
var defaultEntry = validRaceCode
|
||||
? ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race), _new.Slot.IsAccessory(), _new.SetId)
|
||||
: 0;
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
|
||||
editor.MetaEditor.Add(_new.Copy(defaultEntry));
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
if (IdInput("##eqdpId", IdWidth, _new.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1))
|
||||
{
|
||||
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race),
|
||||
_new.Slot.IsAccessory(), setId);
|
||||
_new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, _new.Race, setId);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(ModelSetIdTooltip);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (Combos.Race("##eqdpRace", _new.Race, out var race))
|
||||
{
|
||||
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, race),
|
||||
_new.Slot.IsAccessory(), _new.SetId);
|
||||
_new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, race, _new.SetId);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(ModelRaceTooltip);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (Combos.Gender("##eqdpGender", _new.Gender, out var gender))
|
||||
{
|
||||
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(gender, _new.Race),
|
||||
_new.Slot.IsAccessory(), _new.SetId);
|
||||
_new = new EqdpManipulation(newDefaultEntry, _new.Slot, gender, _new.Race, _new.SetId);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(GenderTooltip);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (Combos.EqdpEquipSlot("##eqdpSlot", _new.Slot, out var slot))
|
||||
{
|
||||
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race),
|
||||
slot.IsAccessory(), _new.SetId);
|
||||
_new = new EqdpManipulation(newDefaultEntry, slot, _new.Gender, _new.Race, _new.SetId);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
|
||||
|
||||
// Values
|
||||
using var disabled = ImRaii.Disabled();
|
||||
ImGui.TableNextColumn();
|
||||
var (bit1, bit2) = defaultEntry.ToBits(_new.Slot);
|
||||
Checkmark("Material##eqdpCheck1", string.Empty, bit1, bit1, out _);
|
||||
ImGui.SameLine();
|
||||
Checkmark("Model##eqdpCheck2", string.Empty, bit2, bit2, out _);
|
||||
}
|
||||
|
||||
public static void Draw(MetaFileManager metaFileManager, EqdpManipulation meta, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
DrawMetaButtons(meta, editor, iconSize);
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.SetId.ToString());
|
||||
ImGuiUtil.HoverTooltip(ModelSetIdTooltipShort);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.Race.ToName());
|
||||
ImGuiUtil.HoverTooltip(ModelRaceTooltip);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.Gender.ToName());
|
||||
ImGuiUtil.HoverTooltip(GenderTooltip);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.Slot.ToName());
|
||||
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
|
||||
|
||||
// Values
|
||||
var defaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(meta.Gender, meta.Race), meta.Slot.IsAccessory(),
|
||||
meta.SetId);
|
||||
var (defaultBit1, defaultBit2) = defaultEntry.ToBits(meta.Slot);
|
||||
var (bit1, bit2) = meta.Entry.ToBits(meta.Slot);
|
||||
ImGui.TableNextColumn();
|
||||
if (Checkmark("Material##eqdpCheck1", string.Empty, bit1, defaultBit1, out var newBit1))
|
||||
editor.MetaEditor.Change(meta.Copy(Eqdp.FromSlotAndBits(meta.Slot, newBit1, bit2)));
|
||||
|
||||
ImGui.SameLine();
|
||||
if (Checkmark("Model##eqdpCheck2", string.Empty, bit2, defaultBit2, out var newBit2))
|
||||
editor.MetaEditor.Change(meta.Copy(Eqdp.FromSlotAndBits(meta.Slot, bit1, newBit2)));
|
||||
}
|
||||
}
|
||||
|
||||
private static class EstRow
|
||||
{
|
||||
private static EstManipulation _new = new(Gender.Male, ModelRace.Midlander, EstType.Body, 1, EstEntry.Zero);
|
||||
|
||||
private static float IdWidth
|
||||
=> 100 * UiHelpers.Scale;
|
||||
|
||||
public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current EST manipulations to clipboard.", iconSize,
|
||||
editor.MetaEditor.Est.Select(m => (MetaManipulation)m));
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = editor.MetaEditor.CanAdd(_new);
|
||||
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
|
||||
var defaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, _new.Race), _new.SetId);
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
|
||||
editor.MetaEditor.Add(_new.Copy(defaultEntry));
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
if (IdInput("##estId", IdWidth, _new.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1))
|
||||
{
|
||||
var newDefaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, _new.Race), setId);
|
||||
_new = new EstManipulation(_new.Gender, _new.Race, _new.Slot, setId, newDefaultEntry);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(ModelSetIdTooltip);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (Combos.Race("##estRace", _new.Race, out var race))
|
||||
{
|
||||
var newDefaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, race), _new.SetId);
|
||||
_new = new EstManipulation(_new.Gender, race, _new.Slot, _new.SetId, newDefaultEntry);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(ModelRaceTooltip);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (Combos.Gender("##estGender", _new.Gender, out var gender))
|
||||
{
|
||||
var newDefaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(gender, _new.Race), _new.SetId);
|
||||
_new = new EstManipulation(gender, _new.Race, _new.Slot, _new.SetId, newDefaultEntry);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(GenderTooltip);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (Combos.EstSlot("##estSlot", _new.Slot, out var slot))
|
||||
{
|
||||
var newDefaultEntry = EstFile.GetDefault(metaFileManager, slot, Names.CombinedRace(_new.Gender, _new.Race), _new.SetId);
|
||||
_new = new EstManipulation(_new.Gender, _new.Race, slot, _new.SetId, newDefaultEntry);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(EstTypeTooltip);
|
||||
|
||||
// Values
|
||||
using var disabled = ImRaii.Disabled();
|
||||
ImGui.TableNextColumn();
|
||||
IntDragInput("##estSkeleton", "Skeleton Index", IdWidth, _new.Entry.Value, defaultEntry.Value, out _, 0, ushort.MaxValue, 0.05f);
|
||||
}
|
||||
|
||||
public static void Draw(MetaFileManager metaFileManager, EstManipulation meta, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
DrawMetaButtons(meta, editor, iconSize);
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.SetId.ToString());
|
||||
ImGuiUtil.HoverTooltip(ModelSetIdTooltipShort);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.Race.ToName());
|
||||
ImGuiUtil.HoverTooltip(ModelRaceTooltip);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.Gender.ToName());
|
||||
ImGuiUtil.HoverTooltip(GenderTooltip);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.Slot.ToString());
|
||||
ImGuiUtil.HoverTooltip(EstTypeTooltip);
|
||||
|
||||
// Values
|
||||
var defaultEntry = EstFile.GetDefault(metaFileManager, meta.Slot, Names.CombinedRace(meta.Gender, meta.Race), meta.SetId);
|
||||
ImGui.TableNextColumn();
|
||||
if (IntDragInput("##estSkeleton", $"Skeleton Index\nDefault Value: {defaultEntry}", IdWidth, meta.Entry.Value, defaultEntry.Value,
|
||||
out var entry, 0, ushort.MaxValue, 0.05f))
|
||||
editor.MetaEditor.Change(meta.Copy(new EstEntry((ushort)entry)));
|
||||
}
|
||||
}
|
||||
private static class GmpRow
|
||||
{
|
||||
private static GmpManipulation _new = new(GmpEntry.Default, 1);
|
||||
|
||||
private static float RotationWidth
|
||||
=> 75 * UiHelpers.Scale;
|
||||
|
||||
private static float UnkWidth
|
||||
=> 50 * UiHelpers.Scale;
|
||||
|
||||
private static float IdWidth
|
||||
=> 100 * UiHelpers.Scale;
|
||||
|
||||
public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current GMP manipulations to clipboard.", iconSize,
|
||||
editor.MetaEditor.Gmp.Select(m => (MetaManipulation)m));
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = editor.MetaEditor.CanAdd(_new);
|
||||
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
|
||||
var defaultEntry = ExpandedGmpFile.GetDefault(metaFileManager, _new.SetId);
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
|
||||
editor.MetaEditor.Add(_new.Copy(defaultEntry));
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
if (IdInput("##gmpId", IdWidth, _new.SetId.Id, out var setId, 1, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1))
|
||||
_new = new GmpManipulation(ExpandedGmpFile.GetDefault(metaFileManager, setId), setId);
|
||||
|
||||
ImGuiUtil.HoverTooltip(ModelSetIdTooltip);
|
||||
|
||||
// Values
|
||||
using var disabled = ImRaii.Disabled();
|
||||
ImGui.TableNextColumn();
|
||||
Checkmark("##gmpEnabled", "Gimmick Enabled", defaultEntry.Enabled, defaultEntry.Enabled, out _);
|
||||
ImGui.TableNextColumn();
|
||||
Checkmark("##gmpAnimated", "Gimmick Animated", defaultEntry.Animated, defaultEntry.Animated, out _);
|
||||
ImGui.TableNextColumn();
|
||||
IntDragInput("##gmpRotationA", "Rotation A in Degrees", RotationWidth, defaultEntry.RotationA, defaultEntry.RotationA, out _, 0,
|
||||
360, 0f);
|
||||
ImGui.SameLine();
|
||||
IntDragInput("##gmpRotationB", "Rotation B in Degrees", RotationWidth, defaultEntry.RotationB, defaultEntry.RotationB, out _, 0,
|
||||
360, 0f);
|
||||
ImGui.SameLine();
|
||||
IntDragInput("##gmpRotationC", "Rotation C in Degrees", RotationWidth, defaultEntry.RotationC, defaultEntry.RotationC, out _, 0,
|
||||
360, 0f);
|
||||
ImGui.TableNextColumn();
|
||||
IntDragInput("##gmpUnkA", "Animation Type A?", UnkWidth, defaultEntry.UnknownA, defaultEntry.UnknownA, out _, 0, 15, 0f);
|
||||
ImGui.SameLine();
|
||||
IntDragInput("##gmpUnkB", "Animation Type B?", UnkWidth, defaultEntry.UnknownB, defaultEntry.UnknownB, out _, 0, 15, 0f);
|
||||
}
|
||||
|
||||
public static void Draw(MetaFileManager metaFileManager, GmpManipulation meta, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
DrawMetaButtons(meta, editor, iconSize);
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.SetId.ToString());
|
||||
ImGuiUtil.HoverTooltip(ModelSetIdTooltipShort);
|
||||
|
||||
// Values
|
||||
var defaultEntry = ExpandedGmpFile.GetDefault(metaFileManager, meta.SetId);
|
||||
ImGui.TableNextColumn();
|
||||
if (Checkmark("##gmpEnabled", "Gimmick Enabled", meta.Entry.Enabled, defaultEntry.Enabled, out var enabled))
|
||||
editor.MetaEditor.Change(meta.Copy(meta.Entry with { Enabled = enabled }));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (Checkmark("##gmpAnimated", "Gimmick Animated", meta.Entry.Animated, defaultEntry.Animated, out var animated))
|
||||
editor.MetaEditor.Change(meta.Copy(meta.Entry with { Animated = animated }));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (IntDragInput("##gmpRotationA", $"Rotation A in Degrees\nDefault Value: {defaultEntry.RotationA}", RotationWidth,
|
||||
meta.Entry.RotationA, defaultEntry.RotationA, out var rotationA, 0, 360, 0.05f))
|
||||
editor.MetaEditor.Change(meta.Copy(meta.Entry with { RotationA = (ushort)rotationA }));
|
||||
|
||||
ImGui.SameLine();
|
||||
if (IntDragInput("##gmpRotationB", $"Rotation B in Degrees\nDefault Value: {defaultEntry.RotationB}", RotationWidth,
|
||||
meta.Entry.RotationB, defaultEntry.RotationB, out var rotationB, 0, 360, 0.05f))
|
||||
editor.MetaEditor.Change(meta.Copy(meta.Entry with { RotationB = (ushort)rotationB }));
|
||||
|
||||
ImGui.SameLine();
|
||||
if (IntDragInput("##gmpRotationC", $"Rotation C in Degrees\nDefault Value: {defaultEntry.RotationC}", RotationWidth,
|
||||
meta.Entry.RotationC, defaultEntry.RotationC, out var rotationC, 0, 360, 0.05f))
|
||||
editor.MetaEditor.Change(meta.Copy(meta.Entry with { RotationC = (ushort)rotationC }));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (IntDragInput("##gmpUnkA", $"Animation Type A?\nDefault Value: {defaultEntry.UnknownA}", UnkWidth, meta.Entry.UnknownA,
|
||||
defaultEntry.UnknownA, out var unkA, 0, 15, 0.01f))
|
||||
editor.MetaEditor.Change(meta.Copy(meta.Entry with { UnknownA = (byte)unkA }));
|
||||
|
||||
ImGui.SameLine();
|
||||
if (IntDragInput("##gmpUnkB", $"Animation Type B?\nDefault Value: {defaultEntry.UnknownB}", UnkWidth, meta.Entry.UnknownB,
|
||||
defaultEntry.UnknownB, out var unkB, 0, 15, 0.01f))
|
||||
editor.MetaEditor.Change(meta.Copy(meta.Entry with { UnknownB = (byte)unkB }));
|
||||
}
|
||||
}
|
||||
private static class RspRow
|
||||
{
|
||||
private static RspManipulation _new = new(SubRace.Midlander, RspAttribute.MaleMinSize, RspEntry.One);
|
||||
|
||||
private static float FloatWidth
|
||||
=> 150 * UiHelpers.Scale;
|
||||
|
||||
public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current RSP manipulations to clipboard.", iconSize,
|
||||
editor.MetaEditor.Rsp.Select(m => (MetaManipulation)m));
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = editor.MetaEditor.CanAdd(_new);
|
||||
var tt = canAdd ? "Stage this edit." : "This entry is already edited.";
|
||||
var defaultEntry = CmpFile.GetDefault(metaFileManager, _new.SubRace, _new.Attribute);
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
|
||||
editor.MetaEditor.Add(_new.Copy(defaultEntry));
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
if (Combos.SubRace("##rspSubRace", _new.SubRace, out var subRace))
|
||||
_new = new RspManipulation(subRace, _new.Attribute, CmpFile.GetDefault(metaFileManager, subRace, _new.Attribute));
|
||||
|
||||
ImGuiUtil.HoverTooltip(RacialTribeTooltip);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (Combos.RspAttribute("##rspAttribute", _new.Attribute, out var attribute))
|
||||
_new = new RspManipulation(_new.SubRace, attribute, CmpFile.GetDefault(metaFileManager, subRace, attribute));
|
||||
|
||||
ImGuiUtil.HoverTooltip(ScalingTypeTooltip);
|
||||
|
||||
// Values
|
||||
using var disabled = ImRaii.Disabled();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(FloatWidth);
|
||||
var value = defaultEntry.Value;
|
||||
ImGui.DragFloat("##rspValue", ref value, 0f);
|
||||
}
|
||||
|
||||
public static void Draw(MetaFileManager metaFileManager, RspManipulation meta, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
DrawMetaButtons(meta, editor, iconSize);
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.SubRace.ToName());
|
||||
ImGuiUtil.HoverTooltip(RacialTribeTooltip);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImGui.TextUnformatted(meta.Attribute.ToFullString());
|
||||
ImGuiUtil.HoverTooltip(ScalingTypeTooltip);
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
// Values
|
||||
var def = CmpFile.GetDefault(metaFileManager, meta.SubRace, meta.Attribute).Value;
|
||||
var value = meta.Entry.Value;
|
||||
ImGui.SetNextItemWidth(FloatWidth);
|
||||
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
||||
def < value ? ColorId.IncreasedMetaValue.Value() : ColorId.DecreasedMetaValue.Value(),
|
||||
def != value);
|
||||
if (ImGui.DragFloat("##rspValue", ref value, 0.001f, RspEntry.MinValue, RspEntry.MaxValue)
|
||||
&& value is >= RspEntry.MinValue and <= RspEntry.MaxValue)
|
||||
editor.MetaEditor.Change(meta.Copy(new RspEntry(value)));
|
||||
|
||||
ImGuiUtil.HoverTooltip($"Default Value: {def:0.###}");
|
||||
}
|
||||
}
|
||||
private static class GlobalEqpRow
|
||||
{
|
||||
private static GlobalEqpManipulation _new = new()
|
||||
{
|
||||
Type = GlobalEqpType.DoNotHideEarrings,
|
||||
Condition = 1,
|
||||
};
|
||||
|
||||
public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current global EQP manipulations to clipboard.", iconSize,
|
||||
editor.MetaEditor.GlobalEqp.Select(m => (MetaManipulation)m));
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = editor.MetaEditor.CanAdd(_new);
|
||||
var tt = canAdd ? "Stage this edit." : "This entry is already manipulated.";
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
|
||||
editor.MetaEditor.Add(_new);
|
||||
|
||||
// Identifier
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(250 * ImUtf8.GlobalScale);
|
||||
using (var combo = ImUtf8.Combo("##geqpType"u8, _new.Type.ToName()))
|
||||
{
|
||||
if (combo)
|
||||
foreach (var type in Enum.GetValues<GlobalEqpType>())
|
||||
{
|
||||
if (ImUtf8.Selectable(type.ToName(), type == _new.Type))
|
||||
_new = new GlobalEqpManipulation
|
||||
{
|
||||
Type = type,
|
||||
Condition = type.HasCondition() ? _new.Type.HasCondition() ? _new.Condition : 1 : 0,
|
||||
};
|
||||
ImUtf8.HoverTooltip(type.ToDescription());
|
||||
}
|
||||
}
|
||||
|
||||
ImUtf8.HoverTooltip(_new.Type.ToDescription());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (!_new.Type.HasCondition())
|
||||
return;
|
||||
|
||||
if (IdInput("##geqpCond", 100 * ImUtf8.GlobalScale, _new.Condition.Id, out var newId, 1, ushort.MaxValue, _new.Condition.Id <= 1))
|
||||
_new = _new with { Condition = newId };
|
||||
ImUtf8.HoverTooltip("The Model ID for the item that should not be hidden."u8);
|
||||
}
|
||||
|
||||
public static void Draw(MetaFileManager metaFileManager, GlobalEqpManipulation meta, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
DrawMetaButtons(meta, editor, iconSize);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImUtf8.Text(meta.Type.ToName());
|
||||
ImUtf8.HoverTooltip(meta.Type.ToDescription());
|
||||
ImGui.TableNextColumn();
|
||||
if (meta.Type.HasCondition())
|
||||
{
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X);
|
||||
ImUtf8.Text($"{meta.Condition.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// A number input for ids with a optional max id of given width.
|
||||
// Returns true if newId changed against currentId.
|
||||
private static bool IdInput(string label, float width, ushort currentId, out ushort newId, int minId, int maxId, bool border)
|
||||
{
|
||||
int tmp = currentId;
|
||||
ImGui.SetNextItemWidth(width);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, UiHelpers.Scale, border);
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.RegexWarningBorder, border);
|
||||
if (ImGui.InputInt(label, ref tmp, 0))
|
||||
tmp = Math.Clamp(tmp, minId, maxId);
|
||||
|
||||
newId = (ushort)tmp;
|
||||
return newId != currentId;
|
||||
}
|
||||
|
||||
// A checkmark that compares against a default value and shows a tooltip.
|
||||
// Returns true if newValue is changed against currentValue.
|
||||
private static bool Checkmark(string label, string tooltip, bool currentValue, bool defaultValue, out bool newValue)
|
||||
{
|
||||
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
||||
defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
|
||||
defaultValue != currentValue);
|
||||
newValue = currentValue;
|
||||
ImGui.Checkbox(label, ref newValue);
|
||||
ImGuiUtil.HoverTooltip(tooltip, ImGuiHoveredFlags.AllowWhenDisabled);
|
||||
return newValue != currentValue;
|
||||
}
|
||||
|
||||
// A dragging int input of given width that compares against a default value, shows a tooltip and clamps against min and max.
|
||||
// Returns true if newValue changed against currentValue.
|
||||
private static bool IntDragInput(string label, string tooltip, float width, int currentValue, int defaultValue, out int newValue,
|
||||
int minValue, int maxValue, float speed)
|
||||
{
|
||||
newValue = currentValue;
|
||||
using var color = ImRaii.PushColor(ImGuiCol.FrameBg,
|
||||
defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
|
||||
defaultValue != currentValue);
|
||||
ImGui.SetNextItemWidth(width);
|
||||
if (ImGui.DragInt(label, ref newValue, speed, minValue, maxValue))
|
||||
newValue = Math.Clamp(newValue, minValue, maxValue);
|
||||
|
||||
ImGuiUtil.HoverTooltip(tooltip, ImGuiHoveredFlags.AllowWhenDisabled);
|
||||
|
||||
return newValue != currentValue;
|
||||
}
|
||||
|
||||
private static void CopyToClipboardButton(string tooltip, Vector2 iconSize, MetaDictionary manipulations)
|
||||
{
|
||||
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), iconSize, tooltip, false, true))
|
||||
return;
|
||||
|
||||
var text = Functions.ToCompressedBase64(manipulations, MetaManipulation.CurrentVersion);
|
||||
var text = Functions.ToCompressedBase64(manipulations, MetaApi.CurrentVersion);
|
||||
if (text.Length > 0)
|
||||
ImGui.SetClipboardText(text);
|
||||
}
|
||||
|
|
@ -731,8 +119,11 @@ public partial class ModEditWindow
|
|||
var clipboard = ImGuiUtil.GetClipboardText();
|
||||
|
||||
var version = Functions.FromCompressedBase64<MetaDictionary>(clipboard, out var manips);
|
||||
if (version == MetaManipulation.CurrentVersion && manips != null)
|
||||
if (version == MetaApi.CurrentVersion && manips != null)
|
||||
{
|
||||
_editor.MetaEditor.UpdateTo(manips);
|
||||
_editor.MetaEditor.Changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(
|
||||
|
|
@ -745,194 +136,14 @@ public partial class ModEditWindow
|
|||
{
|
||||
var clipboard = ImGuiUtil.GetClipboardText();
|
||||
var version = Functions.FromCompressedBase64<MetaDictionary>(clipboard, out var manips);
|
||||
if (version == MetaManipulation.CurrentVersion && manips != null)
|
||||
if (version == MetaApi.CurrentVersion && manips != null)
|
||||
{
|
||||
_editor.MetaEditor.SetTo(manips);
|
||||
_editor.MetaEditor.Changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(
|
||||
"Try to set the current meta manipulations to the set currently stored in the clipboard.\nRemoves all other manipulations.");
|
||||
}
|
||||
|
||||
private static void DrawMetaButtons(MetaManipulation meta, ModEditor editor, Vector2 iconSize)
|
||||
{
|
||||
//ImGui.TableNextColumn();
|
||||
//CopyToClipboardButton("Copy this manipulation to clipboard.", iconSize, Array.Empty<MetaManipulation>().Append(meta));
|
||||
//
|
||||
//ImGui.TableNextColumn();
|
||||
//if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), iconSize, "Delete this meta manipulation.", false, true))
|
||||
// editor.MetaEditor.Delete(meta);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface IMetaDrawer
|
||||
{
|
||||
public void Draw();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public abstract class MetaDrawer<TIdentifier, TEntry>(ModEditor editor, MetaFileManager metaFiles) : IMetaDrawer
|
||||
where TIdentifier : unmanaged, IMetaIdentifier
|
||||
where TEntry : unmanaged
|
||||
{
|
||||
protected readonly ModEditor Editor = editor;
|
||||
protected readonly MetaFileManager MetaFiles = metaFiles;
|
||||
protected TIdentifier Identifier;
|
||||
protected TEntry Entry;
|
||||
private bool _initialized;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
Initialize();
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
DrawNew();
|
||||
foreach (var ((identifier, entry), idx) in Enumerate().WithIndex())
|
||||
{
|
||||
using var id = ImUtf8.PushId(idx);
|
||||
DrawEntry(identifier, entry);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void DrawNew();
|
||||
protected abstract void Initialize();
|
||||
protected abstract void DrawEntry(TIdentifier identifier, TEntry entry);
|
||||
|
||||
protected abstract IEnumerable<(TIdentifier, TEntry)> Enumerate();
|
||||
}
|
||||
|
||||
|
||||
#if false
|
||||
public sealed class GmpMetaDrawer(ModEditor editor) : MetaDrawer<GmpIdentifier, GmpEntry>, IService
|
||||
{
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new GmpIdentifier(1, EquipSlot.Body);
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
|
||||
|
||||
protected override void DrawNew()
|
||||
{ }
|
||||
|
||||
protected override void DrawEntry(GmpIdentifier identifier, GmpEntry entry)
|
||||
{ }
|
||||
|
||||
protected override IEnumerable<(GmpIdentifier, GmpEntry)> Enumerate()
|
||||
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
|
||||
}
|
||||
|
||||
public sealed class EstMetaDrawer(ModEditor editor) : MetaDrawer<EstIdentifier, EstEntry>, IService
|
||||
{
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new EqpIdentifier(1, EquipSlot.Body);
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
|
||||
|
||||
protected override void DrawNew()
|
||||
{ }
|
||||
|
||||
protected override void DrawEntry(EstIdentifier identifier, EstEntry entry)
|
||||
{ }
|
||||
|
||||
protected override IEnumerable<(EstIdentifier, EstEntry)> Enumerate()
|
||||
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
|
||||
}
|
||||
|
||||
public sealed class EqdpMetaDrawer(ModEditor editor) : MetaDrawer<EqdpIdentifier, EqdpEntry>, IService
|
||||
{
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new EqdpIdentifier(1, EquipSlot.Body);
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
|
||||
|
||||
protected override void DrawNew()
|
||||
{ }
|
||||
|
||||
protected override void DrawEntry(EqdpIdentifier identifier, EqdpEntry entry)
|
||||
{ }
|
||||
|
||||
protected override IEnumerable<(EqdpIdentifier, EqdpEntry)> Enumerate()
|
||||
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
|
||||
}
|
||||
|
||||
public sealed class EqpMetaDrawer(ModEditor editor, MetaFileManager metaManager) : MetaDrawer<EqpIdentifier, EqpEntry>, IService
|
||||
{
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new EqpIdentifier(1, EquipSlot.Body);
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
|
||||
|
||||
protected override void DrawNew()
|
||||
{ }
|
||||
|
||||
protected override void DrawEntry(EqpIdentifier identifier, EqpEntry entry)
|
||||
{ }
|
||||
|
||||
protected override IEnumerable<(EqpIdentifier, EqpEntry)> Enumerate()
|
||||
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
|
||||
}
|
||||
|
||||
public sealed class RspMetaDrawer(ModEditor editor) : MetaDrawer<RspIdentifier, RspEntry>, IService
|
||||
{
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new RspIdentifier(1, EquipSlot.Body);
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
|
||||
|
||||
protected override void DrawNew()
|
||||
{ }
|
||||
|
||||
protected override void DrawEntry(RspIdentifier identifier, RspEntry entry)
|
||||
{ }
|
||||
|
||||
protected override IEnumerable<(RspIdentifier, RspEntry)> Enumerate()
|
||||
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public sealed class GlobalEqpMetaDrawer(ModEditor editor) : MetaDrawer<GlobalEqpManipulation, byte>, IService
|
||||
{
|
||||
protected override void Initialize()
|
||||
{
|
||||
Identifier = new EqpIdentifier(1, EquipSlot.Body);
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = ExpandedEqpFile.GetDefault(metaManager, Identifier.SetId);
|
||||
|
||||
protected override void DrawNew()
|
||||
{ }
|
||||
|
||||
protected override void DrawEntry(GlobalEqpManipulation identifier, byte _)
|
||||
{ }
|
||||
|
||||
protected override IEnumerable<(GlobalEqpManipulation, byte)> Enumerate()
|
||||
=> editor.MetaEditor.Eqp.Select(kvp => (kvp.Key, kvp.Value.ToEntry(kvp.Key.Slot)));
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public partial class ModEditWindow
|
|||
task.ContinueWith(t => { GamePaths = FinalizeIo(t); }, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
private EstManipulation[] GetCurrentEstManipulations()
|
||||
private KeyValuePair<EstIdentifier, EstEntry>[] GetCurrentEstManipulations()
|
||||
{
|
||||
var mod = _edit._editor.Mod;
|
||||
var option = _edit._editor.Option;
|
||||
|
|
@ -106,9 +106,7 @@ public partial class ModEditWindow
|
|||
return mod.AllDataContainers
|
||||
.Where(subMod => subMod != option)
|
||||
.Prepend(option)
|
||||
.SelectMany(subMod => subMod.Manipulations)
|
||||
.Where(manipulation => manipulation.ManipulationType is MetaManipulation.Type.Est)
|
||||
.Select(manipulation => manipulation.Est)
|
||||
.SelectMany(subMod => subMod.Manipulations.Est)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ using Penumbra.Mods.SubMods;
|
|||
using Penumbra.Services;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.UI.AdvancedWindow.Meta;
|
||||
using Penumbra.UI.Classes;
|
||||
using Penumbra.Util;
|
||||
using MdlMaterialEditor = Penumbra.Mods.Editor.MdlMaterialEditor;
|
||||
|
|
@ -586,7 +587,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
StainService stainService, ActiveCollections activeCollections, ModMergeTab modMergeTab,
|
||||
CommunicatorService communicator, TextureManager textures, ModelManager models, IDragDropManager dragDropManager,
|
||||
ResourceTreeViewerFactory resourceTreeViewerFactory, ObjectManager objects, IFramework framework,
|
||||
CharacterBaseDestructor characterBaseDestructor)
|
||||
CharacterBaseDestructor characterBaseDestructor, MetaDrawers metaDrawers)
|
||||
: base(WindowBaseLabel)
|
||||
{
|
||||
_performance = performance;
|
||||
|
|
@ -606,6 +607,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
_objects = objects;
|
||||
_framework = framework;
|
||||
_characterBaseDestructor = characterBaseDestructor;
|
||||
_metaDrawers = metaDrawers;
|
||||
_materialTab = new FileEditor<MtrlTab>(this, _communicator, gameData, config, _editor.Compactor, _fileDialog, "Materials", ".mtrl",
|
||||
() => PopulateIsOnPlayer(_editor.Files.Mtrl, ResourceType.Mtrl), DrawMaterialPanel, () => Mod?.ModPath.FullName ?? string.Empty,
|
||||
(bytes, path, writable) => new MtrlTab(this, new MtrlFile(bytes), path, writable));
|
||||
|
|
|
|||
|
|
@ -120,9 +120,9 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy
|
|||
{
|
||||
var _ = data switch
|
||||
{
|
||||
Utf8GamePath p => ImGuiNative.igSelectable_Bool(p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero) > 0,
|
||||
MetaManipulation m => ImGui.Selectable(m.Manipulation?.ToString() ?? string.Empty),
|
||||
_ => false,
|
||||
Utf8GamePath p => ImGuiNative.igSelectable_Bool(p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero) > 0,
|
||||
IMetaIdentifier m => ImGui.Selectable(m.ToString()),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ public class EffectiveTab(CollectionManager collectionManager, CollectionSelectH
|
|||
// Filters mean we can not use the known counts.
|
||||
if (hasFilters)
|
||||
{
|
||||
var it2 = m.Select(p => (p.Key.ToString(), p.Value.Name));
|
||||
var it2 = m.IdentifierSources.Select(p => (p.Item1.ToString(), p.Item2.Name));
|
||||
if (stop >= 0)
|
||||
{
|
||||
ImGuiClip.DrawEndDummy(stop + it2.Count(CheckFilters), height);
|
||||
|
|
@ -117,7 +117,7 @@ public class EffectiveTab(CollectionManager collectionManager, CollectionSelectH
|
|||
}
|
||||
else
|
||||
{
|
||||
stop = ImGuiClip.ClippedDraw(m, skips, DrawLine, m.Count, ~stop);
|
||||
stop = ImGuiClip.ClippedDraw(m.IdentifierSources, skips, DrawLine, m.Count, ~stop);
|
||||
ImGuiClip.DrawEndDummy(stop, height);
|
||||
}
|
||||
}
|
||||
|
|
@ -152,11 +152,11 @@ public class EffectiveTab(CollectionManager collectionManager, CollectionSelectH
|
|||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.PrintIcon(FontAwesomeIcon.LongArrowAltLeft);
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.CopyOnClickSelectable(name);
|
||||
ImGuiUtil.CopyOnClickSelectable(name.Text);
|
||||
}
|
||||
|
||||
/// <summary> Draw a line for a unfiltered/unconverted manipulation and mod-index pair. </summary>
|
||||
private static void DrawLine(KeyValuePair<MetaManipulation, IMod> pair)
|
||||
private static void DrawLine((IMetaIdentifier, IMod) pair)
|
||||
{
|
||||
var (manipulation, mod) = pair;
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -165,7 +165,7 @@ public class EffectiveTab(CollectionManager collectionManager, CollectionSelectH
|
|||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.PrintIcon(FontAwesomeIcon.LongArrowAltLeft);
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.CopyOnClickSelectable(mod.Name);
|
||||
ImGuiUtil.CopyOnClickSelectable(mod.Name.Text);
|
||||
}
|
||||
|
||||
/// <summary> Check filters for file replacements. </summary>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using OtterGui.Classes;
|
|||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.Mods.SubMods;
|
||||
|
||||
|
|
@ -10,103 +9,14 @@ namespace Penumbra.Util;
|
|||
|
||||
public static class IdentifierExtensions
|
||||
{
|
||||
/// <summary> Compute the items changed by a given meta manipulation and put them into the changedItems dictionary. </summary>
|
||||
public static void MetaChangedItems(this ObjectIdentification identifier, IDictionary<string, object?> changedItems,
|
||||
MetaManipulation manip)
|
||||
{
|
||||
switch (manip.ManipulationType)
|
||||
{
|
||||
case MetaManipulation.Type.Imc:
|
||||
switch (manip.Imc.ObjectType)
|
||||
{
|
||||
case ObjectType.Equipment:
|
||||
case ObjectType.Accessory:
|
||||
identifier.Identify(changedItems,
|
||||
GamePaths.Equipment.Mtrl.Path(manip.Imc.PrimaryId, GenderRace.MidlanderMale, manip.Imc.EquipSlot, manip.Imc.Variant,
|
||||
"a"));
|
||||
break;
|
||||
case ObjectType.Weapon:
|
||||
identifier.Identify(changedItems,
|
||||
GamePaths.Weapon.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.Variant, "a"));
|
||||
break;
|
||||
case ObjectType.DemiHuman:
|
||||
identifier.Identify(changedItems,
|
||||
GamePaths.DemiHuman.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.EquipSlot, manip.Imc.Variant,
|
||||
"a"));
|
||||
break;
|
||||
case ObjectType.Monster:
|
||||
identifier.Identify(changedItems,
|
||||
GamePaths.Monster.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.Variant, "a"));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case MetaManipulation.Type.Eqdp:
|
||||
identifier.Identify(changedItems,
|
||||
GamePaths.Equipment.Mdl.Path(manip.Eqdp.SetId, Names.CombinedRace(manip.Eqdp.Gender, manip.Eqdp.Race), manip.Eqdp.Slot));
|
||||
break;
|
||||
case MetaManipulation.Type.Eqp:
|
||||
identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(manip.Eqp.SetId, GenderRace.MidlanderMale, manip.Eqp.Slot));
|
||||
break;
|
||||
case MetaManipulation.Type.Est:
|
||||
switch (manip.Est.Slot)
|
||||
{
|
||||
case EstType.Hair:
|
||||
changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Hair (Hair) {manip.Est.SetId}", null);
|
||||
break;
|
||||
case EstType.Face:
|
||||
changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Face (Face) {manip.Est.SetId}", null);
|
||||
break;
|
||||
case EstType.Body:
|
||||
identifier.Identify(changedItems,
|
||||
GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race),
|
||||
EquipSlot.Body));
|
||||
break;
|
||||
case EstType.Head:
|
||||
identifier.Identify(changedItems,
|
||||
GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race),
|
||||
EquipSlot.Head));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case MetaManipulation.Type.Gmp:
|
||||
identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(manip.Gmp.SetId, GenderRace.MidlanderMale, EquipSlot.Head));
|
||||
break;
|
||||
case MetaManipulation.Type.Rsp:
|
||||
changedItems.TryAdd($"{manip.Rsp.SubRace.ToName()} {manip.Rsp.Attribute.ToFullString()}", null);
|
||||
break;
|
||||
case MetaManipulation.Type.GlobalEqp:
|
||||
var path = manip.GlobalEqp.Type switch
|
||||
{
|
||||
GlobalEqpType.DoNotHideEarrings => GamePaths.Accessory.Mdl.Path(manip.GlobalEqp.Condition, GenderRace.MidlanderMale,
|
||||
EquipSlot.Ears),
|
||||
GlobalEqpType.DoNotHideNecklace => GamePaths.Accessory.Mdl.Path(manip.GlobalEqp.Condition, GenderRace.MidlanderMale,
|
||||
EquipSlot.Neck),
|
||||
GlobalEqpType.DoNotHideBracelets => GamePaths.Accessory.Mdl.Path(manip.GlobalEqp.Condition, GenderRace.MidlanderMale,
|
||||
EquipSlot.Wrists),
|
||||
GlobalEqpType.DoNotHideRingR => GamePaths.Accessory.Mdl.Path(manip.GlobalEqp.Condition, GenderRace.MidlanderMale,
|
||||
EquipSlot.RFinger),
|
||||
GlobalEqpType.DoNotHideRingL => GamePaths.Accessory.Mdl.Path(manip.GlobalEqp.Condition, GenderRace.MidlanderMale,
|
||||
EquipSlot.LFinger),
|
||||
GlobalEqpType.DoNotHideHrothgarHats => string.Empty,
|
||||
GlobalEqpType.DoNotHideVieraHats => string.Empty,
|
||||
_ => string.Empty,
|
||||
};
|
||||
if (path.Length > 0)
|
||||
identifier.Identify(changedItems, path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddChangedItems(this ObjectIdentification identifier, IModDataContainer container,
|
||||
IDictionary<string, object?> changedItems)
|
||||
{
|
||||
foreach (var gamePath in container.Files.Keys.Concat(container.FileSwaps.Keys))
|
||||
identifier.Identify(changedItems, gamePath.ToString());
|
||||
|
||||
foreach (var manip in container.Manipulations)
|
||||
MetaChangedItems(identifier, changedItems, manip);
|
||||
foreach (var manip in container.Manipulations.Identifiers)
|
||||
manip.AddChangedItems(identifier, changedItems);
|
||||
}
|
||||
|
||||
public static void RemoveMachinistOffhands(this SortedList<string, object?> changedItems)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue