mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-15 13:14:17 +01:00
Atch stuff.
This commit is contained in:
parent
65538868c3
commit
b1be868a6a
32 changed files with 802 additions and 43 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 07d18f7f7218811956e6663592e53c4145f2d862
|
||||
Subproject commit 2b0c7f3bee0bc2eb466540d2fac265804354493d
|
||||
|
|
@ -5,6 +5,7 @@ using OtterGui;
|
|||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Cache;
|
||||
using Penumbra.GameData.Files.AtchStructs;
|
||||
using Penumbra.GameData.Files.Utility;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
|
@ -66,6 +67,7 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver
|
|||
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)));
|
||||
MetaDictionary.SerializeTo(array, cache.Atch.Select(kvp => new KeyValuePair<AtchIdentifier, AtchEntry>(kvp.Key, kvp.Value.Entry)));
|
||||
}
|
||||
|
||||
return Functions.ToCompressedBase64(array, 0);
|
||||
|
|
@ -97,6 +99,7 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver
|
|||
WriteCache(zipStream, cache.Est);
|
||||
WriteCache(zipStream, cache.Rsp);
|
||||
WriteCache(zipStream, cache.Gmp);
|
||||
WriteCache(zipStream, cache.Atch);
|
||||
cache.GlobalEqp.EnterReadLock();
|
||||
|
||||
try
|
||||
|
|
@ -246,6 +249,15 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver
|
|||
return false;
|
||||
}
|
||||
|
||||
var atchCount = r.ReadInt32();
|
||||
for (var i = 0; i < atchCount; ++i)
|
||||
{
|
||||
var identifier = r.Read<AtchIdentifier>();
|
||||
var value = r.Read<AtchEntry>();
|
||||
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
|
||||
return false;
|
||||
}
|
||||
|
||||
var globalEqpCount = r.ReadInt32();
|
||||
for (var i = 0; i < globalEqpCount; ++i)
|
||||
{
|
||||
|
|
|
|||
122
Penumbra/Collections/Cache/AtchCache.cs
Normal file
122
Penumbra/Collections/Cache/AtchCache.cs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Files.AtchStructs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
namespace Penumbra.Collections.Cache;
|
||||
|
||||
public sealed class AtchCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<AtchIdentifier, AtchEntry>(manager, collection)
|
||||
{
|
||||
private readonly Dictionary<GenderRace, (AtchFile, HashSet<AtchIdentifier>)> _atchFiles = [];
|
||||
|
||||
public bool HasFile(GenderRace gr)
|
||||
=> _atchFiles.ContainsKey(gr);
|
||||
|
||||
public bool GetFile(GenderRace gr, [NotNullWhen(true)] out AtchFile? file)
|
||||
{
|
||||
if (!_atchFiles.TryGetValue(gr, out var p))
|
||||
{
|
||||
file = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
file = p.Item1;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
foreach (var (_, (_, set)) in _atchFiles)
|
||||
set.Clear();
|
||||
|
||||
_atchFiles.Clear();
|
||||
Clear();
|
||||
}
|
||||
|
||||
protected override void ApplyModInternal(AtchIdentifier identifier, AtchEntry entry)
|
||||
{
|
||||
++Collection.AtchChangeCounter;
|
||||
ApplyFile(identifier, entry);
|
||||
}
|
||||
|
||||
private void ApplyFile(AtchIdentifier identifier, AtchEntry entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_atchFiles.TryGetValue(identifier.GenderRace, out var pair))
|
||||
{
|
||||
if (!Manager.AtchManager.AtchFileBase.TryGetValue(identifier.GenderRace, out var baseFile))
|
||||
throw new Exception($"Invalid Atch File for {identifier.GenderRace.ToName()} requested.");
|
||||
|
||||
pair = (baseFile.Clone(), []);
|
||||
}
|
||||
|
||||
|
||||
if (!Apply(pair.Item1, identifier, entry))
|
||||
return;
|
||||
|
||||
pair.Item2.Add(identifier);
|
||||
_atchFiles[identifier.GenderRace] = pair;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not apply ATCH Manipulation {identifier}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RevertModInternal(AtchIdentifier identifier)
|
||||
{
|
||||
++Collection.AtchChangeCounter;
|
||||
if (!_atchFiles.TryGetValue(identifier.GenderRace, out var pair))
|
||||
return;
|
||||
|
||||
if (!pair.Item2.Remove(identifier))
|
||||
return;
|
||||
|
||||
if (pair.Item2.Count == 0)
|
||||
{
|
||||
_atchFiles.Remove(identifier.GenderRace);
|
||||
return;
|
||||
}
|
||||
|
||||
var def = GetDefault(Manager, identifier);
|
||||
if (def == null)
|
||||
throw new Exception($"Reverting an .atch mod had no default value for the identifier to revert to.");
|
||||
|
||||
Apply(pair.Item1, identifier, def.Value);
|
||||
}
|
||||
|
||||
public static AtchEntry? GetDefault(MetaFileManager manager, AtchIdentifier identifier)
|
||||
{
|
||||
if (!manager.AtchManager.AtchFileBase.TryGetValue(identifier.GenderRace, out var baseFile))
|
||||
return null;
|
||||
|
||||
if (baseFile.Points.FirstOrDefault(p => p.Type == identifier.Type) is not { } point)
|
||||
return null;
|
||||
|
||||
if (point.Entries.Length <= identifier.EntryIndex)
|
||||
return null;
|
||||
|
||||
return point.Entries[identifier.EntryIndex];
|
||||
}
|
||||
|
||||
public static bool Apply(AtchFile file, AtchIdentifier identifier, in AtchEntry entry)
|
||||
{
|
||||
if (file.Points.FirstOrDefault(p => p.Type == identifier.Type) is not { } point)
|
||||
return false;
|
||||
|
||||
if (point.Entries.Length <= identifier.EntryIndex)
|
||||
return false;
|
||||
|
||||
point.Entries[identifier.EntryIndex] = entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool _)
|
||||
{
|
||||
Clear();
|
||||
_atchFiles.Clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -228,20 +228,25 @@ public sealed class CollectionCache : IDisposable
|
|||
foreach (var (path, file) in files.FileRedirections)
|
||||
AddFile(path, file, 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 (files.Manipulations.Count > 0)
|
||||
{
|
||||
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, entry) in files.Manipulations.Atch)
|
||||
AddManipulation(mod, identifier, entry);
|
||||
foreach (var identifier in files.Manipulations.GlobalEqp)
|
||||
AddManipulation(mod, identifier, null!);
|
||||
}
|
||||
|
||||
if (addMetaChanges)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files.AtchStructs;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
|
@ -14,11 +15,12 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
|
|||
public readonly GmpCache Gmp = new(manager, collection);
|
||||
public readonly RspCache Rsp = new(manager, collection);
|
||||
public readonly ImcCache Imc = new(manager, collection);
|
||||
public readonly AtchCache Atch = new(manager, collection);
|
||||
public readonly GlobalEqpCache GlobalEqp = new();
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public int Count
|
||||
=> Eqp.Count + Eqdp.Count + Est.Count + Gmp.Count + Rsp.Count + Imc.Count + GlobalEqp.Count;
|
||||
=> Eqp.Count + Eqdp.Count + Est.Count + Gmp.Count + Rsp.Count + Imc.Count + Atch.Count + GlobalEqp.Count;
|
||||
|
||||
public IEnumerable<(IMetaIdentifier, IMod)> IdentifierSources
|
||||
=> Eqp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))
|
||||
|
|
@ -27,6 +29,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
|
|||
.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(Atch.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source)))
|
||||
.Concat(GlobalEqp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value)));
|
||||
|
||||
public void Reset()
|
||||
|
|
@ -37,6 +40,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
|
|||
Gmp.Reset();
|
||||
Rsp.Reset();
|
||||
Imc.Reset();
|
||||
Atch.Reset();
|
||||
GlobalEqp.Clear();
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +56,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
|
|||
Gmp.Dispose();
|
||||
Rsp.Dispose();
|
||||
Imc.Dispose();
|
||||
Atch.Dispose();
|
||||
}
|
||||
|
||||
public bool TryGetMod(IMetaIdentifier identifier, [NotNullWhen(true)] out IMod? mod)
|
||||
|
|
@ -65,6 +70,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
|
|||
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),
|
||||
AtchIdentifier i => Atch.TryGetValue(i, out var p) && Convert(p, out mod),
|
||||
GlobalEqpManipulation i => GlobalEqp.TryGetValue(i, out mod),
|
||||
_ => false,
|
||||
};
|
||||
|
|
@ -85,6 +91,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
|
|||
GmpIdentifier i => Gmp.RevertMod(i, out mod),
|
||||
ImcIdentifier i => Imc.RevertMod(i, out mod),
|
||||
RspIdentifier i => Rsp.RevertMod(i, out mod),
|
||||
AtchIdentifier i => Atch.RevertMod(i, out mod),
|
||||
GlobalEqpManipulation i => GlobalEqp.RevertMod(i, out mod),
|
||||
_ => (mod = null) != null,
|
||||
};
|
||||
|
|
@ -100,6 +107,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
|
|||
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),
|
||||
AtchIdentifier i when entry is AtchEntry e => Atch.ApplyMod(mod, i, e),
|
||||
GlobalEqpManipulation i => GlobalEqp.ApplyMod(mod, i),
|
||||
_ => false,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Dalamud.Hooking;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.Interop.Hooks;
|
||||
|
||||
|
|
@ -31,12 +32,13 @@ public sealed unsafe class DebugHook : IHookService
|
|||
public bool Finished
|
||||
=> _task?.IsCompletedSuccessfully ?? true;
|
||||
|
||||
private delegate void Delegate(nint a, int b, nint c, float* d);
|
||||
private delegate nint Delegate(ResourceHandle* a, int b, int c);
|
||||
|
||||
private void Detour(nint a, int b, nint c, float* d)
|
||||
private nint Detour(ResourceHandle* a, int b, int c)
|
||||
{
|
||||
_task!.Result.Original(a, b, c, d);
|
||||
Penumbra.Log.Information($"[Debug Hook] Results with 0x{a:X} {b} {c:X} {d[0]} {d[1]} {d[2]} {d[3]}.");
|
||||
var ret = _task!.Result.Original(a, b, c);
|
||||
Penumbra.Log.Information($"[Debug Hook] Results with 0x{(nint)a:X}, {b}, {c} -> 0x{ret:X}.");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ public class HookOverrides
|
|||
public bool SetupVisor;
|
||||
public bool UpdateModel;
|
||||
public bool UpdateRender;
|
||||
public bool AtchCaller1;
|
||||
public bool AtchCaller2;
|
||||
}
|
||||
|
||||
public struct ObjectHooks
|
||||
|
|
|
|||
39
Penumbra/Interop/Hooks/Meta/AtchCallerHook1.cs
Normal file
39
Penumbra/Interop/Hooks/Meta/AtchCallerHook1.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Meta;
|
||||
|
||||
public unsafe class AtchCallerHook1 : FastHook<AtchCallerHook1.Delegate>, IDisposable
|
||||
{
|
||||
public delegate void Delegate(DrawObjectData* data, uint slot, nint unk, Model playerModel);
|
||||
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly MetaState _metaState;
|
||||
|
||||
public AtchCallerHook1(HookManager hooks, MetaState metaState, CollectionResolver collectionResolver)
|
||||
{
|
||||
_metaState = metaState;
|
||||
_collectionResolver = collectionResolver;
|
||||
Task = hooks.CreateHook<Delegate>("AtchCaller1", Sigs.AtchCaller1, Detour,
|
||||
metaState.Config.EnableMods && HookOverrides.Instance.Meta.AtchCaller1);
|
||||
if (!HookOverrides.Instance.Meta.AtchCaller1)
|
||||
_metaState.Config.ModsEnabled += Toggle;
|
||||
}
|
||||
|
||||
private void Detour(DrawObjectData* data, uint slot, nint unk, Model playerModel)
|
||||
{
|
||||
var collection = _collectionResolver.IdentifyCollection(playerModel.AsDrawObject, true);
|
||||
_metaState.AtchCollection.Push(collection);
|
||||
Task.Result.Original(data, slot, unk, playerModel);
|
||||
_metaState.AtchCollection.Pop();
|
||||
Penumbra.Log.Excessive(
|
||||
$"[AtchCaller1] Invoked on 0x{(ulong)data:X} with {slot}, {unk:X}, 0x{playerModel.Address:X}, identified to {collection.ModCollection.AnonymizedName}.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> _metaState.Config.ModsEnabled -= Toggle;
|
||||
}
|
||||
38
Penumbra/Interop/Hooks/Meta/AtchCallerHook2.cs
Normal file
38
Penumbra/Interop/Hooks/Meta/AtchCallerHook2.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Meta;
|
||||
|
||||
public unsafe class AtchCallerHook2 : FastHook<AtchCallerHook2.Delegate>, IDisposable
|
||||
{
|
||||
public delegate void Delegate(DrawObjectData* data, uint slot, nint unk, Model playerModel, uint unk2);
|
||||
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly MetaState _metaState;
|
||||
|
||||
public AtchCallerHook2(HookManager hooks, MetaState metaState, CollectionResolver collectionResolver)
|
||||
{
|
||||
_metaState = metaState;
|
||||
_collectionResolver = collectionResolver;
|
||||
Task = hooks.CreateHook<Delegate>("AtchCaller2", Sigs.AtchCaller2, Detour,
|
||||
metaState.Config.EnableMods && HookOverrides.Instance.Meta.AtchCaller2);
|
||||
if (!HookOverrides.Instance.Meta.AtchCaller2)
|
||||
_metaState.Config.ModsEnabled += Toggle;
|
||||
}
|
||||
|
||||
private void Detour(DrawObjectData* data, uint slot, nint unk, Model playerModel, uint unk2)
|
||||
{
|
||||
var collection = _collectionResolver.IdentifyCollection(playerModel.AsDrawObject, true);
|
||||
_metaState.AtchCollection.Push(collection);
|
||||
Task.Result.Original(data, slot, unk, playerModel, unk2);
|
||||
_metaState.AtchCollection.Pop();
|
||||
Penumbra.Log.Excessive(
|
||||
$"[AtchCaller2] Invoked on 0x{(ulong)data:X} with {slot}, {unk:X}, 0x{playerModel.Address:X}, {unk2}, identified to {collection.ModCollection.AnonymizedName}.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> _metaState.Config.ModsEnabled -= Toggle;
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@ public sealed unsafe class MetaState : IDisposable, IService
|
|||
public readonly Stack<ResolveData> EqdpCollection = [];
|
||||
public readonly Stack<ResolveData> EstCollection = [];
|
||||
public readonly Stack<ResolveData> RspCollection = [];
|
||||
public readonly Stack<ResolveData> AtchCollection = [];
|
||||
|
||||
public readonly Stack<(ResolveData Collection, PrimaryId Id)> GmpCollection = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Linq;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
|
|
@ -54,7 +55,7 @@ public class PathResolver : IDisposable, IService
|
|||
|
||||
// Prevent .atch loading to prevent crashes on outdated .atch files. TODO: handle atch modding differently.
|
||||
if (resourceType is ResourceType.Atch)
|
||||
return (null, ResolveData.Invalid);
|
||||
return ResolveAtch(path);
|
||||
|
||||
return category switch
|
||||
{
|
||||
|
|
@ -142,4 +143,10 @@ public class PathResolver : IDisposable, IService
|
|||
private (FullPath?, ResolveData) ResolveUi(Utf8GamePath path)
|
||||
=> (_collectionManager.Active.Interface.ResolvePath(path),
|
||||
_collectionManager.Active.Interface.ToResolveData());
|
||||
|
||||
public (FullPath?, ResolveData) ResolveAtch(Utf8GamePath gamePath)
|
||||
{
|
||||
_metaState.AtchCollection.TryPeek(out var resolveData);
|
||||
return _preprocessor.PreProcess(resolveData, gamePath.Path, false, ResourceType.Atch, null, gamePath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
43
Penumbra/Interop/Processing/AtchFilePostProcessor.cs
Normal file
43
Penumbra/Interop/Processing/AtchFilePostProcessor.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Penumbra.Interop.Processing;
|
||||
|
||||
public sealed class AtchFilePostProcessor(CollectionStorage collections, XivFileAllocator allocator)
|
||||
: IFilePostProcessor
|
||||
{
|
||||
private readonly IFileAllocator _allocator = allocator;
|
||||
|
||||
public ResourceType Type
|
||||
=> ResourceType.Atch;
|
||||
|
||||
public unsafe void PostProcess(ResourceHandle* resource, CiByteString originalGamePath, ReadOnlySpan<byte> additionalData)
|
||||
{
|
||||
if (!PathDataHandler.Read(additionalData, out var data) || data.Discriminator != PathDataHandler.Discriminator)
|
||||
return;
|
||||
|
||||
var collection = collections.ByLocalId(data.Collection);
|
||||
if (collection.MetaCache is not { } cache)
|
||||
return;
|
||||
|
||||
if (!AtchPathPreProcessor.TryGetAtchGenderRace(originalGamePath, out var gr))
|
||||
return;
|
||||
|
||||
if (!collection.MetaCache.Atch.GetFile(gr, out var file))
|
||||
return;
|
||||
|
||||
using var bytes = file.Write();
|
||||
var length = (int)bytes.Position;
|
||||
var alloc = _allocator.Allocate(length, 1);
|
||||
bytes.GetBuffer().AsSpan(0, length).CopyTo(new Span<byte>(alloc, length));
|
||||
var (oldData, oldLength) = resource->GetData();
|
||||
_allocator.Release((void*)oldData, oldLength);
|
||||
resource->SetData((nint)alloc, length);
|
||||
Penumbra.Log.Information($"Post-Processed {originalGamePath} on resource 0x{(nint)resource:X} with {collection} for {gr.ToName()}.");
|
||||
}
|
||||
}
|
||||
44
Penumbra/Interop/Processing/AtchPathPreProcessor.cs
Normal file
44
Penumbra/Interop/Processing/AtchPathPreProcessor.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Interop.Processing;
|
||||
|
||||
public sealed class AtchPathPreProcessor : IPathPreProcessor
|
||||
{
|
||||
public ResourceType Type
|
||||
=> ResourceType.Atch;
|
||||
|
||||
public FullPath? PreProcess(ResolveData resolveData, CiByteString path, Utf8GamePath _, bool nonDefault, FullPath? resolved)
|
||||
{
|
||||
if (!resolveData.Valid)
|
||||
return resolved;
|
||||
|
||||
if (!TryGetAtchGenderRace(path, out var gr))
|
||||
return resolved;
|
||||
|
||||
Penumbra.Log.Information($"Pre-Processed {path} with {resolveData.ModCollection} for {gr.ToName()}.");
|
||||
if (resolveData.ModCollection.MetaCache?.Atch.GetFile(gr, out var file) == true)
|
||||
return PathDataHandler.CreateAtch(path, resolveData.ModCollection);
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public static bool TryGetAtchGenderRace(CiByteString originalGamePath, out GenderRace genderRace)
|
||||
{
|
||||
if (originalGamePath[^6] != '1'
|
||||
|| originalGamePath[^7] != '0'
|
||||
|| !ushort.TryParse(originalGamePath.Span[^9..^7], out var grInt)
|
||||
|| grInt > 18)
|
||||
{
|
||||
genderRace = GenderRace.Unknown;
|
||||
return false;
|
||||
}
|
||||
|
||||
genderRace = (GenderRace)(grInt * 100 + 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,8 +25,7 @@ public class GamePathPreProcessService : IService
|
|||
|
||||
|
||||
public (FullPath? Path, ResolveData Data) PreProcess(ResolveData resolveData, CiByteString path, bool nonDefault, ResourceType type,
|
||||
FullPath? resolved,
|
||||
Utf8GamePath originalPath)
|
||||
FullPath? resolved, Utf8GamePath originalPath)
|
||||
{
|
||||
if (!_processors.TryGetValue(type, out var processor))
|
||||
return (resolved, resolveData);
|
||||
|
|
|
|||
26
Penumbra/Meta/AtchManager.cs
Normal file
26
Penumbra/Meta/AtchManager.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System.Collections.Frozen;
|
||||
using Dalamud.Plugin.Services;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Meta;
|
||||
|
||||
public sealed unsafe class AtchManager : IService
|
||||
{
|
||||
private static readonly IReadOnlyList<GenderRace> GenderRaces =
|
||||
[
|
||||
GenderRace.MidlanderMale, GenderRace.MidlanderFemale, GenderRace.HighlanderMale, GenderRace.HighlanderFemale, GenderRace.ElezenMale,
|
||||
GenderRace.ElezenFemale, GenderRace.MiqoteMale, GenderRace.MiqoteFemale, GenderRace.RoegadynMale, GenderRace.RoegadynFemale,
|
||||
GenderRace.LalafellMale, GenderRace.LalafellFemale, GenderRace.AuRaMale, GenderRace.AuRaFemale, GenderRace.HrothgarMale,
|
||||
GenderRace.HrothgarFemale, GenderRace.VieraMale, GenderRace.VieraFemale,
|
||||
];
|
||||
|
||||
public readonly IReadOnlyDictionary<GenderRace, AtchFile> AtchFileBase;
|
||||
|
||||
public AtchManager(IDataManager manager)
|
||||
{
|
||||
AtchFileBase = GenderRaces.ToFrozenDictionary(gr => gr,
|
||||
gr => new AtchFile(manager.GetFile($"chara/xls/attachOffset/c{gr.ToRaceCode()}.atch")!.DataSpan));
|
||||
}
|
||||
}
|
||||
77
Penumbra/Meta/Manipulations/AtchIdentifier.cs
Normal file
77
Penumbra/Meta/Manipulations/AtchIdentifier.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files.AtchStructs;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
public readonly record struct AtchIdentifier(AtchType Type, GenderRace GenderRace, ushort EntryIndex)
|
||||
: IComparable<AtchIdentifier>, IMetaIdentifier
|
||||
{
|
||||
public Gender Gender
|
||||
=> GenderRace.Split().Item1;
|
||||
|
||||
public ModelRace Race
|
||||
=> GenderRace.Split().Item2;
|
||||
|
||||
public int CompareTo(AtchIdentifier other)
|
||||
{
|
||||
var typeComparison = Type.CompareTo(other.Type);
|
||||
if (typeComparison != 0)
|
||||
return typeComparison;
|
||||
|
||||
var genderRaceComparison = GenderRace.CompareTo(other.GenderRace);
|
||||
if (genderRaceComparison != 0)
|
||||
return genderRaceComparison;
|
||||
|
||||
return EntryIndex.CompareTo(other.EntryIndex);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"Atch - {Type.ToAbbreviation()} - {GenderRace.ToName()} - {EntryIndex}";
|
||||
|
||||
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, IIdentifiedObjectData?> changedItems)
|
||||
{
|
||||
// Nothing specific
|
||||
}
|
||||
|
||||
public MetaIndex FileIndex()
|
||||
=> (MetaIndex)(-1);
|
||||
|
||||
public bool Validate()
|
||||
{
|
||||
var race = (int)GenderRace / 100;
|
||||
var remainder = (int)GenderRace - 100 * race;
|
||||
if (remainder != 1)
|
||||
return false;
|
||||
|
||||
return race is >= 0 and <= 18;
|
||||
}
|
||||
|
||||
public JObject AddToJson(JObject jObj)
|
||||
{
|
||||
var (gender, race) = GenderRace.Split();
|
||||
jObj["Gender"] = gender.ToString();
|
||||
jObj["Race"] = race.ToString();
|
||||
jObj["Type"] = Type.ToAbbreviation();
|
||||
jObj["Index"] = EntryIndex;
|
||||
return jObj;
|
||||
}
|
||||
|
||||
public static AtchIdentifier? FromJson(JObject jObj)
|
||||
{
|
||||
var gender = jObj["Gender"]?.ToObject<Gender>() ?? Gender.Unknown;
|
||||
var race = jObj["Race"]?.ToObject<ModelRace>() ?? ModelRace.Unknown;
|
||||
var type = AtchExtensions.FromString(jObj["Type"]?.ToObject<string>() ?? string.Empty);
|
||||
var entryIndex = jObj["Index"]?.ToObject<ushort>() ?? ushort.MaxValue;
|
||||
if (entryIndex == ushort.MaxValue || type is AtchType.Unknown)
|
||||
return null;
|
||||
|
||||
var ret = new AtchIdentifier(type, Names.CombinedRace(gender, race), entryIndex);
|
||||
return ret.Validate() ? ret : null;
|
||||
}
|
||||
|
||||
MetaManipulationType IMetaIdentifier.Type
|
||||
=> MetaManipulationType.Atch;
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ public enum MetaManipulationType : byte
|
|||
Gmp = 5,
|
||||
Rsp = 6,
|
||||
GlobalEqp = 7,
|
||||
Atch = 8,
|
||||
}
|
||||
|
||||
public interface IMetaIdentifier
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.Collections.Cache;
|
||||
using Penumbra.GameData.Files.AtchStructs;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Util;
|
||||
using ImcEntry = Penumbra.GameData.Structs.ImcEntry;
|
||||
|
|
@ -16,6 +17,7 @@ public class MetaDictionary
|
|||
private readonly Dictionary<EstIdentifier, EstEntry> _est = [];
|
||||
private readonly Dictionary<RspIdentifier, RspEntry> _rsp = [];
|
||||
private readonly Dictionary<GmpIdentifier, GmpEntry> _gmp = [];
|
||||
private readonly Dictionary<AtchIdentifier, AtchEntry> _atch = [];
|
||||
private readonly HashSet<GlobalEqpManipulation> _globalEqp = [];
|
||||
|
||||
public IReadOnlyDictionary<ImcIdentifier, ImcEntry> Imc
|
||||
|
|
@ -36,6 +38,9 @@ public class MetaDictionary
|
|||
public IReadOnlyDictionary<RspIdentifier, RspEntry> Rsp
|
||||
=> _rsp;
|
||||
|
||||
public IReadOnlyDictionary<AtchIdentifier, AtchEntry> Atch
|
||||
=> _atch;
|
||||
|
||||
public IReadOnlySet<GlobalEqpManipulation> GlobalEqp
|
||||
=> _globalEqp;
|
||||
|
||||
|
|
@ -50,6 +55,7 @@ public class MetaDictionary
|
|||
MetaManipulationType.Est => _est.Count,
|
||||
MetaManipulationType.Gmp => _gmp.Count,
|
||||
MetaManipulationType.Rsp => _rsp.Count,
|
||||
MetaManipulationType.Atch => _atch.Count,
|
||||
MetaManipulationType.GlobalEqp => _globalEqp.Count,
|
||||
_ => 0,
|
||||
};
|
||||
|
|
@ -63,6 +69,7 @@ public class MetaDictionary
|
|||
GlobalEqpManipulation i => _globalEqp.Contains(i),
|
||||
GmpIdentifier i => _gmp.ContainsKey(i),
|
||||
ImcIdentifier i => _imc.ContainsKey(i),
|
||||
AtchIdentifier i => _atch.ContainsKey(i),
|
||||
RspIdentifier i => _rsp.ContainsKey(i),
|
||||
_ => false,
|
||||
};
|
||||
|
|
@ -76,6 +83,7 @@ public class MetaDictionary
|
|||
_est.Clear();
|
||||
_rsp.Clear();
|
||||
_gmp.Clear();
|
||||
_atch.Clear();
|
||||
_globalEqp.Clear();
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +96,7 @@ public class MetaDictionary
|
|||
_est.Clear();
|
||||
_rsp.Clear();
|
||||
_gmp.Clear();
|
||||
_atch.Clear();
|
||||
}
|
||||
|
||||
public bool Equals(MetaDictionary other)
|
||||
|
|
@ -98,6 +107,7 @@ public class MetaDictionary
|
|||
&& _est.SetEquals(other._est)
|
||||
&& _rsp.SetEquals(other._rsp)
|
||||
&& _gmp.SetEquals(other._gmp)
|
||||
&& _atch.SetEquals(other._atch)
|
||||
&& _globalEqp.SetEquals(other._globalEqp);
|
||||
|
||||
public IEnumerable<IMetaIdentifier> Identifiers
|
||||
|
|
@ -107,6 +117,7 @@ public class MetaDictionary
|
|||
.Concat(_est.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_gmp.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_rsp.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_atch.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_globalEqp.Cast<IMetaIdentifier>());
|
||||
|
||||
#region TryAdd
|
||||
|
|
@ -171,6 +182,15 @@ public class MetaDictionary
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool TryAdd(AtchIdentifier identifier, in AtchEntry entry)
|
||||
{
|
||||
if (!_atch.TryAdd(identifier, entry))
|
||||
return false;
|
||||
|
||||
++Count;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryAdd(GlobalEqpManipulation identifier)
|
||||
{
|
||||
if (!_globalEqp.Add(identifier))
|
||||
|
|
@ -244,6 +264,15 @@ public class MetaDictionary
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool Update(AtchIdentifier identifier, in AtchEntry entry)
|
||||
{
|
||||
if (!_atch.ContainsKey(identifier))
|
||||
return false;
|
||||
|
||||
_atch[identifier] = entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TryGetValue
|
||||
|
|
@ -266,6 +295,9 @@ public class MetaDictionary
|
|||
public bool TryGetValue(ImcIdentifier identifier, out ImcEntry value)
|
||||
=> _imc.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(AtchIdentifier identifier, out AtchEntry value)
|
||||
=> _atch.TryGetValue(identifier, out value);
|
||||
|
||||
#endregion
|
||||
|
||||
public bool Remove(IMetaIdentifier identifier)
|
||||
|
|
@ -279,6 +311,7 @@ public class MetaDictionary
|
|||
GmpIdentifier i => _gmp.Remove(i),
|
||||
ImcIdentifier i => _imc.Remove(i),
|
||||
RspIdentifier i => _rsp.Remove(i),
|
||||
AtchIdentifier i => _atch.Remove(i),
|
||||
_ => false,
|
||||
};
|
||||
if (ret)
|
||||
|
|
@ -308,6 +341,9 @@ public class MetaDictionary
|
|||
foreach (var (identifier, entry) in manips._est)
|
||||
TryAdd(identifier, entry);
|
||||
|
||||
foreach (var (identifier, entry) in manips._atch)
|
||||
TryAdd(identifier, entry);
|
||||
|
||||
foreach (var identifier in manips._globalEqp)
|
||||
TryAdd(identifier);
|
||||
}
|
||||
|
|
@ -351,6 +387,12 @@ public class MetaDictionary
|
|||
return false;
|
||||
}
|
||||
|
||||
foreach (var (identifier, _) in manips._atch.Where(kvp => !TryAdd(kvp.Key, kvp.Value)))
|
||||
{
|
||||
failedIdentifier = identifier;
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var identifier in manips._globalEqp.Where(identifier => !TryAdd(identifier)))
|
||||
{
|
||||
failedIdentifier = identifier;
|
||||
|
|
@ -369,8 +411,9 @@ public class MetaDictionary
|
|||
_est.SetTo(other._est);
|
||||
_rsp.SetTo(other._rsp);
|
||||
_gmp.SetTo(other._gmp);
|
||||
_atch.SetTo(other._atch);
|
||||
_globalEqp.SetTo(other._globalEqp);
|
||||
Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _globalEqp.Count;
|
||||
Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _atch.Count + _globalEqp.Count;
|
||||
}
|
||||
|
||||
public void UpdateTo(MetaDictionary other)
|
||||
|
|
@ -381,8 +424,9 @@ public class MetaDictionary
|
|||
_est.UpdateTo(other._est);
|
||||
_rsp.UpdateTo(other._rsp);
|
||||
_gmp.UpdateTo(other._gmp);
|
||||
_atch.UpdateTo(other._atch);
|
||||
_globalEqp.UnionWith(other._globalEqp);
|
||||
Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _globalEqp.Count;
|
||||
Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _atch.Count + _globalEqp.Count;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
@ -460,6 +504,16 @@ public class MetaDictionary
|
|||
}),
|
||||
};
|
||||
|
||||
public static JObject Serialize(AtchIdentifier identifier, AtchEntry entry)
|
||||
=> new()
|
||||
{
|
||||
["Type"] = MetaManipulationType.Atch.ToString(),
|
||||
["Manipulation"] = identifier.AddToJson(new JObject
|
||||
{
|
||||
["Entry"] = entry.ToJson(),
|
||||
}),
|
||||
};
|
||||
|
||||
public static JObject Serialize(GlobalEqpManipulation identifier)
|
||||
=> new()
|
||||
{
|
||||
|
|
@ -487,6 +541,8 @@ public class MetaDictionary
|
|||
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(AtchIdentifier) && typeof(TEntry) == typeof(AtchEntry))
|
||||
return Serialize(Unsafe.As<TIdentifier, AtchIdentifier>(ref identifier), Unsafe.As<TEntry, AtchEntry>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(GlobalEqpManipulation))
|
||||
return Serialize(Unsafe.As<TIdentifier, GlobalEqpManipulation>(ref identifier));
|
||||
|
||||
|
|
@ -531,6 +587,7 @@ public class MetaDictionary
|
|||
SerializeTo(array, value._est);
|
||||
SerializeTo(array, value._rsp);
|
||||
SerializeTo(array, value._gmp);
|
||||
SerializeTo(array, value._atch);
|
||||
SerializeTo(array, value._globalEqp);
|
||||
array.WriteTo(writer);
|
||||
}
|
||||
|
|
@ -618,6 +675,16 @@ public class MetaDictionary
|
|||
Penumbra.Log.Warning("Invalid RSP Manipulation encountered.");
|
||||
break;
|
||||
}
|
||||
case MetaManipulationType.Atch:
|
||||
{
|
||||
var identifier = AtchIdentifier.FromJson(manip);
|
||||
var entry = AtchEntry.FromJson(manip["Entry"] as JObject);
|
||||
if (identifier.HasValue && entry.HasValue)
|
||||
dict.TryAdd(identifier.Value, entry.Value);
|
||||
else
|
||||
Penumbra.Log.Warning("Invalid ATCH Manipulation encountered.");
|
||||
break;
|
||||
}
|
||||
case MetaManipulationType.GlobalEqp:
|
||||
{
|
||||
var identifier = GlobalEqpManipulation.FromJson(manip);
|
||||
|
|
@ -648,6 +715,7 @@ public class MetaDictionary
|
|||
_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);
|
||||
_atch = cache.Atch.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry);
|
||||
_globalEqp = cache.GlobalEqp.Select(kvp => kvp.Key).ToHashSet();
|
||||
Count = cache.Count;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Penumbra.Collections;
|
|||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.Import;
|
||||
using Penumbra.Interop.Hooks.Meta;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Mods;
|
||||
|
|
@ -25,13 +26,14 @@ public class MetaFileManager : IService
|
|||
internal readonly ObjectIdentification Identifier;
|
||||
internal readonly FileCompactor Compactor;
|
||||
internal readonly ImcChecker ImcChecker;
|
||||
internal readonly AtchManager AtchManager;
|
||||
internal readonly IFileAllocator MarshalAllocator = new MarshalAllocator();
|
||||
internal readonly IFileAllocator XivAllocator;
|
||||
|
||||
|
||||
public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, IDataManager gameData,
|
||||
ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, ObjectIdentification identifier,
|
||||
FileCompactor compactor, IGameInteropProvider interop)
|
||||
FileCompactor compactor, IGameInteropProvider interop, AtchManager atchManager)
|
||||
{
|
||||
CharacterUtility = characterUtility;
|
||||
ResidentResources = residentResources;
|
||||
|
|
@ -41,6 +43,7 @@ public class MetaFileManager : IService
|
|||
ValidityChecker = validityChecker;
|
||||
Identifier = identifier;
|
||||
Compactor = compactor;
|
||||
AtchManager = atchManager;
|
||||
ImcChecker = new ImcChecker(this);
|
||||
XivAllocator = new XivFileAllocator(interop);
|
||||
interop.InitializeFromAttributes(this);
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ public class ModMetaEditor(
|
|||
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.Atch].Add(name, option.Manipulations.GetCount(MetaManipulationType.Atch));
|
||||
OtherData[MetaManipulationType.GlobalEqp].Add(name, option.Manipulations.GetCount(MetaManipulationType.GlobalEqp));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManage
|
|||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Interop.Hooks;
|
||||
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||
|
||||
|
|
|
|||
245
Penumbra/UI/AdvancedWindow/Meta/AtchMetaDrawer.cs
Normal file
245
Penumbra/UI/AdvancedWindow/Meta/AtchMetaDrawer.cs
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.Collections.Cache;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Files.AtchStructs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow.Meta;
|
||||
|
||||
public sealed class AtchMetaDrawer : MetaDrawer<AtchIdentifier, AtchEntry>, IService
|
||||
{
|
||||
public override ReadOnlySpan<byte> Label
|
||||
=> "Attachment Points (ATCH)###ATCH"u8;
|
||||
|
||||
public override int NumColumns
|
||||
=> 10;
|
||||
|
||||
public override float ColumnHeight
|
||||
=> 2 * ImUtf8.FrameHeightSpacing;
|
||||
|
||||
private AtchFile? _currentBaseAtchFile;
|
||||
private AtchPoint? _currentBaseAtchPoint;
|
||||
private AtchPointCombo _combo;
|
||||
|
||||
public AtchMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles)
|
||||
: base(editor, metaFiles)
|
||||
{
|
||||
_combo = new AtchPointCombo(() => _currentBaseAtchFile?.Points.Select(p => p.Type).ToList() ?? []);
|
||||
}
|
||||
|
||||
private sealed class AtchPointCombo(Func<IReadOnlyList<AtchType>> generator)
|
||||
: FilterComboCache<AtchType>(generator, MouseWheelType.Control, Penumbra.Log)
|
||||
{
|
||||
protected override string ToString(AtchType obj)
|
||||
=> obj.ToName();
|
||||
}
|
||||
|
||||
|
||||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current ATCH manipulations to clipboard."u8,
|
||||
new Lazy<JToken?>(() => MetaDictionary.SerializeTo([], Editor.Atch)));
|
||||
|
||||
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();
|
||||
|
||||
var defaultEntry = AtchCache.GetDefault(MetaFiles, Identifier) ?? default;
|
||||
DrawEntry(defaultEntry, ref defaultEntry, true);
|
||||
}
|
||||
|
||||
private void UpdateEntry()
|
||||
=> Entry = _currentBaseAtchPoint!.Entries[Identifier.EntryIndex];
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
_currentBaseAtchFile = MetaFiles.AtchManager.AtchFileBase[GenderRace.MidlanderMale];
|
||||
_currentBaseAtchPoint = _currentBaseAtchFile.Points.First();
|
||||
Identifier = new AtchIdentifier(_currentBaseAtchPoint.Type, GenderRace.MidlanderMale, 0);
|
||||
Entry = _currentBaseAtchPoint.Entries[0];
|
||||
}
|
||||
|
||||
protected override void DrawEntry(AtchIdentifier identifier, AtchEntry entry)
|
||||
{
|
||||
DrawMetaButtons(identifier, entry);
|
||||
DrawIdentifier(identifier);
|
||||
|
||||
var defaultEntry = AtchCache.GetDefault(MetaFiles, identifier) ?? default;
|
||||
if (DrawEntry(defaultEntry, ref entry, false))
|
||||
Editor.Changes |= Editor.Update(identifier, entry);
|
||||
}
|
||||
|
||||
protected override IEnumerable<(AtchIdentifier, AtchEntry)> Enumerate()
|
||||
=> Editor.Atch.Select(kvp => (kvp.Key, kvp.Value))
|
||||
.OrderBy(p => p.Key.GenderRace)
|
||||
.ThenBy(p => p.Key.Type)
|
||||
.ThenBy(p => p.Key.EntryIndex);
|
||||
|
||||
protected override int Count
|
||||
=> Editor.Atch.Count;
|
||||
|
||||
private bool DrawIdentifierInput(ref AtchIdentifier identifier)
|
||||
{
|
||||
var changes = false;
|
||||
ImGui.TableNextColumn();
|
||||
changes |= DrawRace(ref identifier);
|
||||
ImGui.TableNextColumn();
|
||||
changes |= DrawGender(ref identifier, false);
|
||||
if (changes)
|
||||
UpdateFile();
|
||||
ImGui.TableNextColumn();
|
||||
if (DrawPointInput(ref identifier, _combo))
|
||||
{
|
||||
_currentBaseAtchPoint = _currentBaseAtchFile?.GetPoint(identifier.Type);
|
||||
changes = true;
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
changes |= DrawEntryIndexInput(ref identifier, _currentBaseAtchPoint!);
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private void UpdateFile()
|
||||
{
|
||||
_currentBaseAtchFile = MetaFiles.AtchManager.AtchFileBase[Identifier.GenderRace];
|
||||
_currentBaseAtchPoint = _currentBaseAtchFile.GetPoint(Identifier.Type);
|
||||
if (_currentBaseAtchPoint == null)
|
||||
{
|
||||
_currentBaseAtchPoint = _currentBaseAtchFile.Points.First();
|
||||
Identifier = Identifier with { Type = _currentBaseAtchPoint.Type };
|
||||
}
|
||||
|
||||
if (Identifier.EntryIndex >= _currentBaseAtchPoint.Entries.Length)
|
||||
Identifier = Identifier with { EntryIndex = 0 };
|
||||
}
|
||||
|
||||
private static void DrawIdentifier(AtchIdentifier identifier)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.Race.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Model Race"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawGender(ref identifier, true);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.Type.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Attachment Point Type"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.TextFramed(identifier.EntryIndex.ToString(), FrameColor);
|
||||
ImUtf8.HoverTooltip("State Entry Index"u8);
|
||||
}
|
||||
|
||||
private static bool DrawEntry(in AtchEntry defaultEntry, ref AtchEntry entry, bool disabled)
|
||||
{
|
||||
var changes = false;
|
||||
using var dis = ImRaii.Disabled(disabled);
|
||||
if (defaultEntry.Bone.Length == 0)
|
||||
return false;
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(200 * ImUtf8.GlobalScale);
|
||||
if (ImUtf8.InputText("##BoneName"u8, entry.FullSpan, out TerminatedByteString newBone))
|
||||
{
|
||||
entry.SetBoneName(newBone);
|
||||
changes = true;
|
||||
}
|
||||
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "Bone Name"u8);
|
||||
|
||||
ImGui.SetNextItemWidth(200 * ImUtf8.GlobalScale);
|
||||
changes |= ImUtf8.InputScalar("##AtchScale"u8, ref entry.Scale);
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "Scale"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(120 * ImUtf8.GlobalScale);
|
||||
changes |= ImUtf8.InputScalar("##AtchOffsetX"u8, ref entry.OffsetX);
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "Offset X-Coordinate"u8);
|
||||
ImGui.SetNextItemWidth(120 * ImUtf8.GlobalScale);
|
||||
changes |= ImUtf8.InputScalar("##AtchRotationX"u8, ref entry.RotationX);
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "Rotation X-Axis"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(120 * ImUtf8.GlobalScale);
|
||||
changes |= ImUtf8.InputScalar("##AtchOffsetY"u8, ref entry.OffsetY);
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "Offset Y-Coordinate"u8);
|
||||
ImGui.SetNextItemWidth(120 * ImUtf8.GlobalScale);
|
||||
changes |= ImUtf8.InputScalar("##AtchRotationY"u8, ref entry.RotationY);
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "Rotation Y-Axis"u8);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(120 * ImUtf8.GlobalScale);
|
||||
changes |= ImUtf8.InputScalar("##AtchOffsetZ"u8, ref entry.OffsetZ);
|
||||
ImUtf8.HoverTooltip("Offset Z-Coordinate"u8);
|
||||
ImGui.SetNextItemWidth(120 * ImUtf8.GlobalScale);
|
||||
changes |= ImUtf8.InputScalar("##AtchRotationZ"u8, ref entry.RotationZ);
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "Rotation Z-Axis"u8);
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private static bool DrawRace(ref AtchIdentifier identifier, float unscaledWidth = 100)
|
||||
{
|
||||
var ret = Combos.Race("##atchRace", identifier.Race, out var race, unscaledWidth);
|
||||
ImUtf8.HoverTooltip("Model Race"u8);
|
||||
if (ret)
|
||||
identifier = identifier with { GenderRace = Names.CombinedRace(identifier.Gender, race) };
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawGender(ref AtchIdentifier identifier, bool disabled)
|
||||
{
|
||||
var isMale = identifier.Gender is Gender.Male;
|
||||
|
||||
if (!ImUtf8.IconButton(isMale ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus, "Gender"u8, buttonColor: disabled ? 0x000F0000u : 0)
|
||||
|| disabled)
|
||||
return false;
|
||||
|
||||
identifier = identifier with { GenderRace = Names.CombinedRace(isMale ? Gender.Female : Gender.Male, identifier.Race) };
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool DrawPointInput(ref AtchIdentifier identifier, AtchPointCombo combo)
|
||||
{
|
||||
if (!combo.Draw("##AtchPoint", identifier.Type.ToName(), "Attachment Point Type", 160 * ImUtf8.GlobalScale,
|
||||
ImGui.GetTextLineHeightWithSpacing()))
|
||||
return false;
|
||||
|
||||
identifier = identifier with { Type = combo.CurrentSelection };
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool DrawEntryIndexInput(ref AtchIdentifier identifier, AtchPoint currentAtchPoint)
|
||||
{
|
||||
var index = identifier.EntryIndex;
|
||||
ImGui.SetNextItemWidth(40 * ImUtf8.GlobalScale);
|
||||
var ret = ImUtf8.DragScalar("##AtchEntry"u8, ref index, 0, (ushort)(currentAtchPoint.Entries.Length - 1), 0.05f,
|
||||
ImGuiSliderFlags.AlwaysClamp);
|
||||
ImUtf8.HoverTooltip("State Entry Index"u8);
|
||||
if (!ret)
|
||||
return false;
|
||||
|
||||
index = Math.Clamp(index, (ushort)0, (ushort)(currentAtchPoint!.Entries.Length - 1));
|
||||
identifier = identifier with { EntryIndex = index };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -34,7 +35,7 @@ public sealed class EqdpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFil
|
|||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current EQDP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Eqdp));
|
||||
CopyToClipboardButton("Copy all current EQDP manipulations to clipboard."u8, new Lazy<JToken?>(() => MetaDictionary.SerializeTo([], Editor.Eqdp)));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var validRaceCode = CharacterUtilityData.EqdpIdx(Identifier.GenderRace, false) >= 0;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
|
|
@ -34,7 +35,7 @@ public sealed class EqpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
|
|||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current EQP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Eqp));
|
||||
CopyToClipboardButton("Copy all current EQP manipulations to clipboard."u8, new Lazy<JToken?>(() => MetaDictionary.SerializeTo([], Editor.Eqp)));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = !Editor.Contains(Identifier);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -33,7 +34,7 @@ public sealed class EstMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
|
|||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current EST manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Est));
|
||||
CopyToClipboardButton("Copy all current EST manipulations to clipboard."u8, new Lazy<JToken?>(() => MetaDictionary.SerializeTo([], Editor.Est)));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = !Editor.Contains(Identifier);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.Meta;
|
||||
|
|
@ -29,7 +30,7 @@ public sealed class GlobalEqpMetaDrawer(ModMetaEditor editor, MetaFileManager me
|
|||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current global EQP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.GlobalEqp));
|
||||
CopyToClipboardButton("Copy all current global EQP manipulations to clipboard."u8, new Lazy<JToken?>(() => MetaDictionary.SerializeTo([], Editor.GlobalEqp)));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = !Editor.Contains(Identifier);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Penumbra.Meta.Files;
|
|||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow.Meta;
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ public sealed class GmpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
|
|||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current Gmp manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Gmp));
|
||||
CopyToClipboardButton("Copy all current Gmp manipulations to clipboard."u8, new Lazy<JToken?>(() => MetaDictionary.SerializeTo([], Editor.Gmp)));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = !Editor.Contains(Identifier);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
|
|
@ -35,7 +36,7 @@ public sealed class ImcMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
|
|||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current IMC manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Imc));
|
||||
CopyToClipboardButton("Copy all current IMC manipulations to clipboard."u8, new Lazy<JToken?>(() => MetaDictionary.SerializeTo([], Editor.Imc)));
|
||||
ImGui.TableNextColumn();
|
||||
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;
|
||||
|
|
@ -116,7 +117,6 @@ public sealed class ImcMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
|
|||
ImUtf8.TextFramed(identifier.EquipSlot.ToName(), FrameColor);
|
||||
ImUtf8.HoverTooltip("Equip Slot"u8);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static bool DrawEntry(ImcEntry defaultEntry, ref ImcEntry entry, bool addDefault)
|
||||
|
|
@ -161,8 +161,9 @@ public sealed class ImcMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
|
|||
{
|
||||
var (equipSlot, secondaryId) = type switch
|
||||
{
|
||||
ObjectType.Equipment => (identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head, (SecondaryId) 0),
|
||||
ObjectType.DemiHuman => (identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head, identifier.SecondaryId == 0 ? 1 : identifier.SecondaryId),
|
||||
ObjectType.Equipment => (identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head, (SecondaryId)0),
|
||||
ObjectType.DemiHuman => (identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head,
|
||||
identifier.SecondaryId == 0 ? 1 : identifier.SecondaryId),
|
||||
ObjectType.Accessory => (identifier.EquipSlot.IsAccessory() ? identifier.EquipSlot : EquipSlot.Ears, (SecondaryId)0),
|
||||
_ => (EquipSlot.Unknown, identifier.SecondaryId == 0 ? 1 : identifier.SecondaryId),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ namespace Penumbra.UI.AdvancedWindow.Meta;
|
|||
|
||||
public interface IMetaDrawer
|
||||
{
|
||||
public ReadOnlySpan<byte> Label { get; }
|
||||
public int NumColumns { get; }
|
||||
public ReadOnlySpan<byte> Label { get; }
|
||||
public int NumColumns { get; }
|
||||
public float ColumnHeight { get; }
|
||||
public void Draw();
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ public abstract class MetaDrawer<TIdentifier, TEntry>(ModMetaEditor editor, Meta
|
|||
using var id = ImUtf8.PushId((int)Identifier.Type);
|
||||
DrawNew();
|
||||
|
||||
var height = ImUtf8.FrameHeightSpacing;
|
||||
var height = ColumnHeight;
|
||||
var skips = ImGuiClip.GetNecessarySkipsAtPos(height, ImGui.GetCursorPosY());
|
||||
var remainder = ImGuiClip.ClippedTableDraw(Enumerate(), skips, DrawLine, Count);
|
||||
ImGuiClip.DrawEndDummy(remainder, height);
|
||||
|
|
@ -54,6 +55,9 @@ public abstract class MetaDrawer<TIdentifier, TEntry>(ModMetaEditor editor, Meta
|
|||
public abstract ReadOnlySpan<byte> Label { get; }
|
||||
public abstract int NumColumns { get; }
|
||||
|
||||
public virtual float ColumnHeight
|
||||
=> ImUtf8.FrameHeightSpacing;
|
||||
|
||||
protected abstract void DrawNew();
|
||||
protected abstract void Initialize();
|
||||
protected abstract void DrawEntry(TIdentifier identifier, TEntry entry);
|
||||
|
|
@ -138,14 +142,14 @@ public abstract class MetaDrawer<TIdentifier, TEntry>(ModMetaEditor editor, Meta
|
|||
protected void DrawMetaButtons(TIdentifier identifier, TEntry entry)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy this manipulation to clipboard."u8, new JArray { MetaDictionary.Serialize(identifier, entry)! });
|
||||
CopyToClipboardButton("Copy this manipulation to clipboard."u8, new Lazy<JToken?>(() => 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)
|
||||
protected void CopyToClipboardButton(ReadOnlySpan<byte> tooltip, Lazy<JToken?> manipulations)
|
||||
{
|
||||
if (!ImUtf8.IconButton(FontAwesomeIcon.Clipboard, tooltip))
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ public class MetaDrawers(
|
|||
GlobalEqpMetaDrawer globalEqp,
|
||||
GmpMetaDrawer gmp,
|
||||
ImcMetaDrawer imc,
|
||||
RspMetaDrawer rsp) : IService
|
||||
RspMetaDrawer rsp,
|
||||
AtchMetaDrawer atch) : IService
|
||||
{
|
||||
public readonly EqdpMetaDrawer Eqdp = eqdp;
|
||||
public readonly EqpMetaDrawer Eqp = eqp;
|
||||
|
|
@ -19,6 +20,7 @@ public class MetaDrawers(
|
|||
public readonly RspMetaDrawer Rsp = rsp;
|
||||
public readonly ImcMetaDrawer Imc = imc;
|
||||
public readonly GlobalEqpMetaDrawer GlobalEqp = globalEqp;
|
||||
public readonly AtchMetaDrawer Atch = atch;
|
||||
|
||||
public IMetaDrawer? Get(MetaManipulationType type)
|
||||
=> type switch
|
||||
|
|
@ -29,7 +31,8 @@ public class MetaDrawers(
|
|||
MetaManipulationType.Est => Est,
|
||||
MetaManipulationType.Gmp => Gmp,
|
||||
MetaManipulationType.Rsp => Rsp,
|
||||
MetaManipulationType.Atch => Atch,
|
||||
MetaManipulationType.GlobalEqp => GlobalEqp,
|
||||
_ => null,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -33,7 +34,7 @@ public sealed class RspMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
|
|||
protected override void DrawNew()
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
CopyToClipboardButton("Copy all current RSP manipulations to clipboard."u8, MetaDictionary.SerializeTo([], Editor.Rsp));
|
||||
CopyToClipboardButton("Copy all current RSP manipulations to clipboard."u8, new Lazy<JToken?>(() => MetaDictionary.SerializeTo([], Editor.Rsp)));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var canAdd = !Editor.Contains(Identifier);
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ public partial class ModEditWindow
|
|||
DrawEditHeader(MetaManipulationType.Est);
|
||||
DrawEditHeader(MetaManipulationType.Gmp);
|
||||
DrawEditHeader(MetaManipulationType.Rsp);
|
||||
DrawEditHeader(MetaManipulationType.Atch);
|
||||
DrawEditHeader(MetaManipulationType.GlobalEqp);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue