Add mod setting API functions.

This commit is contained in:
Ottermandias 2022-06-18 15:18:35 +02:00
parent df1a75b58a
commit c578bd3a49
5 changed files with 245 additions and 39 deletions

View file

@ -29,13 +29,14 @@ public enum PenumbraApiEc
CollectionMissing = 2,
ModMissing = 3,
OptionGroupMissing = 4,
SettingMissing = 5,
OptionMissing = 5,
CharacterCollectionExists = 6,
LowerPriority = 7,
InvalidGamePath = 8,
FileMissing = 9,
InvalidManipulation = 10,
InvalidArgument = 11,
}
public interface IPenumbraApi : IPenumbraApiBase
@ -75,7 +76,7 @@ public interface IPenumbraApi : IPenumbraApiBase
public string ResolvePath( string gamePath, string characterName );
// Reverse resolves a given modded local path into its replacement in form of all applicable game path for given character
public IList<string> ReverseResolvePath( string moddedPath, string characterName );
public IList< string > ReverseResolvePath( string moddedPath, string characterName );
// Try to load a given gamePath with the resolved path from Penumbra.
public T? GetFile< T >( string gamePath ) where T : FileResource;
@ -109,12 +110,12 @@ public interface IPenumbraApi : IPenumbraApiBase
// Obtain the potential settings of a mod specified by its directory name first or mod name second.
// Returns null if the mod could not be found.
public IDictionary< string, (IList<string>, SelectType) >? GetAvailableModSettings( string modDirectory, string modName );
public IDictionary< string, (IList< string >, SelectType) >? GetAvailableModSettings( string modDirectory, string modName );
// Obtain the enabled state, the priority, the settings of a mod specified by its directory name first or mod name second,
// and whether these settings are inherited, or null if the collection does not set them at all.
// If allowInheritance is false, only the collection itself will be checked.
public (PenumbraApiEc, (bool, int, IDictionary< string, IList<string> >, bool)?) GetCurrentModSettings( string collectionName,
public (PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)?) GetCurrentModSettings( string collectionName,
string modDirectory, string modName, bool allowInheritance );
// Try to set the inheritance state in the given collection of a mod specified by its directory name first or mod name second.
@ -136,7 +137,7 @@ public interface IPenumbraApi : IPenumbraApiBase
public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, string option );
public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName,
IReadOnlyList<string> options );
IReadOnlyList< string > options );
// Create a temporary collection without actual settings but with a cache.

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
@ -12,12 +13,15 @@ using Penumbra.Collections;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Mods;
using Penumbra.Util;
namespace Penumbra.Api;
public class PenumbraApi : IDisposable, IPenumbraApi
{
public int ApiVersion { get; } = 4;
public int ApiVersion
=> 4;
private Penumbra? _penumbra;
private Lumina.GameData? _lumina;
public event GameObjectRedrawn? GameObjectRedrawn;
@ -231,28 +235,173 @@ public class PenumbraApi : IDisposable, IPenumbraApi
}
public IDictionary< string, (IList< string >, SelectType) >? GetAvailableModSettings( string modDirectory, string modName )
=> throw new NotImplementedException();
{
CheckInitialized();
return Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod )
? mod.Groups.ToDictionary( g => g.Name, g => ( ( IList< string > )g.Select( o => o.Name ).ToList(), g.Type ) )
: null;
}
public (PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)?) GetCurrentModSettings( string collectionName,
string modDirectory, string modName,
bool allowInheritance )
=> throw new NotImplementedException();
string modDirectory, string modName, bool allowInheritance )
{
CheckInitialized();
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
{
return ( PenumbraApiEc.CollectionMissing, null );
}
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
{
return ( PenumbraApiEc.ModMissing, null );
}
var settings = allowInheritance ? collection.Settings[ mod.Index ] : collection[ mod.Index ].Settings;
if( settings == null )
{
return ( PenumbraApiEc.Okay, null );
}
var shareSettings = settings.ConvertToShareable( mod );
return ( PenumbraApiEc.Okay,
( shareSettings.Enabled, shareSettings.Priority, shareSettings.Settings, collection.Settings[ mod.Index ] != null ) );
}
public PenumbraApiEc TryInheritMod( string collectionName, string modDirectory, string modName, bool inherit )
=> throw new NotImplementedException();
{
CheckInitialized();
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
{
return PenumbraApiEc.CollectionMissing;
}
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
{
return PenumbraApiEc.ModMissing;
}
return collection.SetModInheritance( mod.Index, inherit ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged;
}
public PenumbraApiEc TrySetMod( string collectionName, string modDirectory, string modName, bool enabled )
=> throw new NotImplementedException();
{
CheckInitialized();
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
{
return PenumbraApiEc.CollectionMissing;
}
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
{
return PenumbraApiEc.ModMissing;
}
return collection.SetModState( mod.Index, enabled ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged;
}
public PenumbraApiEc TrySetModPriority( string collectionName, string modDirectory, string modName, int priority )
=> throw new NotImplementedException();
{
CheckInitialized();
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
{
return PenumbraApiEc.CollectionMissing;
}
public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, string option )
=> throw new NotImplementedException();
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
{
return PenumbraApiEc.ModMissing;
}
return collection.SetModPriority( mod.Index, priority ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged;
}
public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName,
IReadOnlyList< string > options )
=> throw new NotImplementedException();
string optionName )
{
CheckInitialized();
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
{
return PenumbraApiEc.CollectionMissing;
}
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
{
return PenumbraApiEc.ModMissing;
}
var groupIdx = mod.Groups.IndexOf( g => g.Name == optionGroupName );
if( groupIdx < 0 )
{
return PenumbraApiEc.OptionGroupMissing;
}
var optionIdx = mod.Groups[ groupIdx ].IndexOf( o => o.Name == optionName );
if( optionIdx < 0 )
{
return PenumbraApiEc.OptionMissing;
}
var setting = mod.Groups[ groupIdx ].Type == SelectType.Multi ? 1u << optionIdx : ( uint )optionIdx;
return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged;
}
public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName,
IReadOnlyList< string > optionNames )
{
CheckInitialized();
if( optionNames.Count == 0 )
{
return PenumbraApiEc.InvalidArgument;
}
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
{
return PenumbraApiEc.CollectionMissing;
}
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
{
return PenumbraApiEc.ModMissing;
}
var groupIdx = mod.Groups.IndexOf( g => g.Name == optionGroupName );
if( groupIdx < 0 )
{
return PenumbraApiEc.OptionGroupMissing;
}
var group = mod.Groups[ groupIdx ];
uint setting = 0;
if( group.Type == SelectType.Single )
{
var name = optionNames[ ^1 ];
var optionIdx = group.IndexOf( o => o.Name == name );
if( optionIdx < 0 )
{
return PenumbraApiEc.OptionMissing;
}
setting = ( uint )optionIdx;
}
else
{
foreach( var name in optionNames )
{
var optionIdx = group.IndexOf( o => o.Name == name );
if( optionIdx < 0 )
{
return PenumbraApiEc.OptionMissing;
}
setting |= 1u << optionIdx;
}
}
return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged;
}
public PenumbraApiEc CreateTemporaryCollection( string collectionName, string? character, bool forceOverwriteCharacter )
=> throw new NotImplementedException();

View file

@ -24,17 +24,20 @@ public partial class ModCollection
public event ModSettingChangeDelegate ModSettingChanged;
// Enable or disable the mod inheritance of mod idx.
public void SetModInheritance( int idx, bool inherit )
public bool SetModInheritance( int idx, bool inherit )
{
if( FixInheritance( idx, inherit ) )
{
ModSettingChanged.Invoke( ModSettingChange.Inheritance, idx, inherit ? 0 : 1, 0, false );
return true;
}
return false;
}
// Set the enabled state mod idx to newValue if it differs from the current enabled state.
// If mod idx is currently inherited, stop the inheritance.
public void SetModState( int idx, bool newValue )
public bool SetModState( int idx, bool newValue )
{
var oldValue = _settings[ idx ]?.Enabled ?? this[ idx ].Settings?.Enabled ?? false;
if( newValue != oldValue )
@ -42,7 +45,10 @@ public partial class ModCollection
var inheritance = FixInheritance( idx, false );
_settings[ idx ]!.Enabled = newValue;
ModSettingChanged.Invoke( ModSettingChange.EnableState, idx, inheritance ? -1 : newValue ? 0 : 1, 0, false );
return true;
}
return false;
}
// Enable or disable the mod inheritance of every mod in mods.
@ -78,7 +84,7 @@ public partial class ModCollection
// Set the priority of mod idx to newValue if it differs from the current priority.
// If mod idx is currently inherited, stop the inheritance.
public void SetModPriority( int idx, int newValue )
public bool SetModPriority( int idx, int newValue )
{
var oldValue = _settings[ idx ]?.Priority ?? this[ idx ].Settings?.Priority ?? 0;
if( newValue != oldValue )
@ -86,12 +92,15 @@ public partial class ModCollection
var inheritance = FixInheritance( idx, false );
_settings[ idx ]!.Priority = newValue;
ModSettingChanged.Invoke( ModSettingChange.Priority, idx, inheritance ? -1 : oldValue, 0, false );
return true;
}
return false;
}
// Set a given setting group settingName of mod idx to newValue if it differs from the current value and fix it if necessary.
// If mod idx is currently inherited, stop the inheritance.
public void SetModSetting( int idx, int groupIdx, uint newValue )
public bool SetModSetting( int idx, int groupIdx, uint newValue )
{
var settings = _settings[ idx ] != null ? _settings[ idx ]!.Settings : this[ idx ].Settings?.Settings;
var oldValue = settings?[ groupIdx ] ?? 0;
@ -100,31 +109,26 @@ public partial class ModCollection
var inheritance = FixInheritance( idx, false );
_settings[ idx ]!.SetValue( Penumbra.ModManager[ idx ], groupIdx, newValue );
ModSettingChanged.Invoke( ModSettingChange.Setting, idx, inheritance ? -1 : ( int )oldValue, groupIdx, false );
return true;
}
return false;
}
// Change one of the available mod settings for mod idx discerned by type.
// If type == Setting, settingName should be a valid setting for that mod, otherwise it will be ignored.
// The setting will also be automatically fixed if it is invalid for that setting group.
// For boolean parameters, newValue == 0 will be treated as false and != 0 as true.
public void ChangeModSetting( ModSettingChange type, int idx, int newValue, int groupIdx )
public bool ChangeModSetting( ModSettingChange type, int idx, int newValue, int groupIdx )
{
switch( type )
return type switch
{
case ModSettingChange.Inheritance:
SetModInheritance( idx, newValue != 0 );
break;
case ModSettingChange.EnableState:
SetModState( idx, newValue != 0 );
break;
case ModSettingChange.Priority:
SetModPriority( idx, newValue );
break;
case ModSettingChange.Setting:
SetModSetting( idx, groupIdx, ( uint )newValue );
break;
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
}
ModSettingChange.Inheritance => SetModInheritance( idx, newValue != 0 ),
ModSettingChange.EnableState => SetModState( idx, newValue != 0 ),
ModSettingChange.Priority => SetModPriority( idx, newValue ),
ModSettingChange.Setting => SetModSetting( idx, groupIdx, ( uint )newValue ),
_ => throw new ArgumentOutOfRangeException( nameof( type ), type, null ),
};
}
// Set inheritance of a mod without saving,

View file

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Penumbra.Mods;
@ -37,5 +38,28 @@ public sealed partial class Mod
ModOptionChanged += OnModOptionChange;
ModPathChanged += OnModPathChange;
}
// Try to obtain a mod by its directory name (unique identifier, preferred),
// or the first mod of the given name if no directory fits.
public bool TryGetMod( string modDirectory, string modName, [NotNullWhen( true )] out Mod? mod )
{
mod = null;
foreach( var m in _mods )
{
if( m.ModPath.Name == modDirectory )
{
mod = m;
return true;
}
if( m.Name == modName )
{
mod ??= m;
}
}
return mod != null;
}
}
}

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using OtterGui.Filesystem;
using Penumbra.Util;
namespace Penumbra.Mods;
@ -10,7 +11,7 @@ namespace Penumbra.Mods;
public class ModSettings
{
public static readonly ModSettings Empty = new();
public List< uint > Settings { get; init; } = new();
public List< uint > Settings { get; private init; } = new();
public int Priority { get; set; }
public bool Enabled { get; set; }
@ -100,7 +101,7 @@ public class ModSettings
private static uint FixSetting( IModGroup group, uint value )
=> group.Type switch
{
SelectType.Single => ( uint )Math.Min( value, group.Count - 1 ),
SelectType.Single => ( uint )Math.Min( value, group.Count - 1 ),
SelectType.Multi => ( uint )( value & ( ( 1ul << group.Count ) - 1 ) ),
_ => value,
};
@ -208,4 +209,31 @@ public class ModSettings
return changes;
}
}
// 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 )
{
var dict = new Dictionary< string, IList< string > >( Settings.Count );
foreach( var (setting, idx) in Settings.WithIndex() )
{
if( idx >= mod.Groups.Count )
{
break;
}
var group = mod.Groups[ idx ];
if( group.Type == SelectType.Single && setting < group.Count )
{
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 );
}
}
return ( Enabled, Priority, dict );
}
}