diff --git a/Penumbra.Api b/Penumbra.Api index 891eb195..36a06e50 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 891eb195c29904824004c45f84b92a9e1dd98ddf +Subproject commit 36a06e509bf0a7023b4c9b148b06f71f8116cc81 diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index fea10f95..1b4c1d1d 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -1084,6 +1084,13 @@ public class IpcTester : IDisposable _lastSettingsError = Ipc.TrySetModPriority.Subscriber( _pi ).Invoke( _settingsCollection, _settingsModDirectory, _settingsModName, _settingsPriority ); } + DrawIntro( Ipc.CopyModSettings.Label, "Copy Mod Settings" ); + if( ImGui.Button( "Copy Settings" ) ) + { + _lastSettingsError = Ipc.CopyModSettings.Subscriber( _pi ).Invoke( _settingsCollection, _settingsModDirectory, _settingsModName ); + } + ImGuiUtil.HoverTooltip( "Copy settings from Mod Directory Name to Mod Name (as directory) in collection." ); + DrawIntro( Ipc.TrySetModSetting.Label, "Set Setting(s)" ); if( _availableSettings == null ) { diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index ebb09ddb..54be2b27 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -618,6 +618,31 @@ public class PenumbraApi : IDisposable, IPenumbraApi } + public PenumbraApiEc CopyModSettings( string? collectionName, string modDirectoryFrom, string modDirectoryTo ) + { + CheckInitialized(); + + var sourceModIdx = Penumbra.ModManager.FirstOrDefault( m => string.Equals( m.ModPath.Name, modDirectoryFrom, StringComparison.OrdinalIgnoreCase ) )?.Index ?? -1; + var targetModIdx = Penumbra.ModManager.FirstOrDefault( m => string.Equals( m.ModPath.Name, modDirectoryTo, StringComparison.OrdinalIgnoreCase ) )?.Index ?? -1; + if( string.IsNullOrEmpty( collectionName ) ) + { + foreach( var collection in Penumbra.CollectionManager ) + { + collection.CopyModSettings( sourceModIdx, modDirectoryFrom, targetModIdx, modDirectoryTo ); + } + } + else if( Penumbra.CollectionManager.ByName( collectionName, out var collection ) ) + { + collection.CopyModSettings( sourceModIdx, modDirectoryFrom, targetModIdx, modDirectoryTo ); + } + else + { + return PenumbraApiEc.CollectionMissing; + } + + return PenumbraApiEc.Success; + } + public (PenumbraApiEc, string) CreateTemporaryCollection( string tag, string character, bool forceOverwriteCharacter ) { CheckInitialized(); diff --git a/Penumbra/Api/PenumbraIpcProviders.cs b/Penumbra/Api/PenumbraIpcProviders.cs index 69db9c99..352edfcf 100644 --- a/Penumbra/Api/PenumbraIpcProviders.cs +++ b/Penumbra/Api/PenumbraIpcProviders.cs @@ -91,6 +91,7 @@ public class PenumbraIpcProviders : IDisposable internal readonly FuncProvider< string, string, string, string, string, PenumbraApiEc > TrySetModSetting; internal readonly FuncProvider< string, string, string, string, IReadOnlyList< string >, PenumbraApiEc > TrySetModSettings; internal readonly EventProvider< ModSettingChange, string, string, bool > ModSettingChanged; + internal readonly FuncProvider< string, string, string, PenumbraApiEc > CopyModSettings; // Temporary internal readonly FuncProvider< string, string, bool, (PenumbraApiEc, string) > CreateTemporaryCollection; @@ -188,9 +189,10 @@ public class PenumbraIpcProviders : IDisposable TrySetModPriority = Ipc.TrySetModPriority.Provider( pi, Api.TrySetModPriority ); TrySetModSetting = Ipc.TrySetModSetting.Provider( pi, Api.TrySetModSetting ); TrySetModSettings = Ipc.TrySetModSettings.Provider( pi, Api.TrySetModSettings ); - ModSettingChanged = Ipc.ModSettingChanged.Provider( pi, + ModSettingChanged = Ipc.ModSettingChanged.Provider( pi, () => Api.ModSettingChanged += ModSettingChangedEvent, () => Api.ModSettingChanged -= ModSettingChangedEvent ); + CopyModSettings = Ipc.CopyModSettings.Provider( pi, Api.CopyModSettings ); // Temporary CreateTemporaryCollection = Ipc.CreateTemporaryCollection.Provider( pi, Api.CreateTemporaryCollection ); @@ -287,6 +289,7 @@ public class PenumbraIpcProviders : IDisposable TrySetModSetting.Dispose(); TrySetModSettings.Dispose(); ModSettingChanged.Dispose(); + CopyModSettings.Dispose(); // Temporary CreateTemporaryCollection.Dispose(); diff --git a/Penumbra/Collections/ModCollection.cs b/Penumbra/Collections/ModCollection.cs index 0b165b1f..2b7a8605 100644 --- a/Penumbra/Collections/ModCollection.cs +++ b/Penumbra/Collections/ModCollection.cs @@ -3,6 +3,7 @@ using Penumbra.Mods; using System; using System.Collections.Generic; using System.Linq; +using OtterGui; namespace Penumbra.Collections; @@ -101,7 +102,7 @@ public partial class ModCollection // Check if a name is valid to use for a collection. // Does not check for uniqueness. public static bool IsValidName( string name ) - => name.Length > 0 && name.All( c => !c.IsInvalidAscii() && c is not '|' && !c.IsInvalidInPath() ); + => name.Length > 0 && name.All( c => !c.IsInvalidAscii() && c is not '|' && !c.IsInvalidInPath() ); // Remove all settings for not currently-installed mods. public void CleanUnavailableSettings() @@ -135,7 +136,7 @@ public partial class ModCollection var settings = _settings[ idx ]; if( settings != null ) { - _unusedSettings[mod.ModPath.Name] = new ModSettings.SavedSettings( settings, mod ); + _unusedSettings[ mod.ModPath.Name ] = new ModSettings.SavedSettings( settings, mod ); } _settings.RemoveAt( idx ); @@ -173,6 +174,63 @@ public partial class ModCollection } } + public bool CopyModSettings( int modIdx, string modName, int targetIdx, string targetName ) + { + if( targetName.Length == 0 && targetIdx < 0 || modName.Length == 0 && modIdx < 0 ) + { + return false; + } + + // If the source mod exists, convert its settings to saved settings or null if its inheriting. + // If it does not exist, check unused settings. + // If it does not exist and has no unused settings, also use null. + ModSettings.SavedSettings? savedSettings = modIdx >= 0 + ? _settings[ modIdx ] != null + ? new ModSettings.SavedSettings( _settings[ modIdx ]!, Penumbra.ModManager[ modIdx ] ) + : null + : _unusedSettings.TryGetValue( modName, out var s ) + ? s + : null; + + if( targetIdx >= 0 ) + { + if( savedSettings != null ) + { + // The target mod exists and the source settings are not inheriting, convert and fix the settings and copy them. + // This triggers multiple events. + savedSettings.Value.ToSettings( Penumbra.ModManager[ targetIdx ], out var settings ); + SetModState( targetIdx, settings.Enabled ); + SetModPriority( targetIdx, settings.Priority ); + foreach( var (value, index) in settings.Settings.WithIndex() ) + { + SetModSetting( targetIdx, index, value ); + } + } + else + { + // The target mod exists, but the source is inheriting, set the target to inheriting. + // This triggers events. + SetModInheritance( targetIdx, true ); + } + } + else + { + // The target mod does not exist. + // Either copy the unused source settings directly if they are not inheriting, + // or remove any unused settings for the target if they are inheriting. + if( savedSettings != null ) + { + _unusedSettings[ targetName ] = savedSettings.Value; + } + else + { + _unusedSettings.Remove( targetName ); + } + } + + return true; + } + public override string ToString() => Name; } \ No newline at end of file diff --git a/Penumbra/Mods/Manager/Mod.Manager.cs b/Penumbra/Mods/Manager/Mod.Manager.cs index 71069dbe..55ef005f 100644 --- a/Penumbra/Mods/Manager/Mod.Manager.cs +++ b/Penumbra/Mods/Manager/Mod.Manager.cs @@ -52,7 +52,7 @@ public sealed partial class Mod mod = null; foreach( var m in _mods ) { - if( m.ModPath.Name == modDirectory ) + if( string.Equals(m.ModPath.Name, modDirectory, StringComparison.OrdinalIgnoreCase) ) { mod = m; return true;