mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Rework API, use Collection ID in crash handler, use collection GUIDs in more places.
This commit is contained in:
parent
793ed4f0a7
commit
ba8999914f
88 changed files with 4193 additions and 3930 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit 4673e93f5165108a7f5b91236406d527f16384a5
|
||||
Subproject commit 9599c806877e2972f964dfa68e5207cf3a8f2b84
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 8787efc8fc897dfbb4515ebbabbcd5e6f54d1b42
|
||||
Subproject commit e5c8f5446879e2e0e541eb4d8fee15e98b1885bc
|
||||
|
|
@ -24,7 +24,7 @@ public record struct VfxFuncInvokedEntry(
|
|||
string InvocationType,
|
||||
string CharacterName,
|
||||
string CharacterAddress,
|
||||
string CollectionName) : ICrashDataEntry;
|
||||
Guid CollectionId) : ICrashDataEntry;
|
||||
|
||||
/// <summary> Only expose the write interface for the buffer. </summary>
|
||||
public interface IAnimationInvocationBufferWriter
|
||||
|
|
@ -32,19 +32,19 @@ public interface IAnimationInvocationBufferWriter
|
|||
/// <summary> Write a line into the buffer with the given data. </summary>
|
||||
/// <param name="characterAddress"> The address of the related character, if known. </param>
|
||||
/// <param name="characterName"> The name of the related character, anonymized or relying on index if unavailable, if known. </param>
|
||||
/// <param name="collectionName"> The name of the associated collection. Not anonymized. </param>
|
||||
/// <param name="collectionId"> The GUID of the associated collection. </param>
|
||||
/// <param name="type"> The type of VFX func called. </param>
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName, AnimationInvocationType type);
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId, AnimationInvocationType type);
|
||||
}
|
||||
|
||||
internal sealed class AnimationInvocationBuffer : MemoryMappedBuffer, IAnimationInvocationBufferWriter, IBufferReader
|
||||
{
|
||||
private const int _version = 1;
|
||||
private const int _lineCount = 64;
|
||||
private const int _lineCapacity = 256;
|
||||
private const int _lineCapacity = 128;
|
||||
private const string _name = "Penumbra.AnimationInvocation";
|
||||
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName, AnimationInvocationType type)
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId, AnimationInvocationType type)
|
||||
{
|
||||
var accessor = GetCurrentLineLocking();
|
||||
lock (accessor)
|
||||
|
|
@ -53,10 +53,10 @@ internal sealed class AnimationInvocationBuffer : MemoryMappedBuffer, IAnimation
|
|||
accessor.Write(8, Environment.CurrentManagedThreadId);
|
||||
accessor.Write(12, (int)type);
|
||||
accessor.Write(16, characterAddress);
|
||||
var span = GetSpan(accessor, 24, 104);
|
||||
var span = GetSpan(accessor, 24, 16);
|
||||
collectionId.TryWriteBytes(span);
|
||||
span = GetSpan(accessor, 40);
|
||||
WriteSpan(characterName, span);
|
||||
span = GetSpan(accessor, 128);
|
||||
WriteString(collectionName, span);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,13 +68,13 @@ internal sealed class AnimationInvocationBuffer : MemoryMappedBuffer, IAnimation
|
|||
var lineCount = (int)CurrentLineCount;
|
||||
for (var i = lineCount - 1; i >= 0; --i)
|
||||
{
|
||||
var line = GetLine(i);
|
||||
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
|
||||
var thread = BitConverter.ToInt32(line[8..]);
|
||||
var type = (AnimationInvocationType)BitConverter.ToInt32(line[12..]);
|
||||
var address = BitConverter.ToUInt64(line[16..]);
|
||||
var characterName = ReadString(line[24..]);
|
||||
var collectionName = ReadString(line[128..]);
|
||||
var line = GetLine(i);
|
||||
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
|
||||
var thread = BitConverter.ToInt32(line[8..]);
|
||||
var type = (AnimationInvocationType)BitConverter.ToInt32(line[12..]);
|
||||
var address = BitConverter.ToUInt64(line[16..]);
|
||||
var collectionId = new Guid(line[24..40]);
|
||||
var characterName = ReadString(line[40..]);
|
||||
yield return new JsonObject()
|
||||
{
|
||||
[nameof(VfxFuncInvokedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
|
||||
|
|
@ -83,7 +83,7 @@ internal sealed class AnimationInvocationBuffer : MemoryMappedBuffer, IAnimation
|
|||
[nameof(VfxFuncInvokedEntry.InvocationType)] = ToName(type),
|
||||
[nameof(VfxFuncInvokedEntry.CharacterName)] = characterName,
|
||||
[nameof(VfxFuncInvokedEntry.CharacterAddress)] = address.ToString("X"),
|
||||
[nameof(VfxFuncInvokedEntry.CollectionName)] = collectionName,
|
||||
[nameof(VfxFuncInvokedEntry.CollectionId)] = collectionId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ public interface ICharacterBaseBufferWriter
|
|||
/// <summary> Write a line into the buffer with the given data. </summary>
|
||||
/// <param name="characterAddress"> The address of the related character, if known. </param>
|
||||
/// <param name="characterName"> The name of the related character, anonymized or relying on index if unavailable, if known. </param>
|
||||
/// <param name="collectionName"> The name of the associated collection. Not anonymized. </param>
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName);
|
||||
/// <param name="collectionId"> The GUID of the associated collection. </param>
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId);
|
||||
}
|
||||
|
||||
/// <summary> The full crash entry for a loaded character base. </summary>
|
||||
|
|
@ -19,27 +19,27 @@ public record struct CharacterLoadedEntry(
|
|||
int ThreadId,
|
||||
string CharacterName,
|
||||
string CharacterAddress,
|
||||
string CollectionName) : ICrashDataEntry;
|
||||
Guid CollectionId) : ICrashDataEntry;
|
||||
|
||||
internal sealed class CharacterBaseBuffer : MemoryMappedBuffer, ICharacterBaseBufferWriter, IBufferReader
|
||||
{
|
||||
private const int _version = 1;
|
||||
private const int _lineCount = 10;
|
||||
private const int _lineCapacity = 256;
|
||||
private const string _name = "Penumbra.CharacterBase";
|
||||
private const int _version = 1;
|
||||
private const int _lineCount = 10;
|
||||
private const int _lineCapacity = 128;
|
||||
private const string _name = "Penumbra.CharacterBase";
|
||||
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName)
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId)
|
||||
{
|
||||
var accessor = GetCurrentLineLocking();
|
||||
lock (accessor)
|
||||
{
|
||||
accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
|
||||
accessor.Write(8, Environment.CurrentManagedThreadId);
|
||||
accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
|
||||
accessor.Write(8, Environment.CurrentManagedThreadId);
|
||||
accessor.Write(12, characterAddress);
|
||||
var span = GetSpan(accessor, 20, 108);
|
||||
var span = GetSpan(accessor, 20, 16);
|
||||
collectionId.TryWriteBytes(span);
|
||||
span = GetSpan(accessor, 36);
|
||||
WriteSpan(characterName, span);
|
||||
span = GetSpan(accessor, 128);
|
||||
WriteString(collectionName, span);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,20 +48,20 @@ internal sealed class CharacterBaseBuffer : MemoryMappedBuffer, ICharacterBaseBu
|
|||
var lineCount = (int)CurrentLineCount;
|
||||
for (var i = lineCount - 1; i >= 0; --i)
|
||||
{
|
||||
var line = GetLine(i);
|
||||
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
|
||||
var thread = BitConverter.ToInt32(line[8..]);
|
||||
var address = BitConverter.ToUInt64(line[12..]);
|
||||
var characterName = ReadString(line[20..]);
|
||||
var collectionName = ReadString(line[128..]);
|
||||
var line = GetLine(i);
|
||||
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
|
||||
var thread = BitConverter.ToInt32(line[8..]);
|
||||
var address = BitConverter.ToUInt64(line[12..]);
|
||||
var collectionId = new Guid(line[20..36]);
|
||||
var characterName = ReadString(line[36..]);
|
||||
yield return new JsonObject
|
||||
{
|
||||
[nameof(CharacterLoadedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
|
||||
[nameof(CharacterLoadedEntry.Timestamp)] = timestamp,
|
||||
[nameof(CharacterLoadedEntry.ThreadId)] = thread,
|
||||
[nameof(CharacterLoadedEntry.CharacterName)] = characterName,
|
||||
[nameof(CharacterLoadedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
|
||||
[nameof(CharacterLoadedEntry.Timestamp)] = timestamp,
|
||||
[nameof(CharacterLoadedEntry.ThreadId)] = thread,
|
||||
[nameof(CharacterLoadedEntry.CharacterName)] = characterName,
|
||||
[nameof(CharacterLoadedEntry.CharacterAddress)] = address.ToString("X"),
|
||||
[nameof(CharacterLoadedEntry.CollectionName)] = collectionName,
|
||||
[nameof(CharacterLoadedEntry.CollectionId)] = collectionId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ public interface IModdedFileBufferWriter
|
|||
/// <summary> Write a line into the buffer with the given data. </summary>
|
||||
/// <param name="characterAddress"> The address of the related character, if known. </param>
|
||||
/// <param name="characterName"> The name of the related character, anonymized or relying on index if unavailable, if known. </param>
|
||||
/// <param name="collectionName"> The name of the associated collection. Not anonymized. </param>
|
||||
/// <param name="collectionId"> The GUID of the associated collection. </param>
|
||||
/// <param name="requestedFileName"> The file name as requested by the game. </param>
|
||||
/// <param name="actualFileName"> The actual modded file name loaded. </param>
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName, ReadOnlySpan<byte> requestedFileName,
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId, ReadOnlySpan<byte> requestedFileName,
|
||||
ReadOnlySpan<byte> actualFileName);
|
||||
}
|
||||
|
||||
|
|
@ -22,33 +22,33 @@ public record struct ModdedFileLoadedEntry(
|
|||
int ThreadId,
|
||||
string CharacterName,
|
||||
string CharacterAddress,
|
||||
string CollectionName,
|
||||
Guid CollectionId,
|
||||
string RequestedFileName,
|
||||
string ActualFileName) : ICrashDataEntry;
|
||||
|
||||
internal sealed class ModdedFileBuffer : MemoryMappedBuffer, IModdedFileBufferWriter, IBufferReader
|
||||
{
|
||||
private const int _version = 1;
|
||||
private const int _lineCount = 128;
|
||||
private const int _lineCapacity = 1024;
|
||||
private const string _name = "Penumbra.ModdedFile";
|
||||
private const int _version = 1;
|
||||
private const int _lineCount = 128;
|
||||
private const int _lineCapacity = 1024;
|
||||
private const string _name = "Penumbra.ModdedFile";
|
||||
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, string collectionName, ReadOnlySpan<byte> requestedFileName,
|
||||
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId, ReadOnlySpan<byte> requestedFileName,
|
||||
ReadOnlySpan<byte> actualFileName)
|
||||
{
|
||||
var accessor = GetCurrentLineLocking();
|
||||
lock (accessor)
|
||||
{
|
||||
accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
|
||||
accessor.Write(8, Environment.CurrentManagedThreadId);
|
||||
accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
|
||||
accessor.Write(8, Environment.CurrentManagedThreadId);
|
||||
accessor.Write(12, characterAddress);
|
||||
var span = GetSpan(accessor, 20, 80);
|
||||
var span = GetSpan(accessor, 20, 16);
|
||||
collectionId.TryWriteBytes(span);
|
||||
span = GetSpan(accessor, 36, 80);
|
||||
WriteSpan(characterName, span);
|
||||
span = GetSpan(accessor, 92, 80);
|
||||
WriteString(collectionName, span);
|
||||
span = GetSpan(accessor, 172, 260);
|
||||
span = GetSpan(accessor, 116, 260);
|
||||
WriteSpan(requestedFileName, span);
|
||||
span = GetSpan(accessor, 432);
|
||||
span = GetSpan(accessor, 376);
|
||||
WriteSpan(actualFileName, span);
|
||||
}
|
||||
}
|
||||
|
|
@ -61,24 +61,24 @@ internal sealed class ModdedFileBuffer : MemoryMappedBuffer, IModdedFileBufferWr
|
|||
var lineCount = (int)CurrentLineCount;
|
||||
for (var i = lineCount - 1; i >= 0; --i)
|
||||
{
|
||||
var line = GetLine(i);
|
||||
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
|
||||
var thread = BitConverter.ToInt32(line[8..]);
|
||||
var address = BitConverter.ToUInt64(line[12..]);
|
||||
var characterName = ReadString(line[20..]);
|
||||
var collectionName = ReadString(line[92..]);
|
||||
var requestedFileName = ReadString(line[172..]);
|
||||
var actualFileName = ReadString(line[432..]);
|
||||
var line = GetLine(i);
|
||||
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
|
||||
var thread = BitConverter.ToInt32(line[8..]);
|
||||
var address = BitConverter.ToUInt64(line[12..]);
|
||||
var collectionId = new Guid(line[20..36]);
|
||||
var characterName = ReadString(line[36..]);
|
||||
var requestedFileName = ReadString(line[116..]);
|
||||
var actualFileName = ReadString(line[376..]);
|
||||
yield return new JsonObject()
|
||||
{
|
||||
[nameof(ModdedFileLoadedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
|
||||
[nameof(ModdedFileLoadedEntry.Timestamp)] = timestamp,
|
||||
[nameof(ModdedFileLoadedEntry.ThreadId)] = thread,
|
||||
[nameof(ModdedFileLoadedEntry.CharacterName)] = characterName,
|
||||
[nameof(ModdedFileLoadedEntry.CharacterAddress)] = address.ToString("X"),
|
||||
[nameof(ModdedFileLoadedEntry.CollectionName)] = collectionName,
|
||||
[nameof(ModdedFileLoadedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
|
||||
[nameof(ModdedFileLoadedEntry.Timestamp)] = timestamp,
|
||||
[nameof(ModdedFileLoadedEntry.ThreadId)] = thread,
|
||||
[nameof(ModdedFileLoadedEntry.CharacterName)] = characterName,
|
||||
[nameof(ModdedFileLoadedEntry.CharacterAddress)] = address.ToString("X"),
|
||||
[nameof(ModdedFileLoadedEntry.CollectionId)] = collectionId,
|
||||
[nameof(ModdedFileLoadedEntry.RequestedFileName)] = requestedFileName,
|
||||
[nameof(ModdedFileLoadedEntry.ActualFileName)] = actualFileName,
|
||||
[nameof(ModdedFileLoadedEntry.ActualFileName)] = actualFileName,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 45679aa32cc37b59f5eeb7cf6bf5a3ea36c626e0
|
||||
Subproject commit 60222d79420662fb8e9960a66e262a380fcaf186
|
||||
76
Penumbra/Api/Api/ApiHelpers.cs
Normal file
76
Penumbra/Api/Api/ApiHelpers.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
using OtterGui.Log;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class ApiHelpers(
|
||||
CollectionManager collectionManager,
|
||||
ObjectManager objects,
|
||||
CollectionResolver collectionResolver,
|
||||
ActorManager actors) : IApiService
|
||||
{
|
||||
/// <summary> Return the associated identifier for an object given by its index. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal ActorIdentifier AssociatedIdentifier(int gameObjectIdx)
|
||||
{
|
||||
if (gameObjectIdx < 0 || gameObjectIdx >= objects.TotalCount)
|
||||
return ActorIdentifier.Invalid;
|
||||
|
||||
var ptr = objects[gameObjectIdx];
|
||||
return actors.FromObject(ptr, out _, false, true, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the collection associated to a current game object. If it does not exist, return the default collection.
|
||||
/// If the index is invalid, returns false and the default collection.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal unsafe bool AssociatedCollection(int gameObjectIdx, out ModCollection collection)
|
||||
{
|
||||
collection = collectionManager.Active.Default;
|
||||
if (gameObjectIdx < 0 || gameObjectIdx >= objects.TotalCount)
|
||||
return false;
|
||||
|
||||
var ptr = objects[gameObjectIdx];
|
||||
var data = collectionResolver.IdentifyCollection(ptr.AsObject, false);
|
||||
if (data.Valid)
|
||||
collection = data.ModCollection;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static PenumbraApiEc Return(PenumbraApiEc ec, LazyString args, [CallerMemberName] string name = "Unknown")
|
||||
{
|
||||
Penumbra.Log.Debug(
|
||||
$"[{name}] Called with {args}, returned {ec}.");
|
||||
return ec;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static LazyString Args(params object[] arguments)
|
||||
{
|
||||
if (arguments.Length == 0)
|
||||
return new LazyString(() => "no arguments");
|
||||
|
||||
return new LazyString(() =>
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (var i = 0; i < arguments.Length / 2; ++i)
|
||||
{
|
||||
sb.Append(arguments[2 * i]);
|
||||
sb.Append(" = ");
|
||||
sb.Append(arguments[2 * i + 1]);
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
return sb.ToString(0, sb.Length - 2);
|
||||
});
|
||||
}
|
||||
}
|
||||
141
Penumbra/Api/Api/CollectionApi.cs
Normal file
141
Penumbra/Api/Api/CollectionApi.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : IPenumbraApiCollection, IApiService
|
||||
{
|
||||
public Dictionary<Guid, string> GetCollections()
|
||||
=> collections.Storage.ToDictionary(c => c.Id, c => c.Name);
|
||||
|
||||
public Dictionary<string, object?> GetChangedItemsForCollection(Guid collectionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!collections.Storage.ById(collectionId, out var collection))
|
||||
collection = ModCollection.Empty;
|
||||
|
||||
if (collection.HasCache)
|
||||
return collection.ChangedItems.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Item2);
|
||||
|
||||
Penumbra.Log.Warning($"Collection {collectionId} does not exist or is not loaded.");
|
||||
return [];
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not obtain Changed Items for {collectionId}:\n{e}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public (Guid Id, string Name)? GetCollection(ApiCollectionType type)
|
||||
{
|
||||
if (!Enum.IsDefined(type))
|
||||
return null;
|
||||
|
||||
var collection = collections.Active.ByType((CollectionType)type);
|
||||
return collection == null ? null : (collection.Id, collection.Name);
|
||||
}
|
||||
|
||||
internal (Guid Id, string Name)? GetCollection(byte type)
|
||||
=> GetCollection((ApiCollectionType)type);
|
||||
|
||||
public (bool ObjectValid, bool IndividualSet, (Guid Id, string Name) EffectiveCollection) GetCollectionForObject(int gameObjectIdx)
|
||||
{
|
||||
var id = helpers.AssociatedIdentifier(gameObjectIdx);
|
||||
if (!id.IsValid)
|
||||
return (false, false, (collections.Active.Default.Id, collections.Active.Default.Name));
|
||||
|
||||
if (collections.Active.Individuals.TryGetValue(id, out var collection))
|
||||
return (true, true, (collection.Id, collection.Name));
|
||||
|
||||
helpers.AssociatedCollection(gameObjectIdx, out collection);
|
||||
return (true, false, (collection.Id, collection.Name));
|
||||
}
|
||||
|
||||
public Guid[] GetCollectionByName(string name)
|
||||
=> collections.Storage.Where(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase)).Select(c => c.Id).ToArray();
|
||||
|
||||
public (PenumbraApiEc, (Guid Id, string Name)? OldCollection) SetCollection(ApiCollectionType type, Guid? collectionId,
|
||||
bool allowCreateNew, bool allowDelete)
|
||||
{
|
||||
if (!Enum.IsDefined(type))
|
||||
return (PenumbraApiEc.InvalidArgument, null);
|
||||
|
||||
var oldCollection = collections.Active.ByType((CollectionType)type);
|
||||
var old = oldCollection != null ? (oldCollection.Id, oldCollection.Name) : new ValueTuple<Guid, string>?();
|
||||
if (collectionId == null)
|
||||
{
|
||||
if (old == null)
|
||||
return (PenumbraApiEc.NothingChanged, old);
|
||||
|
||||
if (!allowDelete || type is ApiCollectionType.Current or ApiCollectionType.Default or ApiCollectionType.Interface)
|
||||
return (PenumbraApiEc.AssignmentDeletionDisallowed, old);
|
||||
|
||||
collections.Active.RemoveSpecialCollection((CollectionType)type);
|
||||
return (PenumbraApiEc.Success, old);
|
||||
}
|
||||
|
||||
if (!collections.Storage.ById(collectionId.Value, out var collection))
|
||||
return (PenumbraApiEc.CollectionMissing, old);
|
||||
|
||||
if (old == null)
|
||||
{
|
||||
if (!allowCreateNew)
|
||||
return (PenumbraApiEc.AssignmentCreationDisallowed, old);
|
||||
|
||||
collections.Active.CreateSpecialCollection((CollectionType)type);
|
||||
}
|
||||
else if (old.Value.Item1 == collection.Id)
|
||||
{
|
||||
return (PenumbraApiEc.NothingChanged, old);
|
||||
}
|
||||
|
||||
collections.Active.SetCollection(collection, (CollectionType)type);
|
||||
return (PenumbraApiEc.Success, old);
|
||||
}
|
||||
|
||||
public (PenumbraApiEc, (Guid Id, string Name)? OldCollection) SetCollectionForObject(int gameObjectIdx, Guid? collectionId,
|
||||
bool allowCreateNew, bool allowDelete)
|
||||
{
|
||||
var id = helpers.AssociatedIdentifier(gameObjectIdx);
|
||||
if (!id.IsValid)
|
||||
return (PenumbraApiEc.InvalidIdentifier, (collections.Active.Default.Id, collections.Active.Default.Name));
|
||||
|
||||
var oldCollection = collections.Active.Individuals.TryGetValue(id, out var c) ? c : null;
|
||||
var old = oldCollection != null ? (oldCollection.Id, oldCollection.Name) : new ValueTuple<Guid, string>?();
|
||||
if (collectionId == null)
|
||||
{
|
||||
if (old == null)
|
||||
return (PenumbraApiEc.NothingChanged, old);
|
||||
|
||||
if (!allowDelete)
|
||||
return (PenumbraApiEc.AssignmentDeletionDisallowed, old);
|
||||
|
||||
var idx = collections.Active.Individuals.Index(id);
|
||||
collections.Active.RemoveIndividualCollection(idx);
|
||||
return (PenumbraApiEc.Success, old);
|
||||
}
|
||||
|
||||
if (!collections.Storage.ById(collectionId.Value, out var collection))
|
||||
return (PenumbraApiEc.CollectionMissing, old);
|
||||
|
||||
if (old == null)
|
||||
{
|
||||
if (!allowCreateNew)
|
||||
return (PenumbraApiEc.AssignmentCreationDisallowed, old);
|
||||
|
||||
var ids = collections.Active.Individuals.GetGroup(id);
|
||||
collections.Active.CreateIndividualCollection(ids);
|
||||
}
|
||||
else if (old.Value.Item1 == collection.Id)
|
||||
{
|
||||
return (PenumbraApiEc.NothingChanged, old);
|
||||
}
|
||||
|
||||
collections.Active.SetCollection(collection, CollectionType.Individual, collections.Active.Individuals.Index(id));
|
||||
return (PenumbraApiEc.Success, old);
|
||||
}
|
||||
}
|
||||
40
Penumbra/Api/Api/EditingApi.cs
Normal file
40
Penumbra/Api/Api/EditingApi.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using OtterGui.Services;
|
||||
using Penumbra.Import.Textures;
|
||||
using TextureType = Penumbra.Api.Enums.TextureType;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class EditingApi(TextureManager textureManager) : IPenumbraApiEditing, IApiService
|
||||
{
|
||||
public Task ConvertTextureFile(string inputFile, string outputFile, TextureType textureType, bool mipMaps)
|
||||
=> textureType switch
|
||||
{
|
||||
TextureType.Png => textureManager.SavePng(inputFile, outputFile),
|
||||
TextureType.AsIsTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, inputFile, outputFile),
|
||||
TextureType.AsIsDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, inputFile, outputFile),
|
||||
TextureType.RgbaTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, inputFile, outputFile),
|
||||
TextureType.RgbaDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, false, inputFile, outputFile),
|
||||
TextureType.Bc3Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, true, inputFile, outputFile),
|
||||
TextureType.Bc3Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, false, inputFile, outputFile),
|
||||
TextureType.Bc7Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, true, inputFile, outputFile),
|
||||
TextureType.Bc7Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, false, inputFile, outputFile),
|
||||
_ => Task.FromException(new Exception($"Invalid input value {textureType}.")),
|
||||
};
|
||||
|
||||
// @formatter:off
|
||||
public Task ConvertTextureData(byte[] rgbaData, int width, string outputFile, TextureType textureType, bool mipMaps)
|
||||
=> textureType switch
|
||||
{
|
||||
TextureType.Png => textureManager.SavePng(new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.AsIsTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.AsIsDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.RgbaTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.RgbaDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.Bc3Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.Bc3Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.Bc7Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
TextureType.Bc7Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
|
||||
_ => Task.FromException(new Exception($"Invalid input value {textureType}.")),
|
||||
};
|
||||
// @formatter:on
|
||||
}
|
||||
82
Penumbra/Api/Api/GameStateApi.cs
Normal file
82
Penumbra/Api/Api/GameStateApi.cs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.Interop.ResourceLoading;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class GameStateApi : IPenumbraApiGameState, IApiService, IDisposable
|
||||
{
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly CutsceneService _cutsceneService;
|
||||
private readonly ResourceLoader _resourceLoader;
|
||||
|
||||
public unsafe GameStateApi(CommunicatorService communicator, CollectionResolver collectionResolver, CutsceneService cutsceneService,
|
||||
ResourceLoader resourceLoader)
|
||||
{
|
||||
_communicator = communicator;
|
||||
_collectionResolver = collectionResolver;
|
||||
_cutsceneService = cutsceneService;
|
||||
_resourceLoader = resourceLoader;
|
||||
_resourceLoader.ResourceLoaded += OnResourceLoaded;
|
||||
_communicator.CreatedCharacterBase.Subscribe(OnCreatedCharacterBase, Communication.CreatedCharacterBase.Priority.Api);
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
_resourceLoader.ResourceLoaded -= OnResourceLoaded;
|
||||
_communicator.CreatedCharacterBase.Unsubscribe(OnCreatedCharacterBase);
|
||||
}
|
||||
|
||||
public event CreatedCharacterBaseDelegate? CreatedCharacterBase;
|
||||
public event GameObjectResourceResolvedDelegate? GameObjectResourceResolved;
|
||||
|
||||
public event CreatingCharacterBaseDelegate? CreatingCharacterBase
|
||||
{
|
||||
add
|
||||
{
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
_communicator.CreatingCharacterBase.Subscribe(new Action<nint, Guid, nint, nint, nint>(value),
|
||||
Communication.CreatingCharacterBase.Priority.Api);
|
||||
}
|
||||
remove
|
||||
{
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
_communicator.CreatingCharacterBase.Unsubscribe(new Action<nint, Guid, nint, nint, nint>(value));
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe (nint GameObject, (Guid Id, string Name) Collection) GetDrawObjectInfo(nint drawObject)
|
||||
{
|
||||
var data = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
||||
return (data.AssociatedGameObject, (data.ModCollection.Id, data.ModCollection.Name));
|
||||
}
|
||||
|
||||
public int GetCutsceneParentIndex(int actorIdx)
|
||||
=> _cutsceneService.GetParentIndex(actorIdx);
|
||||
|
||||
public PenumbraApiEc SetCutsceneParentIndex(int copyIdx, int newParentIdx)
|
||||
=> _cutsceneService.SetParentIndex(copyIdx, newParentIdx)
|
||||
? PenumbraApiEc.Success
|
||||
: PenumbraApiEc.InvalidArgument;
|
||||
|
||||
private unsafe void OnResourceLoaded(ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, ResolveData resolveData)
|
||||
{
|
||||
if (resolveData.AssociatedGameObject != nint.Zero)
|
||||
GameObjectResourceResolved?.Invoke(resolveData.AssociatedGameObject, originalPath.ToString(),
|
||||
manipulatedPath?.ToString() ?? originalPath.ToString());
|
||||
}
|
||||
|
||||
private void OnCreatedCharacterBase(nint gameObject, ModCollection collection, nint drawObject)
|
||||
=> CreatedCharacterBase?.Invoke(gameObject, collection.Id, drawObject);
|
||||
}
|
||||
23
Penumbra/Api/Api/MetaApi.cs
Normal file
23
Penumbra/Api/Api/MetaApi.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using OtterGui;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class MetaApi(CollectionResolver collectionResolver, ApiHelpers helpers) : IPenumbraApiMeta, IApiService
|
||||
{
|
||||
public string GetPlayerMetaManipulations()
|
||||
{
|
||||
var collection = collectionResolver.PlayerCollection();
|
||||
var set = collection.MetaCache?.Manipulations.ToArray() ?? [];
|
||||
return Functions.ToCompressedBase64(set, MetaManipulation.CurrentVersion);
|
||||
}
|
||||
|
||||
public string GetMetaManipulations(int gameObjectIdx)
|
||||
{
|
||||
helpers.AssociatedCollection(gameObjectIdx, out var collection);
|
||||
var set = collection.MetaCache?.Manipulations.ToArray() ?? [];
|
||||
return Functions.ToCompressedBase64(set, MetaManipulation.CurrentVersion);
|
||||
}
|
||||
}
|
||||
282
Penumbra/Api/Api/ModSettingsApi.cs
Normal file
282
Penumbra/Api/Api/ModSettingsApi.cs
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
using OtterGui;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Mods.Subclasses;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
|
||||
{
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly ModManager _modManager;
|
||||
private readonly CollectionManager _collectionManager;
|
||||
private readonly CollectionEditor _collectionEditor;
|
||||
private readonly CommunicatorService _communicator;
|
||||
|
||||
public ModSettingsApi(CollectionResolver collectionResolver,
|
||||
ModManager modManager,
|
||||
CollectionManager collectionManager,
|
||||
CollectionEditor collectionEditor,
|
||||
CommunicatorService communicator)
|
||||
{
|
||||
_collectionResolver = collectionResolver;
|
||||
_modManager = modManager;
|
||||
_collectionManager = collectionManager;
|
||||
_collectionEditor = collectionEditor;
|
||||
_communicator = communicator;
|
||||
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ApiModSettings);
|
||||
_communicator.ModSettingChanged.Subscribe(OnModSettingChange, Communication.ModSettingChanged.Priority.Api);
|
||||
_communicator.ModOptionChanged.Subscribe(OnModOptionEdited, ModOptionChanged.Priority.Api);
|
||||
_communicator.ModFileChanged.Subscribe(OnModFileChanged, ModFileChanged.Priority.Api);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_communicator.ModPathChanged.Unsubscribe(OnModPathChange);
|
||||
_communicator.ModSettingChanged.Unsubscribe(OnModSettingChange);
|
||||
_communicator.ModOptionChanged.Unsubscribe(OnModOptionEdited);
|
||||
_communicator.ModFileChanged.Unsubscribe(OnModFileChanged);
|
||||
}
|
||||
|
||||
public event ModSettingChangedDelegate? ModSettingChanged;
|
||||
|
||||
public AvailableModSettings? GetAvailableModSettings(string modDirectory, string modName)
|
||||
{
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
return null;
|
||||
|
||||
var dict = new Dictionary<string, (string[], int)>(mod.Groups.Count);
|
||||
foreach (var g in mod.Groups)
|
||||
dict.Add(g.Name, (g.Select(o => o.Name).ToArray(), (int)g.Type));
|
||||
return new AvailableModSettings(dict);
|
||||
}
|
||||
|
||||
public Dictionary<string, (string[], int)>? GetAvailableModSettingsBase(string modDirectory, string modName)
|
||||
=> _modManager.TryGetMod(modDirectory, modName, out var mod)
|
||||
? mod.Groups.ToDictionary(g => g.Name, g => (g.Select(o => o.Name).ToArray(), (int)g.Type))
|
||||
: null;
|
||||
|
||||
public (PenumbraApiEc, (bool, int, Dictionary<string, List<string>>, bool)?) GetCurrentModSettings(Guid collectionId, string modDirectory,
|
||||
string modName, bool ignoreInheritance)
|
||||
{
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
return (PenumbraApiEc.ModMissing, null);
|
||||
|
||||
if (!_collectionManager.Storage.ById(collectionId, out var collection))
|
||||
return (PenumbraApiEc.CollectionMissing, null);
|
||||
|
||||
var settings = collection.Id == Guid.Empty
|
||||
? null
|
||||
: ignoreInheritance
|
||||
? collection.Settings[mod.Index]
|
||||
: collection[mod.Index].Settings;
|
||||
if (settings == null)
|
||||
return (PenumbraApiEc.Success, null);
|
||||
|
||||
var (enabled, priority, dict) = settings.ConvertToShareable(mod);
|
||||
return (PenumbraApiEc.Success,
|
||||
(enabled, priority.Value, dict, collection.Settings[mod.Index] == null));
|
||||
}
|
||||
|
||||
public PenumbraApiEc TryInheritMod(Guid collectionId, string modDirectory, string modName, bool inherit)
|
||||
{
|
||||
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Inherit",
|
||||
inherit.ToString());
|
||||
|
||||
if (collectionId == Guid.Empty)
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
|
||||
|
||||
if (!_collectionManager.Storage.ById(collectionId, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
|
||||
|
||||
var ret = _collectionEditor.SetModInheritance(collection, mod, inherit)
|
||||
? PenumbraApiEc.Success
|
||||
: PenumbraApiEc.NothingChanged;
|
||||
return ApiHelpers.Return(ret, args);
|
||||
}
|
||||
|
||||
public PenumbraApiEc TrySetMod(Guid collectionId, string modDirectory, string modName, bool enabled)
|
||||
{
|
||||
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Enabled", enabled);
|
||||
if (!_collectionManager.Storage.ById(collectionId, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
|
||||
|
||||
var ret = _collectionEditor.SetModState(collection, mod, enabled)
|
||||
? PenumbraApiEc.Success
|
||||
: PenumbraApiEc.NothingChanged;
|
||||
return ApiHelpers.Return(ret, args);
|
||||
}
|
||||
|
||||
public PenumbraApiEc TrySetModPriority(Guid collectionId, string modDirectory, string modName, int priority)
|
||||
{
|
||||
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Priority", priority);
|
||||
|
||||
if (!_collectionManager.Storage.ById(collectionId, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
|
||||
|
||||
var ret = _collectionEditor.SetModPriority(collection, mod, new ModPriority(priority))
|
||||
? PenumbraApiEc.Success
|
||||
: PenumbraApiEc.NothingChanged;
|
||||
return ApiHelpers.Return(ret, args);
|
||||
}
|
||||
|
||||
public PenumbraApiEc TrySetModSetting(Guid collectionId, string modDirectory, string modName, string optionGroupName, string optionName)
|
||||
{
|
||||
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName",
|
||||
optionGroupName, "OptionName", optionName);
|
||||
|
||||
if (!_collectionManager.Storage.ById(collectionId, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
|
||||
|
||||
var groupIdx = mod.Groups.IndexOf(g => g.Name == optionGroupName);
|
||||
if (groupIdx < 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.OptionGroupMissing, args);
|
||||
|
||||
var optionIdx = mod.Groups[groupIdx].IndexOf(o => o.Name == optionName);
|
||||
if (optionIdx < 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args);
|
||||
|
||||
var setting = mod.Groups[groupIdx] switch
|
||||
{
|
||||
MultiModGroup => Setting.Multi(optionIdx),
|
||||
SingleModGroup => Setting.Single(optionIdx),
|
||||
_ => Setting.Zero,
|
||||
};
|
||||
var ret = _collectionEditor.SetModSetting(collection, mod, groupIdx, setting)
|
||||
? PenumbraApiEc.Success
|
||||
: PenumbraApiEc.NothingChanged;
|
||||
return ApiHelpers.Return(ret, args);
|
||||
}
|
||||
|
||||
public PenumbraApiEc TrySetModSettings(Guid collectionId, string modDirectory, string modName, string optionGroupName,
|
||||
IReadOnlyList<string> optionNames)
|
||||
{
|
||||
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName",
|
||||
optionGroupName, "#optionNames", optionNames.Count);
|
||||
|
||||
if (!_collectionManager.Storage.ById(collectionId, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
|
||||
|
||||
var groupIdx = mod.Groups.IndexOf(g => g.Name == optionGroupName);
|
||||
if (groupIdx < 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.OptionGroupMissing, args);
|
||||
|
||||
var setting = Setting.Zero;
|
||||
switch (mod.Groups[groupIdx])
|
||||
{
|
||||
case SingleModGroup single:
|
||||
{
|
||||
var optionIdx = optionNames.Count == 0 ? -1 : single.IndexOf(o => o.Name == optionNames[^1]);
|
||||
if (optionIdx < 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args);
|
||||
|
||||
setting = Setting.Single(optionIdx);
|
||||
break;
|
||||
}
|
||||
case MultiModGroup multi:
|
||||
{
|
||||
foreach (var name in optionNames)
|
||||
{
|
||||
var optionIdx = multi.IndexOf(o => o.Name == name);
|
||||
if (optionIdx < 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args);
|
||||
|
||||
setting |= Setting.Multi(optionIdx);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var ret = _collectionEditor.SetModSetting(collection, mod, groupIdx, setting)
|
||||
? PenumbraApiEc.Success
|
||||
: PenumbraApiEc.NothingChanged;
|
||||
return ApiHelpers.Return(ret, args);
|
||||
}
|
||||
|
||||
public PenumbraApiEc CopyModSettings(Guid? collectionId, string modDirectoryFrom, string modDirectoryTo)
|
||||
{
|
||||
var args = ApiHelpers.Args("CollectionId", collectionId.HasValue ? collectionId.Value.ToString() : "NULL",
|
||||
"From", modDirectoryFrom, "To", modDirectoryTo);
|
||||
var sourceMod = _modManager.FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryFrom, StringComparison.OrdinalIgnoreCase));
|
||||
var targetMod = _modManager.FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryTo, StringComparison.OrdinalIgnoreCase));
|
||||
if (collectionId == null)
|
||||
foreach (var collection in _collectionManager.Storage)
|
||||
_collectionEditor.CopyModSettings(collection, sourceMod, modDirectoryFrom, targetMod, modDirectoryTo);
|
||||
else if (_collectionManager.Storage.ById(collectionId.Value, out var collection))
|
||||
_collectionEditor.CopyModSettings(collection, sourceMod, modDirectoryFrom, targetMod, modDirectoryTo);
|
||||
else
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
return ApiHelpers.Return(PenumbraApiEc.Success, args);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void TriggerSettingEdited(Mod mod)
|
||||
{
|
||||
var collection = _collectionResolver.PlayerCollection();
|
||||
var (settings, parent) = collection[mod.Index];
|
||||
if (settings is { Enabled: true })
|
||||
ModSettingChanged?.Invoke(ModSettingChange.Edited, collection.Id, mod.Identifier, parent != collection);
|
||||
}
|
||||
|
||||
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? _1, DirectoryInfo? _2)
|
||||
{
|
||||
if (type == ModPathChangeType.Reloaded)
|
||||
TriggerSettingEdited(mod);
|
||||
}
|
||||
|
||||
private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting _1, int _2, bool inherited)
|
||||
=> ModSettingChanged?.Invoke(type, collection.Id, mod?.ModPath.Name ?? string.Empty, inherited);
|
||||
|
||||
private void OnModOptionEdited(ModOptionChangeType type, Mod mod, int groupIndex, int optionIndex, int moveIndex)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ModOptionChangeType.GroupDeleted:
|
||||
case ModOptionChangeType.GroupMoved:
|
||||
case ModOptionChangeType.GroupTypeChanged:
|
||||
case ModOptionChangeType.PriorityChanged:
|
||||
case ModOptionChangeType.OptionDeleted:
|
||||
case ModOptionChangeType.OptionMoved:
|
||||
case ModOptionChangeType.OptionFilesChanged:
|
||||
case ModOptionChangeType.OptionFilesAdded:
|
||||
case ModOptionChangeType.OptionSwapsChanged:
|
||||
case ModOptionChangeType.OptionMetaChanged:
|
||||
TriggerSettingEdited(mod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnModFileChanged(Mod mod, FileRegistry file)
|
||||
{
|
||||
if (file.CurrentUsage == 0)
|
||||
return;
|
||||
|
||||
TriggerSettingEdited(mod);
|
||||
}
|
||||
}
|
||||
132
Penumbra/Api/Api/ModsApi.cs
Normal file
132
Penumbra/Api/Api/ModsApi.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
using OtterGui.Compression;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
|
||||
{
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly ModManager _modManager;
|
||||
private readonly ModImportManager _modImportManager;
|
||||
private readonly Configuration _config;
|
||||
private readonly ModFileSystem _modFileSystem;
|
||||
|
||||
public ModsApi(ModManager modManager, ModImportManager modImportManager, Configuration config, ModFileSystem modFileSystem,
|
||||
CommunicatorService communicator)
|
||||
{
|
||||
_modManager = modManager;
|
||||
_modImportManager = modImportManager;
|
||||
_config = config;
|
||||
_modFileSystem = modFileSystem;
|
||||
_communicator = communicator;
|
||||
_communicator.ModPathChanged.Subscribe(OnModPathChanged, ModPathChanged.Priority.ApiMods);
|
||||
}
|
||||
|
||||
private void OnModPathChanged(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory, DirectoryInfo? newDirectory)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ModPathChangeType.Deleted when oldDirectory != null:
|
||||
ModDeleted?.Invoke(oldDirectory.Name);
|
||||
break;
|
||||
case ModPathChangeType.Added when newDirectory != null:
|
||||
ModAdded?.Invoke(newDirectory.Name);
|
||||
break;
|
||||
case ModPathChangeType.Moved when newDirectory != null && oldDirectory != null:
|
||||
ModMoved?.Invoke(oldDirectory.Name, newDirectory.Name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> _communicator.ModPathChanged.Unsubscribe(OnModPathChanged);
|
||||
|
||||
public Dictionary<string, string> GetModList()
|
||||
=> _modManager.ToDictionary(m => m.ModPath.Name, m => m.Name.Text);
|
||||
|
||||
public PenumbraApiEc InstallMod(string modFilePackagePath)
|
||||
{
|
||||
if (!File.Exists(modFilePackagePath))
|
||||
return ApiHelpers.Return(PenumbraApiEc.FileMissing, ApiHelpers.Args("ModFilePackagePath", modFilePackagePath));
|
||||
|
||||
_modImportManager.AddUnpack(modFilePackagePath);
|
||||
return ApiHelpers.Return(PenumbraApiEc.Success, ApiHelpers.Args("ModFilePackagePath", modFilePackagePath));
|
||||
}
|
||||
|
||||
public PenumbraApiEc ReloadMod(string modDirectory, string modName)
|
||||
{
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
return ApiHelpers.Return(PenumbraApiEc.ModMissing, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
|
||||
|
||||
_modManager.ReloadMod(mod);
|
||||
return ApiHelpers.Return(PenumbraApiEc.Success, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
|
||||
}
|
||||
|
||||
public PenumbraApiEc AddMod(string modDirectory)
|
||||
{
|
||||
var args = ApiHelpers.Args("ModDirectory", modDirectory);
|
||||
|
||||
var dir = new DirectoryInfo(Path.Join(_modManager.BasePath.FullName, Path.GetFileName(modDirectory)));
|
||||
if (!dir.Exists)
|
||||
return ApiHelpers.Return(PenumbraApiEc.FileMissing, args);
|
||||
|
||||
if (_modManager.BasePath.FullName != dir.Parent?.FullName)
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
|
||||
|
||||
_modManager.AddMod(dir);
|
||||
if (_config.UseFileSystemCompression)
|
||||
new FileCompactor(Penumbra.Log).StartMassCompact(dir.EnumerateFiles("*.*", SearchOption.AllDirectories),
|
||||
CompressionAlgorithm.Xpress8K);
|
||||
return ApiHelpers.Return(PenumbraApiEc.Success, args);
|
||||
}
|
||||
|
||||
public PenumbraApiEc DeleteMod(string modDirectory, string modName)
|
||||
{
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
return ApiHelpers.Return(PenumbraApiEc.NothingChanged, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
|
||||
|
||||
_modManager.DeleteMod(mod);
|
||||
return ApiHelpers.Return(PenumbraApiEc.Success, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
|
||||
}
|
||||
|
||||
public event Action<string>? ModDeleted;
|
||||
public event Action<string>? ModAdded;
|
||||
public event Action<string, string>? ModMoved;
|
||||
|
||||
public (PenumbraApiEc, string, bool, bool) GetModPath(string modDirectory, string modName)
|
||||
{
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod)
|
||||
|| !_modFileSystem.FindLeaf(mod, out var leaf))
|
||||
return (PenumbraApiEc.ModMissing, string.Empty, false, false);
|
||||
|
||||
var fullPath = leaf.FullName();
|
||||
var isDefault = ModFileSystem.ModHasDefaultPath(mod, fullPath);
|
||||
var isNameDefault = isDefault || ModFileSystem.ModHasDefaultPath(mod, leaf.Name);
|
||||
return (PenumbraApiEc.Success, fullPath, !isDefault, !isNameDefault );
|
||||
}
|
||||
|
||||
public PenumbraApiEc SetModPath(string modDirectory, string modName, string newPath)
|
||||
{
|
||||
if (newPath.Length == 0)
|
||||
return PenumbraApiEc.InvalidArgument;
|
||||
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod)
|
||||
|| !_modFileSystem.FindLeaf(mod, out var leaf))
|
||||
return PenumbraApiEc.ModMissing;
|
||||
|
||||
try
|
||||
{
|
||||
_modFileSystem.RenameAndMove(leaf, newPath);
|
||||
return PenumbraApiEc.Success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return PenumbraApiEc.PathRenameFailed;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Penumbra/Api/Api/PenumbraApi.cs
Normal file
40
Penumbra/Api/Api/PenumbraApi.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using OtterGui.Services;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class PenumbraApi(
|
||||
CollectionApi collection,
|
||||
EditingApi editing,
|
||||
GameStateApi gameState,
|
||||
MetaApi meta,
|
||||
ModsApi mods,
|
||||
ModSettingsApi modSettings,
|
||||
PluginStateApi pluginState,
|
||||
RedrawApi redraw,
|
||||
ResolveApi resolve,
|
||||
ResourceTreeApi resourceTree,
|
||||
TemporaryApi temporary,
|
||||
UiApi ui) : IDisposable, IApiService, IPenumbraApi
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
Valid = false;
|
||||
}
|
||||
|
||||
public (int Breaking, int Feature) ApiVersion
|
||||
=> (5, 0);
|
||||
|
||||
public bool Valid { get; private set; } = true;
|
||||
public IPenumbraApiCollection Collection { get; } = collection;
|
||||
public IPenumbraApiEditing Editing { get; } = editing;
|
||||
public IPenumbraApiGameState GameState { get; } = gameState;
|
||||
public IPenumbraApiMeta Meta { get; } = meta;
|
||||
public IPenumbraApiMods Mods { get; } = mods;
|
||||
public IPenumbraApiModSettings ModSettings { get; } = modSettings;
|
||||
public IPenumbraApiPluginState PluginState { get; } = pluginState;
|
||||
public IPenumbraApiRedraw Redraw { get; } = redraw;
|
||||
public IPenumbraApiResolve Resolve { get; } = resolve;
|
||||
public IPenumbraApiResourceTree ResourceTree { get; } = resourceTree;
|
||||
public IPenumbraApiTemporary Temporary { get; } = temporary;
|
||||
public IPenumbraApiUi Ui { get; } = ui;
|
||||
}
|
||||
30
Penumbra/Api/Api/PluginStateApi.cs
Normal file
30
Penumbra/Api/Api/PluginStateApi.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using Newtonsoft.Json;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class PluginStateApi(Configuration config, CommunicatorService communicator) : IPenumbraApiPluginState, IApiService
|
||||
{
|
||||
public string GetModDirectory()
|
||||
=> config.ModDirectory;
|
||||
|
||||
public string GetConfiguration()
|
||||
=> JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||
|
||||
public event Action<string, bool>? ModDirectoryChanged
|
||||
{
|
||||
add => communicator.ModDirectoryChanged.Subscribe(value!, Communication.ModDirectoryChanged.Priority.Api);
|
||||
remove => communicator.ModDirectoryChanged.Unsubscribe(value!);
|
||||
}
|
||||
|
||||
public bool GetEnabledState()
|
||||
=> config.EnableMods;
|
||||
|
||||
public event Action<bool>? EnabledChange
|
||||
{
|
||||
add => communicator.EnabledChanged.Subscribe(value!, EnabledChanged.Priority.Api);
|
||||
remove => communicator.EnabledChanged.Unsubscribe(value!);
|
||||
}
|
||||
}
|
||||
27
Penumbra/Api/Api/RedrawApi.cs
Normal file
27
Penumbra/Api/Api/RedrawApi.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Interop.Services;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class RedrawApi(RedrawService redrawService) : IPenumbraApiRedraw, IApiService
|
||||
{
|
||||
public void RedrawObject(int gameObjectIndex, RedrawType setting)
|
||||
=> redrawService.RedrawObject(gameObjectIndex, setting);
|
||||
|
||||
public void RedrawObject(string name, RedrawType setting)
|
||||
=> redrawService.RedrawObject(name, setting);
|
||||
|
||||
public void RedrawObject(GameObject? gameObject, RedrawType setting)
|
||||
=> redrawService.RedrawObject(gameObject, setting);
|
||||
|
||||
public void RedrawAll(RedrawType setting)
|
||||
=> redrawService.RedrawAll(setting);
|
||||
|
||||
public event GameObjectRedrawnDelegate? GameObjectRedrawn
|
||||
{
|
||||
add => redrawService.GameObjectRedrawn += value;
|
||||
remove => redrawService.GameObjectRedrawn -= value;
|
||||
}
|
||||
}
|
||||
101
Penumbra/Api/Api/ResolveApi.cs
Normal file
101
Penumbra/Api/Api/ResolveApi.cs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class ResolveApi(
|
||||
ModManager modManager,
|
||||
CollectionManager collectionManager,
|
||||
Configuration config,
|
||||
CollectionResolver collectionResolver,
|
||||
ApiHelpers helpers,
|
||||
IFramework framework) : IPenumbraApiResolve, IApiService
|
||||
{
|
||||
public string ResolveDefaultPath(string gamePath)
|
||||
=> ResolvePath(gamePath, modManager, collectionManager.Active.Default);
|
||||
|
||||
public string ResolveInterfacePath(string gamePath)
|
||||
=> ResolvePath(gamePath, modManager, collectionManager.Active.Interface);
|
||||
|
||||
public string ResolveGameObjectPath(string gamePath, int gameObjectIdx)
|
||||
{
|
||||
helpers.AssociatedCollection(gameObjectIdx, out var collection);
|
||||
return ResolvePath(gamePath, modManager, collection);
|
||||
}
|
||||
|
||||
public string ResolvePlayerPath(string gamePath)
|
||||
=> ResolvePath(gamePath, modManager, collectionResolver.PlayerCollection());
|
||||
|
||||
public string[] ReverseResolveGameObjectPath(string moddedPath, int gameObjectIdx)
|
||||
{
|
||||
if (!config.EnableMods)
|
||||
return [moddedPath];
|
||||
|
||||
helpers.AssociatedCollection(gameObjectIdx, out var collection);
|
||||
var ret = collection.ReverseResolvePath(new FullPath(moddedPath));
|
||||
return ret.Select(r => r.ToString()).ToArray();
|
||||
}
|
||||
|
||||
public string[] ReverseResolvePlayerPath(string moddedPath)
|
||||
{
|
||||
if (!config.EnableMods)
|
||||
return [moddedPath];
|
||||
|
||||
var ret = collectionResolver.PlayerCollection().ReverseResolvePath(new FullPath(moddedPath));
|
||||
return ret.Select(r => r.ToString()).ToArray();
|
||||
}
|
||||
|
||||
public (string[], string[][]) ResolvePlayerPaths(string[] forward, string[] reverse)
|
||||
{
|
||||
if (!config.EnableMods)
|
||||
return (forward, reverse.Select(p => new[]
|
||||
{
|
||||
p,
|
||||
}).ToArray());
|
||||
|
||||
var playerCollection = collectionResolver.PlayerCollection();
|
||||
var resolved = forward.Select(p => ResolvePath(p, modManager, playerCollection)).ToArray();
|
||||
var reverseResolved = playerCollection.ReverseResolvePaths(reverse);
|
||||
return (resolved, reverseResolved.Select(a => a.Select(p => p.ToString()).ToArray()).ToArray());
|
||||
}
|
||||
|
||||
public async Task<(string[], string[][])> ResolvePlayerPathsAsync(string[] forward, string[] reverse)
|
||||
{
|
||||
if (!config.EnableMods)
|
||||
return (forward, reverse.Select(p => new[]
|
||||
{
|
||||
p,
|
||||
}).ToArray());
|
||||
|
||||
return await Task.Run(async () =>
|
||||
{
|
||||
var playerCollection = await framework.RunOnFrameworkThread(collectionResolver.PlayerCollection).ConfigureAwait(false);
|
||||
var forwardTask = Task.Run(() =>
|
||||
{
|
||||
var forwardRet = new string[forward.Length];
|
||||
Parallel.For(0, forward.Length, idx => forwardRet[idx] = ResolvePath(forward[idx], modManager, playerCollection));
|
||||
return forwardRet;
|
||||
}).ConfigureAwait(false);
|
||||
var reverseTask = Task.Run(() => playerCollection.ReverseResolvePaths(reverse)).ConfigureAwait(false);
|
||||
var reverseResolved = (await reverseTask).Select(a => a.Select(p => p.ToString()).ToArray()).ToArray();
|
||||
return (await forwardTask, reverseResolved);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary> Resolve a path given by string for a specific collection. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private string ResolvePath(string path, ModManager _, ModCollection collection)
|
||||
{
|
||||
if (!config.EnableMods)
|
||||
return path;
|
||||
|
||||
var gamePath = Utf8GamePath.FromString(path, out var p, true) ? p : Utf8GamePath.Empty;
|
||||
var ret = collection.ResolvePath(gamePath);
|
||||
return ret?.ToString() ?? path;
|
||||
}
|
||||
}
|
||||
63
Penumbra/Api/Api/ResourceTreeApi.cs
Normal file
63
Penumbra/Api/Api/ResourceTreeApi.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class ResourceTreeApi(ResourceTreeFactory resourceTreeFactory, ObjectManager objects) : IPenumbraApiResourceTree, IApiService
|
||||
{
|
||||
public Dictionary<string, HashSet<string>>?[] GetGameObjectResourcePaths(params ushort[] gameObjects)
|
||||
{
|
||||
var characters = gameObjects.Select(index => objects.GetDalamudObject((int)index)).OfType<Character>();
|
||||
var resourceTrees = resourceTreeFactory.FromCharacters(characters, 0);
|
||||
var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees);
|
||||
|
||||
return Array.ConvertAll(gameObjects, obj => pathDictionaries.GetValueOrDefault(obj));
|
||||
}
|
||||
|
||||
public Dictionary<ushort, Dictionary<string, HashSet<string>>> GetPlayerResourcePaths()
|
||||
{
|
||||
var resourceTrees = resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly);
|
||||
return ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees);
|
||||
}
|
||||
|
||||
public GameResourceDict?[] GetGameObjectResourcesOfType(ResourceType type, bool withUiData,
|
||||
params ushort[] gameObjects)
|
||||
{
|
||||
var characters = gameObjects.Select(index => objects.GetDalamudObject((int)index)).OfType<Character>();
|
||||
var resourceTrees = resourceTreeFactory.FromCharacters(characters, withUiData ? ResourceTreeFactory.Flags.WithUiData : 0);
|
||||
var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
|
||||
|
||||
return Array.ConvertAll(gameObjects, obj => resDictionaries.GetValueOrDefault(obj));
|
||||
}
|
||||
|
||||
public Dictionary<ushort, GameResourceDict> GetPlayerResourcesOfType(ResourceType type,
|
||||
bool withUiData)
|
||||
{
|
||||
var resourceTrees = resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly
|
||||
| (withUiData ? ResourceTreeFactory.Flags.WithUiData : 0));
|
||||
return ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
|
||||
}
|
||||
|
||||
public JObject?[] GetGameObjectResourceTrees(bool withUiData, params ushort[] gameObjects)
|
||||
{
|
||||
var characters = gameObjects.Select(index => objects.GetDalamudObject((int)index)).OfType<Character>();
|
||||
var resourceTrees = resourceTreeFactory.FromCharacters(characters, withUiData ? ResourceTreeFactory.Flags.WithUiData : 0);
|
||||
var resDictionary = ResourceTreeApiHelper.EncapsulateResourceTrees(resourceTrees);
|
||||
|
||||
return Array.ConvertAll(gameObjects, obj => resDictionary.GetValueOrDefault(obj));
|
||||
}
|
||||
|
||||
public Dictionary<ushort, JObject> GetPlayerResourceTrees(bool withUiData)
|
||||
{
|
||||
var resourceTrees = resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly
|
||||
| (withUiData ? ResourceTreeFactory.Flags.WithUiData : 0));
|
||||
var resDictionary = ResourceTreeApiHelper.EncapsulateResourceTrees(resourceTrees);
|
||||
|
||||
return resDictionary;
|
||||
}
|
||||
}
|
||||
190
Penumbra/Api/Api/TemporaryApi.cs
Normal file
190
Penumbra/Api/Api/TemporaryApi.cs
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
using OtterGui;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Subclasses;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class TemporaryApi(
|
||||
TempCollectionManager tempCollections,
|
||||
ObjectManager objects,
|
||||
ActorManager actors,
|
||||
CollectionManager collectionManager,
|
||||
TempModManager tempMods) : IPenumbraApiTemporary, IApiService
|
||||
{
|
||||
public Guid CreateTemporaryCollection(string name)
|
||||
=> tempCollections.CreateTemporaryCollection(name);
|
||||
|
||||
public PenumbraApiEc DeleteTemporaryCollection(Guid collectionId)
|
||||
=> tempCollections.RemoveTemporaryCollection(collectionId)
|
||||
? PenumbraApiEc.Success
|
||||
: PenumbraApiEc.CollectionMissing;
|
||||
|
||||
public PenumbraApiEc AssignTemporaryCollection(Guid collectionId, int actorIndex, bool forceAssignment)
|
||||
{
|
||||
var args = ApiHelpers.Args("CollectionId", collectionId, "ActorIndex", actorIndex, "Forced", forceAssignment);
|
||||
if (actorIndex < 0 || actorIndex >= objects.TotalCount)
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
|
||||
|
||||
var identifier = actors.FromObject(objects[actorIndex], out _, false, false, true);
|
||||
if (!identifier.IsValid)
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
|
||||
|
||||
if (!tempCollections.CollectionById(collectionId, out var collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
if (forceAssignment)
|
||||
{
|
||||
if (tempCollections.Collections.ContainsKey(identifier) && !tempCollections.Collections.Delete(identifier))
|
||||
return ApiHelpers.Return(PenumbraApiEc.AssignmentDeletionFailed, args);
|
||||
}
|
||||
else if (tempCollections.Collections.ContainsKey(identifier)
|
||||
|| collectionManager.Active.Individuals.ContainsKey(identifier))
|
||||
{
|
||||
return ApiHelpers.Return(PenumbraApiEc.CharacterCollectionExists, args);
|
||||
}
|
||||
|
||||
var group = tempCollections.Collections.GetGroup(identifier);
|
||||
var ret = tempCollections.AddIdentifier(collection, group)
|
||||
? PenumbraApiEc.Success
|
||||
: PenumbraApiEc.UnknownError;
|
||||
return ApiHelpers.Return(ret, args);
|
||||
}
|
||||
|
||||
public PenumbraApiEc AddTemporaryModAll(string tag, Dictionary<string, string> paths, string manipString, int priority)
|
||||
{
|
||||
var args = ApiHelpers.Args("Tag", tag, "#Paths", paths.Count, "ManipString", manipString, "Priority", priority);
|
||||
if (!ConvertPaths(paths, out var p))
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidGamePath, args);
|
||||
|
||||
if (!ConvertManips(manipString, out var m))
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidManipulation, args);
|
||||
|
||||
var ret = tempMods.Register(tag, null, p, m, new ModPriority(priority)) switch
|
||||
{
|
||||
RedirectResult.Success => PenumbraApiEc.Success,
|
||||
_ => PenumbraApiEc.UnknownError,
|
||||
};
|
||||
return ApiHelpers.Return(ret, args);
|
||||
}
|
||||
|
||||
public PenumbraApiEc AddTemporaryMod(string tag, Guid collectionId, Dictionary<string, string> paths, string manipString, int priority)
|
||||
{
|
||||
var args = ApiHelpers.Args("Tag", tag, "CollectionId", collectionId, "#Paths", paths.Count, "ManipString",
|
||||
manipString, "Priority", priority);
|
||||
|
||||
if (collectionId == Guid.Empty)
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
|
||||
|
||||
if (!tempCollections.CollectionById(collectionId, out var collection)
|
||||
&& !collectionManager.Storage.ById(collectionId, out collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
if (!ConvertPaths(paths, out var p))
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidGamePath, args);
|
||||
|
||||
if (!ConvertManips(manipString, out var m))
|
||||
return ApiHelpers.Return(PenumbraApiEc.InvalidManipulation, args);
|
||||
|
||||
var ret = tempMods.Register(tag, collection, p, m, new ModPriority(priority)) switch
|
||||
{
|
||||
RedirectResult.Success => PenumbraApiEc.Success,
|
||||
_ => PenumbraApiEc.UnknownError,
|
||||
};
|
||||
return ApiHelpers.Return(ret, args);
|
||||
}
|
||||
|
||||
public PenumbraApiEc RemoveTemporaryModAll(string tag, int priority)
|
||||
{
|
||||
var ret = tempMods.Unregister(tag, null, new ModPriority(priority)) switch
|
||||
{
|
||||
RedirectResult.Success => PenumbraApiEc.Success,
|
||||
RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged,
|
||||
_ => PenumbraApiEc.UnknownError,
|
||||
};
|
||||
return ApiHelpers.Return(ret, ApiHelpers.Args("Tag", tag, "Priority", priority));
|
||||
}
|
||||
|
||||
public PenumbraApiEc RemoveTemporaryMod(string tag, Guid collectionId, int priority)
|
||||
{
|
||||
var args = ApiHelpers.Args("Tag", tag, "CollectionId", collectionId, "Priority", priority);
|
||||
|
||||
if (!tempCollections.CollectionById(collectionId, out var collection)
|
||||
&& !collectionManager.Storage.ById(collectionId, out collection))
|
||||
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
|
||||
|
||||
var ret = tempMods.Unregister(tag, collection, new ModPriority(priority)) switch
|
||||
{
|
||||
RedirectResult.Success => PenumbraApiEc.Success,
|
||||
RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged,
|
||||
_ => PenumbraApiEc.UnknownError,
|
||||
};
|
||||
return ApiHelpers.Return(ret, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a dictionary of strings to a dictionary of game paths to full paths.
|
||||
/// Only returns true if all paths can successfully be converted and added.
|
||||
/// </summary>
|
||||
private static bool ConvertPaths(IReadOnlyDictionary<string, string> redirections,
|
||||
[NotNullWhen(true)] out Dictionary<Utf8GamePath, FullPath>? paths)
|
||||
{
|
||||
paths = new Dictionary<Utf8GamePath, FullPath>(redirections.Count);
|
||||
foreach (var (gString, fString) in redirections)
|
||||
{
|
||||
if (!Utf8GamePath.FromString(gString, out var path, false))
|
||||
{
|
||||
paths = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var fullPath = new FullPath(fString);
|
||||
if (!paths.TryAdd(path, fullPath))
|
||||
{
|
||||
paths = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert manipulations from a transmitted base64 string to actual manipulations.
|
||||
/// The empty string is treated as an empty set.
|
||||
/// Only returns true if all conversions are successful and distinct.
|
||||
/// </summary>
|
||||
private static bool ConvertManips(string manipString,
|
||||
[NotNullWhen(true)] out HashSet<MetaManipulation>? manips)
|
||||
{
|
||||
if (manipString.Length == 0)
|
||||
{
|
||||
manips = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Functions.FromCompressedBase64<MetaManipulation[]>(manipString, out var manipArray) != MetaManipulation.CurrentVersion)
|
||||
{
|
||||
manips = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
manips = new HashSet<MetaManipulation>(manipArray!.Length);
|
||||
foreach (var manip in manipArray.Where(m => m.Validate()))
|
||||
{
|
||||
if (manips.Add(manip))
|
||||
continue;
|
||||
|
||||
Penumbra.Log.Warning($"Manipulation {manip} {manip.EntryToString()} is invalid and was skipped.");
|
||||
manips = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
101
Penumbra/Api/Api/UiApi.cs
Normal file
101
Penumbra/Api/Api/UiApi.cs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.UI;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class UiApi : IPenumbraApiUi, IApiService, IDisposable
|
||||
{
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly ConfigWindow _configWindow;
|
||||
private readonly ModManager _modManager;
|
||||
|
||||
public UiApi(CommunicatorService communicator, ConfigWindow configWindow, ModManager modManager)
|
||||
{
|
||||
_communicator = communicator;
|
||||
_configWindow = configWindow;
|
||||
_modManager = modManager;
|
||||
_communicator.ChangedItemHover.Subscribe(OnChangedItemHover, ChangedItemHover.Priority.Default);
|
||||
_communicator.ChangedItemClick.Subscribe(OnChangedItemClick, ChangedItemClick.Priority.Default);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_communicator.ChangedItemHover.Unsubscribe(OnChangedItemHover);
|
||||
_communicator.ChangedItemClick.Unsubscribe(OnChangedItemClick);
|
||||
}
|
||||
|
||||
public event Action<ChangedItemType, uint>? ChangedItemTooltip;
|
||||
|
||||
public event Action<MouseButton, ChangedItemType, uint>? ChangedItemClicked;
|
||||
|
||||
public event Action<string, float, float>? PreSettingsTabBarDraw
|
||||
{
|
||||
add => _communicator.PreSettingsTabBarDraw.Subscribe(value!, Communication.PreSettingsTabBarDraw.Priority.Default);
|
||||
remove => _communicator.PreSettingsTabBarDraw.Unsubscribe(value!);
|
||||
}
|
||||
|
||||
public event Action<string>? PreSettingsPanelDraw
|
||||
{
|
||||
add => _communicator.PreSettingsPanelDraw.Subscribe(value!, Communication.PreSettingsPanelDraw.Priority.Default);
|
||||
remove => _communicator.PreSettingsPanelDraw.Unsubscribe(value!);
|
||||
}
|
||||
|
||||
public event Action<string>? PostEnabledDraw
|
||||
{
|
||||
add => _communicator.PostEnabledDraw.Subscribe(value!, Communication.PostEnabledDraw.Priority.Default);
|
||||
remove => _communicator.PostEnabledDraw.Unsubscribe(value!);
|
||||
}
|
||||
|
||||
public event Action<string>? PostSettingsPanelDraw
|
||||
{
|
||||
add => _communicator.PostSettingsPanelDraw.Subscribe(value!, Communication.PostSettingsPanelDraw.Priority.Default);
|
||||
remove => _communicator.PostSettingsPanelDraw.Unsubscribe(value!);
|
||||
}
|
||||
|
||||
public PenumbraApiEc OpenMainWindow(TabType tab, string modDirectory, string modName)
|
||||
{
|
||||
_configWindow.IsOpen = true;
|
||||
if (!Enum.IsDefined(tab))
|
||||
return PenumbraApiEc.InvalidArgument;
|
||||
|
||||
if (tab == TabType.Mods && (modDirectory.Length > 0 || modName.Length > 0))
|
||||
{
|
||||
if (_modManager.TryGetMod(modDirectory, modName, out var mod))
|
||||
_communicator.SelectTab.Invoke(tab, mod);
|
||||
else
|
||||
return PenumbraApiEc.ModMissing;
|
||||
}
|
||||
else if (tab != TabType.None)
|
||||
{
|
||||
_communicator.SelectTab.Invoke(tab, null);
|
||||
}
|
||||
|
||||
return PenumbraApiEc.Success;
|
||||
}
|
||||
|
||||
public void CloseMainWindow()
|
||||
=> _configWindow.IsOpen = false;
|
||||
|
||||
private void OnChangedItemClick(MouseButton button, object? data)
|
||||
{
|
||||
if (ChangedItemClicked == null)
|
||||
return;
|
||||
|
||||
var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId(data);
|
||||
ChangedItemClicked.Invoke(button, type, id);
|
||||
}
|
||||
|
||||
private void OnChangedItemHover(object? data)
|
||||
{
|
||||
if (ChangedItemTooltip == null)
|
||||
return;
|
||||
|
||||
var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId(data);
|
||||
ChangedItemTooltip.Invoke(type, id);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Communication;
|
||||
|
|
@ -9,7 +10,7 @@ using Penumbra.String.Classes;
|
|||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
public class DalamudSubstitutionProvider : IDisposable
|
||||
public class DalamudSubstitutionProvider : IDisposable, IApiService
|
||||
{
|
||||
private readonly ITextureSubstitutionProvider _substitution;
|
||||
private readonly ActiveCollectionData _activeCollectionData;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
using EmbedIO;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.WebApi;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Api.Enums;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
public class HttpApi : IDisposable
|
||||
public class HttpApi : IDisposable, IApiService
|
||||
{
|
||||
private partial class Controller : WebApiController
|
||||
{
|
||||
|
|
@ -67,7 +69,7 @@ public class HttpApi : IDisposable
|
|||
public partial object? GetMods()
|
||||
{
|
||||
Penumbra.Log.Debug($"[HTTP] {nameof(GetMods)} triggered.");
|
||||
return _api.GetModList();
|
||||
return _api.Mods.GetModList();
|
||||
}
|
||||
|
||||
public async partial Task Redraw()
|
||||
|
|
@ -75,17 +77,15 @@ public class HttpApi : IDisposable
|
|||
var data = await HttpContext.GetRequestDataAsync<RedrawData>();
|
||||
Penumbra.Log.Debug($"[HTTP] {nameof(Redraw)} triggered with {data}.");
|
||||
if (data.ObjectTableIndex >= 0)
|
||||
_api.RedrawObject(data.ObjectTableIndex, data.Type);
|
||||
else if (data.Name.Length > 0)
|
||||
_api.RedrawObject(data.Name, data.Type);
|
||||
_api.Redraw.RedrawObject(data.ObjectTableIndex, data.Type);
|
||||
else
|
||||
_api.RedrawAll(data.Type);
|
||||
_api.Redraw.RedrawAll(data.Type);
|
||||
}
|
||||
|
||||
public partial void RedrawAll()
|
||||
{
|
||||
Penumbra.Log.Debug($"[HTTP] {nameof(RedrawAll)} triggered.");
|
||||
_api.RedrawAll(RedrawType.Redraw);
|
||||
_api.Redraw.RedrawAll(RedrawType.Redraw);
|
||||
}
|
||||
|
||||
public async partial Task ReloadMod()
|
||||
|
|
@ -95,10 +95,10 @@ public class HttpApi : IDisposable
|
|||
// Add the mod if it is not already loaded and if the directory name is given.
|
||||
// AddMod returns Success if the mod is already loaded.
|
||||
if (data.Path.Length != 0)
|
||||
_api.AddMod(data.Path);
|
||||
_api.Mods.AddMod(data.Path);
|
||||
|
||||
// Reload the mod by path or name, which will also remove no-longer existing mods.
|
||||
_api.ReloadMod(data.Path, data.Name);
|
||||
_api.Mods.ReloadMod(data.Path, data.Name);
|
||||
}
|
||||
|
||||
public async partial Task InstallMod()
|
||||
|
|
@ -106,13 +106,13 @@ public class HttpApi : IDisposable
|
|||
var data = await HttpContext.GetRequestDataAsync<ModInstallData>();
|
||||
Penumbra.Log.Debug($"[HTTP] {nameof(InstallMod)} triggered with {data}.");
|
||||
if (data.Path.Length != 0)
|
||||
_api.InstallMod(data.Path);
|
||||
_api.Mods.InstallMod(data.Path);
|
||||
}
|
||||
|
||||
public partial void OpenWindow()
|
||||
{
|
||||
Penumbra.Log.Debug($"[HTTP] {nameof(OpenWindow)} triggered.");
|
||||
_api.OpenMainWindow(TabType.Mods, string.Empty, string.Empty);
|
||||
_api.Ui.OpenMainWindow(TabType.Mods, string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
private record ModReloadData(string Path, string Name)
|
||||
|
|
|
|||
118
Penumbra/Api/IpcProviders.cs
Normal file
118
Penumbra/Api/IpcProviders.cs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
using Dalamud.Plugin;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Api.Helpers;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
public sealed class IpcProviders : IDisposable, IApiService
|
||||
{
|
||||
private readonly List<IDisposable> _providers;
|
||||
|
||||
private readonly EventProvider _disposedProvider;
|
||||
private readonly EventProvider _initializedProvider;
|
||||
|
||||
public IpcProviders(DalamudPluginInterface pi, IPenumbraApi api)
|
||||
{
|
||||
_disposedProvider = IpcSubscribers.Disposed.Provider(pi);
|
||||
_initializedProvider = IpcSubscribers.Initialized.Provider(pi);
|
||||
_providers =
|
||||
[
|
||||
IpcSubscribers.GetCollections.Provider(pi, api.Collection),
|
||||
IpcSubscribers.GetChangedItemsForCollection.Provider(pi, api.Collection),
|
||||
IpcSubscribers.GetCollection.Provider(pi, api.Collection),
|
||||
IpcSubscribers.GetCollectionForObject.Provider(pi, api.Collection),
|
||||
IpcSubscribers.SetCollection.Provider(pi, api.Collection),
|
||||
IpcSubscribers.SetCollectionForObject.Provider(pi, api.Collection),
|
||||
|
||||
IpcSubscribers.ConvertTextureFile.Provider(pi, api.Editing),
|
||||
IpcSubscribers.ConvertTextureData.Provider(pi, api.Editing),
|
||||
|
||||
IpcSubscribers.GetDrawObjectInfo.Provider(pi, api.GameState),
|
||||
IpcSubscribers.GetCutsceneParentIndex.Provider(pi, api.GameState),
|
||||
IpcSubscribers.SetCutsceneParentIndex.Provider(pi, api.GameState),
|
||||
IpcSubscribers.CreatingCharacterBase.Provider(pi, api.GameState),
|
||||
IpcSubscribers.CreatedCharacterBase.Provider(pi, api.GameState),
|
||||
IpcSubscribers.GameObjectResourcePathResolved.Provider(pi, api.GameState),
|
||||
|
||||
IpcSubscribers.GetPlayerMetaManipulations.Provider(pi, api.Meta),
|
||||
IpcSubscribers.GetMetaManipulations.Provider(pi, api.Meta),
|
||||
|
||||
IpcSubscribers.GetModList.Provider(pi, api.Mods),
|
||||
IpcSubscribers.InstallMod.Provider(pi, api.Mods),
|
||||
IpcSubscribers.ReloadMod.Provider(pi, api.Mods),
|
||||
IpcSubscribers.AddMod.Provider(pi, api.Mods),
|
||||
IpcSubscribers.DeleteMod.Provider(pi, api.Mods),
|
||||
IpcSubscribers.ModDeleted.Provider(pi, api.Mods),
|
||||
IpcSubscribers.ModAdded.Provider(pi, api.Mods),
|
||||
IpcSubscribers.ModMoved.Provider(pi, api.Mods),
|
||||
IpcSubscribers.GetModPath.Provider(pi, api.Mods),
|
||||
IpcSubscribers.SetModPath.Provider(pi, api.Mods),
|
||||
|
||||
IpcSubscribers.GetAvailableModSettings.Provider(pi, api.ModSettings),
|
||||
IpcSubscribers.GetCurrentModSettings.Provider(pi, api.ModSettings),
|
||||
IpcSubscribers.TryInheritMod.Provider(pi, api.ModSettings),
|
||||
IpcSubscribers.TrySetMod.Provider(pi, api.ModSettings),
|
||||
IpcSubscribers.TrySetModPriority.Provider(pi, api.ModSettings),
|
||||
IpcSubscribers.TrySetModSetting.Provider(pi, api.ModSettings),
|
||||
IpcSubscribers.TrySetModSettings.Provider(pi, api.ModSettings),
|
||||
IpcSubscribers.ModSettingChanged.Provider(pi, api.ModSettings),
|
||||
IpcSubscribers.CopyModSettings.Provider(pi, api.ModSettings),
|
||||
|
||||
IpcSubscribers.ApiVersion.Provider(pi, api),
|
||||
IpcSubscribers.GetModDirectory.Provider(pi, api.PluginState),
|
||||
IpcSubscribers.GetConfiguration.Provider(pi, api.PluginState),
|
||||
IpcSubscribers.ModDirectoryChanged.Provider(pi, api.PluginState),
|
||||
IpcSubscribers.GetEnabledState.Provider(pi, api.PluginState),
|
||||
IpcSubscribers.EnabledChange.Provider(pi, api.PluginState),
|
||||
|
||||
IpcSubscribers.RedrawObject.Provider(pi, api.Redraw),
|
||||
IpcSubscribers.RedrawAll.Provider(pi, api.Redraw),
|
||||
IpcSubscribers.GameObjectRedrawn.Provider(pi, api.Redraw),
|
||||
|
||||
IpcSubscribers.ResolveDefaultPath.Provider(pi, api.Resolve),
|
||||
IpcSubscribers.ResolveInterfacePath.Provider(pi, api.Resolve),
|
||||
IpcSubscribers.ResolveGameObjectPath.Provider(pi, api.Resolve),
|
||||
IpcSubscribers.ResolvePlayerPath.Provider(pi, api.Resolve),
|
||||
IpcSubscribers.ReverseResolveGameObjectPath.Provider(pi, api.Resolve),
|
||||
IpcSubscribers.ReverseResolvePlayerPath.Provider(pi, api.Resolve),
|
||||
IpcSubscribers.ResolvePlayerPaths.Provider(pi, api.Resolve),
|
||||
IpcSubscribers.ResolvePlayerPathsAsync.Provider(pi, api.Resolve),
|
||||
|
||||
IpcSubscribers.GetGameObjectResourcePaths.Provider(pi, api.ResourceTree),
|
||||
IpcSubscribers.GetPlayerResourcePaths.Provider(pi, api.ResourceTree),
|
||||
IpcSubscribers.GetGameObjectResourcesOfType.Provider(pi, api.ResourceTree),
|
||||
IpcSubscribers.GetPlayerResourcesOfType.Provider(pi, api.ResourceTree),
|
||||
IpcSubscribers.GetGameObjectResourceTrees.Provider(pi, api.ResourceTree),
|
||||
IpcSubscribers.GetPlayerResourceTrees.Provider(pi, api.ResourceTree),
|
||||
|
||||
IpcSubscribers.CreateTemporaryCollection.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.DeleteTemporaryCollection.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.AssignTemporaryCollection.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.AddTemporaryModAll.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.AddTemporaryMod.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.RemoveTemporaryModAll.Provider(pi, api.Temporary),
|
||||
IpcSubscribers.RemoveTemporaryMod.Provider(pi, api.Temporary),
|
||||
|
||||
IpcSubscribers.ChangedItemTooltip.Provider(pi, api.Ui),
|
||||
IpcSubscribers.ChangedItemClicked.Provider(pi, api.Ui),
|
||||
IpcSubscribers.PreSettingsTabBarDraw.Provider(pi, api.Ui),
|
||||
IpcSubscribers.PreSettingsPanelDraw.Provider(pi, api.Ui),
|
||||
IpcSubscribers.PostEnabledDraw.Provider(pi, api.Ui),
|
||||
IpcSubscribers.PostSettingsPanelDraw.Provider(pi, api.Ui),
|
||||
IpcSubscribers.OpenMainWindow.Provider(pi, api.Ui),
|
||||
IpcSubscribers.CloseMainWindow.Provider(pi, api.Ui),
|
||||
];
|
||||
_initializedProvider.Invoke();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var provider in _providers)
|
||||
provider.Dispose();
|
||||
_providers.Clear();
|
||||
_initializedProvider.Dispose();
|
||||
_disposedProvider.Invoke();
|
||||
_disposedProvider.Dispose();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
166
Penumbra/Api/IpcTester/CollectionsIpcTester.cs
Normal file
166
Penumbra/Api/IpcTester/CollectionsIpcTester.cs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.GameData.Enums;
|
||||
using ImGuiClip = OtterGui.ImGuiClip;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class CollectionsIpcTester(DalamudPluginInterface pi) : IUiService
|
||||
{
|
||||
private int _objectIdx;
|
||||
private string _collectionIdString = string.Empty;
|
||||
private Guid? _collectionId = null;
|
||||
private bool _allowCreation = true;
|
||||
private bool _allowDeletion = true;
|
||||
private ApiCollectionType _type = ApiCollectionType.Yourself;
|
||||
|
||||
private Dictionary<Guid, string> _collections = [];
|
||||
private (string, ChangedItemType, uint)[] _changedItems = [];
|
||||
private PenumbraApiEc _returnCode = PenumbraApiEc.Success;
|
||||
private (Guid Id, string Name)? _oldCollection;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Collections");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
ImGuiUtil.GenericEnumCombo("Collection Type", 200, _type, out _type, t => ((CollectionType)t).ToName());
|
||||
ImGui.InputInt("Object Index##Collections", ref _objectIdx, 0, 0);
|
||||
ImGuiUtil.GuidInput("Collection Id##Collections", "Collection GUID...", string.Empty, ref _collectionId, ref _collectionIdString);
|
||||
ImGui.Checkbox("Allow Assignment Creation", ref _allowCreation);
|
||||
ImGui.SameLine();
|
||||
ImGui.Checkbox("Allow Assignment Deletion", ref _allowDeletion);
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, 4, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
IpcTester.DrawIntro("Last Return Code", _returnCode.ToString());
|
||||
if (_oldCollection != null)
|
||||
ImGui.TextUnformatted(!_oldCollection.HasValue ? "Created" : _oldCollection.ToString());
|
||||
|
||||
IpcTester.DrawIntro(GetCollection.Label, "Current Collection");
|
||||
DrawCollection(new GetCollection(pi).Invoke(ApiCollectionType.Current));
|
||||
|
||||
IpcTester.DrawIntro(GetCollection.Label, "Default Collection");
|
||||
DrawCollection(new GetCollection(pi).Invoke(ApiCollectionType.Default));
|
||||
|
||||
IpcTester.DrawIntro(GetCollection.Label, "Interface Collection");
|
||||
DrawCollection(new GetCollection(pi).Invoke(ApiCollectionType.Interface));
|
||||
|
||||
IpcTester.DrawIntro(GetCollection.Label, "Special Collection");
|
||||
DrawCollection(new GetCollection(pi).Invoke(_type));
|
||||
|
||||
IpcTester.DrawIntro(GetCollections.Label, "Collections");
|
||||
DrawCollectionPopup();
|
||||
if (ImGui.Button("Get##Collections"))
|
||||
{
|
||||
_collections = new GetCollections(pi).Invoke();
|
||||
ImGui.OpenPopup("Collections");
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(GetCollectionForObject.Label, "Get Object Collection");
|
||||
var (valid, individual, effectiveCollection) = new GetCollectionForObject(pi).Invoke(_objectIdx);
|
||||
DrawCollection(effectiveCollection);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"({(valid ? "Valid" : "Invalid")} Object{(individual ? ", Individual Assignment)" : ")")}");
|
||||
|
||||
IpcTester.DrawIntro(SetCollection.Label, "Set Special Collection");
|
||||
if (ImGui.Button("Set##SpecialCollection"))
|
||||
(_returnCode, _oldCollection) =
|
||||
new SetCollection(pi).Invoke(_type, _collectionId.GetValueOrDefault(Guid.Empty), _allowCreation, _allowDeletion);
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.Button("Remove##SpecialCollection"))
|
||||
(_returnCode, _oldCollection) = new SetCollection(pi).Invoke(_type, null, _allowCreation, _allowDeletion);
|
||||
|
||||
IpcTester.DrawIntro(SetCollectionForObject.Label, "Set Object Collection");
|
||||
if (ImGui.Button("Set##ObjectCollection"))
|
||||
(_returnCode, _oldCollection) = new SetCollectionForObject(pi).Invoke(_objectIdx, _collectionId.GetValueOrDefault(Guid.Empty),
|
||||
_allowCreation, _allowDeletion);
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.Button("Remove##ObjectCollection"))
|
||||
(_returnCode, _oldCollection) = new SetCollectionForObject(pi).Invoke(_objectIdx, null, _allowCreation, _allowDeletion);
|
||||
|
||||
IpcTester.DrawIntro(GetChangedItemsForCollection.Label, "Changed Item List");
|
||||
DrawChangedItemPopup();
|
||||
if (ImGui.Button("Get##ChangedItems"))
|
||||
{
|
||||
var items = new GetChangedItemsForCollection(pi).Invoke(_collectionId.GetValueOrDefault(Guid.Empty));
|
||||
_changedItems = items.Select(kvp =>
|
||||
{
|
||||
var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId(kvp.Value);
|
||||
return (kvp.Key, type, id);
|
||||
}).ToArray();
|
||||
ImGui.OpenPopup("Changed Item List");
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawChangedItemPopup()
|
||||
{
|
||||
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
|
||||
using var p = ImRaii.Popup("Changed Item List");
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
using (var t = ImRaii.Table("##ChangedItems", 3, ImGuiTableFlags.SizingFixedFit))
|
||||
{
|
||||
if (t)
|
||||
ImGuiClip.ClippedDraw(_changedItems, t =>
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(t.Item1);
|
||||
ImGuiUtil.DrawTableColumn(t.Item2.ToString());
|
||||
ImGuiUtil.DrawTableColumn(t.Item3.ToString());
|
||||
}, ImGui.GetTextLineHeightWithSpacing());
|
||||
}
|
||||
|
||||
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
private void DrawCollectionPopup()
|
||||
{
|
||||
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
|
||||
using var p = ImRaii.Popup("Collections");
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
using (var t = ImRaii.Table("collections", 2, ImGuiTableFlags.SizingFixedFit))
|
||||
{
|
||||
if (t)
|
||||
foreach (var collection in _collections)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
DrawCollection((collection.Key, collection.Value));
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
private static void DrawCollection((Guid Id, string Name)? collection)
|
||||
{
|
||||
if (collection == null)
|
||||
{
|
||||
ImGui.TextUnformatted("<Unassigned>");
|
||||
ImGui.TableNextColumn();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(collection.Value.Name);
|
||||
ImGui.TableNextColumn();
|
||||
using (ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
ImGuiUtil.CopyOnClickSelectable(collection.Value.Id.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Penumbra/Api/IpcTester/EditingIpcTester.cs
Normal file
70
Penumbra/Api/IpcTester/EditingIpcTester.cs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class EditingIpcTester(DalamudPluginInterface pi) : IUiService
|
||||
{
|
||||
private string _inputPath = string.Empty;
|
||||
private string _inputPath2 = string.Empty;
|
||||
private string _outputPath = string.Empty;
|
||||
private string _outputPath2 = string.Empty;
|
||||
|
||||
private TextureType _typeSelector;
|
||||
private bool _mipMaps = true;
|
||||
|
||||
private Task? _task1;
|
||||
private Task? _task2;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Editing");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
ImGui.InputTextWithHint("##inputPath", "Input Texture Path...", ref _inputPath, 256);
|
||||
ImGui.InputTextWithHint("##outputPath", "Output Texture Path...", ref _outputPath, 256);
|
||||
ImGui.InputTextWithHint("##inputPath2", "Input Texture Path 2...", ref _inputPath2, 256);
|
||||
ImGui.InputTextWithHint("##outputPath2", "Output Texture Path 2...", ref _outputPath2, 256);
|
||||
TypeCombo();
|
||||
ImGui.Checkbox("Add MipMaps", ref _mipMaps);
|
||||
|
||||
using var table = ImRaii.Table("...", 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
IpcTester.DrawIntro(ConvertTextureFile.Label, (string)"Convert Texture 1");
|
||||
if (ImGuiUtil.DrawDisabledButton("Save 1", Vector2.Zero, string.Empty, _task1 is { IsCompleted: false }))
|
||||
_task1 = new ConvertTextureFile(pi).Invoke(_inputPath, _outputPath, _typeSelector, _mipMaps);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_task1 == null ? "Not Initiated" : _task1.Status.ToString());
|
||||
if (ImGui.IsItemHovered() && _task1?.Status == TaskStatus.Faulted)
|
||||
ImGui.SetTooltip(_task1.Exception?.ToString());
|
||||
|
||||
IpcTester.DrawIntro(ConvertTextureFile.Label, (string)"Convert Texture 2");
|
||||
if (ImGuiUtil.DrawDisabledButton("Save 2", Vector2.Zero, string.Empty, _task2 is { IsCompleted: false }))
|
||||
_task2 = new ConvertTextureFile(pi).Invoke(_inputPath2, _outputPath2, _typeSelector, _mipMaps);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_task2 == null ? "Not Initiated" : _task2.Status.ToString());
|
||||
if (ImGui.IsItemHovered() && _task2?.Status == TaskStatus.Faulted)
|
||||
ImGui.SetTooltip(_task2.Exception?.ToString());
|
||||
}
|
||||
|
||||
private void TypeCombo()
|
||||
{
|
||||
using var combo = ImRaii.Combo("Convert To", _typeSelector.ToString());
|
||||
if (!combo)
|
||||
return;
|
||||
|
||||
foreach (var value in Enum.GetValues<TextureType>())
|
||||
{
|
||||
if (ImGui.Selectable(value.ToString(), _typeSelector == value))
|
||||
_typeSelector = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
137
Penumbra/Api/IpcTester/GameStateIpcTester.cs
Normal file
137
Penumbra/Api/IpcTester/GameStateIpcTester.cs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class GameStateIpcTester : IUiService, IDisposable
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
public readonly EventSubscriber<nint, Guid, nint, nint, nint> CharacterBaseCreating;
|
||||
public readonly EventSubscriber<nint, Guid, nint> CharacterBaseCreated;
|
||||
public readonly EventSubscriber<nint, string, string> GameObjectResourcePathResolved;
|
||||
|
||||
private string _lastCreatedGameObjectName = string.Empty;
|
||||
private nint _lastCreatedDrawObject = nint.Zero;
|
||||
private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue;
|
||||
private string _lastResolvedGamePath = string.Empty;
|
||||
private string _lastResolvedFullPath = string.Empty;
|
||||
private string _lastResolvedObject = string.Empty;
|
||||
private DateTimeOffset _lastResolvedGamePathTime = DateTimeOffset.MaxValue;
|
||||
private string _currentDrawObjectString = string.Empty;
|
||||
private nint _currentDrawObject = nint.Zero;
|
||||
private int _currentCutsceneActor;
|
||||
private int _currentCutsceneParent;
|
||||
private PenumbraApiEc _cutsceneError = PenumbraApiEc.Success;
|
||||
|
||||
public GameStateIpcTester(DalamudPluginInterface pi)
|
||||
{
|
||||
_pi = pi;
|
||||
CharacterBaseCreating = CreatingCharacterBase.Subscriber(pi, UpdateLastCreated);
|
||||
CharacterBaseCreated = CreatedCharacterBase.Subscriber(pi, UpdateLastCreated2);
|
||||
GameObjectResourcePathResolved = IpcSubscribers.GameObjectResourcePathResolved.Subscriber(pi, UpdateGameObjectResourcePath);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CharacterBaseCreating.Dispose();
|
||||
CharacterBaseCreated.Dispose();
|
||||
GameObjectResourcePathResolved.Dispose();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Game State");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
if (ImGui.InputTextWithHint("##drawObject", "Draw Object Address..", ref _currentDrawObjectString, 16,
|
||||
ImGuiInputTextFlags.CharsHexadecimal))
|
||||
_currentDrawObject = nint.TryParse(_currentDrawObjectString, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
||||
out var tmp)
|
||||
? tmp
|
||||
: nint.Zero;
|
||||
|
||||
ImGui.InputInt("Cutscene Actor", ref _currentCutsceneActor, 0);
|
||||
ImGui.InputInt("Cutscene Parent", ref _currentCutsceneParent, 0);
|
||||
if (_cutsceneError is not PenumbraApiEc.Success)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted("Invalid Argument on last Call");
|
||||
}
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
IpcTester.DrawIntro(GetDrawObjectInfo.Label, "Draw Object Info");
|
||||
if (_currentDrawObject == nint.Zero)
|
||||
{
|
||||
ImGui.TextUnformatted("Invalid");
|
||||
}
|
||||
else
|
||||
{
|
||||
var (ptr, (collectionId, collectionName)) = new GetDrawObjectInfo(_pi).Invoke(_currentDrawObject);
|
||||
ImGui.TextUnformatted(ptr == nint.Zero ? $"No Actor Associated, {collectionName}" : $"{ptr:X}, {collectionName}");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
ImGui.TextUnformatted(collectionId.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(GetCutsceneParentIndex.Label, "Cutscene Parent");
|
||||
ImGui.TextUnformatted(new GetCutsceneParentIndex(_pi).Invoke(_currentCutsceneActor).ToString());
|
||||
|
||||
IpcTester.DrawIntro(SetCutsceneParentIndex.Label, "Cutscene Parent");
|
||||
if (ImGui.Button("Set Parent"))
|
||||
_cutsceneError = new SetCutsceneParentIndex(_pi)
|
||||
.Invoke(_currentCutsceneActor, _currentCutsceneParent);
|
||||
|
||||
IpcTester.DrawIntro(CreatingCharacterBase.Label, "Last Drawobject created");
|
||||
if (_lastCreatedGameObjectTime < DateTimeOffset.Now)
|
||||
ImGui.TextUnformatted(_lastCreatedDrawObject != nint.Zero
|
||||
? $"0x{_lastCreatedDrawObject:X} for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}"
|
||||
: $"NULL for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}");
|
||||
|
||||
IpcTester.DrawIntro(IpcSubscribers.GameObjectResourcePathResolved.Label, "Last GamePath resolved");
|
||||
if (_lastResolvedGamePathTime < DateTimeOffset.Now)
|
||||
ImGui.TextUnformatted(
|
||||
$"{_lastResolvedGamePath} -> {_lastResolvedFullPath} for <{_lastResolvedObject}> at {_lastResolvedGamePathTime}");
|
||||
}
|
||||
|
||||
private void UpdateLastCreated(nint gameObject, Guid _, nint _2, nint _3, nint _4)
|
||||
{
|
||||
_lastCreatedGameObjectName = GetObjectName(gameObject);
|
||||
_lastCreatedGameObjectTime = DateTimeOffset.Now;
|
||||
_lastCreatedDrawObject = nint.Zero;
|
||||
}
|
||||
|
||||
private void UpdateLastCreated2(nint gameObject, Guid _, nint drawObject)
|
||||
{
|
||||
_lastCreatedGameObjectName = GetObjectName(gameObject);
|
||||
_lastCreatedGameObjectTime = DateTimeOffset.Now;
|
||||
_lastCreatedDrawObject = drawObject;
|
||||
}
|
||||
|
||||
private void UpdateGameObjectResourcePath(nint gameObject, string gamePath, string fullPath)
|
||||
{
|
||||
_lastResolvedObject = GetObjectName(gameObject);
|
||||
_lastResolvedGamePath = gamePath;
|
||||
_lastResolvedFullPath = fullPath;
|
||||
_lastResolvedGamePathTime = DateTimeOffset.Now;
|
||||
}
|
||||
|
||||
private static unsafe string GetObjectName(nint gameObject)
|
||||
{
|
||||
var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject;
|
||||
var name = obj != null ? obj->Name : null;
|
||||
return name != null && *name != 0 ? new ByteString(name).ToString() : "Unknown";
|
||||
}
|
||||
}
|
||||
133
Penumbra/Api/IpcTester/IpcTester.cs
Normal file
133
Penumbra/Api/IpcTester/IpcTester.cs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Api;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class IpcTester(
|
||||
IpcProviders ipcProviders,
|
||||
IPenumbraApi api,
|
||||
PluginStateIpcTester pluginStateIpcTester,
|
||||
UiIpcTester uiIpcTester,
|
||||
RedrawingIpcTester redrawingIpcTester,
|
||||
GameStateIpcTester gameStateIpcTester,
|
||||
ResolveIpcTester resolveIpcTester,
|
||||
CollectionsIpcTester collectionsIpcTester,
|
||||
MetaIpcTester metaIpcTester,
|
||||
ModsIpcTester modsIpcTester,
|
||||
ModSettingsIpcTester modSettingsIpcTester,
|
||||
EditingIpcTester editingIpcTester,
|
||||
TemporaryIpcTester temporaryIpcTester,
|
||||
ResourceTreeIpcTester resourceTreeIpcTester,
|
||||
IFramework framework) : IUiService
|
||||
{
|
||||
private readonly IpcProviders _ipcProviders = ipcProviders;
|
||||
private DateTime _lastUpdate;
|
||||
private bool _subscribed = false;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
try
|
||||
{
|
||||
_lastUpdate = framework.LastUpdateUTC.AddSeconds(1);
|
||||
Subscribe();
|
||||
|
||||
ImGui.TextUnformatted($"API Version: {api.ApiVersion.Breaking}.{api.ApiVersion.Feature:D4}");
|
||||
collectionsIpcTester.Draw();
|
||||
editingIpcTester.Draw();
|
||||
gameStateIpcTester.Draw();
|
||||
metaIpcTester.Draw();
|
||||
modSettingsIpcTester.Draw();
|
||||
modsIpcTester.Draw();
|
||||
pluginStateIpcTester.Draw();
|
||||
redrawingIpcTester.Draw();
|
||||
resolveIpcTester.Draw();
|
||||
resourceTreeIpcTester.Draw();
|
||||
uiIpcTester.Draw();
|
||||
temporaryIpcTester.Draw();
|
||||
temporaryIpcTester.DrawCollections();
|
||||
temporaryIpcTester.DrawMods();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Error during IPC Tests:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DrawIntro(string label, string info)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(label);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(info);
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
|
||||
private void Subscribe()
|
||||
{
|
||||
if (_subscribed)
|
||||
return;
|
||||
|
||||
Penumbra.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester.");
|
||||
gameStateIpcTester.GameObjectResourcePathResolved.Enable();
|
||||
gameStateIpcTester.CharacterBaseCreated.Enable();
|
||||
gameStateIpcTester.CharacterBaseCreating.Enable();
|
||||
modSettingsIpcTester.SettingChanged.Enable();
|
||||
modsIpcTester.DeleteSubscriber.Enable();
|
||||
modsIpcTester.AddSubscriber.Enable();
|
||||
modsIpcTester.MoveSubscriber.Enable();
|
||||
pluginStateIpcTester.ModDirectoryChanged.Enable();
|
||||
pluginStateIpcTester.Initialized.Enable();
|
||||
pluginStateIpcTester.Disposed.Enable();
|
||||
pluginStateIpcTester.EnabledChange.Enable();
|
||||
redrawingIpcTester.Redrawn.Enable();
|
||||
uiIpcTester.PreSettingsTabBar.Enable();
|
||||
uiIpcTester.PreSettingsPanel.Enable();
|
||||
uiIpcTester.PostEnabled.Enable();
|
||||
uiIpcTester.PostSettingsPanelDraw.Enable();
|
||||
uiIpcTester.ChangedItemTooltip.Enable();
|
||||
uiIpcTester.ChangedItemClicked.Enable();
|
||||
|
||||
framework.Update += CheckUnsubscribe;
|
||||
_subscribed = true;
|
||||
}
|
||||
|
||||
private void CheckUnsubscribe(IFramework framework1)
|
||||
{
|
||||
if (_lastUpdate > framework.LastUpdateUTC)
|
||||
return;
|
||||
|
||||
Unsubscribe();
|
||||
framework.Update -= CheckUnsubscribe;
|
||||
}
|
||||
|
||||
private void Unsubscribe()
|
||||
{
|
||||
if (!_subscribed)
|
||||
return;
|
||||
|
||||
Penumbra.Log.Debug("[IPCTester] Unsubscribed from IPC events for IPC tester.");
|
||||
_subscribed = false;
|
||||
gameStateIpcTester.GameObjectResourcePathResolved.Disable();
|
||||
gameStateIpcTester.CharacterBaseCreated.Disable();
|
||||
gameStateIpcTester.CharacterBaseCreating.Disable();
|
||||
modSettingsIpcTester.SettingChanged.Disable();
|
||||
modsIpcTester.DeleteSubscriber.Disable();
|
||||
modsIpcTester.AddSubscriber.Disable();
|
||||
modsIpcTester.MoveSubscriber.Disable();
|
||||
pluginStateIpcTester.ModDirectoryChanged.Disable();
|
||||
pluginStateIpcTester.Initialized.Disable();
|
||||
pluginStateIpcTester.Disposed.Disable();
|
||||
pluginStateIpcTester.EnabledChange.Disable();
|
||||
redrawingIpcTester.Redrawn.Disable();
|
||||
uiIpcTester.PreSettingsTabBar.Disable();
|
||||
uiIpcTester.PreSettingsPanel.Disable();
|
||||
uiIpcTester.PostEnabled.Disable();
|
||||
uiIpcTester.PostSettingsPanelDraw.Disable();
|
||||
uiIpcTester.ChangedItemTooltip.Disable();
|
||||
uiIpcTester.ChangedItemClicked.Disable();
|
||||
}
|
||||
}
|
||||
38
Penumbra/Api/IpcTester/MetaIpcTester.cs
Normal file
38
Penumbra/Api/IpcTester/MetaIpcTester.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class MetaIpcTester(DalamudPluginInterface pi) : IUiService
|
||||
{
|
||||
private int _gameObjectIndex;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Meta");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
ImGui.InputInt("##metaIdx", ref _gameObjectIndex, 0, 0);
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
IpcTester.DrawIntro(GetPlayerMetaManipulations.Label, "Player Meta Manipulations");
|
||||
if (ImGui.Button("Copy to Clipboard##Player"))
|
||||
{
|
||||
var base64 = new GetPlayerMetaManipulations(pi).Invoke();
|
||||
ImGui.SetClipboardText(base64);
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(GetMetaManipulations.Label, "Game Object Manipulations");
|
||||
if (ImGui.Button("Copy to Clipboard##GameObject"))
|
||||
{
|
||||
var base64 = new GetMetaManipulations(pi).Invoke(_gameObjectIndex);
|
||||
ImGui.SetClipboardText(base64);
|
||||
}
|
||||
}
|
||||
}
|
||||
181
Penumbra/Api/IpcTester/ModSettingsIpcTester.cs
Normal file
181
Penumbra/Api/IpcTester/ModSettingsIpcTester.cs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
using Penumbra.UI;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class ModSettingsIpcTester : IUiService, IDisposable
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
public readonly EventSubscriber<ModSettingChange, Guid, string, bool> SettingChanged;
|
||||
|
||||
private PenumbraApiEc _lastSettingsError = PenumbraApiEc.Success;
|
||||
private ModSettingChange _lastSettingChangeType;
|
||||
private Guid _lastSettingChangeCollection = Guid.Empty;
|
||||
private string _lastSettingChangeMod = string.Empty;
|
||||
private bool _lastSettingChangeInherited;
|
||||
private DateTimeOffset _lastSettingChange;
|
||||
|
||||
private string _settingsModDirectory = string.Empty;
|
||||
private string _settingsModName = string.Empty;
|
||||
private Guid? _settingsCollection;
|
||||
private string _settingsCollectionName = string.Empty;
|
||||
private bool _settingsIgnoreInheritance;
|
||||
private bool _settingsInherit;
|
||||
private bool _settingsEnabled;
|
||||
private int _settingsPriority;
|
||||
private IReadOnlyDictionary<string, (string[], GroupType)>? _availableSettings;
|
||||
private Dictionary<string, List<string>>? _currentSettings;
|
||||
|
||||
public ModSettingsIpcTester(DalamudPluginInterface pi)
|
||||
{
|
||||
_pi = pi;
|
||||
SettingChanged = ModSettingChanged.Subscriber(pi, UpdateLastModSetting);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SettingChanged.Dispose();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Mod Settings");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
ImGui.InputTextWithHint("##settingsDir", "Mod Directory Name...", ref _settingsModDirectory, 100);
|
||||
ImGui.InputTextWithHint("##settingsName", "Mod Name...", ref _settingsModName, 100);
|
||||
ImGuiUtil.GuidInput("##settingsCollection", "Collection...", string.Empty, ref _settingsCollection, ref _settingsCollectionName);
|
||||
ImGui.Checkbox("Ignore Inheritance", ref _settingsIgnoreInheritance);
|
||||
var collection = _settingsCollection.GetValueOrDefault(Guid.Empty);
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
IpcTester.DrawIntro("Last Error", _lastSettingsError.ToString());
|
||||
|
||||
IpcTester.DrawIntro(ModSettingChanged.Label, "Last Mod Setting Changed");
|
||||
ImGui.TextUnformatted(_lastSettingChangeMod.Length > 0
|
||||
? $"{_lastSettingChangeType} of {_lastSettingChangeMod} in {_lastSettingChangeCollection}{(_lastSettingChangeInherited ? " (Inherited)" : string.Empty)} at {_lastSettingChange}"
|
||||
: "None");
|
||||
|
||||
IpcTester.DrawIntro(GetAvailableModSettings.Label, "Get Available Settings");
|
||||
if (ImGui.Button("Get##Available"))
|
||||
{
|
||||
_availableSettings = new GetAvailableModSettings(_pi).Invoke(_settingsModDirectory, _settingsModName);
|
||||
_lastSettingsError = _availableSettings == null ? PenumbraApiEc.ModMissing : PenumbraApiEc.Success;
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(GetCurrentModSettings.Label, "Get Current Settings");
|
||||
if (ImGui.Button("Get##Current"))
|
||||
{
|
||||
var ret = new GetCurrentModSettings(_pi)
|
||||
.Invoke(collection, _settingsModDirectory, _settingsModName, _settingsIgnoreInheritance);
|
||||
_lastSettingsError = ret.Item1;
|
||||
if (ret.Item1 == PenumbraApiEc.Success)
|
||||
{
|
||||
_settingsEnabled = ret.Item2?.Item1 ?? false;
|
||||
_settingsInherit = ret.Item2?.Item4 ?? true;
|
||||
_settingsPriority = ret.Item2?.Item2 ?? 0;
|
||||
_currentSettings = ret.Item2?.Item3;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentSettings = null;
|
||||
}
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(TryInheritMod.Label, "Inherit Mod");
|
||||
ImGui.Checkbox("##inherit", ref _settingsInherit);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Set##Inherit"))
|
||||
_lastSettingsError = new TryInheritMod(_pi)
|
||||
.Invoke(collection, _settingsModDirectory, _settingsInherit, _settingsModName);
|
||||
|
||||
IpcTester.DrawIntro(TrySetMod.Label, "Set Enabled");
|
||||
ImGui.Checkbox("##enabled", ref _settingsEnabled);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Set##Enabled"))
|
||||
_lastSettingsError = new TrySetMod(_pi)
|
||||
.Invoke(collection, _settingsModDirectory, _settingsEnabled, _settingsModName);
|
||||
|
||||
IpcTester.DrawIntro(TrySetModPriority.Label, "Set Priority");
|
||||
ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
|
||||
ImGui.DragInt("##Priority", ref _settingsPriority);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Set##Priority"))
|
||||
_lastSettingsError = new TrySetModPriority(_pi)
|
||||
.Invoke(collection, _settingsModDirectory, _settingsPriority, _settingsModName);
|
||||
|
||||
IpcTester.DrawIntro(CopyModSettings.Label, "Copy Mod Settings");
|
||||
if (ImGui.Button("Copy Settings"))
|
||||
_lastSettingsError = new CopyModSettings(_pi)
|
||||
.Invoke(_settingsCollection, _settingsModDirectory, _settingsModName);
|
||||
|
||||
ImGuiUtil.HoverTooltip("Copy settings from Mod Directory Name to Mod Name (as directory) in collection.");
|
||||
|
||||
IpcTester.DrawIntro(TrySetModSetting.Label, "Set Setting(s)");
|
||||
if (_availableSettings == null)
|
||||
return;
|
||||
|
||||
foreach (var (group, (list, type)) in _availableSettings)
|
||||
{
|
||||
using var id = ImRaii.PushId(group);
|
||||
var preview = list.Length > 0 ? list[0] : string.Empty;
|
||||
if (_currentSettings != null && _currentSettings.TryGetValue(group, out var current) && current.Count > 0)
|
||||
{
|
||||
preview = current[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
current = [];
|
||||
if (_currentSettings != null)
|
||||
_currentSettings[group] = current;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
|
||||
using (var c = ImRaii.Combo("##group", preview))
|
||||
{
|
||||
if (c)
|
||||
foreach (var s in list)
|
||||
{
|
||||
var contained = current.Contains(s);
|
||||
if (ImGui.Checkbox(s, ref contained))
|
||||
{
|
||||
if (contained)
|
||||
current.Add(s);
|
||||
else
|
||||
current.Remove(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Set##setting"))
|
||||
_lastSettingsError = type == GroupType.Single
|
||||
? new TrySetModSetting(_pi).Invoke(collection, _settingsModDirectory, group, current.Count > 0 ? current[0] : string.Empty,
|
||||
_settingsModName)
|
||||
: new TrySetModSettings(_pi).Invoke(collection, _settingsModDirectory, group, current.ToArray(), _settingsModName);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(group);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLastModSetting(ModSettingChange type, Guid collection, string mod, bool inherited)
|
||||
{
|
||||
_lastSettingChangeType = type;
|
||||
_lastSettingChangeCollection = collection;
|
||||
_lastSettingChangeMod = mod;
|
||||
_lastSettingChangeInherited = inherited;
|
||||
_lastSettingChange = DateTimeOffset.Now;
|
||||
}
|
||||
}
|
||||
154
Penumbra/Api/IpcTester/ModsIpcTester.cs
Normal file
154
Penumbra/Api/IpcTester/ModsIpcTester.cs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class ModsIpcTester : IUiService, IDisposable
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
|
||||
private string _modDirectory = string.Empty;
|
||||
private string _modName = string.Empty;
|
||||
private string _pathInput = string.Empty;
|
||||
private string _newInstallPath = string.Empty;
|
||||
private PenumbraApiEc _lastReloadEc;
|
||||
private PenumbraApiEc _lastAddEc;
|
||||
private PenumbraApiEc _lastDeleteEc;
|
||||
private PenumbraApiEc _lastSetPathEc;
|
||||
private PenumbraApiEc _lastInstallEc;
|
||||
private Dictionary<string, string> _mods = [];
|
||||
|
||||
public readonly EventSubscriber<string> DeleteSubscriber;
|
||||
public readonly EventSubscriber<string> AddSubscriber;
|
||||
public readonly EventSubscriber<string, string> MoveSubscriber;
|
||||
|
||||
private DateTimeOffset _lastDeletedModTime = DateTimeOffset.UnixEpoch;
|
||||
private string _lastDeletedMod = string.Empty;
|
||||
private DateTimeOffset _lastAddedModTime = DateTimeOffset.UnixEpoch;
|
||||
private string _lastAddedMod = string.Empty;
|
||||
private DateTimeOffset _lastMovedModTime = DateTimeOffset.UnixEpoch;
|
||||
private string _lastMovedModFrom = string.Empty;
|
||||
private string _lastMovedModTo = string.Empty;
|
||||
|
||||
public ModsIpcTester(DalamudPluginInterface pi)
|
||||
{
|
||||
_pi = pi;
|
||||
DeleteSubscriber = ModDeleted.Subscriber(pi, s =>
|
||||
{
|
||||
_lastDeletedModTime = DateTimeOffset.UtcNow;
|
||||
_lastDeletedMod = s;
|
||||
});
|
||||
AddSubscriber = ModAdded.Subscriber(pi, s =>
|
||||
{
|
||||
_lastAddedModTime = DateTimeOffset.UtcNow;
|
||||
_lastAddedMod = s;
|
||||
});
|
||||
MoveSubscriber = ModMoved.Subscriber(pi, (s1, s2) =>
|
||||
{
|
||||
_lastMovedModTime = DateTimeOffset.UtcNow;
|
||||
_lastMovedModFrom = s1;
|
||||
_lastMovedModTo = s2;
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DeleteSubscriber.Dispose();
|
||||
AddSubscriber.Dispose();
|
||||
MoveSubscriber.Dispose();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Mods");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
ImGui.InputTextWithHint("##install", "Install File Path...", ref _newInstallPath, 100);
|
||||
ImGui.InputTextWithHint("##modDir", "Mod Directory Name...", ref _modDirectory, 100);
|
||||
ImGui.InputTextWithHint("##modName", "Mod Name...", ref _modName, 100);
|
||||
ImGui.InputTextWithHint("##path", "New Path...", ref _pathInput, 100);
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
IpcTester.DrawIntro(GetModList.Label, "Mods");
|
||||
DrawModsPopup();
|
||||
if (ImGui.Button("Get##Mods"))
|
||||
{
|
||||
_mods = new GetModList(_pi).Invoke();
|
||||
ImGui.OpenPopup("Mods");
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(ReloadMod.Label, "Reload Mod");
|
||||
if (ImGui.Button("Reload"))
|
||||
_lastReloadEc = new ReloadMod(_pi).Invoke(_modDirectory, _modName);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastReloadEc.ToString());
|
||||
|
||||
IpcTester.DrawIntro(InstallMod.Label, "Install Mod");
|
||||
if (ImGui.Button("Install"))
|
||||
_lastInstallEc = new InstallMod(_pi).Invoke(_newInstallPath);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastInstallEc.ToString());
|
||||
|
||||
IpcTester.DrawIntro(AddMod.Label, "Add Mod");
|
||||
if (ImGui.Button("Add"))
|
||||
_lastAddEc = new AddMod(_pi).Invoke(_modDirectory);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastAddEc.ToString());
|
||||
|
||||
IpcTester.DrawIntro(DeleteMod.Label, "Delete Mod");
|
||||
if (ImGui.Button("Delete"))
|
||||
_lastDeleteEc = new DeleteMod(_pi).Invoke(_modDirectory, _modName);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastDeleteEc.ToString());
|
||||
|
||||
IpcTester.DrawIntro(GetModPath.Label, "Current Path");
|
||||
var (ec, path, def, nameDef) = new GetModPath(_pi).Invoke(_modDirectory, _modName);
|
||||
ImGui.TextUnformatted($"{path} ({(def ? "Custom" : "Default")} Path, {(nameDef ? "Custom" : "Default")} Name) [{ec}]");
|
||||
|
||||
IpcTester.DrawIntro(SetModPath.Label, "Set Path");
|
||||
if (ImGui.Button("Set"))
|
||||
_lastSetPathEc = new SetModPath(_pi).Invoke(_modDirectory, _pathInput, _modName);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastSetPathEc.ToString());
|
||||
|
||||
IpcTester.DrawIntro(ModDeleted.Label, "Last Mod Deleted");
|
||||
if (_lastDeletedModTime > DateTimeOffset.UnixEpoch)
|
||||
ImGui.TextUnformatted($"{_lastDeletedMod} at {_lastDeletedModTime}");
|
||||
|
||||
IpcTester.DrawIntro(ModAdded.Label, "Last Mod Added");
|
||||
if (_lastAddedModTime > DateTimeOffset.UnixEpoch)
|
||||
ImGui.TextUnformatted($"{_lastAddedMod} at {_lastAddedModTime}");
|
||||
|
||||
IpcTester.DrawIntro(ModMoved.Label, "Last Mod Moved");
|
||||
if (_lastMovedModTime > DateTimeOffset.UnixEpoch)
|
||||
ImGui.TextUnformatted($"{_lastMovedModFrom} -> {_lastMovedModTo} at {_lastMovedModTime}");
|
||||
}
|
||||
|
||||
private void DrawModsPopup()
|
||||
{
|
||||
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
|
||||
using var p = ImRaii.Popup("Mods");
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
foreach (var (modDir, modName) in _mods)
|
||||
ImGui.TextUnformatted($"{modDir}: {modName}");
|
||||
|
||||
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
132
Penumbra/Api/IpcTester/PluginStateIpcTester.cs
Normal file
132
Penumbra/Api/IpcTester/PluginStateIpcTester.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class PluginStateIpcTester : IUiService, IDisposable
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
public readonly EventSubscriber<string, bool> ModDirectoryChanged;
|
||||
public readonly EventSubscriber Initialized;
|
||||
public readonly EventSubscriber Disposed;
|
||||
public readonly EventSubscriber<bool> EnabledChange;
|
||||
|
||||
private string _currentConfiguration = string.Empty;
|
||||
private string _lastModDirectory = string.Empty;
|
||||
private bool _lastModDirectoryValid;
|
||||
private DateTimeOffset _lastModDirectoryTime = DateTimeOffset.MinValue;
|
||||
|
||||
private readonly List<DateTimeOffset> _initializedList = [];
|
||||
private readonly List<DateTimeOffset> _disposedList = [];
|
||||
|
||||
private DateTimeOffset _lastEnabledChange = DateTimeOffset.UnixEpoch;
|
||||
private bool? _lastEnabledValue;
|
||||
|
||||
public PluginStateIpcTester(DalamudPluginInterface pi)
|
||||
{
|
||||
_pi = pi;
|
||||
ModDirectoryChanged = IpcSubscribers.ModDirectoryChanged.Subscriber(pi, UpdateModDirectoryChanged);
|
||||
Initialized = IpcSubscribers.Initialized.Subscriber(pi, AddInitialized);
|
||||
Disposed = IpcSubscribers.Disposed.Subscriber(pi, AddDisposed);
|
||||
EnabledChange = IpcSubscribers.EnabledChange.Subscriber(pi, SetLastEnabled);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ModDirectoryChanged.Dispose();
|
||||
Initialized.Dispose();
|
||||
Disposed.Dispose();
|
||||
EnabledChange.Dispose();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Plugin State");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
DrawList(IpcSubscribers.Initialized.Label, "Last Initialized", _initializedList);
|
||||
DrawList(IpcSubscribers.Disposed.Label, "Last Disposed", _disposedList);
|
||||
|
||||
IpcTester.DrawIntro(ApiVersion.Label, "Current Version");
|
||||
var (breaking, features) = new ApiVersion(_pi).Invoke();
|
||||
ImGui.TextUnformatted($"{breaking}.{features:D4}");
|
||||
|
||||
IpcTester.DrawIntro(GetEnabledState.Label, "Current State");
|
||||
ImGui.TextUnformatted($"{new GetEnabledState(_pi).Invoke()}");
|
||||
|
||||
IpcTester.DrawIntro(IpcSubscribers.EnabledChange.Label, "Last Change");
|
||||
ImGui.TextUnformatted(_lastEnabledValue is { } v ? $"{_lastEnabledChange} (to {v})" : "Never");
|
||||
|
||||
DrawConfigPopup();
|
||||
IpcTester.DrawIntro(GetConfiguration.Label, "Configuration");
|
||||
if (ImGui.Button("Get"))
|
||||
{
|
||||
_currentConfiguration = new GetConfiguration(_pi).Invoke();
|
||||
ImGui.OpenPopup("Config Popup");
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(GetModDirectory.Label, "Current Mod Directory");
|
||||
ImGui.TextUnformatted(new GetModDirectory(_pi).Invoke());
|
||||
|
||||
IpcTester.DrawIntro(IpcSubscribers.ModDirectoryChanged.Label, "Last Mod Directory Change");
|
||||
ImGui.TextUnformatted(_lastModDirectoryTime > DateTimeOffset.MinValue
|
||||
? $"{_lastModDirectory} ({(_lastModDirectoryValid ? "Valid" : "Invalid")}) at {_lastModDirectoryTime}"
|
||||
: "None");
|
||||
|
||||
void DrawList(string label, string text, List<DateTimeOffset> list)
|
||||
{
|
||||
IpcTester.DrawIntro(label, text);
|
||||
if (list.Count == 0)
|
||||
{
|
||||
ImGui.TextUnformatted("Never");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted(list[^1].LocalDateTime.ToString(CultureInfo.CurrentCulture));
|
||||
if (list.Count > 1 && ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip(string.Join("\n",
|
||||
list.SkipLast(1).Select(t => t.LocalDateTime.ToString(CultureInfo.CurrentCulture))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawConfigPopup()
|
||||
{
|
||||
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
|
||||
using var popup = ImRaii.Popup("Config Popup");
|
||||
if (!popup)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
ImGuiUtil.TextWrapped(_currentConfiguration);
|
||||
}
|
||||
|
||||
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
private void UpdateModDirectoryChanged(string path, bool valid)
|
||||
=> (_lastModDirectory, _lastModDirectoryValid, _lastModDirectoryTime) = (path, valid, DateTimeOffset.Now);
|
||||
|
||||
private void AddInitialized()
|
||||
=> _initializedList.Add(DateTimeOffset.UtcNow);
|
||||
|
||||
private void AddDisposed()
|
||||
=> _disposedList.Add(DateTimeOffset.UtcNow);
|
||||
|
||||
private void SetLastEnabled(bool val)
|
||||
=> (_lastEnabledChange, _lastEnabledValue) = (DateTimeOffset.Now, val);
|
||||
}
|
||||
72
Penumbra/Api/IpcTester/RedrawingIpcTester.cs
Normal file
72
Penumbra/Api/IpcTester/RedrawingIpcTester.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.UI;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class RedrawingIpcTester : IUiService, IDisposable
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
private readonly ObjectManager _objects;
|
||||
public readonly EventSubscriber<nint, int> Redrawn;
|
||||
|
||||
private int _redrawIndex;
|
||||
private string _lastRedrawnString = "None";
|
||||
|
||||
public RedrawingIpcTester(DalamudPluginInterface pi, ObjectManager objects)
|
||||
{
|
||||
_pi = pi;
|
||||
_objects = objects;
|
||||
Redrawn = GameObjectRedrawn.Subscriber(_pi, SetLastRedrawn);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Redrawn.Dispose();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Redrawing");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
IpcTester.DrawIntro(RedrawObject.Label, "Redraw by Index");
|
||||
var tmp = _redrawIndex;
|
||||
ImGui.SetNextItemWidth(100 * UiHelpers.Scale);
|
||||
if (ImGui.DragInt("##redrawIndex", ref tmp, 0.1f, 0, _objects.TotalCount))
|
||||
_redrawIndex = Math.Clamp(tmp, 0, _objects.TotalCount);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Redraw##Index"))
|
||||
new RedrawObject(_pi).Invoke(_redrawIndex);
|
||||
|
||||
IpcTester.DrawIntro(RedrawAll.Label, "Redraw All");
|
||||
if (ImGui.Button("Redraw##All"))
|
||||
new RedrawAll(_pi).Invoke();
|
||||
|
||||
IpcTester.DrawIntro(GameObjectRedrawn.Label, "Last Redrawn Object:");
|
||||
ImGui.TextUnformatted(_lastRedrawnString);
|
||||
}
|
||||
|
||||
private void SetLastRedrawn(nint address, int index)
|
||||
{
|
||||
if (index < 0
|
||||
|| index > _objects.TotalCount
|
||||
|| address == nint.Zero
|
||||
|| _objects[index].Address != address)
|
||||
_lastRedrawnString = "Invalid";
|
||||
|
||||
_lastRedrawnString = $"{_objects[index].Utf8Name} (0x{address:X}, {index})";
|
||||
}
|
||||
}
|
||||
114
Penumbra/Api/IpcTester/ResolveIpcTester.cs
Normal file
114
Penumbra/Api/IpcTester/ResolveIpcTester.cs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class ResolveIpcTester(DalamudPluginInterface pi) : IUiService
|
||||
{
|
||||
private string _currentResolvePath = string.Empty;
|
||||
private string _currentReversePath = string.Empty;
|
||||
private int _currentReverseIdx;
|
||||
private Task<(string[], string[][])> _task = Task.FromResult<(string[], string[][])>(([], []));
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode("Resolving");
|
||||
if (!tree)
|
||||
return;
|
||||
|
||||
ImGui.InputTextWithHint("##resolvePath", "Resolve this game path...", ref _currentResolvePath, Utf8GamePath.MaxGamePathLength);
|
||||
ImGui.InputTextWithHint("##resolveInversePath", "Reverse-resolve this path...", ref _currentReversePath,
|
||||
Utf8GamePath.MaxGamePathLength);
|
||||
ImGui.InputInt("##resolveIdx", ref _currentReverseIdx, 0, 0);
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
IpcTester.DrawIntro(ResolveDefaultPath.Label, "Default Collection Resolve");
|
||||
if (_currentResolvePath.Length != 0)
|
||||
ImGui.TextUnformatted(new ResolveDefaultPath(pi).Invoke(_currentResolvePath));
|
||||
|
||||
IpcTester.DrawIntro(ResolveInterfacePath.Label, "Interface Collection Resolve");
|
||||
if (_currentResolvePath.Length != 0)
|
||||
ImGui.TextUnformatted(new ResolveInterfacePath(pi).Invoke(_currentResolvePath));
|
||||
|
||||
IpcTester.DrawIntro(ResolvePlayerPath.Label, "Player Collection Resolve");
|
||||
if (_currentResolvePath.Length != 0)
|
||||
ImGui.TextUnformatted(new ResolvePlayerPath(pi).Invoke(_currentResolvePath));
|
||||
|
||||
IpcTester.DrawIntro(ResolveGameObjectPath.Label, "Game Object Collection Resolve");
|
||||
if (_currentResolvePath.Length != 0)
|
||||
ImGui.TextUnformatted(new ResolveGameObjectPath(pi).Invoke(_currentResolvePath, _currentReverseIdx));
|
||||
|
||||
IpcTester.DrawIntro(ReverseResolvePlayerPath.Label, "Reversed Game Paths (Player)");
|
||||
if (_currentReversePath.Length > 0)
|
||||
{
|
||||
var list = new ReverseResolvePlayerPath(pi).Invoke(_currentReversePath);
|
||||
if (list.Length > 0)
|
||||
{
|
||||
ImGui.TextUnformatted(list[0]);
|
||||
if (list.Length > 1 && ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
|
||||
}
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(ReverseResolveGameObjectPath.Label, "Reversed Game Paths (Game Object)");
|
||||
if (_currentReversePath.Length > 0)
|
||||
{
|
||||
var list = new ReverseResolveGameObjectPath(pi).Invoke(_currentReversePath, _currentReverseIdx);
|
||||
if (list.Length > 0)
|
||||
{
|
||||
ImGui.TextUnformatted(list[0]);
|
||||
if (list.Length > 1 && ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
|
||||
}
|
||||
}
|
||||
|
||||
var forwardArray = _currentResolvePath.Length > 0
|
||||
? [_currentResolvePath]
|
||||
: Array.Empty<string>();
|
||||
var reverseArray = _currentReversePath.Length > 0
|
||||
? [_currentReversePath]
|
||||
: Array.Empty<string>();
|
||||
|
||||
IpcTester.DrawIntro(ResolvePlayerPaths.Label, "Resolved Paths (Player)");
|
||||
if (forwardArray.Length > 0 || reverseArray.Length > 0)
|
||||
{
|
||||
var ret = new ResolvePlayerPaths(pi).Invoke(forwardArray, reverseArray);
|
||||
ImGui.TextUnformatted(ConvertText(ret));
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(ResolvePlayerPathsAsync.Label, "Resolved Paths Async (Player)");
|
||||
if (ImGui.Button("Start"))
|
||||
_task = new ResolvePlayerPathsAsync(pi).Invoke(forwardArray, reverseArray);
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
ImGui.SameLine();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(_task.Status.ToString());
|
||||
if ((hovered || ImGui.IsItemHovered()) && _task.IsCompletedSuccessfully)
|
||||
ImGui.SetTooltip(ConvertText(_task.Result));
|
||||
return;
|
||||
|
||||
static string ConvertText((string[], string[][]) data)
|
||||
{
|
||||
var text = string.Empty;
|
||||
if (data.Item1.Length > 0)
|
||||
{
|
||||
if (data.Item2.Length > 0)
|
||||
text = $"Forward: {data.Item1[0]} | Reverse: {string.Join("; ", data.Item2[0])}.";
|
||||
else
|
||||
text = $"Forward: {data.Item1[0]}.";
|
||||
}
|
||||
else if (data.Item2.Length > 0)
|
||||
{
|
||||
text = $"Reverse: {string.Join("; ", data.Item2[0])}.";
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
349
Penumbra/Api/IpcTester/ResourceTreeIpcTester.cs
Normal file
349
Penumbra/Api/IpcTester/ResourceTreeIpcTester.cs
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class ResourceTreeIpcTester(DalamudPluginInterface pi, ObjectManager objects) : IUiService
|
||||
{
|
||||
private readonly Stopwatch _stopwatch = new();
|
||||
|
||||
private string _gameObjectIndices = "0";
|
||||
private ResourceType _type = ResourceType.Mtrl;
|
||||
private bool _withUiData;
|
||||
|
||||
private (string, Dictionary<string, HashSet<string>>?)[]? _lastGameObjectResourcePaths;
|
||||
private (string, Dictionary<string, HashSet<string>>?)[]? _lastPlayerResourcePaths;
|
||||
private (string, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)[]? _lastGameObjectResourcesOfType;
|
||||
private (string, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)[]? _lastPlayerResourcesOfType;
|
||||
private (string, ResourceTreeDto?)[]? _lastGameObjectResourceTrees;
|
||||
private (string, ResourceTreeDto)[]? _lastPlayerResourceTrees;
|
||||
private TimeSpan _lastCallDuration;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Resource Tree");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
ImGui.InputText("GameObject indices", ref _gameObjectIndices, 511);
|
||||
ImGuiUtil.GenericEnumCombo("Resource type", ImGui.CalcItemWidth(), _type, out _type, Enum.GetValues<ResourceType>());
|
||||
ImGui.Checkbox("Also get names and icons", ref _withUiData);
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
IpcTester.DrawIntro(GetGameObjectResourcePaths.Label, "Get GameObject resource paths");
|
||||
if (ImGui.Button("Get##GameObjectResourcePaths"))
|
||||
{
|
||||
var gameObjects = GetSelectedGameObjects();
|
||||
var subscriber = new GetGameObjectResourcePaths(pi);
|
||||
_stopwatch.Restart();
|
||||
var resourcePaths = subscriber.Invoke(gameObjects);
|
||||
|
||||
_lastCallDuration = _stopwatch.Elapsed;
|
||||
_lastGameObjectResourcePaths = gameObjects
|
||||
.Select(i => GameObjectToString(i))
|
||||
.Zip(resourcePaths)
|
||||
.ToArray();
|
||||
|
||||
ImGui.OpenPopup(nameof(GetGameObjectResourcePaths));
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(GetPlayerResourcePaths.Label, "Get local player resource paths");
|
||||
if (ImGui.Button("Get##PlayerResourcePaths"))
|
||||
{
|
||||
var subscriber = new GetPlayerResourcePaths(pi);
|
||||
_stopwatch.Restart();
|
||||
var resourcePaths = subscriber.Invoke();
|
||||
|
||||
_lastCallDuration = _stopwatch.Elapsed;
|
||||
_lastPlayerResourcePaths = resourcePaths
|
||||
.Select(pair => (GameObjectToString(pair.Key), pair.Value))
|
||||
.ToArray()!;
|
||||
|
||||
ImGui.OpenPopup(nameof(GetPlayerResourcePaths));
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(GetGameObjectResourcesOfType.Label, "Get GameObject resources of type");
|
||||
if (ImGui.Button("Get##GameObjectResourcesOfType"))
|
||||
{
|
||||
var gameObjects = GetSelectedGameObjects();
|
||||
var subscriber = new GetGameObjectResourcesOfType(pi);
|
||||
_stopwatch.Restart();
|
||||
var resourcesOfType = subscriber.Invoke(_type, _withUiData, gameObjects);
|
||||
|
||||
_lastCallDuration = _stopwatch.Elapsed;
|
||||
_lastGameObjectResourcesOfType = gameObjects
|
||||
.Select(i => GameObjectToString(i))
|
||||
.Zip(resourcesOfType)
|
||||
.ToArray();
|
||||
|
||||
ImGui.OpenPopup(nameof(GetGameObjectResourcesOfType));
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(GetPlayerResourcesOfType.Label, "Get local player resources of type");
|
||||
if (ImGui.Button("Get##PlayerResourcesOfType"))
|
||||
{
|
||||
var subscriber = new GetPlayerResourcesOfType(pi);
|
||||
_stopwatch.Restart();
|
||||
var resourcesOfType = subscriber.Invoke(_type, _withUiData);
|
||||
|
||||
_lastCallDuration = _stopwatch.Elapsed;
|
||||
_lastPlayerResourcesOfType = resourcesOfType
|
||||
.Select(pair => (GameObjectToString(pair.Key), (IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)pair.Value))
|
||||
.ToArray();
|
||||
|
||||
ImGui.OpenPopup(nameof(GetPlayerResourcesOfType));
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(GetGameObjectResourceTrees.Label, "Get GameObject resource trees");
|
||||
if (ImGui.Button("Get##GameObjectResourceTrees"))
|
||||
{
|
||||
var gameObjects = GetSelectedGameObjects();
|
||||
var subscriber = new GetGameObjectResourceTrees(pi);
|
||||
_stopwatch.Restart();
|
||||
var trees = subscriber.Invoke(_withUiData, gameObjects);
|
||||
|
||||
_lastCallDuration = _stopwatch.Elapsed;
|
||||
_lastGameObjectResourceTrees = gameObjects
|
||||
.Select(i => GameObjectToString(i))
|
||||
.Zip(trees)
|
||||
.ToArray();
|
||||
|
||||
ImGui.OpenPopup(nameof(GetGameObjectResourceTrees));
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(GetPlayerResourceTrees.Label, "Get local player resource trees");
|
||||
if (ImGui.Button("Get##PlayerResourceTrees"))
|
||||
{
|
||||
var subscriber = new GetPlayerResourceTrees(pi);
|
||||
_stopwatch.Restart();
|
||||
var trees = subscriber.Invoke(_withUiData);
|
||||
|
||||
_lastCallDuration = _stopwatch.Elapsed;
|
||||
_lastPlayerResourceTrees = trees
|
||||
.Select(pair => (GameObjectToString(pair.Key), pair.Value))
|
||||
.ToArray();
|
||||
|
||||
ImGui.OpenPopup(nameof(GetPlayerResourceTrees));
|
||||
}
|
||||
|
||||
DrawPopup(nameof(GetGameObjectResourcePaths), ref _lastGameObjectResourcePaths, DrawResourcePaths,
|
||||
_lastCallDuration);
|
||||
DrawPopup(nameof(GetPlayerResourcePaths), ref _lastPlayerResourcePaths!, DrawResourcePaths, _lastCallDuration);
|
||||
|
||||
DrawPopup(nameof(GetGameObjectResourcesOfType), ref _lastGameObjectResourcesOfType, DrawResourcesOfType,
|
||||
_lastCallDuration);
|
||||
DrawPopup(nameof(GetPlayerResourcesOfType), ref _lastPlayerResourcesOfType, DrawResourcesOfType,
|
||||
_lastCallDuration);
|
||||
|
||||
DrawPopup(nameof(GetGameObjectResourceTrees), ref _lastGameObjectResourceTrees, DrawResourceTrees,
|
||||
_lastCallDuration);
|
||||
DrawPopup(nameof(GetPlayerResourceTrees), ref _lastPlayerResourceTrees, DrawResourceTrees!, _lastCallDuration);
|
||||
}
|
||||
|
||||
private static void DrawPopup<T>(string popupId, ref T? result, Action<T> drawResult, TimeSpan duration) where T : class
|
||||
{
|
||||
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(1000, 500));
|
||||
using var popup = ImRaii.Popup(popupId);
|
||||
if (!popup)
|
||||
{
|
||||
result = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
drawResult(result);
|
||||
|
||||
ImGui.TextUnformatted($"Invoked in {duration.TotalMilliseconds} ms");
|
||||
|
||||
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
|
||||
{
|
||||
result = null;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawWithHeaders<T>((string, T?)[] result, Action<T> drawItem) where T : class
|
||||
{
|
||||
var firstSeen = new Dictionary<T, string>();
|
||||
foreach (var (label, item) in result)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
ImRaii.TreeNode($"{label}: null", ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstSeen.TryGetValue(item, out var firstLabel))
|
||||
{
|
||||
ImRaii.TreeNode($"{label}: same as {firstLabel}", ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
firstSeen.Add(item, label);
|
||||
|
||||
using var header = ImRaii.TreeNode(label);
|
||||
if (!header)
|
||||
continue;
|
||||
|
||||
drawItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawResourcePaths((string, Dictionary<string, HashSet<string>>?)[] result)
|
||||
{
|
||||
DrawWithHeaders(result, paths =>
|
||||
{
|
||||
using var table = ImRaii.Table(string.Empty, 2, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.6f);
|
||||
ImGui.TableSetupColumn("Game Paths", ImGuiTableColumnFlags.WidthStretch, 0.4f);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (var (actualPath, gamePaths) in paths)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(actualPath);
|
||||
ImGui.TableNextColumn();
|
||||
foreach (var gamePath in gamePaths)
|
||||
ImGui.TextUnformatted(gamePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawResourcesOfType((string, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)[] result)
|
||||
{
|
||||
DrawWithHeaders(result, resources =>
|
||||
{
|
||||
using var table = ImRaii.Table(string.Empty, _withUiData ? 3 : 2, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
ImGui.TableSetupColumn("Resource Handle", ImGuiTableColumnFlags.WidthStretch, 0.15f);
|
||||
ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, _withUiData ? 0.55f : 0.85f);
|
||||
if (_withUiData)
|
||||
ImGui.TableSetupColumn("Icon & Name", ImGuiTableColumnFlags.WidthStretch, 0.3f);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (var (resourceHandle, (actualPath, name, icon)) in resources)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
TextUnformattedMono($"0x{resourceHandle:X}");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(actualPath);
|
||||
if (_withUiData)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
TextUnformattedMono(icon.ToString());
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawResourceTrees((string, ResourceTreeDto?)[] result)
|
||||
{
|
||||
DrawWithHeaders(result, tree =>
|
||||
{
|
||||
ImGui.TextUnformatted($"Name: {tree.Name}\nRaceCode: {(GenderRace)tree.RaceCode}");
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, _withUiData ? 7 : 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
if (_withUiData)
|
||||
{
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 0.5f);
|
||||
ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthStretch, 0.1f);
|
||||
ImGui.TableSetupColumn("Icon", ImGuiTableColumnFlags.WidthStretch, 0.15f);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthStretch, 0.5f);
|
||||
}
|
||||
|
||||
ImGui.TableSetupColumn("Game Path", ImGuiTableColumnFlags.WidthStretch, 0.5f);
|
||||
ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.5f);
|
||||
ImGui.TableSetupColumn("Object Address", ImGuiTableColumnFlags.WidthStretch, 0.2f);
|
||||
ImGui.TableSetupColumn("Resource Handle", ImGuiTableColumnFlags.WidthStretch, 0.2f);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
void DrawNode(ResourceNodeDto node)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
var hasChildren = node.Children.Any();
|
||||
using var treeNode = ImRaii.TreeNode(
|
||||
$"{(_withUiData ? node.Name ?? "Unknown" : node.Type)}##{node.ObjectAddress:X8}",
|
||||
hasChildren
|
||||
? ImGuiTreeNodeFlags.SpanFullWidth
|
||||
: ImGuiTreeNodeFlags.SpanFullWidth | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen);
|
||||
if (_withUiData)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
TextUnformattedMono(node.Type.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
TextUnformattedMono(node.Icon.ToString());
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(node.GamePath ?? "Unknown");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(node.ActualPath);
|
||||
ImGui.TableNextColumn();
|
||||
TextUnformattedMono($"0x{node.ObjectAddress:X8}");
|
||||
ImGui.TableNextColumn();
|
||||
TextUnformattedMono($"0x{node.ResourceHandle:X8}");
|
||||
|
||||
if (treeNode)
|
||||
foreach (var child in node.Children)
|
||||
DrawNode(child);
|
||||
}
|
||||
|
||||
foreach (var node in tree.Nodes)
|
||||
DrawNode(node);
|
||||
});
|
||||
}
|
||||
|
||||
private static void TextUnformattedMono(string text)
|
||||
{
|
||||
using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImGui.TextUnformatted(text);
|
||||
}
|
||||
|
||||
private ushort[] GetSelectedGameObjects()
|
||||
=> _gameObjectIndices.Split(',')
|
||||
.SelectWhere(index => (ushort.TryParse(index.Trim(), out var i), i))
|
||||
.ToArray();
|
||||
|
||||
private unsafe string GameObjectToString(ObjectIndex gameObjectIndex)
|
||||
{
|
||||
var gameObject = objects[gameObjectIndex];
|
||||
|
||||
return gameObject.Valid
|
||||
? $"[{gameObjectIndex}] {gameObject.Utf8Name} ({(ObjectKind)gameObject.AsObject->ObjectKind})"
|
||||
: $"[{gameObjectIndex}] null";
|
||||
}
|
||||
}
|
||||
203
Penumbra/Api/IpcTester/TemporaryIpcTester.cs
Normal file
203
Penumbra/Api/IpcTester/TemporaryIpcTester.cs
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class TemporaryIpcTester(
|
||||
DalamudPluginInterface pi,
|
||||
ModManager modManager,
|
||||
CollectionManager collections,
|
||||
TempModManager tempMods,
|
||||
TempCollectionManager tempCollections,
|
||||
SaveService saveService,
|
||||
Configuration config)
|
||||
: IUiService
|
||||
{
|
||||
public Guid LastCreatedCollectionId = Guid.Empty;
|
||||
|
||||
private Guid? _tempGuid;
|
||||
private string _tempCollectionName = string.Empty;
|
||||
private string _tempCollectionGuidName = string.Empty;
|
||||
private string _tempModName = string.Empty;
|
||||
private string _tempGamePath = "test/game/path.mtrl";
|
||||
private string _tempFilePath = "test/success.mtrl";
|
||||
private string _tempManipulation = string.Empty;
|
||||
private PenumbraApiEc _lastTempError;
|
||||
private int _tempActorIndex;
|
||||
private bool _forceOverwrite;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Temporary");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
ImGui.InputTextWithHint("##tempCollection", "Collection Name...", ref _tempCollectionName, 128);
|
||||
ImGuiUtil.GuidInput("##guid", "Collection GUID...", string.Empty, ref _tempGuid, ref _tempCollectionGuidName);
|
||||
ImGui.InputInt("##tempActorIndex", ref _tempActorIndex, 0, 0);
|
||||
ImGui.InputTextWithHint("##tempMod", "Temporary Mod Name...", ref _tempModName, 32);
|
||||
ImGui.InputTextWithHint("##tempGame", "Game Path...", ref _tempGamePath, 256);
|
||||
ImGui.InputTextWithHint("##tempFile", "File Path...", ref _tempFilePath, 256);
|
||||
ImGui.InputTextWithHint("##tempManip", "Manipulation Base64 String...", ref _tempManipulation, 256);
|
||||
ImGui.Checkbox("Force Character Collection Overwrite", ref _forceOverwrite);
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
IpcTester.DrawIntro("Last Error", _lastTempError.ToString());
|
||||
ImGuiUtil.DrawTableColumn("Last Created Collection");
|
||||
ImGui.TableNextColumn();
|
||||
using (ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
ImGuiUtil.CopyOnClickSelectable(LastCreatedCollectionId.ToString());
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(CreateTemporaryCollection.Label, "Create Temporary Collection");
|
||||
if (ImGui.Button("Create##Collection"))
|
||||
{
|
||||
LastCreatedCollectionId = new CreateTemporaryCollection(pi).Invoke(_tempCollectionName);
|
||||
if (_tempGuid == null)
|
||||
{
|
||||
_tempGuid = LastCreatedCollectionId;
|
||||
_tempCollectionGuidName = LastCreatedCollectionId.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
var guid = _tempGuid.GetValueOrDefault(Guid.Empty);
|
||||
|
||||
IpcTester.DrawIntro(DeleteTemporaryCollection.Label, "Delete Temporary Collection");
|
||||
if (ImGui.Button("Delete##Collection"))
|
||||
_lastTempError = new DeleteTemporaryCollection(pi).Invoke(guid);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Delete Last##Collection"))
|
||||
_lastTempError = new DeleteTemporaryCollection(pi).Invoke(LastCreatedCollectionId);
|
||||
|
||||
IpcTester.DrawIntro(AssignTemporaryCollection.Label, "Assign Temporary Collection");
|
||||
if (ImGui.Button("Assign##NamedCollection"))
|
||||
_lastTempError = new AssignTemporaryCollection(pi).Invoke(guid, _tempActorIndex, _forceOverwrite);
|
||||
|
||||
IpcTester.DrawIntro(AddTemporaryMod.Label, "Add Temporary Mod to specific Collection");
|
||||
if (ImGui.Button("Add##Mod"))
|
||||
_lastTempError = new AddTemporaryMod(pi).Invoke(_tempModName, guid,
|
||||
new Dictionary<string, string> { { _tempGamePath, _tempFilePath } },
|
||||
_tempManipulation.Length > 0 ? _tempManipulation : string.Empty, int.MaxValue);
|
||||
|
||||
IpcTester.DrawIntro(CreateTemporaryCollection.Label, "Copy Existing Collection");
|
||||
if (ImGuiUtil.DrawDisabledButton("Copy##Collection", Vector2.Zero,
|
||||
"Copies the effective list from the collection named in Temporary Mod Name...",
|
||||
!collections.Storage.ByName(_tempModName, out var copyCollection))
|
||||
&& 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);
|
||||
_lastTempError = new AddTemporaryMod(pi).Invoke(_tempModName, guid, files, manips, 999);
|
||||
}
|
||||
|
||||
IpcTester.DrawIntro(AddTemporaryModAll.Label, "Add Temporary Mod to all Collections");
|
||||
if (ImGui.Button("Add##All"))
|
||||
_lastTempError = new AddTemporaryModAll(pi).Invoke(_tempModName,
|
||||
new Dictionary<string, string> { { _tempGamePath, _tempFilePath } },
|
||||
_tempManipulation.Length > 0 ? _tempManipulation : string.Empty, int.MaxValue);
|
||||
|
||||
IpcTester.DrawIntro(RemoveTemporaryMod.Label, "Remove Temporary Mod from specific Collection");
|
||||
if (ImGui.Button("Remove##Mod"))
|
||||
_lastTempError = new RemoveTemporaryMod(pi).Invoke(_tempModName, guid, int.MaxValue);
|
||||
|
||||
IpcTester.DrawIntro(RemoveTemporaryModAll.Label, "Remove Temporary Mod from all Collections");
|
||||
if (ImGui.Button("Remove##ModAll"))
|
||||
_lastTempError = new RemoveTemporaryModAll(pi).Invoke(_tempModName, int.MaxValue);
|
||||
}
|
||||
|
||||
public void DrawCollections()
|
||||
{
|
||||
using var collTree = ImRaii.TreeNode("Temporary Collections##TempCollections");
|
||||
if (!collTree)
|
||||
return;
|
||||
|
||||
using var table = ImRaii.Table("##collTree", 6, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
foreach (var (collection, idx) in tempCollections.Values.WithIndex())
|
||||
{
|
||||
using var id = ImRaii.PushId(idx);
|
||||
ImGui.TableNextColumn();
|
||||
var character = tempCollections.Collections.Where(p => p.Collection == collection).Select(p => p.DisplayName)
|
||||
.FirstOrDefault()
|
||||
?? "Unknown";
|
||||
if (ImGui.Button("Save##Collection"))
|
||||
TemporaryMod.SaveTempCollection(config, saveService, modManager, collection, character);
|
||||
|
||||
using (ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.CopyOnClickSelectable(collection.Identifier);
|
||||
}
|
||||
|
||||
ImGuiUtil.DrawTableColumn(collection.Name);
|
||||
ImGuiUtil.DrawTableColumn(collection.ResolvedFiles.Count.ToString());
|
||||
ImGuiUtil.DrawTableColumn(collection.MetaCache?.Count.ToString() ?? "0");
|
||||
ImGuiUtil.DrawTableColumn(string.Join(", ",
|
||||
tempCollections.Collections.Where(p => p.Collection == collection).Select(c => c.DisplayName)));
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawMods()
|
||||
{
|
||||
using var modTree = ImRaii.TreeNode("Temporary Mods##TempMods");
|
||||
if (!modTree)
|
||||
return;
|
||||
|
||||
using var table = ImRaii.Table("##modTree", 5, ImGuiTableFlags.SizingFixedFit);
|
||||
|
||||
void PrintList(string collectionName, IReadOnlyList<TemporaryMod> list)
|
||||
{
|
||||
foreach (var mod in list)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(mod.Name);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(mod.Priority.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(collectionName);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(mod.Default.Files.Count.ToString());
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
using var tt = ImRaii.Tooltip();
|
||||
foreach (var (path, file) in mod.Default.Files)
|
||||
ImGui.TextUnformatted($"{path} -> {file}");
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(mod.TotalManipulations.ToString());
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
using var tt = ImRaii.Tooltip();
|
||||
foreach (var manip in mod.Default.Manipulations)
|
||||
ImGui.TextUnformatted(manip.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (table)
|
||||
{
|
||||
PrintList("All", tempMods.ModsForAllCollections);
|
||||
foreach (var (collection, list) in tempMods.Mods)
|
||||
PrintList(collection.Name, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
128
Penumbra/Api/IpcTester/UiIpcTester.cs
Normal file
128
Penumbra/Api/IpcTester/UiIpcTester.cs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
using Penumbra.Communication;
|
||||
|
||||
namespace Penumbra.Api.IpcTester;
|
||||
|
||||
public class UiIpcTester : IUiService, IDisposable
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
public readonly EventSubscriber<string, float, float> PreSettingsTabBar;
|
||||
public readonly EventSubscriber<string> PreSettingsPanel;
|
||||
public readonly EventSubscriber<string> PostEnabled;
|
||||
public readonly EventSubscriber<string> PostSettingsPanelDraw;
|
||||
public readonly EventSubscriber<ChangedItemType, uint> ChangedItemTooltip;
|
||||
public readonly EventSubscriber<MouseButton, ChangedItemType, uint> ChangedItemClicked;
|
||||
|
||||
private string _lastDrawnMod = string.Empty;
|
||||
private DateTimeOffset _lastDrawnModTime = DateTimeOffset.MinValue;
|
||||
private bool _subscribedToTooltip;
|
||||
private bool _subscribedToClick;
|
||||
private string _lastClicked = string.Empty;
|
||||
private string _lastHovered = string.Empty;
|
||||
private TabType _selectTab = TabType.None;
|
||||
private string _modName = string.Empty;
|
||||
private PenumbraApiEc _ec = PenumbraApiEc.Success;
|
||||
|
||||
public UiIpcTester(DalamudPluginInterface pi)
|
||||
{
|
||||
_pi = pi;
|
||||
PreSettingsTabBar = IpcSubscribers.PreSettingsTabBarDraw.Subscriber(pi, UpdateLastDrawnMod);
|
||||
PreSettingsPanel = IpcSubscribers.PreSettingsPanelDraw.Subscriber(pi, UpdateLastDrawnMod);
|
||||
PostEnabled = IpcSubscribers.PostEnabledDraw.Subscriber(pi, UpdateLastDrawnMod);
|
||||
PostSettingsPanelDraw = IpcSubscribers.PostSettingsPanelDraw.Subscriber(pi, UpdateLastDrawnMod);
|
||||
ChangedItemTooltip = IpcSubscribers.ChangedItemTooltip.Subscriber(pi, AddedTooltip);
|
||||
ChangedItemClicked = IpcSubscribers.ChangedItemClicked.Subscriber(pi, AddedClick);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
PreSettingsTabBar.Dispose();
|
||||
PreSettingsPanel.Dispose();
|
||||
PostEnabled.Dispose();
|
||||
PostSettingsPanelDraw.Dispose();
|
||||
ChangedItemTooltip.Dispose();
|
||||
ChangedItemClicked.Dispose();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("UI");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
using (var combo = ImRaii.Combo("Tab to Open at", _selectTab.ToString()))
|
||||
{
|
||||
if (combo)
|
||||
foreach (var val in Enum.GetValues<TabType>())
|
||||
{
|
||||
if (ImGui.Selectable(val.ToString(), _selectTab == val))
|
||||
_selectTab = val;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.InputTextWithHint("##openMod", "Mod to Open at...", ref _modName, 256);
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
IpcTester.DrawIntro(IpcSubscribers.PostSettingsPanelDraw.Label, "Last Drawn Mod");
|
||||
ImGui.TextUnformatted(_lastDrawnMod.Length > 0 ? $"{_lastDrawnMod} at {_lastDrawnModTime}" : "None");
|
||||
|
||||
IpcTester.DrawIntro(IpcSubscribers.ChangedItemTooltip.Label, "Add Tooltip");
|
||||
if (ImGui.Checkbox("##tooltip", ref _subscribedToTooltip))
|
||||
{
|
||||
if (_subscribedToTooltip)
|
||||
ChangedItemTooltip.Enable();
|
||||
else
|
||||
ChangedItemTooltip.Disable();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastHovered);
|
||||
|
||||
IpcTester.DrawIntro(IpcSubscribers.ChangedItemClicked.Label, "Subscribe Click");
|
||||
if (ImGui.Checkbox("##click", ref _subscribedToClick))
|
||||
{
|
||||
if (_subscribedToClick)
|
||||
ChangedItemClicked.Enable();
|
||||
else
|
||||
ChangedItemClicked.Disable();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_lastClicked);
|
||||
IpcTester.DrawIntro(OpenMainWindow.Label, "Open Mod Window");
|
||||
if (ImGui.Button("Open##window"))
|
||||
_ec = new OpenMainWindow(_pi).Invoke(_selectTab, _modName, _modName);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_ec.ToString());
|
||||
|
||||
IpcTester.DrawIntro(CloseMainWindow.Label, "Close Mod Window");
|
||||
if (ImGui.Button("Close##window"))
|
||||
new CloseMainWindow(_pi).Invoke();
|
||||
}
|
||||
|
||||
private void UpdateLastDrawnMod(string name)
|
||||
=> (_lastDrawnMod, _lastDrawnModTime) = (name, DateTimeOffset.Now);
|
||||
|
||||
private void UpdateLastDrawnMod(string name, float _1, float _2)
|
||||
=> (_lastDrawnMod, _lastDrawnModTime) = (name, DateTimeOffset.Now);
|
||||
|
||||
private void AddedTooltip(ChangedItemType type, uint id)
|
||||
{
|
||||
_lastHovered = $"{type} {id} at {DateTime.UtcNow.ToLocalTime().ToString(CultureInfo.CurrentCulture)}";
|
||||
ImGui.TextUnformatted("IPC Test Successful");
|
||||
}
|
||||
|
||||
private void AddedClick(MouseButton button, ChangedItemType type, uint id)
|
||||
{
|
||||
_lastClicked = $"{button}-click on {type} {id} at {DateTime.UtcNow.ToLocalTime().ToString(CultureInfo.CurrentCulture)}";
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,435 +0,0 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
using CurrentSettings = ValueTuple<PenumbraApiEc, (bool, int, IDictionary<string, IList<string>>, bool)?>;
|
||||
|
||||
public class PenumbraIpcProviders : IDisposable
|
||||
{
|
||||
internal readonly IPenumbraApi Api;
|
||||
|
||||
// Plugin State
|
||||
internal readonly EventProvider Initialized;
|
||||
internal readonly EventProvider Disposed;
|
||||
internal readonly FuncProvider<int> ApiVersion;
|
||||
internal readonly FuncProvider<(int Breaking, int Features)> ApiVersions;
|
||||
internal readonly FuncProvider<bool> GetEnabledState;
|
||||
internal readonly EventProvider<bool> EnabledChange;
|
||||
|
||||
// Configuration
|
||||
internal readonly FuncProvider<string> GetModDirectory;
|
||||
internal readonly FuncProvider<string> GetConfiguration;
|
||||
internal readonly EventProvider<string, bool> ModDirectoryChanged;
|
||||
|
||||
// UI
|
||||
internal readonly EventProvider<string, float, float> PreSettingsTabBarDraw;
|
||||
internal readonly EventProvider<string> PreSettingsDraw;
|
||||
internal readonly EventProvider<string> PostEnabledDraw;
|
||||
internal readonly EventProvider<string> PostSettingsDraw;
|
||||
internal readonly EventProvider<ChangedItemType, uint> ChangedItemTooltip;
|
||||
internal readonly EventProvider<MouseButton, ChangedItemType, uint> ChangedItemClick;
|
||||
internal readonly FuncProvider<TabType, string, string, PenumbraApiEc> OpenMainWindow;
|
||||
internal readonly ActionProvider CloseMainWindow;
|
||||
|
||||
// Redrawing
|
||||
internal readonly ActionProvider<RedrawType> RedrawAll;
|
||||
internal readonly ActionProvider<GameObject, RedrawType> RedrawObject;
|
||||
internal readonly ActionProvider<int, RedrawType> RedrawObjectByIndex;
|
||||
internal readonly ActionProvider<string, RedrawType> RedrawObjectByName;
|
||||
internal readonly EventProvider<nint, int> GameObjectRedrawn;
|
||||
|
||||
// Game State
|
||||
internal readonly FuncProvider<nint, (nint, string)> GetDrawObjectInfo;
|
||||
internal readonly FuncProvider<int, int> GetCutsceneParentIndex;
|
||||
internal readonly FuncProvider<int, int, PenumbraApiEc> SetCutsceneParentIndex;
|
||||
internal readonly EventProvider<nint, string, nint, nint, nint> CreatingCharacterBase;
|
||||
internal readonly EventProvider<nint, string, nint> CreatedCharacterBase;
|
||||
internal readonly EventProvider<nint, string, string> GameObjectResourcePathResolved;
|
||||
|
||||
// Resolve
|
||||
internal readonly FuncProvider<string, string> ResolveDefaultPath;
|
||||
internal readonly FuncProvider<string, string> ResolveInterfacePath;
|
||||
internal readonly FuncProvider<string, string> ResolvePlayerPath;
|
||||
internal readonly FuncProvider<string, int, string> ResolveGameObjectPath;
|
||||
internal readonly FuncProvider<string, string, string> ResolveCharacterPath;
|
||||
internal readonly FuncProvider<string, string, string[]> ReverseResolvePath;
|
||||
internal readonly FuncProvider<string, int, string[]> ReverseResolveGameObjectPath;
|
||||
internal readonly FuncProvider<string, string[]> ReverseResolvePlayerPath;
|
||||
internal readonly FuncProvider<string[], string[], (string[], string[][])> ResolvePlayerPaths;
|
||||
internal readonly FuncProvider<string[], string[], Task<(string[], string[][])>> ResolvePlayerPathsAsync;
|
||||
|
||||
// Collections
|
||||
internal readonly FuncProvider<IList<string>> GetCollections;
|
||||
internal readonly FuncProvider<string> GetCurrentCollectionName;
|
||||
internal readonly FuncProvider<string> GetDefaultCollectionName;
|
||||
internal readonly FuncProvider<string> GetInterfaceCollectionName;
|
||||
internal readonly FuncProvider<string, (string, bool)> GetCharacterCollectionName;
|
||||
internal readonly FuncProvider<ApiCollectionType, string> GetCollectionForType;
|
||||
internal readonly FuncProvider<ApiCollectionType, string, bool, bool, (PenumbraApiEc, string)> SetCollectionForType;
|
||||
internal readonly FuncProvider<int, (bool, bool, string)> GetCollectionForObject;
|
||||
internal readonly FuncProvider<int, string, bool, bool, (PenumbraApiEc, string)> SetCollectionForObject;
|
||||
internal readonly FuncProvider<string, IReadOnlyDictionary<string, object?>> GetChangedItems;
|
||||
|
||||
// Meta
|
||||
internal readonly FuncProvider<string> GetPlayerMetaManipulations;
|
||||
internal readonly FuncProvider<string, string> GetMetaManipulations;
|
||||
internal readonly FuncProvider<int, string> GetGameObjectMetaManipulations;
|
||||
|
||||
// Mods
|
||||
internal readonly FuncProvider<IList<(string, string)>> GetMods;
|
||||
internal readonly FuncProvider<string, string, PenumbraApiEc> ReloadMod;
|
||||
internal readonly FuncProvider<string, PenumbraApiEc> InstallMod;
|
||||
internal readonly FuncProvider<string, PenumbraApiEc> AddMod;
|
||||
internal readonly FuncProvider<string, string, PenumbraApiEc> DeleteMod;
|
||||
internal readonly FuncProvider<string, string, (PenumbraApiEc, string, bool)> GetModPath;
|
||||
internal readonly FuncProvider<string, string, string, PenumbraApiEc> SetModPath;
|
||||
internal readonly EventProvider<string> ModDeleted;
|
||||
internal readonly EventProvider<string> ModAdded;
|
||||
internal readonly EventProvider<string, string> ModMoved;
|
||||
|
||||
// ModSettings
|
||||
internal readonly FuncProvider<string, string, IDictionary<string, (IList<string>, GroupType)>?> GetAvailableModSettings;
|
||||
internal readonly FuncProvider<string, string, string, bool, CurrentSettings> GetCurrentModSettings;
|
||||
internal readonly FuncProvider<string, string, string, bool, PenumbraApiEc> TryInheritMod;
|
||||
internal readonly FuncProvider<string, string, string, bool, PenumbraApiEc> TrySetMod;
|
||||
internal readonly FuncProvider<string, string, string, int, PenumbraApiEc> TrySetModPriority;
|
||||
internal readonly FuncProvider<string, string, string, string, string, PenumbraApiEc> TrySetModSetting;
|
||||
internal readonly FuncProvider<string, string, string, string, IReadOnlyList<string>, PenumbraApiEc> TrySetModSettings;
|
||||
internal readonly EventProvider<ModSettingChange, string, string, bool> ModSettingChanged;
|
||||
internal readonly FuncProvider<string, string, string, PenumbraApiEc> CopyModSettings;
|
||||
|
||||
// Editing
|
||||
internal readonly FuncProvider<string, string, TextureType, bool, Task> ConvertTextureFile;
|
||||
internal readonly FuncProvider<byte[], int, string, TextureType, bool, Task> ConvertTextureData;
|
||||
|
||||
// Temporary
|
||||
internal readonly FuncProvider<string, string, bool, (PenumbraApiEc, string)> CreateTemporaryCollection;
|
||||
internal readonly FuncProvider<string, PenumbraApiEc> RemoveTemporaryCollection;
|
||||
internal readonly FuncProvider<string, PenumbraApiEc> CreateNamedTemporaryCollection;
|
||||
internal readonly FuncProvider<string, PenumbraApiEc> RemoveTemporaryCollectionByName;
|
||||
internal readonly FuncProvider<string, int, bool, PenumbraApiEc> AssignTemporaryCollection;
|
||||
internal readonly FuncProvider<string, Dictionary<string, string>, string, int, PenumbraApiEc> AddTemporaryModAll;
|
||||
internal readonly FuncProvider<string, string, Dictionary<string, string>, string, int, PenumbraApiEc> AddTemporaryMod;
|
||||
internal readonly FuncProvider<string, int, PenumbraApiEc> RemoveTemporaryModAll;
|
||||
internal readonly FuncProvider<string, string, int, PenumbraApiEc> RemoveTemporaryMod;
|
||||
|
||||
// Resource Tree
|
||||
internal readonly FuncProvider<ushort[], IReadOnlyDictionary<string, string[]>?[]> GetGameObjectResourcePaths;
|
||||
internal readonly FuncProvider<IReadOnlyDictionary<ushort, IReadOnlyDictionary<string, string[]>>> GetPlayerResourcePaths;
|
||||
|
||||
internal readonly FuncProvider<ResourceType, bool, ushort[], IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?[]>
|
||||
GetGameObjectResourcesOfType;
|
||||
|
||||
internal readonly
|
||||
FuncProvider<ResourceType, bool, IReadOnlyDictionary<ushort, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>>>
|
||||
GetPlayerResourcesOfType;
|
||||
|
||||
internal readonly FuncProvider<bool, ushort[], Ipc.ResourceTree?[]> GetGameObjectResourceTrees;
|
||||
internal readonly FuncProvider<bool, IReadOnlyDictionary<ushort, Ipc.ResourceTree>> GetPlayerResourceTrees;
|
||||
|
||||
public PenumbraIpcProviders(DalamudPluginInterface pi, IPenumbraApi api, ModManager modManager, CollectionManager collections,
|
||||
TempModManager tempMods, TempCollectionManager tempCollections, SaveService saveService, Configuration config)
|
||||
{
|
||||
Api = api;
|
||||
|
||||
// Plugin State
|
||||
Initialized = Ipc.Initialized.Provider(pi);
|
||||
Disposed = Ipc.Disposed.Provider(pi);
|
||||
ApiVersion = Ipc.ApiVersion.Provider(pi, DeprecatedVersion);
|
||||
ApiVersions = Ipc.ApiVersions.Provider(pi, () => Api.ApiVersion);
|
||||
GetEnabledState = Ipc.GetEnabledState.Provider(pi, Api.GetEnabledState);
|
||||
EnabledChange =
|
||||
Ipc.EnabledChange.Provider(pi, () => Api.EnabledChange += EnabledChangeEvent, () => Api.EnabledChange -= EnabledChangeEvent);
|
||||
|
||||
// Configuration
|
||||
GetModDirectory = Ipc.GetModDirectory.Provider(pi, Api.GetModDirectory);
|
||||
GetConfiguration = Ipc.GetConfiguration.Provider(pi, Api.GetConfiguration);
|
||||
ModDirectoryChanged = Ipc.ModDirectoryChanged.Provider(pi, a => Api.ModDirectoryChanged += a, a => Api.ModDirectoryChanged -= a);
|
||||
|
||||
// UI
|
||||
PreSettingsTabBarDraw =
|
||||
Ipc.PreSettingsTabBarDraw.Provider(pi, a => Api.PreSettingsTabBarDraw += a, a => Api.PreSettingsTabBarDraw -= a);
|
||||
PreSettingsDraw = Ipc.PreSettingsDraw.Provider(pi, a => Api.PreSettingsPanelDraw += a, a => Api.PreSettingsPanelDraw -= a);
|
||||
PostEnabledDraw =
|
||||
Ipc.PostEnabledDraw.Provider(pi, a => Api.PostEnabledDraw += a, a => Api.PostEnabledDraw -= a);
|
||||
PostSettingsDraw = Ipc.PostSettingsDraw.Provider(pi, a => Api.PostSettingsPanelDraw += a, a => Api.PostSettingsPanelDraw -= a);
|
||||
ChangedItemTooltip =
|
||||
Ipc.ChangedItemTooltip.Provider(pi, () => Api.ChangedItemTooltip += OnTooltip, () => Api.ChangedItemTooltip -= OnTooltip);
|
||||
ChangedItemClick = Ipc.ChangedItemClick.Provider(pi, () => Api.ChangedItemClicked += OnClick, () => Api.ChangedItemClicked -= OnClick);
|
||||
OpenMainWindow = Ipc.OpenMainWindow.Provider(pi, Api.OpenMainWindow);
|
||||
CloseMainWindow = Ipc.CloseMainWindow.Provider(pi, Api.CloseMainWindow);
|
||||
|
||||
// Redrawing
|
||||
RedrawAll = Ipc.RedrawAll.Provider(pi, Api.RedrawAll);
|
||||
RedrawObject = Ipc.RedrawObject.Provider(pi, Api.RedrawObject);
|
||||
RedrawObjectByIndex = Ipc.RedrawObjectByIndex.Provider(pi, Api.RedrawObject);
|
||||
RedrawObjectByName = Ipc.RedrawObjectByName.Provider(pi, Api.RedrawObject);
|
||||
GameObjectRedrawn = Ipc.GameObjectRedrawn.Provider(pi, () => Api.GameObjectRedrawn += OnGameObjectRedrawn,
|
||||
() => Api.GameObjectRedrawn -= OnGameObjectRedrawn);
|
||||
|
||||
// Game State
|
||||
GetDrawObjectInfo = Ipc.GetDrawObjectInfo.Provider(pi, Api.GetDrawObjectInfo);
|
||||
GetCutsceneParentIndex = Ipc.GetCutsceneParentIndex.Provider(pi, Api.GetCutsceneParentIndex);
|
||||
SetCutsceneParentIndex = Ipc.SetCutsceneParentIndex.Provider(pi, Api.SetCutsceneParentIndex);
|
||||
CreatingCharacterBase = Ipc.CreatingCharacterBase.Provider(pi,
|
||||
() => Api.CreatingCharacterBase += CreatingCharacterBaseEvent,
|
||||
() => Api.CreatingCharacterBase -= CreatingCharacterBaseEvent);
|
||||
CreatedCharacterBase = Ipc.CreatedCharacterBase.Provider(pi,
|
||||
() => Api.CreatedCharacterBase += CreatedCharacterBaseEvent,
|
||||
() => Api.CreatedCharacterBase -= CreatedCharacterBaseEvent);
|
||||
GameObjectResourcePathResolved = Ipc.GameObjectResourcePathResolved.Provider(pi,
|
||||
() => Api.GameObjectResourceResolved += GameObjectResourceResolvedEvent,
|
||||
() => Api.GameObjectResourceResolved -= GameObjectResourceResolvedEvent);
|
||||
|
||||
// Resolve
|
||||
ResolveDefaultPath = Ipc.ResolveDefaultPath.Provider(pi, Api.ResolveDefaultPath);
|
||||
ResolveInterfacePath = Ipc.ResolveInterfacePath.Provider(pi, Api.ResolveInterfacePath);
|
||||
ResolvePlayerPath = Ipc.ResolvePlayerPath.Provider(pi, Api.ResolvePlayerPath);
|
||||
ResolveGameObjectPath = Ipc.ResolveGameObjectPath.Provider(pi, Api.ResolveGameObjectPath);
|
||||
ResolveCharacterPath = Ipc.ResolveCharacterPath.Provider(pi, Api.ResolvePath);
|
||||
ReverseResolvePath = Ipc.ReverseResolvePath.Provider(pi, Api.ReverseResolvePath);
|
||||
ReverseResolveGameObjectPath = Ipc.ReverseResolveGameObjectPath.Provider(pi, Api.ReverseResolveGameObjectPath);
|
||||
ReverseResolvePlayerPath = Ipc.ReverseResolvePlayerPath.Provider(pi, Api.ReverseResolvePlayerPath);
|
||||
ResolvePlayerPaths = Ipc.ResolvePlayerPaths.Provider(pi, Api.ResolvePlayerPaths);
|
||||
ResolvePlayerPathsAsync = Ipc.ResolvePlayerPathsAsync.Provider(pi, Api.ResolvePlayerPathsAsync);
|
||||
|
||||
// Collections
|
||||
GetCollections = Ipc.GetCollections.Provider(pi, Api.GetCollections);
|
||||
GetCurrentCollectionName = Ipc.GetCurrentCollectionName.Provider(pi, Api.GetCurrentCollection);
|
||||
GetDefaultCollectionName = Ipc.GetDefaultCollectionName.Provider(pi, Api.GetDefaultCollection);
|
||||
GetInterfaceCollectionName = Ipc.GetInterfaceCollectionName.Provider(pi, Api.GetInterfaceCollection);
|
||||
GetCharacterCollectionName = Ipc.GetCharacterCollectionName.Provider(pi, Api.GetCharacterCollection);
|
||||
GetCollectionForType = Ipc.GetCollectionForType.Provider(pi, Api.GetCollectionForType);
|
||||
SetCollectionForType = Ipc.SetCollectionForType.Provider(pi, Api.SetCollectionForType);
|
||||
GetCollectionForObject = Ipc.GetCollectionForObject.Provider(pi, Api.GetCollectionForObject);
|
||||
SetCollectionForObject = Ipc.SetCollectionForObject.Provider(pi, Api.SetCollectionForObject);
|
||||
GetChangedItems = Ipc.GetChangedItems.Provider(pi, Api.GetChangedItemsForCollection);
|
||||
|
||||
// Meta
|
||||
GetPlayerMetaManipulations = Ipc.GetPlayerMetaManipulations.Provider(pi, Api.GetPlayerMetaManipulations);
|
||||
GetMetaManipulations = Ipc.GetMetaManipulations.Provider(pi, Api.GetMetaManipulations);
|
||||
GetGameObjectMetaManipulations = Ipc.GetGameObjectMetaManipulations.Provider(pi, Api.GetGameObjectMetaManipulations);
|
||||
|
||||
// Mods
|
||||
GetMods = Ipc.GetMods.Provider(pi, Api.GetModList);
|
||||
ReloadMod = Ipc.ReloadMod.Provider(pi, Api.ReloadMod);
|
||||
InstallMod = Ipc.InstallMod.Provider(pi, Api.InstallMod);
|
||||
AddMod = Ipc.AddMod.Provider(pi, Api.AddMod);
|
||||
DeleteMod = Ipc.DeleteMod.Provider(pi, Api.DeleteMod);
|
||||
GetModPath = Ipc.GetModPath.Provider(pi, Api.GetModPath);
|
||||
SetModPath = Ipc.SetModPath.Provider(pi, Api.SetModPath);
|
||||
ModDeleted = Ipc.ModDeleted.Provider(pi, () => Api.ModDeleted += ModDeletedEvent, () => Api.ModDeleted -= ModDeletedEvent);
|
||||
ModAdded = Ipc.ModAdded.Provider(pi, () => Api.ModAdded += ModAddedEvent, () => Api.ModAdded -= ModAddedEvent);
|
||||
ModMoved = Ipc.ModMoved.Provider(pi, () => Api.ModMoved += ModMovedEvent, () => Api.ModMoved -= ModMovedEvent);
|
||||
|
||||
// ModSettings
|
||||
GetAvailableModSettings = Ipc.GetAvailableModSettings.Provider(pi, Api.GetAvailableModSettings);
|
||||
GetCurrentModSettings = Ipc.GetCurrentModSettings.Provider(pi, Api.GetCurrentModSettings);
|
||||
TryInheritMod = Ipc.TryInheritMod.Provider(pi, Api.TryInheritMod);
|
||||
TrySetMod = Ipc.TrySetMod.Provider(pi, Api.TrySetMod);
|
||||
TrySetModPriority = Ipc.TrySetModPriority.Provider(pi, Api.TrySetModPriority);
|
||||
TrySetModSetting = Ipc.TrySetModSetting.Provider(pi, Api.TrySetModSetting);
|
||||
TrySetModSettings = Ipc.TrySetModSettings.Provider(pi, Api.TrySetModSettings);
|
||||
ModSettingChanged = Ipc.ModSettingChanged.Provider(pi,
|
||||
() => Api.ModSettingChanged += ModSettingChangedEvent,
|
||||
() => Api.ModSettingChanged -= ModSettingChangedEvent);
|
||||
CopyModSettings = Ipc.CopyModSettings.Provider(pi, Api.CopyModSettings);
|
||||
|
||||
// Editing
|
||||
ConvertTextureFile = Ipc.ConvertTextureFile.Provider(pi, Api.ConvertTextureFile);
|
||||
ConvertTextureData = Ipc.ConvertTextureData.Provider(pi, Api.ConvertTextureData);
|
||||
|
||||
// Temporary
|
||||
CreateTemporaryCollection = Ipc.CreateTemporaryCollection.Provider(pi, Api.CreateTemporaryCollection);
|
||||
RemoveTemporaryCollection = Ipc.RemoveTemporaryCollection.Provider(pi, Api.RemoveTemporaryCollection);
|
||||
CreateNamedTemporaryCollection = Ipc.CreateNamedTemporaryCollection.Provider(pi, Api.CreateNamedTemporaryCollection);
|
||||
RemoveTemporaryCollectionByName = Ipc.RemoveTemporaryCollectionByName.Provider(pi, Api.RemoveTemporaryCollectionByName);
|
||||
AssignTemporaryCollection = Ipc.AssignTemporaryCollection.Provider(pi, Api.AssignTemporaryCollection);
|
||||
AddTemporaryModAll = Ipc.AddTemporaryModAll.Provider(pi, Api.AddTemporaryModAll);
|
||||
AddTemporaryMod = Ipc.AddTemporaryMod.Provider(pi, Api.AddTemporaryMod);
|
||||
RemoveTemporaryModAll = Ipc.RemoveTemporaryModAll.Provider(pi, Api.RemoveTemporaryModAll);
|
||||
RemoveTemporaryMod = Ipc.RemoveTemporaryMod.Provider(pi, Api.RemoveTemporaryMod);
|
||||
|
||||
// ResourceTree
|
||||
GetGameObjectResourcePaths = Ipc.GetGameObjectResourcePaths.Provider(pi, Api.GetGameObjectResourcePaths);
|
||||
GetPlayerResourcePaths = Ipc.GetPlayerResourcePaths.Provider(pi, Api.GetPlayerResourcePaths);
|
||||
GetGameObjectResourcesOfType = Ipc.GetGameObjectResourcesOfType.Provider(pi, Api.GetGameObjectResourcesOfType);
|
||||
GetPlayerResourcesOfType = Ipc.GetPlayerResourcesOfType.Provider(pi, Api.GetPlayerResourcesOfType);
|
||||
GetGameObjectResourceTrees = Ipc.GetGameObjectResourceTrees.Provider(pi, Api.GetGameObjectResourceTrees);
|
||||
GetPlayerResourceTrees = Ipc.GetPlayerResourceTrees.Provider(pi, Api.GetPlayerResourceTrees);
|
||||
|
||||
Initialized.Invoke();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Plugin State
|
||||
Initialized.Dispose();
|
||||
ApiVersion.Dispose();
|
||||
ApiVersions.Dispose();
|
||||
GetEnabledState.Dispose();
|
||||
EnabledChange.Dispose();
|
||||
|
||||
// Configuration
|
||||
GetModDirectory.Dispose();
|
||||
GetConfiguration.Dispose();
|
||||
ModDirectoryChanged.Dispose();
|
||||
|
||||
// UI
|
||||
PreSettingsTabBarDraw.Dispose();
|
||||
PreSettingsDraw.Dispose();
|
||||
PostEnabledDraw.Dispose();
|
||||
PostSettingsDraw.Dispose();
|
||||
ChangedItemTooltip.Dispose();
|
||||
ChangedItemClick.Dispose();
|
||||
OpenMainWindow.Dispose();
|
||||
CloseMainWindow.Dispose();
|
||||
|
||||
// Redrawing
|
||||
RedrawAll.Dispose();
|
||||
RedrawObject.Dispose();
|
||||
RedrawObjectByIndex.Dispose();
|
||||
RedrawObjectByName.Dispose();
|
||||
GameObjectRedrawn.Dispose();
|
||||
|
||||
// Game State
|
||||
GetDrawObjectInfo.Dispose();
|
||||
GetCutsceneParentIndex.Dispose();
|
||||
SetCutsceneParentIndex.Dispose();
|
||||
CreatingCharacterBase.Dispose();
|
||||
CreatedCharacterBase.Dispose();
|
||||
GameObjectResourcePathResolved.Dispose();
|
||||
|
||||
// Resolve
|
||||
ResolveDefaultPath.Dispose();
|
||||
ResolveInterfacePath.Dispose();
|
||||
ResolvePlayerPath.Dispose();
|
||||
ResolveGameObjectPath.Dispose();
|
||||
ResolveCharacterPath.Dispose();
|
||||
ReverseResolvePath.Dispose();
|
||||
ReverseResolveGameObjectPath.Dispose();
|
||||
ReverseResolvePlayerPath.Dispose();
|
||||
ResolvePlayerPaths.Dispose();
|
||||
ResolvePlayerPathsAsync.Dispose();
|
||||
|
||||
// Collections
|
||||
GetCollections.Dispose();
|
||||
GetCurrentCollectionName.Dispose();
|
||||
GetDefaultCollectionName.Dispose();
|
||||
GetInterfaceCollectionName.Dispose();
|
||||
GetCharacterCollectionName.Dispose();
|
||||
GetCollectionForType.Dispose();
|
||||
SetCollectionForType.Dispose();
|
||||
GetCollectionForObject.Dispose();
|
||||
SetCollectionForObject.Dispose();
|
||||
GetChangedItems.Dispose();
|
||||
|
||||
// Meta
|
||||
GetPlayerMetaManipulations.Dispose();
|
||||
GetMetaManipulations.Dispose();
|
||||
GetGameObjectMetaManipulations.Dispose();
|
||||
|
||||
// Mods
|
||||
GetMods.Dispose();
|
||||
ReloadMod.Dispose();
|
||||
InstallMod.Dispose();
|
||||
AddMod.Dispose();
|
||||
DeleteMod.Dispose();
|
||||
GetModPath.Dispose();
|
||||
SetModPath.Dispose();
|
||||
ModDeleted.Dispose();
|
||||
ModAdded.Dispose();
|
||||
ModMoved.Dispose();
|
||||
|
||||
// ModSettings
|
||||
GetAvailableModSettings.Dispose();
|
||||
GetCurrentModSettings.Dispose();
|
||||
TryInheritMod.Dispose();
|
||||
TrySetMod.Dispose();
|
||||
TrySetModPriority.Dispose();
|
||||
TrySetModSetting.Dispose();
|
||||
TrySetModSettings.Dispose();
|
||||
ModSettingChanged.Dispose();
|
||||
CopyModSettings.Dispose();
|
||||
|
||||
// Temporary
|
||||
CreateTemporaryCollection.Dispose();
|
||||
RemoveTemporaryCollection.Dispose();
|
||||
CreateNamedTemporaryCollection.Dispose();
|
||||
RemoveTemporaryCollectionByName.Dispose();
|
||||
AssignTemporaryCollection.Dispose();
|
||||
AddTemporaryModAll.Dispose();
|
||||
AddTemporaryMod.Dispose();
|
||||
RemoveTemporaryModAll.Dispose();
|
||||
RemoveTemporaryMod.Dispose();
|
||||
|
||||
// Editing
|
||||
ConvertTextureFile.Dispose();
|
||||
ConvertTextureData.Dispose();
|
||||
|
||||
// Resource Tree
|
||||
GetGameObjectResourcePaths.Dispose();
|
||||
GetPlayerResourcePaths.Dispose();
|
||||
GetGameObjectResourcesOfType.Dispose();
|
||||
GetPlayerResourcesOfType.Dispose();
|
||||
GetGameObjectResourceTrees.Dispose();
|
||||
GetPlayerResourceTrees.Dispose();
|
||||
|
||||
Disposed.Invoke();
|
||||
Disposed.Dispose();
|
||||
}
|
||||
|
||||
// Wrappers
|
||||
private int DeprecatedVersion()
|
||||
{
|
||||
Penumbra.Log.Warning($"{Ipc.ApiVersion.Label} is outdated. Please use {Ipc.ApiVersions.Label} instead.");
|
||||
return Api.ApiVersion.Breaking;
|
||||
}
|
||||
|
||||
private void OnClick(MouseButton click, object? item)
|
||||
{
|
||||
var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId(item);
|
||||
ChangedItemClick.Invoke(click, type, id);
|
||||
}
|
||||
|
||||
private void OnTooltip(object? item)
|
||||
{
|
||||
var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId(item);
|
||||
ChangedItemTooltip.Invoke(type, id);
|
||||
}
|
||||
|
||||
private void EnabledChangeEvent(bool value)
|
||||
=> EnabledChange.Invoke(value);
|
||||
|
||||
private void OnGameObjectRedrawn(IntPtr objectAddress, int objectTableIndex)
|
||||
=> GameObjectRedrawn.Invoke(objectAddress, objectTableIndex);
|
||||
|
||||
private void CreatingCharacterBaseEvent(IntPtr gameObject, string collectionName, IntPtr modelId, IntPtr customize, IntPtr equipData)
|
||||
=> CreatingCharacterBase.Invoke(gameObject, collectionName, modelId, customize, equipData);
|
||||
|
||||
private void CreatedCharacterBaseEvent(IntPtr gameObject, string collectionName, IntPtr drawObject)
|
||||
=> CreatedCharacterBase.Invoke(gameObject, collectionName, drawObject);
|
||||
|
||||
private void GameObjectResourceResolvedEvent(IntPtr gameObject, string gamePath, string localPath)
|
||||
=> GameObjectResourcePathResolved.Invoke(gameObject, gamePath, localPath);
|
||||
|
||||
private void ModSettingChangedEvent(ModSettingChange type, string collection, string mod, bool inherited)
|
||||
=> ModSettingChanged.Invoke(type, collection, mod, inherited);
|
||||
|
||||
private void ModDeletedEvent(string name)
|
||||
=> ModDeleted.Invoke(name);
|
||||
|
||||
private void ModAddedEvent(string name)
|
||||
=> ModAdded.Invoke(name);
|
||||
|
||||
private void ModMovedEvent(string from, string to)
|
||||
=> ModMoved.Invoke(from, to);
|
||||
}
|
||||
|
|
@ -243,14 +243,14 @@ public sealed class CollectionCache : IDisposable
|
|||
continue;
|
||||
|
||||
var config = settings.Settings[groupIndex];
|
||||
switch (group.Type)
|
||||
switch (group)
|
||||
{
|
||||
case GroupType.Single:
|
||||
AddSubMod(group[config.AsIndex], mod);
|
||||
case SingleModGroup single:
|
||||
AddSubMod(single[config.AsIndex], mod);
|
||||
break;
|
||||
case GroupType.Multi:
|
||||
case MultiModGroup multi:
|
||||
{
|
||||
foreach (var (option, _) in group.WithIndex()
|
||||
foreach (var (option, _) in multi.WithIndex()
|
||||
.Where(p => config.HasFlag(p.Index))
|
||||
.OrderByDescending(p => group.OptionPriority(p.Index)))
|
||||
AddSubMod(option, mod);
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ public class CollectionCacheManager : IDisposable
|
|||
/// Does not create caches.
|
||||
/// </summary>
|
||||
public void CalculateEffectiveFileList(ModCollection collection)
|
||||
=> _framework.RegisterImportant(nameof(CalculateEffectiveFileList) + collection.Name,
|
||||
=> _framework.RegisterImportant(nameof(CalculateEffectiveFileList) + collection.Identifier,
|
||||
() => CalculateEffectiveFileListInternal(collection));
|
||||
|
||||
private void CalculateEffectiveFileListInternal(ModCollection collection)
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ public readonly struct ImcCache : IDisposable
|
|||
}
|
||||
|
||||
private static FullPath CreateImcPath(ModCollection collection, Utf8GamePath path)
|
||||
=> new($"|{collection.Name}_{collection.ChangeCounter}|{path}");
|
||||
=> new($"|{collection.Id.OptimizedString()}_{collection.ChangeCounter}|{path}");
|
||||
|
||||
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out ImcFile? file)
|
||||
=> _imcFiles.TryGetValue(path, out file);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public class ActiveCollectionData
|
|||
|
||||
public class ActiveCollections : ISavable, IDisposable
|
||||
{
|
||||
public const int Version = 1;
|
||||
public const int Version = 2;
|
||||
|
||||
private readonly CollectionStorage _storage;
|
||||
private readonly CommunicatorService _communicator;
|
||||
|
|
@ -261,16 +261,17 @@ public class ActiveCollections : ISavable, IDisposable
|
|||
var jObj = new JObject
|
||||
{
|
||||
{ nameof(Version), Version },
|
||||
{ nameof(Default), Default.Name },
|
||||
{ nameof(Interface), Interface.Name },
|
||||
{ nameof(Current), Current.Name },
|
||||
{ nameof(Default), Default.Id },
|
||||
{ nameof(Interface), Interface.Id },
|
||||
{ nameof(Current), Current.Id },
|
||||
};
|
||||
foreach (var (type, collection) in SpecialCollections.WithIndex().Where(p => p.Value != null)
|
||||
.Select(p => ((CollectionType)p.Index, p.Value!)))
|
||||
jObj.Add(type.ToString(), collection.Name);
|
||||
jObj.Add(type.ToString(), collection.Id);
|
||||
|
||||
jObj.Add(nameof(Individuals), Individuals.ToJObject());
|
||||
using var j = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||
using var j = new JsonTextWriter(writer);
|
||||
j.Formatting = Formatting.Indented;
|
||||
jObj.WriteTo(j);
|
||||
}
|
||||
|
||||
|
|
@ -319,22 +320,16 @@ public class ActiveCollections : ISavable, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load default, current, special, and character collections from config.
|
||||
/// If a collection does not exist anymore, reset it to an appropriate default.
|
||||
/// </summary>
|
||||
private void LoadCollections()
|
||||
private bool LoadCollectionsV1(JObject jObject)
|
||||
{
|
||||
Penumbra.Log.Debug("[Collections] Reading collection assignments...");
|
||||
var configChanged = !Load(_saveService.FileNames, out var jObject);
|
||||
|
||||
// Load the default collection. If the string does not exist take the Default name if no file existed or the Empty name if one existed.
|
||||
var defaultName = jObject[nameof(Default)]?.ToObject<string>()
|
||||
?? (configChanged ? ModCollection.DefaultCollectionName : ModCollection.Empty.Name);
|
||||
var configChanged = false;
|
||||
// Load the default collection. If the name does not exist take the empty collection.
|
||||
var defaultName = jObject[nameof(Default)]?.ToObject<string>() ?? ModCollection.Empty.Name;
|
||||
if (!_storage.ByName(defaultName, out var defaultCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.DefaultCollection} {defaultName} is not available, reset to {ModCollection.Empty.Name}.", NotificationType.Warning);
|
||||
$"Last choice of {TutorialService.DefaultCollection} {defaultName} is not available, reset to {ModCollection.Empty.Name}.",
|
||||
NotificationType.Warning);
|
||||
Default = ModCollection.Empty;
|
||||
configChanged = true;
|
||||
}
|
||||
|
|
@ -348,7 +343,8 @@ public class ActiveCollections : ISavable, IDisposable
|
|||
if (!_storage.ByName(interfaceName, out var interfaceCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.InterfaceCollection} {interfaceName} is not available, reset to {ModCollection.Empty.Name}.", NotificationType.Warning);
|
||||
$"Last choice of {TutorialService.InterfaceCollection} {interfaceName} is not available, reset to {ModCollection.Empty.Name}.",
|
||||
NotificationType.Warning);
|
||||
Interface = ModCollection.Empty;
|
||||
configChanged = true;
|
||||
}
|
||||
|
|
@ -362,7 +358,8 @@ public class ActiveCollections : ISavable, IDisposable
|
|||
if (!_storage.ByName(currentName, out var currentCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.SelectedCollection} {currentName} is not available, reset to {ModCollection.DefaultCollectionName}.", NotificationType.Warning);
|
||||
$"Last choice of {TutorialService.SelectedCollection} {currentName} is not available, reset to {ModCollection.DefaultCollectionName}.",
|
||||
NotificationType.Warning);
|
||||
Current = _storage.DefaultNamed;
|
||||
configChanged = true;
|
||||
}
|
||||
|
|
@ -393,11 +390,124 @@ public class ActiveCollections : ISavable, IDisposable
|
|||
Penumbra.Log.Debug("[Collections] Loaded non-individual collection assignments.");
|
||||
|
||||
configChanged |= ActiveCollectionMigration.MigrateIndividualCollections(_storage, Individuals, jObject);
|
||||
configChanged |= Individuals.ReadJObject(_saveService, this, jObject[nameof(Individuals)] as JArray, _storage);
|
||||
configChanged |= Individuals.ReadJObject(_saveService, this, jObject[nameof(Individuals)] as JArray, _storage, 1);
|
||||
|
||||
// Save any changes.
|
||||
if (configChanged)
|
||||
_saveService.ImmediateSave(this);
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
private bool LoadCollectionsV2(JObject jObject)
|
||||
{
|
||||
var configChanged = false;
|
||||
// Load the default collection. If the guid does not exist take the empty collection.
|
||||
var defaultId = jObject[nameof(Default)]?.ToObject<Guid>() ?? Guid.Empty;
|
||||
if (!_storage.ById(defaultId, out var defaultCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.DefaultCollection} {defaultId} is not available, reset to {ModCollection.Empty.Name}.",
|
||||
NotificationType.Warning);
|
||||
Default = ModCollection.Empty;
|
||||
configChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Default = defaultCollection;
|
||||
}
|
||||
|
||||
// Load the interface collection. If no string is set, use the name of whatever was set as Default.
|
||||
var interfaceId = jObject[nameof(Interface)]?.ToObject<Guid>() ?? Default.Id;
|
||||
if (!_storage.ById(interfaceId, out var interfaceCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.InterfaceCollection} {interfaceId} is not available, reset to {ModCollection.Empty.Name}.",
|
||||
NotificationType.Warning);
|
||||
Interface = ModCollection.Empty;
|
||||
configChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Interface = interfaceCollection;
|
||||
}
|
||||
|
||||
// Load the current collection.
|
||||
var currentId = jObject[nameof(Current)]?.ToObject<Guid>() ?? _storage.DefaultNamed.Id;
|
||||
if (!_storage.ById(currentId, out var currentCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Last choice of {TutorialService.SelectedCollection} {currentId} is not available, reset to {ModCollection.DefaultCollectionName}.",
|
||||
NotificationType.Warning);
|
||||
Current = _storage.DefaultNamed;
|
||||
configChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Current = currentCollection;
|
||||
}
|
||||
|
||||
// Load special collections.
|
||||
foreach (var (type, name, _) in CollectionTypeExtensions.Special)
|
||||
{
|
||||
var typeId = jObject[type.ToString()]?.ToObject<Guid>();
|
||||
if (typeId == null)
|
||||
continue;
|
||||
|
||||
if (!_storage.ById(typeId.Value, out var typeCollection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage($"Last choice of {name} Collection {typeId.Value} is not available, removed.",
|
||||
NotificationType.Warning);
|
||||
configChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SpecialCollections[(int)type] = typeCollection;
|
||||
}
|
||||
}
|
||||
|
||||
Penumbra.Log.Debug("[Collections] Loaded non-individual collection assignments.");
|
||||
|
||||
configChanged |= ActiveCollectionMigration.MigrateIndividualCollections(_storage, Individuals, jObject);
|
||||
configChanged |= Individuals.ReadJObject(_saveService, this, jObject[nameof(Individuals)] as JArray, _storage, 2);
|
||||
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
private bool LoadCollectionsNew()
|
||||
{
|
||||
Current = _storage.DefaultNamed;
|
||||
Default = _storage.DefaultNamed;
|
||||
Interface = _storage.DefaultNamed;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load default, current, special, and character collections from config.
|
||||
/// If a collection does not exist anymore, reset it to an appropriate default.
|
||||
/// </summary>
|
||||
private void LoadCollections()
|
||||
{
|
||||
Penumbra.Log.Debug("[Collections] Reading collection assignments...");
|
||||
var configChanged = !Load(_saveService.FileNames, out var jObject);
|
||||
var version = jObject["Version"]?.ToObject<int>() ?? 0;
|
||||
var changed = false;
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
changed = LoadCollectionsV1(jObject);
|
||||
break;
|
||||
case 2:
|
||||
changed = LoadCollectionsV2(jObject);
|
||||
break;
|
||||
case 0 when configChanged:
|
||||
changed = LoadCollectionsNew();
|
||||
break;
|
||||
case 0:
|
||||
Penumbra.Messager.NotificationMessage("Active Collections File has unknown version and will be reset.",
|
||||
NotificationType.Warning);
|
||||
changed = LoadCollectionsNew();
|
||||
break;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
_saveService.ImmediateSaveSync(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -410,7 +520,7 @@ public class ActiveCollections : ISavable, IDisposable
|
|||
var jObj = BackupService.GetJObjectForFile(fileNames, file);
|
||||
if (jObj == null)
|
||||
{
|
||||
ret = new JObject();
|
||||
ret = [];
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Filesystem;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Editor;
|
||||
|
|
@ -48,6 +47,25 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Find a collection by its id. If the GUID is empty, the empty collection is returned. </summary>
|
||||
public bool ById(Guid id, [NotNullWhen(true)] out ModCollection? collection)
|
||||
{
|
||||
if (id != Guid.Empty)
|
||||
return _collections.FindFirst(c => c.Id == id, out collection);
|
||||
|
||||
collection = ModCollection.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Find a collection by an identifier, which is interpreted as a GUID first and if it does not correspond to one, as a name. </summary>
|
||||
public bool ByIdentifier(string identifier, [NotNullWhen(true)] out ModCollection? collection)
|
||||
{
|
||||
if (Guid.TryParse(identifier, out var guid))
|
||||
return ById(guid, out collection);
|
||||
|
||||
return ByName(identifier, out collection);
|
||||
}
|
||||
|
||||
public CollectionStorage(CommunicatorService communicator, SaveService saveService, ModStorage modStorage)
|
||||
{
|
||||
_communicator = communicator;
|
||||
|
|
@ -70,31 +88,6 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
_communicator.ModFileChanged.Unsubscribe(OnModFileChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the name is not empty, it is not the name of the empty collection
|
||||
/// and no existing collection results in the same filename as name. Also returns the fixed name.
|
||||
/// </summary>
|
||||
public bool CanAddCollection(string name, out string fixedName)
|
||||
{
|
||||
if (!IsValidName(name))
|
||||
{
|
||||
fixedName = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
name = name.ToLowerInvariant();
|
||||
if (name.Length == 0
|
||||
|| name == ModCollection.Empty.Name.ToLowerInvariant()
|
||||
|| _collections.Any(c => c.Name.ToLowerInvariant() == name))
|
||||
{
|
||||
fixedName = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
fixedName = name;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new collection of the given name.
|
||||
/// If duplicate is not-null, the new collection will be a duplicate of it.
|
||||
|
|
@ -104,14 +97,6 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
/// </summary>
|
||||
public bool AddCollection(string name, ModCollection? duplicate)
|
||||
{
|
||||
if (!CanAddCollection(name, out var fixedName))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"The new collection {name} would lead to the same path {fixedName} as one that already exists.", NotificationType.Warning,
|
||||
false);
|
||||
return false;
|
||||
}
|
||||
|
||||
var newCollection = duplicate?.Duplicate(name, _collections.Count)
|
||||
?? ModCollection.CreateEmpty(name, _collections.Count, _modStorage.Count);
|
||||
_collections.Add(newCollection);
|
||||
|
|
@ -166,16 +151,9 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a name is valid to use for a collection.
|
||||
/// Does not check for uniqueness.
|
||||
/// </summary>
|
||||
private static bool IsValidName(string name)
|
||||
=> name.Length is > 0 and < 64 && name.All(c => !c.IsInvalidAscii() && c is not '|' && !c.IsInvalidInPath());
|
||||
|
||||
/// <summary>
|
||||
/// Read all collection files in the Collection Directory.
|
||||
/// Ensure that the default named collection exists, and apply inheritances afterwards.
|
||||
/// Ensure that the default named collection exists, and apply inheritances afterward.
|
||||
/// Duplicate collection files are not deleted, just not added here.
|
||||
/// </summary>
|
||||
private void ReadCollections(out ModCollection defaultNamedCollection)
|
||||
|
|
@ -183,29 +161,46 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
|
|||
Penumbra.Log.Debug("[Collections] Reading saved collections...");
|
||||
foreach (var file in _saveService.FileNames.CollectionFiles)
|
||||
{
|
||||
if (!ModCollectionSave.LoadFromFile(file, out var name, out var version, out var settings, out var inheritance))
|
||||
if (!ModCollectionSave.LoadFromFile(file, out var id, out var name, out var version, out var settings, out var inheritance))
|
||||
continue;
|
||||
|
||||
if (!IsValidName(name))
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
// TODO: handle better.
|
||||
Penumbra.Messager.NotificationMessage($"Collection of unsupported name found: {name} is not a valid collection name.",
|
||||
Penumbra.Messager.NotificationMessage("Collection without ID found.", NotificationType.Warning);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ById(id, out _))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage($"Duplicate collection found: {id} already exists. Import skipped.",
|
||||
NotificationType.Warning);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ByName(name, out _))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage($"Duplicate collection found: {name} already exists. Import skipped.",
|
||||
NotificationType.Warning);
|
||||
continue;
|
||||
}
|
||||
|
||||
var collection = ModCollection.CreateFromData(_saveService, _modStorage, name, version, Count, settings, inheritance);
|
||||
var collection = ModCollection.CreateFromData(_saveService, _modStorage, id, name, version, Count, settings, inheritance);
|
||||
var correctName = _saveService.FileNames.CollectionFile(collection);
|
||||
if (file.FullName != correctName)
|
||||
Penumbra.Messager.NotificationMessage($"Collection {file.Name} does not correspond to {collection.Name}.",
|
||||
NotificationType.Warning);
|
||||
try
|
||||
{
|
||||
if (version >= 2)
|
||||
{
|
||||
File.Move(file.FullName, correctName, false);
|
||||
Penumbra.Messager.NotificationMessage($"Collection {file.Name} does not correspond to {collection.Identifier}, renamed.",
|
||||
NotificationType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
_saveService.ImmediateSaveSync(new ModCollectionSave(_modStorage, collection));
|
||||
File.Delete(file.FullName);
|
||||
Penumbra.Log.Information($"Migrated collection {name} to Guid {id}.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(e,
|
||||
$"Collection {file.Name} does not correspond to {collection.Identifier}, but could not rename.", NotificationType.Error);
|
||||
}
|
||||
|
||||
_collections.Add(collection);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public partial class IndividualCollections
|
|||
foreach (var (name, identifiers, collection) in Assignments)
|
||||
{
|
||||
var tmp = identifiers[0].ToJson();
|
||||
tmp.Add("Collection", collection.Name);
|
||||
tmp.Add("Collection", collection.Id);
|
||||
tmp.Add("Display", name);
|
||||
ret.Add(tmp);
|
||||
}
|
||||
|
|
@ -26,18 +26,28 @@ public partial class IndividualCollections
|
|||
return ret;
|
||||
}
|
||||
|
||||
public bool ReadJObject(SaveService saver, ActiveCollections parent, JArray? obj, CollectionStorage storage)
|
||||
public bool ReadJObject(SaveService saver, ActiveCollections parent, JArray? obj, CollectionStorage storage, int version)
|
||||
{
|
||||
if (_actors.Awaiter.IsCompletedSuccessfully)
|
||||
{
|
||||
var ret = ReadJObjectInternal(obj, storage);
|
||||
var ret = version switch
|
||||
{
|
||||
1 => ReadJObjectInternalV1(obj, storage),
|
||||
2 => ReadJObjectInternalV2(obj, storage),
|
||||
_ => true,
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
Penumbra.Log.Debug("[Collections] Delayed reading individual assignments until actor service is ready...");
|
||||
_actors.Awaiter.ContinueWith(_ =>
|
||||
{
|
||||
if (ReadJObjectInternal(obj, storage))
|
||||
if (version switch
|
||||
{
|
||||
1 => ReadJObjectInternalV1(obj, storage),
|
||||
2 => ReadJObjectInternalV2(obj, storage),
|
||||
_ => true,
|
||||
})
|
||||
saver.ImmediateSave(parent);
|
||||
IsLoaded = true;
|
||||
Loaded.Invoke();
|
||||
|
|
@ -45,7 +55,55 @@ public partial class IndividualCollections
|
|||
return false;
|
||||
}
|
||||
|
||||
private bool ReadJObjectInternal(JArray? obj, CollectionStorage storage)
|
||||
private bool ReadJObjectInternalV1(JArray? obj, CollectionStorage storage)
|
||||
{
|
||||
Penumbra.Log.Debug("[Collections] Reading individual assignments...");
|
||||
if (obj == null)
|
||||
{
|
||||
Penumbra.Log.Debug($"[Collections] Finished reading {Count} individual assignments...");
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var data in obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
var identifier = _actors.FromJson(data as JObject);
|
||||
var group = GetGroup(identifier);
|
||||
if (group.Length == 0 || group.Any(i => !i.IsValid))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage("Could not load an unknown individual collection, removed.",
|
||||
NotificationType.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
var collectionName = data["Collection"]?.ToObject<string>() ?? string.Empty;
|
||||
if (collectionName.Length == 0 || !storage.ByName(collectionName, out var collection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Could not load the collection \"{collectionName}\" as individual collection for {identifier}, set to None.",
|
||||
NotificationType.Warning);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Add(group, collection))
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage($"Could not add an individual collection for {identifier}, removed.",
|
||||
NotificationType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(e, $"Could not load an unknown individual collection, removed.", NotificationType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
Penumbra.Log.Debug($"Finished reading {Count} individual assignments...");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ReadJObjectInternalV2(JArray? obj, CollectionStorage storage)
|
||||
{
|
||||
Penumbra.Log.Debug("[Collections] Reading individual assignments...");
|
||||
if (obj == null)
|
||||
|
|
@ -64,17 +122,17 @@ public partial class IndividualCollections
|
|||
if (group.Length == 0 || group.Any(i => !i.IsValid))
|
||||
{
|
||||
changes = true;
|
||||
Penumbra.Messager.NotificationMessage("Could not load an unknown individual collection, removed.",
|
||||
Penumbra.Messager.NotificationMessage("Could not load an unknown individual collection, removed assignment.",
|
||||
NotificationType.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
var collectionName = data["Collection"]?.ToObject<string>() ?? string.Empty;
|
||||
if (collectionName.Length == 0 || !storage.ByName(collectionName, out var collection))
|
||||
var collectionId = data["Collection"]?.ToObject<Guid>();
|
||||
if (!collectionId.HasValue || !storage.ById(collectionId.Value, out var collection))
|
||||
{
|
||||
changes = true;
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Could not load the collection \"{collectionName}\" as individual collection for {identifier}, set to None.",
|
||||
$"Could not load the collection {collectionId} as individual collection for {identifier}, removed assignment.",
|
||||
NotificationType.Warning);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -82,14 +140,14 @@ public partial class IndividualCollections
|
|||
if (!Add(group, collection))
|
||||
{
|
||||
changes = true;
|
||||
Penumbra.Messager.NotificationMessage($"Could not add an individual collection for {identifier}, removed.",
|
||||
Penumbra.Messager.NotificationMessage($"Could not add an individual collection for {identifier}, removed assignment.",
|
||||
NotificationType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
changes = true;
|
||||
Penumbra.Messager.NotificationMessage(e, $"Could not load an unknown individual collection, removed.", NotificationType.Error);
|
||||
Penumbra.Messager.NotificationMessage(e, $"Could not load an unknown individual collection, removed assignment.", NotificationType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,14 +158,6 @@ public partial class IndividualCollections
|
|||
|
||||
internal void Migrate0To1(Dictionary<string, ModCollection> old)
|
||||
{
|
||||
static bool FindDataId(string name, NameDictionary data, out NpcId dataId)
|
||||
{
|
||||
var kvp = data.FirstOrDefault(kvp => kvp.Value.Equals(name, StringComparison.OrdinalIgnoreCase),
|
||||
new KeyValuePair<NpcId, string>(uint.MaxValue, string.Empty));
|
||||
dataId = kvp.Key;
|
||||
return kvp.Value.Length > 0;
|
||||
}
|
||||
|
||||
foreach (var (name, collection) in old)
|
||||
{
|
||||
var kind = ObjectKind.None;
|
||||
|
|
@ -155,5 +205,15 @@ public partial class IndividualCollections
|
|||
NotificationType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
static bool FindDataId(string name, NameDictionary data, out NpcId dataId)
|
||||
{
|
||||
var kvp = data.FirstOrDefault(kvp => kvp.Value.Equals(name, StringComparison.OrdinalIgnoreCase),
|
||||
new KeyValuePair<NpcId, string>(uint.MaxValue, string.Empty));
|
||||
dataId = kvp.Key;
|
||||
return kvp.Value.Length > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ public class InheritanceManager : IDisposable
|
|||
var changes = false;
|
||||
foreach (var subCollectionName in collection.InheritanceByName)
|
||||
{
|
||||
if (_storage.ByName(subCollectionName, out var subCollection))
|
||||
if (Guid.TryParse(subCollectionName, out var guid) && _storage.ById(guid, out var subCollection))
|
||||
{
|
||||
if (AddInheritance(collection, subCollection, false))
|
||||
continue;
|
||||
|
|
@ -146,6 +146,15 @@ public class InheritanceManager : IDisposable
|
|||
changes = true;
|
||||
Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.", NotificationType.Warning);
|
||||
}
|
||||
else if (_storage.ByName(subCollectionName, out subCollection))
|
||||
{
|
||||
changes = true;
|
||||
Penumbra.Log.Information($"Migrating inheritance for {collection.AnonymizedName} from name to GUID.");
|
||||
if (AddInheritance(collection, subCollection, false))
|
||||
continue;
|
||||
|
||||
Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.", NotificationType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using OtterGui;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
|
@ -9,13 +10,13 @@ namespace Penumbra.Collections.Manager;
|
|||
|
||||
public class TempCollectionManager : IDisposable
|
||||
{
|
||||
public int GlobalChangeCounter { get; private set; } = 0;
|
||||
public int GlobalChangeCounter { get; private set; }
|
||||
public readonly IndividualCollections Collections;
|
||||
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly CollectionStorage _storage;
|
||||
private readonly ActorManager _actors;
|
||||
private readonly Dictionary<string, ModCollection> _customCollections = new();
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly CollectionStorage _storage;
|
||||
private readonly ActorManager _actors;
|
||||
private readonly Dictionary<Guid, ModCollection> _customCollections = [];
|
||||
|
||||
public TempCollectionManager(Configuration config, CommunicatorService communicator, ActorManager actors, CollectionStorage storage)
|
||||
{
|
||||
|
|
@ -42,36 +43,36 @@ public class TempCollectionManager : IDisposable
|
|||
=> _customCollections.Values;
|
||||
|
||||
public bool CollectionByName(string name, [NotNullWhen(true)] out ModCollection? collection)
|
||||
=> _customCollections.TryGetValue(name.ToLowerInvariant(), out collection);
|
||||
=> _customCollections.Values.FindFirst(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase), out collection);
|
||||
|
||||
public string CreateTemporaryCollection(string name)
|
||||
public bool CollectionById(Guid id, [NotNullWhen(true)] out ModCollection? collection)
|
||||
=> _customCollections.TryGetValue(id, out collection);
|
||||
|
||||
public Guid CreateTemporaryCollection(string name)
|
||||
{
|
||||
if (_storage.ByName(name, out _))
|
||||
return string.Empty;
|
||||
|
||||
if (GlobalChangeCounter == int.MaxValue)
|
||||
GlobalChangeCounter = 0;
|
||||
var collection = ModCollection.CreateTemporary(name, ~Count, GlobalChangeCounter++);
|
||||
Penumbra.Log.Debug($"Creating temporary collection {collection.AnonymizedName}.");
|
||||
if (_customCollections.TryAdd(collection.Name.ToLowerInvariant(), collection))
|
||||
Penumbra.Log.Debug($"Creating temporary collection {collection.Name} with {collection.Id}.");
|
||||
if (_customCollections.TryAdd(collection.Id, collection))
|
||||
{
|
||||
// Temporary collection created.
|
||||
_communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, string.Empty);
|
||||
return collection.Name;
|
||||
return collection.Id;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
public bool RemoveTemporaryCollection(string collectionName)
|
||||
public bool RemoveTemporaryCollection(Guid collectionId)
|
||||
{
|
||||
if (!_customCollections.Remove(collectionName.ToLowerInvariant(), out var collection))
|
||||
if (!_customCollections.Remove(collectionId, out var collection))
|
||||
{
|
||||
Penumbra.Log.Debug($"Tried to delete temporary collection {collectionName.ToLowerInvariant()}, but did not exist.");
|
||||
Penumbra.Log.Debug($"Tried to delete temporary collection {collectionId}, but did not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Penumbra.Log.Debug($"Deleted temporary collection {collection.AnonymizedName}.");
|
||||
Penumbra.Log.Debug($"Deleted temporary collection {collection.Id}.");
|
||||
GlobalChangeCounter += Math.Max(collection.ChangeCounter + 1 - GlobalChangeCounter, 0);
|
||||
for (var i = 0; i < Collections.Count; ++i)
|
||||
{
|
||||
|
|
@ -80,7 +81,7 @@ public class TempCollectionManager : IDisposable
|
|||
|
||||
// Temporary collection assignment removed.
|
||||
_communicator.CollectionChange.Invoke(CollectionType.Temporary, collection, null, Collections[i].DisplayName);
|
||||
Penumbra.Log.Verbose($"Unassigned temporary collection {collection.AnonymizedName} from {Collections[i].DisplayName}.");
|
||||
Penumbra.Log.Verbose($"Unassigned temporary collection {collection.Id} from {Collections[i].DisplayName}.");
|
||||
Collections.Delete(i--);
|
||||
}
|
||||
|
||||
|
|
@ -98,32 +99,32 @@ public class TempCollectionManager : IDisposable
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool AddIdentifier(string collectionName, params ActorIdentifier[] identifiers)
|
||||
public bool AddIdentifier(Guid collectionId, params ActorIdentifier[] identifiers)
|
||||
{
|
||||
if (!_customCollections.TryGetValue(collectionName.ToLowerInvariant(), out var collection))
|
||||
if (!_customCollections.TryGetValue(collectionId, out var collection))
|
||||
return false;
|
||||
|
||||
return AddIdentifier(collection, identifiers);
|
||||
}
|
||||
|
||||
public bool AddIdentifier(string collectionName, string characterName, ushort worldId = ushort.MaxValue)
|
||||
public bool AddIdentifier(Guid collectionId, string characterName, ushort worldId = ushort.MaxValue)
|
||||
{
|
||||
if (!ByteString.FromString(characterName, out var byteString, false))
|
||||
if (!ByteString.FromString(characterName, out var byteString))
|
||||
return false;
|
||||
|
||||
var identifier = _actors.CreatePlayer(byteString, worldId);
|
||||
if (!identifier.IsValid)
|
||||
return false;
|
||||
|
||||
return AddIdentifier(collectionName, identifier);
|
||||
return AddIdentifier(collectionId, identifier);
|
||||
}
|
||||
|
||||
internal bool RemoveByCharacterName(string characterName, ushort worldId = ushort.MaxValue)
|
||||
{
|
||||
if (!ByteString.FromString(characterName, out var byteString, false))
|
||||
if (!ByteString.FromString(characterName, out var byteString))
|
||||
return false;
|
||||
|
||||
var identifier = _actors.CreatePlayer(byteString, worldId);
|
||||
return Collections.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Name);
|
||||
return Collections.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Penumbra.Collections;
|
|||
/// </summary>
|
||||
public partial class ModCollection
|
||||
{
|
||||
public const int CurrentVersion = 1;
|
||||
public const int CurrentVersion = 2;
|
||||
public const string DefaultCollectionName = "Default";
|
||||
public const string EmptyCollectionName = "None";
|
||||
|
||||
|
|
@ -27,15 +27,23 @@ public partial class ModCollection
|
|||
/// </summary>
|
||||
public static readonly ModCollection Empty = CreateEmpty(EmptyCollectionName, 0, 0);
|
||||
|
||||
/// <summary> The name of a collection can not contain characters invalid in a path. </summary>
|
||||
public string Name { get; internal init; }
|
||||
/// <summary> The name of a collection. </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public Guid Id { get; }
|
||||
|
||||
public string Identifier
|
||||
=> Id.ToString();
|
||||
|
||||
public string ShortIdentifier
|
||||
=> Identifier[..8];
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
=> Name.Length > 0 ? Name : ShortIdentifier;
|
||||
|
||||
/// <summary> Get the first two letters of a collection name and its Index (or None if it is the empty collection). </summary>
|
||||
public string AnonymizedName
|
||||
=> this == Empty ? Empty.Name : Name.Length > 2 ? $"{Name[..2]}... ({Index})" : $"{Name} ({Index})";
|
||||
=> this == Empty ? Empty.Name : Name == DefaultCollectionName ? Name : ShortIdentifier;
|
||||
|
||||
/// <summary> The index of the collection is set and kept up-to-date by the CollectionManager. </summary>
|
||||
public int Index { get; internal set; }
|
||||
|
|
@ -112,16 +120,16 @@ public partial class ModCollection
|
|||
public ModCollection Duplicate(string name, int index)
|
||||
{
|
||||
Debug.Assert(index > 0, "Collection duplicated with non-positive index.");
|
||||
return new ModCollection(name, index, 0, CurrentVersion, Settings.Select(s => s?.DeepCopy()).ToList(),
|
||||
return new ModCollection(Guid.NewGuid(), name, index, 0, CurrentVersion, Settings.Select(s => s?.DeepCopy()).ToList(),
|
||||
[.. DirectlyInheritsFrom], UnusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy()));
|
||||
}
|
||||
|
||||
/// <summary> Constructor for reading from files. </summary>
|
||||
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, string name, int version, int index,
|
||||
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, Guid id, string name, int version, int index,
|
||||
Dictionary<string, ModSettings.SavedSettings> allSettings, IReadOnlyList<string> inheritances)
|
||||
{
|
||||
Debug.Assert(index > 0, "Collection read with non-positive index.");
|
||||
var ret = new ModCollection(name, index, 0, version, new List<ModSettings?>(), new List<ModCollection>(), allSettings)
|
||||
var ret = new ModCollection(id, name, index, 0, version, [], [], allSettings)
|
||||
{
|
||||
InheritanceByName = inheritances,
|
||||
};
|
||||
|
|
@ -134,8 +142,7 @@ public partial class ModCollection
|
|||
public static ModCollection CreateTemporary(string name, int index, int changeCounter)
|
||||
{
|
||||
Debug.Assert(index < 0, "Temporary collection created with non-negative index.");
|
||||
var ret = new ModCollection(name, index, changeCounter, CurrentVersion, new List<ModSettings?>(), new List<ModCollection>(),
|
||||
new Dictionary<string, ModSettings.SavedSettings>());
|
||||
var ret = new ModCollection(Guid.NewGuid(), name, index, changeCounter, CurrentVersion, [], [], []);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -143,9 +150,8 @@ public partial class ModCollection
|
|||
public static ModCollection CreateEmpty(string name, int index, int modCount)
|
||||
{
|
||||
Debug.Assert(index >= 0, "Empty collection created with negative index.");
|
||||
return new ModCollection(name, index, 0, CurrentVersion, Enumerable.Repeat((ModSettings?)null, modCount).ToList(),
|
||||
new List<ModCollection>(),
|
||||
new Dictionary<string, ModSettings.SavedSettings>());
|
||||
return new ModCollection(Guid.Empty, name, index, 0, CurrentVersion, Enumerable.Repeat((ModSettings?)null, modCount).ToList(), [],
|
||||
[]);
|
||||
}
|
||||
|
||||
/// <summary> Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. </summary>
|
||||
|
|
@ -193,10 +199,11 @@ public partial class ModCollection
|
|||
saver.ImmediateSave(new ModCollectionSave(mods, this));
|
||||
}
|
||||
|
||||
private ModCollection(string name, int index, int changeCounter, int version, List<ModSettings?> appliedSettings,
|
||||
private ModCollection(Guid id, string name, int index, int changeCounter, int version, List<ModSettings?> appliedSettings,
|
||||
List<ModCollection> inheritsFrom, Dictionary<string, ModSettings.SavedSettings> settings)
|
||||
{
|
||||
Name = name;
|
||||
Id = id;
|
||||
Index = index;
|
||||
ChangeCounter = changeCounter;
|
||||
Settings = appliedSettings;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
|
|||
j.WriteStartObject();
|
||||
j.WritePropertyName("Version");
|
||||
j.WriteValue(ModCollection.CurrentVersion);
|
||||
j.WritePropertyName(nameof(ModCollection.Id));
|
||||
j.WriteValue(modCollection.Identifier);
|
||||
j.WritePropertyName(nameof(ModCollection.Name));
|
||||
j.WriteValue(modCollection.Name);
|
||||
j.WritePropertyName(nameof(ModCollection.Settings));
|
||||
|
|
@ -55,20 +57,20 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
|
|||
|
||||
// Inherit by collection name.
|
||||
j.WritePropertyName("Inheritance");
|
||||
x.Serialize(j, modCollection.InheritanceByName ?? modCollection.DirectlyInheritsFrom.Select(c => c.Name));
|
||||
x.Serialize(j, modCollection.InheritanceByName ?? modCollection.DirectlyInheritsFrom.Select(c => c.Identifier));
|
||||
j.WriteEndObject();
|
||||
}
|
||||
|
||||
public static bool LoadFromFile(FileInfo file, out string name, out int version, out Dictionary<string, ModSettings.SavedSettings> settings,
|
||||
public static bool LoadFromFile(FileInfo file, out Guid id, out string name, out int version, out Dictionary<string, ModSettings.SavedSettings> settings,
|
||||
out IReadOnlyList<string> inheritance)
|
||||
{
|
||||
settings = new Dictionary<string, ModSettings.SavedSettings>();
|
||||
inheritance = Array.Empty<string>();
|
||||
settings = [];
|
||||
inheritance = [];
|
||||
if (!file.Exists)
|
||||
{
|
||||
Penumbra.Log.Error("Could not read collection because file does not exist.");
|
||||
name = string.Empty;
|
||||
|
||||
name = string.Empty;
|
||||
id = Guid.Empty;
|
||||
version = 0;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -76,8 +78,9 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
|
|||
try
|
||||
{
|
||||
var obj = JObject.Parse(File.ReadAllText(file.FullName));
|
||||
name = obj[nameof(ModCollection.Name)]?.ToObject<string>() ?? string.Empty;
|
||||
version = obj["Version"]?.ToObject<int>() ?? 0;
|
||||
name = obj[nameof(ModCollection.Name)]?.ToObject<string>() ?? string.Empty;
|
||||
id = obj[nameof(ModCollection.Id)]?.ToObject<Guid>() ?? (version == 1 ? Guid.NewGuid() : Guid.Empty);
|
||||
// Custom deserialization that is converted with the constructor.
|
||||
settings = obj[nameof(ModCollection.Settings)]?.ToObject<Dictionary<string, ModSettings.SavedSettings>>() ?? settings;
|
||||
inheritance = obj["Inheritance"]?.ToObject<List<string>>() ?? inheritance;
|
||||
|
|
@ -87,6 +90,7 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
|
|||
{
|
||||
name = string.Empty;
|
||||
version = 0;
|
||||
id = Guid.Empty;
|
||||
Penumbra.Log.Error($"Could not read collection information from file:\n{e}");
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ public class CommandHandler : IDisposable
|
|||
|
||||
collection = string.Equals(lowerName, ModCollection.Empty.Name, StringComparison.OrdinalIgnoreCase)
|
||||
? ModCollection.Empty
|
||||
: _collectionManager.Storage.ByName(lowerName, out var c)
|
||||
: _collectionManager.Storage.ByIdentifier(lowerName, out var c)
|
||||
? c
|
||||
: null;
|
||||
if (collection != null)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Api.Enums;
|
||||
|
||||
namespace Penumbra.Communication;
|
||||
|
|
@ -14,7 +15,7 @@ public sealed class ChangedItemClick() : EventWrapper<MouseButton, object?, Chan
|
|||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Api.PenumbraApi.ChangedItemClicked"/>
|
||||
/// <seealso cref="UiApi.OnChangedItemClick"/>
|
||||
Default = 0,
|
||||
|
||||
/// <seealso cref="Penumbra.SetupApi"/>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api.Api;
|
||||
|
||||
namespace Penumbra.Communication;
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ public sealed class ChangedItemHover() : EventWrapper<object?, ChangedItemHover.
|
|||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Api.PenumbraApi.ChangedItemTooltip"/>
|
||||
/// <seealso cref="UiApi.OnChangedItemHover"/>
|
||||
Default = 0,
|
||||
|
||||
/// <seealso cref="Penumbra.SetupApi"/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Collections;
|
||||
|
||||
namespace Penumbra.Communication;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Communication;
|
||||
|
|
@ -14,7 +15,7 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is a pointer to the equip data array. </item>
|
||||
/// </list> </summary>
|
||||
public sealed class CreatingCharacterBase()
|
||||
: EventWrapper<nint, string, nint, nint, nint, CreatingCharacterBase.Priority>(nameof(CreatingCharacterBase))
|
||||
: EventWrapper<nint, Guid, nint, nint, nint, CreatingCharacterBase.Priority>(nameof(CreatingCharacterBase))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
|
||||
namespace Penumbra.Communication;
|
||||
|
||||
|
|
@ -13,7 +14,7 @@ public sealed class EnabledChanged() : EventWrapper<bool, EnabledChanged.Priorit
|
|||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Ipc.EnabledChange"/>
|
||||
/// <seealso cref="Api.IpcSubscribers.Ipc.EnabledChange"/>
|
||||
Api = int.MinValue,
|
||||
|
||||
/// <seealso cref="Api.DalamudSubstitutionProvider.OnEnabledChange"/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Api;
|
||||
|
||||
namespace Penumbra.Communication;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Editor;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
|
||||
|
|
@ -19,8 +20,11 @@ public sealed class ModPathChanged()
|
|||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="PenumbraApi.ModPathChangeSubscriber"/>
|
||||
Api = int.MinValue,
|
||||
/// <seealso cref="ModsApi.OnModPathChange"/>
|
||||
ApiMods = int.MinValue,
|
||||
|
||||
/// <seealso cref="ModSettingsApi.OnModPathChange"/>
|
||||
ApiModSettings = int.MinValue,
|
||||
|
||||
/// <seealso cref="EphemeralConfig.OnModPathChanged"/>
|
||||
EphemeralConfig = -500,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Mods;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api.Api;
|
||||
|
||||
namespace Penumbra.Communication;
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ public sealed class PostEnabledDraw() : EventWrapper<string, PostEnabledDraw.Pri
|
|||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Api.PenumbraApi.PostEnabledDraw"/>
|
||||
/// <seealso cref="PenumbraApi.PostEnabledDraw"/>
|
||||
Default = 0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api.Api;
|
||||
|
||||
namespace Penumbra.Communication;
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ public sealed class PostSettingsPanelDraw() : EventWrapper<string, PostSettingsP
|
|||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Api.PenumbraApi.PostSettingsPanelDraw"/>
|
||||
/// <seealso cref="PenumbraApi.PostSettingsPanelDraw"/>
|
||||
Default = 0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api.Api;
|
||||
|
||||
namespace Penumbra.Communication;
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ public sealed class PreSettingsPanelDraw() : EventWrapper<string, PreSettingsPan
|
|||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Api.PenumbraApi.PreSettingsPanelDraw"/>
|
||||
/// <seealso cref="PenumbraApi.PreSettingsPanelDraw"/>
|
||||
Default = 0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Api.Api;
|
||||
|
||||
namespace Penumbra.Communication;
|
||||
|
||||
|
|
@ -14,7 +15,7 @@ public sealed class PreSettingsTabBarDraw() : EventWrapper<string, float, float,
|
|||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Api.PenumbraApi.PreSettingsTabBarDraw"/>
|
||||
/// <seealso cref="PenumbraApi.PreSettingsTabBarDraw"/>
|
||||
Default = 0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
254
Penumbra/GuidExtensions.cs
Normal file
254
Penumbra/GuidExtensions.cs
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
using System.Collections.Frozen;
|
||||
using OtterGui;
|
||||
|
||||
namespace Penumbra;
|
||||
|
||||
public static class GuidExtensions
|
||||
{
|
||||
private const string Chars =
|
||||
"0123456789"
|
||||
+ "abcdefghij"
|
||||
+ "klmnopqrst"
|
||||
+ "uv";
|
||||
|
||||
private static ReadOnlySpan<byte> Bytes
|
||||
=> "0123456789abcdefghijklmnopqrstuv"u8;
|
||||
|
||||
private static readonly FrozenDictionary<char, byte>
|
||||
ReverseChars = Chars.WithIndex().ToFrozenDictionary(t => t.Value, t => (byte)t.Index);
|
||||
|
||||
private static readonly FrozenDictionary<byte, byte> ReverseBytes =
|
||||
ReverseChars.ToFrozenDictionary(kvp => (byte)kvp.Key, kvp => kvp.Value);
|
||||
|
||||
public static unsafe string OptimizedString(this Guid guid)
|
||||
{
|
||||
var bytes = stackalloc ulong[2];
|
||||
if (!guid.TryWriteBytes(new Span<byte>(bytes, 16)))
|
||||
return guid.ToString("N");
|
||||
|
||||
var u1 = bytes[0];
|
||||
var u2 = bytes[1];
|
||||
Span<char> text =
|
||||
[
|
||||
Chars[(int)(u1 & 0x1F)],
|
||||
Chars[(int)((u1 >> 5) & 0x1F)],
|
||||
Chars[(int)((u1 >> 10) & 0x1F)],
|
||||
Chars[(int)((u1 >> 15) & 0x1F)],
|
||||
Chars[(int)((u1 >> 20) & 0x1F)],
|
||||
Chars[(int)((u1 >> 25) & 0x1F)],
|
||||
Chars[(int)((u1 >> 30) & 0x1F)],
|
||||
Chars[(int)((u1 >> 35) & 0x1F)],
|
||||
Chars[(int)((u1 >> 40) & 0x1F)],
|
||||
Chars[(int)((u1 >> 45) & 0x1F)],
|
||||
Chars[(int)((u1 >> 50) & 0x1F)],
|
||||
Chars[(int)((u1 >> 55) & 0x1F)],
|
||||
Chars[(int)((u1 >> 60) | ((u2 & 0x01) << 4))],
|
||||
Chars[(int)((u2 >> 1) & 0x1F)],
|
||||
Chars[(int)((u2 >> 6) & 0x1F)],
|
||||
Chars[(int)((u2 >> 11) & 0x1F)],
|
||||
Chars[(int)((u2 >> 16) & 0x1F)],
|
||||
Chars[(int)((u2 >> 21) & 0x1F)],
|
||||
Chars[(int)((u2 >> 26) & 0x1F)],
|
||||
Chars[(int)((u2 >> 31) & 0x1F)],
|
||||
Chars[(int)((u2 >> 36) & 0x1F)],
|
||||
Chars[(int)((u2 >> 41) & 0x1F)],
|
||||
Chars[(int)((u2 >> 46) & 0x1F)],
|
||||
Chars[(int)((u2 >> 51) & 0x1F)],
|
||||
Chars[(int)((u2 >> 56) & 0x1F)],
|
||||
Chars[(int)((u2 >> 61) & 0x1F)],
|
||||
];
|
||||
return new string(text);
|
||||
}
|
||||
|
||||
public static unsafe bool FromOptimizedString(ReadOnlySpan<char> text, out Guid guid)
|
||||
{
|
||||
if (text.Length != 26)
|
||||
return Return(out guid);
|
||||
|
||||
var bytes = stackalloc ulong[2];
|
||||
if (!ReverseChars.TryGetValue(text[0], out var b0))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[1], out var b1))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[2], out var b2))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[3], out var b3))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[4], out var b4))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[5], out var b5))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[6], out var b6))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[7], out var b7))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[8], out var b8))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[9], out var b9))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[10], out var b10))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[11], out var b11))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[12], out var b12))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[13], out var b13))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[14], out var b14))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[15], out var b15))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[16], out var b16))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[17], out var b17))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[18], out var b18))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[19], out var b19))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[20], out var b20))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[21], out var b21))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[22], out var b22))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[23], out var b23))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[24], out var b24))
|
||||
return Return(out guid);
|
||||
if (!ReverseChars.TryGetValue(text[25], out var b25))
|
||||
return Return(out guid);
|
||||
|
||||
bytes[0] = b0
|
||||
| ((ulong)b1 << 5)
|
||||
| ((ulong)b2 << 10)
|
||||
| ((ulong)b3 << 15)
|
||||
| ((ulong)b4 << 20)
|
||||
| ((ulong)b5 << 25)
|
||||
| ((ulong)b6 << 30)
|
||||
| ((ulong)b7 << 35)
|
||||
| ((ulong)b8 << 40)
|
||||
| ((ulong)b9 << 45)
|
||||
| ((ulong)b10 << 50)
|
||||
| ((ulong)b11 << 55)
|
||||
| ((ulong)b12 << 60);
|
||||
bytes[1] = ((ulong)b12 >> 4)
|
||||
| ((ulong)b13 << 1)
|
||||
| ((ulong)b14 << 6)
|
||||
| ((ulong)b15 << 11)
|
||||
| ((ulong)b16 << 16)
|
||||
| ((ulong)b17 << 21)
|
||||
| ((ulong)b18 << 26)
|
||||
| ((ulong)b19 << 31)
|
||||
| ((ulong)b20 << 36)
|
||||
| ((ulong)b21 << 41)
|
||||
| ((ulong)b22 << 46)
|
||||
| ((ulong)b23 << 51)
|
||||
| ((ulong)b24 << 56)
|
||||
| ((ulong)b25 << 61);
|
||||
guid = new Guid(new Span<byte>(bytes, 16));
|
||||
return true;
|
||||
|
||||
static bool Return(out Guid guid)
|
||||
{
|
||||
guid = Guid.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe bool FromOptimizedString(ReadOnlySpan<byte> text, out Guid guid)
|
||||
{
|
||||
if (text.Length != 26)
|
||||
return Return(out guid);
|
||||
|
||||
var bytes = stackalloc ulong[2];
|
||||
if (!ReverseBytes.TryGetValue(text[0], out var b0))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[1], out var b1))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[2], out var b2))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[3], out var b3))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[4], out var b4))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[5], out var b5))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[6], out var b6))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[7], out var b7))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[8], out var b8))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[9], out var b9))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[10], out var b10))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[11], out var b11))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[12], out var b12))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[13], out var b13))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[14], out var b14))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[15], out var b15))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[16], out var b16))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[17], out var b17))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[18], out var b18))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[19], out var b19))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[20], out var b20))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[21], out var b21))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[22], out var b22))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[23], out var b23))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[24], out var b24))
|
||||
return Return(out guid);
|
||||
if (!ReverseBytes.TryGetValue(text[25], out var b25))
|
||||
return Return(out guid);
|
||||
|
||||
bytes[0] = b0
|
||||
| ((ulong)b1 << 5)
|
||||
| ((ulong)b2 << 10)
|
||||
| ((ulong)b3 << 15)
|
||||
| ((ulong)b4 << 20)
|
||||
| ((ulong)b5 << 25)
|
||||
| ((ulong)b6 << 30)
|
||||
| ((ulong)b7 << 35)
|
||||
| ((ulong)b8 << 40)
|
||||
| ((ulong)b9 << 45)
|
||||
| ((ulong)b10 << 50)
|
||||
| ((ulong)b11 << 55)
|
||||
| ((ulong)b12 << 60);
|
||||
bytes[1] = ((ulong)b12 >> 4)
|
||||
| ((ulong)b13 << 1)
|
||||
| ((ulong)b14 << 6)
|
||||
| ((ulong)b15 << 11)
|
||||
| ((ulong)b16 << 16)
|
||||
| ((ulong)b17 << 21)
|
||||
| ((ulong)b18 << 26)
|
||||
| ((ulong)b19 << 31)
|
||||
| ((ulong)b20 << 36)
|
||||
| ((ulong)b21 << 41)
|
||||
| ((ulong)b22 << 46)
|
||||
| ((ulong)b23 << 51)
|
||||
| ((ulong)b24 << 56)
|
||||
| ((ulong)b25 << 61);
|
||||
guid = new Guid(new Span<byte>(bytes, 16));
|
||||
return true;
|
||||
|
||||
static bool Return(out Guid guid)
|
||||
{
|
||||
guid = Guid.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ public sealed unsafe class MetaState : IDisposable
|
|||
_lastCreatedCollection = _collectionResolver.IdentifyLastGameObjectCollection(true);
|
||||
if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero)
|
||||
_communicator.CreatingCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject,
|
||||
_lastCreatedCollection.ModCollection.Name, (nint)modelCharaId, (nint)customize, (nint)equipData);
|
||||
_lastCreatedCollection.ModCollection.Id, (nint)modelCharaId, (nint)customize, (nint)equipData);
|
||||
|
||||
var decal = new DecalReverter(_config, _characterUtility, _resources, _lastCreatedCollection,
|
||||
UsesDecal(*(uint*)modelCharaId, (nint)customize));
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Runtime;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
|
|
@ -43,8 +44,8 @@ public class PathResolver : IDisposable
|
|||
}
|
||||
|
||||
/// <summary> Obtain a temporary or permanent collection by name. </summary>
|
||||
public bool CollectionByName(string name, [NotNullWhen(true)] out ModCollection? collection)
|
||||
=> _tempCollections.CollectionByName(name, out collection) || _collectionManager.Storage.ByName(name, out collection);
|
||||
public bool CollectionById(Guid id, [NotNullWhen(true)] out ModCollection? collection)
|
||||
=> _tempCollections.CollectionById(id, out collection) || _collectionManager.Storage.ById(id, out collection);
|
||||
|
||||
/// <summary> Try to resolve the given game path to the replaced path. </summary>
|
||||
public (FullPath?, ResolveData) ResolvePath(Utf8GamePath path, ResourceCategory category, ResourceType resourceType)
|
||||
|
|
@ -136,9 +137,10 @@ public class PathResolver : IDisposable
|
|||
return;
|
||||
|
||||
var lastUnderscore = additionalData.LastIndexOf((byte)'_');
|
||||
var name = lastUnderscore == -1 ? additionalData.ToString() : additionalData.Substring(0, lastUnderscore).ToString();
|
||||
var idString = lastUnderscore == -1 ? additionalData : additionalData.Substring(0, lastUnderscore);
|
||||
if (Utf8GamePath.FromByteString(path, out var gamePath)
|
||||
&& CollectionByName(name, out var collection)
|
||||
&& GuidExtensions.FromOptimizedString(idString.Span, out var id)
|
||||
&& CollectionById(id, out var collection)
|
||||
&& collection.HasCache
|
||||
&& collection.GetImcFile(gamePath, out var file))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ public sealed unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyV
|
|||
case ResourceType.Mtrl:
|
||||
case ResourceType.Avfx:
|
||||
case ResourceType.Tmb:
|
||||
var fullPath = new FullPath($"|{resolveData.ModCollection.Name}_{resolveData.ModCollection.ChangeCounter}|{path}");
|
||||
var fullPath = new FullPath($"|{resolveData.ModCollection.Id.OptimizedString()}_{resolveData.ModCollection.ChangeCounter}|{path}");
|
||||
data = (fullPath, resolveData);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Penumbra.Api;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.UI;
|
||||
|
||||
|
|
@ -8,7 +9,8 @@ namespace Penumbra.Interop.ResourceTree;
|
|||
|
||||
internal static class ResourceTreeApiHelper
|
||||
{
|
||||
public static Dictionary<ushort, IReadOnlyDictionary<string, string[]>> GetResourcePathDictionaries(IEnumerable<(Character, ResourceTree)> resourceTrees)
|
||||
public static Dictionary<ushort, Dictionary<string, HashSet<string>>> GetResourcePathDictionaries(
|
||||
IEnumerable<(Character, ResourceTree)> resourceTrees)
|
||||
{
|
||||
var pathDictionaries = new Dictionary<ushort, Dictionary<string, HashSet<string>>>(4);
|
||||
|
||||
|
|
@ -23,8 +25,7 @@ internal static class ResourceTreeApiHelper
|
|||
CollectResourcePaths(pathDictionary, resourceTree);
|
||||
}
|
||||
|
||||
return pathDictionaries.ToDictionary(pair => pair.Key,
|
||||
pair => (IReadOnlyDictionary<string, string[]>)pair.Value.ToDictionary(pair => pair.Key, pair => pair.Value.ToArray()).AsReadOnly());
|
||||
return pathDictionaries;
|
||||
}
|
||||
|
||||
private static void CollectResourcePaths(Dictionary<string, HashSet<string>> pathDictionary, ResourceTree resourceTree)
|
||||
|
|
@ -37,7 +38,7 @@ internal static class ResourceTreeApiHelper
|
|||
var fullPath = node.FullPath.ToPath();
|
||||
if (!pathDictionary.TryGetValue(fullPath, out var gamePaths))
|
||||
{
|
||||
gamePaths = new();
|
||||
gamePaths = [];
|
||||
pathDictionary.Add(fullPath, gamePaths);
|
||||
}
|
||||
|
||||
|
|
@ -46,17 +47,17 @@ internal static class ResourceTreeApiHelper
|
|||
}
|
||||
}
|
||||
|
||||
public static Dictionary<ushort, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>> GetResourcesOfType(IEnumerable<(Character, ResourceTree)> resourceTrees,
|
||||
public static Dictionary<ushort, GameResourceDict> GetResourcesOfType(IEnumerable<(Character, ResourceTree)> resourceTrees,
|
||||
ResourceType type)
|
||||
{
|
||||
var resDictionaries = new Dictionary<ushort, Dictionary<nint, (string, string, ChangedItemIcon)>>(4);
|
||||
var resDictionaries = new Dictionary<ushort, GameResourceDict>(4);
|
||||
foreach (var (gameObject, resourceTree) in resourceTrees)
|
||||
{
|
||||
if (resDictionaries.ContainsKey(gameObject.ObjectIndex))
|
||||
continue;
|
||||
|
||||
var resDictionary = new Dictionary<nint, (string, string, ChangedItemIcon)>();
|
||||
resDictionaries.Add(gameObject.ObjectIndex, resDictionary);
|
||||
var resDictionary = new Dictionary<nint, (string, string, uint)>();
|
||||
resDictionaries.Add(gameObject.ObjectIndex, new GameResourceDict(resDictionary));
|
||||
|
||||
foreach (var node in resourceTree.FlatNodes)
|
||||
{
|
||||
|
|
@ -66,38 +67,16 @@ internal static class ResourceTreeApiHelper
|
|||
continue;
|
||||
|
||||
var fullPath = node.FullPath.ToPath();
|
||||
resDictionary.Add(node.ResourceHandle, (fullPath, node.Name ?? string.Empty, ChangedItemDrawer.ToApiIcon(node.Icon)));
|
||||
resDictionary.Add(node.ResourceHandle, (fullPath, node.Name ?? string.Empty, (uint)ChangedItemDrawer.ToApiIcon(node.Icon)));
|
||||
}
|
||||
}
|
||||
|
||||
return resDictionaries.ToDictionary(pair => pair.Key,
|
||||
pair => (IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>)pair.Value.AsReadOnly());
|
||||
return resDictionaries;
|
||||
}
|
||||
|
||||
public static Dictionary<ushort, Ipc.ResourceTree> EncapsulateResourceTrees(IEnumerable<(Character, ResourceTree)> resourceTrees)
|
||||
public static Dictionary<ushort, JObject> EncapsulateResourceTrees(IEnumerable<(Character, ResourceTree)> resourceTrees)
|
||||
{
|
||||
static Ipc.ResourceNode GetIpcNode(ResourceNode node) =>
|
||||
new()
|
||||
{
|
||||
Type = node.Type,
|
||||
Icon = ChangedItemDrawer.ToApiIcon(node.Icon),
|
||||
Name = node.Name,
|
||||
GamePath = node.GamePath.Equals(Utf8GamePath.Empty) ? null : node.GamePath.ToString(),
|
||||
ActualPath = node.FullPath.ToString(),
|
||||
ObjectAddress = node.ObjectAddress,
|
||||
ResourceHandle = node.ResourceHandle,
|
||||
Children = node.Children.Select(GetIpcNode).ToList(),
|
||||
};
|
||||
|
||||
static Ipc.ResourceTree GetIpcTree(ResourceTree tree) =>
|
||||
new()
|
||||
{
|
||||
Name = tree.Name,
|
||||
RaceCode = (ushort)tree.RaceCode,
|
||||
Nodes = tree.Nodes.Select(GetIpcNode).ToList(),
|
||||
};
|
||||
|
||||
var resDictionary = new Dictionary<ushort, Ipc.ResourceTree>(4);
|
||||
var resDictionary = new Dictionary<ushort, JObject>(4);
|
||||
foreach (var (gameObject, resourceTree) in resourceTrees)
|
||||
{
|
||||
if (resDictionary.ContainsKey(gameObject.ObjectIndex))
|
||||
|
|
@ -107,5 +86,38 @@ internal static class ResourceTreeApiHelper
|
|||
}
|
||||
|
||||
return resDictionary;
|
||||
|
||||
static JObject GetIpcTree(ResourceTree tree)
|
||||
{
|
||||
var ret = new JObject
|
||||
{
|
||||
[nameof(ResourceTreeDto.Name)] = tree.Name,
|
||||
[nameof(ResourceTreeDto.RaceCode)] = (ushort)tree.RaceCode,
|
||||
};
|
||||
var children = new JArray();
|
||||
foreach (var child in tree.Nodes)
|
||||
children.Add(GetIpcNode(child));
|
||||
ret[nameof(ResourceTreeDto.Nodes)] = children;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static JObject GetIpcNode(ResourceNode node)
|
||||
{
|
||||
var ret = new JObject
|
||||
{
|
||||
[nameof(ResourceNodeDto.Type)] = new JValue(node.Type),
|
||||
[nameof(ResourceNodeDto.Icon)] = new JValue(ChangedItemDrawer.ToApiIcon(node.Icon)),
|
||||
[nameof(ResourceNodeDto.Name)] = node.Name,
|
||||
[nameof(ResourceNodeDto.GamePath)] = node.GamePath.Equals(Utf8GamePath.Empty) ? null : node.GamePath.ToString(),
|
||||
[nameof(ResourceNodeDto.ActualPath)] = node.FullPath.ToString(),
|
||||
[nameof(ResourceNodeDto.ObjectAddress)] = node.ObjectAddress,
|
||||
[nameof(ResourceNodeDto.ResourceHandle)] = node.ResourceHandle,
|
||||
};
|
||||
var children = new JArray();
|
||||
foreach (var child in node.Children)
|
||||
children.Add(GetIpcNode(child));
|
||||
ret[nameof(ResourceNodeDto.Children)] = children;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,22 +272,19 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
return;
|
||||
|
||||
var group = mod.Groups[groupIdx];
|
||||
if (group.Type is GroupType.Multi && group.Count >= IModGroup.MaxMultiOptions)
|
||||
{
|
||||
Penumbra.Log.Error(
|
||||
$"Could not add option {option.Name} to {group.Name} for mod {mod.Name}, "
|
||||
+ $"since only up to {IModGroup.MaxMultiOptions} options are supported in one group.");
|
||||
return;
|
||||
}
|
||||
|
||||
o.SetPosition(groupIdx, group.Count);
|
||||
|
||||
switch (group)
|
||||
{
|
||||
case MultiModGroup { Count: >= IModGroup.MaxMultiOptions }:
|
||||
Penumbra.Log.Error(
|
||||
$"Could not add option {option.Name} to {group.Name} for mod {mod.Name}, "
|
||||
+ $"since only up to {IModGroup.MaxMultiOptions} options are supported in one group.");
|
||||
return;
|
||||
case SingleModGroup s:
|
||||
o.SetPosition(groupIdx, s.Count);
|
||||
s.OptionData.Add(o);
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
o.SetPosition(groupIdx, m.Count);
|
||||
m.PrioritizedOptions.Add((o, priority));
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public class ModCombo : FilterComboCache<Mod>
|
|||
public class ModStorage : IReadOnlyList<Mod>
|
||||
{
|
||||
/// <summary> The actual list of mods. </summary>
|
||||
protected readonly List<Mod> Mods = new();
|
||||
protected readonly List<Mod> Mods = [];
|
||||
|
||||
public int Count
|
||||
=> Mods.Count;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ using Penumbra.Services;
|
|||
|
||||
namespace Penumbra.Mods.Subclasses;
|
||||
|
||||
public interface IModGroup : IEnumerable<ISubMod>
|
||||
public interface IModGroup : IReadOnlyCollection<ISubMod>
|
||||
{
|
||||
public const int MaxMultiOptions = 32;
|
||||
public const int MaxMultiOptions = 63;
|
||||
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
|
|
@ -18,15 +18,7 @@ public interface IModGroup : IEnumerable<ISubMod>
|
|||
|
||||
public ISubMod this[Index idx] { get; }
|
||||
|
||||
public int Count { get; }
|
||||
|
||||
public bool IsOption
|
||||
=> Type switch
|
||||
{
|
||||
GroupType.Single => Count > 1,
|
||||
GroupType.Multi => Count > 0,
|
||||
_ => false,
|
||||
};
|
||||
public bool IsOption { get; }
|
||||
|
||||
public IModGroup Convert(GroupType type);
|
||||
public bool MoveOption(int optionIdxFrom, int optionIdxTo);
|
||||
|
|
@ -94,11 +86,13 @@ public readonly struct ModSaveGroup : ISavable
|
|||
j.WritePropertyName("Options");
|
||||
j.WriteStartArray();
|
||||
for (var idx = 0; idx < _group.Count; ++idx)
|
||||
{
|
||||
ISubMod.WriteSubMod(j, serializer, _group[idx], _basePath, _group.Type switch
|
||||
{
|
||||
GroupType.Multi => _group.OptionPriority(idx),
|
||||
_ => null,
|
||||
_ => null,
|
||||
});
|
||||
}
|
||||
|
||||
j.WriteEndArray();
|
||||
j.WriteEndObject();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public class ModSettings
|
|||
{
|
||||
public static readonly ModSettings Empty = new();
|
||||
public SettingList Settings { get; private init; } = [];
|
||||
public ModPriority Priority { get; set; }
|
||||
public ModPriority Priority { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
// Create an independent copy of the current settings.
|
||||
|
|
@ -152,7 +152,7 @@ public class ModSettings
|
|||
public struct SavedSettings
|
||||
{
|
||||
public Dictionary<string, Setting> Settings;
|
||||
public ModPriority Priority;
|
||||
public ModPriority Priority;
|
||||
public bool Enabled;
|
||||
|
||||
public SavedSettings DeepCopy()
|
||||
|
|
@ -203,9 +203,9 @@ public class ModSettings
|
|||
|
||||
// Return the settings for a given mod in a shareable format, using the names of groups and options instead of indices.
|
||||
// Does not repair settings but ignores settings not fitting to the given mod.
|
||||
public (bool Enabled, ModPriority Priority, Dictionary<string, IList<string>> Settings) ConvertToShareable(Mod mod)
|
||||
public (bool Enabled, ModPriority Priority, Dictionary<string, List<string>> Settings) ConvertToShareable(Mod mod)
|
||||
{
|
||||
var dict = new Dictionary<string, IList<string>>(Settings.Count);
|
||||
var dict = new Dictionary<string, List<string>>(Settings.Count);
|
||||
foreach (var (setting, idx) in Settings.WithIndex())
|
||||
{
|
||||
if (idx >= mod.Groups.Count)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ public sealed class MultiModGroup : IModGroup
|
|||
public ISubMod this[Index idx]
|
||||
=> PrioritizedOptions[idx].Mod;
|
||||
|
||||
public bool IsOption
|
||||
=> Count > 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public int Count
|
||||
=> PrioritizedOptions.Count;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ public sealed class SingleModGroup : IModGroup
|
|||
public ISubMod this[Index idx]
|
||||
=> OptionData[idx];
|
||||
|
||||
public bool IsOption
|
||||
=> Count > 1;
|
||||
|
||||
[JsonIgnore]
|
||||
public int Count
|
||||
=> OptionData.Count;
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public class TemporaryMod : IMod
|
|||
dir = ModCreator.CreateModFolder(modManager.BasePath, collection.Name, config.ReplaceNonAsciiOnImport, true);
|
||||
var fileDir = Directory.CreateDirectory(Path.Combine(dir.FullName, "files"));
|
||||
modManager.DataEditor.CreateMeta(dir, collection.Name, character ?? config.DefaultModAuthor,
|
||||
$"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null);
|
||||
$"Mod generated from temporary collection {collection.Id} for {character ?? "Unknown Character"} with name {collection.Name}.", null, null);
|
||||
var mod = new Mod(dir);
|
||||
var defaultMod = mod.Default;
|
||||
foreach (var (gamePath, fullPath) in collection.ResolvedFiles)
|
||||
|
|
@ -86,11 +86,11 @@ public class TemporaryMod : IMod
|
|||
|
||||
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport));
|
||||
modManager.AddMod(dir);
|
||||
Penumbra.Log.Information($"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}.");
|
||||
Penumbra.Log.Information($"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Identifier}.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Could not save temporary collection {collection.Name} to permanent Mod:\n{e}");
|
||||
Penumbra.Log.Error($"Could not save temporary collection {collection.Identifier} to permanent Mod:\n{e}");
|
||||
if (dir != null && Directory.Exists(dir.FullName))
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ using ChangedItemHover = Penumbra.Communication.ChangedItemHover;
|
|||
using OtterGui.Tasks;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.UI;
|
||||
using IPenumbraApi = Penumbra.Api.Api.IPenumbraApi;
|
||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||
|
||||
namespace Penumbra;
|
||||
|
|
@ -105,8 +106,7 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
private void SetupApi()
|
||||
{
|
||||
var api = _services.GetService<IPenumbraApi>();
|
||||
_services.GetService<PenumbraIpcProviders>();
|
||||
_services.GetService<IpcProviders>();
|
||||
_communicatorService.ChangedItemHover.Subscribe(it =>
|
||||
{
|
||||
if (it is (Item, FullEquipType))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ public class ConfigMigrationService(SaveService saveService) : IService
|
|||
try
|
||||
{
|
||||
var jObject = JObject.Parse(File.ReadAllText(collection.FullName));
|
||||
if (jObject[nameof(ModCollection.Name)]?.ToObject<string>() == ForcedCollection)
|
||||
if (jObject["Name"]?.ToObject<string>() == ForcedCollection)
|
||||
continue;
|
||||
|
||||
jObject[nameof(ModCollection.DirectlyInheritsFrom)] = JToken.FromObject(new List<string> { ForcedCollection });
|
||||
|
|
@ -365,7 +365,7 @@ public class ConfigMigrationService(SaveService saveService) : IService
|
|||
dict = dict.ToDictionary(kvp => kvp.Key, kvp => kvp.Value with { Priority = maxPriority - kvp.Value.Priority });
|
||||
|
||||
var emptyStorage = new ModStorage();
|
||||
var collection = ModCollection.CreateFromData(saveService, emptyStorage, ModCollection.DefaultCollectionName, 0, 1, dict, []);
|
||||
var collection = ModCollection.CreateFromData(saveService, emptyStorage, Guid.NewGuid(), ModCollection.DefaultCollectionName, 0, 1, dict, []);
|
||||
saveService.ImmediateSaveSync(new ModCollectionSave(emptyStorage, collection));
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ public sealed class CrashHandlerService : IDisposable, IService
|
|||
var name = GetActorName(character);
|
||||
lock (_eventWriter)
|
||||
{
|
||||
_eventWriter?.AnimationFuncInvoked.WriteLine(character, name.Span, collection.Name, type);
|
||||
_eventWriter?.AnimationFuncInvoked.WriteLine(character, name.Span, collection.Id, type);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -248,7 +248,7 @@ public sealed class CrashHandlerService : IDisposable, IService
|
|||
}
|
||||
}
|
||||
|
||||
private void OnCreatingCharacterBase(nint address, string collection, nint _1, nint _2, nint _3)
|
||||
private void OnCreatingCharacterBase(nint address, Guid collection, nint _1, nint _2, nint _3)
|
||||
{
|
||||
if (_eventWriter == null)
|
||||
return;
|
||||
|
|
@ -293,7 +293,7 @@ public sealed class CrashHandlerService : IDisposable, IService
|
|||
var name = GetActorName(resolveData.AssociatedGameObject);
|
||||
lock (_eventWriter)
|
||||
{
|
||||
_eventWriter!.FileLoaded.WriteLine(resolveData.AssociatedGameObject, name.Span, resolveData.ModCollection.Name,
|
||||
_eventWriter!.FileLoaded.WriteLine(resolveData.AssociatedGameObject, name.Span, resolveData.ModCollection.Id,
|
||||
manipulatedPath.Value.InternalName.Span, originalPath.Path.Span);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public class FilenameService(DalamudPluginInterface pi) : IService
|
|||
|
||||
/// <summary> Obtain the path of a collection file given its name.</summary>
|
||||
public string CollectionFile(ModCollection collection)
|
||||
=> CollectionFile(collection.Name);
|
||||
=> CollectionFile(collection.Identifier);
|
||||
|
||||
/// <summary> Obtain the path of a collection file given its name. </summary>
|
||||
public string CollectionFile(string collectionName)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using OtterGui.Classes;
|
|||
using OtterGui.Log;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Api;
|
||||
using Penumbra.Collections.Cache;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
|
@ -30,8 +31,10 @@ using Penumbra.UI.ModsTab;
|
|||
using Penumbra.UI.ResourceWatcher;
|
||||
using Penumbra.UI.Tabs;
|
||||
using Penumbra.UI.Tabs.Debug;
|
||||
using IPenumbraApi = Penumbra.Api.Api.IPenumbraApi;
|
||||
using MdlMaterialEditor = Penumbra.Mods.Editor.MdlMaterialEditor;
|
||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||
using Penumbra.Api.IpcTester;
|
||||
|
||||
namespace Penumbra.Services;
|
||||
|
||||
|
|
@ -195,10 +198,5 @@ public static class StaticServiceManager
|
|||
.AddSingleton<ModelManager>();
|
||||
|
||||
private static ServiceManager AddApi(this ServiceManager services)
|
||||
=> services.AddSingleton<PenumbraApi>()
|
||||
.AddSingleton<IPenumbraApi>(x => x.GetRequiredService<PenumbraApi>())
|
||||
.AddSingleton<PenumbraIpcProviders>()
|
||||
.AddSingleton<HttpApi>()
|
||||
.AddSingleton<IpcTester>()
|
||||
.AddSingleton<DalamudSubstitutionProvider>();
|
||||
=> services.AddSingleton<IPenumbraApi>(x => x.GetRequiredService<PenumbraApi>());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ public partial class ModEditWindow
|
|||
var resources = ResourceTreeApiHelper
|
||||
.GetResourcesOfType(_resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly), type)
|
||||
.Values
|
||||
.SelectMany(resources => resources.Values)
|
||||
.Select(resource => resource.Item1);
|
||||
.SelectMany(r => r.Values)
|
||||
.Select(r => r.Item1);
|
||||
|
||||
return new HashSet<string>(resources, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ using Dalamud.Game.ClientState.Objects;
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
|
|
@ -28,8 +30,8 @@ public sealed class CollectionPanel : IDisposable
|
|||
private readonly IndividualAssignmentUi _individualAssignmentUi;
|
||||
private readonly InheritanceUi _inheritanceUi;
|
||||
private readonly ModStorage _mods;
|
||||
|
||||
private readonly IFontHandle _nameFont;
|
||||
private readonly FilenameService _fileNames;
|
||||
private readonly IFontHandle _nameFont;
|
||||
|
||||
private static readonly IReadOnlyDictionary<CollectionType, (string Name, uint Border)> Buttons = CreateButtons();
|
||||
private static readonly IReadOnlyList<(CollectionType, bool, bool, string, uint)> AdvancedTree = CreateTree();
|
||||
|
|
@ -38,7 +40,7 @@ public sealed class CollectionPanel : IDisposable
|
|||
private int _draggedIndividualAssignment = -1;
|
||||
|
||||
public CollectionPanel(DalamudPluginInterface pi, CommunicatorService communicator, CollectionManager manager,
|
||||
CollectionSelector selector, ActorManager actors, ITargetManager targets, ModStorage mods)
|
||||
CollectionSelector selector, ActorManager actors, ITargetManager targets, ModStorage mods, FilenameService fileNames)
|
||||
{
|
||||
_collections = manager.Storage;
|
||||
_active = manager.Active;
|
||||
|
|
@ -46,6 +48,7 @@ public sealed class CollectionPanel : IDisposable
|
|||
_actors = actors;
|
||||
_targets = targets;
|
||||
_mods = mods;
|
||||
_fileNames = fileNames;
|
||||
_individualAssignmentUi = new IndividualAssignmentUi(communicator, actors, manager);
|
||||
_inheritanceUi = new InheritanceUi(manager, _selector);
|
||||
_nameFont = pi.UiBuilder.FontAtlas.NewGameFontHandle(new GameFontStyle(GameFontFamilyAndSize.Jupiter23));
|
||||
|
|
@ -206,12 +209,57 @@ public sealed class CollectionPanel : IDisposable
|
|||
var collection = _active.Current;
|
||||
DrawCollectionName(collection);
|
||||
DrawStatistics(collection);
|
||||
DrawCollectionData(collection);
|
||||
_inheritanceUi.Draw();
|
||||
ImGui.Separator();
|
||||
DrawInactiveSettingsList(collection);
|
||||
DrawSettingsList(collection);
|
||||
}
|
||||
|
||||
private void DrawCollectionData(ModCollection collection)
|
||||
{
|
||||
ImGui.Dummy(Vector2.Zero);
|
||||
ImGui.BeginGroup();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted("Name");
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted("Identifier");
|
||||
ImGui.EndGroup();
|
||||
ImGui.SameLine();
|
||||
ImGui.BeginGroup();
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f));
|
||||
var name = collection.Name;
|
||||
var identifier = collection.Identifier;
|
||||
var width = ImGui.GetContentRegionAvail().X;
|
||||
var fileName = _fileNames.CollectionFile(collection);
|
||||
ImGui.SetNextItemWidth(width);
|
||||
ImGui.InputText("##name", ref name, 128);
|
||||
using (ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
if (ImGui.Button(collection.Identifier, new Vector2(width, 0)))
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(ex, $"Could not open file {fileName}.", $"Could not open file {fileName}",
|
||||
NotificationType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
ImGui.SetClipboardText(identifier);
|
||||
|
||||
ImGuiUtil.HoverTooltip(
|
||||
$"Open the file\n\t{fileName}\ncontaining this design in the .json-editor of your choice.\n\nRight-Click to copy identifier to clipboard.");
|
||||
|
||||
ImGui.EndGroup();
|
||||
ImGui.Dummy(Vector2.Zero);
|
||||
ImGui.Separator();
|
||||
ImGui.Dummy(Vector2.Zero);
|
||||
}
|
||||
|
||||
private void DrawContext(bool open, ModCollection? collection, CollectionType type, ActorIdentifier identifier, string text, char suffix)
|
||||
{
|
||||
var label = $"{type}{text}{suffix}";
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public sealed class CollectionSelector : ItemSelector<ModCollection>, IDisposabl
|
|||
|
||||
public CollectionSelector(Configuration config, CommunicatorService communicator, CollectionStorage storage, ActiveCollections active,
|
||||
TutorialService tutorial)
|
||||
: base(new List<ModCollection>(), Flags.Delete | Flags.Add | Flags.Duplicate | Flags.Filter)
|
||||
: base([], Flags.Delete | Flags.Add | Flags.Duplicate | Flags.Filter)
|
||||
{
|
||||
_config = config;
|
||||
_communicator = communicator;
|
||||
|
|
|
|||
|
|
@ -41,12 +41,12 @@ public sealed class CollectionsTab : IDisposable, ITab
|
|||
}
|
||||
|
||||
public CollectionsTab(DalamudPluginInterface pi, Configuration configuration, CommunicatorService communicator,
|
||||
CollectionManager collectionManager, ModStorage modStorage, ActorManager actors, ITargetManager targets, TutorialService tutorial)
|
||||
CollectionManager collectionManager, ModStorage modStorage, ActorManager actors, ITargetManager targets, TutorialService tutorial, FilenameService fileNames)
|
||||
{
|
||||
_config = configuration.Ephemeral;
|
||||
_tutorial = tutorial;
|
||||
_selector = new CollectionSelector(configuration, communicator, collectionManager.Storage, collectionManager.Active, _tutorial);
|
||||
_panel = new CollectionPanel(pi, communicator, collectionManager, _selector, actors, targets, modStorage);
|
||||
_panel = new CollectionPanel(pi, communicator, collectionManager, _selector, actors, targets, modStorage, fileNames);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public static class CrashDataExtensions
|
|||
ImGuiUtil.DrawTableColumn(character.Age.ToString(CultureInfo.InvariantCulture));
|
||||
ImGuiUtil.DrawTableColumn(character.ThreadId.ToString());
|
||||
ImGuiUtil.DrawTableColumn(character.CharacterName);
|
||||
ImGuiUtil.DrawTableColumn(character.CollectionName);
|
||||
ImGuiUtil.DrawTableColumn(character.CollectionId.ToString());
|
||||
ImGuiUtil.DrawTableColumn(character.CharacterAddress);
|
||||
ImGuiUtil.DrawTableColumn(character.Timestamp.ToString());
|
||||
}, ImGui.GetTextLineHeightWithSpacing());
|
||||
|
|
@ -79,7 +79,7 @@ public static class CrashDataExtensions
|
|||
ImGuiUtil.DrawTableColumn(file.ActualFileName);
|
||||
ImGuiUtil.DrawTableColumn(file.RequestedFileName);
|
||||
ImGuiUtil.DrawTableColumn(file.CharacterName);
|
||||
ImGuiUtil.DrawTableColumn(file.CollectionName);
|
||||
ImGuiUtil.DrawTableColumn(file.CollectionId.ToString());
|
||||
ImGuiUtil.DrawTableColumn(file.CharacterAddress);
|
||||
ImGuiUtil.DrawTableColumn(file.Timestamp.ToString());
|
||||
}, ImGui.GetTextLineHeightWithSpacing());
|
||||
|
|
@ -102,7 +102,7 @@ public static class CrashDataExtensions
|
|||
ImGuiUtil.DrawTableColumn(vfx.ThreadId.ToString());
|
||||
ImGuiUtil.DrawTableColumn(vfx.InvocationType);
|
||||
ImGuiUtil.DrawTableColumn(vfx.CharacterName);
|
||||
ImGuiUtil.DrawTableColumn(vfx.CollectionName);
|
||||
ImGuiUtil.DrawTableColumn(vfx.CollectionId.ToString());
|
||||
ImGuiUtil.DrawTableColumn(vfx.CharacterAddress);
|
||||
ImGuiUtil.DrawTableColumn(vfx.Timestamp.ToString());
|
||||
}, ImGui.GetTextLineHeightWithSpacing());
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
|||
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||
using ImGuiClip = OtterGui.ImGuiClip;
|
||||
using Penumbra.Api.IpcTester;
|
||||
|
||||
namespace Penumbra.UI.Tabs.Debug;
|
||||
|
||||
|
|
@ -76,7 +77,6 @@ public class DebugTab : Window, ITab
|
|||
private readonly CharacterUtility _characterUtility;
|
||||
private readonly ResidentResourceManager _residentResources;
|
||||
private readonly ResourceManagerService _resourceManager;
|
||||
private readonly PenumbraIpcProviders _ipc;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly DrawObjectState _drawObjectState;
|
||||
private readonly PathState _pathState;
|
||||
|
|
@ -100,7 +100,7 @@ public class DebugTab : Window, ITab
|
|||
IClientState clientState,
|
||||
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorManager actors, StainService stains,
|
||||
CharacterUtility characterUtility, ResidentResourceManager residentResources,
|
||||
ResourceManagerService resourceManager, PenumbraIpcProviders ipc, CollectionResolver collectionResolver,
|
||||
ResourceManagerService resourceManager, CollectionResolver collectionResolver,
|
||||
DrawObjectState drawObjectState, PathState pathState, SubfileHelper subfileHelper, IdentifiedCollectionCache identifiedCollectionCache,
|
||||
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
||||
TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes,
|
||||
|
|
@ -124,7 +124,6 @@ public class DebugTab : Window, ITab
|
|||
_characterUtility = characterUtility;
|
||||
_residentResources = residentResources;
|
||||
_resourceManager = resourceManager;
|
||||
_ipc = ipc;
|
||||
_collectionResolver = collectionResolver;
|
||||
_drawObjectState = drawObjectState;
|
||||
_pathState = pathState;
|
||||
|
|
@ -440,7 +439,9 @@ public class DebugTab : Window, ITab
|
|||
: $"0x{(nint)((Character*)obj.Address)->GameObject.GetDrawObject():X}");
|
||||
var identifier = _actors.FromObject(obj, out _, false, true, false);
|
||||
ImGuiUtil.DrawTableColumn(_actors.ToString(identifier));
|
||||
var id = obj.AsObject->ObjectKind ==(byte) ObjectKind.BattleNpc ? $"{identifier.DataId} | {obj.AsObject->DataID}" : identifier.DataId.ToString();
|
||||
var id = obj.AsObject->ObjectKind == (byte)ObjectKind.BattleNpc
|
||||
? $"{identifier.DataId} | {obj.AsObject->DataID}"
|
||||
: identifier.DataId.ToString();
|
||||
ImGuiUtil.DrawTableColumn(id);
|
||||
}
|
||||
|
||||
|
|
@ -969,13 +970,8 @@ public class DebugTab : Window, ITab
|
|||
/// <summary> Draw information about IPC options and availability. </summary>
|
||||
private void DrawDebugTabIpc()
|
||||
{
|
||||
if (!ImGui.CollapsingHeader("IPC"))
|
||||
{
|
||||
_ipcTester.UnsubscribeEvents();
|
||||
return;
|
||||
}
|
||||
|
||||
_ipcTester.Draw();
|
||||
if (ImGui.CollapsingHeader("IPC"))
|
||||
_ipcTester.Draw();
|
||||
}
|
||||
|
||||
/// <summary> Helper to print a property and its value in a 2-column table. </summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue