diff --git a/Penumbra/Models/ModInfo.cs b/Penumbra/Models/ModInfo.cs index 369085a9..2fa0e5a7 100644 --- a/Penumbra/Models/ModInfo.cs +++ b/Penumbra/Models/ModInfo.cs @@ -1,20 +1,23 @@ -using System.Collections.Generic; using Newtonsoft.Json; using Penumbra.Mods; namespace Penumbra.Models { - public class ModInfo + public class ModInfo : ModSettings { public ModInfo( ResourceMod mod ) => Mod = mod; public string FolderName { get; set; } = ""; public bool Enabled { get; set; } - public int Priority { get; set; } - public Dictionary< string, int > Conf { get; set; } = new(); [JsonIgnore] public ResourceMod Mod { get; set; } + + public bool FixSpecificSetting( string name ) + => FixSpecificSetting( Mod.Meta, name ); + + public bool FixInvalidSettings() + => FixInvalidSettings( Mod.Meta ); } } \ No newline at end of file diff --git a/Penumbra/Models/ModMeta.cs b/Penumbra/Models/ModMeta.cs index 1c17dbce..b283f10e 100644 --- a/Penumbra/Models/ModMeta.cs +++ b/Penumbra/Models/ModMeta.cs @@ -43,5 +43,84 @@ namespace Penumbra.Models // todo: handle broken mods properly } } + + private static bool ApplySingleGroupFiles( OptionGroup group, RelPath relPath, int selection, HashSet< GamePath > paths ) + { + if( group.Options[ selection ].OptionFiles.TryGetValue( relPath, out var groupPaths ) ) + { + paths.UnionWith( groupPaths ); + return true; + } + + for( var i = 0; i < group.Options.Count; ++i ) + { + if( i == selection ) + { + continue; + } + + if( group.Options[ i ].OptionFiles.ContainsKey( relPath ) ) + { + return true; + } + } + + return false; + } + + private static bool ApplyMultiGroupFiles( OptionGroup group, RelPath relPath, int selection, HashSet< GamePath > paths ) + { + var doNotAdd = false; + for( var i = 0; i < group.Options.Count; ++i ) + { + if( ( selection & ( 1 << i ) ) != 0 ) + { + if( group.Options[ i ].OptionFiles.TryGetValue( relPath, out var groupPaths ) ) + { + paths.UnionWith( groupPaths ); + } + } + else if( group.Options[ i ].OptionFiles.ContainsKey( relPath ) ) + { + doNotAdd = true; + } + } + + return doNotAdd; + } + + public (bool configChanged, HashSet< GamePath > paths) GetFilesForConfig( RelPath relPath, ModSettings settings ) + { + var doNotAdd = false; + var configChanged = false; + + HashSet< GamePath > paths = new(); + foreach( var group in Groups.Values ) + { + configChanged |= settings.FixSpecificSetting( this, group.GroupName ); + + if( group.Options.Count == 0 ) + { + continue; + } + + switch( group.SelectionType ) + { + case SelectType.Single: + doNotAdd |= ApplySingleGroupFiles( group, relPath, settings.Settings[ group.GroupName ], paths ); + break; + case SelectType.Multi: + doNotAdd |= ApplyMultiGroupFiles( group, relPath, settings.Settings[ group.GroupName ], paths ); + break; + } + } + + if( !doNotAdd ) + { + paths.Add( new GamePath( relPath ) ); + } + + return ( configChanged, paths ); + } } } \ No newline at end of file diff --git a/Penumbra/Models/ModSettings.cs b/Penumbra/Models/ModSettings.cs new file mode 100644 index 00000000..b7a1662e --- /dev/null +++ b/Penumbra/Models/ModSettings.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Linq; +using System; + +namespace Penumbra.Models +{ + public class ModSettings + { + public int Priority { get; set; } + public Dictionary< string, int > Settings { get; set; } = new(); + + // For backwards compatibility + private Dictionary< string, int > Conf + { + set => Settings = value; + } + + public static ModSettings CreateFrom( NamedModSettings n, ModMeta meta ) + { + ModSettings ret = new() + { + Priority = n.Priority, + Settings = n.Settings.Keys.ToDictionary( K => K, K => 0 ) + }; + + foreach( var kvp in n.Settings ) + { + if( !meta.Groups.TryGetValue( kvp.Key, out var info ) ) + { + continue; + } + + if( info.SelectionType == SelectType.Single ) + { + if( n.Settings[ kvp.Key ].Count == 0 ) + { + ret.Settings[ kvp.Key ] = 0; + } + else + { + var idx = info.Options.FindIndex( O => O.OptionName == n.Settings[ kvp.Key ].Last() ); + ret.Settings[ kvp.Key ] = idx < 0 ? 0 : idx; + } + } + else + { + foreach( var idx in n.Settings[ kvp.Key ] + .Select( option => info.Options.FindIndex( O => O.OptionName == option ) ) + .Where( idx => idx >= 0 ) ) + { + ret.Settings[ kvp.Key ] |= 1 << idx; + } + } + } + + return ret; + } + + public bool FixSpecificSetting( ModMeta meta, string name ) + { + if( !meta.Groups.TryGetValue( name, out var group ) ) + { + return Settings.Remove( name ); + } + + if( Settings.TryGetValue( name, out var oldSetting ) ) + { + Settings[ name ] = group.SelectionType switch + { + SelectType.Single => Math.Min( Math.Max( oldSetting, 0 ), group.Options.Count - 1 ), + SelectType.Multi => Math.Min( Math.Max( oldSetting, 0 ), ( 1 << group.Options.Count ) - 1 ), + _ => Settings[ group.GroupName ] + }; + return oldSetting != Settings[ group.GroupName ]; + } + + Settings[ name ] = 0; + return true; + } + + public bool FixInvalidSettings( ModMeta meta ) + { + if( meta.Groups.Count == 0 ) + { + return false; + } + + return Settings.Keys.ToArray().Union( meta.Groups.Keys ) + .Aggregate( false, ( current, name ) => current | FixSpecificSetting( meta, name ) ); + } + } +} \ No newline at end of file diff --git a/Penumbra/Models/NamedModSettings.cs b/Penumbra/Models/NamedModSettings.cs new file mode 100644 index 00000000..5b70e029 --- /dev/null +++ b/Penumbra/Models/NamedModSettings.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Penumbra.Models +{ + public class NamedModSettings + { + public int Priority { get; set; } + public Dictionary< string, HashSet< string > > Settings { get; set; } = new(); + + public void AddFromModSetting( ModSettings s, ModMeta meta ) + { + Priority = s.Priority; + Settings = s.Settings.Keys.ToDictionary( K => K, K => new HashSet< string >() ); + + foreach( var kvp in Settings ) + { + if( !meta.Groups.TryGetValue( kvp.Key, out var info ) ) + { + continue; + } + + var setting = s.Settings[ kvp.Key ]; + if( info.SelectionType == SelectType.Single ) + { + var name = setting < info.Options.Count + ? info.Options[ setting ].OptionName + : info.Options[ 0 ].OptionName; + kvp.Value.Add( name ); + } + else + { + for( var i = 0; i < info.Options.Count; ++i ) + { + if( ( ( setting >> i ) & 1 ) != 0 ) + { + kvp.Value.Add( info.Options[ i ].OptionName ); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Penumbra/Mods/ModCollection.cs b/Penumbra/Mods/ModCollection.cs index 6973b421..5f1b38a2 100644 --- a/Penumbra/Mods/ModCollection.cs +++ b/Penumbra/Mods/ModCollection.cs @@ -146,13 +146,13 @@ namespace Penumbra.Mods public ModInfo AddModSettings( ResourceMod mod ) { - var entry = new ModInfo(mod) + var entry = new ModInfo( mod ) { Priority = ModSettings?.Count ?? 0, FolderName = mod.ModBasePath.Name, - Enabled = true, + Enabled = true }; - + entry.FixInvalidSettings(); #if DEBUG PluginLog.Information( "creating mod settings {ModName}", entry.FolderName ); #endif @@ -170,14 +170,14 @@ namespace Penumbra.Mods } settings.Mod = mod; + settings.FixInvalidSettings(); return settings; - } public IEnumerable< ModInfo > GetOrderedAndEnabledModSettings( bool invertOrder = false ) { var query = ModSettings? - .Where( x => x.Enabled ) ?? Enumerable.Empty(); + .Where( x => x.Enabled ) ?? Enumerable.Empty< ModInfo >(); if( !invertOrder ) { diff --git a/Penumbra/Mods/ModManager.cs b/Penumbra/Mods/ModManager.cs index e0a542b8..cf47b503 100644 --- a/Penumbra/Mods/ModManager.cs +++ b/Penumbra/Mods/ModManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Dalamud.Plugin; using Penumbra.Models; using Penumbra.Util; @@ -39,7 +38,6 @@ namespace Penumbra.Mods Mods = new ModCollection( basePath ); Mods.Load(); - Mods.Save(); CalculateEffectiveFileList(); } @@ -54,16 +52,19 @@ namespace Penumbra.Mods return; } + var changedSettings = false; var registeredFiles = new Dictionary< GamePath, string >(); - foreach( var (mod, settings) in Mods.GetOrderedAndEnabledModListWithSettings( _plugin!.Configuration!.InvertModListOrder ) ) { mod.FileConflicts.Clear(); - ProcessModFiles( registeredFiles, mod, settings ); + changedSettings |= ProcessModFiles( registeredFiles, mod, settings ); ProcessSwappedFiles( registeredFiles, mod, settings ); } + if (changedSettings) + Mods.Save(); + _plugin!.GameUtils!.ReloadPlayerResources(); } @@ -84,76 +85,21 @@ namespace Penumbra.Mods } } - private void ProcessModFiles( Dictionary< GamePath, string > registeredFiles, ResourceMod mod, ModInfo settings ) + private bool ProcessModFiles( Dictionary< GamePath, string > registeredFiles, ResourceMod mod, ModInfo settings ) { + var changedConfig = settings.FixInvalidSettings(); foreach( var file in mod.ModFiles ) { RelPath relativeFilePath = new( file, mod.ModBasePath ); - var doNotAdd = false; - foreach( var group in mod.Meta.Groups.Values ) - { - if( !settings.Conf.TryGetValue( group.GroupName, out var setting ) - || group.SelectionType == SelectType.Single - && settings.Conf[ group.GroupName ] >= group.Options.Count ) - { - settings.Conf[ group.GroupName ] = 0; - Mods!.Save(); - setting = 0; - } - - if( group.Options.Count == 0 ) - { - continue; - } - - if( group.SelectionType == SelectType.Multi ) - { - settings.Conf[ group.GroupName ] &= ( 1 << group.Options.Count ) - 1; - } - - HashSet< GamePath > paths; - switch( group.SelectionType ) - { - case SelectType.Single: - if( group.Options[ setting ].OptionFiles.TryGetValue( relativeFilePath, out paths ) ) - { - doNotAdd |= AddFiles( paths, file, registeredFiles, mod ); - } - else - { - doNotAdd |= group.Options.Where( ( o, i ) => i != setting ) - .Any( option => option.OptionFiles.ContainsKey( relativeFilePath ) ); - } - - break; - case SelectType.Multi: - for( var i = 0; i < group.Options.Count; ++i ) - { - if( ( setting & ( 1 << i ) ) != 0 ) - { - if( group.Options[ i ].OptionFiles.TryGetValue( relativeFilePath, out paths ) ) - { - doNotAdd |= AddFiles( paths, file, registeredFiles, mod ); - } - } - else - { - doNotAdd |= group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ); - } - } - - break; - } - } - - if( !doNotAdd ) - { - AddFiles( new GamePath[] { new( relativeFilePath ) }, file, registeredFiles, mod ); - } + var (configChanged, gamePaths) = mod.Meta.GetFilesForConfig( relativeFilePath, settings ); + changedConfig |= configChanged; + AddFiles( gamePaths, file, registeredFiles, mod ); } + + return changedConfig; } - private bool AddFiles( IEnumerable< GamePath > gamePaths, FileInfo file, Dictionary< GamePath, string > registeredFiles, + private void AddFiles( IEnumerable< GamePath > gamePaths, FileInfo file, Dictionary< GamePath, string > registeredFiles, ResourceMod mod ) { foreach( var gamePath in gamePaths ) @@ -168,7 +114,6 @@ namespace Penumbra.Mods mod.AddConflict( modName, gamePath ); } } - return true; } public void ChangeModPriority( ModInfo info, bool up = false ) diff --git a/Penumbra/UI/Custom/ImGuiFramedGroup.cs b/Penumbra/UI/Custom/ImGuiFramedGroup.cs index c8bedc99..a8b0ee2d 100644 --- a/Penumbra/UI/Custom/ImGuiFramedGroup.cs +++ b/Penumbra/UI/Custom/ImGuiFramedGroup.cs @@ -64,7 +64,8 @@ namespace Penumbra.UI ImGui.PopStyleVar( 2 ); - ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X - frameHeight, ImGui.GetWindowSize().Y ) ); + // This seems wrong? + //ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X - frameHeight, ImGui.GetWindowSize().Y ) ); var itemWidth = ImGui.CalcItemWidth(); ImGui.PushItemWidth( Math.Max( 0f, itemWidth - frameHeight ) ); @@ -127,7 +128,8 @@ namespace Penumbra.UI frameMax, borderColor, halfFrame.X ); ImGui.PopStyleVar( 2 ); - ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X + frameHeight, ImGui.GetWindowSize().Y ) ); + // This seems wrong? + // ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X + frameHeight, ImGui.GetWindowSize().Y ) ); ImGui.Dummy( ZeroVector ); ImGui.EndGroup(); // Close first group diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs index 8ceb0b8f..bc9341bb 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs @@ -552,7 +552,7 @@ namespace Penumbra.UI var oldEnabled = enabled; if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled ) { - Mod.Conf[ group.GroupName ] ^= 1 << idx; + Mod.Settings[ group.GroupName ] ^= 1 << idx; Save(); } } @@ -567,7 +567,7 @@ namespace Penumbra.UI ImGuiCustom.BeginFramedGroup( group.GroupName ); for( var i = 0; i < group.Options.Count; ++i ) { - DrawMultiSelectorCheckBox( group, i, Mod.Conf[ group.GroupName ], + DrawMultiSelectorCheckBox( group, i, Mod.Settings[ group.GroupName ], $"{group.Options[ i ].OptionName}##{group.GroupName}" ); } @@ -581,11 +581,11 @@ namespace Penumbra.UI return; } - var code = Mod.Conf[ group.GroupName ]; + var code = Mod.Settings[ group.GroupName ]; if( ImGui.Combo( group.GroupName, ref code , group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) ) { - Mod.Conf[ group.GroupName ] = code; + Mod.Settings[ group.GroupName ] = code; Save(); } } diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs index 7b9d564e..5cf73119 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs @@ -110,14 +110,18 @@ namespace Penumbra.UI if( ImGuiCustom.BeginFramedGroupEdit( ref groupName ) && groupName != group.GroupName && !Meta!.Groups.ContainsKey( groupName ) ) { - var oldConf = Mod!.Conf[ group.GroupName ]; + var oldConf = Mod!.Settings[ group.GroupName ]; Meta.Groups.Remove( group.GroupName ); - Mod.Conf.Remove( group.GroupName ); + Mod.FixSpecificSetting( group.GroupName ); if( groupName.Length > 0 ) { Meta.Groups[ groupName ] = new OptionGroup() - { GroupName = groupName, SelectionType = SelectType.Multi, Options = group.Options }; - Mod.Conf[ groupName ] = oldConf; + { + GroupName = groupName, + SelectionType = SelectType.Multi, + Options = group.Options + }; + Mod.Settings[ groupName ] = oldConf; } return true; @@ -143,14 +147,13 @@ namespace Penumbra.UI private void DrawMultiSelectorEdit( OptionGroup group ) { var nameBoxStart = CheckMarkSize; - var flag = Mod!.Conf[ group.GroupName ]; - - var modChanged = DrawMultiSelectorEditBegin( group ); + var flag = Mod!.Settings[ group.GroupName ]; + var modChanged = DrawMultiSelectorEditBegin( group ); for( var i = 0; i < group.Options.Count; ++i ) { var opt = group.Options[ i ]; - var label = $"##{opt.OptionName}_{group.GroupName}"; + var label = $"##{group.GroupName}_{i}"; DrawMultiSelectorCheckBox( group, i, flag, label ); ImGui.SameLine(); @@ -168,8 +171,9 @@ namespace Penumbra.UI { group.Options.RemoveAt( i ); var bitmaskFront = ( 1 << i ) - 1; - Mod.Conf[ group.GroupName ] = ( flag & bitmaskFront ) | ( ( flag & ~bitmaskFront ) >> 1 ); - modChanged = true; + var bitmaskBack = ~( bitmaskFront | ( 1 << i ) ); + Mod.Settings[ group.GroupName ] = ( flag & bitmaskFront ) | ( ( flag & bitmaskBack ) >> 1 ); + modChanged = true; } else if( newName != opt.OptionName ) { @@ -191,24 +195,28 @@ namespace Penumbra.UI ImGuiCustom.EndFramedGroup(); } - private bool DrawSingleSelectorEditGroup( OptionGroup group ) + private bool DrawSingleSelectorEditGroup( OptionGroup group, ref bool selectionChanged ) { var groupName = group.GroupName; if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) && !Meta!.Groups.ContainsKey( groupName ) ) { - var oldConf = Mod!.Conf[ group.GroupName ]; + var oldConf = Mod!.Settings[ group.GroupName ]; if( groupName != group.GroupName ) { Meta.Groups.Remove( group.GroupName ); - Mod.Conf.Remove( group.GroupName ); + selectionChanged |= Mod.FixSpecificSetting( group.GroupName ); } if( groupName.Length > 0 ) { - Meta.Groups.Add( groupName, - new OptionGroup() { GroupName = groupName, Options = group.Options, SelectionType = SelectType.Single } ); - Mod.Conf[ groupName ] = oldConf; + Meta.Groups.Add( groupName, new OptionGroup() + { + GroupName = groupName, + Options = group.Options, + SelectionType = SelectType.Single + } ); + Mod.Settings[ groupName ] = oldConf; } return true; @@ -219,7 +227,7 @@ namespace Penumbra.UI private float DrawSingleSelectorEdit( OptionGroup group ) { - var code = Mod!.Conf[ group.GroupName ]; + var code = Mod!.Settings[ group.GroupName ]; var selectionChanged = false; var modChanged = false; if( ImGuiCustom.RenameableCombo( $"##{group.GroupName}", ref code, out var newName, @@ -229,11 +237,15 @@ namespace Penumbra.UI { if( newName.Length > 0 ) { - selectionChanged = true; - modChanged = true; - Mod.Conf[ group.GroupName ] = code; + selectionChanged = true; + modChanged = true; + Mod.Settings[ group.GroupName ] = code; group.Options.Add( new Option() - { OptionName = newName, OptionDesc = "", OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >() } ); + { + OptionName = newName, + OptionDesc = "", + OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >() + } ); } } else @@ -242,32 +254,30 @@ namespace Penumbra.UI { modChanged = true; group.Options.RemoveAt( code ); - if( code >= group.Options.Count ) - { - code = 0; - } } - else if( newName != group.Options[ code ].OptionName ) + else { - modChanged = true; - group.Options[ code ] = new Option() + if( newName != group.Options[ code ].OptionName ) { - OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc, - OptionFiles = group.Options[ code ].OptionFiles - }; + modChanged = true; + group.Options[ code ] = new Option() + { + OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc, + OptionFiles = group.Options[ code ].OptionFiles + }; + } + + selectionChanged |= Mod.Settings[ group.GroupName ] != code; + Mod.Settings[ group.GroupName ] = code; } - if( Mod.Conf[ group.GroupName ] != code ) - { - selectionChanged = true; - Mod.Conf[ group.GroupName ] = code; - } + selectionChanged |= Mod.FixSpecificSetting( group.GroupName ); } } ImGui.SameLine(); var labelEditPos = ImGui.GetCursorPosX(); - modChanged |= DrawSingleSelectorEditGroup( group ); + modChanged |= DrawSingleSelectorEditGroup( group, ref selectionChanged ); if( modChanged ) { @@ -296,7 +306,7 @@ namespace Penumbra.UI Options = new List< Option >() }; - Mod.Conf[ newGroup ] = 0; + Mod.Settings[ newGroup ] = 0; _selector.SaveCurrentMod(); Save(); }