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.Collections;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.Mod;
using Penumbra.Mods; using Penumbra.Mods;
namespace Penumbra.Api; namespace Penumbra.Api;
@ -77,7 +78,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
_penumbra!.ObjectReloader.RedrawAll( setting ); _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 ) if( !Penumbra.Config.EnableMods )
{ {
@ -134,7 +135,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
{ {
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) ) if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
{ {
collection = ModCollection2.Empty; collection = ModCollection.Empty;
} }
if( collection.HasCache ) if( collection.HasCache )

View file

@ -1,261 +1,261 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.Meta.Manager;
using Penumbra.Mod; using Penumbra.Mod;
using Penumbra.Mods;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Collections; namespace Penumbra.Collections;
public sealed partial class CollectionManager2 public partial class ModCollection
{ {
// Is invoked after the collections actually changed. public sealed partial class Manager
public event CollectionChangeDelegate? CollectionChanged;
private int _currentIdx = -1;
private int _defaultIdx = -1;
private int _defaultNameIdx = 0;
public ModCollection2 Current
=> this[ _currentIdx ];
public ModCollection2 Default
=> this[ _defaultIdx ];
private readonly Dictionary< string, int > _character = new();
public ModCollection2 Character( string name )
=> _character.TryGetValue( name, out var idx ) ? _collections[ idx ] : Default;
public bool HasCharacterCollections
=> _character.Count > 0;
private void OnModChanged( ModChangeType type, int idx, ModData mod )
{ {
switch( type ) // 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: var meta = mod.Resources.MetaManipulations.Count > 0;
foreach( var collection in _collections ) switch( type )
{
collection.AddMod( mod );
}
foreach( var collection in _collections.Where( c => c.HasCache && c[ ^1 ].Settings?.Enabled == true ) )
{
collection.UpdateCache();
}
break;
case ModChangeType.Removed:
var list = new List< ModSettings? >( _collections.Count );
foreach( var collection in _collections )
{
list.Add( collection[ idx ].Settings );
collection.RemoveMod( mod, idx );
}
foreach( var (collection, _) in _collections.Zip( list ).Where( c => c.First.HasCache && c.Second?.Enabled == true ) )
{
collection.UpdateCache();
}
break;
case ModChangeType.Changed:
foreach( var collection in _collections.Where(
collection => collection.Settings[ idx ]?.FixInvalidSettings( mod.Meta ) ?? false ) )
{
collection.Save();
}
foreach( var collection in _collections.Where( c => c.HasCache && c[ idx ].Settings?.Enabled == true ) )
{
collection.UpdateCache();
}
break;
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
}
}
private void CreateNecessaryCaches()
{
if( _defaultIdx >= 0 )
{
Default.CreateCache();
}
if( _currentIdx >= 0 )
{
Current.CreateCache();
}
foreach( var idx in _character.Values.Where( i => i >= 0 ) )
{
_collections[ idx ].CreateCache();
}
}
public void UpdateCaches()
{
foreach( var collection in _collections )
{
collection.UpdateCache();
}
}
private void RemoveCache( int idx )
{
if( idx != _defaultIdx && idx != _currentIdx && _character.All( kvp => kvp.Value != idx ) )
{
_collections[ idx ].ClearCache();
}
}
public void SetCollection( string name, CollectionType type, string? characterName = null )
=> SetCollection( GetIndexForCollectionName( name ), type, characterName );
public void SetCollection( ModCollection2 collection, CollectionType type, string? characterName = null )
=> SetCollection( GetIndexForCollectionName( collection.Name ), type, characterName );
public void SetCollection( int newIdx, CollectionType type, string? characterName = null )
{
var oldCollectionIdx = type switch
{
CollectionType.Default => _defaultIdx,
CollectionType.Current => _currentIdx,
CollectionType.Character => characterName?.Length > 0
? _character.TryGetValue( characterName, out var c )
? c
: _defaultIdx
: -2,
_ => -2,
};
if( oldCollectionIdx == -2 || newIdx == oldCollectionIdx )
{
return;
}
var newCollection = this[ newIdx ];
if( newIdx >= 0 )
{
newCollection.CreateCache();
}
RemoveCache( oldCollectionIdx );
switch( type )
{
case CollectionType.Default:
_defaultIdx = newIdx;
Penumbra.Config.DefaultCollection = newCollection.Name;
Penumbra.ResidentResources.Reload();
Default.SetFiles();
break;
case CollectionType.Current:
_currentIdx = newIdx;
Penumbra.Config.CurrentCollection = newCollection.Name;
break;
case CollectionType.Character:
_character[ characterName! ] = newIdx;
Penumbra.Config.CharacterCollections[ characterName! ] = newCollection.Name;
break;
}
CollectionChanged?.Invoke( this[ oldCollectionIdx ], newCollection, type, characterName );
Penumbra.Config.Save();
}
public bool CreateCharacterCollection( string characterName )
{
if( _character.ContainsKey( characterName ) )
{
return false;
}
_character[ characterName ] = -1;
Penumbra.Config.CharacterCollections[ characterName ] = ModCollection2.Empty.Name;
Penumbra.Config.Save();
CollectionChanged?.Invoke( null, ModCollection2.Empty, CollectionType.Character, characterName );
return true;
}
public void RemoveCharacterCollection( string characterName )
{
if( _character.TryGetValue( characterName, out var collection ) )
{
RemoveCache( collection );
_character.Remove( characterName );
CollectionChanged?.Invoke( this[ collection ], null, CollectionType.Character, characterName );
}
if( Penumbra.Config.CharacterCollections.Remove( characterName ) )
{
Penumbra.Config.Save();
}
}
private int GetIndexForCollectionName( string name )
{
if( name.Length == 0 || name == ModCollection2.DefaultCollection )
{
return -1;
}
var idx = _collections.IndexOf( c => c.Name == Penumbra.Config.DefaultCollection );
return idx < 0 ? -2 : idx;
}
public void LoadCollections()
{
var configChanged = false;
_defaultIdx = GetIndexForCollectionName( Penumbra.Config.DefaultCollection );
if( _defaultIdx == -2 )
{
PluginLog.Error( $"Last choice of Default Collection {Penumbra.Config.DefaultCollection} is not available, reset to None." );
_defaultIdx = -1;
Penumbra.Config.DefaultCollection = this[ _defaultIdx ].Name;
configChanged = true;
}
_currentIdx = GetIndexForCollectionName( Penumbra.Config.CurrentCollection );
if( _currentIdx == -2 )
{
PluginLog.Error( $"Last choice of Current Collection {Penumbra.Config.CurrentCollection} is not available, reset to Default." );
_currentIdx = _defaultNameIdx;
Penumbra.Config.DefaultCollection = this[ _currentIdx ].Name;
configChanged = true;
}
if( LoadCharacterCollections() || configChanged )
{
Penumbra.Config.Save();
}
CreateNecessaryCaches();
}
private bool LoadCharacterCollections()
{
var configChanged = false;
foreach( var (player, collectionName) in Penumbra.Config.CharacterCollections.ToArray() )
{
var idx = GetIndexForCollectionName( collectionName );
if( idx == -2 )
{ {
PluginLog.Error( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to None." ); case Mod.Mod.ChangeType.Added:
_character.Add( player, -1 ); foreach( var collection in this )
Penumbra.Config.CharacterCollections[ player ] = ModCollection2.Empty.Name; {
configChanged = true; collection.AddMod( mod );
} }
else
{ foreach( var collection in this.Where( c => c.HasCache && c[ ^1 ].Settings?.Enabled == true ) )
_character.Add( player, idx ); {
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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.Mods;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Collections; namespace Penumbra.Collections;
public enum CollectionType : byte public partial class ModCollection
{ {
Inactive, public enum Type : byte
Default,
Character,
Current,
}
public sealed partial class CollectionManager2 : IDisposable, IEnumerable< ModCollection2 >
{
public delegate void CollectionChangeDelegate( ModCollection2? oldCollection, ModCollection2? newCollection, CollectionType type,
string? characterName = null );
private readonly ModManager _modManager;
private readonly List< ModCollection2 > _collections = new();
public ModCollection2 this[ Index idx ]
=> idx.Value == -1 ? ModCollection2.Empty : _collections[ idx ];
public ModCollection2? this[ string name ]
=> ByName( name, out var c ) ? c : null;
public bool ByName( string name, [NotNullWhen( true )] out ModCollection2? collection )
=> _collections.FindFirst( c => string.Equals( c.Name, name, StringComparison.InvariantCultureIgnoreCase ), out collection );
public IEnumerator< ModCollection2 > GetEnumerator()
=> _collections.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public CollectionManager2( ModManager manager )
{ {
_modManager = manager; Inactive,
Default,
_modManager.ModsRediscovered += OnModsRediscovered; Character,
_modManager.ModChange += OnModChanged; Current,
ReadCollections();
LoadCollections();
} }
public void Dispose() public sealed partial class Manager : IDisposable, IEnumerable< ModCollection >
{ {
_modManager.ModsRediscovered -= OnModsRediscovered; public delegate void CollectionChangeDelegate( ModCollection? oldCollection, ModCollection? newCollection, Type type,
_modManager.ModChange -= OnModChanged; string? characterName = null );
}
private void OnModsRediscovered() private readonly Mod.Mod.Manager _modManager;
{
UpdateCaches();
Default.SetFiles();
}
private void AddDefaultCollection() private readonly List< ModCollection > _collections = new()
{
var idx = _collections.IndexOf( c => c.Name == ModCollection2.DefaultCollection );
if( idx >= 0 )
{ {
_defaultNameIdx = idx; Empty,
return; };
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 ); public void Dispose()
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; _modManager.ModsRediscovered -= OnModsRediscovered;
foreach( var subCollectionName in inheritance ) _modManager.ModChange -= OnModChanged;
{
if( !ByName( subCollectionName, out var subCollection ) )
{
changes = true;
PluginLog.Warning( $"Inherited collection {subCollectionName} for {collection.Name} does not exist, removed." );
}
else if( !collection.AddInheritance( subCollection ) )
{
changes = true;
PluginLog.Warning( $"{collection.Name} can not inherit from {subCollectionName}, removed." );
}
}
foreach( var (setting, mod) in collection.Settings.Zip( _modManager.Mods ).Where( s => s.First != null ) )
{
changes |= setting!.FixInvalidSettings( mod.Meta );
}
if( changes )
{
collection.Save();
}
} }
}
private void ReadCollections() private void OnModsRediscovered()
{
var collectionDir = new DirectoryInfo( ModCollection2.CollectionDirectory );
var inheritances = new List< IReadOnlyList< string > >();
if( collectionDir.Exists )
{ {
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 ); _defaultNameIdx = idx;
if( collection == null || collection.Name.Length == 0 ) 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." ); collection.Save();
}
else
{
inheritances.Add( inheritance );
_collections.Add( collection );
} }
} }
} }
AddDefaultCollection(); private void ReadCollections()
ApplyInheritancesAndFixSettings( inheritances );
}
public bool AddCollection( string name, ModCollection2? duplicate )
{
var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant();
if( nameFixed.Length == 0
|| nameFixed == ModCollection2.Empty.Name.ToLowerInvariant()
|| _collections.Any( c => c.Name.RemoveInvalidPathSymbols().ToLowerInvariant() == nameFixed ) )
{ {
PluginLog.Warning( $"The new collection {name} would lead to the same path as one that already exists." ); var collectionDir = new DirectoryInfo( CollectionDirectory );
return false; var inheritances = new List< IReadOnlyList< string > >();
} if( collectionDir.Exists )
var newCollection = duplicate?.Duplicate( name ) ?? ModCollection2.CreateNewEmpty( name );
_collections.Add( newCollection );
newCollection.Save();
CollectionChanged?.Invoke( null, newCollection, CollectionType.Inactive );
SetCollection( _collections.Count - 1, CollectionType.Current );
return true;
}
public bool RemoveCollection( int idx )
{
if( idx < 0 || idx >= _collections.Count )
{
PluginLog.Error( "Can not remove the empty collection." );
return false;
}
if( idx == _defaultNameIdx )
{
PluginLog.Error( "Can not remove the default collection." );
return false;
}
if( idx == _currentIdx )
{
SetCollection( _defaultNameIdx, CollectionType.Current );
}
else if( _currentIdx > idx )
{
--_currentIdx;
}
if( idx == _defaultIdx )
{
SetCollection( -1, CollectionType.Default );
}
else if( _defaultIdx > idx )
{
--_defaultIdx;
}
if( _defaultNameIdx > idx )
{
--_defaultNameIdx;
}
foreach( var (characterName, characterIdx) in _character.ToList() )
{
if( idx == characterIdx )
{ {
SetCollection( -1, CollectionType.Character, characterName ); foreach( var file in collectionDir.EnumerateFiles( "*.json" ) )
} {
else if( characterIdx > idx ) var collection = LoadFromFile( file, out var inheritance );
{ if( collection == null || collection.Name.Length == 0 )
_character[ characterName ] = characterIdx - 1; {
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 ]; public bool AddCollection( string name, ModCollection? duplicate )
collection.Delete(); {
_collections.RemoveAt( idx ); var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant();
CollectionChanged?.Invoke( collection, null, CollectionType.Inactive ); if( nameFixed.Length == 0
return true; || 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.GameData.ByteString;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
namespace Penumbra.Mod; namespace Penumbra.Collections;
public struct ConflictCache public struct ConflictCache
{ {
@ -60,6 +60,12 @@ public struct ConflictCache
public IReadOnlyList< ModCacheStruct > Conflicts public IReadOnlyList< ModCacheStruct > Conflicts
=> _conflicts ?? ( IReadOnlyList< ModCacheStruct > )Array.Empty< ModCacheStruct >(); => _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() public void Sort()
=> _conflicts?.Sort(); => _conflicts?.Sort();
@ -89,55 +95,4 @@ public struct ConflictCache
public void ClearConflictsWithMod( int modIdx ) public void ClearConflictsWithMod( int modIdx )
=> _conflicts?.RemoveAll( m => m.Mod1 == modIdx || m.Mod2 == ~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;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
@ -14,27 +12,34 @@ using Penumbra.Util;
namespace Penumbra.Collections; namespace Penumbra.Collections;
public partial class ModCollection2 public partial class ModCollection
{ {
private Cache? _cache; private Cache? _cache;
public bool HasCache public bool HasCache
=> _cache != null; => _cache != null;
public void CreateCache() public void CreateCache( bool isDefault )
{ {
if( Index == 0 )
{
return;
}
if( _cache == null ) if( _cache == null )
{ {
_cache = new Cache( this ); CalculateEffectiveFileList( true, isDefault );
_cache.CalculateEffectiveFileList();
} }
} }
public void UpdateCache() public void ForceCacheUpdate( bool isDefault )
=> _cache?.CalculateEffectiveFileList(); => CalculateEffectiveFileList( true, isDefault );
public void ClearCache() public void ClearCache()
=> _cache = null; {
_cache?.Dispose();
_cache = null;
}
public FullPath? ResolvePath( Utf8GamePath path ) public FullPath? ResolvePath( Utf8GamePath path )
=> _cache?.ResolvePath( path ); => _cache?.ResolvePath( path );
@ -60,8 +65,16 @@ public partial class ModCollection2
internal IReadOnlyList< ConflictCache.ModCacheStruct > Conflicts internal IReadOnlyList< ConflictCache.ModCacheStruct > Conflicts
=> _cache?.Conflicts.Conflicts ?? Array.Empty< ConflictCache.ModCacheStruct >(); => _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 ) public void CalculateEffectiveFileList( bool withMetaManipulations, bool reloadResident )
{ {
if( Index == 0 )
{
return;
}
PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}]", Name, withMetaManipulations ); PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}]", Name, withMetaManipulations );
_cache ??= new Cache( this ); _cache ??= new Cache( this );
_cache.CalculateEffectiveFileList(); _cache.CalculateEffectiveFileList();
@ -74,19 +87,21 @@ public partial class ModCollection2
{ {
Penumbra.ResidentResources.Reload(); Penumbra.ResidentResources.Reload();
} }
_cache.Conflicts.Sort();
} }
// The ModCollectionCache contains all required temporary data to use a collection. // The ModCollectionCache contains all required temporary data to use a collection.
// It will only be setup if a collection gets activated in any way. // 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. // Shared caches to avoid allocations.
private static readonly BitArray FileSeen = new(256); private static readonly BitArray FileSeen = new(256);
private static readonly Dictionary< Utf8GamePath, int > RegisteredFiles = new(256); private static readonly Dictionary< Utf8GamePath, int > RegisteredFiles = new(256);
private static readonly List< ModSettings? > ResolvedSettings = new(128); private static readonly List< ModSettings? > ResolvedSettings = new(128);
private readonly ModCollection2 _collection; private readonly ModCollection _collection;
private readonly SortedList< string, object? > _changedItems = new(); private readonly SortedList< string, object? > _changedItems = new();
public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new(); public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new();
public readonly HashSet< FullPath > MissingFiles = 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; _collection = collection;
MetaManipulations = new MetaManager( 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 ) private static void ResetFileSeen( int size )
{ {
if( size < FileSeen.Length ) if( size < FileSeen.Length )
@ -136,6 +174,7 @@ public partial class ModCollection2
{ {
ClearStorageAndPrepare(); ClearStorageAndPrepare();
Conflicts.ClearFileConflicts();
for( var i = 0; i < Penumbra.ModManager.Mods.Count; ++i ) for( var i = 0; i < Penumbra.ModManager.Mods.Count; ++i )
{ {
if( ResolvedSettings[ i ]?.Enabled == true ) 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 ) 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 ); Debug.Assert( singleGroup.SelectionType == SelectType.Single );
var settings = ResolvedSettings[ modIdx ]!; 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 ); Debug.Assert( multiGroup.SelectionType == SelectType.Multi );
var settings = ResolvedSettings[ modIdx ]!; 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 ) for( var i = 0; i < mod.Resources.ModFiles.Count; ++i )
{ {

View file

@ -11,9 +11,9 @@ public enum ModSettingChange
Setting, 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; public event ModSettingChangeDelegate ModSettingChanged;
// Enable or disable the mod inheritance of mod idx. // Enable or disable the mod inheritance of mod idx.
@ -21,7 +21,7 @@ public partial class ModCollection2
{ {
if( FixInheritance( idx, inherit ) ) 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; var oldValue = _settings[ idx ]?.Enabled ?? this[ idx ].Settings?.Enabled ?? false;
if( newValue != oldValue ) if( newValue != oldValue )
{ {
var inheritance = FixInheritance( idx, true ); var inheritance = FixInheritance( idx, false );
_settings[ idx ]!.Enabled = newValue; _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; var oldValue = _settings[ idx ]?.Priority ?? this[ idx ].Settings?.Priority ?? 0;
if( newValue != oldValue ) if( newValue != oldValue )
{ {
var inheritance = FixInheritance( idx, true ); var inheritance = FixInheritance( idx, false );
_settings[ idx ]!.Priority = newValue; _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; : newValue;
if( oldValue != newValue ) if( oldValue != newValue )
{ {
var inheritance = FixInheritance( idx, true ); var inheritance = FixInheritance( idx, false );
_settings[ idx ]!.Settings[ settingName ] = newValue; _settings[ idx ]!.Settings[ settingName ] = newValue;
_settings[ idx ]!.FixSpecificSetting( settingName, Penumbra.ModManager.Mods[ idx ].Meta ); _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; return false;
} }
private void SaveOnChange( ModSettingChange _1, int _2, int _3, string? _4 ) private void SaveOnChange( ModSettingChange _1, int _2, int _3, string? _4, bool inherited )
=> Save(); => SaveOnChange( inherited );
private void SaveOnChange( bool inherited )
{
if( !inherited )
{
Save();
}
}
} }

View file

@ -6,16 +6,16 @@ using Penumbra.Util;
namespace Penumbra.Collections; 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; => _inheritance;
public IEnumerable< ModCollection2 > GetFlattenedInheritance() public IEnumerable< ModCollection > GetFlattenedInheritance()
{ {
yield return this; 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 ) ) if( ReferenceEquals( collection, this ) || _inheritance.Contains( collection ) )
{ {
@ -35,25 +35,41 @@ public partial class ModCollection2
} }
_inheritance.Add( collection ); _inheritance.Add( collection );
InheritanceChanged.Invoke(); collection.ModSettingChanged += OnInheritedModSettingChange;
collection.InheritanceChanged += OnInheritedInheritanceChange;
InheritanceChanged.Invoke( false );
return true; return true;
} }
public void RemoveInheritance( int idx ) public void RemoveInheritance( int idx )
{ {
var inheritance = _inheritance[ idx ];
inheritance.ModSettingChanged -= OnInheritedModSettingChange;
inheritance.InheritanceChanged -= OnInheritedInheritanceChange;
_inheritance.RemoveAt( idx ); _inheritance.RemoveAt( idx );
InheritanceChanged.Invoke(); InheritanceChanged.Invoke( false );
} }
public void MoveInheritance( int from, int to ) public void MoveInheritance( int from, int to )
{ {
if( _inheritance.Move( from, 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 get
{ {

View file

@ -3,11 +3,11 @@ using Penumbra.Mod;
namespace Penumbra.Collections; namespace Penumbra.Collections;
public sealed partial class ModCollection2 public sealed partial class ModCollection
{ {
private static class Migration private static class Migration
{ {
public static void Migrate( ModCollection2 collection ) public static void Migrate( ModCollection collection )
{ {
var changes = MigrateV0ToV1( collection ); var changes = MigrateV0ToV1( collection );
if( changes ) 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 ) 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. // 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. // 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. // Active ModCollections build a cache of currently relevant data.
public partial class ModCollection2 public partial class ModCollection
{ {
public const int CurrentVersion = 1; public const int CurrentVersion = 1;
public const string DefaultCollection = "Default"; 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 string Name { get; private init; }
public int Version { get; private set; } public int Version { get; private set; }
public int Index { get; private set; } = -1;
private readonly List< ModSettings? > _settings; private readonly List< ModSettings? > _settings;
@ -37,7 +47,7 @@ public partial class ModCollection2
private readonly Dictionary< string, ModSettings > _unusedSettings; private readonly Dictionary< string, ModSettings > _unusedSettings;
private ModCollection2( string name, ModCollection2 duplicate ) private ModCollection( string name, ModCollection duplicate )
{ {
Name = name; Name = name;
Version = duplicate.Version; Version = duplicate.Version;
@ -45,10 +55,10 @@ public partial class ModCollection2
_unusedSettings = duplicate._unusedSettings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.DeepCopy() ); _unusedSettings = duplicate._unusedSettings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.DeepCopy() );
_inheritance = duplicate._inheritance.ToList(); _inheritance = duplicate._inheritance.ToList();
ModSettingChanged += SaveOnChange; 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; Name = name;
Version = version; Version = version;
@ -66,19 +76,19 @@ public partial class ModCollection2
Migration.Migrate( this ); Migration.Migrate( this );
ModSettingChanged += SaveOnChange; 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 >()); => new(name, CurrentVersion, new Dictionary< string, ModSettings >());
public ModCollection2 Duplicate( string name ) public ModCollection Duplicate( string name )
=> new(name, this); => 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); => new(name, 0, allSettings);
private void CleanUnavailableSettings() public void CleanUnavailableSettings()
{ {
var any = _unusedSettings.Count > 0; var any = _unusedSettings.Count > 0;
_unusedSettings.Clear(); _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 ) ) 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 ]; var settings = _settings[ idx ];
if( settings != null ) if( settings != null )
@ -165,6 +175,11 @@ public partial class ModCollection2
public void Delete() public void Delete()
{ {
if( Index == 0 )
{
return;
}
var file = FileName; var file = FileName;
if( file.Exists ) 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 >(); inheritance = Array.Empty< string >();
if( !file.Exists ) if( !file.Exists )
@ -197,7 +212,7 @@ public partial class ModCollection2
?? new Dictionary< string, ModSettings >(); ?? new Dictionary< string, ModSettings >();
inheritance = obj[ nameof( Inheritance ) ]?.ToObject< List< string > >() ?? ( IReadOnlyList< string > )Array.Empty< string >(); 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 ) catch( Exception e )
{ {

View file

@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.Mod;
using Penumbra.Mods; using Penumbra.Mods;
using FileMode = Penumbra.Interop.Structs.FileMode; using FileMode = Penumbra.Interop.Structs.FileMode;
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; 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. // Use the default method of path replacement.
public static (FullPath?, object?) DefaultResolver( Utf8GamePath path ) public static (FullPath?, object?) DefaultResolver( Utf8GamePath path )
{ {
var resolved = ModManager.ResolvePath( path ); var resolved = Mod.Mod.Manager.ResolvePath( path );
return ( resolved, null ); 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. // 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. // 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. // 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; internal GameObject* LastGameObject = null;
@ -159,7 +159,7 @@ public unsafe partial class PathResolver
} }
// Identify the correct collection for a GameObject by index and name. // 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 ) 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. // 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; return;
} }
@ -200,7 +200,7 @@ public unsafe partial class PathResolver
} }
// Use the stored information to find the GameObject and Collection linked to a DrawObject. // 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 ) ) 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. // 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 ) if( PathCollections.ContainsKey( path ) || path.IsOwned )
{ {

View file

@ -41,7 +41,7 @@ public unsafe partial class PathResolver
return ret; return ret;
} }
private ModCollection2? _mtrlCollection; private ModCollection? _mtrlCollection;
private void LoadMtrlHelper( IntPtr mtrlResourceHandle ) 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. // 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 ) 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. // 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 ) out (FullPath?, object?) data )
{ {
if( nonDefault && type == ResourceType.Mtrl ) if( nonDefault && type == ResourceType.Mtrl )

View file

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

View file

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

View file

@ -104,9 +104,9 @@ public partial class PathResolver : IDisposable
Penumbra.CollectionManager.CollectionChanged -= OnCollectionChange; 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; return;
} }

View file

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

View file

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

View file

@ -34,7 +34,7 @@ public static class MigrateConfiguration
return; return;
} }
var defaultCollection = ModCollection2.CreateNewEmpty( ModCollection2.DefaultCollection ); var defaultCollection = ModCollection.CreateNewEmpty( ModCollection.DefaultCollection );
var defaultCollectionFile = defaultCollection.FileName; var defaultCollectionFile = defaultCollection.FileName;
if( defaultCollectionFile.Exists ) 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(); defaultCollection.Save();
} }
catch( Exception e ) 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.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using Dalamud.Logging;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.Mods;
namespace Penumbra.Mod; namespace Penumbra.Mod;
// A complete Mod containing settings (i.e. dependent on a collection) // Mod contains all permanent information about a mod,
// and the resulting cache. // and is independent of collections or settings.
public class Mod // It only changes when the user actively changes the mod or their filesystem.
public partial class Mod
{ {
public ModSettings Settings { get; } public DirectoryInfo BasePath;
public ModData Data { get; } public ModMeta Meta;
public ModCache Cache { get; } 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; BasePath = basePath;
Data = data; Meta = meta;
Cache = new ModCache(); Resources = resources;
MetaFile = MetaFileInfo( basePath );
Order = new SortOrder( parentFolder, Meta.Name );
Order.ParentFolder.AddMod( this );
ComputeChangedItems();
} }
public bool FixSettings() public void ComputeChangedItems()
=> Settings.FixInvalidSettings( Data.Meta );
public HashSet< Utf8GamePath > GetFiles( FileInfo file )
{ {
var relPath = Utf8RelPath.FromFile( file, Data.BasePath, out var p ) ? p : Utf8RelPath.Empty; var identifier = GameData.GameData.GetIdentifier();
return ModFunctions.GetFilesForConfig( relPath, Settings, Data.Meta ); 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() 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}"; var newName = $"{mod.BasePath.Name}_{optionGroup}_{option}";
return TexToolsImport.CreateModFolder( new DirectoryInfo( Penumbra.Config.ModDirectory ), newName ); 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 idx = Penumbra.ModManager.AddMod( newDir );
var newMod = Penumbra.ModManager.Mods[ idx ]; var newMod = Penumbra.ModManager.Mods[ idx ];
@ -69,7 +69,7 @@ public class ModCleanup
return newMod; 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 var newMeta = new ModMeta
{ {
@ -82,7 +82,7 @@ public class ModCleanup
return newMeta; 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 try
{ {
@ -105,8 +105,8 @@ public class ModCleanup
} }
var newSortOrder = group.SelectionType == SelectType.Single var newSortOrder = group.SelectionType == SelectType.Single
? $"{mod.SortOrder.ParentFolder.FullName}/{mod.Meta.Name}/{group.GroupName}/{option.OptionName}" ? $"{mod.Order.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}";
CreateNewMod( newDir, newSortOrder ); CreateNewMod( newDir, newSortOrder );
} }
catch( Exception e ) 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 ) 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. // Rename the SortOrderName of a single mod. Slashes are replaced by Backslashes.
// Saves and returns true if anything changed. // 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 ) ) if( RenameNoSave( mod, newName ) )
{ {
@ -63,7 +63,7 @@ public static partial class ModFileSystem
// Move a single mod to the target folder. // Move a single mod to the target folder.
// Returns true and saves if anything changed. // 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 ) ) 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. // Move a mod to the filesystem location specified by sortOrder and rename its SortOrderName.
// Creates all necessary Subfolders. // 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 split = sortOrder.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries );
var folder = Root; var folder = Root;
@ -129,7 +129,7 @@ public static partial class ModFileSystem
{ {
foreach( var mod in target.AllMods( true ) ) 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(); 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. // 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 ) if( ReferenceEquals( mod.Order.ParentFolder, Root )
&& string.Equals( mod.SortOrder.SortOrderName, mod.Meta.Name.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) ) && string.Equals( mod.Order.SortOrderName, mod.Meta.Name.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) )
{ {
Penumbra.Config.ModSortOrder.Remove( mod.BasePath.Name ); Penumbra.Config.ModSortOrder.Remove( mod.BasePath.Name );
} }
else else
{ {
Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullName; Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.Order.FullName;
} }
Penumbra.Config.Save(); Penumbra.Config.Save();
@ -184,30 +184,30 @@ public static partial class ModFileSystem
return true; return true;
} }
private static bool RenameNoSave( ModData mod, string newName ) private static bool RenameNoSave( Mod.Mod mod, string newName )
{ {
newName = newName.Replace( '/', '\\' ); newName = newName.Replace( '/', '\\' );
if( mod.SortOrder.SortOrderName == newName ) if( mod.Order.SortOrderName == newName )
{ {
return false; return false;
} }
mod.SortOrder.ParentFolder.RemoveModIgnoreEmpty( mod ); mod.Order.ParentFolder.RemoveModIgnoreEmpty( mod );
mod.SortOrder = new SortOrder( mod.SortOrder.ParentFolder, newName ); mod.Order = new Mod.Mod.SortOrder( mod.Order.ParentFolder, newName );
mod.SortOrder.ParentFolder.AddMod( mod ); mod.Order.ParentFolder.AddMod( mod );
return true; 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 ) ) if( ReferenceEquals( target, oldParent ) )
{ {
return false; return false;
} }
oldParent.RemoveMod( mod ); oldParent.RemoveMod( mod );
mod.SortOrder = new SortOrder( target, mod.SortOrder.SortOrderName ); mod.Order = new Mod.Mod.SortOrder( target, mod.Order.SortOrderName );
target.AddMod( mod ); target.AddMod( mod );
return true; return true;
} }

View file

@ -27,7 +27,7 @@ namespace Penumbra.Mods
} }
public List< ModFolder > SubFolders { get; } = new(); 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 ) public ModFolder( ModFolder parent, string name )
{ {
@ -45,7 +45,7 @@ namespace Penumbra.Mods
=> SubFolders.Sum( f => f.TotalDescendantFolders() ); => SubFolders.Sum( f => f.TotalDescendantFolders() );
// Return all descendant mods in the specified order. // Return all descendant mods in the specified order.
public IEnumerable< ModData > AllMods( bool foldersFirst ) public IEnumerable< Mod.Mod > AllMods( bool foldersFirst )
{ {
if( foldersFirst ) if( foldersFirst )
{ {
@ -59,7 +59,7 @@ namespace Penumbra.Mods
return folder.AllMods( false ); 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. // Add the given mod as a child, if it is not already a child.
// Returns the index of the found or inserted mod. // 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 ); var idx = Mods.BinarySearch( mod, ModComparer );
if( idx >= 0 ) if( idx >= 0 )
@ -132,7 +132,7 @@ namespace Penumbra.Mods
// Remove mod as a child if it exists. // Remove mod as a child if it exists.
// If this folder is empty afterwards, remove it from its parent. // If this folder is empty afterwards, remove it from its parent.
public void RemoveMod( ModData mod ) public void RemoveMod( Mod.Mod mod )
{ {
RemoveModIgnoreEmpty( mod ); RemoveModIgnoreEmpty( mod );
CheckEmpty(); CheckEmpty();
@ -157,20 +157,20 @@ namespace Penumbra.Mods
: string.Compare( x?.Name ?? string.Empty, y?.Name ?? string.Empty, CompareType ); : 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; 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. // 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. // 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 ) ) if( ReferenceEquals( x, y ) )
{ {
return 0; 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 ) if( cmp != 0 )
{ {
return cmp; return cmp;
@ -193,7 +193,7 @@ namespace Penumbra.Mods
for( ; modIdx < Mods.Count; ++modIdx ) for( ; modIdx < Mods.Count; ++modIdx )
{ {
var mod = Mods[ modIdx ]; var mod = Mods[ modIdx ];
var modString = mod.SortOrder.SortOrderName; var modString = mod.Order.SortOrderName;
if( string.Compare( folderString, modString, StringComparison.InvariantCultureIgnoreCase ) > 0 ) if( string.Compare( folderString, modString, StringComparison.InvariantCultureIgnoreCase ) > 0 )
{ {
yield return mod; 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. // 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 ); var idx = Mods.BinarySearch( mod, ModComparer );
if( idx >= 0 ) if( idx >= 0 )

View file

@ -9,9 +9,9 @@ namespace Penumbra.Mods;
public partial class ModManagerNew 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; => _mods;
public void DiscoverMods() 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. // Contains all change functions on a specific mod that also require corresponding changes to collections.
public static class ModManagerEditExtensions 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 ) ) if( newName.Length == 0 || string.Equals( newName, mod.Meta.Name, StringComparison.InvariantCulture ) )
{ {
@ -25,23 +25,23 @@ public static class ModManagerEditExtensions
return true; 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; 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 ) if( newSortOrder == string.Empty || newSortOrder == inRoot.SortOrderName )
{ {
mod.SortOrder = inRoot; mod.Order = inRoot;
manager.Config.ModSortOrder.Remove( mod.BasePath.Name ); manager.Config.ModSortOrder.Remove( mod.BasePath.Name );
} }
else else
{ {
mod.Move( newSortOrder ); mod.Move( newSortOrder );
manager.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullPath; manager.Config.ModSortOrder[ mod.BasePath.Name ] = mod.Order.FullPath;
} }
manager.Config.Save(); manager.Config.Save();
@ -49,7 +49,7 @@ public static class ModManagerEditExtensions
return true; 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 ) if( move )
{ {
@ -73,7 +73,7 @@ public static class ModManagerEditExtensions
var oldBasePath = mod.BasePath; var oldBasePath = mod.BasePath;
mod.BasePath = newDir; mod.BasePath = newDir;
mod.MetaFile = ModData.MetaFileInfo( newDir ); mod.MetaFile = Mod.Mod.MetaFileInfo( newDir );
manager.UpdateMod( mod ); manager.UpdateMod( mod );
if( manager.Config.ModSortOrder.ContainsKey( oldBasePath.Name ) ) if( manager.Config.ModSortOrder.ContainsKey( oldBasePath.Name ) )
@ -95,7 +95,7 @@ public static class ModManagerEditExtensions
return true; 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 ) SelectType type = SelectType.Single )
{ {
if( newGroupName == oldGroupName || mod.Meta.Groups.ContainsKey( newGroupName ) ) if( newGroupName == oldGroupName || mod.Meta.Groups.ContainsKey( newGroupName ) )
@ -157,7 +157,7 @@ public static class ModManagerEditExtensions
return true; 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 ) if( optionIdx < 0 || optionIdx >= group.Options.Count )
{ {
@ -202,7 +202,7 @@ public static class ModManagerEditExtensions
if( collection.HasCache && settings.Enabled ) if( collection.HasCache && settings.Enabled )
{ {
collection.CalculateEffectiveFileList( mod.Resources.MetaManipulations.Count > 0, 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.Collections;
using Penumbra.Interop.Loader; using Penumbra.Interop.Loader;
using Penumbra.Interop.Resolver; using Penumbra.Interop.Resolver;
using Penumbra.Mod;
namespace Penumbra; namespace Penumbra;
@ -34,8 +35,8 @@ public class Penumbra : IDalamudPlugin
public static ResidentResourceManager ResidentResources { get; private set; } = null!; public static ResidentResourceManager ResidentResources { get; private set; } = null!;
public static CharacterUtility CharacterUtility { get; private set; } = null!; public static CharacterUtility CharacterUtility { get; private set; } = null!;
public static ModManager ModManager { get; private set; } = null!; public static Mod.Mod.Manager ModManager { get; private set; } = null!;
public static CollectionManager2 CollectionManager { get; private set; } = null!; public static ModCollection.Manager CollectionManager { get; private set; } = null!;
public static ResourceLoader ResourceLoader { get; set; } = null!; public static ResourceLoader ResourceLoader { get; set; } = null!;
public ResourceLogger ResourceLogger { get; } public ResourceLogger ResourceLogger { get; }
@ -66,9 +67,9 @@ public class Penumbra : IDalamudPlugin
CharacterUtility = new CharacterUtility(); CharacterUtility = new CharacterUtility();
ResourceLoader = new ResourceLoader( this ); ResourceLoader = new ResourceLoader( this );
ResourceLogger = new ResourceLogger( ResourceLoader ); ResourceLogger = new ResourceLogger( ResourceLoader );
ModManager = new ModManager(); ModManager = new Mod.Mod.Manager();
ModManager.DiscoverMods(); ModManager.DiscoverMods();
CollectionManager = new CollectionManager2( ModManager ); CollectionManager = new ModCollection.Manager( ModManager );
ObjectReloader = new ObjectReloader(); ObjectReloader = new ObjectReloader();
PathResolver = new PathResolver( ResourceLoader ); PathResolver = new PathResolver( ResourceLoader );
@ -223,9 +224,9 @@ public class Penumbra : IDalamudPlugin
type = type.ToLowerInvariant(); type = type.ToLowerInvariant();
collectionName = collectionName.ToLowerInvariant(); collectionName = collectionName.ToLowerInvariant();
var collection = string.Equals( collectionName, ModCollection2.Empty.Name, StringComparison.InvariantCultureIgnoreCase ) var collection = string.Equals( collectionName, ModCollection.Empty.Name, StringComparison.InvariantCultureIgnoreCase )
? ModCollection2.Empty ? ModCollection.Empty
: CollectionManager[collectionName]; : CollectionManager[ collectionName ];
if( collection == null ) if( collection == null )
{ {
Dalamud.Chat.Print( $"The collection {collection} does not exist." ); Dalamud.Chat.Print( $"The collection {collection} does not exist." );
@ -241,7 +242,7 @@ public class Penumbra : IDalamudPlugin
return false; return false;
} }
CollectionManager.SetCollection( collection, CollectionType.Default ); CollectionManager.SetCollection( collection, ModCollection.Type.Default );
Dalamud.Chat.Print( $"Set {collection.Name} as default collection." ); Dalamud.Chat.Print( $"Set {collection.Name} as default collection." );
SettingsInterface.ResetDefaultCollection(); SettingsInterface.ResetDefaultCollection();
return true; return true;

View file

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

View file

@ -22,9 +22,8 @@ public partial class SettingsInterface
private readonly Selector _selector; private readonly Selector _selector;
private string _collectionNames = null!; private string _collectionNames = null!;
private string _collectionNamesWithNone = null!; private string _collectionNamesWithNone = null!;
private ModCollection2[] _collections = null!; private ModCollection[] _collections = null!;
private int _currentCollectionIndex; private int _currentCollectionIndex;
private int _currentForcedIndex;
private int _currentDefaultIndex; private int _currentDefaultIndex;
private readonly Dictionary< string, int > _currentCharacterIndices = new(); private readonly Dictionary< string, int > _currentCharacterIndices = new();
private string _newCollectionName = string.Empty; private string _newCollectionName = string.Empty;
@ -32,14 +31,14 @@ public partial class SettingsInterface
private void UpdateNames() 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'; _collectionNames = string.Join( "\0", _collections.Skip( 1 ).Select( c => c.Name ) ) + '\0';
_collectionNamesWithNone = "None\0" + _collectionNames; _collectionNamesWithNone = "None\0" + _collectionNames;
UpdateIndices(); UpdateIndices();
} }
private int GetIndex( ModCollection2 collection ) private int GetIndex( ModCollection collection )
{ {
var ret = _collections.IndexOf( c => c.Name == collection.Name ); var ret = _collections.IndexOf( c => c.Name == collection.Name );
if( ret < 0 ) if( ret < 0 )
@ -52,20 +51,17 @@ public partial class SettingsInterface
} }
private void UpdateIndex() private void UpdateIndex()
=> _currentCollectionIndex = GetIndex( Penumbra.CollectionManager.CurrentCollection ) - 1; => _currentCollectionIndex = GetIndex( Penumbra.CollectionManager.Current ) - 1;
public void UpdateForcedIndex()
=> _currentForcedIndex = GetIndex( Penumbra.CollectionManager.ForcedCollection );
public void UpdateDefaultIndex() public void UpdateDefaultIndex()
=> _currentDefaultIndex = GetIndex( Penumbra.CollectionManager.DefaultCollection ); => _currentDefaultIndex = GetIndex( Penumbra.CollectionManager.Default );
private void UpdateCharacterIndices() private void UpdateCharacterIndices()
{ {
_currentCharacterIndices.Clear(); _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(); UpdateIndex();
UpdateDefaultIndex(); UpdateDefaultIndex();
UpdateForcedIndex();
UpdateCharacterIndices(); UpdateCharacterIndices();
} }
@ -83,24 +78,22 @@ public partial class SettingsInterface
UpdateNames(); 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(); UpdateNames();
SetCurrentCollection( Penumbra.CollectionManager.ByName( _newCollectionName )!, true ); SetCurrentCollection( Penumbra.CollectionManager[ _newCollectionName ]!, true );
} }
_newCollectionName = string.Empty; _newCollectionName = string.Empty;
} }
private void DrawCleanCollectionButton() private static void DrawCleanCollectionButton()
{ {
if( ImGui.Button( "Clean Settings" ) ) if( ImGui.Button( "Clean Settings" ) )
{ {
var changes = ModFunctions.CleanUpCollection( Penumbra.CollectionManager.CurrentCollection.Settings, Penumbra.CollectionManager.Current.CleanUnavailableSettings();
Penumbra.ModManager.BasePath.EnumerateDirectories() );
Penumbra.CollectionManager.CurrentCollection.UpdateSettings( changes );
} }
ImGuiCustom.HoverTooltip( ImGuiCustom.HoverTooltip(
@ -120,14 +113,14 @@ public partial class SettingsInterface
if( ImGui.Button( "Create New Empty Collection" ) && _newCollectionName.Length > 0 ) if( ImGui.Button( "Create New Empty Collection" ) && _newCollectionName.Length > 0 )
{ {
CreateNewCollection( new Dictionary< string, ModSettings >() ); CreateNewCollection( false );
} }
var hover = ImGui.IsItemHovered(); var hover = ImGui.IsItemHovered();
ImGui.SameLine(); ImGui.SameLine();
if( ImGui.Button( "Duplicate Current Collection" ) && _newCollectionName.Length > 0 ) if( ImGui.Button( "Duplicate Current Collection" ) && _newCollectionName.Length > 0 )
{ {
CreateNewCollection( Penumbra.CollectionManager.CurrentCollection.Settings ); CreateNewCollection( true );
} }
hover |= ImGui.IsItemHovered(); hover |= ImGui.IsItemHovered();
@ -138,13 +131,12 @@ public partial class SettingsInterface
ImGui.SetTooltip( "Please enter a name before creating a collection." ); ImGui.SetTooltip( "Please enter a name before creating a collection." );
} }
var deleteCondition = Penumbra.CollectionManager.Collections.Count > 1 var deleteCondition = Penumbra.CollectionManager.Current.Name != ModCollection.DefaultCollection;
&& Penumbra.CollectionManager.CurrentCollection.Name != ModCollection.DefaultCollection;
ImGui.SameLine(); ImGui.SameLine();
if( ImGuiCustom.DisableButton( "Delete Current Collection", deleteCondition ) ) if( ImGuiCustom.DisableButton( "Delete Current Collection", deleteCondition ) )
{ {
Penumbra.CollectionManager.RemoveCollection( Penumbra.CollectionManager.CurrentCollection.Name ); Penumbra.CollectionManager.RemoveCollection( Penumbra.CollectionManager.Current );
SetCurrentCollection( Penumbra.CollectionManager.CurrentCollection, true ); SetCurrentCollection( Penumbra.CollectionManager.Current, true );
UpdateNames(); UpdateNames();
} }
@ -167,7 +159,7 @@ public partial class SettingsInterface
return; return;
} }
Penumbra.CollectionManager.SetCollection( _collections[ idx + 1 ], CollectionType.Current ); Penumbra.CollectionManager.SetCollection( _collections[ idx + 1 ], ModCollection.Type.Current );
_currentCollectionIndex = idx; _currentCollectionIndex = idx;
_selector.Cache.TriggerListReset(); _selector.Cache.TriggerListReset();
if( _selector.Mod != null ) 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; var idx = Array.IndexOf( _collections, collection ) - 1;
if( idx >= 0 ) if( idx >= 0 )
@ -206,7 +198,7 @@ public partial class SettingsInterface
ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth );
if( ImGui.Combo( "##Default Collection", ref index, _collectionNamesWithNone ) && index != _currentDefaultIndex ) 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; _currentDefaultIndex = index;
} }
@ -219,34 +211,6 @@ public partial class SettingsInterface
ImGui.Text( "Default Collection" ); 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() private void DrawNewCharacterCollection()
{ {
ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth );
@ -339,16 +303,15 @@ public partial class SettingsInterface
} }
DrawDefaultCollectionSelector(); 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 idx = _currentCharacterIndices[ name ];
var tmp = idx; var tmp = idx;
ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth );
if( ImGui.Combo( $"##{name}collection", ref tmp, _collectionNamesWithNone ) && idx != tmp ) 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; _currentCharacterIndices[ name ] = tmp;
} }

View file

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

View file

@ -100,24 +100,66 @@ public partial class SettingsInterface
return !_filePathFilter.Any() || kvp.Item3.Contains( _filePathFilterLower ); 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 );
{
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 );
//}
} }
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() public void Draw()
@ -133,17 +175,12 @@ public partial class SettingsInterface
const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX; const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX;
var modManager = Penumbra.ModManager; var resolved = Penumbra.CollectionManager.Default.ResolvedFiles;
var defaultCollection = Penumbra.CollectionManager.DefaultCollection.Cache; var meta = Penumbra.CollectionManager.Default.MetaCache;
var forcedCollection = Penumbra.CollectionManager.ForcedCollection.Cache; var metaCount = meta?.Count ?? 0;
var resolvedCount = resolved.Count;
var (defaultResolved, defaultMeta) = defaultCollection != null var totalLines = resolvedCount + metaCount;
? ( 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;
if( totalLines == 0 ) if( totalLines == 0 )
{ {
return; return;
@ -159,7 +196,7 @@ public partial class SettingsInterface
if( _filePathFilter.Length > 0 || _gamePathFilter.Length > 0 ) if( _filePathFilter.Length > 0 || _gamePathFilter.Length > 0 )
{ {
DrawFilteredRows( defaultCollection, forcedCollection ); DrawFilteredRows( Penumbra.CollectionManager.Default );
} }
else else
{ {
@ -178,29 +215,17 @@ public partial class SettingsInterface
{ {
var row = actualRow; var row = actualRow;
ImGui.TableNextRow(); ImGui.TableNextRow();
if( row < defaultResolved ) if( row < resolvedCount )
{ {
var (gamePath, file) = defaultCollection!.ResolvedFiles.ElementAt( row ); var (gamePath, file) = resolved.ElementAt( row );
DrawLine( gamePath, file ); DrawLine( gamePath, file );
} }
else if( ( row -= defaultResolved ) < defaultMeta ) else if( ( row -= resolved.Count ) < metaCount )
{ {
// TODO // TODO
//var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row ); //var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row );
DrawLine( 0.ToString(), 0.ToString() ); 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.Collections.Generic;
using System.Linq; using System.Linq;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.Mod;
using Penumbra.Mods; 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; get => _stateFilter;
public const uint DisabledModColor = 0xFF666666u; set
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; var diff = _stateFilter != value;
set _stateFilter = value;
if( diff )
{ {
var diff = _stateFilter != value; TriggerFilterReset();
_stateFilter = value;
if( diff )
{
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(); 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; 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; return ret;
} }
if( _modFilterAuthor.Any() && !mod.Data.Meta.LowerAuthor.Contains( _modFilterAuthor ) ) ret.Item2 = ret.Item2 == 0 ? DisabledModColor : ret.Item2;
{ }
return ret;
}
if( _modFilterChanges.Any() && !mod.Data.LowerChangedItemsString.Contains( _modFilterChanges ) ) if( mod.Settings.Enabled && !StateFilter.HasFlag( ModFilter.Enabled ) )
{ {
return ret; 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( !StateFilter.HasFlag( ModFilter.UnsolvedConflict ) )
}
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; return ret;
} }
ret.Item2 = ret.Item2 == 0 ? DisabledModColor : ret.Item2; ret.Item2 = ret.Item2 == 0 ? ConflictingModColor : ret.Item2;
} }
else
if( mod.Settings.Enabled && !StateFilter.HasFlag( ModFilter.Enabled ) )
{ {
return ret; if( !StateFilter.HasFlag( ModFilter.SolvedConflict ) )
}
if( mod.Cache.Conflicts.Any() )
{
if( mod.Cache.Conflicts.Keys.Any( m => m.Settings.Priority == mod.Settings.Priority ) )
{ {
if( !StateFilter.HasFlag( ModFilter.UnsolvedConflict ) ) return ret;
{
return ret;
}
ret.Item2 = ret.Item2 == 0 ? ConflictingModColor : ret.Item2;
} }
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 ) ) }
{ else if( !StateFilter.HasFlag( ModFilter.NoConflict ) )
return ret; {
}
ret.Item1 = true;
if( isNew )
{
ret.Item2 = NewModColor;
}
SetFolderAndParentsVisible( mod.Data.SortOrder.ParentFolder );
return ret; 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.Enums;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
using Penumbra.Mod; using Penumbra.Mod;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.UI.Custom; 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. // This is only drawn when we have a mod selected, so we can forgive nulls.
private Mod.Mod Mod private FullMod Mod
=> _selector.Mod!; => _selector.Mod!;
private ModMeta Meta private ModMeta Meta
=> Mod.Data.Meta; => Mod.Data.Meta;
private void Save()
{
Penumbra.CollectionManager.CurrentCollection.Save();
}
private void DrawAboutTab() private void DrawAboutTab()
{ {
if( !_editMode && Meta.Description.Length == 0 ) if( !_editMode && Meta.Description.Length == 0 )
@ -189,7 +185,8 @@ public partial class SettingsInterface
private void DrawConflictTab() 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; return;
} }
@ -203,32 +200,43 @@ public partial class SettingsInterface
} }
raii.Push( ImGui.EndListBox ); raii.Push( ImGui.EndListBox );
using var indent = ImGuiRaii.PushIndent( 0 ); using var indent = ImGuiRaii.PushIndent( 0 );
foreach( var (mod, (files, manipulations)) in Mod.Cache.Conflicts ) 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(); if( conflict.Conflict is Utf8GamePath p )
ImGui.Text( $"(Priority {mod.Settings.Priority})" );
indent.Push( 15f );
foreach( var file in files )
{ {
unsafe unsafe
{ {
ImGuiNative.igSelectable_Bool( file.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero ); ImGuiNative.igSelectable_Bool( p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero );
} }
} }
else if( conflict.Conflict is MetaManipulation m )
foreach( var manip in manipulations )
{ {
//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; _fullFilenameList = null;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
var idx = Penumbra.ModManager.Mods.IndexOf( Mod.Data );
// Since files may have changed, we need to recompute effective files. // Since files may have changed, we need to recompute effective files.
foreach( var collection in Penumbra.CollectionManager.Collections foreach( var collection in Penumbra.CollectionManager
.Where( c => c.Cache != null && c.Settings[ Mod.Data.BasePath.Name ].Enabled ) ) .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. // 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; var oldEnabled = enabled;
if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled ) if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled )
{ {
Mod.Settings.Settings[ group.GroupName ] ^= 1 << idx; Penumbra.CollectionManager.Current.SetModSetting( Mod.Data.Index, group.GroupName,
Save(); Mod.Settings.Settings[ group.GroupName ] ^ ( 1 << idx ) );
// If the mod is enabled, recalculate files and filters. // If the mod is enabled, recalculate files and filters.
if( Mod.Settings.Enabled ) 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 ) , group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count )
&& code != Mod.Settings.Settings[ group.GroupName ] ) && code != Mod.Settings.Settings[ group.GroupName ] )
{ {
Mod.Settings.Settings[ group.GroupName ] = code; Penumbra.CollectionManager.Current.SetModSetting( Mod.Data.Index, group.GroupName, code );
Save();
// If the mod is enabled, recalculate files and filters.
if( Mod.Settings.Enabled ) 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 ) 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() group.Options.Add( new Option()
{ {
OptionName = newName, OptionName = newName,
@ -245,11 +245,6 @@ public partial class SettingsInterface
} }
} }
if( code != oldSetting )
{
Save();
}
ImGui.SameLine(); ImGui.SameLine();
var labelEditPos = ImGui.GetCursorPosX(); var labelEditPos = ImGui.GetCursorPosX();
DrawSingleSelectorEditGroup( group ); DrawSingleSelectorEditGroup( group );

View file

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

View file

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

View file

@ -55,26 +55,6 @@ public partial class SettingsInterface : IDisposable
_menu.InstalledTab.Selector.Cache.TriggerListReset(); _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() public void ResetDefaultCollection()
=> _menu.CollectionsTab.UpdateDefaultIndex(); => _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 ) ) if( ValidStrings( from, to ) )
{ {