This commit is contained in:
Ottermandias 2022-03-26 18:34:32 +01:00
parent bc47e08e08
commit 9a0b0bfa0f
35 changed files with 1365 additions and 1997 deletions

View file

@ -1,574 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Penumbra.Mod;
using Penumbra.Util;
namespace Penumbra.Mods;
public sealed class CollectionManager2 : IDisposable, IEnumerable< ModCollection2 >
{
private readonly ModManager _modManager;
private readonly List< ModCollection2 > _collections = new();
public ModCollection2 this[ int idx ]
=> _collections[ idx ];
public ModCollection2? this[ string name ]
=> ByName( name, out var c ) ? c : null;
public ModCollection2 Default
=> this[ ModCollection2.DefaultCollection ]!;
public bool ByName( string name, [NotNullWhen( true )] out ModCollection2? collection )
=> _collections.FindFirst( c => c.Name == name, out collection );
public IEnumerator< ModCollection2 > GetEnumerator()
=> _collections.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public CollectionManager2( ModManager manager )
{
_modManager = manager;
//_modManager.ModsRediscovered += OnModsRediscovered;
//_modManager.ModChange += OnModChanged;
ReadCollections();
//LoadConfigCollections( Penumbra.Config );
}
public void Dispose()
{ }
private void AddDefaultCollection()
{
if( this[ ModCollection.DefaultCollection ] != null )
{
return;
}
var defaultCollection = ModCollection2.CreateNewEmpty( ModCollection2.DefaultCollection );
defaultCollection.Save();
_collections.Add( defaultCollection );
}
private void ApplyInheritancesAndFixSettings( IEnumerable< IReadOnlyList< string > > inheritances )
{
foreach( var (collection, inheritance) in this.Zip( inheritances ) )
{
var changes = false;
foreach( var subCollectionName in inheritance )
{
if( !ByName( subCollectionName, out var subCollection ) )
{
changes = true;
PluginLog.Warning( $"Inherited collection {subCollectionName} for {collection.Name} does not exist, removed." );
}
else if( !collection.AddInheritance( subCollection ) )
{
changes = true;
PluginLog.Warning( $"{collection.Name} can not inherit from {subCollectionName}, removed." );
}
}
foreach( var (setting, mod) in collection.Settings.Zip( Penumbra.ModManager.Mods ).Where( s => s.First != null ) )
{
changes |= setting!.FixInvalidSettings( mod.Meta );
}
if( changes )
{
collection.Save();
}
}
}
private void ReadCollections()
{
var collectionDir = new DirectoryInfo( ModCollection2.CollectionDirectory );
var inheritances = new List< IReadOnlyList< string > >();
if( collectionDir.Exists )
{
foreach( var file in collectionDir.EnumerateFiles( "*.json" ) )
{
var collection = ModCollection2.LoadFromFile( file, out var inheritance );
if( collection == null || collection.Name.Length == 0 )
{
continue;
}
if( file.Name != $"{collection.Name.RemoveInvalidPathSymbols()}.json" )
{
PluginLog.Warning( $"Collection {file.Name} does not correspond to {collection.Name}." );
}
if( this[ collection.Name ] != null )
{
PluginLog.Warning( $"Duplicate collection found: {collection.Name} already exists." );
}
else
{
inheritances.Add( inheritance );
_collections.Add( collection );
}
}
}
AddDefaultCollection();
ApplyInheritancesAndFixSettings( inheritances );
}
}
public enum CollectionType : byte
{
Inactive,
Default,
Forced,
Character,
Current,
}
public delegate void CollectionChangeDelegate( ModCollection? oldCollection, ModCollection? newCollection, CollectionType type,
string? characterName = null );
// Contains all collections and respective functions, as well as the collection settings.
public sealed class CollectionManager : IDisposable
{
private readonly ModManager _manager;
public List< ModCollection > Collections { get; } = new();
public Dictionary< string, ModCollection > CharacterCollection { get; } = new();
public ModCollection CurrentCollection { get; private set; } = ModCollection.Empty;
public ModCollection DefaultCollection { get; private set; } = ModCollection.Empty;
public ModCollection ForcedCollection { get; private set; } = ModCollection.Empty;
public bool IsActive( ModCollection collection )
=> ReferenceEquals( collection, DefaultCollection ) || ReferenceEquals( collection, ForcedCollection );
public ModCollection Default
=> ByName( ModCollection.DefaultCollection )!;
public ModCollection? ByName( string name )
=> name.Length > 0
? Collections.Find( c => string.Equals( c.Name, name, StringComparison.InvariantCultureIgnoreCase ) )
: ModCollection.Empty;
public bool ByName( string name, [NotNullWhen( true )] out ModCollection? collection )
{
if( name.Length > 0 )
{
return Collections.FindFirst( c => string.Equals( c.Name, name, StringComparison.InvariantCultureIgnoreCase ), out collection );
}
collection = ModCollection.Empty;
return true;
}
// Is invoked after the collections actually changed.
public event CollectionChangeDelegate? CollectionChanged;
public CollectionManager( ModManager manager )
{
_manager = manager;
_manager.ModsRediscovered += OnModsRediscovered;
_manager.ModChange += OnModChanged;
ReadCollections();
LoadConfigCollections( Penumbra.Config );
}
public void Dispose()
{
_manager.ModsRediscovered -= OnModsRediscovered;
_manager.ModChange -= OnModChanged;
}
private void OnModsRediscovered()
{
RecreateCaches();
DefaultCollection.SetFiles();
}
private void OnModChanged( ModChangeType type, int idx, ModData mod )
{
switch( type )
{
case ModChangeType.Added:
foreach( var collection in Collections )
{
collection.AddMod( mod );
}
break;
case ModChangeType.Removed:
RemoveModFromCaches( mod.BasePath );
break;
case ModChangeType.Changed:
// TODO
break;
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
}
}
public void CreateNecessaryCaches()
{
AddCache( DefaultCollection );
AddCache( ForcedCollection );
foreach( var (_, collection) in CharacterCollection )
{
AddCache( collection );
}
}
public void RecreateCaches()
{
foreach( var collection in Collections.Where( c => c.Cache != null ) )
{
collection.CreateCache( _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) );
}
CreateNecessaryCaches();
}
public void RemoveModFromCaches( DirectoryInfo modDir )
{
foreach( var collection in Collections )
{
collection.Cache?.RemoveMod( modDir );
}
}
internal void UpdateCollections( ModData mod, bool metaChanges, ResourceChange fileChanges, bool nameChange, bool reloadMeta )
{
foreach( var collection in Collections )
{
if( metaChanges )
{
collection.UpdateSetting( mod );
}
if( fileChanges.HasFlag( ResourceChange.Files )
&& collection.Settings.TryGetValue( mod.BasePath.Name, out var settings )
&& settings.Enabled )
{
collection.Cache?.CalculateEffectiveFileList();
}
if( reloadMeta )
{
collection.Cache?.UpdateMetaManipulations();
}
}
if( reloadMeta && DefaultCollection.Settings.TryGetValue( mod.BasePath.Name, out var config ) && config.Enabled )
{
Penumbra.ResidentResources.Reload();
}
}
public bool AddCollection( string name, Dictionary< string, ModSettings > settings )
{
var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant();
if( nameFixed.Length == 0 || Collections.Any( c => c.Name.RemoveInvalidPathSymbols().ToLowerInvariant() == nameFixed ) )
{
PluginLog.Warning( $"The new collection {name} would lead to the same path as one that already exists." );
return false;
}
var newCollection = new ModCollection( name, settings );
Collections.Add( newCollection );
newCollection.Save();
CollectionChanged?.Invoke( null, newCollection, CollectionType.Inactive );
SetCollection( newCollection, CollectionType.Current );
return true;
}
public bool RemoveCollection( string name )
{
if( name == ModCollection.DefaultCollection )
{
PluginLog.Error( "Can not remove the default collection." );
return false;
}
var idx = Collections.IndexOf( c => c.Name == name );
if( idx < 0 )
{
return false;
}
var collection = Collections[ idx ];
if( CurrentCollection == collection )
{
SetCollection( Default, CollectionType.Current );
}
if( ForcedCollection == collection )
{
SetCollection( ModCollection.Empty, CollectionType.Forced );
}
if( DefaultCollection == collection )
{
SetCollection( ModCollection.Empty, CollectionType.Default );
}
foreach( var (characterName, characterCollection) in CharacterCollection.ToArray() )
{
if( characterCollection == collection )
{
SetCollection( ModCollection.Empty, CollectionType.Character, characterName );
}
}
collection.Delete();
Collections.RemoveAt( idx );
CollectionChanged?.Invoke( collection, null, CollectionType.Inactive );
return true;
}
private void AddCache( ModCollection collection )
{
if( collection.Cache == null && collection.Name != string.Empty )
{
collection.CreateCache( _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) );
}
}
private void RemoveCache( ModCollection collection )
{
if( collection.Name != ForcedCollection.Name
&& collection.Name != CurrentCollection.Name
&& collection.Name != DefaultCollection.Name
&& CharacterCollection.All( kvp => kvp.Value.Name != collection.Name ) )
{
collection.ClearCache();
}
}
public void SetCollection( ModCollection newCollection, CollectionType type, string? characterName = null )
{
var oldCollection = type switch
{
CollectionType.Default => DefaultCollection,
CollectionType.Forced => ForcedCollection,
CollectionType.Current => CurrentCollection,
CollectionType.Character => characterName?.Length > 0
? CharacterCollection.TryGetValue( characterName, out var c )
? c
: ModCollection.Empty
: null,
_ => null,
};
if( oldCollection == null || newCollection.Name == oldCollection.Name )
{
return;
}
AddCache( newCollection );
RemoveCache( oldCollection );
switch( type )
{
case CollectionType.Default:
DefaultCollection = newCollection;
Penumbra.Config.DefaultCollection = newCollection.Name;
Penumbra.ResidentResources.Reload();
DefaultCollection.SetFiles();
break;
case CollectionType.Forced:
ForcedCollection = newCollection;
Penumbra.Config.ForcedCollection = newCollection.Name;
Penumbra.ResidentResources.Reload();
break;
case CollectionType.Current:
CurrentCollection = newCollection;
Penumbra.Config.CurrentCollection = newCollection.Name;
break;
case CollectionType.Character:
CharacterCollection[ characterName! ] = newCollection;
Penumbra.Config.CharacterCollections[ characterName! ] = newCollection.Name;
break;
}
CollectionChanged?.Invoke( oldCollection, newCollection, type, characterName );
Penumbra.Config.Save();
}
public bool CreateCharacterCollection( string characterName )
{
if( CharacterCollection.ContainsKey( characterName ) )
{
return false;
}
CharacterCollection[ characterName ] = ModCollection.Empty;
Penumbra.Config.CharacterCollections[ characterName ] = string.Empty;
Penumbra.Config.Save();
CollectionChanged?.Invoke( null, ModCollection.Empty, CollectionType.Character, characterName );
return true;
}
public void RemoveCharacterCollection( string characterName )
{
if( CharacterCollection.TryGetValue( characterName, out var collection ) )
{
RemoveCache( collection );
CharacterCollection.Remove( characterName );
CollectionChanged?.Invoke( collection, null, CollectionType.Character, characterName );
}
if( Penumbra.Config.CharacterCollections.Remove( characterName ) )
{
Penumbra.Config.Save();
}
}
private bool LoadCurrentCollection( Configuration config )
{
if( ByName( config.CurrentCollection, out var currentCollection ) )
{
CurrentCollection = currentCollection;
AddCache( CurrentCollection );
return false;
}
PluginLog.Error( $"Last choice of CurrentCollection {config.CurrentCollection} is not available, reset to Default." );
CurrentCollection = Default;
if( CurrentCollection.Cache == null )
{
CurrentCollection.CreateCache( _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) );
}
config.CurrentCollection = ModCollection.DefaultCollection;
return true;
}
private bool LoadForcedCollection( Configuration config )
{
if( config.ForcedCollection.Length == 0 )
{
ForcedCollection = ModCollection.Empty;
return false;
}
if( ByName( config.ForcedCollection, out var forcedCollection ) )
{
ForcedCollection = forcedCollection;
AddCache( ForcedCollection );
return false;
}
PluginLog.Error( $"Last choice of ForcedCollection {config.ForcedCollection} is not available, reset to None." );
ForcedCollection = ModCollection.Empty;
config.ForcedCollection = string.Empty;
return true;
}
private bool LoadDefaultCollection( Configuration config )
{
if( config.DefaultCollection.Length == 0 )
{
DefaultCollection = ModCollection.Empty;
return false;
}
if( ByName( config.DefaultCollection, out var defaultCollection ) )
{
DefaultCollection = defaultCollection;
AddCache( DefaultCollection );
return false;
}
PluginLog.Error( $"Last choice of DefaultCollection {config.DefaultCollection} is not available, reset to None." );
DefaultCollection = ModCollection.Empty;
config.DefaultCollection = string.Empty;
return true;
}
private bool LoadCharacterCollections( Configuration config )
{
var configChanged = false;
foreach( var (player, collectionName) in config.CharacterCollections.ToArray() )
{
if( collectionName.Length == 0 )
{
CharacterCollection.Add( player, ModCollection.Empty );
}
else if( ByName( collectionName, out var charCollection ) )
{
AddCache( charCollection );
CharacterCollection.Add( player, charCollection );
}
else
{
PluginLog.Error( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to None." );
CharacterCollection.Add( player, ModCollection.Empty );
config.CharacterCollections[ player ] = string.Empty;
configChanged = true;
}
}
return configChanged;
}
private void LoadConfigCollections( Configuration config )
{
var configChanged = LoadCurrentCollection( config );
configChanged |= LoadDefaultCollection( config );
configChanged |= LoadForcedCollection( config );
configChanged |= LoadCharacterCollections( config );
if( configChanged )
{
config.Save();
}
}
private void ReadCollections()
{
var collectionDir = ModCollection.CollectionDir();
if( collectionDir.Exists )
{
foreach( var file in collectionDir.EnumerateFiles( "*.json" ) )
{
var collection = ModCollection.LoadFromFile( file );
if( collection == null || collection.Name == string.Empty )
{
continue;
}
if( file.Name != $"{collection.Name.RemoveInvalidPathSymbols()}.json" )
{
PluginLog.Warning( $"Collection {file.Name} does not correspond to {collection.Name}." );
}
if( ByName( collection.Name ) != null )
{
PluginLog.Warning( $"Duplicate collection found: {collection.Name} already exists." );
}
else
{
Collections.Add( collection );
}
}
}
if( ByName( ModCollection.DefaultCollection ) == null )
{
var defaultCollection = new ModCollection();
defaultCollection.Save();
Collections.Add( defaultCollection );
}
}
}

View file

@ -1,113 +0,0 @@
using System;
using Penumbra.Mod;
namespace Penumbra.Mods;
public enum ModSettingChange
{
Inheritance,
EnableState,
Priority,
Setting,
}
public partial class ModCollection2
{
public delegate void ModSettingChangeDelegate( ModSettingChange type, int modIdx, int oldValue, string? optionName );
public event ModSettingChangeDelegate ModSettingChanged;
// Enable or disable the mod inheritance of mod idx.
public void SetModInheritance( int idx, bool inherit )
{
if( FixInheritance( idx, inherit ) )
{
ModSettingChanged.Invoke( ModSettingChange.Inheritance, idx, inherit ? 0 : 1, null );
}
}
// Set the enabled state mod idx to newValue if it differs from the current priority.
// If mod idx is currently inherited, stop the inheritance.
public void SetModState( int idx, bool newValue )
{
var oldValue = _settings[ idx ]?.Enabled ?? this[ idx ].Settings?.Enabled ?? false;
if( newValue != oldValue )
{
var inheritance = FixInheritance( idx, true );
_settings[ idx ]!.Enabled = newValue;
ModSettingChanged.Invoke( ModSettingChange.EnableState, idx, inheritance ? -1 : newValue ? 0 : 1, null );
}
}
// 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 )
{
var oldValue = _settings[ idx ]?.Priority ?? this[ idx ].Settings?.Priority ?? 0;
if( newValue != oldValue )
{
var inheritance = FixInheritance( idx, true );
_settings[ idx ]!.Priority = newValue;
ModSettingChanged.Invoke( ModSettingChange.Priority, idx, inheritance ? -1 : oldValue, null );
}
}
// 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, string settingName, int newValue )
{
var settings = _settings[ idx ] != null ? _settings[ idx ]!.Settings : this[ idx ].Settings?.Settings;
var oldValue = settings != null
? settings.TryGetValue( settingName, out var v ) ? v : newValue
: Penumbra.ModManager.Mods[ idx ].Meta.Groups.ContainsKey( settingName )
? 0
: newValue;
if( oldValue != newValue )
{
var inheritance = FixInheritance( idx, true );
_settings[ idx ]!.Settings[ settingName ] = newValue;
_settings[ idx ]!.FixSpecificSetting( settingName, Penumbra.ModManager.Mods[ idx ].Meta );
ModSettingChanged.Invoke( ModSettingChange.Setting, idx, inheritance ? -1 : oldValue, settingName );
}
}
// 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, string? settingName = null )
{
switch( type )
{
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, settingName ?? string.Empty, newValue );
break;
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
}
}
// Set inheritance of a mod without saving,
// to be used as an intermediary.
private bool FixInheritance( int idx, bool inherit )
{
var settings = _settings[ idx ];
if( inherit != ( settings == null ) )
{
_settings[ idx ] = inherit ? null : this[ idx ].Settings ?? ModSettings.DefaultSettings( Penumbra.ModManager.Mods[ idx ].Meta );
return true;
}
return false;
}
private void SaveOnChange( ModSettingChange _1, int _2, int _3, string? _4 )
=> Save();
}

View file

@ -1,72 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Penumbra.Mod;
using Penumbra.Util;
namespace Penumbra.Mods;
public partial class ModCollection2
{
private readonly List< ModCollection2 > _inheritance = new();
public event Action InheritanceChanged;
public IReadOnlyList< ModCollection2 > Inheritance
=> _inheritance;
public IEnumerable< ModCollection2 > GetFlattenedInheritance()
{
yield return this;
foreach( var collection in _inheritance.SelectMany( c => c._inheritance )
.Where( c => !ReferenceEquals( this, c ) )
.Distinct() )
{
yield return collection;
}
}
public bool AddInheritance( ModCollection2 collection )
{
if( ReferenceEquals( collection, this ) || _inheritance.Contains( collection ) )
{
return false;
}
_inheritance.Add( collection );
InheritanceChanged.Invoke();
return true;
}
public void RemoveInheritance( int idx )
{
_inheritance.RemoveAt( idx );
InheritanceChanged.Invoke();
}
public void MoveInheritance( int from, int to )
{
if( _inheritance.Move( from, to ) )
{
InheritanceChanged.Invoke();
}
}
public (ModSettings? Settings, ModCollection2 Collection) this[ int idx ]
{
get
{
foreach( var collection in GetFlattenedInheritance() )
{
var settings = _settings[ idx ];
if( settings != null )
{
return ( settings, collection );
}
}
return ( null, this );
}
}
}

View file

@ -1,47 +0,0 @@
using System.Linq;
using Penumbra.Mod;
namespace Penumbra.Mods;
public partial class ModCollection2
{
private static class Migration
{
public static void Migrate( ModCollection2 collection )
{
var changes = MigrateV0ToV1( collection );
if( changes )
{
collection.Save();
}
}
private static bool MigrateV0ToV1( ModCollection2 collection )
{
if( collection.Version > 0 )
{
return false;
}
collection.Version = 1;
for( var i = 0; i < collection._settings.Count; ++i )
{
var setting = collection._settings[ i ];
if( SettingIsDefaultV0( collection._settings[ i ] ) )
{
collection._settings[ i ] = null;
}
}
foreach( var (key, _) in collection._unusedSettings.Where( kvp => SettingIsDefaultV0( kvp.Value ) ).ToList() )
{
collection._unusedSettings.Remove( key );
}
return true;
}
private static bool SettingIsDefaultV0( ModSettings? setting )
=> setting is { Enabled: false, Priority: 0 } && setting.Settings.Values.All( s => s == 0 );
}
}

View file

@ -1,523 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Dalamud.Logging;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manager;
using Penumbra.Mod;
using Penumbra.Util;
namespace Penumbra.Mods;
public partial class ModCollection2
{
public const int CurrentVersion = 1;
public const string DefaultCollection = "Default";
public string Name { get; private init; }
public int Version { get; private set; }
private readonly List< ModSettings? > _settings;
public IReadOnlyList< ModSettings? > Settings
=> _settings;
public IEnumerable< ModSettings? > ActualSettings
=> Enumerable.Range( 0, _settings.Count ).Select( i => this[ i ].Settings );
private readonly Dictionary< string, ModSettings > _unusedSettings;
private ModCollection2( string name, ModCollection2 duplicate )
{
Name = name;
Version = duplicate.Version;
_settings = duplicate._settings.ConvertAll( s => s?.DeepCopy() );
_unusedSettings = duplicate._unusedSettings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.DeepCopy() );
_inheritance = duplicate._inheritance.ToList();
ModSettingChanged += SaveOnChange;
InheritanceChanged += Save;
}
private ModCollection2( string name, int version, Dictionary< string, ModSettings > allSettings )
{
Name = name;
Version = version;
_unusedSettings = allSettings;
_settings = Enumerable.Repeat( ( ModSettings? )null, Penumbra.ModManager.Count ).ToList();
for( var i = 0; i < Penumbra.ModManager.Count; ++i )
{
var modName = Penumbra.ModManager[ i ].BasePath.Name;
if( _unusedSettings.TryGetValue( Penumbra.ModManager[ i ].BasePath.Name, out var settings ) )
{
_unusedSettings.Remove( modName );
_settings[ i ] = settings;
}
}
Migration.Migrate( this );
ModSettingChanged += SaveOnChange;
InheritanceChanged += Save;
}
public static ModCollection2 CreateNewEmpty( string name )
=> new(name, CurrentVersion, new Dictionary< string, ModSettings >());
public ModCollection2 Duplicate( string name )
=> new(name, this);
private void CleanUnavailableSettings()
{
var any = _unusedSettings.Count > 0;
_unusedSettings.Clear();
if( any )
{
Save();
}
}
public void AddMod( ModData mod )
{
if( _unusedSettings.TryGetValue( mod.BasePath.Name, out var settings ) )
{
_settings.Add( settings );
_unusedSettings.Remove( mod.BasePath.Name );
}
else
{
_settings.Add( null );
}
}
public void RemoveMod( ModData mod, int idx )
{
var settings = _settings[ idx ];
if( settings != null )
{
_unusedSettings.Add( mod.BasePath.Name, settings );
}
_settings.RemoveAt( idx );
}
public static string CollectionDirectory
=> Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(), "collections" );
private FileInfo FileName
=> new(Path.Combine( CollectionDirectory, $"{Name.RemoveInvalidPathSymbols()}.json" ));
public void Save()
{
try
{
var file = FileName;
file.Directory?.Create();
using var s = file.Open( FileMode.Truncate );
using var w = new StreamWriter( s, Encoding.UTF8 );
using var j = new JsonTextWriter( w );
j.Formatting = Formatting.Indented;
var x = JsonSerializer.Create( new JsonSerializerSettings { Formatting = Formatting.Indented } );
j.WriteStartObject();
j.WritePropertyName( nameof( Version ) );
j.WriteValue( Version );
j.WritePropertyName( nameof( Name ) );
j.WriteValue( Name );
j.WritePropertyName( nameof( Settings ) );
j.WriteStartObject();
for( var i = 0; i < _settings.Count; ++i )
{
var settings = _settings[ i ];
if( settings != null )
{
j.WritePropertyName( Penumbra.ModManager[ i ].BasePath.Name );
x.Serialize( j, settings );
}
}
foreach( var settings in _unusedSettings )
{
j.WritePropertyName( settings.Key );
x.Serialize( j, settings.Value );
}
j.WriteEndObject();
j.WritePropertyName( nameof( Inheritance ) );
x.Serialize( j, Inheritance );
j.WriteEndObject();
}
catch( Exception e )
{
PluginLog.Error( $"Could not save collection {Name}:\n{e}" );
}
}
public void Delete()
{
var file = FileName;
if( file.Exists )
{
try
{
file.Delete();
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete collection file {file.FullName} for {Name}:\n{e}" );
}
}
}
public static ModCollection2? LoadFromFile( FileInfo file, out IReadOnlyList< string > inheritance )
{
inheritance = Array.Empty< string >();
if( !file.Exists )
{
PluginLog.Error( $"Could not read collection because {file.FullName} does not exist." );
return null;
}
try
{
var obj = JObject.Parse( File.ReadAllText( file.FullName ) );
var name = obj[ nameof( Name ) ]?.ToObject< string >() ?? string.Empty;
var version = obj[ nameof( Version ) ]?.ToObject< int >() ?? 0;
var settings = obj[ nameof( Settings ) ]?.ToObject< Dictionary< string, ModSettings > >()
?? new Dictionary< string, ModSettings >();
inheritance = obj[ nameof( Inheritance ) ]?.ToObject< List< string > >() ?? ( IReadOnlyList< string > )Array.Empty< string >();
return new ModCollection2( name, version, settings );
}
catch( Exception e )
{
PluginLog.Error( $"Could not read collection information from {file.FullName}:\n{e}" );
}
return null;
}
}
// A ModCollection is a named set of ModSettings to all of the users' installed mods.
// It is meant to be local only, and thus should always contain settings for every mod, not just the enabled ones.
// Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made.
// Active ModCollections build a cache of currently relevant data.
public class ModCollection
{
public const string DefaultCollection = "Default";
public string Name { get; set; }
public Dictionary< string, ModSettings > Settings { get; }
public ModCollection()
{
Name = DefaultCollection;
Settings = new Dictionary< string, ModSettings >();
}
public ModCollection( string name, Dictionary< string, ModSettings > settings )
{
Name = name;
Settings = settings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.DeepCopy() );
}
public Mod.Mod GetMod( ModData mod )
{
if( Cache != null && Cache.AvailableMods.TryGetValue( mod.BasePath.Name, out var ret ) )
{
return ret;
}
if( Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
{
return new Mod.Mod( settings, mod );
}
var newSettings = ModSettings.DefaultSettings( mod.Meta );
Settings.Add( mod.BasePath.Name, newSettings );
Save();
return new Mod.Mod( newSettings, mod );
}
private bool CleanUnavailableSettings( Dictionary< string, ModData > data )
{
var removeList = Settings.Where( settingKvp => !data.ContainsKey( settingKvp.Key ) ).ToArray();
foreach( var s in removeList )
{
Settings.Remove( s.Key );
}
return removeList.Length > 0;
}
public void CreateCache( IEnumerable< ModData > data )
{
Cache = new ModCollectionCache( this );
var changedSettings = false;
foreach( var mod in data )
{
if( Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
{
Cache.AddMod( settings, mod, false );
}
else
{
changedSettings = true;
var newSettings = ModSettings.DefaultSettings( mod.Meta );
Settings.Add( mod.BasePath.Name, newSettings );
Cache.AddMod( newSettings, mod, false );
}
}
if( changedSettings )
{
Save();
}
CalculateEffectiveFileList( true, false );
}
public void ClearCache()
=> Cache = null;
public void UpdateSetting( DirectoryInfo modPath, ModMeta meta, bool clear )
{
if( !Settings.TryGetValue( modPath.Name, out var settings ) )
{
return;
}
if( clear )
{
settings.Settings.Clear();
}
if( settings.FixInvalidSettings( meta ) )
{
Save();
}
}
public void UpdateSetting( ModData mod )
=> UpdateSetting( mod.BasePath, mod.Meta, false );
public void UpdateSettings( bool forceSave )
{
if( Cache == null )
{
return;
}
var changes = false;
foreach( var mod in Cache.AvailableMods.Values )
{
changes |= mod.FixSettings();
}
if( forceSave || changes )
{
Save();
}
}
public void CalculateEffectiveFileList( bool withMetaManipulations, bool reloadResident )
{
PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}]", Name, withMetaManipulations );
Cache ??= new ModCollectionCache( this );
UpdateSettings( false );
Cache.CalculateEffectiveFileList();
if( withMetaManipulations )
{
Cache.UpdateMetaManipulations();
}
if( reloadResident )
{
Penumbra.ResidentResources.Reload();
}
}
[JsonIgnore]
public ModCollectionCache? Cache { get; private set; }
public static ModCollection? LoadFromFile( FileInfo file )
{
if( !file.Exists )
{
PluginLog.Error( $"Could not read collection because {file.FullName} does not exist." );
return null;
}
try
{
var collection = JsonConvert.DeserializeObject< ModCollection >( File.ReadAllText( file.FullName ) );
return collection;
}
catch( Exception e )
{
PluginLog.Error( $"Could not read collection information from {file.FullName}:\n{e}" );
}
return null;
}
private void SaveToFile( FileInfo file )
{
try
{
File.WriteAllText( file.FullName, JsonConvert.SerializeObject( this, Formatting.Indented ) );
}
catch( Exception e )
{
PluginLog.Error( $"Could not write collection {Name} to {file.FullName}:\n{e}" );
}
}
public static DirectoryInfo CollectionDir()
=> new(Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(), "collections" ));
private static FileInfo FileName( DirectoryInfo collectionDir, string name )
=> new(Path.Combine( collectionDir.FullName, $"{name.RemoveInvalidPathSymbols()}.json" ));
public FileInfo FileName()
=> new(Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(),
$"{Name.RemoveInvalidPathSymbols()}.json" ));
public void Save()
{
try
{
var dir = CollectionDir();
dir.Create();
var file = FileName( dir, Name );
SaveToFile( file );
}
catch( Exception e )
{
PluginLog.Error( $"Could not save collection {Name}:\n{e}" );
}
}
public static ModCollection? Load( string name )
{
var file = FileName( CollectionDir(), name );
return file.Exists ? LoadFromFile( file ) : null;
}
public void Delete()
{
var file = FileName( CollectionDir(), Name );
if( file.Exists )
{
try
{
file.Delete();
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete collection file {file} for {Name}:\n{e}" );
}
}
}
public void AddMod( ModData data )
{
if( Cache == null )
{
return;
}
Cache.AddMod( Settings.TryGetValue( data.BasePath.Name, out var settings )
? settings
: ModSettings.DefaultSettings( data.Meta ),
data );
}
public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
=> Cache?.ResolveSwappedOrReplacementPath( gameResourcePath );
[Conditional( "USE_EQP" )]
public void SetEqpFiles()
{
if( Cache == null )
{
MetaManager.MetaManagerEqp.ResetFiles();
}
else
{
Cache.MetaManipulations.Eqp.SetFiles();
}
}
[Conditional( "USE_EQDP" )]
public void SetEqdpFiles()
{
if( Cache == null )
{
MetaManager.MetaManagerEqdp.ResetFiles();
}
else
{
Cache.MetaManipulations.Eqdp.SetFiles();
}
}
[Conditional( "USE_GMP" )]
public void SetGmpFiles()
{
if( Cache == null )
{
MetaManager.MetaManagerGmp.ResetFiles();
}
else
{
Cache.MetaManipulations.Gmp.SetFiles();
}
}
[Conditional( "USE_EST" )]
public void SetEstFiles()
{
if( Cache == null )
{
MetaManager.MetaManagerEst.ResetFiles();
}
else
{
Cache.MetaManipulations.Est.SetFiles();
}
}
[Conditional( "USE_CMP" )]
public void SetCmpFiles()
{
if( Cache == null )
{
MetaManager.MetaManagerCmp.ResetFiles();
}
else
{
Cache.MetaManipulations.Cmp.SetFiles();
}
}
public void SetFiles()
{
if( Cache == null )
{
Penumbra.CharacterUtility.ResetAll();
}
else
{
Cache.MetaManipulations.SetFiles();
}
}
public static readonly ModCollection Empty = new() { Name = "" };
}

View file

@ -1,662 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manager;
using Penumbra.Mod;
using Penumbra.Util;
namespace Penumbra.Mods;
// The ModCollectionCache contains all required temporary data to use a collection.
// It will only be setup if a collection gets activated in any way.
public class ModCollectionCache2
{
// Shared caches to avoid allocations.
private static readonly BitArray FileSeen = new(256);
private static readonly Dictionary< Utf8GamePath, int > RegisteredFiles = new(256);
private static readonly List< ModSettings? > ResolvedSettings = new(128);
private readonly ModCollection2 _collection;
private readonly SortedList< string, object? > _changedItems = new();
public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new();
public readonly HashSet< FullPath > MissingFiles = new();
public readonly MetaManager MetaManipulations;
private ModCache2 _cache;
public IReadOnlyDictionary< string, object? > ChangedItems
{
get
{
SetChangedItems();
return _changedItems;
}
}
public ModCollectionCache2( ModCollection2 collection )
=> _collection = collection;
//MetaManipulations = new MetaManager( collection );
private static void ResetFileSeen( int size )
{
if( size < FileSeen.Length )
{
FileSeen.Length = size;
FileSeen.SetAll( false );
}
else
{
FileSeen.SetAll( false );
FileSeen.Length = size;
}
}
private void ClearStorageAndPrepare()
{
ResolvedFiles.Clear();
MissingFiles.Clear();
RegisteredFiles.Clear();
_changedItems.Clear();
_cache.ClearFileConflicts();
ResolvedSettings.Clear();
ResolvedSettings.AddRange( _collection.ActualSettings );
}
public void CalculateEffectiveFileList()
{
ClearStorageAndPrepare();
for( var i = 0; i < Penumbra.ModManager.Mods.Count; ++i )
{
if( ResolvedSettings[ i ]?.Enabled == true )
{
AddFiles( i );
AddSwaps( i );
}
}
AddMetaFiles();
}
private void SetChangedItems()
{
if( _changedItems.Count > 0 || ResolvedFiles.Count + MetaManipulations.Count == 0 )
{
return;
}
try
{
// Skip IMCs because they would result in far too many false-positive items,
// since they are per set instead of per item-slot/item/variant.
var identifier = GameData.GameData.GetIdentifier();
foreach( var resolved in ResolvedFiles.Keys.Where( file => !file.Path.EndsWith( 'i', 'm', 'c' ) ) )
{
identifier.Identify( _changedItems, resolved.ToGamePath() );
}
}
catch( Exception e )
{
PluginLog.Error( $"Unknown Error:\n{e}" );
}
}
private void AddFiles( int idx )
{
var mod = Penumbra.ModManager.Mods[ idx ];
ResetFileSeen( mod.Resources.ModFiles.Count );
// Iterate in reverse so that later groups take precedence before earlier ones.
foreach( var group in mod.Meta.Groups.Values.Reverse() )
{
switch( group.SelectionType )
{
case SelectType.Single:
AddFilesForSingle( group, mod, idx );
break;
case SelectType.Multi:
AddFilesForMulti( group, mod, idx );
break;
default: throw new InvalidEnumArgumentException();
}
}
AddRemainingFiles( mod, idx );
}
private static bool FilterFile( Utf8GamePath gamePath )
{
// If audio streaming is not disabled, replacing .scd files crashes the game,
// so only add those files if it is disabled.
if( !Penumbra.Config.DisableSoundStreaming
&& gamePath.Path.EndsWith( '.', 's', 'c', 'd' ) )
{
return true;
}
return false;
}
private void AddFile( int modIdx, Utf8GamePath gamePath, FullPath file )
{
if( FilterFile( gamePath ) )
{
return;
}
if( !RegisteredFiles.TryGetValue( gamePath, out var oldModIdx ) )
{
RegisteredFiles.Add( gamePath, modIdx );
ResolvedFiles[ gamePath ] = file;
}
else
{
var priority = ResolvedSettings[ modIdx ]!.Priority;
var oldPriority = ResolvedSettings[ oldModIdx ]!.Priority;
_cache.AddConflict( oldModIdx, modIdx, oldPriority, priority, gamePath );
if( priority > oldPriority )
{
ResolvedFiles[ gamePath ] = file;
RegisteredFiles[ gamePath ] = modIdx;
}
}
}
private void AddMissingFile( FullPath file )
{
switch( file.Extension.ToLowerInvariant() )
{
case ".meta":
case ".rgsp":
return;
default:
MissingFiles.Add( file );
return;
}
}
private void AddPathsForOption( Option option, ModData mod, int modIdx, bool enabled )
{
foreach( var (file, paths) in option.OptionFiles )
{
var fullPath = new FullPath( mod.BasePath, file );
var idx = mod.Resources.ModFiles.IndexOf( f => f.Equals( fullPath ) );
if( idx < 0 )
{
AddMissingFile( fullPath );
continue;
}
var registeredFile = mod.Resources.ModFiles[ idx ];
if( !registeredFile.Exists )
{
AddMissingFile( registeredFile );
continue;
}
FileSeen.Set( idx, true );
if( enabled )
{
foreach( var path in paths )
{
AddFile( modIdx, path, registeredFile );
}
}
}
}
private void AddFilesForSingle( OptionGroup singleGroup, ModData mod, int modIdx )
{
Debug.Assert( singleGroup.SelectionType == SelectType.Single );
var settings = ResolvedSettings[ modIdx ]!;
if( !settings.Settings.TryGetValue( singleGroup.GroupName, out var setting ) )
{
setting = 0;
}
for( var i = 0; i < singleGroup.Options.Count; ++i )
{
AddPathsForOption( singleGroup.Options[ i ], mod, modIdx, setting == i );
}
}
private void AddFilesForMulti( OptionGroup multiGroup, ModData mod, int modIdx )
{
Debug.Assert( multiGroup.SelectionType == SelectType.Multi );
var settings = ResolvedSettings[ modIdx ]!;
if( !settings.Settings.TryGetValue( multiGroup.GroupName, out var setting ) )
{
return;
}
// Also iterate options in reverse so that later options take precedence before earlier ones.
for( var i = multiGroup.Options.Count - 1; i >= 0; --i )
{
AddPathsForOption( multiGroup.Options[ i ], mod, modIdx, ( setting & ( 1 << i ) ) != 0 );
}
}
private void AddRemainingFiles( ModData mod, int modIdx )
{
for( var i = 0; i < mod.Resources.ModFiles.Count; ++i )
{
if( FileSeen.Get( i ) )
{
continue;
}
var file = mod.Resources.ModFiles[ i ];
if( file.Exists )
{
if( file.ToGamePath( mod.BasePath, out var gamePath ) )
{
AddFile( modIdx, gamePath, file );
}
else
{
PluginLog.Warning( $"Could not convert {file} in {mod.BasePath.FullName} to GamePath." );
}
}
else
{
MissingFiles.Add( file );
}
}
}
private void AddMetaFiles()
=> MetaManipulations.Imc.SetFiles();
private void AddSwaps( int modIdx )
{
var mod = Penumbra.ModManager.Mods[ modIdx ];
foreach( var (gamePath, swapPath) in mod.Meta.FileSwaps.Where( kvp => !FilterFile( kvp.Key ) ) )
{
AddFile( modIdx, gamePath, swapPath );
}
}
// TODO Manipulations
public FullPath? GetCandidateForGameFile( Utf8GamePath gameResourcePath )
{
if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) )
{
return null;
}
if( candidate.InternalName.Length > Utf8GamePath.MaxGamePathLength
|| candidate.IsRooted && !candidate.Exists )
{
return null;
}
return candidate;
}
public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
=> GetCandidateForGameFile( gameResourcePath );
}
// The ModCollectionCache contains all required temporary data to use a collection.
// It will only be setup if a collection gets activated in any way.
public class ModCollectionCache
{
// Shared caches to avoid allocations.
private static readonly BitArray FileSeen = new(256);
private static readonly Dictionary< Utf8GamePath, Mod.Mod > RegisteredFiles = new(256);
public readonly Dictionary< string, Mod.Mod > AvailableMods = new();
private readonly SortedList< string, object? > _changedItems = new();
public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new();
public readonly HashSet< FullPath > MissingFiles = new();
public readonly MetaManager MetaManipulations;
public IReadOnlyDictionary< string, object? > ChangedItems
{
get
{
SetChangedItems();
return _changedItems;
}
}
public ModCollectionCache( ModCollection collection )
=> MetaManipulations = new MetaManager( collection );
private static void ResetFileSeen( int size )
{
if( size < FileSeen.Length )
{
FileSeen.Length = size;
FileSeen.SetAll( false );
}
else
{
FileSeen.SetAll( false );
FileSeen.Length = size;
}
}
public void CalculateEffectiveFileList()
{
ResolvedFiles.Clear();
MissingFiles.Clear();
RegisteredFiles.Clear();
_changedItems.Clear();
foreach( var mod in AvailableMods.Values
.Where( m => m.Settings.Enabled )
.OrderByDescending( m => m.Settings.Priority ) )
{
mod.Cache.ClearFileConflicts();
AddFiles( mod );
AddSwaps( mod );
}
AddMetaFiles();
}
private void SetChangedItems()
{
if( _changedItems.Count > 0 || ResolvedFiles.Count + MetaManipulations.Count == 0 )
{
return;
}
try
{
// Skip IMCs because they would result in far too many false-positive items,
// since they are per set instead of per item-slot/item/variant.
var identifier = GameData.GameData.GetIdentifier();
foreach( var resolved in ResolvedFiles.Keys.Where( file => !file.Path.EndsWith( 'i', 'm', 'c' ) ) )
{
identifier.Identify( _changedItems, resolved.ToGamePath() );
}
}
catch( Exception e )
{
PluginLog.Error( $"Unknown Error:\n{e}" );
}
}
private void AddFiles( Mod.Mod mod )
{
ResetFileSeen( mod.Data.Resources.ModFiles.Count );
// Iterate in reverse so that later groups take precedence before earlier ones.
foreach( var group in mod.Data.Meta.Groups.Values.Reverse() )
{
switch( group.SelectionType )
{
case SelectType.Single:
AddFilesForSingle( group, mod );
break;
case SelectType.Multi:
AddFilesForMulti( group, mod );
break;
default: throw new InvalidEnumArgumentException();
}
}
AddRemainingFiles( mod );
}
private static bool FilterFile( Utf8GamePath gamePath )
{
// If audio streaming is not disabled, replacing .scd files crashes the game,
// so only add those files if it is disabled.
if( !Penumbra.Config.DisableSoundStreaming
&& gamePath.Path.EndsWith( '.', 's', 'c', 'd' ) )
{
return true;
}
return false;
}
private void AddFile( Mod.Mod mod, Utf8GamePath gamePath, FullPath file )
{
if( FilterFile( gamePath ) )
{
return;
}
if( !RegisteredFiles.TryGetValue( gamePath, out var oldMod ) )
{
RegisteredFiles.Add( gamePath, mod );
ResolvedFiles[ gamePath ] = file;
}
else
{
mod.Cache.AddConflict( oldMod, gamePath );
if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod.Settings.Priority )
{
oldMod.Cache.AddConflict( mod, gamePath );
}
}
}
private void AddMissingFile( FullPath file )
{
switch( file.Extension.ToLowerInvariant() )
{
case ".meta":
case ".rgsp":
return;
default:
MissingFiles.Add( file );
return;
}
}
private void AddPathsForOption( Option option, Mod.Mod mod, bool enabled )
{
foreach( var (file, paths) in option.OptionFiles )
{
var fullPath = new FullPath( mod.Data.BasePath, file );
var idx = mod.Data.Resources.ModFiles.IndexOf( f => f.Equals( fullPath ) );
if( idx < 0 )
{
AddMissingFile( fullPath );
continue;
}
var registeredFile = mod.Data.Resources.ModFiles[ idx ];
if( !registeredFile.Exists )
{
AddMissingFile( registeredFile );
continue;
}
FileSeen.Set( idx, true );
if( enabled )
{
foreach( var path in paths )
{
AddFile( mod, path, registeredFile );
}
}
}
}
private void AddFilesForSingle( OptionGroup singleGroup, Mod.Mod mod )
{
Debug.Assert( singleGroup.SelectionType == SelectType.Single );
if( !mod.Settings.Settings.TryGetValue( singleGroup.GroupName, out var setting ) )
{
setting = 0;
}
for( var i = 0; i < singleGroup.Options.Count; ++i )
{
AddPathsForOption( singleGroup.Options[ i ], mod, setting == i );
}
}
private void AddFilesForMulti( OptionGroup multiGroup, Mod.Mod mod )
{
Debug.Assert( multiGroup.SelectionType == SelectType.Multi );
if( !mod.Settings.Settings.TryGetValue( multiGroup.GroupName, out var setting ) )
{
return;
}
// Also iterate options in reverse so that later options take precedence before earlier ones.
for( var i = multiGroup.Options.Count - 1; i >= 0; --i )
{
AddPathsForOption( multiGroup.Options[ i ], mod, ( setting & ( 1 << i ) ) != 0 );
}
}
private void AddRemainingFiles( Mod.Mod mod )
{
for( var i = 0; i < mod.Data.Resources.ModFiles.Count; ++i )
{
if( FileSeen.Get( i ) )
{
continue;
}
var file = mod.Data.Resources.ModFiles[ i ];
if( file.Exists )
{
if( file.ToGamePath( mod.Data.BasePath, out var gamePath ) )
{
AddFile( mod, gamePath, file );
}
else
{
PluginLog.Warning( $"Could not convert {file} in {mod.Data.BasePath.FullName} to GamePath." );
}
}
else
{
MissingFiles.Add( file );
}
}
}
private void AddMetaFiles()
=> MetaManipulations.Imc.SetFiles();
private void AddSwaps( Mod.Mod mod )
{
foreach( var (key, value) in mod.Data.Meta.FileSwaps.Where( kvp => !FilterFile( kvp.Key ) ) )
{
if( !RegisteredFiles.TryGetValue( key, out var oldMod ) )
{
RegisteredFiles.Add( key, mod );
ResolvedFiles.Add( key, value );
}
else
{
mod.Cache.AddConflict( oldMod, key );
if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod.Settings.Priority )
{
oldMod.Cache.AddConflict( mod, key );
}
}
}
}
private void AddManipulations( Mod.Mod mod )
{
foreach( var manip in mod.Data.Resources.MetaManipulations.GetManipulationsForConfig( mod.Settings, mod.Data.Meta ) )
{
if( !MetaManipulations.TryGetValue( manip, out var oldMod ) )
{
MetaManipulations.ApplyMod( manip, mod );
}
else
{
mod.Cache.AddConflict( oldMod!, manip );
if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod!.Settings.Priority )
{
oldMod.Cache.AddConflict( mod, manip );
}
}
}
}
public void UpdateMetaManipulations()
{
MetaManipulations.Reset();
foreach( var mod in AvailableMods.Values.Where( m => m.Settings.Enabled && m.Data.Resources.MetaManipulations.Count > 0 ) )
{
mod.Cache.ClearMetaConflicts();
AddManipulations( mod );
}
}
public void RemoveMod( DirectoryInfo basePath )
{
if( !AvailableMods.TryGetValue( basePath.Name, out var mod ) )
{
return;
}
AvailableMods.Remove( basePath.Name );
if( !mod.Settings.Enabled )
{
return;
}
CalculateEffectiveFileList();
if( mod.Data.Resources.MetaManipulations.Count > 0 )
{
UpdateMetaManipulations();
}
}
public void AddMod( ModSettings settings, ModData data, bool updateFileList = true )
{
if( AvailableMods.ContainsKey( data.BasePath.Name ) )
{
return;
}
AvailableMods[ data.BasePath.Name ] = new Mod.Mod( settings, data );
if( !updateFileList || !settings.Enabled )
{
return;
}
CalculateEffectiveFileList();
if( data.Resources.MetaManipulations.Count > 0 )
{
UpdateMetaManipulations();
}
}
public FullPath? GetCandidateForGameFile( Utf8GamePath gameResourcePath )
{
if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) )
{
return null;
}
if( candidate.InternalName.Length > Utf8GamePath.MaxGamePathLength
|| candidate.IsRooted && !candidate.Exists )
{
return null;
}
return candidate;
}
public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
=> GetCandidateForGameFile( gameResourcePath );
}

View file

@ -263,7 +263,7 @@ public class ModManager : IEnumerable< ModData >
mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) );
}
Penumbra.CollectionManager.UpdateCollections( mod, metaChanges, fileChanges, nameChange, reloadMeta ); // TODO
// TODO: more specific mod changes?
ModChange?.Invoke( ModChangeType.Changed, idx, mod );
return true;
}
@ -271,10 +271,6 @@ public class ModManager : IEnumerable< ModData >
public bool UpdateMod( ModData mod, bool reloadMeta = false, bool recomputeMeta = false, bool force = false )
=> UpdateMod( Mods.IndexOf( mod ), reloadMeta, recomputeMeta, force );
public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
{
var ret = Penumbra.CollectionManager.DefaultCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
ret ??= Penumbra.CollectionManager.ForcedCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
return ret;
}
public static FullPath? ResolvePath( Utf8GamePath gameResourcePath )
=> Penumbra.CollectionManager.Default.ResolvePath( gameResourcePath );
}

View file

@ -4,6 +4,7 @@ using System.ComponentModel;
using System.IO;
using Dalamud.Logging;
using Penumbra.Mod;
using Penumbra.Util;
namespace Penumbra.Mods;
@ -82,20 +83,13 @@ public static class ModManagerEditExtensions
manager.Config.Save();
}
foreach( var collection in Penumbra.CollectionManager.Collections )
var idx = manager.Mods.IndexOf( mod );
foreach( var collection in Penumbra.CollectionManager )
{
if( collection.Settings.TryGetValue( oldBasePath.Name, out var settings ) )
if( collection.Settings[ idx ] != null )
{
collection.Settings[ newDir.Name ] = settings;
collection.Settings.Remove( oldBasePath.Name );
collection.Save();
}
if( collection.Cache != null )
{
collection.Cache.RemoveMod( newDir );
collection.AddMod( mod );
}
}
return true;
@ -140,9 +134,13 @@ public static class ModManagerEditExtensions
mod.SaveMeta();
foreach( var collection in Penumbra.CollectionManager.Collections )
// TODO to indices
var idx = Penumbra.ModManager.Mods.IndexOf( mod );
foreach( var collection in Penumbra.CollectionManager )
{
if( !collection.Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
var settings = collection.Settings[ idx ];
if( settings == null )
{
continue;
}
@ -176,9 +174,11 @@ public static class ModManagerEditExtensions
return ( oldSetting & bitmaskFront ) | ( ( oldSetting & bitmaskBack ) >> 1 );
}
foreach( var collection in Penumbra.CollectionManager.Collections )
var idx = Penumbra.ModManager.Mods.IndexOf( mod ); // TODO
foreach( var collection in Penumbra.CollectionManager )
{
if( !collection.Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
var settings = collection.Settings[ idx ];
if( settings == null )
{
continue;
}
@ -199,10 +199,10 @@ public static class ModManagerEditExtensions
{
settings.Settings[ group.GroupName ] = newSetting;
collection.Save();
if( collection.Cache != null && settings.Enabled )
if( collection.HasCache && settings.Enabled )
{
collection.CalculateEffectiveFileList( mod.Resources.MetaManipulations.Count > 0,
Penumbra.CollectionManager.IsActive( collection ) );
Penumbra.CollectionManager.Default == collection );
}
}
}