mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-23 16:27:47 +01:00
tmp
This commit is contained in:
parent
bc47e08e08
commit
9a0b0bfa0f
35 changed files with 1365 additions and 1997 deletions
261
Penumbra/Collections/CollectionManager.Active.cs
Normal file
261
Penumbra/Collections/CollectionManager.Active.cs
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.Meta.Manager;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
public sealed partial class CollectionManager2
|
||||
{
|
||||
// Is invoked after the collections actually changed.
|
||||
public event CollectionChangeDelegate? CollectionChanged;
|
||||
|
||||
private int _currentIdx = -1;
|
||||
private int _defaultIdx = -1;
|
||||
private int _defaultNameIdx = 0;
|
||||
|
||||
public ModCollection2 Current
|
||||
=> this[ _currentIdx ];
|
||||
|
||||
public ModCollection2 Default
|
||||
=> this[ _defaultIdx ];
|
||||
|
||||
private readonly Dictionary< string, int > _character = new();
|
||||
|
||||
public ModCollection2 Character( string name )
|
||||
=> _character.TryGetValue( name, out var idx ) ? _collections[ idx ] : Default;
|
||||
|
||||
public bool HasCharacterCollections
|
||||
=> _character.Count > 0;
|
||||
|
||||
private void OnModChanged( ModChangeType type, int idx, ModData mod )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case ModChangeType.Added:
|
||||
foreach( var collection in _collections )
|
||||
{
|
||||
collection.AddMod( mod );
|
||||
}
|
||||
|
||||
foreach( var collection in _collections.Where( c => c.HasCache && c[ ^1 ].Settings?.Enabled == true ) )
|
||||
{
|
||||
collection.UpdateCache();
|
||||
}
|
||||
|
||||
break;
|
||||
case ModChangeType.Removed:
|
||||
var list = new List< ModSettings? >( _collections.Count );
|
||||
foreach( var collection in _collections )
|
||||
{
|
||||
list.Add( collection[ idx ].Settings );
|
||||
collection.RemoveMod( mod, idx );
|
||||
}
|
||||
|
||||
foreach( var (collection, _) in _collections.Zip( list ).Where( c => c.First.HasCache && c.Second?.Enabled == true ) )
|
||||
{
|
||||
collection.UpdateCache();
|
||||
}
|
||||
|
||||
break;
|
||||
case ModChangeType.Changed:
|
||||
foreach( var collection in _collections.Where(
|
||||
collection => collection.Settings[ idx ]?.FixInvalidSettings( mod.Meta ) ?? false ) )
|
||||
{
|
||||
collection.Save();
|
||||
}
|
||||
|
||||
foreach( var collection in _collections.Where( c => c.HasCache && c[ idx ].Settings?.Enabled == true ) )
|
||||
{
|
||||
collection.UpdateCache();
|
||||
}
|
||||
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateNecessaryCaches()
|
||||
{
|
||||
if( _defaultIdx >= 0 )
|
||||
{
|
||||
Default.CreateCache();
|
||||
}
|
||||
|
||||
if( _currentIdx >= 0 )
|
||||
{
|
||||
Current.CreateCache();
|
||||
}
|
||||
|
||||
foreach( var idx in _character.Values.Where( i => i >= 0 ) )
|
||||
{
|
||||
_collections[ idx ].CreateCache();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCaches()
|
||||
{
|
||||
foreach( var collection in _collections )
|
||||
{
|
||||
collection.UpdateCache();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveCache( int idx )
|
||||
{
|
||||
if( idx != _defaultIdx && idx != _currentIdx && _character.All( kvp => kvp.Value != idx ) )
|
||||
{
|
||||
_collections[ idx ].ClearCache();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCollection( string name, CollectionType type, string? characterName = null )
|
||||
=> SetCollection( GetIndexForCollectionName( name ), type, characterName );
|
||||
|
||||
public void SetCollection( ModCollection2 collection, CollectionType type, string? characterName = null )
|
||||
=> SetCollection( GetIndexForCollectionName( collection.Name ), type, characterName );
|
||||
|
||||
public void SetCollection( int newIdx, CollectionType type, string? characterName = null )
|
||||
{
|
||||
var oldCollectionIdx = type switch
|
||||
{
|
||||
CollectionType.Default => _defaultIdx,
|
||||
CollectionType.Current => _currentIdx,
|
||||
CollectionType.Character => characterName?.Length > 0
|
||||
? _character.TryGetValue( characterName, out var c )
|
||||
? c
|
||||
: _defaultIdx
|
||||
: -2,
|
||||
_ => -2,
|
||||
};
|
||||
|
||||
if( oldCollectionIdx == -2 || newIdx == oldCollectionIdx )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newCollection = this[ newIdx ];
|
||||
if( newIdx >= 0 )
|
||||
{
|
||||
newCollection.CreateCache();
|
||||
}
|
||||
|
||||
RemoveCache( oldCollectionIdx );
|
||||
switch( type )
|
||||
{
|
||||
case CollectionType.Default:
|
||||
_defaultIdx = newIdx;
|
||||
Penumbra.Config.DefaultCollection = newCollection.Name;
|
||||
Penumbra.ResidentResources.Reload();
|
||||
Default.SetFiles();
|
||||
break;
|
||||
case CollectionType.Current:
|
||||
_currentIdx = newIdx;
|
||||
Penumbra.Config.CurrentCollection = newCollection.Name;
|
||||
break;
|
||||
case CollectionType.Character:
|
||||
_character[ characterName! ] = newIdx;
|
||||
Penumbra.Config.CharacterCollections[ characterName! ] = newCollection.Name;
|
||||
break;
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke( this[ oldCollectionIdx ], newCollection, type, characterName );
|
||||
Penumbra.Config.Save();
|
||||
}
|
||||
|
||||
public bool CreateCharacterCollection( string characterName )
|
||||
{
|
||||
if( _character.ContainsKey( characterName ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_character[ characterName ] = -1;
|
||||
Penumbra.Config.CharacterCollections[ characterName ] = ModCollection2.Empty.Name;
|
||||
Penumbra.Config.Save();
|
||||
CollectionChanged?.Invoke( null, ModCollection2.Empty, CollectionType.Character, characterName );
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveCharacterCollection( string characterName )
|
||||
{
|
||||
if( _character.TryGetValue( characterName, out var collection ) )
|
||||
{
|
||||
RemoveCache( collection );
|
||||
_character.Remove( characterName );
|
||||
CollectionChanged?.Invoke( this[ collection ], null, CollectionType.Character, characterName );
|
||||
}
|
||||
|
||||
if( Penumbra.Config.CharacterCollections.Remove( characterName ) )
|
||||
{
|
||||
Penumbra.Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetIndexForCollectionName( string name )
|
||||
{
|
||||
if( name.Length == 0 || name == ModCollection2.DefaultCollection )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var idx = _collections.IndexOf( c => c.Name == Penumbra.Config.DefaultCollection );
|
||||
return idx < 0 ? -2 : idx;
|
||||
}
|
||||
|
||||
public void LoadCollections()
|
||||
{
|
||||
var configChanged = false;
|
||||
_defaultIdx = GetIndexForCollectionName( Penumbra.Config.DefaultCollection );
|
||||
if( _defaultIdx == -2 )
|
||||
{
|
||||
PluginLog.Error( $"Last choice of Default Collection {Penumbra.Config.DefaultCollection} is not available, reset to None." );
|
||||
_defaultIdx = -1;
|
||||
Penumbra.Config.DefaultCollection = this[ _defaultIdx ].Name;
|
||||
configChanged = true;
|
||||
}
|
||||
|
||||
_currentIdx = GetIndexForCollectionName( Penumbra.Config.CurrentCollection );
|
||||
if( _currentIdx == -2 )
|
||||
{
|
||||
PluginLog.Error( $"Last choice of Current Collection {Penumbra.Config.CurrentCollection} is not available, reset to Default." );
|
||||
_currentIdx = _defaultNameIdx;
|
||||
Penumbra.Config.DefaultCollection = this[ _currentIdx ].Name;
|
||||
configChanged = true;
|
||||
}
|
||||
|
||||
if( LoadCharacterCollections() || configChanged )
|
||||
{
|
||||
Penumbra.Config.Save();
|
||||
}
|
||||
|
||||
CreateNecessaryCaches();
|
||||
}
|
||||
|
||||
private bool LoadCharacterCollections()
|
||||
{
|
||||
var configChanged = false;
|
||||
foreach( var (player, collectionName) in Penumbra.Config.CharacterCollections.ToArray() )
|
||||
{
|
||||
var idx = GetIndexForCollectionName( collectionName );
|
||||
if( idx == -2 )
|
||||
{
|
||||
PluginLog.Error( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to None." );
|
||||
_character.Add( player, -1 );
|
||||
Penumbra.Config.CharacterCollections[ player ] = ModCollection2.Empty.Name;
|
||||
configChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_character.Add( player, idx );
|
||||
}
|
||||
}
|
||||
|
||||
return configChanged;
|
||||
}
|
||||
}
|
||||
223
Penumbra/Collections/CollectionManager.cs
Normal file
223
Penumbra/Collections/CollectionManager.cs
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
public enum CollectionType : byte
|
||||
{
|
||||
Inactive,
|
||||
Default,
|
||||
Character,
|
||||
Current,
|
||||
}
|
||||
|
||||
public sealed partial class CollectionManager2 : IDisposable, IEnumerable< ModCollection2 >
|
||||
{
|
||||
public delegate void CollectionChangeDelegate( ModCollection2? oldCollection, ModCollection2? newCollection, CollectionType type,
|
||||
string? characterName = null );
|
||||
|
||||
private readonly ModManager _modManager;
|
||||
|
||||
private readonly List< ModCollection2 > _collections = new();
|
||||
|
||||
public ModCollection2 this[ Index idx ]
|
||||
=> idx.Value == -1 ? ModCollection2.Empty : _collections[ idx ];
|
||||
|
||||
public ModCollection2? this[ string name ]
|
||||
=> ByName( name, out var c ) ? c : null;
|
||||
|
||||
public bool ByName( string name, [NotNullWhen( true )] out ModCollection2? collection )
|
||||
=> _collections.FindFirst( c => string.Equals( c.Name, name, StringComparison.InvariantCultureIgnoreCase ), 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();
|
||||
LoadCollections();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_modManager.ModsRediscovered -= OnModsRediscovered;
|
||||
_modManager.ModChange -= OnModChanged;
|
||||
}
|
||||
|
||||
private void OnModsRediscovered()
|
||||
{
|
||||
UpdateCaches();
|
||||
Default.SetFiles();
|
||||
}
|
||||
|
||||
private void AddDefaultCollection()
|
||||
{
|
||||
var idx = _collections.IndexOf( c => c.Name == ModCollection2.DefaultCollection );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
_defaultNameIdx = idx;
|
||||
return;
|
||||
}
|
||||
|
||||
var defaultCollection = ModCollection2.CreateNewEmpty( ModCollection2.DefaultCollection );
|
||||
defaultCollection.Save();
|
||||
_defaultNameIdx = _collections.Count;
|
||||
_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( _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 bool AddCollection( string name, ModCollection2? duplicate )
|
||||
{
|
||||
var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant();
|
||||
if( nameFixed.Length == 0
|
||||
|| nameFixed == ModCollection2.Empty.Name.ToLowerInvariant()
|
||||
|| _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 = duplicate?.Duplicate( name ) ?? ModCollection2.CreateNewEmpty( name );
|
||||
_collections.Add( newCollection );
|
||||
newCollection.Save();
|
||||
CollectionChanged?.Invoke( null, newCollection, CollectionType.Inactive );
|
||||
SetCollection( _collections.Count - 1, CollectionType.Current );
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveCollection( int idx )
|
||||
{
|
||||
if( idx < 0 || idx >= _collections.Count )
|
||||
{
|
||||
PluginLog.Error( "Can not remove the empty collection." );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( idx == _defaultNameIdx )
|
||||
{
|
||||
PluginLog.Error( "Can not remove the default collection." );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( idx == _currentIdx )
|
||||
{
|
||||
SetCollection( _defaultNameIdx, CollectionType.Current );
|
||||
}
|
||||
else if( _currentIdx > idx )
|
||||
{
|
||||
--_currentIdx;
|
||||
}
|
||||
|
||||
if( idx == _defaultIdx )
|
||||
{
|
||||
SetCollection( -1, CollectionType.Default );
|
||||
}
|
||||
else if( _defaultIdx > idx )
|
||||
{
|
||||
--_defaultIdx;
|
||||
}
|
||||
|
||||
if( _defaultNameIdx > idx )
|
||||
{
|
||||
--_defaultNameIdx;
|
||||
}
|
||||
|
||||
foreach( var (characterName, characterIdx) in _character.ToList() )
|
||||
{
|
||||
if( idx == characterIdx )
|
||||
{
|
||||
SetCollection( -1, CollectionType.Character, characterName );
|
||||
}
|
||||
else if( characterIdx > idx )
|
||||
{
|
||||
_character[ characterName ] = characterIdx - 1;
|
||||
}
|
||||
}
|
||||
|
||||
var collection = _collections[ idx ];
|
||||
collection.Delete();
|
||||
_collections.RemoveAt( idx );
|
||||
CollectionChanged?.Invoke( collection, null, CollectionType.Inactive );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
472
Penumbra/Collections/ModCollection.Cache.cs
Normal file
472
Penumbra/Collections/ModCollection.Cache.cs
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
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.Collections;
|
||||
|
||||
public partial class ModCollection2
|
||||
{
|
||||
private Cache? _cache;
|
||||
|
||||
public bool HasCache
|
||||
=> _cache != null;
|
||||
|
||||
public void CreateCache()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
_cache = new Cache( this );
|
||||
_cache.CalculateEffectiveFileList();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCache()
|
||||
=> _cache?.CalculateEffectiveFileList();
|
||||
|
||||
public void ClearCache()
|
||||
=> _cache = null;
|
||||
|
||||
public FullPath? ResolvePath( Utf8GamePath path )
|
||||
=> _cache?.ResolvePath( path );
|
||||
|
||||
internal void ForceFile( Utf8GamePath path, FullPath fullPath )
|
||||
=> _cache!.ResolvedFiles[ path ] = fullPath;
|
||||
|
||||
internal void RemoveFile( Utf8GamePath path )
|
||||
=> _cache!.ResolvedFiles.Remove( path );
|
||||
|
||||
internal MetaManager? MetaCache
|
||||
=> _cache?.MetaManipulations;
|
||||
|
||||
internal IReadOnlyDictionary< Utf8GamePath, FullPath > ResolvedFiles
|
||||
=> _cache?.ResolvedFiles ?? new Dictionary< Utf8GamePath, FullPath >();
|
||||
|
||||
internal IReadOnlySet< FullPath > MissingFiles
|
||||
=> _cache?.MissingFiles ?? new HashSet< FullPath >();
|
||||
|
||||
internal IReadOnlyDictionary< string, object? > ChangedItems
|
||||
=> _cache?.ChangedItems ?? new Dictionary< string, object? >();
|
||||
|
||||
internal IReadOnlyList< ConflictCache.ModCacheStruct > Conflicts
|
||||
=> _cache?.Conflicts.Conflicts ?? Array.Empty< ConflictCache.ModCacheStruct >();
|
||||
|
||||
public void CalculateEffectiveFileList( bool withMetaManipulations, bool reloadResident )
|
||||
{
|
||||
PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}]", Name, withMetaManipulations );
|
||||
_cache ??= new Cache( this );
|
||||
_cache.CalculateEffectiveFileList();
|
||||
if( withMetaManipulations )
|
||||
{
|
||||
_cache.UpdateMetaManipulations();
|
||||
}
|
||||
|
||||
if( reloadResident )
|
||||
{
|
||||
Penumbra.ResidentResources.Reload();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The ModCollectionCache contains all required temporary data to use a collection.
|
||||
// It will only be setup if a collection gets activated in any way.
|
||||
private class Cache
|
||||
{
|
||||
// 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;
|
||||
public ConflictCache Conflicts;
|
||||
|
||||
public IReadOnlyDictionary< string, object? > ChangedItems
|
||||
{
|
||||
get
|
||||
{
|
||||
SetChangedItems();
|
||||
return _changedItems;
|
||||
}
|
||||
}
|
||||
|
||||
public Cache( 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();
|
||||
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();
|
||||
Conflicts.Sort();
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
// If audio streaming is not disabled, replacing .scd files crashes the game,
|
||||
// so only add those files if it is disabled.
|
||||
private static bool FilterFile( Utf8GamePath gamePath )
|
||||
=> !Penumbra.Config.DisableSoundStreaming
|
||||
&& gamePath.Path.EndsWith( '.', 's', 'c', 'd' );
|
||||
|
||||
|
||||
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;
|
||||
Conflicts.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 );
|
||||
}
|
||||
}
|
||||
|
||||
private void AddManipulations( int modIdx )
|
||||
{
|
||||
var mod = Penumbra.ModManager.Mods[ modIdx ];
|
||||
foreach( var manip in mod.Resources.MetaManipulations.GetManipulationsForConfig( ResolvedSettings[ modIdx ]!, mod.Meta ) )
|
||||
{
|
||||
if( !MetaManipulations.TryGetValue( manip, out var oldModIdx ) )
|
||||
{
|
||||
MetaManipulations.ApplyMod( manip, modIdx );
|
||||
}
|
||||
else
|
||||
{
|
||||
var priority = ResolvedSettings[ modIdx ]!.Priority;
|
||||
var oldPriority = ResolvedSettings[ oldModIdx ]!.Priority;
|
||||
Conflicts.AddConflict( oldModIdx, modIdx, oldPriority, priority, manip );
|
||||
if( priority > oldPriority )
|
||||
{
|
||||
MetaManipulations.ApplyMod( manip, modIdx );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateMetaManipulations()
|
||||
{
|
||||
MetaManipulations.Reset();
|
||||
Conflicts.ClearMetaConflicts();
|
||||
|
||||
foreach( var mod in Penumbra.ModManager.Mods.Zip( ResolvedSettings )
|
||||
.Select( ( m, i ) => ( m.First, m.Second, i ) )
|
||||
.Where( m => m.Second?.Enabled == true && m.First.Resources.MetaManipulations.Count > 0 ) )
|
||||
{
|
||||
AddManipulations( mod.i );
|
||||
}
|
||||
}
|
||||
|
||||
public FullPath? ResolvePath( 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;
|
||||
}
|
||||
}
|
||||
|
||||
[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();
|
||||
}
|
||||
}
|
||||
}
|
||||
113
Penumbra/Collections/ModCollection.Changes.cs
Normal file
113
Penumbra/Collections/ModCollection.Changes.cs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
using System;
|
||||
using Penumbra.Mod;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
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();
|
||||
}
|
||||
72
Penumbra/Collections/ModCollection.Inheritance.cs
Normal file
72
Penumbra/Collections/ModCollection.Inheritance.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
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[ Index idx ]
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach( var collection in GetFlattenedInheritance() )
|
||||
{
|
||||
var settings = _settings[ idx ];
|
||||
if( settings != null )
|
||||
{
|
||||
return ( settings, collection );
|
||||
}
|
||||
}
|
||||
|
||||
return ( null, this );
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Penumbra/Collections/ModCollection.Migration.cs
Normal file
47
Penumbra/Collections/ModCollection.Migration.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using System.Linq;
|
||||
using Penumbra.Mod;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
public sealed 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 );
|
||||
}
|
||||
}
|
||||
209
Penumbra/Collections/ModCollection.cs
Normal file
209
Penumbra/Collections/ModCollection.cs
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
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.Mod;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
// 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 partial class ModCollection2
|
||||
{
|
||||
public const int CurrentVersion = 1;
|
||||
public const string DefaultCollection = "Default";
|
||||
|
||||
public static readonly ModCollection2 Empty = CreateNewEmpty( "None" );
|
||||
|
||||
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);
|
||||
|
||||
internal static ModCollection2 MigrateFromV0( string name, Dictionary< string, ModSettings > allSettings )
|
||||
=> new(name, 0, allSettings);
|
||||
|
||||
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" );
|
||||
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue