This commit is contained in:
Ottermandias 2022-03-26 23:28:52 +01:00
parent 9a0b0bfa0f
commit ac70f8db89
41 changed files with 1546 additions and 1520 deletions

View file

@ -8,6 +8,7 @@ using Lumina.Data;
using Penumbra.Collections;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Mod;
using Penumbra.Mods;
namespace Penumbra.Api;
@ -77,7 +78,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
_penumbra!.ObjectReloader.RedrawAll( setting );
}
private static string ResolvePath( string path, ModManager _, ModCollection2 collection )
private static string ResolvePath( string path, Mod.Mod.Manager _, ModCollection collection )
{
if( !Penumbra.Config.EnableMods )
{
@ -134,7 +135,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
{
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
{
collection = ModCollection2.Empty;
collection = ModCollection.Empty;
}
if( collection.HasCache )

View file

@ -1,261 +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
public partial class ModCollection
{
// 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 )
public sealed partial class Manager
{
switch( type )
// Is invoked after the collections actually changed.
public event CollectionChangeDelegate? CollectionChanged;
private int _currentIdx = 1;
private int _defaultIdx = 0;
private int _defaultNameIdx = 0;
public ModCollection Current
=> this[ _currentIdx ];
public ModCollection Default
=> this[ _defaultIdx ];
private readonly Dictionary< string, int > _character = new();
public ModCollection Character( string name )
=> _character.TryGetValue( name, out var idx ) ? this[ idx ] : Default;
public IEnumerable< (string, ModCollection) > Characters
=> _character.Select( kvp => ( kvp.Key, this[ kvp.Value ] ) );
public bool HasCharacterCollections
=> _character.Count > 0;
private void OnModChanged( Mod.Mod.ChangeType type, int idx, Mod.Mod mod )
{
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 )
var meta = mod.Resources.MetaManipulations.Count > 0;
switch( type )
{
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 );
case Mod.Mod.ChangeType.Added:
foreach( var collection in this )
{
collection.AddMod( mod );
}
foreach( var collection in this.Where( c => c.HasCache && c[ ^1 ].Settings?.Enabled == true ) )
{
collection.CalculateEffectiveFileList( meta, collection == Penumbra.CollectionManager.Default );
}
break;
case Mod.Mod.ChangeType.Removed:
var list = new List< ModSettings? >( _collections.Count );
foreach( var collection in this )
{
list.Add( collection[ idx ].Settings );
collection.RemoveMod( mod, idx );
}
foreach( var (collection, _) in this.Zip( list ).Where( c => c.First.HasCache && c.Second?.Enabled == true ) )
{
collection.CalculateEffectiveFileList( meta, collection == Penumbra.CollectionManager.Default );
}
break;
case Mod.Mod.ChangeType.Changed:
foreach( var collection in this.Where(
collection => collection.Settings[ idx ]?.FixInvalidSettings( mod.Meta ) ?? false ) )
{
collection.Save();
}
foreach( var collection in this.Where( c => c.HasCache && c[ idx ].Settings?.Enabled == true ) )
{
collection.CalculateEffectiveFileList( meta, collection == Penumbra.CollectionManager.Default );
}
break;
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
}
}
return configChanged;
private void CreateNecessaryCaches()
{
if( _defaultIdx > Empty.Index )
{
Default.CreateCache(true);
}
if( _currentIdx > Empty.Index )
{
Current.CreateCache(false);
}
foreach( var idx in _character.Values.Where( i => i > Empty.Index ) )
{
_collections[ idx ].CreateCache(false);
}
}
public void ForceCacheUpdates()
{
foreach( var collection in this )
{
collection.ForceCacheUpdate(collection == Default);
}
}
private void RemoveCache( int idx )
{
if( idx != _defaultIdx && idx != _currentIdx && _character.All( kvp => kvp.Value != idx ) )
{
_collections[ idx ].ClearCache();
}
}
public void SetCollection( ModCollection collection, Type type, string? characterName = null )
=> SetCollection( collection.Index, type, characterName );
public void SetCollection( int newIdx, Type type, string? characterName = null )
{
var oldCollectionIdx = type switch
{
Type.Default => _defaultIdx,
Type.Current => _currentIdx,
Type.Character => characterName?.Length > 0
? _character.TryGetValue( characterName, out var c )
? c
: _defaultIdx
: -1,
_ => -1,
};
if( oldCollectionIdx == -1 || newIdx == oldCollectionIdx )
{
return;
}
var newCollection = this[ newIdx ];
if( newIdx > Empty.Index )
{
newCollection.CreateCache(false);
}
RemoveCache( oldCollectionIdx );
switch( type )
{
case Type.Default:
_defaultIdx = newIdx;
Penumbra.Config.DefaultCollection = newCollection.Name;
Penumbra.ResidentResources.Reload();
Default.SetFiles();
break;
case Type.Current:
_currentIdx = newIdx;
Penumbra.Config.CurrentCollection = newCollection.Name;
break;
case Type.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 ] = Empty.Index;
Penumbra.Config.CharacterCollections[ characterName ] = Empty.Name;
Penumbra.Config.Save();
CollectionChanged?.Invoke( null, Empty, Type.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, Type.Character, characterName );
}
if( Penumbra.Config.CharacterCollections.Remove( characterName ) )
{
Penumbra.Config.Save();
}
}
private int GetIndexForCollectionName( string name )
{
if( name.Length == 0 )
{
return Empty.Index;
}
return _collections.IndexOf( c => c.Name == name );
}
public void LoadCollections()
{
var configChanged = false;
_defaultIdx = GetIndexForCollectionName( Penumbra.Config.DefaultCollection );
if( _defaultIdx < 0 )
{
PluginLog.Error( $"Last choice of Default Collection {Penumbra.Config.DefaultCollection} is not available, reset to None." );
_defaultIdx = Empty.Index;
Penumbra.Config.DefaultCollection = this[ _defaultIdx ].Name;
configChanged = true;
}
_currentIdx = GetIndexForCollectionName( Penumbra.Config.CurrentCollection );
if( _currentIdx < 0 )
{
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 < 0 )
{
PluginLog.Error( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to None." );
_character.Add( player, Empty.Index );
Penumbra.Config.CharacterCollections[ player ] = Empty.Name;
configChanged = true;
}
else
{
_character.Add( player, idx );
}
}
return configChanged;
}
}
}

View file

@ -2,222 +2,239 @@ 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
public partial class ModCollection
{
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 )
public enum Type : byte
{
_modManager = manager;
_modManager.ModsRediscovered += OnModsRediscovered;
_modManager.ModChange += OnModChanged;
ReadCollections();
LoadCollections();
Inactive,
Default,
Character,
Current,
}
public void Dispose()
public sealed partial class Manager : IDisposable, IEnumerable< ModCollection >
{
_modManager.ModsRediscovered -= OnModsRediscovered;
_modManager.ModChange -= OnModChanged;
}
public delegate void CollectionChangeDelegate( ModCollection? oldCollection, ModCollection? newCollection, Type type,
string? characterName = null );
private void OnModsRediscovered()
{
UpdateCaches();
Default.SetFiles();
}
private readonly Mod.Mod.Manager _modManager;
private void AddDefaultCollection()
{
var idx = _collections.IndexOf( c => c.Name == ModCollection2.DefaultCollection );
if( idx >= 0 )
private readonly List< ModCollection > _collections = new()
{
_defaultNameIdx = idx;
return;
Empty,
};
public ModCollection this[ Index idx ]
=> _collections[ idx ];
public ModCollection this[ int idx ]
=> _collections[ idx ];
public ModCollection? this[ string name ]
=> ByName( name, out var c ) ? c : null;
public bool ByName( string name, [NotNullWhen( true )] out ModCollection? collection )
=> _collections.FindFirst( c => string.Equals( c.Name, name, StringComparison.InvariantCultureIgnoreCase ), out collection );
public IEnumerator< ModCollection > GetEnumerator()
=> _collections.Skip( 1 ).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public Manager( Mod.Mod.Manager manager )
{
_modManager = manager;
_modManager.ModsRediscovered += OnModsRediscovered;
_modManager.ModChange += OnModChanged;
ReadCollections();
LoadCollections();
}
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 ) )
public void Dispose()
{
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();
}
_modManager.ModsRediscovered -= OnModsRediscovered;
_modManager.ModChange -= OnModChanged;
}
}
private void ReadCollections()
{
var collectionDir = new DirectoryInfo( ModCollection2.CollectionDirectory );
var inheritances = new List< IReadOnlyList< string > >();
if( collectionDir.Exists )
private void OnModsRediscovered()
{
foreach( var file in collectionDir.EnumerateFiles( "*.json" ) )
ForceCacheUpdates();
Default.SetFiles();
}
private void AddDefaultCollection()
{
var idx = _collections.IndexOf( c => c.Name == DefaultCollection );
if( idx >= 0 )
{
var collection = ModCollection2.LoadFromFile( file, out var inheritance );
if( collection == null || collection.Name.Length == 0 )
_defaultNameIdx = idx;
return;
}
var defaultCollection = CreateNewEmpty( 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 )
{
continue;
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." );
}
}
if( file.Name != $"{collection.Name.RemoveInvalidPathSymbols()}.json" )
foreach( var (setting, mod) in collection.Settings.Zip( _modManager.Mods ).Where( s => s.First != null ) )
{
PluginLog.Warning( $"Collection {file.Name} does not correspond to {collection.Name}." );
changes |= setting!.FixInvalidSettings( mod.Meta );
}
if( this[ collection.Name ] != null )
if( changes )
{
PluginLog.Warning( $"Duplicate collection found: {collection.Name} already exists." );
}
else
{
inheritances.Add( inheritance );
_collections.Add( collection );
collection.Save();
}
}
}
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 ) )
private void ReadCollections()
{
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 )
var collectionDir = new DirectoryInfo( CollectionDirectory );
var inheritances = new List< IReadOnlyList< string > >();
if( collectionDir.Exists )
{
SetCollection( -1, CollectionType.Character, characterName );
}
else if( characterIdx > idx )
{
_character[ characterName ] = characterIdx - 1;
foreach( var file in collectionDir.EnumerateFiles( "*.json" ) )
{
var collection = 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 );
collection.Index = _collections.Count;
_collections.Add( collection );
}
}
}
AddDefaultCollection();
ApplyInheritancesAndFixSettings( inheritances );
}
var collection = _collections[ idx ];
collection.Delete();
_collections.RemoveAt( idx );
CollectionChanged?.Invoke( collection, null, CollectionType.Inactive );
return true;
public bool AddCollection( string name, ModCollection? duplicate )
{
var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant();
if( nameFixed.Length == 0
|| nameFixed == 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 ) ?? CreateNewEmpty( name );
newCollection.Index = _collections.Count;
_collections.Add( newCollection );
newCollection.Save();
CollectionChanged?.Invoke( null, newCollection, Type.Inactive );
SetCollection( newCollection.Index, Type.Current );
return true;
}
public bool RemoveCollection( ModCollection collection )
=> RemoveCollection( collection.Index );
public bool RemoveCollection( int idx )
{
if( idx <= Empty.Index || 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, Type.Current );
}
else if( _currentIdx > idx )
{
--_currentIdx;
}
if( idx == _defaultIdx )
{
SetCollection( -1, Type.Default );
}
else if( _defaultIdx > idx )
{
--_defaultIdx;
}
if( _defaultNameIdx > idx )
{
--_defaultNameIdx;
}
foreach( var (characterName, characterIdx) in _character.ToList() )
{
if( idx == characterIdx )
{
SetCollection( -1, Type.Character, characterName );
}
else if( characterIdx > idx )
{
_character[ characterName ] = characterIdx - 1;
}
}
var collection = _collections[ idx ];
collection.Delete();
_collections.RemoveAt( idx );
for( var i = idx; i < _collections.Count; ++i )
{
--_collections[ i ].Index;
}
CollectionChanged?.Invoke( collection, null, Type.Inactive );
return true;
}
}
}

View file

@ -4,7 +4,7 @@ using System.Linq;
using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Mod;
namespace Penumbra.Collections;
public struct ConflictCache
{
@ -60,6 +60,12 @@ public struct ConflictCache
public IReadOnlyList< ModCacheStruct > Conflicts
=> _conflicts ?? ( IReadOnlyList< ModCacheStruct > )Array.Empty< ModCacheStruct >();
public IEnumerable< ModCacheStruct > ModConflicts( int modIdx )
{
return _conflicts?.SkipWhile( c => c.Mod1 < modIdx ).TakeWhile( c => c.Mod1 == modIdx )
?? Array.Empty< ModCacheStruct >();
}
public void Sort()
=> _conflicts?.Sort();
@ -89,55 +95,4 @@ public struct ConflictCache
public void ClearConflictsWithMod( int modIdx )
=> _conflicts?.RemoveAll( m => m.Mod1 == modIdx || m.Mod2 == ~modIdx );
}
// The ModCache contains volatile information dependent on all current settings in a collection.
public class ModCache
{
public Dictionary< Mod, (List< Utf8GamePath > Files, List< MetaManipulation > Manipulations) > Conflicts { get; private set; } = new();
public void AddConflict( Mod precedingMod, Utf8GamePath gamePath )
{
if( Conflicts.TryGetValue( precedingMod, out var conflicts ) && !conflicts.Files.Contains( gamePath ) )
{
conflicts.Files.Add( gamePath );
}
else
{
Conflicts[ precedingMod ] = ( new List< Utf8GamePath > { gamePath }, new List< MetaManipulation >() );
}
}
public void AddConflict( Mod precedingMod, MetaManipulation manipulation )
{
if( Conflicts.TryGetValue( precedingMod, out var conflicts ) && !conflicts.Manipulations.Contains( manipulation ) )
{
conflicts.Manipulations.Add( manipulation );
}
else
{
Conflicts[ precedingMod ] = ( new List< Utf8GamePath >(), new List< MetaManipulation > { manipulation } );
}
}
public void ClearConflicts()
=> Conflicts.Clear();
public void ClearFileConflicts()
{
Conflicts = Conflicts.Where( kvp => kvp.Value.Manipulations.Count > 0 ).ToDictionary( kvp => kvp.Key, kvp =>
{
kvp.Value.Files.Clear();
return kvp.Value;
} );
}
public void ClearMetaConflicts()
{
Conflicts = Conflicts.Where( kvp => kvp.Value.Files.Count > 0 ).ToDictionary( kvp => kvp.Key, kvp =>
{
kvp.Value.Manipulations.Clear();
return kvp.Value;
} );
}
}

View file

@ -1,10 +1,8 @@
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;
@ -14,27 +12,34 @@ using Penumbra.Util;
namespace Penumbra.Collections;
public partial class ModCollection2
public partial class ModCollection
{
private Cache? _cache;
public bool HasCache
=> _cache != null;
public void CreateCache()
public void CreateCache( bool isDefault )
{
if( Index == 0 )
{
return;
}
if( _cache == null )
{
_cache = new Cache( this );
_cache.CalculateEffectiveFileList();
CalculateEffectiveFileList( true, isDefault );
}
}
public void UpdateCache()
=> _cache?.CalculateEffectiveFileList();
public void ForceCacheUpdate( bool isDefault )
=> CalculateEffectiveFileList( true, isDefault );
public void ClearCache()
=> _cache = null;
{
_cache?.Dispose();
_cache = null;
}
public FullPath? ResolvePath( Utf8GamePath path )
=> _cache?.ResolvePath( path );
@ -60,8 +65,16 @@ public partial class ModCollection2
internal IReadOnlyList< ConflictCache.ModCacheStruct > Conflicts
=> _cache?.Conflicts.Conflicts ?? Array.Empty< ConflictCache.ModCacheStruct >();
internal IEnumerable< ConflictCache.ModCacheStruct > ModConflicts( int modIdx )
=> _cache?.Conflicts.ModConflicts( modIdx ) ?? Array.Empty< ConflictCache.ModCacheStruct >();
public void CalculateEffectiveFileList( bool withMetaManipulations, bool reloadResident )
{
if( Index == 0 )
{
return;
}
PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}]", Name, withMetaManipulations );
_cache ??= new Cache( this );
_cache.CalculateEffectiveFileList();
@ -74,19 +87,21 @@ public partial class ModCollection2
{
Penumbra.ResidentResources.Reload();
}
_cache.Conflicts.Sort();
}
// 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
private class Cache : IDisposable
{
// 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 ModCollection _collection;
private readonly SortedList< string, object? > _changedItems = new();
public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new();
public readonly HashSet< FullPath > MissingFiles = new();
@ -102,12 +117,35 @@ public partial class ModCollection2
}
}
public Cache( ModCollection2 collection )
public Cache( ModCollection collection )
{
_collection = collection;
MetaManipulations = new MetaManager( collection );
_collection = collection;
MetaManipulations = new MetaManager( collection );
_collection.ModSettingChanged += OnModSettingChange;
_collection.InheritanceChanged += OnInheritanceChange;
}
public void Dispose()
{
_collection.ModSettingChanged -= OnModSettingChange;
_collection.InheritanceChanged -= OnInheritanceChange;
}
private void OnModSettingChange( ModSettingChange type, int modIdx, int oldValue, string? optionName, bool _ )
{
if( type == ModSettingChange.Priority && !Conflicts.ModConflicts( modIdx ).Any()
|| type == ModSettingChange.Setting && !_collection[ modIdx ].Settings!.Enabled )
{
return;
}
var hasMeta = Penumbra.ModManager[ modIdx ].Resources.MetaManipulations.Count > 0;
_collection.CalculateEffectiveFileList( hasMeta, Penumbra.CollectionManager.Default == _collection );
}
private void OnInheritanceChange( bool _ )
=> _collection.CalculateEffectiveFileList( true, true );
private static void ResetFileSeen( int size )
{
if( size < FileSeen.Length )
@ -136,6 +174,7 @@ public partial class ModCollection2
{
ClearStorageAndPrepare();
Conflicts.ClearFileConflicts();
for( var i = 0; i < Penumbra.ModManager.Mods.Count; ++i )
{
if( ResolvedSettings[ i ]?.Enabled == true )
@ -240,7 +279,7 @@ public partial class ModCollection2
}
}
private void AddPathsForOption( Option option, ModData mod, int modIdx, bool enabled )
private void AddPathsForOption( Option option, Mod.Mod mod, int modIdx, bool enabled )
{
foreach( var (file, paths) in option.OptionFiles )
{
@ -270,7 +309,7 @@ public partial class ModCollection2
}
}
private void AddFilesForSingle( OptionGroup singleGroup, ModData mod, int modIdx )
private void AddFilesForSingle( OptionGroup singleGroup, Mod.Mod mod, int modIdx )
{
Debug.Assert( singleGroup.SelectionType == SelectType.Single );
var settings = ResolvedSettings[ modIdx ]!;
@ -285,7 +324,7 @@ public partial class ModCollection2
}
}
private void AddFilesForMulti( OptionGroup multiGroup, ModData mod, int modIdx )
private void AddFilesForMulti( OptionGroup multiGroup, Mod.Mod mod, int modIdx )
{
Debug.Assert( multiGroup.SelectionType == SelectType.Multi );
var settings = ResolvedSettings[ modIdx ]!;
@ -301,7 +340,7 @@ public partial class ModCollection2
}
}
private void AddRemainingFiles( ModData mod, int modIdx )
private void AddRemainingFiles( Mod.Mod mod, int modIdx )
{
for( var i = 0; i < mod.Resources.ModFiles.Count; ++i )
{

View file

@ -11,9 +11,9 @@ public enum ModSettingChange
Setting,
}
public partial class ModCollection2
public partial class ModCollection
{
public delegate void ModSettingChangeDelegate( ModSettingChange type, int modIdx, int oldValue, string? optionName );
public delegate void ModSettingChangeDelegate( ModSettingChange type, int modIdx, int oldValue, string? optionName, bool inherited );
public event ModSettingChangeDelegate ModSettingChanged;
// Enable or disable the mod inheritance of mod idx.
@ -21,7 +21,7 @@ public partial class ModCollection2
{
if( FixInheritance( idx, inherit ) )
{
ModSettingChanged.Invoke( ModSettingChange.Inheritance, idx, inherit ? 0 : 1, null );
ModSettingChanged.Invoke( ModSettingChange.Inheritance, idx, inherit ? 0 : 1, null, false );
}
}
@ -32,9 +32,9 @@ public partial class ModCollection2
var oldValue = _settings[ idx ]?.Enabled ?? this[ idx ].Settings?.Enabled ?? false;
if( newValue != oldValue )
{
var inheritance = FixInheritance( idx, true );
var inheritance = FixInheritance( idx, false );
_settings[ idx ]!.Enabled = newValue;
ModSettingChanged.Invoke( ModSettingChange.EnableState, idx, inheritance ? -1 : newValue ? 0 : 1, null );
ModSettingChanged.Invoke( ModSettingChange.EnableState, idx, inheritance ? -1 : newValue ? 0 : 1, null, false );
}
}
@ -45,9 +45,9 @@ public partial class ModCollection2
var oldValue = _settings[ idx ]?.Priority ?? this[ idx ].Settings?.Priority ?? 0;
if( newValue != oldValue )
{
var inheritance = FixInheritance( idx, true );
var inheritance = FixInheritance( idx, false );
_settings[ idx ]!.Priority = newValue;
ModSettingChanged.Invoke( ModSettingChange.Priority, idx, inheritance ? -1 : oldValue, null );
ModSettingChanged.Invoke( ModSettingChange.Priority, idx, inheritance ? -1 : oldValue, null, false );
}
}
@ -63,10 +63,10 @@ public partial class ModCollection2
: newValue;
if( oldValue != newValue )
{
var inheritance = FixInheritance( idx, true );
var inheritance = FixInheritance( idx, false );
_settings[ idx ]!.Settings[ settingName ] = newValue;
_settings[ idx ]!.FixSpecificSetting( settingName, Penumbra.ModManager.Mods[ idx ].Meta );
ModSettingChanged.Invoke( ModSettingChange.Setting, idx, inheritance ? -1 : oldValue, settingName );
ModSettingChanged.Invoke( ModSettingChange.Setting, idx, inheritance ? -1 : oldValue, settingName, false );
}
}
@ -108,6 +108,14 @@ public partial class ModCollection2
return false;
}
private void SaveOnChange( ModSettingChange _1, int _2, int _3, string? _4 )
=> Save();
private void SaveOnChange( ModSettingChange _1, int _2, int _3, string? _4, bool inherited )
=> SaveOnChange( inherited );
private void SaveOnChange( bool inherited )
{
if( !inherited )
{
Save();
}
}
}

View file

@ -6,16 +6,16 @@ using Penumbra.Util;
namespace Penumbra.Collections;
public partial class ModCollection2
public partial class ModCollection
{
private readonly List< ModCollection2 > _inheritance = new();
private readonly List< ModCollection > _inheritance = new();
public event Action InheritanceChanged;
public event Action< bool > InheritanceChanged;
public IReadOnlyList< ModCollection2 > Inheritance
public IReadOnlyList< ModCollection > Inheritance
=> _inheritance;
public IEnumerable< ModCollection2 > GetFlattenedInheritance()
public IEnumerable< ModCollection > GetFlattenedInheritance()
{
yield return this;
@ -27,7 +27,7 @@ public partial class ModCollection2
}
}
public bool AddInheritance( ModCollection2 collection )
public bool AddInheritance( ModCollection collection )
{
if( ReferenceEquals( collection, this ) || _inheritance.Contains( collection ) )
{
@ -35,25 +35,41 @@ public partial class ModCollection2
}
_inheritance.Add( collection );
InheritanceChanged.Invoke();
collection.ModSettingChanged += OnInheritedModSettingChange;
collection.InheritanceChanged += OnInheritedInheritanceChange;
InheritanceChanged.Invoke( false );
return true;
}
public void RemoveInheritance( int idx )
{
var inheritance = _inheritance[ idx ];
inheritance.ModSettingChanged -= OnInheritedModSettingChange;
inheritance.InheritanceChanged -= OnInheritedInheritanceChange;
_inheritance.RemoveAt( idx );
InheritanceChanged.Invoke();
InheritanceChanged.Invoke( false );
}
public void MoveInheritance( int from, int to )
{
if( _inheritance.Move( from, to ) )
{
InheritanceChanged.Invoke();
InheritanceChanged.Invoke( false );
}
}
public (ModSettings? Settings, ModCollection2 Collection) this[ Index idx ]
private void OnInheritedModSettingChange( ModSettingChange type, int modIdx, int oldValue, string? optionName, bool _ )
{
if( _settings[ modIdx ] == null )
{
ModSettingChanged.Invoke( type, modIdx, oldValue, optionName, true );
}
}
private void OnInheritedInheritanceChange( bool _ )
=> InheritanceChanged.Invoke( true );
public (ModSettings? Settings, ModCollection Collection) this[ Index idx ]
{
get
{

View file

@ -3,11 +3,11 @@ using Penumbra.Mod;
namespace Penumbra.Collections;
public sealed partial class ModCollection2
public sealed partial class ModCollection
{
private static class Migration
{
public static void Migrate( ModCollection2 collection )
public static void Migrate( ModCollection collection )
{
var changes = MigrateV0ToV1( collection );
if( changes )
@ -16,7 +16,7 @@ public sealed partial class ModCollection2
}
}
private static bool MigrateV0ToV1( ModCollection2 collection )
private static bool MigrateV0ToV1( ModCollection collection )
{
if( collection.Version > 0 )
{

View file

@ -16,15 +16,25 @@ namespace Penumbra.Collections;
// 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 partial class ModCollection
{
public const int CurrentVersion = 1;
public const string DefaultCollection = "Default";
public const string EmptyCollection = "None";
public static readonly ModCollection2 Empty = CreateNewEmpty( "None" );
public static readonly ModCollection Empty = CreateEmpty();
private static ModCollection CreateEmpty()
{
var collection = CreateNewEmpty( EmptyCollection );
collection.Index = 0;
collection._settings.Clear();
return collection;
}
public string Name { get; private init; }
public int Version { get; private set; }
public int Index { get; private set; } = -1;
private readonly List< ModSettings? > _settings;
@ -37,7 +47,7 @@ public partial class ModCollection2
private readonly Dictionary< string, ModSettings > _unusedSettings;
private ModCollection2( string name, ModCollection2 duplicate )
private ModCollection( string name, ModCollection duplicate )
{
Name = name;
Version = duplicate.Version;
@ -45,10 +55,10 @@ public partial class ModCollection2
_unusedSettings = duplicate._unusedSettings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.DeepCopy() );
_inheritance = duplicate._inheritance.ToList();
ModSettingChanged += SaveOnChange;
InheritanceChanged += Save;
InheritanceChanged += SaveOnChange;
}
private ModCollection2( string name, int version, Dictionary< string, ModSettings > allSettings )
private ModCollection( string name, int version, Dictionary< string, ModSettings > allSettings )
{
Name = name;
Version = version;
@ -66,19 +76,19 @@ public partial class ModCollection2
Migration.Migrate( this );
ModSettingChanged += SaveOnChange;
InheritanceChanged += Save;
InheritanceChanged += SaveOnChange;
}
public static ModCollection2 CreateNewEmpty( string name )
public static ModCollection CreateNewEmpty( string name )
=> new(name, CurrentVersion, new Dictionary< string, ModSettings >());
public ModCollection2 Duplicate( string name )
public ModCollection Duplicate( string name )
=> new(name, this);
internal static ModCollection2 MigrateFromV0( string name, Dictionary< string, ModSettings > allSettings )
internal static ModCollection MigrateFromV0( string name, Dictionary< string, ModSettings > allSettings )
=> new(name, 0, allSettings);
private void CleanUnavailableSettings()
public void CleanUnavailableSettings()
{
var any = _unusedSettings.Count > 0;
_unusedSettings.Clear();
@ -88,7 +98,7 @@ public partial class ModCollection2
}
}
public void AddMod( ModData mod )
public void AddMod( Mod.Mod mod )
{
if( _unusedSettings.TryGetValue( mod.BasePath.Name, out var settings ) )
{
@ -101,7 +111,7 @@ public partial class ModCollection2
}
}
public void RemoveMod( ModData mod, int idx )
public void RemoveMod( Mod.Mod mod, int idx )
{
var settings = _settings[ idx ];
if( settings != null )
@ -165,6 +175,11 @@ public partial class ModCollection2
public void Delete()
{
if( Index == 0 )
{
return;
}
var file = FileName;
if( file.Exists )
{
@ -179,7 +194,7 @@ public partial class ModCollection2
}
}
public static ModCollection2? LoadFromFile( FileInfo file, out IReadOnlyList< string > inheritance )
public static ModCollection? LoadFromFile( FileInfo file, out IReadOnlyList< string > inheritance )
{
inheritance = Array.Empty< string >();
if( !file.Exists )
@ -197,7 +212,7 @@ public partial class ModCollection2
?? new Dictionary< string, ModSettings >();
inheritance = obj[ nameof( Inheritance ) ]?.ToObject< List< string > >() ?? ( IReadOnlyList< string > )Array.Empty< string >();
return new ModCollection2( name, version, settings );
return new ModCollection( name, version, settings );
}
catch( Exception e )
{

View file

@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
using Penumbra.Mod;
using Penumbra.Mods;
using FileMode = Penumbra.Interop.Structs.FileMode;
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
@ -92,7 +93,7 @@ public unsafe partial class ResourceLoader
// Use the default method of path replacement.
public static (FullPath?, object?) DefaultResolver( Utf8GamePath path )
{
var resolved = ModManager.ResolvePath( path );
var resolved = Mod.Mod.Manager.ResolvePath( path );
return ( resolved, null );
}

View file

@ -91,10 +91,10 @@ public unsafe partial class PathResolver
// This map links DrawObjects directly to Actors (by ObjectTable index) and their collections.
// It contains any DrawObjects that correspond to a human actor, even those without specific collections.
internal readonly Dictionary< IntPtr, (ModCollection2, int) > DrawObjectToObject = new();
internal readonly Dictionary< IntPtr, (ModCollection, int) > DrawObjectToObject = new();
// This map links files to their corresponding collection, if it is non-default.
internal readonly ConcurrentDictionary< Utf8String, ModCollection2 > PathCollections = new();
internal readonly ConcurrentDictionary< Utf8String, ModCollection > PathCollections = new();
internal GameObject* LastGameObject = null;
@ -159,7 +159,7 @@ public unsafe partial class PathResolver
}
// Identify the correct collection for a GameObject by index and name.
private static ModCollection2 IdentifyCollection( GameObject* gameObject )
private static ModCollection IdentifyCollection( GameObject* gameObject )
{
if( gameObject == null )
{
@ -180,9 +180,9 @@ public unsafe partial class PathResolver
}
// Update collections linked to Game/DrawObjects due to a change in collection configuration.
private void CheckCollections( ModCollection2? _1, ModCollection2? _2, CollectionType type, string? name )
private void CheckCollections( ModCollection? _1, ModCollection? _2, ModCollection.Type type, string? name )
{
if( type is not (CollectionType.Character or CollectionType.Default) )
if( type is not (ModCollection.Type.Character or ModCollection.Type.Default) )
{
return;
}
@ -200,7 +200,7 @@ public unsafe partial class PathResolver
}
// Use the stored information to find the GameObject and Collection linked to a DrawObject.
private GameObject* FindParent( IntPtr drawObject, out ModCollection2 collection )
private GameObject* FindParent( IntPtr drawObject, out ModCollection collection )
{
if( DrawObjectToObject.TryGetValue( drawObject, out var data ) )
{
@ -225,7 +225,7 @@ public unsafe partial class PathResolver
// Special handling for paths so that we do not store non-owned temporary strings in the dictionary.
private void SetCollection( Utf8String path, ModCollection2 collection )
private void SetCollection( Utf8String path, ModCollection collection )
{
if( PathCollections.ContainsKey( path ) || path.IsOwned )
{

View file

@ -41,7 +41,7 @@ public unsafe partial class PathResolver
return ret;
}
private ModCollection2? _mtrlCollection;
private ModCollection? _mtrlCollection;
private void LoadMtrlHelper( IntPtr mtrlResourceHandle )
{
@ -56,7 +56,7 @@ public unsafe partial class PathResolver
}
// Check specifically for shpk and tex files whether we are currently in a material load.
private bool HandleMaterialSubFiles( ResourceType type, out ModCollection2? collection )
private bool HandleMaterialSubFiles( ResourceType type, out ModCollection? collection )
{
if( _mtrlCollection != null && type is ResourceType.Tex or ResourceType.Shpk )
{
@ -96,7 +96,7 @@ public unsafe partial class PathResolver
}
// Materials need to be set per collection so they can load their textures independently from each other.
private static void HandleMtrlCollection( ModCollection2 collection, string path, bool nonDefault, ResourceType type, FullPath? resolved,
private static void HandleMtrlCollection( ModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved,
out (FullPath?, object?) data )
{
if( nonDefault && type == ResourceType.Mtrl )

View file

@ -161,7 +161,7 @@ public unsafe partial class PathResolver
RspSetupCharacterHook?.Dispose();
}
private ModCollection2? GetCollection( IntPtr drawObject )
private ModCollection? GetCollection( IntPtr drawObject )
{
var parent = FindParent( drawObject, out var collection );
if( parent == null || collection == Penumbra.CollectionManager.Default )
@ -195,7 +195,7 @@ public unsafe partial class PathResolver
}
}
public static MetaChanger ChangeEqp( ModCollection2 collection )
public static MetaChanger ChangeEqp( ModCollection collection )
{
#if USE_EQP
collection.SetEqpFiles();
@ -233,7 +233,7 @@ public unsafe partial class PathResolver
return new MetaChanger( MetaManipulation.Type.Unknown );
}
public static MetaChanger ChangeEqdp( ModCollection2 collection )
public static MetaChanger ChangeEqdp( ModCollection collection )
{
#if USE_EQDP
collection.SetEqdpFiles();
@ -269,7 +269,7 @@ public unsafe partial class PathResolver
return new MetaChanger( MetaManipulation.Type.Unknown );
}
public static MetaChanger ChangeCmp( PathResolver resolver, out ModCollection2? collection )
public static MetaChanger ChangeCmp( PathResolver resolver, out ModCollection? collection )
{
if( resolver.LastGameObject != null )
{

View file

@ -130,7 +130,7 @@ public unsafe partial class PathResolver
// Just add or remove the resolved path.
[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
private IntPtr ResolvePathDetour( ModCollection2 collection, IntPtr path )
private IntPtr ResolvePathDetour( ModCollection collection, IntPtr path )
{
if( path == IntPtr.Zero )
{

View file

@ -104,9 +104,9 @@ public partial class PathResolver : IDisposable
Penumbra.CollectionManager.CollectionChanged -= OnCollectionChange;
}
private void OnCollectionChange( ModCollection2? _1, ModCollection2? _2, CollectionType type, string? characterName )
private void OnCollectionChange( ModCollection? _1, ModCollection? _2, ModCollection.Type type, string? characterName )
{
if( type != CollectionType.Character )
if( type != ModCollection.Type.Character )
{
return;
}

View file

@ -19,11 +19,11 @@ public partial class MetaManager
public readonly Dictionary< Utf8GamePath, ImcFile > Files = new();
public readonly Dictionary< ImcManipulation, int > Manipulations = new();
private readonly ModCollection2 _collection;
private readonly ModCollection _collection;
private static int _imcManagerCount;
public MetaManagerImc( ModCollection2 collection )
public MetaManagerImc( ModCollection collection )
{
_collection = collection;
SetupDelegate();
@ -156,7 +156,7 @@ public partial class MetaManager
{
// Only check imcs.
if( resource->FileType != ResourceType.Imc
|| resolveData is not ModCollection2 { HasCache: true } collection
|| resolveData is not ModCollection { HasCache: true } collection
|| !collection.MetaCache!.Imc.Files.TryGetValue( gamePath, out var file )
|| !file.ChangesSinceLoad )
{

View file

@ -51,7 +51,7 @@ public partial class MetaManager : IDisposable
+ Est.Manipulations.Count
+ Eqp.Manipulations.Count;
public MetaManager( ModCollection2 collection )
public MetaManager( ModCollection collection )
=> Imc = new MetaManagerImc( collection );
public void SetFiles()

View file

@ -34,7 +34,7 @@ public static class MigrateConfiguration
return;
}
var defaultCollection = ModCollection2.CreateNewEmpty( ModCollection2.DefaultCollection );
var defaultCollection = ModCollection.CreateNewEmpty( ModCollection.DefaultCollection );
var defaultCollectionFile = defaultCollection.FileName;
if( defaultCollectionFile.Exists )
{
@ -74,7 +74,7 @@ public static class MigrateConfiguration
}
}
defaultCollection = ModCollection2.MigrateFromV0( ModCollection2.DefaultCollection, dict );
defaultCollection = ModCollection.MigrateFromV0( ModCollection.DefaultCollection, dict );
defaultCollection.Save();
}
catch( Exception e )

31
Penumbra/Mod/FullMod.cs Normal file
View file

@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.IO;
using Penumbra.GameData.ByteString;
namespace Penumbra.Mod;
// A complete Mod containing settings (i.e. dependent on a collection)
// and the resulting cache.
public class FullMod
{
public ModSettings Settings { get; }
public Mod Data { get; }
public FullMod( ModSettings settings, Mod data )
{
Settings = settings;
Data = data;
}
public bool FixSettings()
=> Settings.FixInvalidSettings( Data.Meta );
public HashSet< Utf8GamePath > GetFiles( FileInfo file )
{
var relPath = Utf8RelPath.FromFile( file, Data.BasePath, out var p ) ? p : Utf8RelPath.Empty;
return ModFunctions.GetFilesForConfig( relPath, Settings, Data.Meta );
}
public override string ToString()
=> Data.Meta.Name;
}

View file

@ -0,0 +1,45 @@
using System;
using Penumbra.Mods;
namespace Penumbra.Mod;
public partial class Mod
{
public struct SortOrder : IComparable< SortOrder >
{
public ModFolder ParentFolder { get; set; }
private string _sortOrderName;
public string SortOrderName
{
get => _sortOrderName;
set => _sortOrderName = value.Replace( '/', '\\' );
}
public string SortOrderPath
=> ParentFolder.FullName;
public string FullName
{
get
{
var path = SortOrderPath;
return path.Length > 0 ? $"{path}/{SortOrderName}" : SortOrderName;
}
}
public SortOrder( ModFolder parentFolder, string name )
{
ParentFolder = parentFolder;
_sortOrderName = name.Replace( '/', '\\' );
}
public string FullPath
=> SortOrderPath.Length > 0 ? $"{SortOrderPath}/{SortOrderName}" : SortOrderName;
public int CompareTo( SortOrder other )
=> string.Compare( FullPath, other.FullPath, StringComparison.InvariantCultureIgnoreCase );
}
}

View file

@ -1,33 +1,96 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Penumbra.GameData.ByteString;
using Penumbra.Mods;
namespace Penumbra.Mod;
// A complete Mod containing settings (i.e. dependent on a collection)
// and the resulting cache.
public class Mod
// Mod contains all permanent information about a mod,
// and is independent of collections or settings.
// It only changes when the user actively changes the mod or their filesystem.
public partial class Mod
{
public ModSettings Settings { get; }
public ModData Data { get; }
public ModCache Cache { get; }
public DirectoryInfo BasePath;
public ModMeta Meta;
public ModResources Resources;
public Mod( ModSettings settings, ModData data )
public SortOrder Order;
public SortedList< string, object? > ChangedItems { get; } = new();
public string LowerChangedItemsString { get; private set; } = string.Empty;
public FileInfo MetaFile { get; set; }
public int Index { get; private set; } = -1;
private Mod( ModFolder parentFolder, DirectoryInfo basePath, ModMeta meta, ModResources resources )
{
Settings = settings;
Data = data;
Cache = new ModCache();
BasePath = basePath;
Meta = meta;
Resources = resources;
MetaFile = MetaFileInfo( basePath );
Order = new SortOrder( parentFolder, Meta.Name );
Order.ParentFolder.AddMod( this );
ComputeChangedItems();
}
public bool FixSettings()
=> Settings.FixInvalidSettings( Data.Meta );
public HashSet< Utf8GamePath > GetFiles( FileInfo file )
public void ComputeChangedItems()
{
var relPath = Utf8RelPath.FromFile( file, Data.BasePath, out var p ) ? p : Utf8RelPath.Empty;
return ModFunctions.GetFilesForConfig( relPath, Settings, Data.Meta );
var identifier = GameData.GameData.GetIdentifier();
ChangedItems.Clear();
foreach( var file in Resources.ModFiles.Select( f => f.ToRelPath( BasePath, out var p ) ? p : Utf8RelPath.Empty ) )
{
foreach( var path in ModFunctions.GetAllFiles( file, Meta ) )
{
identifier.Identify( ChangedItems, path.ToGamePath() );
}
}
foreach( var path in Meta.FileSwaps.Keys )
{
identifier.Identify( ChangedItems, path.ToGamePath() );
}
LowerChangedItemsString = string.Join( "\0", ChangedItems.Keys.Select( k => k.ToLowerInvariant() ) );
}
public static FileInfo MetaFileInfo( DirectoryInfo basePath )
=> new(Path.Combine( basePath.FullName, "meta.json" ));
public static Mod? LoadMod( ModFolder parentFolder, DirectoryInfo basePath )
{
basePath.Refresh();
if( !basePath.Exists )
{
PluginLog.Error( $"Supplied mod directory {basePath} does not exist." );
return null;
}
var metaFile = MetaFileInfo( basePath );
if( !metaFile.Exists )
{
PluginLog.Debug( "No mod meta found for {ModLocation}.", basePath.Name );
return null;
}
var meta = ModMeta.LoadFromFile( metaFile );
if( meta == null )
{
return null;
}
var data = new ModResources();
if( data.RefreshModFiles( basePath ).HasFlag( ResourceChange.Meta ) )
{
data.SetManipulations( meta, basePath );
}
return new Mod( parentFolder, basePath, meta, data );
}
public void SaveMeta()
=> Meta.SaveToFile( MetaFile );
public override string ToString()
=> Data.Meta.Name;
=> Order.FullPath;
}

View file

@ -53,13 +53,13 @@ public class ModCleanup
}
}
private static DirectoryInfo CreateNewModDir( ModData mod, string optionGroup, string option )
private static DirectoryInfo CreateNewModDir( Mod mod, string optionGroup, string option )
{
var newName = $"{mod.BasePath.Name}_{optionGroup}_{option}";
return TexToolsImport.CreateModFolder( new DirectoryInfo( Penumbra.Config.ModDirectory ), newName );
}
private static ModData CreateNewMod( DirectoryInfo newDir, string newSortOrder )
private static Mod CreateNewMod( DirectoryInfo newDir, string newSortOrder )
{
var idx = Penumbra.ModManager.AddMod( newDir );
var newMod = Penumbra.ModManager.Mods[ idx ];
@ -69,7 +69,7 @@ public class ModCleanup
return newMod;
}
private static ModMeta CreateNewMeta( DirectoryInfo newDir, ModData mod, string name, string optionGroup, string option )
private static ModMeta CreateNewMeta( DirectoryInfo newDir, Mod mod, string name, string optionGroup, string option )
{
var newMeta = new ModMeta
{
@ -82,7 +82,7 @@ public class ModCleanup
return newMeta;
}
private static void CreateModSplit( HashSet< string > unseenPaths, ModData mod, OptionGroup group, Option option )
private static void CreateModSplit( HashSet< string > unseenPaths, Mod mod, OptionGroup group, Option option )
{
try
{
@ -105,8 +105,8 @@ public class ModCleanup
}
var newSortOrder = group.SelectionType == SelectType.Single
? $"{mod.SortOrder.ParentFolder.FullName}/{mod.Meta.Name}/{group.GroupName}/{option.OptionName}"
: $"{mod.SortOrder.ParentFolder.FullName}/{mod.Meta.Name}/{group.GroupName} - {option.OptionName}";
? $"{mod.Order.ParentFolder.FullName}/{mod.Meta.Name}/{group.GroupName}/{option.OptionName}"
: $"{mod.Order.ParentFolder.FullName}/{mod.Meta.Name}/{group.GroupName} - {option.OptionName}";
CreateNewMod( newDir, newSortOrder );
}
catch( Exception e )
@ -115,7 +115,7 @@ public class ModCleanup
}
}
public static void SplitMod( ModData mod )
public static void SplitMod( Mod mod )
{
if( mod.Meta.Groups.Count == 0 )
{

View file

@ -1,135 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Penumbra.GameData.ByteString;
using Penumbra.Mods;
namespace Penumbra.Mod;
public struct SortOrder : IComparable< SortOrder >
{
public ModFolder ParentFolder { get; set; }
private string _sortOrderName;
public string SortOrderName
{
get => _sortOrderName;
set => _sortOrderName = value.Replace( '/', '\\' );
}
public string SortOrderPath
=> ParentFolder.FullName;
public string FullName
{
get
{
var path = SortOrderPath;
return path.Length > 0 ? $"{path}/{SortOrderName}" : SortOrderName;
}
}
public SortOrder( ModFolder parentFolder, string name )
{
ParentFolder = parentFolder;
_sortOrderName = name.Replace( '/', '\\' );
}
public string FullPath
=> SortOrderPath.Length > 0 ? $"{SortOrderPath}/{SortOrderName}" : SortOrderName;
public int CompareTo( SortOrder other )
=> string.Compare( FullPath, other.FullPath, StringComparison.InvariantCultureIgnoreCase );
}
// ModData contains all permanent information about a mod,
// and is independent of collections or settings.
// It only changes when the user actively changes the mod or their filesystem.
public class ModData
{
public DirectoryInfo BasePath;
public ModMeta Meta;
public ModResources Resources;
public SortOrder SortOrder;
public SortedList< string, object? > ChangedItems { get; } = new();
public string LowerChangedItemsString { get; private set; } = string.Empty;
public FileInfo MetaFile { get; set; }
private ModData( ModFolder parentFolder, DirectoryInfo basePath, ModMeta meta, ModResources resources )
{
BasePath = basePath;
Meta = meta;
Resources = resources;
MetaFile = MetaFileInfo( basePath );
SortOrder = new SortOrder( parentFolder, Meta.Name );
SortOrder.ParentFolder.AddMod( this );
ComputeChangedItems();
}
public void ComputeChangedItems()
{
var identifier = GameData.GameData.GetIdentifier();
ChangedItems.Clear();
foreach( var file in Resources.ModFiles.Select( f => f.ToRelPath( BasePath, out var p ) ? p : Utf8RelPath.Empty ) )
{
foreach( var path in ModFunctions.GetAllFiles( file, Meta ) )
{
identifier.Identify( ChangedItems, path.ToGamePath() );
}
}
foreach( var path in Meta.FileSwaps.Keys )
{
identifier.Identify( ChangedItems, path.ToGamePath() );
}
LowerChangedItemsString = string.Join( "\0", ChangedItems.Keys.Select( k => k.ToLowerInvariant() ) );
}
public static FileInfo MetaFileInfo( DirectoryInfo basePath )
=> new(Path.Combine( basePath.FullName, "meta.json" ));
public static ModData? LoadMod( ModFolder parentFolder, DirectoryInfo basePath )
{
basePath.Refresh();
if( !basePath.Exists )
{
PluginLog.Error( $"Supplied mod directory {basePath} does not exist." );
return null;
}
var metaFile = MetaFileInfo( basePath );
if( !metaFile.Exists )
{
PluginLog.Debug( "No mod meta found for {ModLocation}.", basePath.Name );
return null;
}
var meta = ModMeta.LoadFromFile( metaFile );
if( meta == null )
{
return null;
}
var data = new ModResources();
if( data.RefreshModFiles( basePath ).HasFlag( ResourceChange.Meta ) )
{
data.SetManipulations( meta, basePath );
}
return new ModData( parentFolder, basePath, meta, data );
}
public void SaveMeta()
=> Meta.SaveToFile( MetaFile );
public override string ToString()
=> SortOrder.FullPath;
}

286
Penumbra/Mod/ModManager.cs Normal file
View file

@ -0,0 +1,286 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Penumbra.GameData.ByteString;
using Penumbra.Meta;
using Penumbra.Mod;
using Penumbra.Mods;
using Penumbra.Util;
namespace Penumbra.Mod;
public partial class Mod
{
public enum ChangeType
{
Added,
Removed,
Changed,
}
// The ModManager handles the basic mods installed to the mod directory.
// It also contains the CollectionManager that handles all collections.
public class Manager : IEnumerable< Mod >
{
public DirectoryInfo BasePath { get; private set; } = null!;
private readonly List< Mod > _mods = new();
public Mod this[ int idx ]
=> _mods[ idx ];
public IReadOnlyList< Mod > Mods
=> _mods;
public IEnumerator< Mod > GetEnumerator()
=> _mods.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public ModFolder StructuredMods { get; } = ModFileSystem.Root;
public delegate void ModChangeDelegate( ChangeType type, int modIndex, Mod mod );
public event ModChangeDelegate? ModChange;
public event Action? ModsRediscovered;
public bool Valid { get; private set; }
public int Count
=> _mods.Count;
public Configuration Config
=> Penumbra.Config;
public void DiscoverMods( string newDir )
{
SetBaseDirectory( newDir, false );
DiscoverMods();
}
private void SetBaseDirectory( string newPath, bool firstTime )
{
if( !firstTime && string.Equals( newPath, Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) )
{
return;
}
if( newPath.Length == 0 )
{
Valid = false;
BasePath = new DirectoryInfo( "." );
}
else
{
var newDir = new DirectoryInfo( newPath );
if( !newDir.Exists )
{
try
{
Directory.CreateDirectory( newDir.FullName );
newDir.Refresh();
}
catch( Exception e )
{
PluginLog.Error( $"Could not create specified mod directory {newDir.FullName}:\n{e}" );
}
}
BasePath = newDir;
Valid = true;
if( Config.ModDirectory != BasePath.FullName )
{
Config.ModDirectory = BasePath.FullName;
Config.Save();
}
}
ModsRediscovered?.Invoke();
}
public Manager()
{
SetBaseDirectory( Config.ModDirectory, true );
}
private bool SetSortOrderPath( Mod mod, string path )
{
mod.Move( path );
var fixedPath = mod.Order.FullPath;
if( fixedPath.Length == 0 || string.Equals( fixedPath, mod.Meta.Name, StringComparison.InvariantCultureIgnoreCase ) )
{
Config.ModSortOrder.Remove( mod.BasePath.Name );
return true;
}
if( path != fixedPath )
{
Config.ModSortOrder[ mod.BasePath.Name ] = fixedPath;
return true;
}
return false;
}
private void SetModStructure( bool removeOldPaths = false )
{
var changes = false;
foreach( var (folder, path) in Config.ModSortOrder.ToArray() )
{
if( path.Length > 0 && _mods.FindFirst( m => m.BasePath.Name == folder, out var mod ) )
{
changes |= SetSortOrderPath( mod, path );
}
else if( removeOldPaths )
{
changes = true;
Config.ModSortOrder.Remove( folder );
}
}
if( changes )
{
Config.Save();
}
}
public void DiscoverMods()
{
_mods.Clear();
BasePath.Refresh();
StructuredMods.SubFolders.Clear();
StructuredMods.Mods.Clear();
if( Valid && BasePath.Exists )
{
foreach( var modFolder in BasePath.EnumerateDirectories() )
{
var mod = LoadMod( StructuredMods, modFolder );
if( mod == null )
{
continue;
}
mod.Index = _mods.Count;
_mods.Add( mod );
}
SetModStructure();
}
ModsRediscovered?.Invoke();
}
public void DeleteMod( DirectoryInfo modFolder )
{
if( Directory.Exists( modFolder.FullName ) )
{
try
{
Directory.Delete( modFolder.FullName, true );
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete the mod {modFolder.Name}:\n{e}" );
}
}
var idx = _mods.FindIndex( m => m.BasePath.Name == modFolder.Name );
if( idx >= 0 )
{
var mod = _mods[ idx ];
mod.Order.ParentFolder.RemoveMod( mod );
_mods.RemoveAt( idx );
for( var i = idx; i < _mods.Count; ++i )
{
--_mods[ i ].Index;
}
ModChange?.Invoke( ChangeType.Removed, idx, mod );
}
}
public int AddMod( DirectoryInfo modFolder )
{
var mod = LoadMod( StructuredMods, modFolder );
if( mod == null )
{
return -1;
}
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
{
if( SetSortOrderPath( mod, sortOrder ) )
{
Config.Save();
}
}
if( _mods.Any( m => m.BasePath.Name == modFolder.Name ) )
{
return -1;
}
_mods.Add( mod );
ModChange?.Invoke( ChangeType.Added, _mods.Count - 1, mod );
return _mods.Count - 1;
}
public bool UpdateMod( int idx, bool reloadMeta = false, bool recomputeMeta = false, bool force = false )
{
var mod = Mods[ idx ];
var oldName = mod.Meta.Name;
var metaChanges = mod.Meta.RefreshFromFile( mod.MetaFile ) || force;
var fileChanges = mod.Resources.RefreshModFiles( mod.BasePath );
if( !recomputeMeta && !reloadMeta && !metaChanges && fileChanges == 0 )
{
return false;
}
if( metaChanges || fileChanges.HasFlag( ResourceChange.Files ) )
{
mod.ComputeChangedItems();
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
{
mod.Move( sortOrder );
var path = mod.Order.FullPath;
if( path != sortOrder )
{
Config.ModSortOrder[ mod.BasePath.Name ] = path;
Config.Save();
}
}
else
{
mod.Order = new SortOrder( StructuredMods, mod.Meta.Name );
}
}
var nameChange = !string.Equals( oldName, mod.Meta.Name, StringComparison.InvariantCulture );
recomputeMeta |= fileChanges.HasFlag( ResourceChange.Meta );
if( recomputeMeta )
{
mod.Resources.MetaManipulations.Update( mod.Resources.MetaFiles, mod.BasePath, mod.Meta );
mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) );
}
// TODO: more specific mod changes?
ModChange?.Invoke( ChangeType.Changed, idx, mod );
return true;
}
public bool UpdateMod( Mod mod, bool reloadMeta = false, bool recomputeMeta = false, bool force = false )
=> UpdateMod( Mods.IndexOf( mod ), reloadMeta, recomputeMeta, force );
public static FullPath? ResolvePath( Utf8GamePath gameResourcePath )
=> Penumbra.CollectionManager.Default.ResolvePath( gameResourcePath );
}
}

View file

@ -37,7 +37,7 @@ public static partial class ModFileSystem
// Rename the SortOrderName of a single mod. Slashes are replaced by Backslashes.
// Saves and returns true if anything changed.
public static bool Rename( this ModData mod, string newName )
public static bool Rename( this Mod.Mod mod, string newName )
{
if( RenameNoSave( mod, newName ) )
{
@ -63,7 +63,7 @@ public static partial class ModFileSystem
// Move a single mod to the target folder.
// Returns true and saves if anything changed.
public static bool Move( this ModData mod, ModFolder target )
public static bool Move( this Mod.Mod mod, ModFolder target )
{
if( MoveNoSave( mod, target ) )
{
@ -76,7 +76,7 @@ public static partial class ModFileSystem
// Move a mod to the filesystem location specified by sortOrder and rename its SortOrderName.
// Creates all necessary Subfolders.
public static void Move( this ModData mod, string sortOrder )
public static void Move( this Mod.Mod mod, string sortOrder )
{
var split = sortOrder.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries );
var folder = Root;
@ -129,7 +129,7 @@ public static partial class ModFileSystem
{
foreach( var mod in target.AllMods( true ) )
{
Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullName;
Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.Order.FullName;
}
Penumbra.Config.Save();
@ -137,16 +137,16 @@ public static partial class ModFileSystem
}
// Sets and saves the sort order of a single mod, removing the entry if it is unnecessary.
private static void SaveMod( ModData mod )
private static void SaveMod( Mod.Mod mod )
{
if( ReferenceEquals( mod.SortOrder.ParentFolder, Root )
&& string.Equals( mod.SortOrder.SortOrderName, mod.Meta.Name.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) )
if( ReferenceEquals( mod.Order.ParentFolder, Root )
&& string.Equals( mod.Order.SortOrderName, mod.Meta.Name.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) )
{
Penumbra.Config.ModSortOrder.Remove( mod.BasePath.Name );
}
else
{
Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullName;
Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.Order.FullName;
}
Penumbra.Config.Save();
@ -184,30 +184,30 @@ public static partial class ModFileSystem
return true;
}
private static bool RenameNoSave( ModData mod, string newName )
private static bool RenameNoSave( Mod.Mod mod, string newName )
{
newName = newName.Replace( '/', '\\' );
if( mod.SortOrder.SortOrderName == newName )
if( mod.Order.SortOrderName == newName )
{
return false;
}
mod.SortOrder.ParentFolder.RemoveModIgnoreEmpty( mod );
mod.SortOrder = new SortOrder( mod.SortOrder.ParentFolder, newName );
mod.SortOrder.ParentFolder.AddMod( mod );
mod.Order.ParentFolder.RemoveModIgnoreEmpty( mod );
mod.Order = new Mod.Mod.SortOrder( mod.Order.ParentFolder, newName );
mod.Order.ParentFolder.AddMod( mod );
return true;
}
private static bool MoveNoSave( ModData mod, ModFolder target )
private static bool MoveNoSave( Mod.Mod mod, ModFolder target )
{
var oldParent = mod.SortOrder.ParentFolder;
var oldParent = mod.Order.ParentFolder;
if( ReferenceEquals( target, oldParent ) )
{
return false;
}
oldParent.RemoveMod( mod );
mod.SortOrder = new SortOrder( target, mod.SortOrder.SortOrderName );
mod.Order = new Mod.Mod.SortOrder( target, mod.Order.SortOrderName );
target.AddMod( mod );
return true;
}

View file

@ -27,7 +27,7 @@ namespace Penumbra.Mods
}
public List< ModFolder > SubFolders { get; } = new();
public List< ModData > Mods { get; } = new();
public List< Mod.Mod > Mods { get; } = new();
public ModFolder( ModFolder parent, string name )
{
@ -45,7 +45,7 @@ namespace Penumbra.Mods
=> SubFolders.Sum( f => f.TotalDescendantFolders() );
// Return all descendant mods in the specified order.
public IEnumerable< ModData > AllMods( bool foldersFirst )
public IEnumerable< Mod.Mod > AllMods( bool foldersFirst )
{
if( foldersFirst )
{
@ -59,7 +59,7 @@ namespace Penumbra.Mods
return folder.AllMods( false );
}
return new[] { ( ModData )f };
return new[] { ( Mod.Mod )f };
} );
}
@ -116,7 +116,7 @@ namespace Penumbra.Mods
// Add the given mod as a child, if it is not already a child.
// Returns the index of the found or inserted mod.
public int AddMod( ModData mod )
public int AddMod( Mod.Mod mod )
{
var idx = Mods.BinarySearch( mod, ModComparer );
if( idx >= 0 )
@ -132,7 +132,7 @@ namespace Penumbra.Mods
// Remove mod as a child if it exists.
// If this folder is empty afterwards, remove it from its parent.
public void RemoveMod( ModData mod )
public void RemoveMod( Mod.Mod mod )
{
RemoveModIgnoreEmpty( mod );
CheckEmpty();
@ -157,20 +157,20 @@ namespace Penumbra.Mods
: string.Compare( x?.Name ?? string.Empty, y?.Name ?? string.Empty, CompareType );
}
internal class ModDataComparer : IComparer< ModData >
internal class ModDataComparer : IComparer< Mod.Mod >
{
public StringComparison CompareType = StringComparison.InvariantCultureIgnoreCase;
// Compare only the direct SortOrderNames since this is only used inside an enumeration of direct mod children of one folder.
// Since mod SortOrderNames do not have to be unique inside a folder, also compare their BasePaths (and thus their identity) if necessary.
public int Compare( ModData? x, ModData? y )
public int Compare( Mod.Mod? x, Mod.Mod? y )
{
if( ReferenceEquals( x, y ) )
{
return 0;
}
var cmp = string.Compare( x?.SortOrder.SortOrderName, y?.SortOrder.SortOrderName, CompareType );
var cmp = string.Compare( x?.Order.SortOrderName, y?.Order.SortOrderName, CompareType );
if( cmp != 0 )
{
return cmp;
@ -193,7 +193,7 @@ namespace Penumbra.Mods
for( ; modIdx < Mods.Count; ++modIdx )
{
var mod = Mods[ modIdx ];
var modString = mod.SortOrder.SortOrderName;
var modString = mod.Order.SortOrderName;
if( string.Compare( folderString, modString, StringComparison.InvariantCultureIgnoreCase ) > 0 )
{
yield return mod;
@ -235,7 +235,7 @@ namespace Penumbra.Mods
}
// Remove a mod, but do not remove this folder from its parent if it is empty afterwards.
internal void RemoveModIgnoreEmpty( ModData mod )
internal void RemoveModIgnoreEmpty( Mod.Mod mod )
{
var idx = Mods.BinarySearch( mod, ModComparer );
if( idx >= 0 )

View file

@ -9,9 +9,9 @@ namespace Penumbra.Mods;
public partial class ModManagerNew
{
private readonly List< ModData > _mods = new();
private readonly List< Mod.Mod > _mods = new();
public IReadOnlyList< ModData > Mods
public IReadOnlyList< Mod.Mod > Mods
=> _mods;
public void DiscoverMods()

View file

@ -1,276 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Penumbra.GameData.ByteString;
using Penumbra.Meta;
using Penumbra.Mod;
using Penumbra.Util;
namespace Penumbra.Mods;
public enum ModChangeType
{
Added,
Removed,
Changed,
}
public delegate void ModChangeDelegate( ModChangeType type, int modIndex, ModData modData );
// The ModManager handles the basic mods installed to the mod directory.
// It also contains the CollectionManager that handles all collections.
public class ModManager : IEnumerable< ModData >
{
public DirectoryInfo BasePath { get; private set; } = null!;
private readonly List< ModData > _mods = new();
public ModData this[ int idx ]
=> _mods[ idx ];
public IReadOnlyList< ModData > Mods
=> _mods;
public IEnumerator< ModData > GetEnumerator()
=> _mods.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public ModFolder StructuredMods { get; } = ModFileSystem.Root;
public event ModChangeDelegate? ModChange;
public event Action? ModsRediscovered;
public bool Valid { get; private set; }
public int Count
=> _mods.Count;
public Configuration Config
=> Penumbra.Config;
public void DiscoverMods( string newDir )
{
SetBaseDirectory( newDir, false );
DiscoverMods();
}
private void SetBaseDirectory( string newPath, bool firstTime )
{
if( !firstTime && string.Equals( newPath, Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) )
{
return;
}
if( newPath.Length == 0 )
{
Valid = false;
BasePath = new DirectoryInfo( "." );
}
else
{
var newDir = new DirectoryInfo( newPath );
if( !newDir.Exists )
{
try
{
Directory.CreateDirectory( newDir.FullName );
newDir.Refresh();
}
catch( Exception e )
{
PluginLog.Error( $"Could not create specified mod directory {newDir.FullName}:\n{e}" );
}
}
BasePath = newDir;
Valid = true;
if( Config.ModDirectory != BasePath.FullName )
{
Config.ModDirectory = BasePath.FullName;
Config.Save();
}
}
ModsRediscovered?.Invoke();
}
public ModManager()
{
SetBaseDirectory( Config.ModDirectory, true );
}
private bool SetSortOrderPath( ModData mod, string path )
{
mod.Move( path );
var fixedPath = mod.SortOrder.FullPath;
if( fixedPath.Length == 0 || string.Equals( fixedPath, mod.Meta.Name, StringComparison.InvariantCultureIgnoreCase ) )
{
Config.ModSortOrder.Remove( mod.BasePath.Name );
return true;
}
if( path != fixedPath )
{
Config.ModSortOrder[ mod.BasePath.Name ] = fixedPath;
return true;
}
return false;
}
private void SetModStructure( bool removeOldPaths = false )
{
var changes = false;
foreach( var (folder, path) in Config.ModSortOrder.ToArray() )
{
if( path.Length > 0 && _mods.FindFirst( m => m.BasePath.Name == folder, out var mod ) )
{
changes |= SetSortOrderPath( mod, path );
}
else if( removeOldPaths )
{
changes = true;
Config.ModSortOrder.Remove( folder );
}
}
if( changes )
{
Config.Save();
}
}
public void DiscoverMods()
{
_mods.Clear();
BasePath.Refresh();
StructuredMods.SubFolders.Clear();
StructuredMods.Mods.Clear();
if( Valid && BasePath.Exists )
{
foreach( var modFolder in BasePath.EnumerateDirectories() )
{
var mod = ModData.LoadMod( StructuredMods, modFolder );
if( mod == null )
{
continue;
}
_mods.Add( mod );
}
SetModStructure();
}
ModsRediscovered?.Invoke();
}
public void DeleteMod( DirectoryInfo modFolder )
{
if( Directory.Exists( modFolder.FullName ) )
{
try
{
Directory.Delete( modFolder.FullName, true );
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete the mod {modFolder.Name}:\n{e}" );
}
}
var idx = _mods.FindIndex( m => m.BasePath.Name == modFolder.Name );
if( idx >= 0 )
{
var mod = _mods[ idx ];
mod.SortOrder.ParentFolder.RemoveMod( mod );
_mods.RemoveAt( idx );
ModChange?.Invoke( ModChangeType.Removed, idx, mod );
}
}
public int AddMod( DirectoryInfo modFolder )
{
var mod = ModData.LoadMod( StructuredMods, modFolder );
if( mod == null )
{
return -1;
}
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
{
if( SetSortOrderPath( mod, sortOrder ) )
{
Config.Save();
}
}
if( _mods.Any( m => m.BasePath.Name == modFolder.Name ) )
{
return -1;
}
_mods.Add( mod );
ModChange?.Invoke( ModChangeType.Added, _mods.Count - 1, mod );
return _mods.Count - 1;
}
public bool UpdateMod( int idx, bool reloadMeta = false, bool recomputeMeta = false, bool force = false )
{
var mod = Mods[ idx ];
var oldName = mod.Meta.Name;
var metaChanges = mod.Meta.RefreshFromFile( mod.MetaFile ) || force;
var fileChanges = mod.Resources.RefreshModFiles( mod.BasePath );
if( !recomputeMeta && !reloadMeta && !metaChanges && fileChanges == 0 )
{
return false;
}
if( metaChanges || fileChanges.HasFlag( ResourceChange.Files ) )
{
mod.ComputeChangedItems();
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
{
mod.Move( sortOrder );
var path = mod.SortOrder.FullPath;
if( path != sortOrder )
{
Config.ModSortOrder[ mod.BasePath.Name ] = path;
Config.Save();
}
}
else
{
mod.SortOrder = new SortOrder( StructuredMods, mod.Meta.Name );
}
}
var nameChange = !string.Equals( oldName, mod.Meta.Name, StringComparison.InvariantCulture );
recomputeMeta |= fileChanges.HasFlag( ResourceChange.Meta );
if( recomputeMeta )
{
mod.Resources.MetaManipulations.Update( mod.Resources.MetaFiles, mod.BasePath, mod.Meta );
mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) );
}
// TODO: more specific mod changes?
ModChange?.Invoke( ModChangeType.Changed, idx, mod );
return true;
}
public bool UpdateMod( ModData mod, bool reloadMeta = false, bool recomputeMeta = false, bool force = false )
=> UpdateMod( Mods.IndexOf( mod ), reloadMeta, recomputeMeta, force );
public static FullPath? ResolvePath( Utf8GamePath gameResourcePath )
=> Penumbra.CollectionManager.Default.ResolvePath( gameResourcePath );
}

View file

@ -12,7 +12,7 @@ namespace Penumbra.Mods;
// Contains all change functions on a specific mod that also require corresponding changes to collections.
public static class ModManagerEditExtensions
{
public static bool RenameMod( this ModManager manager, string newName, ModData mod )
public static bool RenameMod( this Mod.Mod.Manager manager, string newName, Mod.Mod mod )
{
if( newName.Length == 0 || string.Equals( newName, mod.Meta.Name, StringComparison.InvariantCulture ) )
{
@ -25,23 +25,23 @@ public static class ModManagerEditExtensions
return true;
}
public static bool ChangeSortOrder( this ModManager manager, ModData mod, string newSortOrder )
public static bool ChangeSortOrder( this Mod.Mod.Manager manager, Mod.Mod mod, string newSortOrder )
{
if( string.Equals( mod.SortOrder.FullPath, newSortOrder, StringComparison.InvariantCultureIgnoreCase ) )
if( string.Equals( mod.Order.FullPath, newSortOrder, StringComparison.InvariantCultureIgnoreCase ) )
{
return false;
}
var inRoot = new SortOrder( manager.StructuredMods, mod.Meta.Name );
var inRoot = new Mod.Mod.SortOrder( manager.StructuredMods, mod.Meta.Name );
if( newSortOrder == string.Empty || newSortOrder == inRoot.SortOrderName )
{
mod.SortOrder = inRoot;
mod.Order = inRoot;
manager.Config.ModSortOrder.Remove( mod.BasePath.Name );
}
else
{
mod.Move( newSortOrder );
manager.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullPath;
manager.Config.ModSortOrder[ mod.BasePath.Name ] = mod.Order.FullPath;
}
manager.Config.Save();
@ -49,7 +49,7 @@ public static class ModManagerEditExtensions
return true;
}
public static bool RenameModFolder( this ModManager manager, ModData mod, DirectoryInfo newDir, bool move = true )
public static bool RenameModFolder( this Mod.Mod.Manager manager, Mod.Mod mod, DirectoryInfo newDir, bool move = true )
{
if( move )
{
@ -73,7 +73,7 @@ public static class ModManagerEditExtensions
var oldBasePath = mod.BasePath;
mod.BasePath = newDir;
mod.MetaFile = ModData.MetaFileInfo( newDir );
mod.MetaFile = Mod.Mod.MetaFileInfo( newDir );
manager.UpdateMod( mod );
if( manager.Config.ModSortOrder.ContainsKey( oldBasePath.Name ) )
@ -95,7 +95,7 @@ public static class ModManagerEditExtensions
return true;
}
public static bool ChangeModGroup( this ModManager manager, string oldGroupName, string newGroupName, ModData mod,
public static bool ChangeModGroup( this Mod.Mod.Manager manager, string oldGroupName, string newGroupName, Mod.Mod mod,
SelectType type = SelectType.Single )
{
if( newGroupName == oldGroupName || mod.Meta.Groups.ContainsKey( newGroupName ) )
@ -157,7 +157,7 @@ public static class ModManagerEditExtensions
return true;
}
public static bool RemoveModOption( this ModManager manager, int optionIdx, OptionGroup group, ModData mod )
public static bool RemoveModOption( this Mod.Mod.Manager manager, int optionIdx, OptionGroup group, Mod.Mod mod )
{
if( optionIdx < 0 || optionIdx >= group.Options.Count )
{
@ -202,7 +202,7 @@ public static class ModManagerEditExtensions
if( collection.HasCache && settings.Enabled )
{
collection.CalculateEffectiveFileList( mod.Resources.MetaManipulations.Count > 0,
Penumbra.CollectionManager.Default == collection );
Penumbra.CollectionManager.Default == collection );
}
}
}

View file

@ -16,6 +16,7 @@ using Penumbra.Util;
using Penumbra.Collections;
using Penumbra.Interop.Loader;
using Penumbra.Interop.Resolver;
using Penumbra.Mod;
namespace Penumbra;
@ -34,8 +35,8 @@ public class Penumbra : IDalamudPlugin
public static ResidentResourceManager ResidentResources { get; private set; } = null!;
public static CharacterUtility CharacterUtility { get; private set; } = null!;
public static ModManager ModManager { get; private set; } = null!;
public static CollectionManager2 CollectionManager { get; private set; } = null!;
public static Mod.Mod.Manager ModManager { get; private set; } = null!;
public static ModCollection.Manager CollectionManager { get; private set; } = null!;
public static ResourceLoader ResourceLoader { get; set; } = null!;
public ResourceLogger ResourceLogger { get; }
@ -66,9 +67,9 @@ public class Penumbra : IDalamudPlugin
CharacterUtility = new CharacterUtility();
ResourceLoader = new ResourceLoader( this );
ResourceLogger = new ResourceLogger( ResourceLoader );
ModManager = new ModManager();
ModManager = new Mod.Mod.Manager();
ModManager.DiscoverMods();
CollectionManager = new CollectionManager2( ModManager );
CollectionManager = new ModCollection.Manager( ModManager );
ObjectReloader = new ObjectReloader();
PathResolver = new PathResolver( ResourceLoader );
@ -223,9 +224,9 @@ public class Penumbra : IDalamudPlugin
type = type.ToLowerInvariant();
collectionName = collectionName.ToLowerInvariant();
var collection = string.Equals( collectionName, ModCollection2.Empty.Name, StringComparison.InvariantCultureIgnoreCase )
? ModCollection2.Empty
: CollectionManager[collectionName];
var collection = string.Equals( collectionName, ModCollection.Empty.Name, StringComparison.InvariantCultureIgnoreCase )
? ModCollection.Empty
: CollectionManager[ collectionName ];
if( collection == null )
{
Dalamud.Chat.Print( $"The collection {collection} does not exist." );
@ -241,7 +242,7 @@ public class Penumbra : IDalamudPlugin
return false;
}
CollectionManager.SetCollection( collection, CollectionType.Default );
CollectionManager.SetCollection( collection, ModCollection.Type.Default );
Dalamud.Chat.Print( $"Set {collection.Name} as default collection." );
SettingsInterface.ResetDefaultCollection();
return true;

View file

@ -25,9 +25,7 @@ public partial class SettingsInterface
return;
}
var modManager = Penumbra.ModManager;
var items = Penumbra.CollectionManager.DefaultCollection.Cache?.ChangedItems ?? new Dictionary< string, object? >();
var forced = Penumbra.CollectionManager.ForcedCollection.Cache?.ChangedItems ?? new Dictionary< string, object? >();
var items = Penumbra.CollectionManager.Default.ChangedItems;
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem );
@ -45,12 +43,8 @@ public partial class SettingsInterface
raii.Push( ImGui.EndTable );
var list = items.AsEnumerable();
if( forced.Count > 0 )
{
list = list.Concat( forced ).OrderBy( kvp => kvp.Key );
}
if( _filter.Any() )
if( _filter.Length > 0 )
{
list = list.Where( kvp => kvp.Key.ToLowerInvariant().Contains( _filterLower ) );
}

View file

@ -22,9 +22,8 @@ public partial class SettingsInterface
private readonly Selector _selector;
private string _collectionNames = null!;
private string _collectionNamesWithNone = null!;
private ModCollection2[] _collections = null!;
private ModCollection[] _collections = null!;
private int _currentCollectionIndex;
private int _currentForcedIndex;
private int _currentDefaultIndex;
private readonly Dictionary< string, int > _currentCharacterIndices = new();
private string _newCollectionName = string.Empty;
@ -32,14 +31,14 @@ public partial class SettingsInterface
private void UpdateNames()
{
_collections = Penumbra.CollectionManager.Prepend( ModCollection2.Empty ).ToArray();
_collections = Penumbra.CollectionManager.Prepend( ModCollection.Empty ).ToArray();
_collectionNames = string.Join( "\0", _collections.Skip( 1 ).Select( c => c.Name ) ) + '\0';
_collectionNamesWithNone = "None\0" + _collectionNames;
UpdateIndices();
}
private int GetIndex( ModCollection2 collection )
private int GetIndex( ModCollection collection )
{
var ret = _collections.IndexOf( c => c.Name == collection.Name );
if( ret < 0 )
@ -52,20 +51,17 @@ public partial class SettingsInterface
}
private void UpdateIndex()
=> _currentCollectionIndex = GetIndex( Penumbra.CollectionManager.CurrentCollection ) - 1;
public void UpdateForcedIndex()
=> _currentForcedIndex = GetIndex( Penumbra.CollectionManager.ForcedCollection );
=> _currentCollectionIndex = GetIndex( Penumbra.CollectionManager.Current ) - 1;
public void UpdateDefaultIndex()
=> _currentDefaultIndex = GetIndex( Penumbra.CollectionManager.DefaultCollection );
=> _currentDefaultIndex = GetIndex( Penumbra.CollectionManager.Default );
private void UpdateCharacterIndices()
{
_currentCharacterIndices.Clear();
foreach( var kvp in Penumbra.CollectionManager.CharacterCollection )
foreach( var (character, collection) in Penumbra.CollectionManager.Characters )
{
_currentCharacterIndices[ kvp.Key ] = GetIndex( kvp.Value );
_currentCharacterIndices[ character ] = GetIndex( collection );
}
}
@ -73,7 +69,6 @@ public partial class SettingsInterface
{
UpdateIndex();
UpdateDefaultIndex();
UpdateForcedIndex();
UpdateCharacterIndices();
}
@ -83,24 +78,22 @@ public partial class SettingsInterface
UpdateNames();
}
private void CreateNewCollection( Dictionary< string, ModSettings > settings )
private void CreateNewCollection( bool duplicate )
{
if( Penumbra.CollectionManager.AddCollection( _newCollectionName, settings ) )
if( Penumbra.CollectionManager.AddCollection( _newCollectionName, duplicate ? Penumbra.CollectionManager.Current : null ) )
{
UpdateNames();
SetCurrentCollection( Penumbra.CollectionManager.ByName( _newCollectionName )!, true );
SetCurrentCollection( Penumbra.CollectionManager[ _newCollectionName ]!, true );
}
_newCollectionName = string.Empty;
}
private void DrawCleanCollectionButton()
private static void DrawCleanCollectionButton()
{
if( ImGui.Button( "Clean Settings" ) )
{
var changes = ModFunctions.CleanUpCollection( Penumbra.CollectionManager.CurrentCollection.Settings,
Penumbra.ModManager.BasePath.EnumerateDirectories() );
Penumbra.CollectionManager.CurrentCollection.UpdateSettings( changes );
Penumbra.CollectionManager.Current.CleanUnavailableSettings();
}
ImGuiCustom.HoverTooltip(
@ -120,14 +113,14 @@ public partial class SettingsInterface
if( ImGui.Button( "Create New Empty Collection" ) && _newCollectionName.Length > 0 )
{
CreateNewCollection( new Dictionary< string, ModSettings >() );
CreateNewCollection( false );
}
var hover = ImGui.IsItemHovered();
ImGui.SameLine();
if( ImGui.Button( "Duplicate Current Collection" ) && _newCollectionName.Length > 0 )
{
CreateNewCollection( Penumbra.CollectionManager.CurrentCollection.Settings );
CreateNewCollection( true );
}
hover |= ImGui.IsItemHovered();
@ -138,13 +131,12 @@ public partial class SettingsInterface
ImGui.SetTooltip( "Please enter a name before creating a collection." );
}
var deleteCondition = Penumbra.CollectionManager.Collections.Count > 1
&& Penumbra.CollectionManager.CurrentCollection.Name != ModCollection.DefaultCollection;
var deleteCondition = Penumbra.CollectionManager.Current.Name != ModCollection.DefaultCollection;
ImGui.SameLine();
if( ImGuiCustom.DisableButton( "Delete Current Collection", deleteCondition ) )
{
Penumbra.CollectionManager.RemoveCollection( Penumbra.CollectionManager.CurrentCollection.Name );
SetCurrentCollection( Penumbra.CollectionManager.CurrentCollection, true );
Penumbra.CollectionManager.RemoveCollection( Penumbra.CollectionManager.Current );
SetCurrentCollection( Penumbra.CollectionManager.Current, true );
UpdateNames();
}
@ -167,7 +159,7 @@ public partial class SettingsInterface
return;
}
Penumbra.CollectionManager.SetCollection( _collections[ idx + 1 ], CollectionType.Current );
Penumbra.CollectionManager.SetCollection( _collections[ idx + 1 ], ModCollection.Type.Current );
_currentCollectionIndex = idx;
_selector.Cache.TriggerListReset();
if( _selector.Mod != null )
@ -176,7 +168,7 @@ public partial class SettingsInterface
}
}
public void SetCurrentCollection( ModCollection2 collection, bool force = false )
public void SetCurrentCollection( ModCollection collection, bool force = false )
{
var idx = Array.IndexOf( _collections, collection ) - 1;
if( idx >= 0 )
@ -206,7 +198,7 @@ public partial class SettingsInterface
ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth );
if( ImGui.Combo( "##Default Collection", ref index, _collectionNamesWithNone ) && index != _currentDefaultIndex )
{
Penumbra.CollectionManager.SetCollection( _collections[ index ], CollectionType.Default );
Penumbra.CollectionManager.SetCollection( _collections[ index ], ModCollection.Type.Default );
_currentDefaultIndex = index;
}
@ -219,34 +211,6 @@ public partial class SettingsInterface
ImGui.Text( "Default Collection" );
}
private void DrawForcedCollectionSelector()
{
var index = _currentForcedIndex;
ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth );
using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, Penumbra.CollectionManager.CharacterCollection.Count == 0 );
if( ImGui.Combo( "##Forced Collection", ref index, _collectionNamesWithNone )
&& index != _currentForcedIndex
&& Penumbra.CollectionManager.CharacterCollection.Count > 0 )
{
Penumbra.CollectionManager.SetCollection( _collections[ index ], CollectionType.Forced );
_currentForcedIndex = index;
}
style.Pop();
if( Penumbra.CollectionManager.CharacterCollection.Count == 0 && ImGui.IsItemHovered() )
{
ImGui.SetTooltip(
"Forced Collections only provide value if you have at least one Character Collection. There is no need to set one until then." );
}
ImGui.SameLine();
ImGuiComponents.HelpMarker(
"Mods in the forced collection are always loaded if not overwritten by anything in the current or character-based collection.\n"
+ "Please avoid mixing meta-manipulating mods in Forced and other collections, as this will probably not work correctly." );
ImGui.SameLine();
ImGui.Text( "Forced Collection" );
}
private void DrawNewCharacterCollection()
{
ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth );
@ -339,16 +303,15 @@ public partial class SettingsInterface
}
DrawDefaultCollectionSelector();
DrawForcedCollectionSelector();
foreach( var name in Penumbra.CollectionManager.CharacterCollection.Keys.ToArray() )
foreach( var (name, collection) in Penumbra.CollectionManager.Characters.ToArray() )
{
var idx = _currentCharacterIndices[ name ];
var tmp = idx;
ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth );
if( ImGui.Combo( $"##{name}collection", ref tmp, _collectionNamesWithNone ) && idx != tmp )
{
Penumbra.CollectionManager.SetCollection( _collections[ tmp ], CollectionType.Character, name );
Penumbra.CollectionManager.SetCollection( _collections[ tmp ], ModCollection.Type.Character, name );
_currentCharacterIndices[ name ] = tmp;
}

View file

@ -37,12 +37,10 @@ public partial class SettingsInterface
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable );
var manager = Penumbra.ModManager;
PrintValue( "Current Collection", Penumbra.CollectionManager.CurrentCollection.Name );
PrintValue( " has Cache", ( Penumbra.CollectionManager.CurrentCollection.Cache != null ).ToString() );
PrintValue( "Default Collection", Penumbra.CollectionManager.DefaultCollection.Name );
PrintValue( " has Cache", ( Penumbra.CollectionManager.DefaultCollection.Cache != null ).ToString() );
PrintValue( "Forced Collection", Penumbra.CollectionManager.ForcedCollection.Name );
PrintValue( " has Cache", ( Penumbra.CollectionManager.ForcedCollection.Cache != null ).ToString() );
PrintValue( "Current Collection", Penumbra.CollectionManager.Current.Name );
PrintValue( " has Cache", Penumbra.CollectionManager.Current.HasCache.ToString() );
PrintValue( "Default Collection", Penumbra.CollectionManager.Default.Name );
PrintValue( " has Cache", Penumbra.CollectionManager.Default.HasCache.ToString() );
PrintValue( "Mod Manager BasePath", manager.BasePath.Name );
PrintValue( "Mod Manager BasePath-Full", manager.BasePath.FullName );
PrintValue( "Mod Manager BasePath IsRooted", Path.IsPathRooted( Penumbra.Config.ModDirectory ).ToString() );
@ -111,15 +109,15 @@ public partial class SettingsInterface
return;
}
var cache = Penumbra.CollectionManager.CurrentCollection.Cache;
if( cache == null || !ImGui.BeginTable( "##MissingFilesDebugList", 1, ImGuiTableFlags.RowBg, -Vector2.UnitX ) )
if( !Penumbra.CollectionManager.Current.HasCache
|| !ImGui.BeginTable( "##MissingFilesDebugList", 1, ImGuiTableFlags.RowBg, -Vector2.UnitX ) )
{
return;
}
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable );
foreach( var file in cache.MissingFiles )
foreach( var file in Penumbra.CollectionManager.Current.MissingFiles )
{
ImGui.TableNextRow();
ImGui.TableNextColumn();

View file

@ -100,24 +100,66 @@ public partial class SettingsInterface
return !_filePathFilter.Any() || kvp.Item3.Contains( _filePathFilterLower );
}
private void DrawFilteredRows( ModCollection2 active )
private void DrawFilteredRows( ModCollection active )
{
void DrawFileLines( ModCollection2.Cache cache )
foreach( var (gp, fp) in active.ResolvedFiles.Where( CheckFilters ) )
{
foreach( var (gp, fp) in cache.ResolvedFiles.Where( CheckFilters ) )
{
DrawLine( gp, fp );
}
//foreach( var (mp, mod, _) in cache.MetaManipulations.Manipulations
// .Select( p => ( p.Item1.IdentifierString(), p.Item2.Data.Meta.Name, p.Item2.Data.Meta.LowerName ) )
// .Where( CheckFilters ) )
//{
// DrawLine( mp, mod );
//}
DrawLine( gp, fp );
}
DrawFileLines( active );
var cache = active.MetaCache;
if( cache == null )
{
return;
}
foreach( var (mp, mod, _) in cache.Cmp.Manipulations
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name,
Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) )
.Where( CheckFilters ) )
{
DrawLine( mp, mod );
}
foreach( var (mp, mod, _) in cache.Eqp.Manipulations
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name,
Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) )
.Where( CheckFilters ) )
{
DrawLine( mp, mod );
}
foreach( var (mp, mod, _) in cache.Eqdp.Manipulations
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name,
Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) )
.Where( CheckFilters ) )
{
DrawLine( mp, mod );
}
foreach( var (mp, mod, _) in cache.Gmp.Manipulations
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name,
Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) )
.Where( CheckFilters ) )
{
DrawLine( mp, mod );
}
foreach( var (mp, mod, _) in cache.Est.Manipulations
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name,
Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) )
.Where( CheckFilters ) )
{
DrawLine( mp, mod );
}
foreach( var (mp, mod, _) in cache.Imc.Manipulations
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name,
Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) )
.Where( CheckFilters ) )
{
DrawLine( mp, mod );
}
}
public void Draw()
@ -133,17 +175,12 @@ public partial class SettingsInterface
const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX;
var modManager = Penumbra.ModManager;
var defaultCollection = Penumbra.CollectionManager.DefaultCollection.Cache;
var forcedCollection = Penumbra.CollectionManager.ForcedCollection.Cache;
var resolved = Penumbra.CollectionManager.Default.ResolvedFiles;
var meta = Penumbra.CollectionManager.Default.MetaCache;
var metaCount = meta?.Count ?? 0;
var resolvedCount = resolved.Count;
var (defaultResolved, defaultMeta) = defaultCollection != null
? ( defaultCollection.ResolvedFiles.Count, defaultCollection.MetaManipulations.Count )
: ( 0, 0 );
var (forcedResolved, forcedMeta) = forcedCollection != null
? ( forcedCollection.ResolvedFiles.Count, forcedCollection.MetaManipulations.Count )
: ( 0, 0 );
var totalLines = defaultResolved + forcedResolved + defaultMeta + forcedMeta;
var totalLines = resolvedCount + metaCount;
if( totalLines == 0 )
{
return;
@ -159,7 +196,7 @@ public partial class SettingsInterface
if( _filePathFilter.Length > 0 || _gamePathFilter.Length > 0 )
{
DrawFilteredRows( defaultCollection, forcedCollection );
DrawFilteredRows( Penumbra.CollectionManager.Default );
}
else
{
@ -178,29 +215,17 @@ public partial class SettingsInterface
{
var row = actualRow;
ImGui.TableNextRow();
if( row < defaultResolved )
if( row < resolvedCount )
{
var (gamePath, file) = defaultCollection!.ResolvedFiles.ElementAt( row );
var (gamePath, file) = resolved.ElementAt( row );
DrawLine( gamePath, file );
}
else if( ( row -= defaultResolved ) < defaultMeta )
else if( ( row -= resolved.Count ) < metaCount )
{
// TODO
//var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row );
DrawLine( 0.ToString(), 0.ToString() );
}
else if( ( row -= defaultMeta ) < forcedResolved )
{
var (gamePath, file) = forcedCollection!.ResolvedFiles.ElementAt( row );
DrawLine( gamePath, file );
}
else
{
// TODO
row -= forcedResolved;
//var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row );
DrawLine( 0.ToString(), 0.ToString() );
}
}
}
}

View file

@ -2,324 +2,328 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Logging;
using Penumbra.Mod;
using Penumbra.Mods;
using Penumbra.Util;
namespace Penumbra.UI
namespace Penumbra.UI;
public class ModListCache : IDisposable
{
public class ModListCache : IDisposable
public const uint NewModColor = 0xFF66DD66u;
public const uint DisabledModColor = 0xFF666666u;
public const uint ConflictingModColor = 0xFFAAAAFFu;
public const uint HandledConflictModColor = 0xFF88DDDDu;
private readonly Mod.Mod.Manager _manager;
private readonly List< FullMod > _modsInOrder = new();
private readonly List< (bool visible, uint color) > _visibleMods = new();
private readonly Dictionary< ModFolder, (bool visible, bool enabled) > _visibleFolders = new();
private readonly IReadOnlySet< string > _newMods;
private string _modFilter = string.Empty;
private string _modFilterChanges = string.Empty;
private string _modFilterAuthor = string.Empty;
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
private bool _listResetNecessary;
private bool _filterResetNecessary;
public ModFilter StateFilter
{
public const uint NewModColor = 0xFF66DD66u;
public const uint DisabledModColor = 0xFF666666u;
public const uint ConflictingModColor = 0xFFAAAAFFu;
public const uint HandledConflictModColor = 0xFF88DDDDu;
private readonly ModManager _manager;
private readonly List< Mod.Mod > _modsInOrder = new();
private readonly List< (bool visible, uint color) > _visibleMods = new();
private readonly Dictionary< ModFolder, (bool visible, bool enabled) > _visibleFolders = new();
private readonly IReadOnlySet< string > _newMods;
private string _modFilter = string.Empty;
private string _modFilterChanges = string.Empty;
private string _modFilterAuthor = string.Empty;
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
private bool _listResetNecessary;
private bool _filterResetNecessary;
public ModFilter StateFilter
get => _stateFilter;
set
{
get => _stateFilter;
set
var diff = _stateFilter != value;
_stateFilter = value;
if( diff )
{
var diff = _stateFilter != value;
_stateFilter = value;
if( diff )
{
TriggerFilterReset();
}
TriggerFilterReset();
}
}
}
public ModListCache( ModManager manager, IReadOnlySet< string > newMods )
public ModListCache( Mod.Mod.Manager manager, IReadOnlySet< string > newMods )
{
_manager = manager;
_newMods = newMods;
ResetModList();
ModFileSystem.ModFileSystemChanged += TriggerListReset;
}
public void Dispose()
{
ModFileSystem.ModFileSystemChanged -= TriggerListReset;
}
public int Count
=> _modsInOrder.Count;
public bool Update()
{
if( _listResetNecessary )
{
_manager = manager;
_newMods = newMods;
ResetModList();
ModFileSystem.ModFileSystemChanged += TriggerListReset;
}
public void Dispose()
{
ModFileSystem.ModFileSystemChanged -= TriggerListReset;
}
public int Count
=> _modsInOrder.Count;
public bool Update()
{
if( _listResetNecessary )
{
ResetModList();
return true;
}
if( _filterResetNecessary )
{
ResetFilters();
return true;
}
return false;
}
public void TriggerListReset()
=> _listResetNecessary = true;
public void TriggerFilterReset()
=> _filterResetNecessary = true;
public void RemoveMod( Mod.Mod mod )
{
var idx = _modsInOrder.IndexOf( mod );
if( idx >= 0 )
{
_modsInOrder.RemoveAt( idx );
_visibleMods.RemoveAt( idx );
UpdateFolders();
}
}
private void SetFolderAndParentsVisible( ModFolder? folder )
{
while( folder != null && ( !_visibleFolders.TryGetValue( folder, out var state ) || !state.visible ) )
{
_visibleFolders[ folder ] = ( true, true );
folder = folder.Parent;
}
}
private void UpdateFolders()
{
_visibleFolders.Clear();
for( var i = 0; i < _modsInOrder.Count; ++i )
{
if( _visibleMods[ i ].visible )
{
SetFolderAndParentsVisible( _modsInOrder[ i ].Data.SortOrder.ParentFolder );
}
}
}
public void SetTextFilter( string filter )
{
var lower = filter.ToLowerInvariant();
if( lower.StartsWith( "c:" ) )
{
_modFilterChanges = lower[ 2.. ];
_modFilter = string.Empty;
_modFilterAuthor = string.Empty;
}
else if( lower.StartsWith( "a:" ) )
{
_modFilterAuthor = lower[ 2.. ];
_modFilter = string.Empty;
_modFilterChanges = string.Empty;
}
else
{
_modFilter = lower;
_modFilterAuthor = string.Empty;
_modFilterChanges = string.Empty;
}
ResetFilters();
}
private void ResetModList()
{
_modsInOrder.Clear();
_visibleMods.Clear();
_visibleFolders.Clear();
PluginLog.Debug( "Resetting mod selector list..." );
if( _modsInOrder.Count == 0 )
{
foreach( var modData in _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) )
{
var mod = Penumbra.CollectionManager.Current.GetMod( modData );
_modsInOrder.Add( mod );
_visibleMods.Add( CheckFilters( mod ) );
}
}
_listResetNecessary = false;
_filterResetNecessary = false;
}
private void ResetFilters()
{
_visibleMods.Clear();
_visibleFolders.Clear();
PluginLog.Debug( "Resetting mod selector filters..." );
foreach( var mod in _modsInOrder )
{
_visibleMods.Add( CheckFilters( mod ) );
}
_filterResetNecessary = false;
}
public (Mod.Mod? mod, int idx) GetModByName( string name )
{
for( var i = 0; i < Count; ++i )
{
if( _modsInOrder[ i ].Data.Meta.Name == name )
{
return ( _modsInOrder[ i ], i );
}
}
return ( null, 0 );
}
public (Mod.Mod? mod, int idx) GetModByBasePath( string basePath )
{
for( var i = 0; i < Count; ++i )
{
if( _modsInOrder[ i ].Data.BasePath.Name == basePath )
{
return ( _modsInOrder[ i ], i );
}
}
return ( null, 0 );
}
public (bool visible, bool enabled) GetFolder( ModFolder folder )
=> _visibleFolders.TryGetValue( folder, out var ret ) ? ret : ( false, false );
public (Mod.Mod?, bool visible, uint color) GetMod( int idx )
=> idx >= 0 && idx < _modsInOrder.Count
? ( _modsInOrder[ idx ], _visibleMods[ idx ].visible, _visibleMods[ idx ].color )
: ( null, false, 0 );
private bool CheckFlags( int count, ModFilter hasNoFlag, ModFilter hasFlag )
{
if( count == 0 )
{
if( StateFilter.HasFlag( hasNoFlag ) )
{
return false;
}
}
else if( StateFilter.HasFlag( hasFlag ) )
{
return false;
}
return true;
}
private (bool, uint) CheckFilters( Mod.Mod mod )
if( _filterResetNecessary )
{
var ret = ( false, 0u );
ResetFilters();
return true;
}
if( _modFilter.Any() && !mod.Data.Meta.LowerName.Contains( _modFilter ) )
return false;
}
public void TriggerListReset()
=> _listResetNecessary = true;
public void TriggerFilterReset()
=> _filterResetNecessary = true;
public void RemoveMod( FullMod mod )
{
var idx = _modsInOrder.IndexOf( mod );
if( idx >= 0 )
{
_modsInOrder.RemoveAt( idx );
_visibleMods.RemoveAt( idx );
UpdateFolders();
}
}
private void SetFolderAndParentsVisible( ModFolder? folder )
{
while( folder != null && ( !_visibleFolders.TryGetValue( folder, out var state ) || !state.visible ) )
{
_visibleFolders[ folder ] = ( true, true );
folder = folder.Parent;
}
}
private void UpdateFolders()
{
_visibleFolders.Clear();
for( var i = 0; i < _modsInOrder.Count; ++i )
{
if( _visibleMods[ i ].visible )
{
SetFolderAndParentsVisible( _modsInOrder[ i ].Data.Order.ParentFolder );
}
}
}
public void SetTextFilter( string filter )
{
var lower = filter.ToLowerInvariant();
if( lower.StartsWith( "c:" ) )
{
_modFilterChanges = lower[ 2.. ];
_modFilter = string.Empty;
_modFilterAuthor = string.Empty;
}
else if( lower.StartsWith( "a:" ) )
{
_modFilterAuthor = lower[ 2.. ];
_modFilter = string.Empty;
_modFilterChanges = string.Empty;
}
else
{
_modFilter = lower;
_modFilterAuthor = string.Empty;
_modFilterChanges = string.Empty;
}
ResetFilters();
}
private void ResetModList()
{
_modsInOrder.Clear();
_visibleMods.Clear();
_visibleFolders.Clear();
PluginLog.Debug( "Resetting mod selector list..." );
if( _modsInOrder.Count == 0 )
{
foreach( var modData in _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) )
{
var idx = Penumbra.ModManager.Mods.IndexOf( modData );
var mod = new FullMod( Penumbra.CollectionManager.Current[ idx ].Settings ?? ModSettings.DefaultSettings( modData.Meta ),
modData );
_modsInOrder.Add( mod );
_visibleMods.Add( CheckFilters( mod ) );
}
}
_listResetNecessary = false;
_filterResetNecessary = false;
}
private void ResetFilters()
{
_visibleMods.Clear();
_visibleFolders.Clear();
PluginLog.Debug( "Resetting mod selector filters..." );
foreach( var mod in _modsInOrder )
{
_visibleMods.Add( CheckFilters( mod ) );
}
_filterResetNecessary = false;
}
public (FullMod? mod, int idx) GetModByName( string name )
{
for( var i = 0; i < Count; ++i )
{
if( _modsInOrder[ i ].Data.Meta.Name == name )
{
return ( _modsInOrder[ i ], i );
}
}
return ( null, 0 );
}
public (FullMod? mod, int idx) GetModByBasePath( string basePath )
{
for( var i = 0; i < Count; ++i )
{
if( _modsInOrder[ i ].Data.BasePath.Name == basePath )
{
return ( _modsInOrder[ i ], i );
}
}
return ( null, 0 );
}
public (bool visible, bool enabled) GetFolder( ModFolder folder )
=> _visibleFolders.TryGetValue( folder, out var ret ) ? ret : ( false, false );
public (FullMod?, bool visible, uint color) GetMod( int idx )
=> idx >= 0 && idx < _modsInOrder.Count
? ( _modsInOrder[ idx ], _visibleMods[ idx ].visible, _visibleMods[ idx ].color )
: ( null, false, 0 );
private bool CheckFlags( int count, ModFilter hasNoFlag, ModFilter hasFlag )
{
if( count == 0 )
{
if( StateFilter.HasFlag( hasNoFlag ) )
{
return false;
}
}
else if( StateFilter.HasFlag( hasFlag ) )
{
return false;
}
return true;
}
private (bool, uint) CheckFilters( FullMod mod )
{
var ret = ( false, 0u );
if( _modFilter.Length > 0 && !mod.Data.Meta.LowerName.Contains( _modFilter ) )
{
return ret;
}
if( _modFilterAuthor.Length > 0 && !mod.Data.Meta.LowerAuthor.Contains( _modFilterAuthor ) )
{
return ret;
}
if( _modFilterChanges.Length > 0 && !mod.Data.LowerChangedItemsString.Contains( _modFilterChanges ) )
{
return ret;
}
if( CheckFlags( mod.Data.Resources.ModFiles.Count, ModFilter.HasNoFiles, ModFilter.HasFiles ) )
{
return ret;
}
if( CheckFlags( mod.Data.Meta.FileSwaps.Count, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps ) )
{
return ret;
}
if( CheckFlags( mod.Data.Resources.MetaManipulations.Count, ModFilter.HasNoMetaManipulations,
ModFilter.HasMetaManipulations ) )
{
return ret;
}
if( CheckFlags( mod.Data.Meta.HasGroupsWithConfig ? 1 : 0, ModFilter.HasNoConfig, ModFilter.HasConfig ) )
{
return ret;
}
var isNew = _newMods.Contains( mod.Data.BasePath.Name );
if( CheckFlags( isNew ? 1 : 0, ModFilter.IsNew, ModFilter.NotNew ) )
{
return ret;
}
if( !mod.Settings.Enabled )
{
if( !StateFilter.HasFlag( ModFilter.Disabled ) || !StateFilter.HasFlag( ModFilter.NoConflict ) )
{
return ret;
}
if( _modFilterAuthor.Any() && !mod.Data.Meta.LowerAuthor.Contains( _modFilterAuthor ) )
{
return ret;
}
ret.Item2 = ret.Item2 == 0 ? DisabledModColor : ret.Item2;
}
if( _modFilterChanges.Any() && !mod.Data.LowerChangedItemsString.Contains( _modFilterChanges ) )
{
return ret;
}
if( mod.Settings.Enabled && !StateFilter.HasFlag( ModFilter.Enabled ) )
{
return ret;
}
if( CheckFlags( mod.Data.Resources.ModFiles.Count, ModFilter.HasNoFiles, ModFilter.HasFiles ) )
var conflicts = Penumbra.CollectionManager.Current.ModConflicts( mod.Data.Index ).ToList();
if( conflicts.Count > 0 )
{
if( conflicts.Any( c => !c.Solved ) )
{
return ret;
}
if( CheckFlags( mod.Data.Meta.FileSwaps.Count, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps ) )
{
return ret;
}
if( CheckFlags( mod.Data.Resources.MetaManipulations.Count, ModFilter.HasNoMetaManipulations,
ModFilter.HasMetaManipulations ) )
{
return ret;
}
if( CheckFlags( mod.Data.Meta.HasGroupsWithConfig ? 1 : 0, ModFilter.HasNoConfig, ModFilter.HasConfig ) )
{
return ret;
}
var isNew = _newMods.Contains( mod.Data.BasePath.Name );
if( CheckFlags( isNew ? 1 : 0, ModFilter.IsNew, ModFilter.NotNew ) )
{
return ret;
}
if( !mod.Settings.Enabled )
{
if( !StateFilter.HasFlag( ModFilter.Disabled ) || !StateFilter.HasFlag( ModFilter.NoConflict ) )
if( !StateFilter.HasFlag( ModFilter.UnsolvedConflict ) )
{
return ret;
}
ret.Item2 = ret.Item2 == 0 ? DisabledModColor : ret.Item2;
ret.Item2 = ret.Item2 == 0 ? ConflictingModColor : ret.Item2;
}
if( mod.Settings.Enabled && !StateFilter.HasFlag( ModFilter.Enabled ) )
else
{
return ret;
}
if( mod.Cache.Conflicts.Any() )
{
if( mod.Cache.Conflicts.Keys.Any( m => m.Settings.Priority == mod.Settings.Priority ) )
if( !StateFilter.HasFlag( ModFilter.SolvedConflict ) )
{
if( !StateFilter.HasFlag( ModFilter.UnsolvedConflict ) )
{
return ret;
}
ret.Item2 = ret.Item2 == 0 ? ConflictingModColor : ret.Item2;
return ret;
}
else
{
if( !StateFilter.HasFlag( ModFilter.SolvedConflict ) )
{
return ret;
}
ret.Item2 = ret.Item2 == 0 ? HandledConflictModColor : ret.Item2;
}
ret.Item2 = ret.Item2 == 0 ? HandledConflictModColor : ret.Item2;
}
else if( !StateFilter.HasFlag( ModFilter.NoConflict ) )
{
return ret;
}
ret.Item1 = true;
if( isNew )
{
ret.Item2 = NewModColor;
}
SetFolderAndParentsVisible( mod.Data.SortOrder.ParentFolder );
}
else if( !StateFilter.HasFlag( ModFilter.NoConflict ) )
{
return ret;
}
ret.Item1 = true;
if( isNew )
{
ret.Item2 = NewModColor;
}
SetFolderAndParentsVisible( mod.Data.Order.ParentFolder );
return ret;
}
}

View file

@ -9,6 +9,7 @@ using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Util;
using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
using Penumbra.Mod;
using Penumbra.Mods;
using Penumbra.UI.Custom;
@ -119,17 +120,12 @@ public partial class SettingsInterface
}
// This is only drawn when we have a mod selected, so we can forgive nulls.
private Mod.Mod Mod
private FullMod Mod
=> _selector.Mod!;
private ModMeta Meta
=> Mod.Data.Meta;
private void Save()
{
Penumbra.CollectionManager.CurrentCollection.Save();
}
private void DrawAboutTab()
{
if( !_editMode && Meta.Description.Length == 0 )
@ -189,7 +185,8 @@ public partial class SettingsInterface
private void DrawConflictTab()
{
if( !Mod.Cache.Conflicts.Any() || !ImGui.BeginTabItem( LabelConflictsTab ) )
var conflicts = Penumbra.CollectionManager.Current.ModConflicts( Mod.Data.Index ).ToList();
if( conflicts.Count == 0 || !ImGui.BeginTabItem( LabelConflictsTab ) )
{
return;
}
@ -203,32 +200,43 @@ public partial class SettingsInterface
}
raii.Push( ImGui.EndListBox );
using var indent = ImGuiRaii.PushIndent( 0 );
foreach( var (mod, (files, manipulations)) in Mod.Cache.Conflicts )
using var indent = ImGuiRaii.PushIndent( 0 );
Mod.Mod? oldBadMod = null;
foreach( var conflict in conflicts )
{
if( ImGui.Selectable( mod.Data.Meta.Name ) )
var badMod = Penumbra.ModManager[ conflict.Mod2 ];
if( badMod != oldBadMod )
{
_selector.SelectModByDir( mod.Data.BasePath.Name );
if( oldBadMod != null )
{
indent.Pop( 30f );
}
if( ImGui.Selectable( badMod.Meta.Name ) )
{
_selector.SelectModByDir( badMod.BasePath.Name );
}
ImGui.SameLine();
using var color = ImGuiRaii.PushColor( ImGuiCol.Text, conflict.Mod1Priority ? ColorGreen : ColorRed );
ImGui.Text( $"(Priority {Penumbra.CollectionManager.Current[ conflict.Mod2 ].Settings!.Priority})" );
indent.Push( 30f );
}
ImGui.SameLine();
ImGui.Text( $"(Priority {mod.Settings.Priority})" );
indent.Push( 15f );
foreach( var file in files )
if( conflict.Conflict is Utf8GamePath p )
{
unsafe
{
ImGuiNative.igSelectable_Bool( file.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero );
ImGuiNative.igSelectable_Bool( p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero );
}
}
foreach( var manip in manipulations )
else if( conflict.Conflict is MetaManipulation m )
{
//ImGui.Text( manip.IdentifierString() );
ImGui.Selectable( m.Manipulation?.ToString() ?? string.Empty );
}
indent.Pop( 15f );
oldBadMod = badMod;
}
}
@ -421,11 +429,12 @@ public partial class SettingsInterface
{
_fullFilenameList = null;
_selector.SaveCurrentMod();
var idx = Penumbra.ModManager.Mods.IndexOf( Mod.Data );
// Since files may have changed, we need to recompute effective files.
foreach( var collection in Penumbra.CollectionManager.Collections
.Where( c => c.Cache != null && c.Settings[ Mod.Data.BasePath.Name ].Enabled ) )
foreach( var collection in Penumbra.CollectionManager
.Where( c => c.HasCache && c[ idx ].Settings?.Enabled == true ) )
{
collection.CalculateEffectiveFileList( false, Penumbra.CollectionManager.IsActive( collection ) );
collection.CalculateEffectiveFileList( false, collection == Penumbra.CollectionManager.Default );
}
// If the mod is enabled in the current collection, its conflicts may have changed.
@ -553,12 +562,12 @@ public partial class SettingsInterface
var oldEnabled = enabled;
if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled )
{
Mod.Settings.Settings[ group.GroupName ] ^= 1 << idx;
Save();
Penumbra.CollectionManager.Current.SetModSetting( Mod.Data.Index, group.GroupName,
Mod.Settings.Settings[ group.GroupName ] ^ ( 1 << idx ) );
// If the mod is enabled, recalculate files and filters.
if( Mod.Settings.Enabled )
{
_base.RecalculateCurrent( Mod.Data.Resources.MetaManipulations.Count > 0 );
_selector.Cache.TriggerFilterReset();
}
}
}
@ -591,12 +600,10 @@ public partial class SettingsInterface
, group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count )
&& code != Mod.Settings.Settings[ group.GroupName ] )
{
Mod.Settings.Settings[ group.GroupName ] = code;
Save();
// If the mod is enabled, recalculate files and filters.
Penumbra.CollectionManager.Current.SetModSetting( Mod.Data.Index, group.GroupName, code );
if( Mod.Settings.Enabled )
{
_base.RecalculateCurrent( Mod.Data.Resources.MetaManipulations.Count > 0 );
_selector.Cache.TriggerFilterReset();
}
}
}

View file

@ -209,7 +209,7 @@ public partial class SettingsInterface
{
if( newName.Length > 0 )
{
Mod.Settings.Settings[ group.GroupName ] = code;
Penumbra.CollectionManager.Current.SetModSetting(Mod.Data.Index, group.GroupName, code);
group.Options.Add( new Option()
{
OptionName = newName,
@ -245,11 +245,6 @@ public partial class SettingsInterface
}
}
if( code != oldSetting )
{
Save();
}
ImGui.SameLine();
var labelEditPos = ImGui.GetCursorPosX();
DrawSingleSelectorEditGroup( group );

View file

@ -69,7 +69,7 @@ public partial class SettingsInterface
_currentWebsite = Meta?.Website ?? "";
}
private Mod.Mod? Mod
private Mod.FullMod? Mod
=> _selector.Mod;
private ModMeta? Meta
@ -205,8 +205,7 @@ public partial class SettingsInterface
ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale );
if( ImGui.InputInt( "Priority", ref priority, 0 ) && priority != Mod!.Settings.Priority )
{
Mod.Settings.Priority = priority;
_base.SaveCurrentCollection( Mod.Data.Resources.MetaManipulations.Count > 0 );
Penumbra.CollectionManager.Current.SetModPriority( Mod.Data.Index, priority );
_selector.Cache.TriggerFilterReset();
}
@ -220,24 +219,18 @@ public partial class SettingsInterface
var enabled = Mod!.Settings.Enabled;
if( ImGui.Checkbox( LabelModEnabled, ref enabled ) )
{
Mod.Settings.Enabled = enabled;
Penumbra.CollectionManager.Current.SetModState( Mod.Data.Index, enabled );
if( enabled )
{
_newMods.Remove( Mod.Data.BasePath.Name );
}
else
{
Mod.Cache.ClearConflicts();
}
_base.SaveCurrentCollection( Mod.Data.Resources.MetaManipulations.Count > 0 );
_selector.Cache.TriggerFilterReset();
}
}
public static bool DrawSortOrder( ModData mod, ModManager manager, Selector selector )
public static bool DrawSortOrder( Mod.Mod mod, Mod.Mod.Manager manager, Selector selector )
{
var currentSortOrder = mod.SortOrder.FullPath;
var currentSortOrder = mod.Order.FullPath;
ImGui.SetNextItemWidth( 300 * ImGuiHelpers.GlobalScale );
if( ImGui.InputText( "Sort Order", ref currentSortOrder, 256, ImGuiInputTextFlags.EnterReturnsTrue ) )
{

View file

@ -410,11 +410,11 @@ public partial class SettingsInterface
// Selection
private partial class Selector
{
public Mod.Mod? Mod { get; private set; }
public Mod.FullMod? Mod { get; private set; }
private int _index;
private string _nextDir = string.Empty;
private void SetSelection( int idx, Mod.Mod? info )
private void SetSelection( int idx, Mod.FullMod? info )
{
Mod = info;
if( idx != _index )
@ -480,7 +480,7 @@ public partial class SettingsInterface
private partial class Selector
{
// === Mod ===
private void DrawModOrderPopup( string popupName, Mod.Mod mod, bool firstOpen )
private void DrawModOrderPopup( string popupName, Mod.FullMod mod, bool firstOpen )
{
if( !ImGui.BeginPopup( popupName ) )
{
@ -496,7 +496,7 @@ public partial class SettingsInterface
if( firstOpen )
{
ImGui.SetKeyboardFocusHere( mod.Data.SortOrder.FullPath.Length - 1 );
ImGui.SetKeyboardFocusHere( mod.Data.Order.FullPath.Length - 1 );
}
}
@ -527,10 +527,10 @@ public partial class SettingsInterface
}
Cache.TriggerFilterReset();
var collection = Penumbra.CollectionManager.CurrentCollection;
if( collection.Cache != null )
var collection = Penumbra.CollectionManager.Current;
if( collection.HasCache )
{
collection.CalculateEffectiveFileList( metaManips, Penumbra.CollectionManager.IsActive( collection ) );
collection.CalculateEffectiveFileList( metaManips, collection == Penumbra.CollectionManager.Default );
}
collection.Save();
@ -607,9 +607,9 @@ public partial class SettingsInterface
Cache = new ModListCache( Penumbra.ModManager, newMods );
}
private void DrawCollectionButton( string label, string tooltipLabel, float size, ModCollection2 collection )
private void DrawCollectionButton( string label, string tooltipLabel, float size, ModCollection collection )
{
if( collection == ModCollection2.Empty
if( collection == ModCollection.Empty
|| collection == Penumbra.CollectionManager.Current )
{
using var _ = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f );
@ -664,7 +664,7 @@ public partial class SettingsInterface
idx += sub.TotalDescendantMods();
}
}
else if( item is ModData _ )
else if( item is Mod.Mod _ )
{
var (mod, visible, color) = Cache.GetMod( idx );
if( mod != null && visible )
@ -721,7 +721,7 @@ public partial class SettingsInterface
}
}
private void DrawMod( Mod.Mod mod, int modIndex, uint color )
private void DrawMod( Mod.FullMod mod, int modIndex, uint color )
{
using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Text, color, color != 0 );
@ -736,7 +736,7 @@ public partial class SettingsInterface
firstOpen = true;
}
DragDropTarget( mod.Data.SortOrder.ParentFolder );
DragDropTarget( mod.Data.Order.ParentFolder );
DragDropSourceMod( modIndex, mod.Data.Meta.Name );
DrawModOrderPopup( popupName, mod, firstOpen );

View file

@ -55,26 +55,6 @@ public partial class SettingsInterface : IDisposable
_menu.InstalledTab.Selector.Cache.TriggerListReset();
}
private void SaveCurrentCollection( bool recalculateMeta )
{
var current = Penumbra.CollectionManager.CurrentCollection;
current.Save();
RecalculateCurrent( recalculateMeta );
}
private void RecalculateCurrent( bool recalculateMeta )
{
var current = Penumbra.CollectionManager.CurrentCollection;
if( current.Cache != null )
{
current.CalculateEffectiveFileList( recalculateMeta, Penumbra.CollectionManager.IsActive( current ) );
_menu.InstalledTab.Selector.Cache.TriggerFilterReset();
}
}
public void ResetDefaultCollection()
=> _menu.CollectionsTab.UpdateDefaultIndex();
public void ResetForcedCollection()
=> _menu.CollectionsTab.UpdateForcedIndex();
}

View file

@ -74,7 +74,7 @@ public static class ModelChanger
}
}
public static bool ChangeModMaterials( ModData mod, string from, string to )
public static bool ChangeModMaterials( Mod.Mod mod, string from, string to )
{
if( ValidStrings( from, to ) )
{