diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index 2472b3e2..a9288829 100644 --- a/Penumbra/Api/IPenumbraApi.cs +++ b/Penumbra/Api/IPenumbraApi.cs @@ -49,6 +49,10 @@ public interface IPenumbraApi : IPenumbraApiBase // Obtain the currently set mod directory from the configuration. public string GetModDirectory(); + // Fired whenever a mod directory change is finished. + // Gives the full path of the mod directory and whether Penumbra treats it as valid. + public event Action< string, bool >? ModDirectoryChanged; + // Obtain the entire current penumbra configuration as a json encoded string. public string GetConfiguration(); diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index 81ef08a8..3b3390ee 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -28,6 +28,7 @@ public class IpcTester : IDisposable private readonly ICallGateSubscriber< object? > _disposed; private readonly ICallGateSubscriber< string, object? > _preSettingsDraw; private readonly ICallGateSubscriber< string, object? > _postSettingsDraw; + private readonly ICallGateSubscriber< string, bool, object? > _modDirectoryChanged; private readonly ICallGateSubscriber< IntPtr, int, object? > _redrawn; private readonly ICallGateSubscriber< ModSettingChange, string, string, bool, object? > _settingChanged; private readonly ICallGateSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? > _characterBaseCreated; @@ -45,6 +46,7 @@ public class IpcTester : IDisposable _preSettingsDraw = _pi.GetIpcSubscriber< string, object? >( PenumbraIpc.LabelProviderPreSettingsDraw ); _postSettingsDraw = _pi.GetIpcSubscriber< string, object? >( PenumbraIpc.LabelProviderPostSettingsDraw ); _settingChanged = _pi.GetIpcSubscriber< ModSettingChange, string, string, bool, object? >( PenumbraIpc.LabelProviderModSettingChanged ); + _modDirectoryChanged = _pi.GetIpcSubscriber< string, bool, object? >( PenumbraIpc.LabelProviderModDirectoryChanged ); _characterBaseCreated = _pi.GetIpcSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >( PenumbraIpc.LabelProviderCreatingCharacterBase ); _initialized.Subscribe( AddInitialized ); @@ -54,6 +56,7 @@ public class IpcTester : IDisposable _postSettingsDraw.Subscribe( UpdateLastDrawnMod ); _settingChanged.Subscribe( UpdateLastModSetting ); _characterBaseCreated.Subscribe( UpdateLastCreated ); + _modDirectoryChanged.Subscribe( UpdateModDirectoryChanged ); } public void Dispose() @@ -67,6 +70,7 @@ public class IpcTester : IDisposable _postSettingsDraw.Unsubscribe( UpdateLastDrawnMod ); _settingChanged.Unsubscribe( UpdateLastModSetting ); _characterBaseCreated.Unsubscribe( UpdateLastCreated ); + _modDirectoryChanged.Unsubscribe( UpdateModDirectoryChanged ); } private void AddInitialized() @@ -131,11 +135,18 @@ public class IpcTester : IDisposable private string _currentConfiguration = string.Empty; private string _lastDrawnMod = string.Empty; - private DateTimeOffset _lastDrawnModTime; + private DateTimeOffset _lastDrawnModTime = DateTimeOffset.MinValue; private void UpdateLastDrawnMod( string name ) => ( _lastDrawnMod, _lastDrawnModTime ) = ( name, DateTimeOffset.Now ); + private string _lastModDirectory = string.Empty; + private bool _lastModDirectoryValid = false; + private DateTimeOffset _lastModDirectoryTime = DateTimeOffset.MinValue; + + private void UpdateModDirectoryChanged( string path, bool valid ) + => ( _lastModDirectory, _lastModDirectoryValid, _lastModDirectoryTime ) = ( path, valid, DateTimeOffset.Now ); + private void DrawGeneral() { using var _ = ImRaii.TreeNode( "General IPC" ); @@ -173,6 +184,10 @@ public class IpcTester : IDisposable ImGui.TextUnformatted( $"{breaking}.{features:D4}" ); DrawIntro( PenumbraIpc.LabelProviderGetModDirectory, "Current Mod Directory" ); ImGui.TextUnformatted( _pi.GetIpcSubscriber< string >( PenumbraIpc.LabelProviderGetModDirectory ).InvokeFunc() ); + DrawIntro( PenumbraIpc.LabelProviderModDirectoryChanged, "Last Mod Directory Change" ); + ImGui.TextUnformatted( _lastModDirectoryTime > DateTimeOffset.MinValue + ? $"{_lastModDirectory} ({( _lastModDirectoryValid ? "Valid" : "Invalid" )}) at {_lastModDirectoryTime}" + : "None" ); DrawIntro( PenumbraIpc.LabelProviderGetConfiguration, "Configuration" ); if( ImGui.Button( "Get" ) ) { diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 55bf0472..9e938322 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -84,6 +84,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi return Penumbra.Config.ModDirectory; } + public event Action< string, bool >? ModDirectoryChanged + { + add => Penumbra.ModManager.ModDirectoryChanged += value; + remove => Penumbra.ModManager.ModDirectoryChanged -= value; + } + public string GetConfiguration() { CheckInitialized(); diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index 63b71322..98b3b7f9 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -26,6 +26,7 @@ public partial class PenumbraIpc : IDisposable InitializeSettingProviders( pi ); InitializeTempProviders( pi ); ProviderInitialized?.SendMessage(); + InvokeModDirectoryChanged( Penumbra.ModManager.BasePath.FullName, Penumbra.ModManager.Valid ); } public void Dispose() @@ -44,20 +45,22 @@ public partial class PenumbraIpc : IDisposable public partial class PenumbraIpc { - public const string LabelProviderInitialized = "Penumbra.Initialized"; - public const string LabelProviderDisposed = "Penumbra.Disposed"; - public const string LabelProviderApiVersion = "Penumbra.ApiVersion"; - public const string LabelProviderApiVersions = "Penumbra.ApiVersions"; - public const string LabelProviderGetModDirectory = "Penumbra.GetModDirectory"; - public const string LabelProviderGetConfiguration = "Penumbra.GetConfiguration"; - public const string LabelProviderPreSettingsDraw = "Penumbra.PreSettingsDraw"; - public const string LabelProviderPostSettingsDraw = "Penumbra.PostSettingsDraw"; + public const string LabelProviderInitialized = "Penumbra.Initialized"; + public const string LabelProviderDisposed = "Penumbra.Disposed"; + public const string LabelProviderApiVersion = "Penumbra.ApiVersion"; + public const string LabelProviderApiVersions = "Penumbra.ApiVersions"; + public const string LabelProviderGetModDirectory = "Penumbra.GetModDirectory"; + public const string LabelProviderModDirectoryChanged = "Penumbra.ModDirectoryChanged"; + public const string LabelProviderGetConfiguration = "Penumbra.GetConfiguration"; + public const string LabelProviderPreSettingsDraw = "Penumbra.PreSettingsDraw"; + public const string LabelProviderPostSettingsDraw = "Penumbra.PostSettingsDraw"; internal ICallGateProvider< object? >? ProviderInitialized; internal ICallGateProvider< object? >? ProviderDisposed; internal ICallGateProvider< int >? ProviderApiVersion; internal ICallGateProvider< (int Breaking, int Features) >? ProviderApiVersions; internal ICallGateProvider< string >? ProviderGetModDirectory; + internal ICallGateProvider< string, bool, object? >? ProviderModDirectoryChanged; internal ICallGateProvider< string >? ProviderGetConfiguration; internal ICallGateProvider< string, object? >? ProviderPreSettingsDraw; internal ICallGateProvider< string, object? >? ProviderPostSettingsDraw; @@ -116,6 +119,16 @@ public partial class PenumbraIpc PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetModDirectory}:\n{e}" ); } + try + { + ProviderModDirectoryChanged = pi.GetIpcProvider< string, bool, object? >( LabelProviderModDirectoryChanged ); + Api.ModDirectoryChanged += InvokeModDirectoryChanged; + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderModDirectoryChanged}:\n{e}" ); + } + try { ProviderGetConfiguration = pi.GetIpcProvider< string >( LabelProviderGetConfiguration ); @@ -155,7 +168,17 @@ public partial class PenumbraIpc ProviderApiVersions?.UnregisterFunc(); Api.PreSettingsPanelDraw -= InvokeSettingsPreDraw; Api.PostSettingsPanelDraw -= InvokeSettingsPostDraw; + Api.ModDirectoryChanged -= InvokeModDirectoryChanged; } + + private void InvokeSettingsPreDraw( string modDirectory ) + => ProviderPreSettingsDraw!.SendMessage( modDirectory ); + + private void InvokeSettingsPostDraw( string modDirectory ) + => ProviderPostSettingsDraw!.SendMessage( modDirectory ); + + private void InvokeModDirectoryChanged( string modDirectory, bool valid ) + => ProviderModDirectoryChanged?.SendMessage( modDirectory, valid ); } public partial class PenumbraIpc @@ -239,12 +262,6 @@ public partial class PenumbraIpc private void OnGameObjectRedrawn( IntPtr objectAddress, int objectTableIndex ) => ProviderGameObjectRedrawn?.SendMessage( objectAddress, objectTableIndex ); - private void InvokeSettingsPreDraw( string modDirectory ) - => ProviderPreSettingsDraw!.SendMessage( modDirectory ); - - private void InvokeSettingsPostDraw( string modDirectory ) - => ProviderPostSettingsDraw!.SendMessage( modDirectory ); - private void DisposeRedrawProviders() { ProviderRedrawName?.UnregisterAction(); @@ -319,7 +336,7 @@ public partial class PenumbraIpc try { - ProviderGetCutsceneParentIndex = pi.GetIpcProvider( LabelProviderGetCutsceneParentIndex ); + ProviderGetCutsceneParentIndex = pi.GetIpcProvider< int, int >( LabelProviderGetCutsceneParentIndex ); ProviderGetCutsceneParentIndex.RegisterFunc( Api.GetCutsceneParentIndex ); } catch( Exception e ) diff --git a/Penumbra/Interop/Resolver/CutsceneCharacters.cs b/Penumbra/Interop/Resolver/CutsceneCharacters.cs index a701c293..e8c1f05e 100644 --- a/Penumbra/Interop/Resolver/CutsceneCharacters.cs +++ b/Penumbra/Interop/Resolver/CutsceneCharacters.cs @@ -14,7 +14,7 @@ public class CutsceneCharacters : IDisposable public const int CutsceneSlots = 40; public const int CutsceneEndIdx = CutsceneStartIdx + CutsceneSlots; - private readonly short[] _copiedCharacters = Enumerable.Repeat( ( short )-1, ObjectReloader.CutsceneSlots ).ToArray(); + private readonly short[] _copiedCharacters = Enumerable.Repeat( ( short )-1, CutsceneSlots ).ToArray(); public IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > Actors => Enumerable.Range( CutsceneStartIdx, CutsceneSlots ) diff --git a/Penumbra/Interop/Resolver/PathResolver.Identification.cs b/Penumbra/Interop/Resolver/PathResolver.Identification.cs index d9c21999..222276c7 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Identification.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Identification.cs @@ -190,7 +190,7 @@ public unsafe partial class PathResolver 242 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // try-on 243 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // dye preview - >= ObjectReloader.CutsceneStartIdx and < ObjectReloader.CutsceneEndIdx => GetCutsceneName( gameObject ), + >= CutsceneCharacters.CutsceneStartIdx and < CutsceneCharacters.CutsceneEndIdx => GetCutsceneName( gameObject ), _ => null, } diff --git a/Penumbra/Mods/Manager/Mod.Manager.Root.cs b/Penumbra/Mods/Manager/Mod.Manager.Root.cs index b331daca..92a8c51e 100644 --- a/Penumbra/Mods/Manager/Mod.Manager.Root.cs +++ b/Penumbra/Mods/Manager/Mod.Manager.Root.cs @@ -13,6 +13,7 @@ public sealed partial class Mod public event Action? ModDiscoveryStarted; public event Action? ModDiscoveryFinished; + public event Action< string, bool > ModDirectoryChanged; // Change the mod base directory and discover available mods. public void DiscoverMods( string newDir ) @@ -35,6 +36,10 @@ public sealed partial class Mod { Valid = false; BasePath = new DirectoryInfo( "." ); + if( Penumbra.Config.ModDirectory != BasePath.FullName ) + { + ModDirectoryChanged.Invoke( string.Empty, false ); + } } else { @@ -56,13 +61,19 @@ public sealed partial class Mod Valid = Directory.Exists( newDir.FullName ); if( Penumbra.Config.ModDirectory != BasePath.FullName ) { - PluginLog.Information( "Set new mod base directory from {OldDirectory:l} to {NewDirectory:l}.", Penumbra.Config.ModDirectory, BasePath.FullName ); - Penumbra.Config.ModDirectory = BasePath.FullName; - Penumbra.Config.Save(); + ModDirectoryChanged.Invoke( BasePath.FullName, Valid ); } } } + private static void OnModDirectoryChange( string newPath, bool _ ) + { + PluginLog.Information( "Set new mod base directory from {OldDirectory:l} to {NewDirectory:l}.", + Penumbra.Config.ModDirectory, newPath ); + Penumbra.Config.ModDirectory = newPath; + Penumbra.Config.Save(); + } + // Discover new mods. public void DiscoverMods() { diff --git a/Penumbra/Mods/Manager/Mod.Manager.cs b/Penumbra/Mods/Manager/Mod.Manager.cs index 1306be5f..ff800473 100644 --- a/Penumbra/Mods/Manager/Mod.Manager.cs +++ b/Penumbra/Mods/Manager/Mod.Manager.cs @@ -34,6 +34,7 @@ public sealed partial class Mod public Manager( string modDirectory ) { + ModDirectoryChanged += OnModDirectoryChange; SetBaseDirectory( modDirectory, true ); ModOptionChanged += OnModOptionChange; ModPathChanged += OnModPathChange;