mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Fix exception with empty option groups.
This commit is contained in:
parent
4610686a70
commit
a5f0c2f943
2 changed files with 87 additions and 95 deletions
|
|
@ -11,9 +11,9 @@ namespace Penumbra.Mods.Subclasses;
|
|||
public class ModSettings
|
||||
{
|
||||
public static readonly ModSettings Empty = new();
|
||||
public List< uint > Settings { get; private init; } = [];
|
||||
public int Priority { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public List<uint> Settings { get; private init; } = [];
|
||||
public int Priority { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
// Create an independent copy of the current settings.
|
||||
public ModSettings DeepCopy()
|
||||
|
|
@ -25,148 +25,143 @@ public class ModSettings
|
|||
};
|
||||
|
||||
// Create default settings for a given mod.
|
||||
public static ModSettings DefaultSettings( Mod mod )
|
||||
public static ModSettings DefaultSettings(Mod mod)
|
||||
=> new()
|
||||
{
|
||||
Enabled = false,
|
||||
Priority = 0,
|
||||
Settings = mod.Groups.Select( g => g.DefaultSettings ).ToList(),
|
||||
Settings = mod.Groups.Select(g => g.DefaultSettings).ToList(),
|
||||
};
|
||||
|
||||
// Return everything required to resolve things for a single mod with given settings (which can be null, in which case the default is used.
|
||||
public static (Dictionary< Utf8GamePath, FullPath >, HashSet< MetaManipulation >) GetResolveData( Mod mod, ModSettings? settings )
|
||||
public static (Dictionary<Utf8GamePath, FullPath>, HashSet<MetaManipulation>) GetResolveData(Mod mod, ModSettings? settings)
|
||||
{
|
||||
if (settings == null)
|
||||
settings = DefaultSettings(mod);
|
||||
else
|
||||
settings.AddMissingSettings( mod );
|
||||
settings.AddMissingSettings(mod);
|
||||
|
||||
var dict = new Dictionary< Utf8GamePath, FullPath >();
|
||||
var set = new HashSet< MetaManipulation >();
|
||||
var dict = new Dictionary<Utf8GamePath, FullPath>();
|
||||
var set = new HashSet<MetaManipulation>();
|
||||
|
||||
foreach( var (group, index) in mod.Groups.WithIndex().OrderByDescending( g => g.Value.Priority ) )
|
||||
foreach (var (group, index) in mod.Groups.WithIndex().OrderByDescending(g => g.Value.Priority))
|
||||
{
|
||||
if( group.Type is GroupType.Single )
|
||||
if (group.Type is GroupType.Single)
|
||||
{
|
||||
AddOption( group[ ( int )settings.Settings[ index ] ] );
|
||||
if (group.Count > 0)
|
||||
AddOption(group[(int)settings.Settings[index]]);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach( var (option, optionIdx) in group.WithIndex().OrderByDescending( o => group.OptionPriority( o.Index ) ) )
|
||||
foreach (var (option, optionIdx) in group.WithIndex().OrderByDescending(o => group.OptionPriority(o.Index)))
|
||||
{
|
||||
if( ( ( settings.Settings[ index ] >> optionIdx ) & 1 ) == 1 )
|
||||
{
|
||||
AddOption( option );
|
||||
}
|
||||
if (((settings.Settings[index] >> optionIdx) & 1) == 1)
|
||||
AddOption(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddOption( mod.Default );
|
||||
return ( dict, set );
|
||||
AddOption(mod.Default);
|
||||
return (dict, set);
|
||||
|
||||
void AddOption( ISubMod option )
|
||||
void AddOption(ISubMod option)
|
||||
{
|
||||
foreach( var (path, file) in option.Files.Concat( option.FileSwaps ) )
|
||||
{
|
||||
dict.TryAdd( path, file );
|
||||
}
|
||||
foreach (var (path, file) in option.Files.Concat(option.FileSwaps))
|
||||
dict.TryAdd(path, file);
|
||||
|
||||
foreach( var manip in option.Manipulations )
|
||||
{
|
||||
set.Add( manip );
|
||||
}
|
||||
foreach (var manip in option.Manipulations)
|
||||
set.Add(manip);
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically react to changes in a mods available options.
|
||||
public bool HandleChanges( ModOptionChangeType type, Mod mod, int groupIdx, int optionIdx, int movedToIdx )
|
||||
public bool HandleChanges(ModOptionChangeType type, Mod mod, int groupIdx, int optionIdx, int movedToIdx)
|
||||
{
|
||||
switch( type )
|
||||
switch (type)
|
||||
{
|
||||
case ModOptionChangeType.GroupRenamed: return true;
|
||||
case ModOptionChangeType.GroupAdded:
|
||||
// Add new empty setting for new mod.
|
||||
Settings.Insert( groupIdx, mod.Groups[ groupIdx ].DefaultSettings );
|
||||
Settings.Insert(groupIdx, mod.Groups[groupIdx].DefaultSettings);
|
||||
return true;
|
||||
case ModOptionChangeType.GroupDeleted:
|
||||
// Remove setting for deleted mod.
|
||||
Settings.RemoveAt( groupIdx );
|
||||
Settings.RemoveAt(groupIdx);
|
||||
return true;
|
||||
case ModOptionChangeType.GroupTypeChanged:
|
||||
{
|
||||
// Fix settings for a changed group type.
|
||||
// Single -> Multi: set single as enabled, rest as disabled
|
||||
// Multi -> Single: set the first enabled option or 0.
|
||||
var group = mod.Groups[ groupIdx ];
|
||||
var config = Settings[ groupIdx ];
|
||||
Settings[ groupIdx ] = group.Type switch
|
||||
var group = mod.Groups[groupIdx];
|
||||
var config = Settings[groupIdx];
|
||||
Settings[groupIdx] = group.Type switch
|
||||
{
|
||||
GroupType.Single => ( uint )Math.Max( Math.Min( group.Count - 1, BitOperations.TrailingZeroCount( config ) ), 0 ),
|
||||
GroupType.Multi => 1u << ( int )config,
|
||||
GroupType.Single => (uint)Math.Max(Math.Min(group.Count - 1, BitOperations.TrailingZeroCount(config)), 0),
|
||||
GroupType.Multi => 1u << (int)config,
|
||||
_ => config,
|
||||
};
|
||||
return config != Settings[ groupIdx ];
|
||||
return config != Settings[groupIdx];
|
||||
}
|
||||
case ModOptionChangeType.OptionDeleted:
|
||||
{
|
||||
// Single -> select the previous option if any.
|
||||
// Multi -> excise the corresponding bit.
|
||||
var group = mod.Groups[ groupIdx ];
|
||||
var config = Settings[ groupIdx ];
|
||||
Settings[ groupIdx ] = group.Type switch
|
||||
var group = mod.Groups[groupIdx];
|
||||
var config = Settings[groupIdx];
|
||||
Settings[groupIdx] = group.Type switch
|
||||
{
|
||||
GroupType.Single => config >= optionIdx ? config > 1 ? config - 1 : 0 : config,
|
||||
GroupType.Multi => Functions.RemoveBit( config, optionIdx ),
|
||||
GroupType.Multi => Functions.RemoveBit(config, optionIdx),
|
||||
_ => config,
|
||||
};
|
||||
return config != Settings[ groupIdx ];
|
||||
return config != Settings[groupIdx];
|
||||
}
|
||||
case ModOptionChangeType.GroupMoved:
|
||||
// Move the group the same way.
|
||||
return Settings.Move( groupIdx, movedToIdx );
|
||||
return Settings.Move(groupIdx, movedToIdx);
|
||||
case ModOptionChangeType.OptionMoved:
|
||||
{
|
||||
// Single -> select the moved option if it was currently selected
|
||||
// Multi -> move the corresponding bit
|
||||
var group = mod.Groups[ groupIdx ];
|
||||
var config = Settings[ groupIdx ];
|
||||
Settings[ groupIdx ] = group.Type switch
|
||||
var group = mod.Groups[groupIdx];
|
||||
var config = Settings[groupIdx];
|
||||
Settings[groupIdx] = group.Type switch
|
||||
{
|
||||
GroupType.Single => config == optionIdx ? ( uint )movedToIdx : config,
|
||||
GroupType.Multi => Functions.MoveBit( config, optionIdx, movedToIdx ),
|
||||
GroupType.Single => config == optionIdx ? (uint)movedToIdx : config,
|
||||
GroupType.Multi => Functions.MoveBit(config, optionIdx, movedToIdx),
|
||||
_ => config,
|
||||
};
|
||||
return config != Settings[ groupIdx ];
|
||||
return config != Settings[groupIdx];
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a value is valid for a group.
|
||||
private static uint FixSetting( IModGroup group, uint value )
|
||||
private static uint FixSetting(IModGroup group, uint value)
|
||||
=> group.Type switch
|
||||
{
|
||||
GroupType.Single => ( uint )Math.Min( value, group.Count - 1 ),
|
||||
GroupType.Multi => ( uint )( value & ( ( 1ul << group.Count ) - 1 ) ),
|
||||
GroupType.Single => (uint)Math.Min(value, group.Count - 1),
|
||||
GroupType.Multi => (uint)(value & ((1ul << group.Count) - 1)),
|
||||
_ => value,
|
||||
};
|
||||
|
||||
// Set a setting. Ensures that there are enough settings and fixes the setting beforehand.
|
||||
public void SetValue( Mod mod, int groupIdx, uint newValue )
|
||||
public void SetValue(Mod mod, int groupIdx, uint newValue)
|
||||
{
|
||||
AddMissingSettings( mod );
|
||||
var group = mod.Groups[ groupIdx ];
|
||||
Settings[ groupIdx ] = FixSetting( group, newValue );
|
||||
AddMissingSettings(mod);
|
||||
var group = mod.Groups[groupIdx];
|
||||
Settings[groupIdx] = FixSetting(group, newValue);
|
||||
}
|
||||
|
||||
// Add defaulted settings up to the required count.
|
||||
private bool AddMissingSettings( Mod mod )
|
||||
private bool AddMissingSettings(Mod mod)
|
||||
{
|
||||
var changes = false;
|
||||
for( var i = Settings.Count; i < mod.Groups.Count; ++i )
|
||||
for (var i = Settings.Count; i < mod.Groups.Count; ++i)
|
||||
{
|
||||
Settings.Add( mod.Groups[ i ].DefaultSettings );
|
||||
Settings.Add(mod.Groups[i].DefaultSettings);
|
||||
changes = true;
|
||||
}
|
||||
|
||||
|
|
@ -176,51 +171,47 @@ public class ModSettings
|
|||
// A simple struct conversion to easily save settings by name instead of value.
|
||||
public struct SavedSettings
|
||||
{
|
||||
public Dictionary< string, long > Settings;
|
||||
public int Priority;
|
||||
public bool Enabled;
|
||||
public Dictionary<string, long> Settings;
|
||||
public int Priority;
|
||||
public bool Enabled;
|
||||
|
||||
public SavedSettings DeepCopy()
|
||||
=> new()
|
||||
{
|
||||
Enabled = Enabled,
|
||||
Priority = Priority,
|
||||
Settings = Settings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value ),
|
||||
Settings = Settings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
|
||||
};
|
||||
|
||||
public SavedSettings( ModSettings settings, Mod mod )
|
||||
public SavedSettings(ModSettings settings, Mod mod)
|
||||
{
|
||||
Priority = settings.Priority;
|
||||
Enabled = settings.Enabled;
|
||||
Settings = new Dictionary< string, long >( mod.Groups.Count );
|
||||
settings.AddMissingSettings( mod );
|
||||
Settings = new Dictionary<string, long>(mod.Groups.Count);
|
||||
settings.AddMissingSettings(mod);
|
||||
|
||||
foreach( var (group, setting) in mod.Groups.Zip( settings.Settings ) )
|
||||
{
|
||||
Settings.Add( group.Name, setting );
|
||||
}
|
||||
foreach (var (group, setting) in mod.Groups.Zip(settings.Settings))
|
||||
Settings.Add(group.Name, setting);
|
||||
}
|
||||
|
||||
// Convert and fix.
|
||||
public bool ToSettings( Mod mod, out ModSettings settings )
|
||||
public bool ToSettings(Mod mod, out ModSettings settings)
|
||||
{
|
||||
var list = new List< uint >( mod.Groups.Count );
|
||||
var list = new List<uint>(mod.Groups.Count);
|
||||
var changes = Settings.Count != mod.Groups.Count;
|
||||
foreach( var group in mod.Groups )
|
||||
foreach (var group in mod.Groups)
|
||||
{
|
||||
if( Settings.TryGetValue( group.Name, out var config ) )
|
||||
if (Settings.TryGetValue(group.Name, out var config))
|
||||
{
|
||||
var castConfig = ( uint )Math.Clamp( config, 0, uint.MaxValue );
|
||||
var actualConfig = FixSetting( group, castConfig );
|
||||
list.Add( actualConfig );
|
||||
if( actualConfig != config )
|
||||
{
|
||||
var castConfig = (uint)Math.Clamp(config, 0, uint.MaxValue);
|
||||
var actualConfig = FixSetting(group, castConfig);
|
||||
list.Add(actualConfig);
|
||||
if (actualConfig != config)
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add( 0 );
|
||||
list.Add(0);
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -238,28 +229,29 @@ public class ModSettings
|
|||
|
||||
// Return the settings for a given mod in a shareable format, using the names of groups and options instead of indices.
|
||||
// Does not repair settings but ignores settings not fitting to the given mod.
|
||||
public (bool Enabled, int Priority, Dictionary< string, IList< string > > Settings) ConvertToShareable( Mod mod )
|
||||
public (bool Enabled, int Priority, Dictionary<string, IList<string>> Settings) ConvertToShareable(Mod mod)
|
||||
{
|
||||
var dict = new Dictionary< string, IList< string > >( Settings.Count );
|
||||
foreach( var (setting, idx) in Settings.WithIndex() )
|
||||
var dict = new Dictionary<string, IList<string>>(Settings.Count);
|
||||
foreach (var (setting, idx) in Settings.WithIndex())
|
||||
{
|
||||
if( idx >= mod.Groups.Count )
|
||||
{
|
||||
if (idx >= mod.Groups.Count)
|
||||
break;
|
||||
}
|
||||
|
||||
var group = mod.Groups[ idx ];
|
||||
if( group.Type == GroupType.Single && setting < group.Count )
|
||||
var group = mod.Groups[idx];
|
||||
if (group.Type == GroupType.Single && setting < group.Count)
|
||||
{
|
||||
dict.Add( group.Name, new[] { group[ ( int )setting ].Name } );
|
||||
dict.Add(group.Name, new[]
|
||||
{
|
||||
group[(int)setting].Name,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var list = group.Where( ( _, optionIdx ) => ( setting & ( 1 << optionIdx ) ) != 0 ).Select( o => o.Name ).ToList();
|
||||
dict.Add( group.Name, list );
|
||||
var list = group.Where((_, optionIdx) => (setting & (1 << optionIdx)) != 0).Select(o => o.Name).ToList();
|
||||
dict.Add(group.Name, list);
|
||||
}
|
||||
}
|
||||
|
||||
return ( Enabled, Priority, dict );
|
||||
return (Enabled, Priority, dict);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public sealed class SingleModGroup : IModGroup
|
|||
public int Priority { get; set; }
|
||||
public uint DefaultSettings { get; set; }
|
||||
|
||||
public readonly List<SubMod> OptionData = new();
|
||||
public readonly List<SubMod> OptionData = [];
|
||||
|
||||
public int OptionPriority(Index _)
|
||||
=> Priority;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue