mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
tmp2
This commit is contained in:
parent
9a0b0bfa0f
commit
ac70f8db89
41 changed files with 1546 additions and 1520 deletions
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public static class MigrateConfiguration
|
|||
return;
|
||||
}
|
||||
|
||||
var defaultCollection = ModCollection2.CreateNewEmpty( ModCollection2.DefaultCollection );
|
||||
var defaultCollection = ModCollection.CreateNewEmpty( ModCollection.DefaultCollection );
|
||||
var defaultCollectionFile = defaultCollection.FileName;
|
||||
if( defaultCollectionFile.Exists )
|
||||
{
|
||||
|
|
@ -74,7 +74,7 @@ public static class MigrateConfiguration
|
|||
}
|
||||
}
|
||||
|
||||
defaultCollection = ModCollection2.MigrateFromV0( ModCollection2.DefaultCollection, dict );
|
||||
defaultCollection = ModCollection.MigrateFromV0( ModCollection.DefaultCollection, dict );
|
||||
defaultCollection.Save();
|
||||
}
|
||||
catch( Exception e )
|
||||
|
|
|
|||
31
Penumbra/Mod/FullMod.cs
Normal file
31
Penumbra/Mod/FullMod.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
// A complete Mod containing settings (i.e. dependent on a collection)
|
||||
// and the resulting cache.
|
||||
public class FullMod
|
||||
{
|
||||
public ModSettings Settings { get; }
|
||||
public Mod Data { get; }
|
||||
|
||||
public FullMod( ModSettings settings, Mod data )
|
||||
{
|
||||
Settings = settings;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public bool FixSettings()
|
||||
=> Settings.FixInvalidSettings( Data.Meta );
|
||||
|
||||
public HashSet< Utf8GamePath > GetFiles( FileInfo file )
|
||||
{
|
||||
var relPath = Utf8RelPath.FromFile( file, Data.BasePath, out var p ) ? p : Utf8RelPath.Empty;
|
||||
return ModFunctions.GetFilesForConfig( relPath, Settings, Data.Meta );
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Data.Meta.Name;
|
||||
}
|
||||
45
Penumbra/Mod/Mod.SortOrder.cs
Normal file
45
Penumbra/Mod/Mod.SortOrder.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
public partial class Mod
|
||||
{
|
||||
public struct SortOrder : IComparable< SortOrder >
|
||||
{
|
||||
public ModFolder ParentFolder { get; set; }
|
||||
|
||||
private string _sortOrderName;
|
||||
|
||||
public string SortOrderName
|
||||
{
|
||||
get => _sortOrderName;
|
||||
set => _sortOrderName = value.Replace( '/', '\\' );
|
||||
}
|
||||
|
||||
public string SortOrderPath
|
||||
=> ParentFolder.FullName;
|
||||
|
||||
public string FullName
|
||||
{
|
||||
get
|
||||
{
|
||||
var path = SortOrderPath;
|
||||
return path.Length > 0 ? $"{path}/{SortOrderName}" : SortOrderName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public SortOrder( ModFolder parentFolder, string name )
|
||||
{
|
||||
ParentFolder = parentFolder;
|
||||
_sortOrderName = name.Replace( '/', '\\' );
|
||||
}
|
||||
|
||||
public string FullPath
|
||||
=> SortOrderPath.Length > 0 ? $"{SortOrderPath}/{SortOrderName}" : SortOrderName;
|
||||
|
||||
public int CompareTo( SortOrder other )
|
||||
=> string.Compare( FullPath, other.FullPath, StringComparison.InvariantCultureIgnoreCase );
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,135 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
public struct SortOrder : IComparable< SortOrder >
|
||||
{
|
||||
public ModFolder ParentFolder { get; set; }
|
||||
|
||||
private string _sortOrderName;
|
||||
|
||||
public string SortOrderName
|
||||
{
|
||||
get => _sortOrderName;
|
||||
set => _sortOrderName = value.Replace( '/', '\\' );
|
||||
}
|
||||
|
||||
public string SortOrderPath
|
||||
=> ParentFolder.FullName;
|
||||
|
||||
public string FullName
|
||||
{
|
||||
get
|
||||
{
|
||||
var path = SortOrderPath;
|
||||
return path.Length > 0 ? $"{path}/{SortOrderName}" : SortOrderName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public SortOrder( ModFolder parentFolder, string name )
|
||||
{
|
||||
ParentFolder = parentFolder;
|
||||
_sortOrderName = name.Replace( '/', '\\' );
|
||||
}
|
||||
|
||||
public string FullPath
|
||||
=> SortOrderPath.Length > 0 ? $"{SortOrderPath}/{SortOrderName}" : SortOrderName;
|
||||
|
||||
public int CompareTo( SortOrder other )
|
||||
=> string.Compare( FullPath, other.FullPath, StringComparison.InvariantCultureIgnoreCase );
|
||||
}
|
||||
|
||||
// ModData contains all permanent information about a mod,
|
||||
// and is independent of collections or settings.
|
||||
// It only changes when the user actively changes the mod or their filesystem.
|
||||
public class ModData
|
||||
{
|
||||
public DirectoryInfo BasePath;
|
||||
public ModMeta Meta;
|
||||
public ModResources Resources;
|
||||
|
||||
public SortOrder SortOrder;
|
||||
|
||||
public SortedList< string, object? > ChangedItems { get; } = new();
|
||||
public string LowerChangedItemsString { get; private set; } = string.Empty;
|
||||
public FileInfo MetaFile { get; set; }
|
||||
|
||||
private ModData( ModFolder parentFolder, DirectoryInfo basePath, ModMeta meta, ModResources resources )
|
||||
{
|
||||
BasePath = basePath;
|
||||
Meta = meta;
|
||||
Resources = resources;
|
||||
MetaFile = MetaFileInfo( basePath );
|
||||
SortOrder = new SortOrder( parentFolder, Meta.Name );
|
||||
SortOrder.ParentFolder.AddMod( this );
|
||||
|
||||
ComputeChangedItems();
|
||||
}
|
||||
|
||||
public void ComputeChangedItems()
|
||||
{
|
||||
var identifier = GameData.GameData.GetIdentifier();
|
||||
ChangedItems.Clear();
|
||||
foreach( var file in Resources.ModFiles.Select( f => f.ToRelPath( BasePath, out var p ) ? p : Utf8RelPath.Empty ) )
|
||||
{
|
||||
foreach( var path in ModFunctions.GetAllFiles( file, Meta ) )
|
||||
{
|
||||
identifier.Identify( ChangedItems, path.ToGamePath() );
|
||||
}
|
||||
}
|
||||
|
||||
foreach( var path in Meta.FileSwaps.Keys )
|
||||
{
|
||||
identifier.Identify( ChangedItems, path.ToGamePath() );
|
||||
}
|
||||
|
||||
LowerChangedItemsString = string.Join( "\0", ChangedItems.Keys.Select( k => k.ToLowerInvariant() ) );
|
||||
}
|
||||
|
||||
public static FileInfo MetaFileInfo( DirectoryInfo basePath )
|
||||
=> new(Path.Combine( basePath.FullName, "meta.json" ));
|
||||
|
||||
public static ModData? LoadMod( ModFolder parentFolder, DirectoryInfo basePath )
|
||||
{
|
||||
basePath.Refresh();
|
||||
if( !basePath.Exists )
|
||||
{
|
||||
PluginLog.Error( $"Supplied mod directory {basePath} does not exist." );
|
||||
return null;
|
||||
}
|
||||
|
||||
var metaFile = MetaFileInfo( basePath );
|
||||
if( !metaFile.Exists )
|
||||
{
|
||||
PluginLog.Debug( "No mod meta found for {ModLocation}.", basePath.Name );
|
||||
return null;
|
||||
}
|
||||
|
||||
var meta = ModMeta.LoadFromFile( metaFile );
|
||||
if( meta == null )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = new ModResources();
|
||||
if( data.RefreshModFiles( basePath ).HasFlag( ResourceChange.Meta ) )
|
||||
{
|
||||
data.SetManipulations( meta, basePath );
|
||||
}
|
||||
|
||||
return new ModData( parentFolder, basePath, meta, data );
|
||||
}
|
||||
|
||||
public void SaveMeta()
|
||||
=> Meta.SaveToFile( MetaFile );
|
||||
|
||||
public override string ToString()
|
||||
=> SortOrder.FullPath;
|
||||
}
|
||||
286
Penumbra/Mod/ModManager.cs
Normal file
286
Penumbra/Mod/ModManager.cs
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
public partial class Mod
|
||||
{
|
||||
public enum ChangeType
|
||||
{
|
||||
Added,
|
||||
Removed,
|
||||
Changed,
|
||||
}
|
||||
|
||||
// The ModManager handles the basic mods installed to the mod directory.
|
||||
// It also contains the CollectionManager that handles all collections.
|
||||
public class Manager : IEnumerable< Mod >
|
||||
{
|
||||
public DirectoryInfo BasePath { get; private set; } = null!;
|
||||
|
||||
private readonly List< Mod > _mods = new();
|
||||
|
||||
public Mod this[ int idx ]
|
||||
=> _mods[ idx ];
|
||||
|
||||
public IReadOnlyList< Mod > Mods
|
||||
=> _mods;
|
||||
|
||||
public IEnumerator< Mod > GetEnumerator()
|
||||
=> _mods.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public ModFolder StructuredMods { get; } = ModFileSystem.Root;
|
||||
|
||||
public delegate void ModChangeDelegate( ChangeType type, int modIndex, Mod mod );
|
||||
|
||||
public event ModChangeDelegate? ModChange;
|
||||
public event Action? ModsRediscovered;
|
||||
|
||||
public bool Valid { get; private set; }
|
||||
|
||||
public int Count
|
||||
=> _mods.Count;
|
||||
|
||||
public Configuration Config
|
||||
=> Penumbra.Config;
|
||||
|
||||
public void DiscoverMods( string newDir )
|
||||
{
|
||||
SetBaseDirectory( newDir, false );
|
||||
DiscoverMods();
|
||||
}
|
||||
|
||||
private void SetBaseDirectory( string newPath, bool firstTime )
|
||||
{
|
||||
if( !firstTime && string.Equals( newPath, Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( newPath.Length == 0 )
|
||||
{
|
||||
Valid = false;
|
||||
BasePath = new DirectoryInfo( "." );
|
||||
}
|
||||
else
|
||||
{
|
||||
var newDir = new DirectoryInfo( newPath );
|
||||
if( !newDir.Exists )
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory( newDir.FullName );
|
||||
newDir.Refresh();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not create specified mod directory {newDir.FullName}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
BasePath = newDir;
|
||||
Valid = true;
|
||||
if( Config.ModDirectory != BasePath.FullName )
|
||||
{
|
||||
Config.ModDirectory = BasePath.FullName;
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
ModsRediscovered?.Invoke();
|
||||
}
|
||||
|
||||
public Manager()
|
||||
{
|
||||
SetBaseDirectory( Config.ModDirectory, true );
|
||||
}
|
||||
|
||||
private bool SetSortOrderPath( Mod mod, string path )
|
||||
{
|
||||
mod.Move( path );
|
||||
var fixedPath = mod.Order.FullPath;
|
||||
if( fixedPath.Length == 0 || string.Equals( fixedPath, mod.Meta.Name, StringComparison.InvariantCultureIgnoreCase ) )
|
||||
{
|
||||
Config.ModSortOrder.Remove( mod.BasePath.Name );
|
||||
return true;
|
||||
}
|
||||
|
||||
if( path != fixedPath )
|
||||
{
|
||||
Config.ModSortOrder[ mod.BasePath.Name ] = fixedPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetModStructure( bool removeOldPaths = false )
|
||||
{
|
||||
var changes = false;
|
||||
|
||||
foreach( var (folder, path) in Config.ModSortOrder.ToArray() )
|
||||
{
|
||||
if( path.Length > 0 && _mods.FindFirst( m => m.BasePath.Name == folder, out var mod ) )
|
||||
{
|
||||
changes |= SetSortOrderPath( mod, path );
|
||||
}
|
||||
else if( removeOldPaths )
|
||||
{
|
||||
changes = true;
|
||||
Config.ModSortOrder.Remove( folder );
|
||||
}
|
||||
}
|
||||
|
||||
if( changes )
|
||||
{
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public void DiscoverMods()
|
||||
{
|
||||
_mods.Clear();
|
||||
BasePath.Refresh();
|
||||
|
||||
StructuredMods.SubFolders.Clear();
|
||||
StructuredMods.Mods.Clear();
|
||||
if( Valid && BasePath.Exists )
|
||||
{
|
||||
foreach( var modFolder in BasePath.EnumerateDirectories() )
|
||||
{
|
||||
var mod = LoadMod( StructuredMods, modFolder );
|
||||
if( mod == null )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
mod.Index = _mods.Count;
|
||||
_mods.Add( mod );
|
||||
}
|
||||
|
||||
SetModStructure();
|
||||
}
|
||||
|
||||
ModsRediscovered?.Invoke();
|
||||
}
|
||||
|
||||
public void DeleteMod( DirectoryInfo modFolder )
|
||||
{
|
||||
if( Directory.Exists( modFolder.FullName ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete( modFolder.FullName, true );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not delete the mod {modFolder.Name}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
var idx = _mods.FindIndex( m => m.BasePath.Name == modFolder.Name );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
var mod = _mods[ idx ];
|
||||
mod.Order.ParentFolder.RemoveMod( mod );
|
||||
_mods.RemoveAt( idx );
|
||||
for( var i = idx; i < _mods.Count; ++i )
|
||||
{
|
||||
--_mods[ i ].Index;
|
||||
}
|
||||
|
||||
ModChange?.Invoke( ChangeType.Removed, idx, mod );
|
||||
}
|
||||
}
|
||||
|
||||
public int AddMod( DirectoryInfo modFolder )
|
||||
{
|
||||
var mod = LoadMod( StructuredMods, modFolder );
|
||||
if( mod == null )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
|
||||
{
|
||||
if( SetSortOrderPath( mod, sortOrder ) )
|
||||
{
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
if( _mods.Any( m => m.BasePath.Name == modFolder.Name ) )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
_mods.Add( mod );
|
||||
ModChange?.Invoke( ChangeType.Added, _mods.Count - 1, mod );
|
||||
|
||||
return _mods.Count - 1;
|
||||
}
|
||||
|
||||
public bool UpdateMod( int idx, bool reloadMeta = false, bool recomputeMeta = false, bool force = false )
|
||||
{
|
||||
var mod = Mods[ idx ];
|
||||
var oldName = mod.Meta.Name;
|
||||
var metaChanges = mod.Meta.RefreshFromFile( mod.MetaFile ) || force;
|
||||
var fileChanges = mod.Resources.RefreshModFiles( mod.BasePath );
|
||||
|
||||
if( !recomputeMeta && !reloadMeta && !metaChanges && fileChanges == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( metaChanges || fileChanges.HasFlag( ResourceChange.Files ) )
|
||||
{
|
||||
mod.ComputeChangedItems();
|
||||
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
|
||||
{
|
||||
mod.Move( sortOrder );
|
||||
var path = mod.Order.FullPath;
|
||||
if( path != sortOrder )
|
||||
{
|
||||
Config.ModSortOrder[ mod.BasePath.Name ] = path;
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mod.Order = new SortOrder( StructuredMods, mod.Meta.Name );
|
||||
}
|
||||
}
|
||||
|
||||
var nameChange = !string.Equals( oldName, mod.Meta.Name, StringComparison.InvariantCulture );
|
||||
|
||||
recomputeMeta |= fileChanges.HasFlag( ResourceChange.Meta );
|
||||
if( recomputeMeta )
|
||||
{
|
||||
mod.Resources.MetaManipulations.Update( mod.Resources.MetaFiles, mod.BasePath, mod.Meta );
|
||||
mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) );
|
||||
}
|
||||
|
||||
// TODO: more specific mod changes?
|
||||
ModChange?.Invoke( ChangeType.Changed, idx, mod );
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool UpdateMod( Mod mod, bool reloadMeta = false, bool recomputeMeta = false, bool force = false )
|
||||
=> UpdateMod( Mods.IndexOf( mod ), reloadMeta, recomputeMeta, force );
|
||||
|
||||
public static FullPath? ResolvePath( Utf8GamePath gameResourcePath )
|
||||
=> Penumbra.CollectionManager.Default.ResolvePath( gameResourcePath );
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 ) );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
|
|
|||
|
|
@ -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 ) )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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 ) )
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue