diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index b50a98ba..25c828e1 100644 --- a/Penumbra/Api/IPenumbraApi.cs +++ b/Penumbra/Api/IPenumbraApi.cs @@ -20,6 +20,7 @@ public interface IPenumbraApiBase public delegate void ChangedItemHover( object? item ); public delegate void ChangedItemClick( MouseButton button, object? item ); +public delegate void GameObjectRedrawn( IntPtr objectPtr, int objectTableIndex ); public enum PenumbraApiEc { @@ -51,7 +52,7 @@ public interface IPenumbraApi : IPenumbraApiBase // Triggered when the user clicks a listed changed object in a mod tab. public event ChangedItemClick? ChangedItemClicked; - event EventHandler? ObjectIsRedrawn; + public event GameObjectRedrawn? GameObjectRedrawn; // Queue redrawing of all actors of the given name with the given RedrawType. public void RedrawObject( string name, RedrawType setting ); @@ -74,7 +75,7 @@ public interface IPenumbraApi : IPenumbraApiBase public string ResolvePath( string gamePath, string characterName ); // Reverse resolves a given modded local path into its replacement in form of all applicable game path for given character - public string[] ReverseResolvePath( string moddedPath, string characterName ); + public IList ReverseResolvePath( string moddedPath, string characterName ); // Try to load a given gamePath with the resolved path from Penumbra. public T? GetFile< T >( string gamePath ) where T : FileResource; diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 87904e0a..33b4dedf 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -20,7 +20,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi public int ApiVersion { get; } = 4; private Penumbra? _penumbra; private Lumina.GameData? _lumina; - public event EventHandler? ObjectIsRedrawn; + public event GameObjectRedrawn? GameObjectRedrawn; public bool Valid => _penumbra != null; @@ -29,21 +29,16 @@ public class PenumbraApi : IDisposable, IPenumbraApi { _penumbra = penumbra; _lumina = ( Lumina.GameData? )Dalamud.GameData.GetType() - .GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic ) - ?.GetValue( Dalamud.GameData ); - _penumbra.ObjectReloader.ObjectIsRedrawn += ObjectReloader_ObjectIsRedrawn; - } - - private void ObjectReloader_ObjectIsRedrawn( object? sender, EventArgs e ) - { - ObjectIsRedrawn?.Invoke( sender, e ); + .GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic ) + ?.GetValue( Dalamud.GameData ); + _penumbra.ObjectReloader.GameObjectRedrawn += OnGameObjectRedrawn; } public void Dispose() { - _penumbra!.ObjectReloader.ObjectIsRedrawn -= ObjectReloader_ObjectIsRedrawn; - _penumbra = null; - _lumina = null; + _penumbra!.ObjectReloader.GameObjectRedrawn -= OnGameObjectRedrawn; + _penumbra = null; + _lumina = null; } public event ChangedItemClick? ChangedItemClicked; @@ -98,13 +93,18 @@ public class PenumbraApi : IDisposable, IPenumbraApi _penumbra!.ObjectReloader.RedrawObject( gameObject, setting ); } + private void OnGameObjectRedrawn( IntPtr objectAddress, int objectTableIndex ) + { + GameObjectRedrawn?.Invoke( objectAddress, objectTableIndex ); + } + public void RedrawAll( RedrawType setting ) { CheckInitialized(); _penumbra!.ObjectReloader.RedrawAll( setting ); } - private static string ResolvePath( string path, Mods.Mod.Manager _, ModCollection collection ) + private static string ResolvePath( string path, Mod.Manager _, ModCollection collection ) { if( !Penumbra.Config.EnableMods ) { @@ -129,7 +129,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi Penumbra.CollectionManager.Character( characterName ) ); } - public string[] ReverseResolvePath( string path, string characterName ) + public IList< string > ReverseResolvePath( string path, string characterName ) { CheckInitialized(); if( !Penumbra.Config.EnableMods ) @@ -137,11 +137,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi return new[] { path }; } - var gamePath = Utf8GamePath.FromString( path, out var p, true ) ? p : Utf8GamePath.Empty; - var ret = Penumbra.CollectionManager.Character( characterName ).ResolveReversePath( new FullPath( path ) ) ?? - new List< Utf8GamePath >(); - if( ret.Count == 0 ) ret.Add( gamePath ); - return ret.Select( r => r.ToString() ).ToArray(); + var ret = Penumbra.CollectionManager.Character( characterName ).ReverseResolvePath( new FullPath( path ) ); + return ret.Select( r => r.ToString() ).ToList(); } private T? GetFileIntern< T >( string resolvedPath ) where T : FileResource @@ -233,10 +230,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi return Penumbra.ModManager.Select( m => ( m.ModPath.Name, m.Name.Text ) ).ToArray(); } - public IDictionary< string, (IList, SelectType) >? GetAvailableModSettings( string modDirectory, string modName ) + public IDictionary< string, (IList< string >, SelectType) >? GetAvailableModSettings( string modDirectory, string modName ) => throw new NotImplementedException(); - public (PenumbraApiEc, (bool, int, IDictionary< string, IList >, bool)?) GetCurrentModSettings( string collectionName, string modDirectory, string modName, + public (PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)?) GetCurrentModSettings( string collectionName, + string modDirectory, string modName, bool allowInheritance ) => throw new NotImplementedException(); @@ -252,7 +250,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, string option ) => throw new NotImplementedException(); - public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, IReadOnlyList options ) + public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, + IReadOnlyList< string > options ) => throw new NotImplementedException(); public PenumbraApiEc CreateTemporaryCollection( string collectionName, string? character, bool forceOverwriteCharacter ) diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index 1d197b07..14f3ea11 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -111,17 +111,17 @@ public partial class PenumbraIpc public partial class PenumbraIpc { - public const string LabelProviderRedrawName = "Penumbra.RedrawObjectByName"; - public const string LabelProviderRedrawIndex = "Penumbra.RedrawObjectByIndex"; - public const string LabelProviderRedrawObject = "Penumbra.RedrawObject"; - public const string LabelProviderRedrawAll = "Penumbra.RedrawAll"; - public const string LabelProviderObjectIsRedrawn = "Penumbra.ObjectIsRedrawn"; + public const string LabelProviderRedrawName = "Penumbra.RedrawObjectByName"; + public const string LabelProviderRedrawIndex = "Penumbra.RedrawObjectByIndex"; + public const string LabelProviderRedrawObject = "Penumbra.RedrawObject"; + public const string LabelProviderRedrawAll = "Penumbra.RedrawAll"; + public const string LabelProviderGameObjectRedrawn = "Penumbra.GameObjectRedrawn"; internal ICallGateProvider< string, int, object >? ProviderRedrawName; internal ICallGateProvider< int, int, object >? ProviderRedrawIndex; internal ICallGateProvider< GameObject, int, object >? ProviderRedrawObject; internal ICallGateProvider< int, object >? ProviderRedrawAll; - internal ICallGateProvider< string, string >? ProviderObjectIsRedrawn; + internal ICallGateProvider< IntPtr, int, object? >? ProviderGameObjectRedrawn; private static RedrawType CheckRedrawType( int value ) { @@ -178,19 +178,17 @@ public partial class PenumbraIpc try { - ProviderObjectIsRedrawn = pi.GetIpcProvider< string, string >( LabelProviderObjectIsRedrawn ); - Api.ObjectIsRedrawn += Api_ObjectIsRedrawn; + ProviderGameObjectRedrawn = pi.GetIpcProvider< IntPtr, int, object? >( LabelProviderGameObjectRedrawn ); + Api.GameObjectRedrawn += OnGameObjectRedrawn; } catch( Exception e ) { - PluginLog.Error( $"Error registering IPC provider for {LabelProviderObjectIsRedrawn}:\n{e}" ); + PluginLog.Error( $"Error registering IPC provider for {LabelProviderGameObjectRedrawn}:\n{e}" ); } } - private void Api_ObjectIsRedrawn( object? sender, EventArgs e ) - { - ProviderObjectIsRedrawn?.SendMessage( ( ( GameObject? )sender )?.Name.ToString() ?? "" ); - } + private void OnGameObjectRedrawn( IntPtr objectAddress, int objectTableIndex ) + => ProviderGameObjectRedrawn?.SendMessage( objectAddress, objectTableIndex ); private void DisposeRedrawProviders() { @@ -198,7 +196,7 @@ public partial class PenumbraIpc ProviderRedrawIndex?.UnregisterAction(); ProviderRedrawObject?.UnregisterAction(); ProviderRedrawAll?.UnregisterAction(); - Api.ObjectIsRedrawn -= Api_ObjectIsRedrawn; + Api.GameObjectRedrawn -= OnGameObjectRedrawn; } } @@ -209,10 +207,10 @@ public partial class PenumbraIpc public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo"; public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath"; - internal ICallGateProvider< string, string >? ProviderResolveDefault; - internal ICallGateProvider< string, string, string >? ProviderResolveCharacter; - internal ICallGateProvider< IntPtr, (IntPtr, string) >? ProviderGetDrawObjectInfo; - internal ICallGateProvider< string, string, string[] >? ProviderReverseResolvePath; + internal ICallGateProvider< string, string >? ProviderResolveDefault; + internal ICallGateProvider< string, string, string >? ProviderResolveCharacter; + internal ICallGateProvider< IntPtr, (IntPtr, string) >? ProviderGetDrawObjectInfo; + internal ICallGateProvider< string, string, IList< string > >? ProviderReverseResolvePath; private void InitializeResolveProviders( DalamudPluginInterface pi ) { @@ -248,7 +246,7 @@ public partial class PenumbraIpc try { - ProviderReverseResolvePath = pi.GetIpcProvider< string, string, string[] >( LabelProviderReverseResolvePath ); + ProviderReverseResolvePath = pi.GetIpcProvider< string, string, IList< string > >( LabelProviderReverseResolvePath ); ProviderReverseResolvePath.RegisterFunc( Api.ReverseResolvePath ); } catch( Exception e ) @@ -262,6 +260,7 @@ public partial class PenumbraIpc ProviderGetDrawObjectInfo?.UnregisterFunc(); ProviderResolveDefault?.UnregisterFunc(); ProviderResolveCharacter?.UnregisterFunc(); + ProviderReverseResolvePath?.UnregisterFunc(); } } diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 871280a2..5cf71fc2 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -44,7 +44,8 @@ public partial class ModCollection PluginLog.Verbose( "Cleared cache of collection {Name:l}.", Name ); } - public List? ResolveReversePath( FullPath path ) => _cache?.ReverseResolvePath( path ); + public IEnumerable< Utf8GamePath > ReverseResolvePath( FullPath path ) + => _cache?.ReverseResolvePath( path ) ?? Array.Empty< Utf8GamePath >(); public FullPath? ResolvePath( Utf8GamePath path ) => _cache?.ResolvePath( path ); diff --git a/Penumbra/Collections/ModCollection.Cache.cs b/Penumbra/Collections/ModCollection.Cache.cs index 4b280697..d4e75a2c 100644 --- a/Penumbra/Collections/ModCollection.Cache.cs +++ b/Penumbra/Collections/ModCollection.Cache.cs @@ -12,7 +12,6 @@ using Penumbra.Util; namespace Penumbra.Collections; public record struct ModPath( Mod Mod, FullPath Path ); - public record ModConflicts( Mod Mod2, List< object > Conflicts, bool HasPriority, bool Solved ); public partial class ModCollection @@ -72,7 +71,7 @@ public partial class ModCollection } if( candidate.Path.InternalName.Length > Utf8GamePath.MaxGamePathLength - || candidate.Path.IsRooted && !candidate.Path.Exists ) + || candidate.Path.IsRooted && !candidate.Path.Exists ) { return null; } @@ -80,11 +79,26 @@ public partial class ModCollection return candidate.Path; } - public List< Utf8GamePath > ReverseResolvePath( FullPath localFilePath ) + // For a given full path, find all game paths that currently use this file. + public IEnumerable< Utf8GamePath > ReverseResolvePath( FullPath localFilePath ) { - string strToSearchFor = localFilePath.FullName.Replace( '/', '\\' ).ToLower(); - return ResolvedFiles.Where( f => f.Value.Path.FullName.ToLower() == strToSearchFor ) - .Select( kvp => kvp.Key ).ToList(); + var needle = localFilePath.FullName.ToLower(); + if( localFilePath.IsRooted ) + { + needle = needle.Replace( '/', '\\' ); + } + + var iterator = ResolvedFiles + .Where( f => string.Equals( f.Value.Path.FullName, needle, StringComparison.InvariantCultureIgnoreCase ) ) + .Select( kvp => kvp.Key ); + + // For files that are not rooted, try to add themselves. + if( !localFilePath.IsRooted && Utf8GamePath.FromString( localFilePath.FullName, out var utf8 ) ) + { + iterator = iterator.Prepend( utf8 ); + } + + return iterator; } private void OnModSettingChange( ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool _ ) @@ -253,8 +267,8 @@ public partial class ModCollection case SelectType.Multi: { foreach( var (option, _) in group.WithIndex() - .OrderByDescending( p => group.OptionPriority( p.Item2 ) ) - .Where( p => ( ( 1 << p.Item2 ) & config ) != 0 ) ) + .OrderByDescending( p => group.OptionPriority( p.Item2 ) ) + .Where( p => ( ( 1 << p.Item2 ) & config ) != 0 ) ) { AddSubMod( option, mod ); } @@ -360,7 +374,7 @@ public partial class ModCollection // Returns if the added mod takes priority before the existing mod. private bool AddConflict( object data, Mod addedMod, Mod existingMod ) { - var addedPriority = addedMod.Index >= 0 ? _collection[ addedMod.Index ].Settings!.Priority : int.MaxValue; + var addedPriority = addedMod.Index >= 0 ? _collection[ addedMod.Index ].Settings!.Priority : int.MaxValue; var existingPriority = existingMod.Index >= 0 ? _collection[ existingMod.Index ].Settings!.Priority : int.MaxValue; if( existingPriority < addedPriority ) @@ -368,9 +382,8 @@ public partial class ModCollection var tmpConflicts = Conflicts( existingMod ); foreach( var conflict in tmpConflicts ) { - if( data is Utf8GamePath path && conflict.Conflicts.RemoveAll( p => p is Utf8GamePath x && x.Equals( path ) ) > 0 - || data is MetaManipulation meta && - conflict.Conflicts.RemoveAll( m => m is MetaManipulation x && x.Equals( meta ) ) > 0 ) + if( data is Utf8GamePath path && conflict.Conflicts.RemoveAll( p => p is Utf8GamePath x && x.Equals( path ) ) > 0 + || data is MetaManipulation meta && conflict.Conflicts.RemoveAll( m => m is MetaManipulation x && x.Equals( meta ) ) > 0 ) { AddConflict( data, addedMod, conflict.Mod2 ); } diff --git a/Penumbra/Interop/ObjectReloader.cs b/Penumbra/Interop/ObjectReloader.cs index d58d434c..daf4ae7f 100644 --- a/Penumbra/Interop/ObjectReloader.cs +++ b/Penumbra/Interop/ObjectReloader.cs @@ -25,8 +25,6 @@ public unsafe partial class ObjectReloader private static void EnableDraw( GameObject actor ) => ( ( delegate* unmanaged< IntPtr, void >** )actor.Address )[ 0 ][ 16 ]( actor.Address ); - public event EventHandler? ObjectIsRedrawn; - // Check whether we currently are in GPose. // Also clear the name list. private void SetGPose() @@ -107,6 +105,8 @@ public sealed unsafe partial class ObjectReloader : IDisposable private readonly List< int > _afterGPoseQueue = new(GPoseSlots); private int _target = -1; + public event Action< IntPtr, int >? GameObjectRedrawn; + public ObjectReloader() => Dalamud.Framework.Update += OnUpdateEvent; @@ -134,7 +134,7 @@ public sealed unsafe partial class ObjectReloader : IDisposable } } - private static void WriteVisible( GameObject? actor ) + private void WriteVisible( GameObject? actor ) { if( BadRedrawIndices( actor, out var tableIndex ) ) { @@ -142,11 +142,12 @@ public sealed unsafe partial class ObjectReloader : IDisposable } *ActorDrawState( actor! ) &= ~DrawState.Invisibility; - if( IsGPoseActor( tableIndex ) ) { EnableDraw( actor! ); } + + GameObjectRedrawn?.Invoke( actor!.Address, tableIndex ); } private void ReloadActor( GameObject? actor ) @@ -282,8 +283,6 @@ public sealed unsafe partial class ObjectReloader : IDisposable break; default: throw new ArgumentOutOfRangeException( nameof( settings ), settings, null ); } - - ObjectIsRedrawn?.Invoke( actor, new EventArgs() ); } private static GameObject? GetLocalPlayer()