diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index 37265ce9..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,6 +52,7 @@ public interface IPenumbraApi : IPenumbraApiBase // Triggered when the user clicks a listed changed object in a mod tab. public event ChangedItemClick? ChangedItemClicked; + public event GameObjectRedrawn? GameObjectRedrawn; // Queue redrawing of all actors of the given name with the given RedrawType. public void RedrawObject( string name, RedrawType setting ); @@ -72,6 +74,9 @@ public interface IPenumbraApi : IPenumbraApiBase // Returns the given gamePath if penumbra would not manipulate it. 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 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; @@ -104,12 +109,12 @@ public interface IPenumbraApi : IPenumbraApiBase // Obtain the potential settings of a mod specified by its directory name first or mod name second. // Returns null if the mod could not be found. - public Dictionary< string, (string[], SelectType) >? GetAvailableModSettings( string modDirectory, string modName ); + public IDictionary< string, (IList, SelectType) >? GetAvailableModSettings( string modDirectory, string modName ); // Obtain the enabled state, the priority, the settings of a mod specified by its directory name first or mod name second, // and whether these settings are inherited, or null if the collection does not set them at all. // If allowInheritance is false, only the collection itself will be checked. - public (PenumbraApiEc, (bool, int, Dictionary< string, string[] >, bool)?) GetCurrentModSettings( string collectionName, + public (PenumbraApiEc, (bool, int, IDictionary< string, IList >, bool)?) GetCurrentModSettings( string collectionName, string modDirectory, string modName, bool allowInheritance ); // Try to set the inheritance state in the given collection of a mod specified by its directory name first or mod name second. @@ -131,7 +136,7 @@ public interface IPenumbraApi : IPenumbraApiBase public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, string option ); public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, - string[] options ); + IReadOnlyList options ); // Create a temporary collection without actual settings but with a cache. diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index a81ab204..33b4dedf 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -20,6 +20,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi public int ApiVersion { get; } = 4; private Penumbra? _penumbra; private Lumina.GameData? _lumina; + public event GameObjectRedrawn? GameObjectRedrawn; public bool Valid => _penumbra != null; @@ -30,12 +31,14 @@ public class PenumbraApi : IDisposable, IPenumbraApi _lumina = ( Lumina.GameData? )Dalamud.GameData.GetType() .GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic ) ?.GetValue( Dalamud.GameData ); + _penumbra.ObjectReloader.GameObjectRedrawn += OnGameObjectRedrawn; } public void Dispose() { - _penumbra = null; - _lumina = null; + _penumbra!.ObjectReloader.GameObjectRedrawn -= OnGameObjectRedrawn; + _penumbra = null; + _lumina = null; } public event ChangedItemClick? ChangedItemClicked; @@ -90,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 ) { @@ -121,6 +129,18 @@ public class PenumbraApi : IDisposable, IPenumbraApi Penumbra.CollectionManager.Character( characterName ) ); } + public IList< string > ReverseResolvePath( string path, string characterName ) + { + CheckInitialized(); + if( !Penumbra.Config.EnableMods ) + { + return new[] { path }; + } + + 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 { CheckInitialized(); @@ -210,10 +230,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi return Penumbra.ModManager.Select( m => ( m.ModPath.Name, m.Name.Text ) ).ToArray(); } - public Dictionary< string, (string[], SelectType) >? GetAvailableModSettings( string modDirectory, string modName ) + public IDictionary< string, (IList< string >, SelectType) >? GetAvailableModSettings( string modDirectory, string modName ) => throw new NotImplementedException(); - public (PenumbraApiEc, (bool, int, Dictionary< string, string[] >, 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(); @@ -229,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, string[] 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 d8dc6db0..14f3ea11 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -111,15 +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 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< IntPtr, int, object? >? ProviderGameObjectRedrawn; private static RedrawType CheckRedrawType( int value ) { @@ -146,7 +148,7 @@ public partial class PenumbraIpc try { - ProviderRedrawIndex = pi.GetIpcProvider( LabelProviderRedrawIndex ); + ProviderRedrawIndex = pi.GetIpcProvider< int, int, object >( LabelProviderRedrawIndex ); ProviderRedrawIndex.RegisterAction( ( idx, i ) => Api.RedrawObject( idx, CheckRedrawType( i ) ) ); } catch( Exception e ) @@ -173,26 +175,42 @@ public partial class PenumbraIpc { PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawAll}:\n{e}" ); } + + try + { + ProviderGameObjectRedrawn = pi.GetIpcProvider< IntPtr, int, object? >( LabelProviderGameObjectRedrawn ); + Api.GameObjectRedrawn += OnGameObjectRedrawn; + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderGameObjectRedrawn}:\n{e}" ); + } } + private void OnGameObjectRedrawn( IntPtr objectAddress, int objectTableIndex ) + => ProviderGameObjectRedrawn?.SendMessage( objectAddress, objectTableIndex ); + private void DisposeRedrawProviders() { ProviderRedrawName?.UnregisterAction(); ProviderRedrawIndex?.UnregisterAction(); ProviderRedrawObject?.UnregisterAction(); ProviderRedrawAll?.UnregisterAction(); + Api.GameObjectRedrawn -= OnGameObjectRedrawn; } } public partial class PenumbraIpc { - public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath"; - public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath"; - public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo"; + public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath"; + public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath"; + 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 >? 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 ) { @@ -225,6 +243,16 @@ public partial class PenumbraIpc { PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetDrawObjectInfo}:\n{e}" ); } + + try + { + ProviderReverseResolvePath = pi.GetIpcProvider< string, string, IList< string > >( LabelProviderReverseResolvePath ); + ProviderReverseResolvePath.RegisterFunc( Api.ReverseResolvePath ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetDrawObjectInfo}:\n{e}" ); + } } private void DisposeResolveProviders() @@ -232,6 +260,7 @@ public partial class PenumbraIpc ProviderGetDrawObjectInfo?.UnregisterFunc(); ProviderResolveDefault?.UnregisterFunc(); ProviderResolveCharacter?.UnregisterFunc(); + ProviderReverseResolvePath?.UnregisterFunc(); } } @@ -356,7 +385,7 @@ public partial class PenumbraIpc try { - ProviderCharacterCollectionName = pi.GetIpcProvider< string, ( string, bool) >( LabelProviderCharacterCollectionName ); + ProviderCharacterCollectionName = pi.GetIpcProvider< string, (string, bool) >( LabelProviderCharacterCollectionName ); ProviderCharacterCollectionName.RegisterFunc( Api.GetCharacterCollection ); } catch( Exception e ) diff --git a/Penumbra/Collections/CollectionManager.Active.cs b/Penumbra/Collections/CollectionManager.Active.cs index d21ad8f9..f06a574f 100644 --- a/Penumbra/Collections/CollectionManager.Active.cs +++ b/Penumbra/Collections/CollectionManager.Active.cs @@ -20,6 +20,9 @@ public partial class ModCollection // The collection currently selected for changing settings. public ModCollection Current { get; private set; } = Empty; + // The collection currently selected is in use either as an active collection or through inheritance. + public bool CurrentCollectionInUse { get; private set; } + // The collection used for general file redirections and all characters not specifically named. public ModCollection Default { get; private set; } = Empty; @@ -78,6 +81,8 @@ public partial class ModCollection break; } + CurrentCollectionInUse = Characters.Values.Prepend( Default ).SelectMany( c => c.GetFlattenedInheritance() ).Contains( Current ); + CollectionChanged.Invoke( type, this[ oldCollectionIdx ], newCollection, characterName ); } diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 2e3bda92..5cf71fc2 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -44,6 +44,8 @@ public partial class ModCollection PluginLog.Verbose( "Cleared cache of collection {Name:l}.", Name ); } + 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 6e959e69..d4e75a2c 100644 --- a/Penumbra/Collections/ModCollection.Cache.cs +++ b/Penumbra/Collections/ModCollection.Cache.cs @@ -79,6 +79,28 @@ public partial class ModCollection return candidate.Path; } + // For a given full path, find all game paths that currently use this file. + public IEnumerable< Utf8GamePath > ReverseResolvePath( FullPath localFilePath ) + { + 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 _ ) { switch( type ) diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 8cb3e746..dfa22fd2 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -27,6 +27,7 @@ public partial class Configuration : IPluginConfiguration public bool UseCharacterCollectionInTryOn { get; set; } = true; public bool UseOwnerNameForCharacterCollection { get; set; } = true; public bool PreferNamedCollectionsOverOwners { get; set; } = true; + public bool UseDefaultCollectionForRetainers { get; set; } = false; #if DEBUG public bool DebugMode { get; set; } = true; diff --git a/Penumbra/Interop/ObjectReloader.cs b/Penumbra/Interop/ObjectReloader.cs index 058fc87c..daf4ae7f 100644 --- a/Penumbra/Interop/ObjectReloader.cs +++ b/Penumbra/Interop/ObjectReloader.cs @@ -25,7 +25,6 @@ public unsafe partial class ObjectReloader private static void EnableDraw( GameObject actor ) => ( ( delegate* unmanaged< IntPtr, void >** )actor.Address )[ 0 ][ 16 ]( actor.Address ); - // Check whether we currently are in GPose. // Also clear the name list. private void SetGPose() @@ -106,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; @@ -133,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 ) ) { @@ -141,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 ) diff --git a/Penumbra/Interop/Resolver/PathResolver.Data.cs b/Penumbra/Interop/Resolver/PathResolver.Data.cs index 00b92905..71cda6c5 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Data.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Data.cs @@ -271,6 +271,10 @@ public unsafe partial class PathResolver return Penumbra.CollectionManager.Default; } + // Housing Retainers + if( Penumbra.Config.UseDefaultCollectionForRetainers && gameObject->ObjectKind == (byte) ObjectKind.EventNpc && gameObject->DataID == 1011832 ) + return Penumbra.CollectionManager.Default; + string? actorName = null; if( Penumbra.Config.PreferNamedCollectionsOverOwners ) { diff --git a/Penumbra/Interop/Resolver/PathResolver.Demihuman.cs b/Penumbra/Interop/Resolver/PathResolver.Demihuman.cs new file mode 100644 index 00000000..c938bb72 --- /dev/null +++ b/Penumbra/Interop/Resolver/PathResolver.Demihuman.cs @@ -0,0 +1,88 @@ +using System; +using Dalamud.Hooking; +using Dalamud.Utility.Signatures; + +namespace Penumbra.Interop.Resolver; + +public unsafe partial class PathResolver +{ + [Signature( "48 8D 05 ?? ?? ?? ?? 45 33 C0 48 89 03 BA", ScanType = ScanType.StaticAddress )] + public IntPtr* DrawObjectDemiVTable; + + public Hook< GeneralResolveDelegate >? ResolveDemiDecalPathHook; + public Hook< EidResolveDelegate >? ResolveDemiEidPathHook; + public Hook< GeneralResolveDelegate >? ResolveDemiImcPathHook; + public Hook< MPapResolveDelegate >? ResolveDemiMPapPathHook; + public Hook< GeneralResolveDelegate >? ResolveDemiMdlPathHook; + public Hook< MaterialResolveDetour >? ResolveDemiMtrlPathHook; + public Hook< MaterialResolveDetour >? ResolveDemiPapPathHook; + public Hook< GeneralResolveDelegate >? ResolveDemiPhybPathHook; + public Hook< GeneralResolveDelegate >? ResolveDemiSklbPathHook; + public Hook< GeneralResolveDelegate >? ResolveDemiSkpPathHook; + public Hook< EidResolveDelegate >? ResolveDemiTmbPathHook; + public Hook< MaterialResolveDetour >? ResolveDemiVfxPathHook; + + private void SetupDemiHooks() + { + ResolveDemiDecalPathHook = new Hook< GeneralResolveDelegate >( DrawObjectDemiVTable[ ResolveDecalIdx ], ResolveDemiDecalDetour ); + ResolveDemiEidPathHook = new Hook< EidResolveDelegate >( DrawObjectDemiVTable[ ResolveEidIdx ], ResolveDemiEidDetour ); + ResolveDemiImcPathHook = new Hook< GeneralResolveDelegate >( DrawObjectDemiVTable[ ResolveImcIdx ], ResolveDemiImcDetour ); + ResolveDemiMPapPathHook = new Hook< MPapResolveDelegate >( DrawObjectDemiVTable[ ResolveMPapIdx ], ResolveDemiMPapDetour ); + ResolveDemiMdlPathHook = new Hook< GeneralResolveDelegate >( DrawObjectDemiVTable[ ResolveMdlIdx ], ResolveDemiMdlDetour ); + ResolveDemiMtrlPathHook = new Hook< MaterialResolveDetour >( DrawObjectDemiVTable[ ResolveMtrlIdx ], ResolveDemiMtrlDetour ); + ResolveDemiPapPathHook = new Hook< MaterialResolveDetour >( DrawObjectDemiVTable[ ResolvePapIdx ], ResolveDemiPapDetour ); + ResolveDemiPhybPathHook = new Hook< GeneralResolveDelegate >( DrawObjectDemiVTable[ ResolvePhybIdx ], ResolveDemiPhybDetour ); + ResolveDemiSklbPathHook = new Hook< GeneralResolveDelegate >( DrawObjectDemiVTable[ ResolveSklbIdx ], ResolveDemiSklbDetour ); + ResolveDemiSkpPathHook = new Hook< GeneralResolveDelegate >( DrawObjectDemiVTable[ ResolveSkpIdx ], ResolveDemiSkpDetour ); + ResolveDemiTmbPathHook = new Hook< EidResolveDelegate >( DrawObjectDemiVTable[ ResolveTmbIdx ], ResolveDemiTmbDetour ); + ResolveDemiVfxPathHook = new Hook< MaterialResolveDetour >( DrawObjectDemiVTable[ ResolveVfxIdx ], ResolveDemiVfxDetour ); + } + + private void EnableDemiHooks() + { + ResolveDemiDecalPathHook?.Enable(); + ResolveDemiEidPathHook?.Enable(); + ResolveDemiImcPathHook?.Enable(); + ResolveDemiMPapPathHook?.Enable(); + ResolveDemiMdlPathHook?.Enable(); + ResolveDemiMtrlPathHook?.Enable(); + ResolveDemiPapPathHook?.Enable(); + ResolveDemiPhybPathHook?.Enable(); + ResolveDemiSklbPathHook?.Enable(); + ResolveDemiSkpPathHook?.Enable(); + ResolveDemiTmbPathHook?.Enable(); + ResolveDemiVfxPathHook?.Enable(); + } + + private void DisableDemiHooks() + { + ResolveDemiDecalPathHook?.Disable(); + ResolveDemiEidPathHook?.Disable(); + ResolveDemiImcPathHook?.Disable(); + ResolveDemiMPapPathHook?.Disable(); + ResolveDemiMdlPathHook?.Disable(); + ResolveDemiMtrlPathHook?.Disable(); + ResolveDemiPapPathHook?.Disable(); + ResolveDemiPhybPathHook?.Disable(); + ResolveDemiSklbPathHook?.Disable(); + ResolveDemiSkpPathHook?.Disable(); + ResolveDemiTmbPathHook?.Disable(); + ResolveDemiVfxPathHook?.Disable(); + } + + private void DisposeDemiHooks() + { + ResolveDemiDecalPathHook?.Dispose(); + ResolveDemiEidPathHook?.Dispose(); + ResolveDemiImcPathHook?.Dispose(); + ResolveDemiMPapPathHook?.Dispose(); + ResolveDemiMdlPathHook?.Dispose(); + ResolveDemiMtrlPathHook?.Dispose(); + ResolveDemiPapPathHook?.Dispose(); + ResolveDemiPhybPathHook?.Dispose(); + ResolveDemiSklbPathHook?.Dispose(); + ResolveDemiSkpPathHook?.Dispose(); + ResolveDemiTmbPathHook?.Dispose(); + ResolveDemiVfxPathHook?.Dispose(); + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Human.cs b/Penumbra/Interop/Resolver/PathResolver.Human.cs index 1ce81342..b4bb8fcd 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Human.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Human.cs @@ -14,12 +14,6 @@ public unsafe partial class PathResolver // [Signature( "48 8D 1D ?? ?? ?? ?? 48 C7 41", ScanType = ScanType.StaticAddress )] // public IntPtr* DrawObjectVTable; // - // [Signature( "48 8D 05 ?? ?? ?? ?? 45 33 C0 48 89 03 BA", ScanType = ScanType.StaticAddress )] - // public IntPtr* DrawObjectDemihumanVTable; - // - // [Signature( "48 8D 05 ?? ?? ?? ?? 48 89 03 33 C0 48 89 83 ?? ?? ?? ?? 48 89 83 ?? ?? ?? ?? C7 83", ScanType = ScanType.StaticAddress )] - // public IntPtr* DrawObjectMonsterVTable; - // // public const int ResolveRootIdx = 71; public const int ResolveSklbIdx = 72; diff --git a/Penumbra/Interop/Resolver/PathResolver.Resolve.cs b/Penumbra/Interop/Resolver/PathResolver.Resolve.cs index a6124a93..3892fcbd 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Resolve.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Resolve.cs @@ -136,6 +136,43 @@ public unsafe partial class PathResolver private IntPtr ResolveMonsterVfxDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) => ResolvePathDetour( drawObject, ResolveMonsterVfxPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); + // Demihumans + private IntPtr ResolveDemiDecalDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePathDetour( drawObject, ResolveDemiDecalPathHook!.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveDemiEidDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) + => ResolvePathDetour( drawObject, ResolveDemiEidPathHook!.Original( drawObject, path, unk3 ) ); + + private IntPtr ResolveDemiImcDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePathDetour( drawObject, ResolveDemiImcPathHook!.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveDemiMPapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5 ) + => ResolvePathDetour( drawObject, ResolveDemiMPapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); + + private IntPtr ResolveDemiMdlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType ) + => ResolvePathDetour( drawObject, ResolveDemiMdlPathHook!.Original( drawObject, path, unk3, modelType ) ); + + private IntPtr ResolveDemiMtrlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + => ResolvePathDetour( drawObject, ResolveDemiMtrlPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); + + private IntPtr ResolveDemiPapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + => ResolvePathDetour( drawObject, ResolveDemiPapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); + + private IntPtr ResolveDemiPhybDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePathDetour( drawObject, ResolveDemiPhybPathHook!.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveDemiSklbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePathDetour( drawObject, ResolveDemiSklbPathHook!.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveDemiSkpDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePathDetour( drawObject, ResolveDemiSkpPathHook!.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveDemiTmbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) + => ResolvePathDetour( drawObject, ResolveDemiTmbPathHook!.Original( drawObject, path, unk3 ) ); + + private IntPtr ResolveDemiVfxDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + => ResolvePathDetour( drawObject, ResolveDemiVfxPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); + // Implementation [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] diff --git a/Penumbra/Interop/Resolver/PathResolver.cs b/Penumbra/Interop/Resolver/PathResolver.cs index c634755a..d12e6d00 100644 --- a/Penumbra/Interop/Resolver/PathResolver.cs +++ b/Penumbra/Interop/Resolver/PathResolver.cs @@ -27,6 +27,7 @@ public partial class PathResolver : IDisposable SetupHumanHooks(); SetupWeaponHooks(); SetupMonsterHooks(); + SetupDemiHooks(); SetupMetaHooks(); } @@ -105,6 +106,7 @@ public partial class PathResolver : IDisposable EnableHumanHooks(); EnableWeaponHooks(); EnableMonsterHooks(); + EnableDemiHooks(); EnableMtrlHooks(); EnableDataHooks(); EnableMetaHooks(); @@ -124,6 +126,7 @@ public partial class PathResolver : IDisposable DisableHumanHooks(); DisableWeaponHooks(); DisableMonsterHooks(); + DisableDemiHooks(); DisableMtrlHooks(); DisableDataHooks(); DisableMetaHooks(); @@ -141,6 +144,7 @@ public partial class PathResolver : IDisposable DisposeHumanHooks(); DisposeWeaponHooks(); DisposeMonsterHooks(); + DisposeDemiHooks(); DisposeMtrlHooks(); DisposeDataHooks(); DisposeMetaHooks(); diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 1ae066db..e4beecf0 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -152,6 +152,10 @@ public class Penumbra : IDisposable { PluginLog.Error( $"{ImcExceptions} IMC Exceptions thrown. Please repair your game files." ); } + else + { + PluginLog.Information( $"Penumbra Version {Version}, Commit #{CommitHash} successfully Loaded." ); + } } private void SetupInterface( out ConfigWindow cfg, out LaunchButton btn, out WindowSystem system ) diff --git a/Penumbra/UI/Classes/ModEditWindow.Textures.cs b/Penumbra/UI/Classes/ModEditWindow.Textures.cs index 6f0ec77f..e18ec33c 100644 --- a/Penumbra/UI/Classes/ModEditWindow.Textures.cs +++ b/Penumbra/UI/Classes/ModEditWindow.Textures.cs @@ -41,7 +41,7 @@ public partial class ModEditWindow private int _offsetX = 0; private int _offsetY = 0; - private readonly FileDialogManager _dialogManager = new(); + private readonly FileDialogManager _dialogManager = ConfigWindow.SetupFileManager(); private static bool DragFloat( string label, float width, ref float value ) { diff --git a/Penumbra/UI/Classes/ModFileSystemSelector.cs b/Penumbra/UI/Classes/ModFileSystemSelector.cs index 14dfd804..51490733 100644 --- a/Penumbra/UI/Classes/ModFileSystemSelector.cs +++ b/Penumbra/UI/Classes/ModFileSystemSelector.cs @@ -19,7 +19,7 @@ namespace Penumbra.UI.Classes; public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, ModFileSystemSelector.ModState > { - private readonly FileDialogManager _fileManager = new(); + private readonly FileDialogManager _fileManager = ConfigWindow.SetupFileManager(); private TexToolsImporter? _import; public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty; public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty; diff --git a/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs b/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs index 596a48f0..6eb6c5ca 100644 --- a/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs +++ b/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs @@ -87,7 +87,7 @@ public partial class ConfigWindow ImGui.TableSetupColumn( "mods", flags, varWidth - 100 * ImGuiHelpers.GlobalScale ); ImGui.TableSetupColumn( "id", flags, 100 * ImGuiHelpers.GlobalScale ); - var items = Penumbra.CollectionManager.Default.ChangedItems; + var items = Penumbra.CollectionManager.Current.ChangedItems; var rest = _changedItemFilter.IsEmpty && _changedItemModFilter.IsEmpty ? ImGuiClip.ClippedDraw( items, skips, DrawChangedItemColumn, items.Count ) : ImGuiClip.FilteredClippedDraw( items, skips, FilterChangedItem, DrawChangedItemColumn ); diff --git a/Penumbra/UI/ConfigWindow.EffectiveTab.cs b/Penumbra/UI/ConfigWindow.EffectiveTab.cs index 4d96423c..38c37485 100644 --- a/Penumbra/UI/ConfigWindow.EffectiveTab.cs +++ b/Penumbra/UI/ConfigWindow.EffectiveTab.cs @@ -50,7 +50,7 @@ public partial class ConfigWindow ImGui.TableSetupColumn( string.Empty, ImGuiTableColumnFlags.WidthFixed, _effectiveArrowLength ); ImGui.TableSetupColumn( "##file", ImGuiTableColumnFlags.WidthFixed, _effectiveRightTextLength ); - DrawEffectiveRows( Penumbra.CollectionManager.Default, skips, height, + DrawEffectiveRows( Penumbra.CollectionManager.Current, skips, height, _effectiveFilePathFilter.Length > 0 || _effectiveGamePathFilter.Length > 0 ); } diff --git a/Penumbra/UI/ConfigWindow.Misc.cs b/Penumbra/UI/ConfigWindow.Misc.cs index 17cc424d..6f70126a 100644 --- a/Penumbra/UI/ConfigWindow.Misc.cs +++ b/Penumbra/UI/ConfigWindow.Misc.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.ImGuiFileDialog; using ImGuiNET; using Lumina.Data.Parsing; using Lumina.Excel.GeneratedSheets; @@ -11,6 +13,7 @@ using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.Interop.Structs; using Penumbra.UI.Classes; +using Penumbra.Util; namespace Penumbra.UI; @@ -101,19 +104,45 @@ public partial class ConfigWindow _ => throw new ArgumentOutOfRangeException( nameof( type ), type, null ), }; - using var combo = ImRaii.Combo( label, current.Name ); - if( !combo ) + using var combo = ImRaii.Combo( label, current.Name ); + if( combo ) { - return; - } - - foreach( var collection in Penumbra.CollectionManager.GetEnumeratorWithEmpty().Skip( withEmpty ? 0 : 1 ).OrderBy( c => c.Name ) ) - { - using var id = ImRaii.PushId( collection.Index ); - if( ImGui.Selectable( collection.Name, collection == current ) ) + foreach( var collection in Penumbra.CollectionManager.GetEnumeratorWithEmpty().Skip( withEmpty ? 0 : 1 ).OrderBy( c => c.Name ) ) { - Penumbra.CollectionManager.SetCollection( collection, type, characterName ); + using var id = ImRaii.PushId( collection.Index ); + if( ImGui.Selectable( collection.Name, collection == current ) ) + { + Penumbra.CollectionManager.SetCollection( collection, type, characterName ); + } } } } + + // Set up the file selector with the right flags and custom side bar items. + public static FileDialogManager SetupFileManager() + { + var fileManager = new FileDialogManager + { + AddedWindowFlags = ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDocking, + }; + + if( Functions.GetDownloadsFolder( out var downloadsFolder ) ) + { + fileManager.CustomSideBarItems.Add( ("Downloads", downloadsFolder, FontAwesomeIcon.Download, -1) ); + } + + if( Functions.GetQuickAccessFolders( out var folders ) ) + { + foreach( var ((name, path), idx) in folders.WithIndex() ) + { + fileManager.CustomSideBarItems.Add( ($"{name}##{idx}", path, FontAwesomeIcon.Folder, -1) ); + } + } + + // Remove Videos and Music. + fileManager.CustomSideBarItems.Add( ("Videos", string.Empty, 0, -1) ); + fileManager.CustomSideBarItems.Add( ("Music", string.Empty, 0, -1) ); + + return fileManager; + } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.ModPanel.Header.cs b/Penumbra/UI/ConfigWindow.ModPanel.Header.cs index 6830884e..4893c6da 100644 --- a/Penumbra/UI/ConfigWindow.ModPanel.Header.cs +++ b/Penumbra/UI/ConfigWindow.ModPanel.Header.cs @@ -12,17 +12,12 @@ namespace Penumbra.UI; public partial class ConfigWindow { - private partial class ModPanel : IDisposable + private partial class ModPanel { // We use a big, nice game font for the title. private readonly GameFontHandle _nameFont = Dalamud.PluginInterface.UiBuilder.GetGameFontHandle( new GameFontStyle( GameFontFamilyAndSize.Jupiter23 ) ); - public void Dispose() - { - _nameFont.Dispose(); - } - // Header data. private string _modName = string.Empty; private string _modAuthor = string.Empty; diff --git a/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs b/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs index d0ad872d..d1c4a838 100644 --- a/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs +++ b/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs @@ -56,7 +56,7 @@ public partial class ConfigWindow DrawChangedItemsTab(); DrawConflictsTab(); DrawEditModTab(); - if( ImGui.TabItemButton( "Advanced Editing", ImGuiTabItemFlags.Trailing | ImGuiTabItemFlags.NoTooltip ) ) + if( Penumbra.Config.ShowAdvanced && ImGui.TabItemButton( "Advanced Editing", ImGuiTabItemFlags.Trailing | ImGuiTabItemFlags.NoTooltip ) ) { _window.ModEditPopup.ChangeMod( _mod ); _window.ModEditPopup.ChangeOption( -1, 0 ); diff --git a/Penumbra/UI/ConfigWindow.ModsTab.cs b/Penumbra/UI/ConfigWindow.ModsTab.cs index f09f1423..d85ff38a 100644 --- a/Penumbra/UI/ConfigWindow.ModsTab.cs +++ b/Penumbra/UI/ConfigWindow.ModsTab.cs @@ -63,6 +63,10 @@ public partial class ConfigWindow DrawInheritedCollectionButton( 3 * buttonSize ); ImGui.SameLine(); DrawCollectionSelector( "##collectionSelector", 2 * buttonSize.X, ModCollection.Type.Current, false, null ); + if( !Penumbra.CollectionManager.CurrentCollectionInUse ) + { + ImGuiUtil.DrawTextButton( "The currently selected collection is not used in any way.", -Vector2.UnitX, Colors.PressEnterWarningBg ); + } } private static void DrawDefaultCollectionButton( Vector2 width ) @@ -112,7 +116,7 @@ public partial class ConfigWindow // The basic setup for the mod panel. // Details are in other files. - private partial class ModPanel + private partial class ModPanel : IDisposable { private readonly ConfigWindow _window; @@ -123,6 +127,11 @@ public partial class ConfigWindow public ModPanel( ConfigWindow window ) => _window = window; + public void Dispose() + { + _nameFont.Dispose(); + } + public void Draw( ModFileSystemSelector selector ) { Init( selector ); diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.General.cs b/Penumbra/UI/ConfigWindow.SettingsTab.General.cs index e69fa219..b545b1eb 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.General.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.General.cs @@ -75,6 +75,10 @@ public partial class ConfigWindow "If you have a character collection set to a specific name for a companion or combat pet, prefer this collection over the owner's collection.\n" + "That is, if you have a 'Topaz Carbuncle' collection, it will use this one instead of the one for its owner.", Penumbra.Config.PreferNamedCollectionsOverOwners, v => Penumbra.Config.PreferNamedCollectionsOverOwners = v ); + Checkbox( "Use Default Collection for Housing Retainers", + "Housing Retainers use the name of their owner instead of their own, you can decide to let them use their owners character collection or the default collection.\n" + + "It is not possible to make them have their own collection, since they have no connection to their actual name.", + Penumbra.Config.UseDefaultCollectionForRetainers, v => Penumbra.Config.UseDefaultCollectionForRetainers = v ); ImGui.Dummy( _window._defaultSpace ); DrawFolderSortType(); DrawAbsoluteSizeSelector(); diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.cs b/Penumbra/UI/ConfigWindow.SettingsTab.cs index de377896..32859822 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.cs @@ -62,7 +62,7 @@ public partial class ConfigWindow // Changing the base mod directory. private string? _newModDirectory; - private readonly FileDialogManager _dialogManager = new(); + private readonly FileDialogManager _dialogManager = SetupFileManager(); private bool _dialogOpen; // For toggling on/off. // Do not change the directory without explicitly pressing enter or this button. diff --git a/Penumbra/UI/ConfigWindow.cs b/Penumbra/UI/ConfigWindow.cs index ff7fa751..ccfac393 100644 --- a/Penumbra/UI/ConfigWindow.cs +++ b/Penumbra/UI/ConfigWindow.cs @@ -33,7 +33,6 @@ public sealed partial class ConfigWindow : Window, IDisposable _effectiveTab = new EffectiveTab(); _debugTab = new DebugTab( this ); _resourceTab = new ResourceTab( this ); - Flags |= ImGuiWindowFlags.NoDocking; if( Penumbra.Config.FixMainWindow ) { Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove;