diff --git a/Penumbra.Api b/Penumbra.Api index 1a3f9d50..891eb195 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 1a3f9d501ad44e020500eb7d0a79f91a04e46c93 +Subproject commit 891eb195c29904824004c45f84b92a9e1dd98ddf diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index d6daf14c..fea10f95 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -13,7 +13,6 @@ using Penumbra.Api.Enums; using Penumbra.Api.Helpers; using Penumbra.String; using Penumbra.String.Classes; -using Swan; using Penumbra.Meta.Manipulations; namespace Penumbra.Api; @@ -582,6 +581,7 @@ public class IpcTester : IDisposable private string _currentResolvePath = string.Empty; private string _currentResolveCharacter = string.Empty; private string _currentReversePath = string.Empty; + private int _currentReverseIdx = 0; public Resolve( DalamudPluginInterface pi ) => _pi = pi; @@ -598,6 +598,7 @@ public class IpcTester : IDisposable ImGui.InputTextWithHint( "##resolveCharacter", "Character Name (leave blank for default)...", ref _currentResolveCharacter, 32 ); 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 ) { @@ -628,6 +629,12 @@ public class IpcTester : IDisposable ImGui.TextUnformatted( Ipc.ResolveCharacterPath.Subscriber( _pi ).Invoke( _currentResolvePath, _currentResolveCharacter ) ); } + DrawIntro( Ipc.ResolveGameObjectPath.Label, "Game Object Collection Resolve" ); + if( _currentResolvePath.Length != 0 ) + { + ImGui.TextUnformatted( Ipc.ResolveGameObjectPath.Subscriber( _pi ).Invoke( _currentResolvePath, _currentReverseIdx ) ); + } + DrawIntro( Ipc.ReverseResolvePath.Label, "Reversed Game Paths" ); if( _currentReversePath.Length > 0 ) { @@ -655,6 +662,20 @@ public class IpcTester : IDisposable } } } + + DrawIntro( Ipc.ReverseResolveGameObjectPath.Label, "Reversed Game Paths (Game Object)" ); + if( _currentReversePath.Length > 0 ) + { + var list = Ipc.ReverseResolveGameObjectPath.Subscriber( _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 ) ) ); + } + } + } } } @@ -763,7 +784,8 @@ public class IpcTester : IDisposable { private readonly DalamudPluginInterface _pi; - private string _characterName = string.Empty; + private string _characterName = string.Empty; + private int _gameObjectIndex = 0; public Meta( DalamudPluginInterface pi ) => _pi = pi; @@ -777,6 +799,7 @@ public class IpcTester : IDisposable } ImGui.InputTextWithHint( "##characterName", "Character Name...", ref _characterName, 64 ); + ImGui.InputInt( "##metaIdx", ref _gameObjectIndex, 0, 0 ); using var table = ImRaii.Table( string.Empty, 3, ImGuiTableFlags.SizingFixedFit ); if( !table ) { @@ -796,6 +819,13 @@ public class IpcTester : IDisposable var base64 = Ipc.GetPlayerMetaManipulations.Subscriber( _pi ).Invoke(); ImGui.SetClipboardText( base64 ); } + + DrawIntro( Ipc.GetGameObjectMetaManipulations.Label, "Game Object Manipulations" ); + if( ImGui.Button( "Copy to Clipboard##GameObject" ) ) + { + var base64 = Ipc.GetGameObjectMetaManipulations.Subscriber( _pi ).Invoke( _gameObjectIndex ); + ImGui.SetClipboardText( base64 ); + } } } diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index aa6bf84a..49ad61cd 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -23,7 +23,7 @@ namespace Penumbra.Api; public class PenumbraApi : IDisposable, IPenumbraApi { public (int, int) ApiVersion - => ( 4, 16 ); + => ( 4, 17 ); private Penumbra? _penumbra; private Lumina.GameData? _lumina; @@ -216,6 +216,13 @@ public class PenumbraApi : IDisposable, IPenumbraApi public string ResolvePath( string path, string characterName ) => ResolvePath( path, characterName, ushort.MaxValue ); + public string ResolveGameObjectPath( string path, int gameObjectIdx ) + { + CheckInitialized(); + AssociatedCollection( gameObjectIdx, out var collection ); + return ResolvePath( path, Penumbra.ModManager, collection ); + } + public string ResolvePath( string path, string characterName, ushort worldId ) { CheckInitialized(); @@ -239,6 +246,19 @@ public class PenumbraApi : IDisposable, IPenumbraApi return ret.Select( r => r.ToString() ).ToArray(); } + public string[] ReverseResolveGameObjectPath( string path, int gameObjectIdx ) + { + CheckInitialized(); + if( !Penumbra.Config.EnableMods ) + { + return new[] { path }; + } + + AssociatedCollection( gameObjectIdx, out var collection ); + var ret = collection.ReverseResolvePath( new FullPath( path ) ); + return ret.Select( r => r.ToString() ).ToArray(); + } + public string[] ReverseResolvePlayerPath( string path ) { CheckInitialized(); @@ -794,6 +814,14 @@ public class PenumbraApi : IDisposable, IPenumbraApi return Functions.ToCompressedBase64( set, MetaManipulation.CurrentVersion ); } + public string GetGameObjectMetaManipulations( int gameObjectIdx ) + { + CheckInitialized(); + AssociatedCollection( gameObjectIdx, out var collection ); + var set = collection.MetaCache?.Manipulations.ToArray() ?? Array.Empty< MetaManipulation >(); + return Functions.ToCompressedBase64( set, MetaManipulation.CurrentVersion ); + } + internal bool HasTooltip => ChangedItemTooltip != null; @@ -812,6 +840,26 @@ public class PenumbraApi : IDisposable, IPenumbraApi } } + // 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. + private unsafe bool AssociatedCollection( int gameObjectIdx, out ModCollection collection ) + { + collection = Penumbra.CollectionManager.Default; + if( gameObjectIdx < 0 || gameObjectIdx >= Dalamud.Objects.Length ) + { + return false; + } + + var ptr = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )Dalamud.Objects.GetObjectAddress( gameObjectIdx ); + var data = PathResolver.IdentifyCollection( ptr, false ); + if( data.Valid ) + { + collection = data.ModCollection; + } + + return true; + } + // Resolve a path given by string for a specific collection. private static string ResolvePath( string path, Mod.Manager _, ModCollection collection ) { diff --git a/Penumbra/Api/PenumbraIpcProviders.cs b/Penumbra/Api/PenumbraIpcProviders.cs index dd042b99..69db9c99 100644 --- a/Penumbra/Api/PenumbraIpcProviders.cs +++ b/Penumbra/Api/PenumbraIpcProviders.cs @@ -52,9 +52,11 @@ public class PenumbraIpcProviders : IDisposable 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, string[] > ReverseResolvePathPlayer; + internal readonly FuncProvider< string, int, string[] > ReverseResolveGameObjectPath; + internal readonly FuncProvider< string, string[] > ReverseResolvePlayerPath; // Collections internal readonly FuncProvider< IList< string > > GetCollections; @@ -67,6 +69,7 @@ public class PenumbraIpcProviders : IDisposable // 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; @@ -144,12 +147,14 @@ public class PenumbraIpcProviders : IDisposable () => 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 ); - ResolveCharacterPath = Ipc.ResolveCharacterPath.Provider( pi, Api.ResolvePath ); - ReverseResolvePath = Ipc.ReverseResolvePath.Provider( pi, Api.ReverseResolvePath ); - ReverseResolvePathPlayer = Ipc.ReverseResolvePlayerPath.Provider( pi, Api.ReverseResolvePlayerPath ); + 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 ); // Collections GetCollections = Ipc.GetCollections.Provider( pi, Api.GetCollections ); @@ -160,8 +165,9 @@ public class PenumbraIpcProviders : IDisposable GetChangedItems = Ipc.GetChangedItems.Provider( pi, Api.GetChangedItemsForCollection ); // Meta - GetPlayerMetaManipulations = Ipc.GetPlayerMetaManipulations.Provider( pi, Api.GetPlayerMetaManipulations ); - GetMetaManipulations = Ipc.GetMetaManipulations.Provider( pi, Api.GetMetaManipulations ); + 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 ); @@ -242,9 +248,11 @@ public class PenumbraIpcProviders : IDisposable ResolveDefaultPath.Dispose(); ResolveInterfacePath.Dispose(); ResolvePlayerPath.Dispose(); + ResolveGameObjectPath.Dispose(); ResolveCharacterPath.Dispose(); ReverseResolvePath.Dispose(); - ReverseResolvePathPlayer.Dispose(); + ReverseResolveGameObjectPath.Dispose(); + ReverseResolvePlayerPath.Dispose(); // Collections GetCollections.Dispose(); @@ -257,6 +265,7 @@ public class PenumbraIpcProviders : IDisposable // Meta GetPlayerMetaManipulations.Dispose(); GetMetaManipulations.Dispose(); + GetGameObjectMetaManipulations.Dispose(); // Mods GetMods.Dispose(); diff --git a/Penumbra/Interop/Resolver/PathResolver.Identification.cs b/Penumbra/Interop/Resolver/PathResolver.Identification.cs index 7895e9fa..991027e4 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Identification.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Identification.cs @@ -17,7 +17,7 @@ namespace Penumbra.Interop.Resolver; public unsafe partial class PathResolver { // Identify the correct collection for a GameObject by index and name. - private static ResolveData IdentifyCollection( GameObject* gameObject, bool useCache ) + public static ResolveData IdentifyCollection( GameObject* gameObject, bool useCache ) { if( gameObject == null ) { diff --git a/Penumbra/packages.lock.json b/Penumbra/packages.lock.json index 38fcd25f..aea52c7e 100644 --- a/Penumbra/packages.lock.json +++ b/Penumbra/packages.lock.json @@ -67,7 +67,7 @@ "penumbra.gamedata": { "type": "Project", "dependencies": { - "Penumbra.Api": "[1.0.0, )", + "Penumbra.Api": "[1.0.3, )", "Penumbra.String": "[1.0.0, )" } },