From 653e21e2379e389c7557944454479c74b695f1c3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Jul 2022 16:21:41 +0200 Subject: [PATCH] Add MaterialUI IPC and increase API Version to 6. No breaking changes, only additions. --- Penumbra/Api/IPenumbraApi.cs | 20 ++++- Penumbra/Api/IpcTester.cs | 67 +++++++++++++-- Penumbra/Api/PenumbraApi.cs | 86 ++++++++++++++++++- Penumbra/Api/PenumbraIpc.cs | 86 +++++++++++++++++-- Penumbra/UI/ConfigWindow.DebugTab.cs | 5 -- Penumbra/UI/ConfigWindow.ModPanel.Settings.cs | 3 + 6 files changed, 242 insertions(+), 25 deletions(-) diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index 1d81b973..44eb92ee 100644 --- a/Penumbra/Api/IPenumbraApi.cs +++ b/Penumbra/Api/IPenumbraApi.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Dalamud.Game.ClientState.Objects.Types; using Lumina.Data; +using Penumbra.Collections; using Penumbra.GameData.Enums; using Penumbra.Mods; @@ -16,7 +17,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 delegate void ModSettingChanged( ModSettingChange type, string collectionName, string modDirectory, bool inherited ); public enum PenumbraApiEc { Success = 0, @@ -47,6 +48,11 @@ public interface IPenumbraApi : IPenumbraApiBase // Can be used to append tooltips. public event ChangedItemHover? ChangedItemTooltip; + // Events that are fired before and after the content of a mod settings panel are drawn. + // Both are fired inside the child window of the settings panel itself. + public event Action? PreSettingsPanelDraw; + public event Action? PostSettingsPanelDraw; + // Triggered when the user clicks a listed changed object in a mod tab. public event ChangedItemClick? ChangedItemClicked; public event GameObjectRedrawn? GameObjectRedrawn; @@ -101,6 +107,16 @@ public interface IPenumbraApi : IPenumbraApiBase // Obtain a list of all installed mods. The first string is their directory name, the second string is their mod name. public IList< (string, string) > GetModList(); + // Try to reload an existing mod by its directory name or mod name. + // Can return ModMissing or success. + // Reload is the same as if triggered by button press and might delete the mod if it is not valid anymore. + public PenumbraApiEc ReloadMod( string modDirectory, string modName ); + + // Try to add a new mod inside the mod root directory (modDirectory should only be the name, not the full name). + // Returns FileMissing if the directory does not exist or success otherwise. + // Note that success does only imply a successful call, not a successful mod load. + public PenumbraApiEc AddMod( string modDirectory ); + // Obtain a base64 encoded, zipped json-string with a prepended version-byte of the current manipulations // for the given collection associated with the character name, or the default collection. public string GetMetaManipulations( string characterName ); @@ -139,6 +155,8 @@ public interface IPenumbraApi : IPenumbraApiBase public PenumbraApiEc TrySetModSettings( string collectionName, string modDirectory, string modName, string optionGroupName, IReadOnlyList< string > options ); + // This event gets fired when any setting in any collection changes. + public event ModSettingChanged? ModSettingChanged; // Create a temporary collection without actual settings but with a cache. // If no character collection for this character exists or forceOverwriteCharacter is true, diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index da087527..fc50f1aa 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -12,6 +13,7 @@ using Dalamud.Plugin.Ipc; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.Mods; @@ -23,23 +25,32 @@ public class IpcTester : IDisposable private readonly PenumbraIpc _ipc; private readonly DalamudPluginInterface _pi; - private readonly ICallGateSubscriber< object? > _initialized; - private readonly ICallGateSubscriber< object? > _disposed; - private readonly ICallGateSubscriber< IntPtr, int, object? > _redrawn; + private readonly ICallGateSubscriber< object? > _initialized; + private readonly ICallGateSubscriber< object? > _disposed; + private readonly ICallGateSubscriber< string, object? > _preSettingsDraw; + private readonly ICallGateSubscriber< string, object? > _postSettingsDraw; + private readonly ICallGateSubscriber< IntPtr, int, object? > _redrawn; + private readonly ICallGateSubscriber< ModSettingChange, string, string, bool, object? > _settingChanged; private readonly List< DateTimeOffset > _initializedList = new(); private readonly List< DateTimeOffset > _disposedList = new(); public IpcTester( DalamudPluginInterface pi, PenumbraIpc ipc ) { - _ipc = ipc; - _pi = pi; + _ipc = ipc; + _pi = pi; _initialized = _pi.GetIpcSubscriber< object? >( PenumbraIpc.LabelProviderInitialized ); - _disposed = _pi.GetIpcSubscriber< object? >( PenumbraIpc.LabelProviderDisposed ); - _redrawn = _pi.GetIpcSubscriber< IntPtr, int, object? >( PenumbraIpc.LabelProviderGameObjectRedrawn ); + _disposed = _pi.GetIpcSubscriber< object? >( PenumbraIpc.LabelProviderDisposed ); + _redrawn = _pi.GetIpcSubscriber< IntPtr, int, object? >( PenumbraIpc.LabelProviderGameObjectRedrawn ); + _preSettingsDraw = _pi.GetIpcSubscriber< string, object? >( PenumbraIpc.LabelProviderPreSettingsDraw ); + _postSettingsDraw = _pi.GetIpcSubscriber< string, object? >( PenumbraIpc.LabelProviderPostSettingsDraw ); + _settingChanged = _pi.GetIpcSubscriber< ModSettingChange, string, string, bool, object? >( PenumbraIpc.LabelProviderModSettingChanged ); _initialized.Subscribe( AddInitialized ); _disposed.Subscribe( AddDisposed ); _redrawn.Subscribe( SetLastRedrawn ); + _preSettingsDraw.Subscribe( UpdateLastDrawnMod ); + _postSettingsDraw.Subscribe( UpdateLastDrawnMod ); + _settingChanged.Subscribe( UpdateLastModSetting ); } public void Dispose() @@ -49,6 +60,9 @@ public class IpcTester : IDisposable _redrawn.Subscribe( SetLastRedrawn ); _tooltip?.Unsubscribe( AddedTooltip ); _click?.Unsubscribe( AddedClick ); + _preSettingsDraw.Unsubscribe( UpdateLastDrawnMod ); + _postSettingsDraw.Unsubscribe( UpdateLastDrawnMod ); + _settingChanged.Unsubscribe( UpdateLastModSetting ); } private void AddInitialized() @@ -111,7 +125,12 @@ public class IpcTester : IDisposable ImGui.TableNextColumn(); } - private string _currentConfiguration = string.Empty; + private string _currentConfiguration = string.Empty; + private string _lastDrawnMod = string.Empty; + private DateTimeOffset _lastDrawnModTime; + + private void UpdateLastDrawnMod( string name ) + => ( _lastDrawnMod, _lastDrawnModTime ) = ( name, DateTimeOffset.Now ); private void DrawGeneral() { @@ -143,6 +162,8 @@ public class IpcTester : IDisposable DrawList( PenumbraIpc.LabelProviderInitialized, "Last Initialized", _initializedList ); DrawList( PenumbraIpc.LabelProviderDisposed, "Last Disposed", _disposedList ); + DrawIntro( PenumbraIpc.LabelProviderPostSettingsDraw, "Last Drawn Mod" ); + ImGui.TextUnformatted( _lastDrawnMod.Length > 0 ? $"{_lastDrawnMod} at {_lastDrawnModTime}" : "None" ); DrawIntro( PenumbraIpc.LabelProviderApiVersion, "Current Version" ); ImGui.TextUnformatted( _pi.GetIpcSubscriber< int >( PenumbraIpc.LabelProviderApiVersion ).InvokeFunc().ToString() ); DrawIntro( PenumbraIpc.LabelProviderGetModDirectory, "Current Mod Directory" ); @@ -500,8 +521,23 @@ public class IpcTester : IDisposable private IDictionary< string, (IList< string >, SelectType) >? _availableSettings; private IDictionary< string, IList< string > >? _currentSettings = null; private PenumbraApiEc _lastSettingsError = PenumbraApiEc.Success; + private ModSettingChange _lastSettingChangeType; + private string _lastSettingChangeCollection = string.Empty; + private string _lastSettingChangeMod = string.Empty; + private bool _lastSettingChangeInherited; + private DateTimeOffset _lastSettingChange; + private PenumbraApiEc _lastReloadEc = PenumbraApiEc.Success; + private void UpdateLastModSetting( ModSettingChange type, string collection, string mod, bool inherited ) + { + _lastSettingChangeType = type; + _lastSettingChangeCollection = collection; + _lastSettingChangeMod = mod; + _lastSettingChangeInherited = inherited; + _lastSettingChange = DateTimeOffset.Now; + } + private void DrawSetting() { using var _ = ImRaii.TreeNode( "Settings IPC" ); @@ -522,7 +558,10 @@ public class IpcTester : IDisposable } DrawIntro( "Last Error", _lastSettingsError.ToString() ); - + DrawIntro( PenumbraIpc.LabelProviderModSettingChanged, "Last Mod Setting Changed" ); + ImGui.TextUnformatted( _lastSettingChangeMod.Length > 0 + ? $"{_lastSettingChangeType} of {_lastSettingChangeMod} in {_lastSettingChangeCollection}{( _lastSettingChangeInherited ? " (Inherited)" : string.Empty )} at {_lastSettingChange}" + : "None" ); DrawIntro( PenumbraIpc.LabelProviderGetAvailableModSettings, "Get Available Settings" ); if( ImGui.Button( "Get##Available" ) ) { @@ -532,6 +571,16 @@ public class IpcTester : IDisposable _lastSettingsError = _availableSettings == null ? PenumbraApiEc.ModMissing : PenumbraApiEc.Success; } + DrawIntro( PenumbraIpc.LabelProviderReloadMod, "Reload Mod" ); + if( ImGui.Button( "Reload" ) ) + { + _lastReloadEc = _pi.GetIpcSubscriber< string, string, PenumbraApiEc >( PenumbraIpc.LabelProviderReloadMod ) + .InvokeFunc( _settingsModDirectory, _settingsModName ); + } + + ImGui.SameLine(); + ImGui.TextUnformatted( _lastReloadEc.ToString() ); + DrawIntro( PenumbraIpc.LabelProviderGetCurrentModSettings, "Get Current Settings" ); if( ImGui.Button( "Get##Current" ) ) { diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index f84cbc58..b81335b9 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -21,11 +21,17 @@ namespace Penumbra.Api; public class PenumbraApi : IDisposable, IPenumbraApi { public int ApiVersion - => 5; + => 6; private Penumbra? _penumbra; private Lumina.GameData? _lumina; + + private readonly Dictionary< ModCollection, ModCollection.ModSettingChangeDelegate > _delegates = new(); + + public event Action? PreSettingsPanelDraw; + public event Action? PostSettingsPanelDraw; public event GameObjectRedrawn? GameObjectRedrawn; + public event ModSettingChanged? ModSettingChanged; public bool Valid => _penumbra != null; @@ -37,13 +43,27 @@ public class PenumbraApi : IDisposable, IPenumbraApi .GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic ) ?.GetValue( Dalamud.GameData ); _penumbra.ObjectReloader.GameObjectRedrawn += OnGameObjectRedrawn; + foreach( var collection in Penumbra.CollectionManager ) + { + SubscribeToCollection( collection ); + } + + Penumbra.CollectionManager.CollectionChanged += SubscribeToNewCollections; } public void Dispose() { - _penumbra!.ObjectReloader.GameObjectRedrawn -= OnGameObjectRedrawn; - _penumbra = null; - _lumina = null; + Penumbra.CollectionManager.CollectionChanged -= SubscribeToNewCollections; + _penumbra!.ObjectReloader.GameObjectRedrawn -= OnGameObjectRedrawn; + _penumbra = null; + _lumina = null; + foreach( var collection in Penumbra.CollectionManager ) + { + if( _delegates.TryGetValue( collection, out var del ) ) + { + collection.ModSettingChanged -= del; + } + } } public event ChangedItemClick? ChangedItemClicked; @@ -214,6 +234,29 @@ public class PenumbraApi : IDisposable, IPenumbraApi ( shareSettings.Enabled, shareSettings.Priority, shareSettings.Settings, collection.Settings[ mod.Index ] != null ) ); } + public PenumbraApiEc ReloadMod( string modDirectory, string modName ) + { + CheckInitialized(); + if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) ) + { + return PenumbraApiEc.ModMissing; + } + + Penumbra.ModManager.ReloadMod( mod.Index ); + return PenumbraApiEc.Success; + } + + public PenumbraApiEc AddMod( string modDirectory ) + { + CheckInitialized(); + var dir = new DirectoryInfo( Path.Combine(Penumbra.ModManager.BasePath.FullName, modDirectory) ); + if( !dir.Exists ) + return PenumbraApiEc.FileMissing; + + Penumbra.ModManager.AddMod( dir ); + return PenumbraApiEc.Success; + } + public PenumbraApiEc TryInheritMod( string collectionName, string modDirectory, string modName, bool inherit ) { CheckInitialized(); @@ -572,4 +615,39 @@ public class PenumbraApi : IDisposable, IPenumbraApi return true; } + + private void SubscribeToCollection( ModCollection c ) + { + var name = c.Name; + + void Del( ModSettingChange type, int idx, int _, int _2, bool inherited ) + => ModSettingChanged?.Invoke( type, name, Penumbra.ModManager[ idx ].ModPath.Name, inherited ); + + _delegates[ c ] = Del; + c.ModSettingChanged += Del; + } + + private void SubscribeToNewCollections( CollectionType type, ModCollection? oldCollection, ModCollection? newCollection, string? _ ) + { + if( type != CollectionType.Inactive ) + { + return; + } + + if( oldCollection != null && _delegates.TryGetValue( oldCollection, out var del ) ) + { + oldCollection.ModSettingChanged -= del; + } + + if( newCollection != null ) + { + SubscribeToCollection( newCollection ); + } + } + + public void InvokePreSettingsPanel(string modDirectory) + => PreSettingsPanelDraw?.Invoke(modDirectory); + + public void InvokePostSettingsPanel(string modDirectory) + => PostSettingsPanelDraw?.Invoke(modDirectory); } \ No newline at end of file diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index ceaacfc4..6d2cd98e 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -4,6 +4,7 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; using Dalamud.Plugin; using Dalamud.Plugin.Ipc; +using Penumbra.Collections; using Penumbra.GameData.Enums; namespace Penumbra.Api; @@ -48,12 +49,16 @@ public partial class PenumbraIpc public const string LabelProviderApiVersion = "Penumbra.ApiVersion"; public const string LabelProviderGetModDirectory = "Penumbra.GetModDirectory"; 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< string >? ProviderGetModDirectory; - internal ICallGateProvider< string >? ProviderGetConfiguration; + internal ICallGateProvider< object? >? ProviderInitialized; + internal ICallGateProvider< object? >? ProviderDisposed; + internal ICallGateProvider< int >? ProviderApiVersion; + internal ICallGateProvider< string >? ProviderGetModDirectory; + internal ICallGateProvider< string >? ProviderGetConfiguration; + internal ICallGateProvider? ProviderPreSettingsDraw; + internal ICallGateProvider? ProviderPostSettingsDraw; private void InitializeGeneralProviders( DalamudPluginInterface pi ) { @@ -104,6 +109,26 @@ public partial class PenumbraIpc { PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetConfiguration}:\n{e}" ); } + + try + { + ProviderPreSettingsDraw = pi.GetIpcProvider( LabelProviderPreSettingsDraw ); + Api.PreSettingsPanelDraw += InvokeSettingsPreDraw; + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderPreSettingsDraw}:\n{e}" ); + } + + try + { + ProviderPostSettingsDraw = pi.GetIpcProvider( LabelProviderPostSettingsDraw ); + Api.PostSettingsPanelDraw += InvokeSettingsPostDraw; + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderPostSettingsDraw}:\n{e}" ); + } } private void DisposeGeneralProviders() @@ -195,13 +220,19 @@ 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(); ProviderRedrawObject?.UnregisterAction(); ProviderRedrawIndex?.UnregisterAction(); ProviderRedrawAll?.UnregisterAction(); - Api.GameObjectRedrawn -= OnGameObjectRedrawn; + Api.GameObjectRedrawn -= OnGameObjectRedrawn; } } @@ -425,14 +456,21 @@ public partial class PenumbraIpc public partial class PenumbraIpc { public const string LabelProviderGetAvailableModSettings = "Penumbra.GetAvailableModSettings"; + public const string LabelProviderReloadMod = "Penumbra.ReloadMod"; + public const string LabelProviderAddMod = "Penumbra.AddMod"; public const string LabelProviderGetCurrentModSettings = "Penumbra.GetCurrentModSettings"; public const string LabelProviderTryInheritMod = "Penumbra.TryInheritMod"; public const string LabelProviderTrySetMod = "Penumbra.TrySetMod"; public const string LabelProviderTrySetModPriority = "Penumbra.TrySetModPriority"; public const string LabelProviderTrySetModSetting = "Penumbra.TrySetModSetting"; public const string LabelProviderTrySetModSettings = "Penumbra.TrySetModSettings"; + public const string LabelProviderModSettingChanged = "Penumbra.ModSettingChanged"; + + internal ICallGateProvider< ModSettingChange, string, string, bool, object? >? ProviderModSettingChanged; internal ICallGateProvider< string, string, IDictionary< string, (IList< string >, Mods.SelectType) >? >? ProviderGetAvailableModSettings; + internal ICallGateProvider< string, string, PenumbraApiEc >? ProviderReloadMod; + internal ICallGateProvider< string, PenumbraApiEc >? ProviderAddMod; internal ICallGateProvider< string, string, string, bool, (PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)?) >? ProviderGetCurrentModSettings; @@ -445,6 +483,16 @@ public partial class PenumbraIpc private void InitializeSettingProviders( DalamudPluginInterface pi ) { + try + { + ProviderModSettingChanged = pi.GetIpcProvider< ModSettingChange, string, string, bool, object? >( LabelProviderModSettingChanged ); + Api.ModSettingChanged += InvokeModSettingChanged; + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderModSettingChanged}:\n{e}" ); + } + try { ProviderGetAvailableModSettings = @@ -457,6 +505,26 @@ public partial class PenumbraIpc PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetAvailableModSettings}:\n{e}" ); } + try + { + ProviderReloadMod = pi.GetIpcProvider< string, string, PenumbraApiEc >( LabelProviderReloadMod ); + ProviderReloadMod.RegisterFunc( Api.ReloadMod ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderReloadMod}:\n{e}" ); + } + + try + { + ProviderAddMod = pi.GetIpcProvider< string, PenumbraApiEc >( LabelProviderAddMod ); + ProviderAddMod.RegisterFunc( Api.AddMod ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderChangedItemClick}:\n{e}" ); + } + try { ProviderGetCurrentModSettings = @@ -524,7 +592,10 @@ public partial class PenumbraIpc private void DisposeSettingProviders() { + Api.ModSettingChanged -= InvokeModSettingChanged; ProviderGetAvailableModSettings?.UnregisterFunc(); + ProviderReloadMod?.UnregisterFunc(); + ProviderAddMod?.UnregisterFunc(); ProviderGetCurrentModSettings?.UnregisterFunc(); ProviderTryInheritMod?.UnregisterFunc(); ProviderTrySetMod?.UnregisterFunc(); @@ -532,6 +603,9 @@ public partial class PenumbraIpc ProviderTrySetModSetting?.UnregisterFunc(); ProviderTrySetModSettings?.UnregisterFunc(); } + + private void InvokeModSettingChanged( ModSettingChange type, string collection, string mod, bool inherited ) + => ProviderModSettingChanged?.SendMessage( type, collection, mod, inherited ); } public partial class PenumbraIpc diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index aa58ad32..612fb69e 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -1,20 +1,15 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; -using System.Reflection; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using Penumbra.Api; -using Penumbra.Collections; using Penumbra.Interop.Loader; using Penumbra.Interop.Structs; -using Penumbra.Mods; using CharacterUtility = Penumbra.Interop.CharacterUtility; namespace Penumbra.UI; diff --git a/Penumbra/UI/ConfigWindow.ModPanel.Settings.cs b/Penumbra/UI/ConfigWindow.ModPanel.Settings.cs index 20954498..96e25e98 100644 --- a/Penumbra/UI/ConfigWindow.ModPanel.Settings.cs +++ b/Penumbra/UI/ConfigWindow.ModPanel.Settings.cs @@ -49,6 +49,7 @@ public partial class ConfigWindow DrawInheritedWarning(); ImGui.Dummy( _window._defaultSpace ); + _window._penumbra.Api.InvokePreSettingsPanel( _mod.ModPath.Name ); DrawEnabledInput(); ImGui.SameLine(); DrawPriorityInput(); @@ -64,6 +65,8 @@ public partial class ConfigWindow { DrawMultiGroup( _mod.Groups[ idx ], idx ); } + + _window._penumbra.Api.InvokePostSettingsPanel(_mod.ModPath.Name); }