diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 6a373d14..7712c56c 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -8,6 +8,7 @@ using Lumina.Data; using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; +using Penumbra.Mod; using Penumbra.Mods; namespace Penumbra.Api; @@ -77,7 +78,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi _penumbra!.ObjectReloader.RedrawAll( setting ); } - private static string ResolvePath( string path, ModManager _, ModCollection2 collection ) + private static string ResolvePath( string path, Mod.Mod.Manager _, ModCollection collection ) { if( !Penumbra.Config.EnableMods ) { @@ -134,7 +135,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi { if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) ) { - collection = ModCollection2.Empty; + collection = ModCollection.Empty; } if( collection.HasCache ) diff --git a/Penumbra/Collections/CollectionManager.Active.cs b/Penumbra/Collections/CollectionManager.Active.cs index 3250e10d..17f11eae 100644 --- a/Penumbra/Collections/CollectionManager.Active.cs +++ b/Penumbra/Collections/CollectionManager.Active.cs @@ -1,261 +1,261 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using Dalamud.Logging; -using Penumbra.Meta.Manager; using Penumbra.Mod; -using Penumbra.Mods; using Penumbra.Util; namespace Penumbra.Collections; -public sealed partial class CollectionManager2 +public partial class ModCollection { - // Is invoked after the collections actually changed. - public event CollectionChangeDelegate? CollectionChanged; - - private int _currentIdx = -1; - private int _defaultIdx = -1; - private int _defaultNameIdx = 0; - - public ModCollection2 Current - => this[ _currentIdx ]; - - public ModCollection2 Default - => this[ _defaultIdx ]; - - private readonly Dictionary< string, int > _character = new(); - - public ModCollection2 Character( string name ) - => _character.TryGetValue( name, out var idx ) ? _collections[ idx ] : Default; - - public bool HasCharacterCollections - => _character.Count > 0; - - private void OnModChanged( ModChangeType type, int idx, ModData mod ) + public sealed partial class Manager { - switch( type ) + // Is invoked after the collections actually changed. + public event CollectionChangeDelegate? CollectionChanged; + + private int _currentIdx = 1; + private int _defaultIdx = 0; + private int _defaultNameIdx = 0; + + public ModCollection Current + => this[ _currentIdx ]; + + public ModCollection Default + => this[ _defaultIdx ]; + + private readonly Dictionary< string, int > _character = new(); + + public ModCollection Character( string name ) + => _character.TryGetValue( name, out var idx ) ? this[ idx ] : Default; + + public IEnumerable< (string, ModCollection) > Characters + => _character.Select( kvp => ( kvp.Key, this[ kvp.Value ] ) ); + + public bool HasCharacterCollections + => _character.Count > 0; + + private void OnModChanged( Mod.Mod.ChangeType type, int idx, Mod.Mod mod ) { - case ModChangeType.Added: - foreach( var collection in _collections ) - { - collection.AddMod( mod ); - } - - foreach( var collection in _collections.Where( c => c.HasCache && c[ ^1 ].Settings?.Enabled == true ) ) - { - collection.UpdateCache(); - } - - break; - case ModChangeType.Removed: - var list = new List< ModSettings? >( _collections.Count ); - foreach( var collection in _collections ) - { - list.Add( collection[ idx ].Settings ); - collection.RemoveMod( mod, idx ); - } - - foreach( var (collection, _) in _collections.Zip( list ).Where( c => c.First.HasCache && c.Second?.Enabled == true ) ) - { - collection.UpdateCache(); - } - - break; - case ModChangeType.Changed: - foreach( var collection in _collections.Where( - collection => collection.Settings[ idx ]?.FixInvalidSettings( mod.Meta ) ?? false ) ) - { - collection.Save(); - } - - foreach( var collection in _collections.Where( c => c.HasCache && c[ idx ].Settings?.Enabled == true ) ) - { - collection.UpdateCache(); - } - - break; - default: throw new ArgumentOutOfRangeException( nameof( type ), type, null ); - } - } - - private void CreateNecessaryCaches() - { - if( _defaultIdx >= 0 ) - { - Default.CreateCache(); - } - - if( _currentIdx >= 0 ) - { - Current.CreateCache(); - } - - foreach( var idx in _character.Values.Where( i => i >= 0 ) ) - { - _collections[ idx ].CreateCache(); - } - } - - public void UpdateCaches() - { - foreach( var collection in _collections ) - { - collection.UpdateCache(); - } - } - - private void RemoveCache( int idx ) - { - if( idx != _defaultIdx && idx != _currentIdx && _character.All( kvp => kvp.Value != idx ) ) - { - _collections[ idx ].ClearCache(); - } - } - - public void SetCollection( string name, CollectionType type, string? characterName = null ) - => SetCollection( GetIndexForCollectionName( name ), type, characterName ); - - public void SetCollection( ModCollection2 collection, CollectionType type, string? characterName = null ) - => SetCollection( GetIndexForCollectionName( collection.Name ), type, characterName ); - - public void SetCollection( int newIdx, CollectionType type, string? characterName = null ) - { - var oldCollectionIdx = type switch - { - CollectionType.Default => _defaultIdx, - CollectionType.Current => _currentIdx, - CollectionType.Character => characterName?.Length > 0 - ? _character.TryGetValue( characterName, out var c ) - ? c - : _defaultIdx - : -2, - _ => -2, - }; - - if( oldCollectionIdx == -2 || newIdx == oldCollectionIdx ) - { - return; - } - - var newCollection = this[ newIdx ]; - if( newIdx >= 0 ) - { - newCollection.CreateCache(); - } - - RemoveCache( oldCollectionIdx ); - switch( type ) - { - case CollectionType.Default: - _defaultIdx = newIdx; - Penumbra.Config.DefaultCollection = newCollection.Name; - Penumbra.ResidentResources.Reload(); - Default.SetFiles(); - break; - case CollectionType.Current: - _currentIdx = newIdx; - Penumbra.Config.CurrentCollection = newCollection.Name; - break; - case CollectionType.Character: - _character[ characterName! ] = newIdx; - Penumbra.Config.CharacterCollections[ characterName! ] = newCollection.Name; - break; - } - - CollectionChanged?.Invoke( this[ oldCollectionIdx ], newCollection, type, characterName ); - Penumbra.Config.Save(); - } - - public bool CreateCharacterCollection( string characterName ) - { - if( _character.ContainsKey( characterName ) ) - { - return false; - } - - _character[ characterName ] = -1; - Penumbra.Config.CharacterCollections[ characterName ] = ModCollection2.Empty.Name; - Penumbra.Config.Save(); - CollectionChanged?.Invoke( null, ModCollection2.Empty, CollectionType.Character, characterName ); - return true; - } - - public void RemoveCharacterCollection( string characterName ) - { - if( _character.TryGetValue( characterName, out var collection ) ) - { - RemoveCache( collection ); - _character.Remove( characterName ); - CollectionChanged?.Invoke( this[ collection ], null, CollectionType.Character, characterName ); - } - - if( Penumbra.Config.CharacterCollections.Remove( characterName ) ) - { - Penumbra.Config.Save(); - } - } - - private int GetIndexForCollectionName( string name ) - { - if( name.Length == 0 || name == ModCollection2.DefaultCollection ) - { - return -1; - } - - var idx = _collections.IndexOf( c => c.Name == Penumbra.Config.DefaultCollection ); - return idx < 0 ? -2 : idx; - } - - public void LoadCollections() - { - var configChanged = false; - _defaultIdx = GetIndexForCollectionName( Penumbra.Config.DefaultCollection ); - if( _defaultIdx == -2 ) - { - PluginLog.Error( $"Last choice of Default Collection {Penumbra.Config.DefaultCollection} is not available, reset to None." ); - _defaultIdx = -1; - Penumbra.Config.DefaultCollection = this[ _defaultIdx ].Name; - configChanged = true; - } - - _currentIdx = GetIndexForCollectionName( Penumbra.Config.CurrentCollection ); - if( _currentIdx == -2 ) - { - PluginLog.Error( $"Last choice of Current Collection {Penumbra.Config.CurrentCollection} is not available, reset to Default." ); - _currentIdx = _defaultNameIdx; - Penumbra.Config.DefaultCollection = this[ _currentIdx ].Name; - configChanged = true; - } - - if( LoadCharacterCollections() || configChanged ) - { - Penumbra.Config.Save(); - } - - CreateNecessaryCaches(); - } - - private bool LoadCharacterCollections() - { - var configChanged = false; - foreach( var (player, collectionName) in Penumbra.Config.CharacterCollections.ToArray() ) - { - var idx = GetIndexForCollectionName( collectionName ); - if( idx == -2 ) + var meta = mod.Resources.MetaManipulations.Count > 0; + switch( type ) { - PluginLog.Error( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to None." ); - _character.Add( player, -1 ); - Penumbra.Config.CharacterCollections[ player ] = ModCollection2.Empty.Name; - configChanged = true; - } - else - { - _character.Add( player, idx ); + case Mod.Mod.ChangeType.Added: + foreach( var collection in this ) + { + collection.AddMod( mod ); + } + + foreach( var collection in this.Where( c => c.HasCache && c[ ^1 ].Settings?.Enabled == true ) ) + { + collection.CalculateEffectiveFileList( meta, collection == Penumbra.CollectionManager.Default ); + } + + break; + case Mod.Mod.ChangeType.Removed: + var list = new List< ModSettings? >( _collections.Count ); + foreach( var collection in this ) + { + list.Add( collection[ idx ].Settings ); + collection.RemoveMod( mod, idx ); + } + + foreach( var (collection, _) in this.Zip( list ).Where( c => c.First.HasCache && c.Second?.Enabled == true ) ) + { + collection.CalculateEffectiveFileList( meta, collection == Penumbra.CollectionManager.Default ); + } + + break; + case Mod.Mod.ChangeType.Changed: + foreach( var collection in this.Where( + collection => collection.Settings[ idx ]?.FixInvalidSettings( mod.Meta ) ?? false ) ) + { + collection.Save(); + } + + foreach( var collection in this.Where( c => c.HasCache && c[ idx ].Settings?.Enabled == true ) ) + { + collection.CalculateEffectiveFileList( meta, collection == Penumbra.CollectionManager.Default ); + } + + break; + default: throw new ArgumentOutOfRangeException( nameof( type ), type, null ); } } - return configChanged; + private void CreateNecessaryCaches() + { + if( _defaultIdx > Empty.Index ) + { + Default.CreateCache(true); + } + + if( _currentIdx > Empty.Index ) + { + Current.CreateCache(false); + } + + foreach( var idx in _character.Values.Where( i => i > Empty.Index ) ) + { + _collections[ idx ].CreateCache(false); + } + } + + public void ForceCacheUpdates() + { + foreach( var collection in this ) + { + collection.ForceCacheUpdate(collection == Default); + } + } + + private void RemoveCache( int idx ) + { + if( idx != _defaultIdx && idx != _currentIdx && _character.All( kvp => kvp.Value != idx ) ) + { + _collections[ idx ].ClearCache(); + } + } + + public void SetCollection( ModCollection collection, Type type, string? characterName = null ) + => SetCollection( collection.Index, type, characterName ); + + public void SetCollection( int newIdx, Type type, string? characterName = null ) + { + var oldCollectionIdx = type switch + { + Type.Default => _defaultIdx, + Type.Current => _currentIdx, + Type.Character => characterName?.Length > 0 + ? _character.TryGetValue( characterName, out var c ) + ? c + : _defaultIdx + : -1, + _ => -1, + }; + + if( oldCollectionIdx == -1 || newIdx == oldCollectionIdx ) + { + return; + } + + var newCollection = this[ newIdx ]; + if( newIdx > Empty.Index ) + { + newCollection.CreateCache(false); + } + + RemoveCache( oldCollectionIdx ); + switch( type ) + { + case Type.Default: + _defaultIdx = newIdx; + Penumbra.Config.DefaultCollection = newCollection.Name; + Penumbra.ResidentResources.Reload(); + Default.SetFiles(); + break; + case Type.Current: + _currentIdx = newIdx; + Penumbra.Config.CurrentCollection = newCollection.Name; + break; + case Type.Character: + _character[ characterName! ] = newIdx; + Penumbra.Config.CharacterCollections[ characterName! ] = newCollection.Name; + break; + } + + CollectionChanged?.Invoke( this[ oldCollectionIdx ], newCollection, type, characterName ); + Penumbra.Config.Save(); + } + + public bool CreateCharacterCollection( string characterName ) + { + if( _character.ContainsKey( characterName ) ) + { + return false; + } + + _character[ characterName ] = Empty.Index; + Penumbra.Config.CharacterCollections[ characterName ] = Empty.Name; + Penumbra.Config.Save(); + CollectionChanged?.Invoke( null, Empty, Type.Character, characterName ); + return true; + } + + public void RemoveCharacterCollection( string characterName ) + { + if( _character.TryGetValue( characterName, out var collection ) ) + { + RemoveCache( collection ); + _character.Remove( characterName ); + CollectionChanged?.Invoke( this[ collection ], null, Type.Character, characterName ); + } + + if( Penumbra.Config.CharacterCollections.Remove( characterName ) ) + { + Penumbra.Config.Save(); + } + } + + private int GetIndexForCollectionName( string name ) + { + if( name.Length == 0 ) + { + return Empty.Index; + } + + return _collections.IndexOf( c => c.Name == name ); + } + + public void LoadCollections() + { + var configChanged = false; + _defaultIdx = GetIndexForCollectionName( Penumbra.Config.DefaultCollection ); + if( _defaultIdx < 0 ) + { + PluginLog.Error( $"Last choice of Default Collection {Penumbra.Config.DefaultCollection} is not available, reset to None." ); + _defaultIdx = Empty.Index; + Penumbra.Config.DefaultCollection = this[ _defaultIdx ].Name; + configChanged = true; + } + + _currentIdx = GetIndexForCollectionName( Penumbra.Config.CurrentCollection ); + if( _currentIdx < 0 ) + { + PluginLog.Error( $"Last choice of Current Collection {Penumbra.Config.CurrentCollection} is not available, reset to Default." ); + _currentIdx = _defaultNameIdx; + Penumbra.Config.DefaultCollection = this[ _currentIdx ].Name; + configChanged = true; + } + + if( LoadCharacterCollections() || configChanged ) + { + Penumbra.Config.Save(); + } + + CreateNecessaryCaches(); + } + + private bool LoadCharacterCollections() + { + var configChanged = false; + foreach( var (player, collectionName) in Penumbra.Config.CharacterCollections.ToArray() ) + { + var idx = GetIndexForCollectionName( collectionName ); + if( idx < 0 ) + { + PluginLog.Error( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to None." ); + _character.Add( player, Empty.Index ); + Penumbra.Config.CharacterCollections[ player ] = Empty.Name; + configChanged = true; + } + else + { + _character.Add( player, idx ); + } + } + + return configChanged; + } } } \ No newline at end of file diff --git a/Penumbra/Collections/CollectionManager.cs b/Penumbra/Collections/CollectionManager.cs index 7fd81136..440f88ac 100644 --- a/Penumbra/Collections/CollectionManager.cs +++ b/Penumbra/Collections/CollectionManager.cs @@ -2,222 +2,239 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Contracts; using System.IO; using System.Linq; using Dalamud.Logging; -using Penumbra.Mods; using Penumbra.Util; namespace Penumbra.Collections; -public enum CollectionType : byte +public partial class ModCollection { - Inactive, - Default, - Character, - Current, -} - -public sealed partial class CollectionManager2 : IDisposable, IEnumerable< ModCollection2 > -{ - public delegate void CollectionChangeDelegate( ModCollection2? oldCollection, ModCollection2? newCollection, CollectionType type, - string? characterName = null ); - - private readonly ModManager _modManager; - - private readonly List< ModCollection2 > _collections = new(); - - public ModCollection2 this[ Index idx ] - => idx.Value == -1 ? ModCollection2.Empty : _collections[ idx ]; - - public ModCollection2? this[ string name ] - => ByName( name, out var c ) ? c : null; - - public bool ByName( string name, [NotNullWhen( true )] out ModCollection2? collection ) - => _collections.FindFirst( c => string.Equals( c.Name, name, StringComparison.InvariantCultureIgnoreCase ), out collection ); - - public IEnumerator< ModCollection2 > GetEnumerator() - => _collections.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - - public CollectionManager2( ModManager manager ) + public enum Type : byte { - _modManager = manager; - - _modManager.ModsRediscovered += OnModsRediscovered; - _modManager.ModChange += OnModChanged; - ReadCollections(); - LoadCollections(); + Inactive, + Default, + Character, + Current, } - public void Dispose() + public sealed partial class Manager : IDisposable, IEnumerable< ModCollection > { - _modManager.ModsRediscovered -= OnModsRediscovered; - _modManager.ModChange -= OnModChanged; - } + public delegate void CollectionChangeDelegate( ModCollection? oldCollection, ModCollection? newCollection, Type type, + string? characterName = null ); - private void OnModsRediscovered() - { - UpdateCaches(); - Default.SetFiles(); - } + private readonly Mod.Mod.Manager _modManager; - private void AddDefaultCollection() - { - var idx = _collections.IndexOf( c => c.Name == ModCollection2.DefaultCollection ); - if( idx >= 0 ) + private readonly List< ModCollection > _collections = new() { - _defaultNameIdx = idx; - return; + Empty, + }; + + public ModCollection this[ Index idx ] + => _collections[ idx ]; + + public ModCollection this[ int idx ] + => _collections[ idx ]; + + public ModCollection? this[ string name ] + => ByName( name, out var c ) ? c : null; + + public bool ByName( string name, [NotNullWhen( true )] out ModCollection? collection ) + => _collections.FindFirst( c => string.Equals( c.Name, name, StringComparison.InvariantCultureIgnoreCase ), out collection ); + + public IEnumerator< ModCollection > GetEnumerator() + => _collections.Skip( 1 ).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public Manager( Mod.Mod.Manager manager ) + { + _modManager = manager; + + _modManager.ModsRediscovered += OnModsRediscovered; + _modManager.ModChange += OnModChanged; + ReadCollections(); + LoadCollections(); } - var defaultCollection = ModCollection2.CreateNewEmpty( ModCollection2.DefaultCollection ); - defaultCollection.Save(); - _defaultNameIdx = _collections.Count; - _collections.Add( defaultCollection ); - } - - private void ApplyInheritancesAndFixSettings( IEnumerable< IReadOnlyList< string > > inheritances ) - { - foreach( var (collection, inheritance) in this.Zip( inheritances ) ) + public void Dispose() { - var changes = false; - foreach( var subCollectionName in inheritance ) - { - if( !ByName( subCollectionName, out var subCollection ) ) - { - changes = true; - PluginLog.Warning( $"Inherited collection {subCollectionName} for {collection.Name} does not exist, removed." ); - } - else if( !collection.AddInheritance( subCollection ) ) - { - changes = true; - PluginLog.Warning( $"{collection.Name} can not inherit from {subCollectionName}, removed." ); - } - } - - foreach( var (setting, mod) in collection.Settings.Zip( _modManager.Mods ).Where( s => s.First != null ) ) - { - changes |= setting!.FixInvalidSettings( mod.Meta ); - } - - if( changes ) - { - collection.Save(); - } + _modManager.ModsRediscovered -= OnModsRediscovered; + _modManager.ModChange -= OnModChanged; } - } - private void ReadCollections() - { - var collectionDir = new DirectoryInfo( ModCollection2.CollectionDirectory ); - var inheritances = new List< IReadOnlyList< string > >(); - if( collectionDir.Exists ) + private void OnModsRediscovered() { - foreach( var file in collectionDir.EnumerateFiles( "*.json" ) ) + ForceCacheUpdates(); + Default.SetFiles(); + } + + private void AddDefaultCollection() + { + var idx = _collections.IndexOf( c => c.Name == DefaultCollection ); + if( idx >= 0 ) { - var collection = ModCollection2.LoadFromFile( file, out var inheritance ); - if( collection == null || collection.Name.Length == 0 ) + _defaultNameIdx = idx; + return; + } + + var defaultCollection = CreateNewEmpty( DefaultCollection ); + defaultCollection.Save(); + _defaultNameIdx = _collections.Count; + _collections.Add( defaultCollection ); + } + + private void ApplyInheritancesAndFixSettings( IEnumerable< IReadOnlyList< string > > inheritances ) + { + foreach( var (collection, inheritance) in this.Zip( inheritances ) ) + { + var changes = false; + foreach( var subCollectionName in inheritance ) { - continue; + if( !ByName( subCollectionName, out var subCollection ) ) + { + changes = true; + PluginLog.Warning( $"Inherited collection {subCollectionName} for {collection.Name} does not exist, removed." ); + } + else if( !collection.AddInheritance( subCollection ) ) + { + changes = true; + PluginLog.Warning( $"{collection.Name} can not inherit from {subCollectionName}, removed." ); + } } - if( file.Name != $"{collection.Name.RemoveInvalidPathSymbols()}.json" ) + foreach( var (setting, mod) in collection.Settings.Zip( _modManager.Mods ).Where( s => s.First != null ) ) { - PluginLog.Warning( $"Collection {file.Name} does not correspond to {collection.Name}." ); + changes |= setting!.FixInvalidSettings( mod.Meta ); } - if( this[ collection.Name ] != null ) + if( changes ) { - PluginLog.Warning( $"Duplicate collection found: {collection.Name} already exists." ); - } - else - { - inheritances.Add( inheritance ); - _collections.Add( collection ); + collection.Save(); } } } - AddDefaultCollection(); - ApplyInheritancesAndFixSettings( inheritances ); - } - - public bool AddCollection( string name, ModCollection2? duplicate ) - { - var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant(); - if( nameFixed.Length == 0 - || nameFixed == ModCollection2.Empty.Name.ToLowerInvariant() - || _collections.Any( c => c.Name.RemoveInvalidPathSymbols().ToLowerInvariant() == nameFixed ) ) + private void ReadCollections() { - PluginLog.Warning( $"The new collection {name} would lead to the same path as one that already exists." ); - return false; - } - - var newCollection = duplicate?.Duplicate( name ) ?? ModCollection2.CreateNewEmpty( name ); - _collections.Add( newCollection ); - newCollection.Save(); - CollectionChanged?.Invoke( null, newCollection, CollectionType.Inactive ); - SetCollection( _collections.Count - 1, CollectionType.Current ); - return true; - } - - public bool RemoveCollection( int idx ) - { - if( idx < 0 || idx >= _collections.Count ) - { - PluginLog.Error( "Can not remove the empty collection." ); - return false; - } - - if( idx == _defaultNameIdx ) - { - PluginLog.Error( "Can not remove the default collection." ); - return false; - } - - if( idx == _currentIdx ) - { - SetCollection( _defaultNameIdx, CollectionType.Current ); - } - else if( _currentIdx > idx ) - { - --_currentIdx; - } - - if( idx == _defaultIdx ) - { - SetCollection( -1, CollectionType.Default ); - } - else if( _defaultIdx > idx ) - { - --_defaultIdx; - } - - if( _defaultNameIdx > idx ) - { - --_defaultNameIdx; - } - - foreach( var (characterName, characterIdx) in _character.ToList() ) - { - if( idx == characterIdx ) + var collectionDir = new DirectoryInfo( CollectionDirectory ); + var inheritances = new List< IReadOnlyList< string > >(); + if( collectionDir.Exists ) { - SetCollection( -1, CollectionType.Character, characterName ); - } - else if( characterIdx > idx ) - { - _character[ characterName ] = characterIdx - 1; + foreach( var file in collectionDir.EnumerateFiles( "*.json" ) ) + { + var collection = LoadFromFile( file, out var inheritance ); + if( collection == null || collection.Name.Length == 0 ) + { + continue; + } + + if( file.Name != $"{collection.Name.RemoveInvalidPathSymbols()}.json" ) + { + PluginLog.Warning( $"Collection {file.Name} does not correspond to {collection.Name}." ); + } + + if( this[ collection.Name ] != null ) + { + PluginLog.Warning( $"Duplicate collection found: {collection.Name} already exists." ); + } + else + { + inheritances.Add( inheritance ); + collection.Index = _collections.Count; + _collections.Add( collection ); + } + } } + + AddDefaultCollection(); + ApplyInheritancesAndFixSettings( inheritances ); } - var collection = _collections[ idx ]; - collection.Delete(); - _collections.RemoveAt( idx ); - CollectionChanged?.Invoke( collection, null, CollectionType.Inactive ); - return true; + public bool AddCollection( string name, ModCollection? duplicate ) + { + var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant(); + if( nameFixed.Length == 0 + || nameFixed == Empty.Name.ToLowerInvariant() + || _collections.Any( c => c.Name.RemoveInvalidPathSymbols().ToLowerInvariant() == nameFixed ) ) + { + PluginLog.Warning( $"The new collection {name} would lead to the same path as one that already exists." ); + return false; + } + + var newCollection = duplicate?.Duplicate( name ) ?? CreateNewEmpty( name ); + newCollection.Index = _collections.Count; + _collections.Add( newCollection ); + newCollection.Save(); + CollectionChanged?.Invoke( null, newCollection, Type.Inactive ); + SetCollection( newCollection.Index, Type.Current ); + return true; + } + + public bool RemoveCollection( ModCollection collection ) + => RemoveCollection( collection.Index ); + + public bool RemoveCollection( int idx ) + { + if( idx <= Empty.Index || idx >= _collections.Count ) + { + PluginLog.Error( "Can not remove the empty collection." ); + return false; + } + + if( idx == _defaultNameIdx ) + { + PluginLog.Error( "Can not remove the default collection." ); + return false; + } + + if( idx == _currentIdx ) + { + SetCollection( _defaultNameIdx, Type.Current ); + } + else if( _currentIdx > idx ) + { + --_currentIdx; + } + + if( idx == _defaultIdx ) + { + SetCollection( -1, Type.Default ); + } + else if( _defaultIdx > idx ) + { + --_defaultIdx; + } + + if( _defaultNameIdx > idx ) + { + --_defaultNameIdx; + } + + foreach( var (characterName, characterIdx) in _character.ToList() ) + { + if( idx == characterIdx ) + { + SetCollection( -1, Type.Character, characterName ); + } + else if( characterIdx > idx ) + { + _character[ characterName ] = characterIdx - 1; + } + } + + var collection = _collections[ idx ]; + collection.Delete(); + _collections.RemoveAt( idx ); + for( var i = idx; i < _collections.Count; ++i ) + { + --_collections[ i ].Index; + } + + CollectionChanged?.Invoke( collection, null, Type.Inactive ); + return true; + } } } \ No newline at end of file diff --git a/Penumbra/Mod/ModCache.cs b/Penumbra/Collections/ConflictCache.cs similarity index 65% rename from Penumbra/Mod/ModCache.cs rename to Penumbra/Collections/ConflictCache.cs index 80e7451c..85db330d 100644 --- a/Penumbra/Mod/ModCache.cs +++ b/Penumbra/Collections/ConflictCache.cs @@ -4,7 +4,7 @@ using System.Linq; using Penumbra.GameData.ByteString; using Penumbra.Meta.Manipulations; -namespace Penumbra.Mod; +namespace Penumbra.Collections; public struct ConflictCache { @@ -60,6 +60,12 @@ public struct ConflictCache public IReadOnlyList< ModCacheStruct > Conflicts => _conflicts ?? ( IReadOnlyList< ModCacheStruct > )Array.Empty< ModCacheStruct >(); + public IEnumerable< ModCacheStruct > ModConflicts( int modIdx ) + { + return _conflicts?.SkipWhile( c => c.Mod1 < modIdx ).TakeWhile( c => c.Mod1 == modIdx ) + ?? Array.Empty< ModCacheStruct >(); + } + public void Sort() => _conflicts?.Sort(); @@ -89,55 +95,4 @@ public struct ConflictCache public void ClearConflictsWithMod( int modIdx ) => _conflicts?.RemoveAll( m => m.Mod1 == modIdx || m.Mod2 == ~modIdx ); -} - -// The ModCache contains volatile information dependent on all current settings in a collection. -public class ModCache -{ - public Dictionary< Mod, (List< Utf8GamePath > Files, List< MetaManipulation > Manipulations) > Conflicts { get; private set; } = new(); - - public void AddConflict( Mod precedingMod, Utf8GamePath gamePath ) - { - if( Conflicts.TryGetValue( precedingMod, out var conflicts ) && !conflicts.Files.Contains( gamePath ) ) - { - conflicts.Files.Add( gamePath ); - } - else - { - Conflicts[ precedingMod ] = ( new List< Utf8GamePath > { gamePath }, new List< MetaManipulation >() ); - } - } - - public void AddConflict( Mod precedingMod, MetaManipulation manipulation ) - { - if( Conflicts.TryGetValue( precedingMod, out var conflicts ) && !conflicts.Manipulations.Contains( manipulation ) ) - { - conflicts.Manipulations.Add( manipulation ); - } - else - { - Conflicts[ precedingMod ] = ( new List< Utf8GamePath >(), new List< MetaManipulation > { manipulation } ); - } - } - - public void ClearConflicts() - => Conflicts.Clear(); - - public void ClearFileConflicts() - { - Conflicts = Conflicts.Where( kvp => kvp.Value.Manipulations.Count > 0 ).ToDictionary( kvp => kvp.Key, kvp => - { - kvp.Value.Files.Clear(); - return kvp.Value; - } ); - } - - public void ClearMetaConflicts() - { - Conflicts = Conflicts.Where( kvp => kvp.Value.Files.Count > 0 ).ToDictionary( kvp => kvp.Key, kvp => - { - kvp.Value.Manipulations.Clear(); - return kvp.Value; - } ); - } } \ No newline at end of file diff --git a/Penumbra/Collections/ModCollection.Cache.cs b/Penumbra/Collections/ModCollection.Cache.cs index 697dde2f..01a53f87 100644 --- a/Penumbra/Collections/ModCollection.Cache.cs +++ b/Penumbra/Collections/ModCollection.Cache.cs @@ -1,10 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; -using System.IO; using System.Linq; using Dalamud.Logging; using Penumbra.GameData.ByteString; @@ -14,27 +12,34 @@ using Penumbra.Util; namespace Penumbra.Collections; -public partial class ModCollection2 +public partial class ModCollection { private Cache? _cache; public bool HasCache => _cache != null; - public void CreateCache() + public void CreateCache( bool isDefault ) { + if( Index == 0 ) + { + return; + } + if( _cache == null ) { - _cache = new Cache( this ); - _cache.CalculateEffectiveFileList(); + CalculateEffectiveFileList( true, isDefault ); } } - public void UpdateCache() - => _cache?.CalculateEffectiveFileList(); + public void ForceCacheUpdate( bool isDefault ) + => CalculateEffectiveFileList( true, isDefault ); public void ClearCache() - => _cache = null; + { + _cache?.Dispose(); + _cache = null; + } public FullPath? ResolvePath( Utf8GamePath path ) => _cache?.ResolvePath( path ); @@ -60,8 +65,16 @@ public partial class ModCollection2 internal IReadOnlyList< ConflictCache.ModCacheStruct > Conflicts => _cache?.Conflicts.Conflicts ?? Array.Empty< ConflictCache.ModCacheStruct >(); + internal IEnumerable< ConflictCache.ModCacheStruct > ModConflicts( int modIdx ) + => _cache?.Conflicts.ModConflicts( modIdx ) ?? Array.Empty< ConflictCache.ModCacheStruct >(); + public void CalculateEffectiveFileList( bool withMetaManipulations, bool reloadResident ) { + if( Index == 0 ) + { + return; + } + PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}]", Name, withMetaManipulations ); _cache ??= new Cache( this ); _cache.CalculateEffectiveFileList(); @@ -74,19 +87,21 @@ public partial class ModCollection2 { Penumbra.ResidentResources.Reload(); } + + _cache.Conflicts.Sort(); } // The ModCollectionCache contains all required temporary data to use a collection. // It will only be setup if a collection gets activated in any way. - private class Cache + private class Cache : IDisposable { // Shared caches to avoid allocations. private static readonly BitArray FileSeen = new(256); private static readonly Dictionary< Utf8GamePath, int > RegisteredFiles = new(256); private static readonly List< ModSettings? > ResolvedSettings = new(128); - private readonly ModCollection2 _collection; + private readonly ModCollection _collection; private readonly SortedList< string, object? > _changedItems = new(); public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new(); public readonly HashSet< FullPath > MissingFiles = new(); @@ -102,12 +117,35 @@ public partial class ModCollection2 } } - public Cache( ModCollection2 collection ) + public Cache( ModCollection collection ) { - _collection = collection; - MetaManipulations = new MetaManager( collection ); + _collection = collection; + MetaManipulations = new MetaManager( collection ); + _collection.ModSettingChanged += OnModSettingChange; + _collection.InheritanceChanged += OnInheritanceChange; } + public void Dispose() + { + _collection.ModSettingChanged -= OnModSettingChange; + _collection.InheritanceChanged -= OnInheritanceChange; + } + + private void OnModSettingChange( ModSettingChange type, int modIdx, int oldValue, string? optionName, bool _ ) + { + if( type == ModSettingChange.Priority && !Conflicts.ModConflicts( modIdx ).Any() + || type == ModSettingChange.Setting && !_collection[ modIdx ].Settings!.Enabled ) + { + return; + } + + var hasMeta = Penumbra.ModManager[ modIdx ].Resources.MetaManipulations.Count > 0; + _collection.CalculateEffectiveFileList( hasMeta, Penumbra.CollectionManager.Default == _collection ); + } + + private void OnInheritanceChange( bool _ ) + => _collection.CalculateEffectiveFileList( true, true ); + private static void ResetFileSeen( int size ) { if( size < FileSeen.Length ) @@ -136,6 +174,7 @@ public partial class ModCollection2 { ClearStorageAndPrepare(); + Conflicts.ClearFileConflicts(); for( var i = 0; i < Penumbra.ModManager.Mods.Count; ++i ) { if( ResolvedSettings[ i ]?.Enabled == true ) @@ -240,7 +279,7 @@ public partial class ModCollection2 } } - private void AddPathsForOption( Option option, ModData mod, int modIdx, bool enabled ) + private void AddPathsForOption( Option option, Mod.Mod mod, int modIdx, bool enabled ) { foreach( var (file, paths) in option.OptionFiles ) { @@ -270,7 +309,7 @@ public partial class ModCollection2 } } - private void AddFilesForSingle( OptionGroup singleGroup, ModData mod, int modIdx ) + private void AddFilesForSingle( OptionGroup singleGroup, Mod.Mod mod, int modIdx ) { Debug.Assert( singleGroup.SelectionType == SelectType.Single ); var settings = ResolvedSettings[ modIdx ]!; @@ -285,7 +324,7 @@ public partial class ModCollection2 } } - private void AddFilesForMulti( OptionGroup multiGroup, ModData mod, int modIdx ) + private void AddFilesForMulti( OptionGroup multiGroup, Mod.Mod mod, int modIdx ) { Debug.Assert( multiGroup.SelectionType == SelectType.Multi ); var settings = ResolvedSettings[ modIdx ]!; @@ -301,7 +340,7 @@ public partial class ModCollection2 } } - private void AddRemainingFiles( ModData mod, int modIdx ) + private void AddRemainingFiles( Mod.Mod mod, int modIdx ) { for( var i = 0; i < mod.Resources.ModFiles.Count; ++i ) { diff --git a/Penumbra/Collections/ModCollection.Changes.cs b/Penumbra/Collections/ModCollection.Changes.cs index 02f6f240..2cfd00ef 100644 --- a/Penumbra/Collections/ModCollection.Changes.cs +++ b/Penumbra/Collections/ModCollection.Changes.cs @@ -11,9 +11,9 @@ public enum ModSettingChange Setting, } -public partial class ModCollection2 +public partial class ModCollection { - public delegate void ModSettingChangeDelegate( ModSettingChange type, int modIdx, int oldValue, string? optionName ); + public delegate void ModSettingChangeDelegate( ModSettingChange type, int modIdx, int oldValue, string? optionName, bool inherited ); public event ModSettingChangeDelegate ModSettingChanged; // Enable or disable the mod inheritance of mod idx. @@ -21,7 +21,7 @@ public partial class ModCollection2 { if( FixInheritance( idx, inherit ) ) { - ModSettingChanged.Invoke( ModSettingChange.Inheritance, idx, inherit ? 0 : 1, null ); + ModSettingChanged.Invoke( ModSettingChange.Inheritance, idx, inherit ? 0 : 1, null, false ); } } @@ -32,9 +32,9 @@ public partial class ModCollection2 var oldValue = _settings[ idx ]?.Enabled ?? this[ idx ].Settings?.Enabled ?? false; if( newValue != oldValue ) { - var inheritance = FixInheritance( idx, true ); + var inheritance = FixInheritance( idx, false ); _settings[ idx ]!.Enabled = newValue; - ModSettingChanged.Invoke( ModSettingChange.EnableState, idx, inheritance ? -1 : newValue ? 0 : 1, null ); + ModSettingChanged.Invoke( ModSettingChange.EnableState, idx, inheritance ? -1 : newValue ? 0 : 1, null, false ); } } @@ -45,9 +45,9 @@ public partial class ModCollection2 var oldValue = _settings[ idx ]?.Priority ?? this[ idx ].Settings?.Priority ?? 0; if( newValue != oldValue ) { - var inheritance = FixInheritance( idx, true ); + var inheritance = FixInheritance( idx, false ); _settings[ idx ]!.Priority = newValue; - ModSettingChanged.Invoke( ModSettingChange.Priority, idx, inheritance ? -1 : oldValue, null ); + ModSettingChanged.Invoke( ModSettingChange.Priority, idx, inheritance ? -1 : oldValue, null, false ); } } @@ -63,10 +63,10 @@ public partial class ModCollection2 : newValue; if( oldValue != newValue ) { - var inheritance = FixInheritance( idx, true ); + var inheritance = FixInheritance( idx, false ); _settings[ idx ]!.Settings[ settingName ] = newValue; _settings[ idx ]!.FixSpecificSetting( settingName, Penumbra.ModManager.Mods[ idx ].Meta ); - ModSettingChanged.Invoke( ModSettingChange.Setting, idx, inheritance ? -1 : oldValue, settingName ); + ModSettingChanged.Invoke( ModSettingChange.Setting, idx, inheritance ? -1 : oldValue, settingName, false ); } } @@ -108,6 +108,14 @@ public partial class ModCollection2 return false; } - private void SaveOnChange( ModSettingChange _1, int _2, int _3, string? _4 ) - => Save(); + private void SaveOnChange( ModSettingChange _1, int _2, int _3, string? _4, bool inherited ) + => SaveOnChange( inherited ); + + private void SaveOnChange( bool inherited ) + { + if( !inherited ) + { + Save(); + } + } } \ No newline at end of file diff --git a/Penumbra/Collections/ModCollection.Inheritance.cs b/Penumbra/Collections/ModCollection.Inheritance.cs index dee20aef..158e0159 100644 --- a/Penumbra/Collections/ModCollection.Inheritance.cs +++ b/Penumbra/Collections/ModCollection.Inheritance.cs @@ -6,16 +6,16 @@ using Penumbra.Util; namespace Penumbra.Collections; -public partial class ModCollection2 +public partial class ModCollection { - private readonly List< ModCollection2 > _inheritance = new(); + private readonly List< ModCollection > _inheritance = new(); - public event Action InheritanceChanged; + public event Action< bool > InheritanceChanged; - public IReadOnlyList< ModCollection2 > Inheritance + public IReadOnlyList< ModCollection > Inheritance => _inheritance; - public IEnumerable< ModCollection2 > GetFlattenedInheritance() + public IEnumerable< ModCollection > GetFlattenedInheritance() { yield return this; @@ -27,7 +27,7 @@ public partial class ModCollection2 } } - public bool AddInheritance( ModCollection2 collection ) + public bool AddInheritance( ModCollection collection ) { if( ReferenceEquals( collection, this ) || _inheritance.Contains( collection ) ) { @@ -35,25 +35,41 @@ public partial class ModCollection2 } _inheritance.Add( collection ); - InheritanceChanged.Invoke(); + collection.ModSettingChanged += OnInheritedModSettingChange; + collection.InheritanceChanged += OnInheritedInheritanceChange; + InheritanceChanged.Invoke( false ); return true; } public void RemoveInheritance( int idx ) { + var inheritance = _inheritance[ idx ]; + inheritance.ModSettingChanged -= OnInheritedModSettingChange; + inheritance.InheritanceChanged -= OnInheritedInheritanceChange; _inheritance.RemoveAt( idx ); - InheritanceChanged.Invoke(); + InheritanceChanged.Invoke( false ); } public void MoveInheritance( int from, int to ) { if( _inheritance.Move( from, to ) ) { - InheritanceChanged.Invoke(); + InheritanceChanged.Invoke( false ); } } - public (ModSettings? Settings, ModCollection2 Collection) this[ Index idx ] + private void OnInheritedModSettingChange( ModSettingChange type, int modIdx, int oldValue, string? optionName, bool _ ) + { + if( _settings[ modIdx ] == null ) + { + ModSettingChanged.Invoke( type, modIdx, oldValue, optionName, true ); + } + } + + private void OnInheritedInheritanceChange( bool _ ) + => InheritanceChanged.Invoke( true ); + + public (ModSettings? Settings, ModCollection Collection) this[ Index idx ] { get { diff --git a/Penumbra/Collections/ModCollection.Migration.cs b/Penumbra/Collections/ModCollection.Migration.cs index abd935ab..19a89036 100644 --- a/Penumbra/Collections/ModCollection.Migration.cs +++ b/Penumbra/Collections/ModCollection.Migration.cs @@ -3,11 +3,11 @@ using Penumbra.Mod; namespace Penumbra.Collections; -public sealed partial class ModCollection2 +public sealed partial class ModCollection { private static class Migration { - public static void Migrate( ModCollection2 collection ) + public static void Migrate( ModCollection collection ) { var changes = MigrateV0ToV1( collection ); if( changes ) @@ -16,7 +16,7 @@ public sealed partial class ModCollection2 } } - private static bool MigrateV0ToV1( ModCollection2 collection ) + private static bool MigrateV0ToV1( ModCollection collection ) { if( collection.Version > 0 ) { diff --git a/Penumbra/Collections/ModCollection.cs b/Penumbra/Collections/ModCollection.cs index 5f6c46ea..ce90144d 100644 --- a/Penumbra/Collections/ModCollection.cs +++ b/Penumbra/Collections/ModCollection.cs @@ -16,15 +16,25 @@ namespace Penumbra.Collections; // It is meant to be local only, and thus should always contain settings for every mod, not just the enabled ones. // Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made. // Active ModCollections build a cache of currently relevant data. -public partial class ModCollection2 +public partial class ModCollection { public const int CurrentVersion = 1; public const string DefaultCollection = "Default"; + public const string EmptyCollection = "None"; - public static readonly ModCollection2 Empty = CreateNewEmpty( "None" ); + public static readonly ModCollection Empty = CreateEmpty(); + + private static ModCollection CreateEmpty() + { + var collection = CreateNewEmpty( EmptyCollection ); + collection.Index = 0; + collection._settings.Clear(); + return collection; + } public string Name { get; private init; } public int Version { get; private set; } + public int Index { get; private set; } = -1; private readonly List< ModSettings? > _settings; @@ -37,7 +47,7 @@ public partial class ModCollection2 private readonly Dictionary< string, ModSettings > _unusedSettings; - private ModCollection2( string name, ModCollection2 duplicate ) + private ModCollection( string name, ModCollection duplicate ) { Name = name; Version = duplicate.Version; @@ -45,10 +55,10 @@ public partial class ModCollection2 _unusedSettings = duplicate._unusedSettings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.DeepCopy() ); _inheritance = duplicate._inheritance.ToList(); ModSettingChanged += SaveOnChange; - InheritanceChanged += Save; + InheritanceChanged += SaveOnChange; } - private ModCollection2( string name, int version, Dictionary< string, ModSettings > allSettings ) + private ModCollection( string name, int version, Dictionary< string, ModSettings > allSettings ) { Name = name; Version = version; @@ -66,19 +76,19 @@ public partial class ModCollection2 Migration.Migrate( this ); ModSettingChanged += SaveOnChange; - InheritanceChanged += Save; + InheritanceChanged += SaveOnChange; } - public static ModCollection2 CreateNewEmpty( string name ) + public static ModCollection CreateNewEmpty( string name ) => new(name, CurrentVersion, new Dictionary< string, ModSettings >()); - public ModCollection2 Duplicate( string name ) + public ModCollection Duplicate( string name ) => new(name, this); - internal static ModCollection2 MigrateFromV0( string name, Dictionary< string, ModSettings > allSettings ) + internal static ModCollection MigrateFromV0( string name, Dictionary< string, ModSettings > allSettings ) => new(name, 0, allSettings); - private void CleanUnavailableSettings() + public void CleanUnavailableSettings() { var any = _unusedSettings.Count > 0; _unusedSettings.Clear(); @@ -88,7 +98,7 @@ public partial class ModCollection2 } } - public void AddMod( ModData mod ) + public void AddMod( Mod.Mod mod ) { if( _unusedSettings.TryGetValue( mod.BasePath.Name, out var settings ) ) { @@ -101,7 +111,7 @@ public partial class ModCollection2 } } - public void RemoveMod( ModData mod, int idx ) + public void RemoveMod( Mod.Mod mod, int idx ) { var settings = _settings[ idx ]; if( settings != null ) @@ -165,6 +175,11 @@ public partial class ModCollection2 public void Delete() { + if( Index == 0 ) + { + return; + } + var file = FileName; if( file.Exists ) { @@ -179,7 +194,7 @@ public partial class ModCollection2 } } - public static ModCollection2? LoadFromFile( FileInfo file, out IReadOnlyList< string > inheritance ) + public static ModCollection? LoadFromFile( FileInfo file, out IReadOnlyList< string > inheritance ) { inheritance = Array.Empty< string >(); if( !file.Exists ) @@ -197,7 +212,7 @@ public partial class ModCollection2 ?? new Dictionary< string, ModSettings >(); inheritance = obj[ nameof( Inheritance ) ]?.ToObject< List< string > >() ?? ( IReadOnlyList< string > )Array.Empty< string >(); - return new ModCollection2( name, version, settings ); + return new ModCollection( name, version, settings ); } catch( Exception e ) { diff --git a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs index 3d869944..de7308a5 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.Interop.Structs; +using Penumbra.Mod; using Penumbra.Mods; using FileMode = Penumbra.Interop.Structs.FileMode; using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; @@ -92,7 +93,7 @@ public unsafe partial class ResourceLoader // Use the default method of path replacement. public static (FullPath?, object?) DefaultResolver( Utf8GamePath path ) { - var resolved = ModManager.ResolvePath( path ); + var resolved = Mod.Mod.Manager.ResolvePath( path ); return ( resolved, null ); } diff --git a/Penumbra/Interop/Resolver/PathResolver.Data.cs b/Penumbra/Interop/Resolver/PathResolver.Data.cs index ba8d6b05..7a7b5e84 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Data.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Data.cs @@ -91,10 +91,10 @@ public unsafe partial class PathResolver // This map links DrawObjects directly to Actors (by ObjectTable index) and their collections. // It contains any DrawObjects that correspond to a human actor, even those without specific collections. - internal readonly Dictionary< IntPtr, (ModCollection2, int) > DrawObjectToObject = new(); + internal readonly Dictionary< IntPtr, (ModCollection, int) > DrawObjectToObject = new(); // This map links files to their corresponding collection, if it is non-default. - internal readonly ConcurrentDictionary< Utf8String, ModCollection2 > PathCollections = new(); + internal readonly ConcurrentDictionary< Utf8String, ModCollection > PathCollections = new(); internal GameObject* LastGameObject = null; @@ -159,7 +159,7 @@ public unsafe partial class PathResolver } // Identify the correct collection for a GameObject by index and name. - private static ModCollection2 IdentifyCollection( GameObject* gameObject ) + private static ModCollection IdentifyCollection( GameObject* gameObject ) { if( gameObject == null ) { @@ -180,9 +180,9 @@ public unsafe partial class PathResolver } // Update collections linked to Game/DrawObjects due to a change in collection configuration. - private void CheckCollections( ModCollection2? _1, ModCollection2? _2, CollectionType type, string? name ) + private void CheckCollections( ModCollection? _1, ModCollection? _2, ModCollection.Type type, string? name ) { - if( type is not (CollectionType.Character or CollectionType.Default) ) + if( type is not (ModCollection.Type.Character or ModCollection.Type.Default) ) { return; } @@ -200,7 +200,7 @@ public unsafe partial class PathResolver } // Use the stored information to find the GameObject and Collection linked to a DrawObject. - private GameObject* FindParent( IntPtr drawObject, out ModCollection2 collection ) + private GameObject* FindParent( IntPtr drawObject, out ModCollection collection ) { if( DrawObjectToObject.TryGetValue( drawObject, out var data ) ) { @@ -225,7 +225,7 @@ public unsafe partial class PathResolver // Special handling for paths so that we do not store non-owned temporary strings in the dictionary. - private void SetCollection( Utf8String path, ModCollection2 collection ) + private void SetCollection( Utf8String path, ModCollection collection ) { if( PathCollections.ContainsKey( path ) || path.IsOwned ) { diff --git a/Penumbra/Interop/Resolver/PathResolver.Material.cs b/Penumbra/Interop/Resolver/PathResolver.Material.cs index 51aeeae8..27d3ebcd 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Material.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Material.cs @@ -41,7 +41,7 @@ public unsafe partial class PathResolver return ret; } - private ModCollection2? _mtrlCollection; + private ModCollection? _mtrlCollection; private void LoadMtrlHelper( IntPtr mtrlResourceHandle ) { @@ -56,7 +56,7 @@ public unsafe partial class PathResolver } // Check specifically for shpk and tex files whether we are currently in a material load. - private bool HandleMaterialSubFiles( ResourceType type, out ModCollection2? collection ) + private bool HandleMaterialSubFiles( ResourceType type, out ModCollection? collection ) { if( _mtrlCollection != null && type is ResourceType.Tex or ResourceType.Shpk ) { @@ -96,7 +96,7 @@ public unsafe partial class PathResolver } // Materials need to be set per collection so they can load their textures independently from each other. - private static void HandleMtrlCollection( ModCollection2 collection, string path, bool nonDefault, ResourceType type, FullPath? resolved, + private static void HandleMtrlCollection( ModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved, out (FullPath?, object?) data ) { if( nonDefault && type == ResourceType.Mtrl ) diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index d32eee9f..b678028d 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -161,7 +161,7 @@ public unsafe partial class PathResolver RspSetupCharacterHook?.Dispose(); } - private ModCollection2? GetCollection( IntPtr drawObject ) + private ModCollection? GetCollection( IntPtr drawObject ) { var parent = FindParent( drawObject, out var collection ); if( parent == null || collection == Penumbra.CollectionManager.Default ) @@ -195,7 +195,7 @@ public unsafe partial class PathResolver } } - public static MetaChanger ChangeEqp( ModCollection2 collection ) + public static MetaChanger ChangeEqp( ModCollection collection ) { #if USE_EQP collection.SetEqpFiles(); @@ -233,7 +233,7 @@ public unsafe partial class PathResolver return new MetaChanger( MetaManipulation.Type.Unknown ); } - public static MetaChanger ChangeEqdp( ModCollection2 collection ) + public static MetaChanger ChangeEqdp( ModCollection collection ) { #if USE_EQDP collection.SetEqdpFiles(); @@ -269,7 +269,7 @@ public unsafe partial class PathResolver return new MetaChanger( MetaManipulation.Type.Unknown ); } - public static MetaChanger ChangeCmp( PathResolver resolver, out ModCollection2? collection ) + public static MetaChanger ChangeCmp( PathResolver resolver, out ModCollection? collection ) { if( resolver.LastGameObject != null ) { diff --git a/Penumbra/Interop/Resolver/PathResolver.Resolve.cs b/Penumbra/Interop/Resolver/PathResolver.Resolve.cs index eea23033..ebdb9ff0 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Resolve.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Resolve.cs @@ -130,7 +130,7 @@ public unsafe partial class PathResolver // Just add or remove the resolved path. [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] - private IntPtr ResolvePathDetour( ModCollection2 collection, IntPtr path ) + private IntPtr ResolvePathDetour( ModCollection collection, IntPtr path ) { if( path == IntPtr.Zero ) { diff --git a/Penumbra/Interop/Resolver/PathResolver.cs b/Penumbra/Interop/Resolver/PathResolver.cs index 4c85380c..7f2c89e7 100644 --- a/Penumbra/Interop/Resolver/PathResolver.cs +++ b/Penumbra/Interop/Resolver/PathResolver.cs @@ -104,9 +104,9 @@ public partial class PathResolver : IDisposable Penumbra.CollectionManager.CollectionChanged -= OnCollectionChange; } - private void OnCollectionChange( ModCollection2? _1, ModCollection2? _2, CollectionType type, string? characterName ) + private void OnCollectionChange( ModCollection? _1, ModCollection? _2, ModCollection.Type type, string? characterName ) { - if( type != CollectionType.Character ) + if( type != ModCollection.Type.Character ) { return; } diff --git a/Penumbra/Meta/Manager/MetaManager.Imc.cs b/Penumbra/Meta/Manager/MetaManager.Imc.cs index 2a6e3a37..a4f32b76 100644 --- a/Penumbra/Meta/Manager/MetaManager.Imc.cs +++ b/Penumbra/Meta/Manager/MetaManager.Imc.cs @@ -19,11 +19,11 @@ public partial class MetaManager public readonly Dictionary< Utf8GamePath, ImcFile > Files = new(); public readonly Dictionary< ImcManipulation, int > Manipulations = new(); - private readonly ModCollection2 _collection; + private readonly ModCollection _collection; private static int _imcManagerCount; - public MetaManagerImc( ModCollection2 collection ) + public MetaManagerImc( ModCollection collection ) { _collection = collection; SetupDelegate(); @@ -156,7 +156,7 @@ public partial class MetaManager { // Only check imcs. if( resource->FileType != ResourceType.Imc - || resolveData is not ModCollection2 { HasCache: true } collection + || resolveData is not ModCollection { HasCache: true } collection || !collection.MetaCache!.Imc.Files.TryGetValue( gamePath, out var file ) || !file.ChangesSinceLoad ) { diff --git a/Penumbra/Meta/Manager/MetaManager.cs b/Penumbra/Meta/Manager/MetaManager.cs index 0672d953..06c97baf 100644 --- a/Penumbra/Meta/Manager/MetaManager.cs +++ b/Penumbra/Meta/Manager/MetaManager.cs @@ -51,7 +51,7 @@ public partial class MetaManager : IDisposable + Est.Manipulations.Count + Eqp.Manipulations.Count; - public MetaManager( ModCollection2 collection ) + public MetaManager( ModCollection collection ) => Imc = new MetaManagerImc( collection ); public void SetFiles() diff --git a/Penumbra/MigrateConfiguration.cs b/Penumbra/MigrateConfiguration.cs index 742b1d5d..be9df0d4 100644 --- a/Penumbra/MigrateConfiguration.cs +++ b/Penumbra/MigrateConfiguration.cs @@ -34,7 +34,7 @@ public static class MigrateConfiguration return; } - var defaultCollection = ModCollection2.CreateNewEmpty( ModCollection2.DefaultCollection ); + var defaultCollection = ModCollection.CreateNewEmpty( ModCollection.DefaultCollection ); var defaultCollectionFile = defaultCollection.FileName; if( defaultCollectionFile.Exists ) { @@ -74,7 +74,7 @@ public static class MigrateConfiguration } } - defaultCollection = ModCollection2.MigrateFromV0( ModCollection2.DefaultCollection, dict ); + defaultCollection = ModCollection.MigrateFromV0( ModCollection.DefaultCollection, dict ); defaultCollection.Save(); } catch( Exception e ) diff --git a/Penumbra/Mod/FullMod.cs b/Penumbra/Mod/FullMod.cs new file mode 100644 index 00000000..6ffe9152 --- /dev/null +++ b/Penumbra/Mod/FullMod.cs @@ -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; +} \ No newline at end of file diff --git a/Penumbra/Mod/Mod.SortOrder.cs b/Penumbra/Mod/Mod.SortOrder.cs new file mode 100644 index 00000000..0e9c39eb --- /dev/null +++ b/Penumbra/Mod/Mod.SortOrder.cs @@ -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 ); + } +} \ No newline at end of file diff --git a/Penumbra/Mod/Mod.cs b/Penumbra/Mod/Mod.cs index cda33a3f..d5f7cc2f 100644 --- a/Penumbra/Mod/Mod.cs +++ b/Penumbra/Mod/Mod.cs @@ -1,33 +1,96 @@ using System.Collections.Generic; using System.IO; +using System.Linq; +using Dalamud.Logging; using Penumbra.GameData.ByteString; +using Penumbra.Mods; namespace Penumbra.Mod; -// A complete Mod containing settings (i.e. dependent on a collection) -// and the resulting cache. -public class Mod +// Mod contains all permanent information about a mod, +// and is independent of collections or settings. +// It only changes when the user actively changes the mod or their filesystem. +public partial class Mod { - public ModSettings Settings { get; } - public ModData Data { get; } - public ModCache Cache { get; } + public DirectoryInfo BasePath; + public ModMeta Meta; + public ModResources Resources; - public Mod( ModSettings settings, ModData data ) + public SortOrder Order; + + public SortedList< string, object? > ChangedItems { get; } = new(); + public string LowerChangedItemsString { get; private set; } = string.Empty; + public FileInfo MetaFile { get; set; } + public int Index { get; private set; } = -1; + + private Mod( ModFolder parentFolder, DirectoryInfo basePath, ModMeta meta, ModResources resources ) { - Settings = settings; - Data = data; - Cache = new ModCache(); + BasePath = basePath; + Meta = meta; + Resources = resources; + MetaFile = MetaFileInfo( basePath ); + Order = new SortOrder( parentFolder, Meta.Name ); + Order.ParentFolder.AddMod( this ); + ComputeChangedItems(); } - public bool FixSettings() - => Settings.FixInvalidSettings( Data.Meta ); - - public HashSet< Utf8GamePath > GetFiles( FileInfo file ) + public void ComputeChangedItems() { - var relPath = Utf8RelPath.FromFile( file, Data.BasePath, out var p ) ? p : Utf8RelPath.Empty; - return ModFunctions.GetFilesForConfig( relPath, Settings, Data.Meta ); + var identifier = GameData.GameData.GetIdentifier(); + ChangedItems.Clear(); + foreach( var file in Resources.ModFiles.Select( f => f.ToRelPath( BasePath, out var p ) ? p : Utf8RelPath.Empty ) ) + { + foreach( var path in ModFunctions.GetAllFiles( file, Meta ) ) + { + identifier.Identify( ChangedItems, path.ToGamePath() ); + } + } + + foreach( var path in Meta.FileSwaps.Keys ) + { + identifier.Identify( ChangedItems, path.ToGamePath() ); + } + + LowerChangedItemsString = string.Join( "\0", ChangedItems.Keys.Select( k => k.ToLowerInvariant() ) ); } + public static FileInfo MetaFileInfo( DirectoryInfo basePath ) + => new(Path.Combine( basePath.FullName, "meta.json" )); + + public static Mod? LoadMod( ModFolder parentFolder, DirectoryInfo basePath ) + { + basePath.Refresh(); + if( !basePath.Exists ) + { + PluginLog.Error( $"Supplied mod directory {basePath} does not exist." ); + return null; + } + + var metaFile = MetaFileInfo( basePath ); + if( !metaFile.Exists ) + { + PluginLog.Debug( "No mod meta found for {ModLocation}.", basePath.Name ); + return null; + } + + var meta = ModMeta.LoadFromFile( metaFile ); + if( meta == null ) + { + return null; + } + + var data = new ModResources(); + if( data.RefreshModFiles( basePath ).HasFlag( ResourceChange.Meta ) ) + { + data.SetManipulations( meta, basePath ); + } + + return new Mod( parentFolder, basePath, meta, data ); + } + + public void SaveMeta() + => Meta.SaveToFile( MetaFile ); + public override string ToString() - => Data.Meta.Name; + => Order.FullPath; } \ No newline at end of file diff --git a/Penumbra/Mod/ModCleanup.cs b/Penumbra/Mod/ModCleanup.cs index 654c2174..0a2b3e4a 100644 --- a/Penumbra/Mod/ModCleanup.cs +++ b/Penumbra/Mod/ModCleanup.cs @@ -53,13 +53,13 @@ public class ModCleanup } } - private static DirectoryInfo CreateNewModDir( ModData mod, string optionGroup, string option ) + private static DirectoryInfo CreateNewModDir( Mod mod, string optionGroup, string option ) { var newName = $"{mod.BasePath.Name}_{optionGroup}_{option}"; return TexToolsImport.CreateModFolder( new DirectoryInfo( Penumbra.Config.ModDirectory ), newName ); } - private static ModData CreateNewMod( DirectoryInfo newDir, string newSortOrder ) + private static Mod CreateNewMod( DirectoryInfo newDir, string newSortOrder ) { var idx = Penumbra.ModManager.AddMod( newDir ); var newMod = Penumbra.ModManager.Mods[ idx ]; @@ -69,7 +69,7 @@ public class ModCleanup return newMod; } - private static ModMeta CreateNewMeta( DirectoryInfo newDir, ModData mod, string name, string optionGroup, string option ) + private static ModMeta CreateNewMeta( DirectoryInfo newDir, Mod mod, string name, string optionGroup, string option ) { var newMeta = new ModMeta { @@ -82,7 +82,7 @@ public class ModCleanup return newMeta; } - private static void CreateModSplit( HashSet< string > unseenPaths, ModData mod, OptionGroup group, Option option ) + private static void CreateModSplit( HashSet< string > unseenPaths, Mod mod, OptionGroup group, Option option ) { try { @@ -105,8 +105,8 @@ public class ModCleanup } var newSortOrder = group.SelectionType == SelectType.Single - ? $"{mod.SortOrder.ParentFolder.FullName}/{mod.Meta.Name}/{group.GroupName}/{option.OptionName}" - : $"{mod.SortOrder.ParentFolder.FullName}/{mod.Meta.Name}/{group.GroupName} - {option.OptionName}"; + ? $"{mod.Order.ParentFolder.FullName}/{mod.Meta.Name}/{group.GroupName}/{option.OptionName}" + : $"{mod.Order.ParentFolder.FullName}/{mod.Meta.Name}/{group.GroupName} - {option.OptionName}"; CreateNewMod( newDir, newSortOrder ); } catch( Exception e ) @@ -115,7 +115,7 @@ public class ModCleanup } } - public static void SplitMod( ModData mod ) + public static void SplitMod( Mod mod ) { if( mod.Meta.Groups.Count == 0 ) { diff --git a/Penumbra/Mod/ModData.cs b/Penumbra/Mod/ModData.cs deleted file mode 100644 index 6400a88d..00000000 --- a/Penumbra/Mod/ModData.cs +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/Penumbra/Mod/ModManager.cs b/Penumbra/Mod/ModManager.cs new file mode 100644 index 00000000..5f30c6bd --- /dev/null +++ b/Penumbra/Mod/ModManager.cs @@ -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 ); + } +} \ No newline at end of file diff --git a/Penumbra/Mods/ModFileSystem.cs b/Penumbra/Mods/ModFileSystem.cs index b0d01bd7..ba946687 100644 --- a/Penumbra/Mods/ModFileSystem.cs +++ b/Penumbra/Mods/ModFileSystem.cs @@ -37,7 +37,7 @@ public static partial class ModFileSystem // Rename the SortOrderName of a single mod. Slashes are replaced by Backslashes. // Saves and returns true if anything changed. - public static bool Rename( this ModData mod, string newName ) + public static bool Rename( this Mod.Mod mod, string newName ) { if( RenameNoSave( mod, newName ) ) { @@ -63,7 +63,7 @@ public static partial class ModFileSystem // Move a single mod to the target folder. // Returns true and saves if anything changed. - public static bool Move( this ModData mod, ModFolder target ) + public static bool Move( this Mod.Mod mod, ModFolder target ) { if( MoveNoSave( mod, target ) ) { @@ -76,7 +76,7 @@ public static partial class ModFileSystem // Move a mod to the filesystem location specified by sortOrder and rename its SortOrderName. // Creates all necessary Subfolders. - public static void Move( this ModData mod, string sortOrder ) + public static void Move( this Mod.Mod mod, string sortOrder ) { var split = sortOrder.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries ); var folder = Root; @@ -129,7 +129,7 @@ public static partial class ModFileSystem { foreach( var mod in target.AllMods( true ) ) { - Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullName; + Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.Order.FullName; } Penumbra.Config.Save(); @@ -137,16 +137,16 @@ public static partial class ModFileSystem } // Sets and saves the sort order of a single mod, removing the entry if it is unnecessary. - private static void SaveMod( ModData mod ) + private static void SaveMod( Mod.Mod mod ) { - if( ReferenceEquals( mod.SortOrder.ParentFolder, Root ) - && string.Equals( mod.SortOrder.SortOrderName, mod.Meta.Name.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) ) + if( ReferenceEquals( mod.Order.ParentFolder, Root ) + && string.Equals( mod.Order.SortOrderName, mod.Meta.Name.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) ) { Penumbra.Config.ModSortOrder.Remove( mod.BasePath.Name ); } else { - Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullName; + Penumbra.Config.ModSortOrder[ mod.BasePath.Name ] = mod.Order.FullName; } Penumbra.Config.Save(); @@ -184,30 +184,30 @@ public static partial class ModFileSystem return true; } - private static bool RenameNoSave( ModData mod, string newName ) + private static bool RenameNoSave( Mod.Mod mod, string newName ) { newName = newName.Replace( '/', '\\' ); - if( mod.SortOrder.SortOrderName == newName ) + if( mod.Order.SortOrderName == newName ) { return false; } - mod.SortOrder.ParentFolder.RemoveModIgnoreEmpty( mod ); - mod.SortOrder = new SortOrder( mod.SortOrder.ParentFolder, newName ); - mod.SortOrder.ParentFolder.AddMod( mod ); + mod.Order.ParentFolder.RemoveModIgnoreEmpty( mod ); + mod.Order = new Mod.Mod.SortOrder( mod.Order.ParentFolder, newName ); + mod.Order.ParentFolder.AddMod( mod ); return true; } - private static bool MoveNoSave( ModData mod, ModFolder target ) + private static bool MoveNoSave( Mod.Mod mod, ModFolder target ) { - var oldParent = mod.SortOrder.ParentFolder; + var oldParent = mod.Order.ParentFolder; if( ReferenceEquals( target, oldParent ) ) { return false; } oldParent.RemoveMod( mod ); - mod.SortOrder = new SortOrder( target, mod.SortOrder.SortOrderName ); + mod.Order = new Mod.Mod.SortOrder( target, mod.Order.SortOrderName ); target.AddMod( mod ); return true; } diff --git a/Penumbra/Mods/ModFolder.cs b/Penumbra/Mods/ModFolder.cs index 76768be0..e2369819 100644 --- a/Penumbra/Mods/ModFolder.cs +++ b/Penumbra/Mods/ModFolder.cs @@ -27,7 +27,7 @@ namespace Penumbra.Mods } public List< ModFolder > SubFolders { get; } = new(); - public List< ModData > Mods { get; } = new(); + public List< Mod.Mod > Mods { get; } = new(); public ModFolder( ModFolder parent, string name ) { @@ -45,7 +45,7 @@ namespace Penumbra.Mods => SubFolders.Sum( f => f.TotalDescendantFolders() ); // Return all descendant mods in the specified order. - public IEnumerable< ModData > AllMods( bool foldersFirst ) + public IEnumerable< Mod.Mod > AllMods( bool foldersFirst ) { if( foldersFirst ) { @@ -59,7 +59,7 @@ namespace Penumbra.Mods return folder.AllMods( false ); } - return new[] { ( ModData )f }; + return new[] { ( Mod.Mod )f }; } ); } @@ -116,7 +116,7 @@ namespace Penumbra.Mods // Add the given mod as a child, if it is not already a child. // Returns the index of the found or inserted mod. - public int AddMod( ModData mod ) + public int AddMod( Mod.Mod mod ) { var idx = Mods.BinarySearch( mod, ModComparer ); if( idx >= 0 ) @@ -132,7 +132,7 @@ namespace Penumbra.Mods // Remove mod as a child if it exists. // If this folder is empty afterwards, remove it from its parent. - public void RemoveMod( ModData mod ) + public void RemoveMod( Mod.Mod mod ) { RemoveModIgnoreEmpty( mod ); CheckEmpty(); @@ -157,20 +157,20 @@ namespace Penumbra.Mods : string.Compare( x?.Name ?? string.Empty, y?.Name ?? string.Empty, CompareType ); } - internal class ModDataComparer : IComparer< ModData > + internal class ModDataComparer : IComparer< Mod.Mod > { public StringComparison CompareType = StringComparison.InvariantCultureIgnoreCase; // Compare only the direct SortOrderNames since this is only used inside an enumeration of direct mod children of one folder. // Since mod SortOrderNames do not have to be unique inside a folder, also compare their BasePaths (and thus their identity) if necessary. - public int Compare( ModData? x, ModData? y ) + public int Compare( Mod.Mod? x, Mod.Mod? y ) { if( ReferenceEquals( x, y ) ) { return 0; } - var cmp = string.Compare( x?.SortOrder.SortOrderName, y?.SortOrder.SortOrderName, CompareType ); + var cmp = string.Compare( x?.Order.SortOrderName, y?.Order.SortOrderName, CompareType ); if( cmp != 0 ) { return cmp; @@ -193,7 +193,7 @@ namespace Penumbra.Mods for( ; modIdx < Mods.Count; ++modIdx ) { var mod = Mods[ modIdx ]; - var modString = mod.SortOrder.SortOrderName; + var modString = mod.Order.SortOrderName; if( string.Compare( folderString, modString, StringComparison.InvariantCultureIgnoreCase ) > 0 ) { yield return mod; @@ -235,7 +235,7 @@ namespace Penumbra.Mods } // Remove a mod, but do not remove this folder from its parent if it is empty afterwards. - internal void RemoveModIgnoreEmpty( ModData mod ) + internal void RemoveModIgnoreEmpty( Mod.Mod mod ) { var idx = Mods.BinarySearch( mod, ModComparer ); if( idx >= 0 ) diff --git a/Penumbra/Mods/ModManager.Directory.cs b/Penumbra/Mods/ModManager.Directory.cs index 59f52ace..c8149a65 100644 --- a/Penumbra/Mods/ModManager.Directory.cs +++ b/Penumbra/Mods/ModManager.Directory.cs @@ -9,9 +9,9 @@ namespace Penumbra.Mods; public partial class ModManagerNew { - private readonly List< ModData > _mods = new(); + private readonly List< Mod.Mod > _mods = new(); - public IReadOnlyList< ModData > Mods + public IReadOnlyList< Mod.Mod > Mods => _mods; public void DiscoverMods() diff --git a/Penumbra/Mods/ModManager.cs b/Penumbra/Mods/ModManager.cs deleted file mode 100644 index b88a9bca..00000000 --- a/Penumbra/Mods/ModManager.cs +++ /dev/null @@ -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 ); -} \ No newline at end of file diff --git a/Penumbra/Mods/ModManagerEditExtensions.cs b/Penumbra/Mods/ModManagerEditExtensions.cs index 5083075a..7160d874 100644 --- a/Penumbra/Mods/ModManagerEditExtensions.cs +++ b/Penumbra/Mods/ModManagerEditExtensions.cs @@ -12,7 +12,7 @@ namespace Penumbra.Mods; // Contains all change functions on a specific mod that also require corresponding changes to collections. public static class ModManagerEditExtensions { - public static bool RenameMod( this ModManager manager, string newName, ModData mod ) + public static bool RenameMod( this Mod.Mod.Manager manager, string newName, Mod.Mod mod ) { if( newName.Length == 0 || string.Equals( newName, mod.Meta.Name, StringComparison.InvariantCulture ) ) { @@ -25,23 +25,23 @@ public static class ModManagerEditExtensions return true; } - public static bool ChangeSortOrder( this ModManager manager, ModData mod, string newSortOrder ) + public static bool ChangeSortOrder( this Mod.Mod.Manager manager, Mod.Mod mod, string newSortOrder ) { - if( string.Equals( mod.SortOrder.FullPath, newSortOrder, StringComparison.InvariantCultureIgnoreCase ) ) + if( string.Equals( mod.Order.FullPath, newSortOrder, StringComparison.InvariantCultureIgnoreCase ) ) { return false; } - var inRoot = new SortOrder( manager.StructuredMods, mod.Meta.Name ); + var inRoot = new Mod.Mod.SortOrder( manager.StructuredMods, mod.Meta.Name ); if( newSortOrder == string.Empty || newSortOrder == inRoot.SortOrderName ) { - mod.SortOrder = inRoot; + mod.Order = inRoot; manager.Config.ModSortOrder.Remove( mod.BasePath.Name ); } else { mod.Move( newSortOrder ); - manager.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullPath; + manager.Config.ModSortOrder[ mod.BasePath.Name ] = mod.Order.FullPath; } manager.Config.Save(); @@ -49,7 +49,7 @@ public static class ModManagerEditExtensions return true; } - public static bool RenameModFolder( this ModManager manager, ModData mod, DirectoryInfo newDir, bool move = true ) + public static bool RenameModFolder( this Mod.Mod.Manager manager, Mod.Mod mod, DirectoryInfo newDir, bool move = true ) { if( move ) { @@ -73,7 +73,7 @@ public static class ModManagerEditExtensions var oldBasePath = mod.BasePath; mod.BasePath = newDir; - mod.MetaFile = ModData.MetaFileInfo( newDir ); + mod.MetaFile = Mod.Mod.MetaFileInfo( newDir ); manager.UpdateMod( mod ); if( manager.Config.ModSortOrder.ContainsKey( oldBasePath.Name ) ) @@ -95,7 +95,7 @@ public static class ModManagerEditExtensions return true; } - public static bool ChangeModGroup( this ModManager manager, string oldGroupName, string newGroupName, ModData mod, + public static bool ChangeModGroup( this Mod.Mod.Manager manager, string oldGroupName, string newGroupName, Mod.Mod mod, SelectType type = SelectType.Single ) { if( newGroupName == oldGroupName || mod.Meta.Groups.ContainsKey( newGroupName ) ) @@ -157,7 +157,7 @@ public static class ModManagerEditExtensions return true; } - public static bool RemoveModOption( this ModManager manager, int optionIdx, OptionGroup group, ModData mod ) + public static bool RemoveModOption( this Mod.Mod.Manager manager, int optionIdx, OptionGroup group, Mod.Mod mod ) { if( optionIdx < 0 || optionIdx >= group.Options.Count ) { @@ -202,7 +202,7 @@ public static class ModManagerEditExtensions if( collection.HasCache && settings.Enabled ) { collection.CalculateEffectiveFileList( mod.Resources.MetaManipulations.Count > 0, - Penumbra.CollectionManager.Default == collection ); + Penumbra.CollectionManager.Default == collection ); } } } diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 70535479..61a803ea 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -16,6 +16,7 @@ using Penumbra.Util; using Penumbra.Collections; using Penumbra.Interop.Loader; using Penumbra.Interop.Resolver; +using Penumbra.Mod; namespace Penumbra; @@ -34,8 +35,8 @@ public class Penumbra : IDalamudPlugin public static ResidentResourceManager ResidentResources { get; private set; } = null!; public static CharacterUtility CharacterUtility { get; private set; } = null!; - public static ModManager ModManager { get; private set; } = null!; - public static CollectionManager2 CollectionManager { get; private set; } = null!; + public static Mod.Mod.Manager ModManager { get; private set; } = null!; + public static ModCollection.Manager CollectionManager { get; private set; } = null!; public static ResourceLoader ResourceLoader { get; set; } = null!; public ResourceLogger ResourceLogger { get; } @@ -66,9 +67,9 @@ public class Penumbra : IDalamudPlugin CharacterUtility = new CharacterUtility(); ResourceLoader = new ResourceLoader( this ); ResourceLogger = new ResourceLogger( ResourceLoader ); - ModManager = new ModManager(); + ModManager = new Mod.Mod.Manager(); ModManager.DiscoverMods(); - CollectionManager = new CollectionManager2( ModManager ); + CollectionManager = new ModCollection.Manager( ModManager ); ObjectReloader = new ObjectReloader(); PathResolver = new PathResolver( ResourceLoader ); @@ -223,9 +224,9 @@ public class Penumbra : IDalamudPlugin type = type.ToLowerInvariant(); collectionName = collectionName.ToLowerInvariant(); - var collection = string.Equals( collectionName, ModCollection2.Empty.Name, StringComparison.InvariantCultureIgnoreCase ) - ? ModCollection2.Empty - : CollectionManager[collectionName]; + var collection = string.Equals( collectionName, ModCollection.Empty.Name, StringComparison.InvariantCultureIgnoreCase ) + ? ModCollection.Empty + : CollectionManager[ collectionName ]; if( collection == null ) { Dalamud.Chat.Print( $"The collection {collection} does not exist." ); @@ -241,7 +242,7 @@ public class Penumbra : IDalamudPlugin return false; } - CollectionManager.SetCollection( collection, CollectionType.Default ); + CollectionManager.SetCollection( collection, ModCollection.Type.Default ); Dalamud.Chat.Print( $"Set {collection.Name} as default collection." ); SettingsInterface.ResetDefaultCollection(); return true; diff --git a/Penumbra/UI/MenuTabs/TabChangedItems.cs b/Penumbra/UI/MenuTabs/TabChangedItems.cs index bcd6ff0a..56bddabd 100644 --- a/Penumbra/UI/MenuTabs/TabChangedItems.cs +++ b/Penumbra/UI/MenuTabs/TabChangedItems.cs @@ -25,9 +25,7 @@ public partial class SettingsInterface return; } - var modManager = Penumbra.ModManager; - var items = Penumbra.CollectionManager.DefaultCollection.Cache?.ChangedItems ?? new Dictionary< string, object? >(); - var forced = Penumbra.CollectionManager.ForcedCollection.Cache?.ChangedItems ?? new Dictionary< string, object? >(); + var items = Penumbra.CollectionManager.Default.ChangedItems; using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); @@ -45,12 +43,8 @@ public partial class SettingsInterface raii.Push( ImGui.EndTable ); var list = items.AsEnumerable(); - if( forced.Count > 0 ) - { - list = list.Concat( forced ).OrderBy( kvp => kvp.Key ); - } - if( _filter.Any() ) + if( _filter.Length > 0 ) { list = list.Where( kvp => kvp.Key.ToLowerInvariant().Contains( _filterLower ) ); } diff --git a/Penumbra/UI/MenuTabs/TabCollections.cs b/Penumbra/UI/MenuTabs/TabCollections.cs index 2d94eaef..4775526e 100644 --- a/Penumbra/UI/MenuTabs/TabCollections.cs +++ b/Penumbra/UI/MenuTabs/TabCollections.cs @@ -22,9 +22,8 @@ public partial class SettingsInterface private readonly Selector _selector; private string _collectionNames = null!; private string _collectionNamesWithNone = null!; - private ModCollection2[] _collections = null!; + private ModCollection[] _collections = null!; private int _currentCollectionIndex; - private int _currentForcedIndex; private int _currentDefaultIndex; private readonly Dictionary< string, int > _currentCharacterIndices = new(); private string _newCollectionName = string.Empty; @@ -32,14 +31,14 @@ public partial class SettingsInterface private void UpdateNames() { - _collections = Penumbra.CollectionManager.Prepend( ModCollection2.Empty ).ToArray(); + _collections = Penumbra.CollectionManager.Prepend( ModCollection.Empty ).ToArray(); _collectionNames = string.Join( "\0", _collections.Skip( 1 ).Select( c => c.Name ) ) + '\0'; _collectionNamesWithNone = "None\0" + _collectionNames; UpdateIndices(); } - private int GetIndex( ModCollection2 collection ) + private int GetIndex( ModCollection collection ) { var ret = _collections.IndexOf( c => c.Name == collection.Name ); if( ret < 0 ) @@ -52,20 +51,17 @@ public partial class SettingsInterface } private void UpdateIndex() - => _currentCollectionIndex = GetIndex( Penumbra.CollectionManager.CurrentCollection ) - 1; - - public void UpdateForcedIndex() - => _currentForcedIndex = GetIndex( Penumbra.CollectionManager.ForcedCollection ); + => _currentCollectionIndex = GetIndex( Penumbra.CollectionManager.Current ) - 1; public void UpdateDefaultIndex() - => _currentDefaultIndex = GetIndex( Penumbra.CollectionManager.DefaultCollection ); + => _currentDefaultIndex = GetIndex( Penumbra.CollectionManager.Default ); private void UpdateCharacterIndices() { _currentCharacterIndices.Clear(); - foreach( var kvp in Penumbra.CollectionManager.CharacterCollection ) + foreach( var (character, collection) in Penumbra.CollectionManager.Characters ) { - _currentCharacterIndices[ kvp.Key ] = GetIndex( kvp.Value ); + _currentCharacterIndices[ character ] = GetIndex( collection ); } } @@ -73,7 +69,6 @@ public partial class SettingsInterface { UpdateIndex(); UpdateDefaultIndex(); - UpdateForcedIndex(); UpdateCharacterIndices(); } @@ -83,24 +78,22 @@ public partial class SettingsInterface UpdateNames(); } - private void CreateNewCollection( Dictionary< string, ModSettings > settings ) + private void CreateNewCollection( bool duplicate ) { - if( Penumbra.CollectionManager.AddCollection( _newCollectionName, settings ) ) + if( Penumbra.CollectionManager.AddCollection( _newCollectionName, duplicate ? Penumbra.CollectionManager.Current : null ) ) { UpdateNames(); - SetCurrentCollection( Penumbra.CollectionManager.ByName( _newCollectionName )!, true ); + SetCurrentCollection( Penumbra.CollectionManager[ _newCollectionName ]!, true ); } _newCollectionName = string.Empty; } - private void DrawCleanCollectionButton() + private static void DrawCleanCollectionButton() { if( ImGui.Button( "Clean Settings" ) ) { - var changes = ModFunctions.CleanUpCollection( Penumbra.CollectionManager.CurrentCollection.Settings, - Penumbra.ModManager.BasePath.EnumerateDirectories() ); - Penumbra.CollectionManager.CurrentCollection.UpdateSettings( changes ); + Penumbra.CollectionManager.Current.CleanUnavailableSettings(); } ImGuiCustom.HoverTooltip( @@ -120,14 +113,14 @@ public partial class SettingsInterface if( ImGui.Button( "Create New Empty Collection" ) && _newCollectionName.Length > 0 ) { - CreateNewCollection( new Dictionary< string, ModSettings >() ); + CreateNewCollection( false ); } var hover = ImGui.IsItemHovered(); ImGui.SameLine(); if( ImGui.Button( "Duplicate Current Collection" ) && _newCollectionName.Length > 0 ) { - CreateNewCollection( Penumbra.CollectionManager.CurrentCollection.Settings ); + CreateNewCollection( true ); } hover |= ImGui.IsItemHovered(); @@ -138,13 +131,12 @@ public partial class SettingsInterface ImGui.SetTooltip( "Please enter a name before creating a collection." ); } - var deleteCondition = Penumbra.CollectionManager.Collections.Count > 1 - && Penumbra.CollectionManager.CurrentCollection.Name != ModCollection.DefaultCollection; + var deleteCondition = Penumbra.CollectionManager.Current.Name != ModCollection.DefaultCollection; ImGui.SameLine(); if( ImGuiCustom.DisableButton( "Delete Current Collection", deleteCondition ) ) { - Penumbra.CollectionManager.RemoveCollection( Penumbra.CollectionManager.CurrentCollection.Name ); - SetCurrentCollection( Penumbra.CollectionManager.CurrentCollection, true ); + Penumbra.CollectionManager.RemoveCollection( Penumbra.CollectionManager.Current ); + SetCurrentCollection( Penumbra.CollectionManager.Current, true ); UpdateNames(); } @@ -167,7 +159,7 @@ public partial class SettingsInterface return; } - Penumbra.CollectionManager.SetCollection( _collections[ idx + 1 ], CollectionType.Current ); + Penumbra.CollectionManager.SetCollection( _collections[ idx + 1 ], ModCollection.Type.Current ); _currentCollectionIndex = idx; _selector.Cache.TriggerListReset(); if( _selector.Mod != null ) @@ -176,7 +168,7 @@ public partial class SettingsInterface } } - public void SetCurrentCollection( ModCollection2 collection, bool force = false ) + public void SetCurrentCollection( ModCollection collection, bool force = false ) { var idx = Array.IndexOf( _collections, collection ) - 1; if( idx >= 0 ) @@ -206,7 +198,7 @@ public partial class SettingsInterface ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); if( ImGui.Combo( "##Default Collection", ref index, _collectionNamesWithNone ) && index != _currentDefaultIndex ) { - Penumbra.CollectionManager.SetCollection( _collections[ index ], CollectionType.Default ); + Penumbra.CollectionManager.SetCollection( _collections[ index ], ModCollection.Type.Default ); _currentDefaultIndex = index; } @@ -219,34 +211,6 @@ public partial class SettingsInterface ImGui.Text( "Default Collection" ); } - private void DrawForcedCollectionSelector() - { - var index = _currentForcedIndex; - ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); - using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, Penumbra.CollectionManager.CharacterCollection.Count == 0 ); - if( ImGui.Combo( "##Forced Collection", ref index, _collectionNamesWithNone ) - && index != _currentForcedIndex - && Penumbra.CollectionManager.CharacterCollection.Count > 0 ) - { - Penumbra.CollectionManager.SetCollection( _collections[ index ], CollectionType.Forced ); - _currentForcedIndex = index; - } - - style.Pop(); - if( Penumbra.CollectionManager.CharacterCollection.Count == 0 && ImGui.IsItemHovered() ) - { - ImGui.SetTooltip( - "Forced Collections only provide value if you have at least one Character Collection. There is no need to set one until then." ); - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Mods in the forced collection are always loaded if not overwritten by anything in the current or character-based collection.\n" - + "Please avoid mixing meta-manipulating mods in Forced and other collections, as this will probably not work correctly." ); - ImGui.SameLine(); - ImGui.Text( "Forced Collection" ); - } - private void DrawNewCharacterCollection() { ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); @@ -339,16 +303,15 @@ public partial class SettingsInterface } DrawDefaultCollectionSelector(); - DrawForcedCollectionSelector(); - foreach( var name in Penumbra.CollectionManager.CharacterCollection.Keys.ToArray() ) + foreach( var (name, collection) in Penumbra.CollectionManager.Characters.ToArray() ) { var idx = _currentCharacterIndices[ name ]; var tmp = idx; ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); if( ImGui.Combo( $"##{name}collection", ref tmp, _collectionNamesWithNone ) && idx != tmp ) { - Penumbra.CollectionManager.SetCollection( _collections[ tmp ], CollectionType.Character, name ); + Penumbra.CollectionManager.SetCollection( _collections[ tmp ], ModCollection.Type.Character, name ); _currentCharacterIndices[ name ] = tmp; } diff --git a/Penumbra/UI/MenuTabs/TabDebug.cs b/Penumbra/UI/MenuTabs/TabDebug.cs index 756d69d2..bfa7d1b8 100644 --- a/Penumbra/UI/MenuTabs/TabDebug.cs +++ b/Penumbra/UI/MenuTabs/TabDebug.cs @@ -37,12 +37,10 @@ public partial class SettingsInterface using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable ); var manager = Penumbra.ModManager; - PrintValue( "Current Collection", Penumbra.CollectionManager.CurrentCollection.Name ); - PrintValue( " has Cache", ( Penumbra.CollectionManager.CurrentCollection.Cache != null ).ToString() ); - PrintValue( "Default Collection", Penumbra.CollectionManager.DefaultCollection.Name ); - PrintValue( " has Cache", ( Penumbra.CollectionManager.DefaultCollection.Cache != null ).ToString() ); - PrintValue( "Forced Collection", Penumbra.CollectionManager.ForcedCollection.Name ); - PrintValue( " has Cache", ( Penumbra.CollectionManager.ForcedCollection.Cache != null ).ToString() ); + PrintValue( "Current Collection", Penumbra.CollectionManager.Current.Name ); + PrintValue( " has Cache", Penumbra.CollectionManager.Current.HasCache.ToString() ); + PrintValue( "Default Collection", Penumbra.CollectionManager.Default.Name ); + PrintValue( " has Cache", Penumbra.CollectionManager.Default.HasCache.ToString() ); PrintValue( "Mod Manager BasePath", manager.BasePath.Name ); PrintValue( "Mod Manager BasePath-Full", manager.BasePath.FullName ); PrintValue( "Mod Manager BasePath IsRooted", Path.IsPathRooted( Penumbra.Config.ModDirectory ).ToString() ); @@ -111,15 +109,15 @@ public partial class SettingsInterface return; } - var cache = Penumbra.CollectionManager.CurrentCollection.Cache; - if( cache == null || !ImGui.BeginTable( "##MissingFilesDebugList", 1, ImGuiTableFlags.RowBg, -Vector2.UnitX ) ) + if( !Penumbra.CollectionManager.Current.HasCache + || !ImGui.BeginTable( "##MissingFilesDebugList", 1, ImGuiTableFlags.RowBg, -Vector2.UnitX ) ) { return; } using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - foreach( var file in cache.MissingFiles ) + foreach( var file in Penumbra.CollectionManager.Current.MissingFiles ) { ImGui.TableNextRow(); ImGui.TableNextColumn(); diff --git a/Penumbra/UI/MenuTabs/TabEffective.cs b/Penumbra/UI/MenuTabs/TabEffective.cs index cbf17b9b..04c0dbd1 100644 --- a/Penumbra/UI/MenuTabs/TabEffective.cs +++ b/Penumbra/UI/MenuTabs/TabEffective.cs @@ -100,24 +100,66 @@ public partial class SettingsInterface return !_filePathFilter.Any() || kvp.Item3.Contains( _filePathFilterLower ); } - private void DrawFilteredRows( ModCollection2 active ) + private void DrawFilteredRows( ModCollection active ) { - void DrawFileLines( ModCollection2.Cache cache ) + foreach( var (gp, fp) in active.ResolvedFiles.Where( CheckFilters ) ) { - foreach( var (gp, fp) in cache.ResolvedFiles.Where( CheckFilters ) ) - { - DrawLine( gp, fp ); - } - - //foreach( var (mp, mod, _) in cache.MetaManipulations.Manipulations - // .Select( p => ( p.Item1.IdentifierString(), p.Item2.Data.Meta.Name, p.Item2.Data.Meta.LowerName ) ) - // .Where( CheckFilters ) ) - //{ - // DrawLine( mp, mod ); - //} + DrawLine( gp, fp ); } - DrawFileLines( active ); + var cache = active.MetaCache; + if( cache == null ) + { + return; + } + + foreach( var (mp, mod, _) in cache.Cmp.Manipulations + .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name, + Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) ) + .Where( CheckFilters ) ) + { + DrawLine( mp, mod ); + } + + foreach( var (mp, mod, _) in cache.Eqp.Manipulations + .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name, + Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) ) + .Where( CheckFilters ) ) + { + DrawLine( mp, mod ); + } + + foreach( var (mp, mod, _) in cache.Eqdp.Manipulations + .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name, + Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) ) + .Where( CheckFilters ) ) + { + DrawLine( mp, mod ); + } + + foreach( var (mp, mod, _) in cache.Gmp.Manipulations + .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name, + Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) ) + .Where( CheckFilters ) ) + { + DrawLine( mp, mod ); + } + + foreach( var (mp, mod, _) in cache.Est.Manipulations + .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name, + Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) ) + .Where( CheckFilters ) ) + { + DrawLine( mp, mod ); + } + + foreach( var (mp, mod, _) in cache.Imc.Manipulations + .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name, + Penumbra.ModManager.Mods[ p.Value ].Meta.LowerName ) ) + .Where( CheckFilters ) ) + { + DrawLine( mp, mod ); + } } public void Draw() @@ -133,17 +175,12 @@ public partial class SettingsInterface const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX; - var modManager = Penumbra.ModManager; - var defaultCollection = Penumbra.CollectionManager.DefaultCollection.Cache; - var forcedCollection = Penumbra.CollectionManager.ForcedCollection.Cache; + var resolved = Penumbra.CollectionManager.Default.ResolvedFiles; + var meta = Penumbra.CollectionManager.Default.MetaCache; + var metaCount = meta?.Count ?? 0; + var resolvedCount = resolved.Count; - var (defaultResolved, defaultMeta) = defaultCollection != null - ? ( defaultCollection.ResolvedFiles.Count, defaultCollection.MetaManipulations.Count ) - : ( 0, 0 ); - var (forcedResolved, forcedMeta) = forcedCollection != null - ? ( forcedCollection.ResolvedFiles.Count, forcedCollection.MetaManipulations.Count ) - : ( 0, 0 ); - var totalLines = defaultResolved + forcedResolved + defaultMeta + forcedMeta; + var totalLines = resolvedCount + metaCount; if( totalLines == 0 ) { return; @@ -159,7 +196,7 @@ public partial class SettingsInterface if( _filePathFilter.Length > 0 || _gamePathFilter.Length > 0 ) { - DrawFilteredRows( defaultCollection, forcedCollection ); + DrawFilteredRows( Penumbra.CollectionManager.Default ); } else { @@ -178,29 +215,17 @@ public partial class SettingsInterface { var row = actualRow; ImGui.TableNextRow(); - if( row < defaultResolved ) + if( row < resolvedCount ) { - var (gamePath, file) = defaultCollection!.ResolvedFiles.ElementAt( row ); + var (gamePath, file) = resolved.ElementAt( row ); DrawLine( gamePath, file ); } - else if( ( row -= defaultResolved ) < defaultMeta ) + else if( ( row -= resolved.Count ) < metaCount ) { // TODO //var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row ); DrawLine( 0.ToString(), 0.ToString() ); } - else if( ( row -= defaultMeta ) < forcedResolved ) - { - var (gamePath, file) = forcedCollection!.ResolvedFiles.ElementAt( row ); - DrawLine( gamePath, file ); - } - else - { - // TODO - row -= forcedResolved; - //var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row ); - DrawLine( 0.ToString(), 0.ToString() ); - } } } } diff --git a/Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs b/Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs index fd0a0951..b797902e 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs @@ -2,324 +2,328 @@ using System; using System.Collections.Generic; using System.Linq; using Dalamud.Logging; +using Penumbra.Mod; using Penumbra.Mods; +using Penumbra.Util; -namespace Penumbra.UI +namespace Penumbra.UI; + +public class ModListCache : IDisposable { - public class ModListCache : IDisposable + public const uint NewModColor = 0xFF66DD66u; + public const uint DisabledModColor = 0xFF666666u; + public const uint ConflictingModColor = 0xFFAAAAFFu; + public const uint HandledConflictModColor = 0xFF88DDDDu; + + private readonly Mod.Mod.Manager _manager; + + private readonly List< FullMod > _modsInOrder = new(); + private readonly List< (bool visible, uint color) > _visibleMods = new(); + private readonly Dictionary< ModFolder, (bool visible, bool enabled) > _visibleFolders = new(); + private readonly IReadOnlySet< string > _newMods; + + private string _modFilter = string.Empty; + private string _modFilterChanges = string.Empty; + private string _modFilterAuthor = string.Empty; + private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods; + private bool _listResetNecessary; + private bool _filterResetNecessary; + + + public ModFilter StateFilter { - public const uint NewModColor = 0xFF66DD66u; - public const uint DisabledModColor = 0xFF666666u; - public const uint ConflictingModColor = 0xFFAAAAFFu; - public const uint HandledConflictModColor = 0xFF88DDDDu; - - private readonly ModManager _manager; - - private readonly List< Mod.Mod > _modsInOrder = new(); - private readonly List< (bool visible, uint color) > _visibleMods = new(); - private readonly Dictionary< ModFolder, (bool visible, bool enabled) > _visibleFolders = new(); - private readonly IReadOnlySet< string > _newMods; - - private string _modFilter = string.Empty; - private string _modFilterChanges = string.Empty; - private string _modFilterAuthor = string.Empty; - private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods; - private bool _listResetNecessary; - private bool _filterResetNecessary; - - - public ModFilter StateFilter + get => _stateFilter; + set { - get => _stateFilter; - set + var diff = _stateFilter != value; + _stateFilter = value; + if( diff ) { - var diff = _stateFilter != value; - _stateFilter = value; - if( diff ) - { - TriggerFilterReset(); - } + TriggerFilterReset(); } } + } - public ModListCache( ModManager manager, IReadOnlySet< string > newMods ) + public ModListCache( Mod.Mod.Manager manager, IReadOnlySet< string > newMods ) + { + _manager = manager; + _newMods = newMods; + ResetModList(); + ModFileSystem.ModFileSystemChanged += TriggerListReset; + } + + public void Dispose() + { + ModFileSystem.ModFileSystemChanged -= TriggerListReset; + } + + public int Count + => _modsInOrder.Count; + + + public bool Update() + { + if( _listResetNecessary ) { - _manager = manager; - _newMods = newMods; ResetModList(); - ModFileSystem.ModFileSystemChanged += TriggerListReset; - } - - public void Dispose() - { - ModFileSystem.ModFileSystemChanged -= TriggerListReset; - } - - public int Count - => _modsInOrder.Count; - - - public bool Update() - { - if( _listResetNecessary ) - { - ResetModList(); - return true; - } - - if( _filterResetNecessary ) - { - ResetFilters(); - return true; - } - - return false; - } - - public void TriggerListReset() - => _listResetNecessary = true; - - public void TriggerFilterReset() - => _filterResetNecessary = true; - - public void RemoveMod( Mod.Mod mod ) - { - var idx = _modsInOrder.IndexOf( mod ); - if( idx >= 0 ) - { - _modsInOrder.RemoveAt( idx ); - _visibleMods.RemoveAt( idx ); - UpdateFolders(); - } - } - - private void SetFolderAndParentsVisible( ModFolder? folder ) - { - while( folder != null && ( !_visibleFolders.TryGetValue( folder, out var state ) || !state.visible ) ) - { - _visibleFolders[ folder ] = ( true, true ); - folder = folder.Parent; - } - } - - private void UpdateFolders() - { - _visibleFolders.Clear(); - - for( var i = 0; i < _modsInOrder.Count; ++i ) - { - if( _visibleMods[ i ].visible ) - { - SetFolderAndParentsVisible( _modsInOrder[ i ].Data.SortOrder.ParentFolder ); - } - } - } - - public void SetTextFilter( string filter ) - { - var lower = filter.ToLowerInvariant(); - if( lower.StartsWith( "c:" ) ) - { - _modFilterChanges = lower[ 2.. ]; - _modFilter = string.Empty; - _modFilterAuthor = string.Empty; - } - else if( lower.StartsWith( "a:" ) ) - { - _modFilterAuthor = lower[ 2.. ]; - _modFilter = string.Empty; - _modFilterChanges = string.Empty; - } - else - { - _modFilter = lower; - _modFilterAuthor = string.Empty; - _modFilterChanges = string.Empty; - } - - ResetFilters(); - } - - private void ResetModList() - { - _modsInOrder.Clear(); - _visibleMods.Clear(); - _visibleFolders.Clear(); - - PluginLog.Debug( "Resetting mod selector list..." ); - if( _modsInOrder.Count == 0 ) - { - foreach( var modData in _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) ) - { - var mod = Penumbra.CollectionManager.Current.GetMod( modData ); - _modsInOrder.Add( mod ); - _visibleMods.Add( CheckFilters( mod ) ); - } - } - - _listResetNecessary = false; - _filterResetNecessary = false; - } - - private void ResetFilters() - { - _visibleMods.Clear(); - _visibleFolders.Clear(); - PluginLog.Debug( "Resetting mod selector filters..." ); - foreach( var mod in _modsInOrder ) - { - _visibleMods.Add( CheckFilters( mod ) ); - } - - _filterResetNecessary = false; - } - - public (Mod.Mod? mod, int idx) GetModByName( string name ) - { - for( var i = 0; i < Count; ++i ) - { - if( _modsInOrder[ i ].Data.Meta.Name == name ) - { - return ( _modsInOrder[ i ], i ); - } - } - - return ( null, 0 ); - } - - public (Mod.Mod? mod, int idx) GetModByBasePath( string basePath ) - { - for( var i = 0; i < Count; ++i ) - { - if( _modsInOrder[ i ].Data.BasePath.Name == basePath ) - { - return ( _modsInOrder[ i ], i ); - } - } - - return ( null, 0 ); - } - - public (bool visible, bool enabled) GetFolder( ModFolder folder ) - => _visibleFolders.TryGetValue( folder, out var ret ) ? ret : ( false, false ); - - public (Mod.Mod?, bool visible, uint color) GetMod( int idx ) - => idx >= 0 && idx < _modsInOrder.Count - ? ( _modsInOrder[ idx ], _visibleMods[ idx ].visible, _visibleMods[ idx ].color ) - : ( null, false, 0 ); - - private bool CheckFlags( int count, ModFilter hasNoFlag, ModFilter hasFlag ) - { - if( count == 0 ) - { - if( StateFilter.HasFlag( hasNoFlag ) ) - { - return false; - } - } - else if( StateFilter.HasFlag( hasFlag ) ) - { - return false; - } - return true; } - private (bool, uint) CheckFilters( Mod.Mod mod ) + if( _filterResetNecessary ) { - var ret = ( false, 0u ); + ResetFilters(); + return true; + } - if( _modFilter.Any() && !mod.Data.Meta.LowerName.Contains( _modFilter ) ) + return false; + } + + public void TriggerListReset() + => _listResetNecessary = true; + + public void TriggerFilterReset() + => _filterResetNecessary = true; + + public void RemoveMod( FullMod mod ) + { + var idx = _modsInOrder.IndexOf( mod ); + if( idx >= 0 ) + { + _modsInOrder.RemoveAt( idx ); + _visibleMods.RemoveAt( idx ); + UpdateFolders(); + } + } + + private void SetFolderAndParentsVisible( ModFolder? folder ) + { + while( folder != null && ( !_visibleFolders.TryGetValue( folder, out var state ) || !state.visible ) ) + { + _visibleFolders[ folder ] = ( true, true ); + folder = folder.Parent; + } + } + + private void UpdateFolders() + { + _visibleFolders.Clear(); + + for( var i = 0; i < _modsInOrder.Count; ++i ) + { + if( _visibleMods[ i ].visible ) + { + SetFolderAndParentsVisible( _modsInOrder[ i ].Data.Order.ParentFolder ); + } + } + } + + public void SetTextFilter( string filter ) + { + var lower = filter.ToLowerInvariant(); + if( lower.StartsWith( "c:" ) ) + { + _modFilterChanges = lower[ 2.. ]; + _modFilter = string.Empty; + _modFilterAuthor = string.Empty; + } + else if( lower.StartsWith( "a:" ) ) + { + _modFilterAuthor = lower[ 2.. ]; + _modFilter = string.Empty; + _modFilterChanges = string.Empty; + } + else + { + _modFilter = lower; + _modFilterAuthor = string.Empty; + _modFilterChanges = string.Empty; + } + + ResetFilters(); + } + + private void ResetModList() + { + _modsInOrder.Clear(); + _visibleMods.Clear(); + _visibleFolders.Clear(); + + PluginLog.Debug( "Resetting mod selector list..." ); + if( _modsInOrder.Count == 0 ) + { + foreach( var modData in _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) ) + { + var idx = Penumbra.ModManager.Mods.IndexOf( modData ); + var mod = new FullMod( Penumbra.CollectionManager.Current[ idx ].Settings ?? ModSettings.DefaultSettings( modData.Meta ), + modData ); + _modsInOrder.Add( mod ); + _visibleMods.Add( CheckFilters( mod ) ); + } + } + + _listResetNecessary = false; + _filterResetNecessary = false; + } + + private void ResetFilters() + { + _visibleMods.Clear(); + _visibleFolders.Clear(); + PluginLog.Debug( "Resetting mod selector filters..." ); + foreach( var mod in _modsInOrder ) + { + _visibleMods.Add( CheckFilters( mod ) ); + } + + _filterResetNecessary = false; + } + + public (FullMod? mod, int idx) GetModByName( string name ) + { + for( var i = 0; i < Count; ++i ) + { + if( _modsInOrder[ i ].Data.Meta.Name == name ) + { + return ( _modsInOrder[ i ], i ); + } + } + + return ( null, 0 ); + } + + public (FullMod? mod, int idx) GetModByBasePath( string basePath ) + { + for( var i = 0; i < Count; ++i ) + { + if( _modsInOrder[ i ].Data.BasePath.Name == basePath ) + { + return ( _modsInOrder[ i ], i ); + } + } + + return ( null, 0 ); + } + + public (bool visible, bool enabled) GetFolder( ModFolder folder ) + => _visibleFolders.TryGetValue( folder, out var ret ) ? ret : ( false, false ); + + public (FullMod?, bool visible, uint color) GetMod( int idx ) + => idx >= 0 && idx < _modsInOrder.Count + ? ( _modsInOrder[ idx ], _visibleMods[ idx ].visible, _visibleMods[ idx ].color ) + : ( null, false, 0 ); + + private bool CheckFlags( int count, ModFilter hasNoFlag, ModFilter hasFlag ) + { + if( count == 0 ) + { + if( StateFilter.HasFlag( hasNoFlag ) ) + { + return false; + } + } + else if( StateFilter.HasFlag( hasFlag ) ) + { + return false; + } + + return true; + } + + private (bool, uint) CheckFilters( FullMod mod ) + { + var ret = ( false, 0u ); + + if( _modFilter.Length > 0 && !mod.Data.Meta.LowerName.Contains( _modFilter ) ) + { + return ret; + } + + if( _modFilterAuthor.Length > 0 && !mod.Data.Meta.LowerAuthor.Contains( _modFilterAuthor ) ) + { + return ret; + } + + if( _modFilterChanges.Length > 0 && !mod.Data.LowerChangedItemsString.Contains( _modFilterChanges ) ) + { + return ret; + } + + if( CheckFlags( mod.Data.Resources.ModFiles.Count, ModFilter.HasNoFiles, ModFilter.HasFiles ) ) + { + return ret; + } + + if( CheckFlags( mod.Data.Meta.FileSwaps.Count, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps ) ) + { + return ret; + } + + if( CheckFlags( mod.Data.Resources.MetaManipulations.Count, ModFilter.HasNoMetaManipulations, + ModFilter.HasMetaManipulations ) ) + { + return ret; + } + + if( CheckFlags( mod.Data.Meta.HasGroupsWithConfig ? 1 : 0, ModFilter.HasNoConfig, ModFilter.HasConfig ) ) + { + return ret; + } + + var isNew = _newMods.Contains( mod.Data.BasePath.Name ); + if( CheckFlags( isNew ? 1 : 0, ModFilter.IsNew, ModFilter.NotNew ) ) + { + return ret; + } + + if( !mod.Settings.Enabled ) + { + if( !StateFilter.HasFlag( ModFilter.Disabled ) || !StateFilter.HasFlag( ModFilter.NoConflict ) ) { return ret; } - if( _modFilterAuthor.Any() && !mod.Data.Meta.LowerAuthor.Contains( _modFilterAuthor ) ) - { - return ret; - } + ret.Item2 = ret.Item2 == 0 ? DisabledModColor : ret.Item2; + } - if( _modFilterChanges.Any() && !mod.Data.LowerChangedItemsString.Contains( _modFilterChanges ) ) - { - return ret; - } + if( mod.Settings.Enabled && !StateFilter.HasFlag( ModFilter.Enabled ) ) + { + return ret; + } - if( CheckFlags( mod.Data.Resources.ModFiles.Count, ModFilter.HasNoFiles, ModFilter.HasFiles ) ) + var conflicts = Penumbra.CollectionManager.Current.ModConflicts( mod.Data.Index ).ToList(); + if( conflicts.Count > 0 ) + { + if( conflicts.Any( c => !c.Solved ) ) { - return ret; - } - - if( CheckFlags( mod.Data.Meta.FileSwaps.Count, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps ) ) - { - return ret; - } - - if( CheckFlags( mod.Data.Resources.MetaManipulations.Count, ModFilter.HasNoMetaManipulations, - ModFilter.HasMetaManipulations ) ) - { - return ret; - } - - if( CheckFlags( mod.Data.Meta.HasGroupsWithConfig ? 1 : 0, ModFilter.HasNoConfig, ModFilter.HasConfig ) ) - { - return ret; - } - - var isNew = _newMods.Contains( mod.Data.BasePath.Name ); - if( CheckFlags( isNew ? 1 : 0, ModFilter.IsNew, ModFilter.NotNew ) ) - { - return ret; - } - - if( !mod.Settings.Enabled ) - { - if( !StateFilter.HasFlag( ModFilter.Disabled ) || !StateFilter.HasFlag( ModFilter.NoConflict ) ) + if( !StateFilter.HasFlag( ModFilter.UnsolvedConflict ) ) { return ret; } - ret.Item2 = ret.Item2 == 0 ? DisabledModColor : ret.Item2; + ret.Item2 = ret.Item2 == 0 ? ConflictingModColor : ret.Item2; } - - if( mod.Settings.Enabled && !StateFilter.HasFlag( ModFilter.Enabled ) ) + else { - return ret; - } - - if( mod.Cache.Conflicts.Any() ) - { - if( mod.Cache.Conflicts.Keys.Any( m => m.Settings.Priority == mod.Settings.Priority ) ) + if( !StateFilter.HasFlag( ModFilter.SolvedConflict ) ) { - if( !StateFilter.HasFlag( ModFilter.UnsolvedConflict ) ) - { - return ret; - } - - ret.Item2 = ret.Item2 == 0 ? ConflictingModColor : ret.Item2; + return ret; } - else - { - if( !StateFilter.HasFlag( ModFilter.SolvedConflict ) ) - { - return ret; - } - ret.Item2 = ret.Item2 == 0 ? HandledConflictModColor : ret.Item2; - } + ret.Item2 = ret.Item2 == 0 ? HandledConflictModColor : ret.Item2; } - else if( !StateFilter.HasFlag( ModFilter.NoConflict ) ) - { - return ret; - } - - ret.Item1 = true; - if( isNew ) - { - ret.Item2 = NewModColor; - } - - SetFolderAndParentsVisible( mod.Data.SortOrder.ParentFolder ); + } + else if( !StateFilter.HasFlag( ModFilter.NoConflict ) ) + { return ret; } + + ret.Item1 = true; + if( isNew ) + { + ret.Item2 = NewModColor; + } + + SetFolderAndParentsVisible( mod.Data.Order.ParentFolder ); + return ret; } } \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs index 3843fdb6..d3444394 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs @@ -9,6 +9,7 @@ using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.GameData.Util; using Penumbra.Meta; +using Penumbra.Meta.Manipulations; using Penumbra.Mod; using Penumbra.Mods; using Penumbra.UI.Custom; @@ -119,17 +120,12 @@ public partial class SettingsInterface } // This is only drawn when we have a mod selected, so we can forgive nulls. - private Mod.Mod Mod + private FullMod Mod => _selector.Mod!; private ModMeta Meta => Mod.Data.Meta; - private void Save() - { - Penumbra.CollectionManager.CurrentCollection.Save(); - } - private void DrawAboutTab() { if( !_editMode && Meta.Description.Length == 0 ) @@ -189,7 +185,8 @@ public partial class SettingsInterface private void DrawConflictTab() { - if( !Mod.Cache.Conflicts.Any() || !ImGui.BeginTabItem( LabelConflictsTab ) ) + var conflicts = Penumbra.CollectionManager.Current.ModConflicts( Mod.Data.Index ).ToList(); + if( conflicts.Count == 0 || !ImGui.BeginTabItem( LabelConflictsTab ) ) { return; } @@ -203,32 +200,43 @@ public partial class SettingsInterface } raii.Push( ImGui.EndListBox ); - using var indent = ImGuiRaii.PushIndent( 0 ); - foreach( var (mod, (files, manipulations)) in Mod.Cache.Conflicts ) + using var indent = ImGuiRaii.PushIndent( 0 ); + Mod.Mod? oldBadMod = null; + foreach( var conflict in conflicts ) { - if( ImGui.Selectable( mod.Data.Meta.Name ) ) + var badMod = Penumbra.ModManager[ conflict.Mod2 ]; + if( badMod != oldBadMod ) { - _selector.SelectModByDir( mod.Data.BasePath.Name ); + if( oldBadMod != null ) + { + indent.Pop( 30f ); + } + + if( ImGui.Selectable( badMod.Meta.Name ) ) + { + _selector.SelectModByDir( badMod.BasePath.Name ); + } + + ImGui.SameLine(); + using var color = ImGuiRaii.PushColor( ImGuiCol.Text, conflict.Mod1Priority ? ColorGreen : ColorRed ); + ImGui.Text( $"(Priority {Penumbra.CollectionManager.Current[ conflict.Mod2 ].Settings!.Priority})" ); + + indent.Push( 30f ); } - ImGui.SameLine(); - ImGui.Text( $"(Priority {mod.Settings.Priority})" ); - - indent.Push( 15f ); - foreach( var file in files ) + if( conflict.Conflict is Utf8GamePath p ) { unsafe { - ImGuiNative.igSelectable_Bool( file.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero ); + ImGuiNative.igSelectable_Bool( p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero ); } } - - foreach( var manip in manipulations ) + else if( conflict.Conflict is MetaManipulation m ) { - //ImGui.Text( manip.IdentifierString() ); + ImGui.Selectable( m.Manipulation?.ToString() ?? string.Empty ); } - indent.Pop( 15f ); + oldBadMod = badMod; } } @@ -421,11 +429,12 @@ public partial class SettingsInterface { _fullFilenameList = null; _selector.SaveCurrentMod(); + var idx = Penumbra.ModManager.Mods.IndexOf( Mod.Data ); // Since files may have changed, we need to recompute effective files. - foreach( var collection in Penumbra.CollectionManager.Collections - .Where( c => c.Cache != null && c.Settings[ Mod.Data.BasePath.Name ].Enabled ) ) + foreach( var collection in Penumbra.CollectionManager + .Where( c => c.HasCache && c[ idx ].Settings?.Enabled == true ) ) { - collection.CalculateEffectiveFileList( false, Penumbra.CollectionManager.IsActive( collection ) ); + collection.CalculateEffectiveFileList( false, collection == Penumbra.CollectionManager.Default ); } // If the mod is enabled in the current collection, its conflicts may have changed. @@ -553,12 +562,12 @@ public partial class SettingsInterface var oldEnabled = enabled; if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled ) { - Mod.Settings.Settings[ group.GroupName ] ^= 1 << idx; - Save(); + Penumbra.CollectionManager.Current.SetModSetting( Mod.Data.Index, group.GroupName, + Mod.Settings.Settings[ group.GroupName ] ^ ( 1 << idx ) ); // If the mod is enabled, recalculate files and filters. if( Mod.Settings.Enabled ) { - _base.RecalculateCurrent( Mod.Data.Resources.MetaManipulations.Count > 0 ); + _selector.Cache.TriggerFilterReset(); } } } @@ -591,12 +600,10 @@ public partial class SettingsInterface , group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) && code != Mod.Settings.Settings[ group.GroupName ] ) { - Mod.Settings.Settings[ group.GroupName ] = code; - Save(); - // If the mod is enabled, recalculate files and filters. + Penumbra.CollectionManager.Current.SetModSetting( Mod.Data.Index, group.GroupName, code ); if( Mod.Settings.Enabled ) { - _base.RecalculateCurrent( Mod.Data.Resources.MetaManipulations.Count > 0 ); + _selector.Cache.TriggerFilterReset(); } } } diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs index 3329a554..5bb4acaa 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs @@ -209,7 +209,7 @@ public partial class SettingsInterface { if( newName.Length > 0 ) { - Mod.Settings.Settings[ group.GroupName ] = code; + Penumbra.CollectionManager.Current.SetModSetting(Mod.Data.Index, group.GroupName, code); group.Options.Add( new Option() { OptionName = newName, @@ -245,11 +245,6 @@ public partial class SettingsInterface } } - if( code != oldSetting ) - { - Save(); - } - ImGui.SameLine(); var labelEditPos = ImGui.GetCursorPosX(); DrawSingleSelectorEditGroup( group ); diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs index 10b85e3d..e6f3b170 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs @@ -69,7 +69,7 @@ public partial class SettingsInterface _currentWebsite = Meta?.Website ?? ""; } - private Mod.Mod? Mod + private Mod.FullMod? Mod => _selector.Mod; private ModMeta? Meta @@ -205,8 +205,7 @@ public partial class SettingsInterface ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale ); if( ImGui.InputInt( "Priority", ref priority, 0 ) && priority != Mod!.Settings.Priority ) { - Mod.Settings.Priority = priority; - _base.SaveCurrentCollection( Mod.Data.Resources.MetaManipulations.Count > 0 ); + Penumbra.CollectionManager.Current.SetModPriority( Mod.Data.Index, priority ); _selector.Cache.TriggerFilterReset(); } @@ -220,24 +219,18 @@ public partial class SettingsInterface var enabled = Mod!.Settings.Enabled; if( ImGui.Checkbox( LabelModEnabled, ref enabled ) ) { - Mod.Settings.Enabled = enabled; + Penumbra.CollectionManager.Current.SetModState( Mod.Data.Index, enabled ); if( enabled ) { _newMods.Remove( Mod.Data.BasePath.Name ); } - else - { - Mod.Cache.ClearConflicts(); - } - - _base.SaveCurrentCollection( Mod.Data.Resources.MetaManipulations.Count > 0 ); _selector.Cache.TriggerFilterReset(); } } - public static bool DrawSortOrder( ModData mod, ModManager manager, Selector selector ) + public static bool DrawSortOrder( Mod.Mod mod, Mod.Mod.Manager manager, Selector selector ) { - var currentSortOrder = mod.SortOrder.FullPath; + var currentSortOrder = mod.Order.FullPath; ImGui.SetNextItemWidth( 300 * ImGuiHelpers.GlobalScale ); if( ImGui.InputText( "Sort Order", ref currentSortOrder, 256, ImGuiInputTextFlags.EnterReturnsTrue ) ) { diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs index 1ae71c8b..5984ac48 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs @@ -410,11 +410,11 @@ public partial class SettingsInterface // Selection private partial class Selector { - public Mod.Mod? Mod { get; private set; } + public Mod.FullMod? Mod { get; private set; } private int _index; private string _nextDir = string.Empty; - private void SetSelection( int idx, Mod.Mod? info ) + private void SetSelection( int idx, Mod.FullMod? info ) { Mod = info; if( idx != _index ) @@ -480,7 +480,7 @@ public partial class SettingsInterface private partial class Selector { // === Mod === - private void DrawModOrderPopup( string popupName, Mod.Mod mod, bool firstOpen ) + private void DrawModOrderPopup( string popupName, Mod.FullMod mod, bool firstOpen ) { if( !ImGui.BeginPopup( popupName ) ) { @@ -496,7 +496,7 @@ public partial class SettingsInterface if( firstOpen ) { - ImGui.SetKeyboardFocusHere( mod.Data.SortOrder.FullPath.Length - 1 ); + ImGui.SetKeyboardFocusHere( mod.Data.Order.FullPath.Length - 1 ); } } @@ -527,10 +527,10 @@ public partial class SettingsInterface } Cache.TriggerFilterReset(); - var collection = Penumbra.CollectionManager.CurrentCollection; - if( collection.Cache != null ) + var collection = Penumbra.CollectionManager.Current; + if( collection.HasCache ) { - collection.CalculateEffectiveFileList( metaManips, Penumbra.CollectionManager.IsActive( collection ) ); + collection.CalculateEffectiveFileList( metaManips, collection == Penumbra.CollectionManager.Default ); } collection.Save(); @@ -607,9 +607,9 @@ public partial class SettingsInterface Cache = new ModListCache( Penumbra.ModManager, newMods ); } - private void DrawCollectionButton( string label, string tooltipLabel, float size, ModCollection2 collection ) + private void DrawCollectionButton( string label, string tooltipLabel, float size, ModCollection collection ) { - if( collection == ModCollection2.Empty + if( collection == ModCollection.Empty || collection == Penumbra.CollectionManager.Current ) { using var _ = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f ); @@ -664,7 +664,7 @@ public partial class SettingsInterface idx += sub.TotalDescendantMods(); } } - else if( item is ModData _ ) + else if( item is Mod.Mod _ ) { var (mod, visible, color) = Cache.GetMod( idx ); if( mod != null && visible ) @@ -721,7 +721,7 @@ public partial class SettingsInterface } } - private void DrawMod( Mod.Mod mod, int modIndex, uint color ) + private void DrawMod( Mod.FullMod mod, int modIndex, uint color ) { using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Text, color, color != 0 ); @@ -736,7 +736,7 @@ public partial class SettingsInterface firstOpen = true; } - DragDropTarget( mod.Data.SortOrder.ParentFolder ); + DragDropTarget( mod.Data.Order.ParentFolder ); DragDropSourceMod( modIndex, mod.Data.Meta.Name ); DrawModOrderPopup( popupName, mod, firstOpen ); diff --git a/Penumbra/UI/SettingsInterface.cs b/Penumbra/UI/SettingsInterface.cs index 8939be48..b406da71 100644 --- a/Penumbra/UI/SettingsInterface.cs +++ b/Penumbra/UI/SettingsInterface.cs @@ -55,26 +55,6 @@ public partial class SettingsInterface : IDisposable _menu.InstalledTab.Selector.Cache.TriggerListReset(); } - private void SaveCurrentCollection( bool recalculateMeta ) - { - var current = Penumbra.CollectionManager.CurrentCollection; - current.Save(); - RecalculateCurrent( recalculateMeta ); - } - - private void RecalculateCurrent( bool recalculateMeta ) - { - var current = Penumbra.CollectionManager.CurrentCollection; - if( current.Cache != null ) - { - current.CalculateEffectiveFileList( recalculateMeta, Penumbra.CollectionManager.IsActive( current ) ); - _menu.InstalledTab.Selector.Cache.TriggerFilterReset(); - } - } - public void ResetDefaultCollection() => _menu.CollectionsTab.UpdateDefaultIndex(); - - public void ResetForcedCollection() - => _menu.CollectionsTab.UpdateForcedIndex(); } \ No newline at end of file diff --git a/Penumbra/Util/ModelChanger.cs b/Penumbra/Util/ModelChanger.cs index 6b2378f0..47d26e1b 100644 --- a/Penumbra/Util/ModelChanger.cs +++ b/Penumbra/Util/ModelChanger.cs @@ -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 ) ) {