mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Complete mod collection cleanup, initial stuff for inheritance. Some further cleanup.
This commit is contained in:
parent
7915d516e2
commit
1861c40a4f
48 changed files with 1151 additions and 898 deletions
|
|
@ -8,8 +8,6 @@ using Lumina.Data;
|
|||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
|
|
@ -78,7 +76,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
_penumbra!.ObjectReloader.RedrawAll( setting );
|
||||
}
|
||||
|
||||
private static string ResolvePath( string path, Mod.Mod.Manager _, ModCollection collection )
|
||||
private static string ResolvePath( string path, Mods.Mod.Manager _, ModCollection collection )
|
||||
{
|
||||
if( !Penumbra.Config.EnableMods )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
|
@ -14,122 +13,39 @@ public partial class ModCollection
|
|||
// Is invoked after the collections actually changed.
|
||||
public event CollectionChangeDelegate? CollectionChanged;
|
||||
|
||||
private int _currentIdx = 1;
|
||||
private int _defaultIdx = 0;
|
||||
private int _defaultNameIdx = 0;
|
||||
// The collection currently selected for changing settings.
|
||||
public ModCollection Current { get; private set; } = Empty;
|
||||
|
||||
public ModCollection Current
|
||||
=> this[ _currentIdx ];
|
||||
// The collection used for general file redirections and all characters not specifically named.
|
||||
public ModCollection Default { get; private set; } = Empty;
|
||||
|
||||
public ModCollection Default
|
||||
=> this[ _defaultIdx ];
|
||||
// A single collection that can not be deleted as a fallback for the current collection.
|
||||
public ModCollection DefaultName { get; private set; } = Empty;
|
||||
|
||||
private readonly Dictionary< string, int > _character = new();
|
||||
// The list of character collections.
|
||||
private readonly Dictionary< string, ModCollection > _characters = new();
|
||||
|
||||
public IReadOnlyDictionary< string, ModCollection > Characters
|
||||
=> _characters;
|
||||
|
||||
// If a name does not correspond to a character, return the default collection instead.
|
||||
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 ] ) );
|
||||
=> _characters.TryGetValue( name, out var c ) ? c : Default;
|
||||
|
||||
public bool HasCharacterCollections
|
||||
=> _character.Count > 0;
|
||||
|
||||
private void OnModChanged( Mod.Mod.ChangeType type, int idx, Mod.Mod mod )
|
||||
{
|
||||
var meta = mod.Resources.MetaManipulations.Count > 0;
|
||||
switch( type )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
=> _characters.Count > 0;
|
||||
|
||||
// Set a active collection, can be used to set Default, Current or Character collections.
|
||||
public void SetCollection( int newIdx, Type type, string? characterName = null )
|
||||
{
|
||||
var oldCollectionIdx = type switch
|
||||
{
|
||||
Type.Default => _defaultIdx,
|
||||
Type.Current => _currentIdx,
|
||||
Type.Default => Default.Index,
|
||||
Type.Current => Current.Index,
|
||||
Type.Character => characterName?.Length > 0
|
||||
? _character.TryGetValue( characterName, out var c )
|
||||
? c
|
||||
: _defaultIdx
|
||||
? _characters.TryGetValue( characterName, out var c )
|
||||
? c.Index
|
||||
: Default.Index
|
||||
: -1,
|
||||
_ => -1,
|
||||
};
|
||||
|
|
@ -142,24 +58,24 @@ public partial class ModCollection
|
|||
var newCollection = this[ newIdx ];
|
||||
if( newIdx > Empty.Index )
|
||||
{
|
||||
newCollection.CreateCache(false);
|
||||
newCollection.CreateCache( false );
|
||||
}
|
||||
|
||||
RemoveCache( oldCollectionIdx );
|
||||
switch( type )
|
||||
{
|
||||
case Type.Default:
|
||||
_defaultIdx = newIdx;
|
||||
Default = newCollection;
|
||||
Penumbra.Config.DefaultCollection = newCollection.Name;
|
||||
Penumbra.ResidentResources.Reload();
|
||||
Default.SetFiles();
|
||||
break;
|
||||
case Type.Current:
|
||||
_currentIdx = newIdx;
|
||||
Current = newCollection;
|
||||
Penumbra.Config.CurrentCollection = newCollection.Name;
|
||||
break;
|
||||
case Type.Character:
|
||||
_character[ characterName! ] = newIdx;
|
||||
_characters[ characterName! ] = newCollection;
|
||||
Penumbra.Config.CharacterCollections[ characterName! ] = newCollection.Name;
|
||||
break;
|
||||
}
|
||||
|
|
@ -168,27 +84,32 @@ public partial class ModCollection
|
|||
Penumbra.Config.Save();
|
||||
}
|
||||
|
||||
public void SetCollection( ModCollection collection, Type type, string? characterName = null )
|
||||
=> SetCollection( collection.Index, type, characterName );
|
||||
|
||||
// Create a new character collection. Returns false if the character name already has a collection.
|
||||
public bool CreateCharacterCollection( string characterName )
|
||||
{
|
||||
if( _character.ContainsKey( characterName ) )
|
||||
if( _characters.ContainsKey( characterName ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_character[ characterName ] = Empty.Index;
|
||||
_characters[ characterName ] = Empty;
|
||||
Penumbra.Config.CharacterCollections[ characterName ] = Empty.Name;
|
||||
Penumbra.Config.Save();
|
||||
CollectionChanged?.Invoke( null, Empty, Type.Character, characterName );
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove a character collection if it exists.
|
||||
public void RemoveCharacterCollection( string characterName )
|
||||
{
|
||||
if( _character.TryGetValue( characterName, out var collection ) )
|
||||
if( _characters.TryGetValue( characterName, out var collection ) )
|
||||
{
|
||||
RemoveCache( collection );
|
||||
_character.Remove( characterName );
|
||||
CollectionChanged?.Invoke( this[ collection ], null, Type.Character, characterName );
|
||||
RemoveCache( collection.Index );
|
||||
_characters.Remove( characterName );
|
||||
CollectionChanged?.Invoke( collection, null, Type.Character, characterName );
|
||||
}
|
||||
|
||||
if( Penumbra.Config.CharacterCollections.Remove( characterName ) )
|
||||
|
|
@ -197,36 +118,41 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
// Obtain the index of a collection by name.
|
||||
private int GetIndexForCollectionName( string name )
|
||||
{
|
||||
if( name.Length == 0 )
|
||||
{
|
||||
return Empty.Index;
|
||||
}
|
||||
=> name.Length == 0 ? Empty.Index : _collections.IndexOf( c => c.Name == name );
|
||||
|
||||
return _collections.IndexOf( c => c.Name == name );
|
||||
}
|
||||
|
||||
// Load default, current and character collections from config.
|
||||
// Then create caches. If a collection does not exist anymore, reset it to an appropriate default.
|
||||
public void LoadCollections()
|
||||
{
|
||||
var configChanged = false;
|
||||
_defaultIdx = GetIndexForCollectionName( Penumbra.Config.DefaultCollection );
|
||||
if( _defaultIdx < 0 )
|
||||
var 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;
|
||||
Default = Empty;
|
||||
Penumbra.Config.DefaultCollection = Default.Name;
|
||||
configChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Default = this[ defaultIdx ];
|
||||
}
|
||||
|
||||
_currentIdx = GetIndexForCollectionName( Penumbra.Config.CurrentCollection );
|
||||
if( _currentIdx < 0 )
|
||||
var 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;
|
||||
Current = DefaultName;
|
||||
Penumbra.Config.DefaultCollection = Current.Name;
|
||||
configChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Current = this[ currentIdx ];
|
||||
}
|
||||
|
||||
if( LoadCharacterCollections() || configChanged )
|
||||
{
|
||||
|
|
@ -236,6 +162,7 @@ public partial class ModCollection
|
|||
CreateNecessaryCaches();
|
||||
}
|
||||
|
||||
// Load character collections. If a player name comes up multiple times, the last one is applied.
|
||||
private bool LoadCharacterCollections()
|
||||
{
|
||||
var configChanged = false;
|
||||
|
|
@ -245,17 +172,71 @@ public partial class ModCollection
|
|||
if( idx < 0 )
|
||||
{
|
||||
PluginLog.Error( $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to None." );
|
||||
_character.Add( player, Empty.Index );
|
||||
_characters.Add( player, Empty );
|
||||
Penumbra.Config.CharacterCollections[ player ] = Empty.Name;
|
||||
configChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_character.Add( player, idx );
|
||||
_characters.Add( player, this[ idx ] );
|
||||
}
|
||||
}
|
||||
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
|
||||
// Cache handling.
|
||||
private void CreateNecessaryCaches()
|
||||
{
|
||||
Default.CreateCache( true );
|
||||
Current.CreateCache( false );
|
||||
|
||||
foreach( var collection in _characters.Values )
|
||||
{
|
||||
collection.CreateCache( false );
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveCache( int idx )
|
||||
{
|
||||
if( idx != Default.Index && idx != Current.Index && _characters.Values.All( c => c.Index != idx ) )
|
||||
{
|
||||
_collections[ idx ].ClearCache();
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceCacheUpdates()
|
||||
{
|
||||
foreach( var collection in this )
|
||||
{
|
||||
collection.ForceCacheUpdate( collection == Default );
|
||||
}
|
||||
}
|
||||
|
||||
// Recalculate effective files for active collections on events.
|
||||
private void OnModAddedActive( bool meta )
|
||||
{
|
||||
foreach( var collection in this.Where( c => c.HasCache && c[ ^1 ].Settings?.Enabled == true ) )
|
||||
{
|
||||
collection.CalculateEffectiveFileList( meta, collection == Penumbra.CollectionManager.Default );
|
||||
}
|
||||
}
|
||||
|
||||
private void OnModRemovedActive( bool meta, IEnumerable< ModSettings? > settings )
|
||||
{
|
||||
foreach( var (collection, _) in this.Zip( settings ).Where( c => c.First.HasCache && c.Second?.Enabled == true ) )
|
||||
{
|
||||
collection.CalculateEffectiveFileList( meta, collection == Penumbra.CollectionManager.Default );
|
||||
}
|
||||
}
|
||||
|
||||
private void OnModChangedActive( bool meta, int modIdx )
|
||||
{
|
||||
foreach( var collection in this.Where( c => c.HasCache && c[ modIdx ].Settings?.Enabled == true ) )
|
||||
{
|
||||
collection.CalculateEffectiveFileList( meta, collection == Penumbra.CollectionManager.Default );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
|
@ -13,19 +14,23 @@ public partial class ModCollection
|
|||
{
|
||||
public enum Type : byte
|
||||
{
|
||||
Inactive,
|
||||
Default,
|
||||
Character,
|
||||
Current,
|
||||
Inactive, // A collection was added or removed
|
||||
Default, // The default collection was changed
|
||||
Character, // A character collection was changed
|
||||
Current, // The current collection was changed.
|
||||
}
|
||||
|
||||
public sealed partial class Manager : IDisposable, IEnumerable< ModCollection >
|
||||
{
|
||||
// On addition, oldCollection is null. On deletion, newCollection is null.
|
||||
// CharacterName is onls set for type == Character.
|
||||
public delegate void CollectionChangeDelegate( ModCollection? oldCollection, ModCollection? newCollection, Type type,
|
||||
string? characterName = null );
|
||||
|
||||
private readonly Mod.Mod.Manager _modManager;
|
||||
private readonly Mods.Mod.Manager _modManager;
|
||||
|
||||
// The empty collection is always available and always has index 0.
|
||||
// It can not be deleted or moved.
|
||||
private readonly List< ModCollection > _collections = new()
|
||||
{
|
||||
Empty,
|
||||
|
|
@ -34,25 +39,28 @@ public partial class ModCollection
|
|||
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 int Count
|
||||
=> _collections.Count;
|
||||
|
||||
// Obtain a collection case-independently by name.
|
||||
public bool ByName( string name, [NotNullWhen( true )] out ModCollection? collection )
|
||||
=> _collections.FindFirst( c => string.Equals( c.Name, name, StringComparison.InvariantCultureIgnoreCase ), out collection );
|
||||
|
||||
// Default enumeration skips the empty collection.
|
||||
public IEnumerator< ModCollection > GetEnumerator()
|
||||
=> _collections.Skip( 1 ).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public Manager( Mod.Mod.Manager manager )
|
||||
public Manager( Mods.Mod.Manager manager )
|
||||
{
|
||||
_modManager = manager;
|
||||
|
||||
// The collection manager reacts to changes in mods by itself.
|
||||
_modManager.ModsRediscovered += OnModsRediscovered;
|
||||
_modManager.ModChange += OnModChanged;
|
||||
ReadCollections();
|
||||
|
|
@ -65,27 +73,143 @@ public partial class ModCollection
|
|||
_modManager.ModChange -= OnModChanged;
|
||||
}
|
||||
|
||||
// Add a new collection of the given name.
|
||||
// If duplicate is not-null, the new collection will be a duplicate of it.
|
||||
// If the name of the collection would result in an already existing filename, skip it.
|
||||
// Returns true if the collection was successfully created and fires a Inactive event.
|
||||
// Also sets the current collection to the new collection afterwards.
|
||||
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;
|
||||
}
|
||||
|
||||
// Remove the given collection if it exists and is neither the empty nor the default-named collection.
|
||||
// If the removed collection was active, it also sets the corresponding collection to the appropriate default.
|
||||
public bool RemoveCollection( int idx )
|
||||
{
|
||||
if( idx <= Empty.Index || idx >= _collections.Count )
|
||||
{
|
||||
PluginLog.Error( "Can not remove the empty collection." );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( idx == DefaultName.Index )
|
||||
{
|
||||
PluginLog.Error( "Can not remove the default collection." );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( idx == Current.Index )
|
||||
{
|
||||
SetCollection( DefaultName, Type.Current );
|
||||
}
|
||||
|
||||
if( idx == Default.Index )
|
||||
{
|
||||
SetCollection( Empty, Type.Default );
|
||||
}
|
||||
|
||||
foreach( var (characterName, _) in _characters.Where( c => c.Value.Index == idx ).ToList() )
|
||||
{
|
||||
SetCollection( Empty, Type.Character, characterName );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public bool RemoveCollection( ModCollection collection )
|
||||
=> RemoveCollection( collection.Index );
|
||||
|
||||
|
||||
private void OnModsRediscovered()
|
||||
{
|
||||
// When mods are rediscovered, force all cache updates and set the files of the default collection.
|
||||
ForceCacheUpdates();
|
||||
Default.SetFiles();
|
||||
}
|
||||
|
||||
|
||||
// A changed mod forces changes for all collections, active and inactive.
|
||||
private void OnModChanged( Mod.ChangeType type, int idx, Mod mod )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case Mod.ChangeType.Added:
|
||||
foreach( var collection in this )
|
||||
{
|
||||
collection.AddMod( mod );
|
||||
}
|
||||
|
||||
OnModAddedActive( mod.Resources.MetaManipulations.Count > 0 );
|
||||
break;
|
||||
case Mod.ChangeType.Removed:
|
||||
var settings = new List< ModSettings? >( _collections.Count );
|
||||
foreach( var collection in this )
|
||||
{
|
||||
settings.Add( collection[ idx ].Settings );
|
||||
collection.RemoveMod( mod, idx );
|
||||
}
|
||||
|
||||
OnModRemovedActive( mod.Resources.MetaManipulations.Count > 0, settings );
|
||||
break;
|
||||
case Mod.ChangeType.Changed:
|
||||
foreach( var collection in this.Where(
|
||||
collection => collection.Settings[ idx ]?.FixInvalidSettings( mod.Meta ) ?? false ) )
|
||||
{
|
||||
collection.Save();
|
||||
}
|
||||
|
||||
OnModChangedActive( mod.Resources.MetaManipulations.Count > 0, mod.Index );
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
|
||||
}
|
||||
}
|
||||
|
||||
// Add the collection with the default name if it does not exist.
|
||||
// It should always be ensured that it exists, otherwise it will be created.
|
||||
// This can also not be deleted, so there are always at least the empty and a collection with default name.
|
||||
private void AddDefaultCollection()
|
||||
{
|
||||
var idx = _collections.IndexOf( c => c.Name == DefaultCollection );
|
||||
var idx = GetIndexForCollectionName( DefaultCollection );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
_defaultNameIdx = idx;
|
||||
DefaultName = this[ idx ];
|
||||
return;
|
||||
}
|
||||
|
||||
var defaultCollection = CreateNewEmpty( DefaultCollection );
|
||||
defaultCollection.Save();
|
||||
_defaultNameIdx = _collections.Count;
|
||||
defaultCollection.Index = _collections.Count;
|
||||
_collections.Add( defaultCollection );
|
||||
}
|
||||
|
||||
// Inheritances can not be setup before all collections are read,
|
||||
// so this happens after reading the collections.
|
||||
// During this iteration, we can also fix all settings that are not valid for the given mod anymore.
|
||||
private void ApplyInheritancesAndFixSettings( IEnumerable< IReadOnlyList< string > > inheritances )
|
||||
{
|
||||
foreach( var (collection, inheritance) in this.Zip( inheritances ) )
|
||||
|
|
@ -117,6 +241,9 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
// Read all collection files in the Collection Directory.
|
||||
// Ensure that the default named collection exists, and apply inheritances afterwards.
|
||||
// Duplicate collection files are not deleted, just not added here.
|
||||
private void ReadCollections()
|
||||
{
|
||||
var collectionDir = new DirectoryInfo( CollectionDirectory );
|
||||
|
|
@ -152,89 +279,5 @@ public partial class ModCollection
|
|||
AddDefaultCollection();
|
||||
ApplyInheritancesAndFixSettings( inheritances );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,24 +8,26 @@ namespace Penumbra.Collections;
|
|||
|
||||
public struct ConflictCache
|
||||
{
|
||||
public readonly struct ModCacheStruct : IComparable< ModCacheStruct >
|
||||
// A conflict stores all data about a mod conflict.
|
||||
public readonly struct Conflict : IComparable< Conflict >
|
||||
{
|
||||
public readonly object Conflict;
|
||||
public readonly object Data;
|
||||
public readonly int Mod1;
|
||||
public readonly int Mod2;
|
||||
public readonly bool Mod1Priority;
|
||||
public readonly bool Solved;
|
||||
|
||||
public ModCacheStruct( int modIdx1, int modIdx2, int priority1, int priority2, object conflict )
|
||||
public Conflict( int modIdx1, int modIdx2, bool priority, bool solved, object data )
|
||||
{
|
||||
Mod1 = modIdx1;
|
||||
Mod2 = modIdx2;
|
||||
Conflict = conflict;
|
||||
Mod1Priority = priority1 >= priority2;
|
||||
Solved = priority1 != priority2;
|
||||
Data = data;
|
||||
Mod1Priority = priority;
|
||||
Solved = solved;
|
||||
}
|
||||
|
||||
public int CompareTo( ModCacheStruct other )
|
||||
// Order: Mod1 -> Mod1 overwritten -> Mod2 -> File > MetaManipulation
|
||||
public int CompareTo( Conflict other )
|
||||
{
|
||||
var idxComp = Mod1.CompareTo( other.Mod1 );
|
||||
if( idxComp != 0 )
|
||||
|
|
@ -44,55 +46,85 @@ public struct ConflictCache
|
|||
return idxComp;
|
||||
}
|
||||
|
||||
return Conflict switch
|
||||
return Data switch
|
||||
{
|
||||
Utf8GamePath p when other.Conflict is Utf8GamePath q => p.CompareTo( q ),
|
||||
Utf8GamePath => -1,
|
||||
MetaManipulation m when other.Conflict is MetaManipulation n => m.CompareTo( n ),
|
||||
MetaManipulation => 1,
|
||||
_ => 0,
|
||||
Utf8GamePath p when other.Data is Utf8GamePath q => p.CompareTo( q ),
|
||||
Utf8GamePath => -1,
|
||||
MetaManipulation m when other.Data is MetaManipulation n => m.CompareTo( n ),
|
||||
MetaManipulation => 1,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private List< ModCacheStruct >? _conflicts;
|
||||
private readonly List< Conflict > _conflicts = new();
|
||||
private bool _isSorted = true;
|
||||
|
||||
public IReadOnlyList< ModCacheStruct > Conflicts
|
||||
=> _conflicts ?? ( IReadOnlyList< ModCacheStruct > )Array.Empty< ModCacheStruct >();
|
||||
public ConflictCache()
|
||||
{ }
|
||||
|
||||
public IEnumerable< ModCacheStruct > ModConflicts( int modIdx )
|
||||
public IReadOnlyList< Conflict > Conflicts
|
||||
{
|
||||
return _conflicts?.SkipWhile( c => c.Mod1 < modIdx ).TakeWhile( c => c.Mod1 == modIdx )
|
||||
?? Array.Empty< ModCacheStruct >();
|
||||
get
|
||||
{
|
||||
Sort();
|
||||
return _conflicts;
|
||||
}
|
||||
}
|
||||
|
||||
public void Sort()
|
||||
=> _conflicts?.Sort();
|
||||
// Find all mod conflicts concerning the specified mod (in both directions).
|
||||
public IEnumerable< Conflict > ModConflicts( int modIdx )
|
||||
{
|
||||
return _conflicts.SkipWhile( c => c.Mod1 < modIdx ).TakeWhile( c => c.Mod1 == modIdx );
|
||||
}
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
if( !_isSorted )
|
||||
{
|
||||
_conflicts?.Sort();
|
||||
}
|
||||
}
|
||||
|
||||
// Add both directions for the mod.
|
||||
// On same priority, it is assumed that mod1 is the earlier one.
|
||||
// Also update older conflicts to refer to the highest-prioritized conflict.
|
||||
private void AddConflict( int modIdx1, int modIdx2, int priority1, int priority2, object data )
|
||||
{
|
||||
var solved = priority1 != priority2;
|
||||
var priority = priority1 >= priority2;
|
||||
var prioritizedMod = priority ? modIdx1 : modIdx2;
|
||||
_conflicts.Add( new Conflict( modIdx1, modIdx2, priority, solved, data ) );
|
||||
_conflicts.Add( new Conflict( modIdx2, modIdx1, !priority, solved, data ) );
|
||||
for( var i = 0; i < _conflicts.Count; ++i )
|
||||
{
|
||||
var c = _conflicts[ i ];
|
||||
if( data.Equals( c.Data ) )
|
||||
{
|
||||
_conflicts[ i ] = c.Mod1Priority
|
||||
? new Conflict( prioritizedMod, c.Mod2, true, c.Solved || solved, data )
|
||||
: new Conflict( c.Mod1, prioritizedMod, false, c.Solved || solved, data );
|
||||
}
|
||||
}
|
||||
|
||||
_isSorted = false;
|
||||
}
|
||||
|
||||
public void AddConflict( int modIdx1, int modIdx2, int priority1, int priority2, Utf8GamePath gamePath )
|
||||
{
|
||||
_conflicts ??= new List< ModCacheStruct >( 2 );
|
||||
|
||||
_conflicts.Add( new ModCacheStruct( modIdx1, modIdx2, priority1, priority2, gamePath ) );
|
||||
_conflicts.Add( new ModCacheStruct( modIdx2, modIdx1, priority2, priority1, gamePath ) );
|
||||
}
|
||||
=> AddConflict( modIdx1, modIdx2, priority1, priority2, ( object )gamePath );
|
||||
|
||||
public void AddConflict( int modIdx1, int modIdx2, int priority1, int priority2, MetaManipulation manipulation )
|
||||
{
|
||||
_conflicts ??= new List< ModCacheStruct >( 2 );
|
||||
_conflicts.Add( new ModCacheStruct( modIdx1, modIdx2, priority1, priority2, manipulation ) );
|
||||
_conflicts.Add( new ModCacheStruct( modIdx2, modIdx1, priority2, priority1, manipulation ) );
|
||||
}
|
||||
=> AddConflict( modIdx1, modIdx2, priority1, priority2, ( object )manipulation );
|
||||
|
||||
public void ClearConflicts()
|
||||
=> _conflicts?.Clear();
|
||||
|
||||
public void ClearFileConflicts()
|
||||
=> _conflicts?.RemoveAll( m => m.Conflict is Utf8GamePath );
|
||||
=> _conflicts?.RemoveAll( m => m.Data is Utf8GamePath );
|
||||
|
||||
public void ClearMetaConflicts()
|
||||
=> _conflicts?.RemoveAll( m => m.Conflict is MetaManipulation );
|
||||
=> _conflicts?.RemoveAll( m => m.Data is MetaManipulation );
|
||||
|
||||
public void ClearConflictsWithMod( int modIdx )
|
||||
=> _conflicts?.RemoveAll( m => m.Mod1 == modIdx || m.Mod2 == ~modIdx );
|
||||
=> _conflicts?.RemoveAll( m => m.Mod1 == modIdx || m.Mod2 == modIdx );
|
||||
}
|
||||
|
|
@ -7,49 +7,53 @@ using System.Linq;
|
|||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta.Manager;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
public partial class ModCollection
|
||||
{
|
||||
// Only active collections need to have a cache.
|
||||
private Cache? _cache;
|
||||
|
||||
public bool HasCache
|
||||
=> _cache != null;
|
||||
|
||||
// Only create, do not update.
|
||||
public void CreateCache( bool isDefault )
|
||||
{
|
||||
if( Index == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( _cache == null )
|
||||
{
|
||||
CalculateEffectiveFileList( true, isDefault );
|
||||
}
|
||||
}
|
||||
|
||||
// Force an update with metadata for this cache.
|
||||
public void ForceCacheUpdate( bool isDefault )
|
||||
=> CalculateEffectiveFileList( true, isDefault );
|
||||
|
||||
|
||||
// Clear the current cache.
|
||||
public void ClearCache()
|
||||
{
|
||||
_cache?.Dispose();
|
||||
_cache = null;
|
||||
}
|
||||
|
||||
|
||||
public FullPath? ResolvePath( Utf8GamePath path )
|
||||
=> _cache?.ResolvePath( path );
|
||||
|
||||
// Force a file to be resolved to a specific path regardless of conflicts.
|
||||
internal void ForceFile( Utf8GamePath path, FullPath fullPath )
|
||||
=> _cache!.ResolvedFiles[ path ] = fullPath;
|
||||
|
||||
// Force a file resolve to be removed.
|
||||
internal void RemoveFile( Utf8GamePath path )
|
||||
=> _cache!.ResolvedFiles.Remove( path );
|
||||
|
||||
// Obtain data from the cache.
|
||||
internal MetaManager? MetaCache
|
||||
=> _cache?.MetaManipulations;
|
||||
|
||||
|
|
@ -62,14 +66,17 @@ public partial class ModCollection
|
|||
internal IReadOnlyDictionary< string, object? > ChangedItems
|
||||
=> _cache?.ChangedItems ?? new Dictionary< string, object? >();
|
||||
|
||||
internal IReadOnlyList< ConflictCache.ModCacheStruct > Conflicts
|
||||
=> _cache?.Conflicts.Conflicts ?? Array.Empty< ConflictCache.ModCacheStruct >();
|
||||
internal IReadOnlyList< ConflictCache.Conflict > Conflicts
|
||||
=> _cache?.Conflicts.Conflicts ?? Array.Empty< ConflictCache.Conflict >();
|
||||
|
||||
internal IEnumerable< ConflictCache.ModCacheStruct > ModConflicts( int modIdx )
|
||||
=> _cache?.Conflicts.ModConflicts( modIdx ) ?? Array.Empty< ConflictCache.ModCacheStruct >();
|
||||
internal IEnumerable< ConflictCache.Conflict > ModConflicts( int modIdx )
|
||||
=> _cache?.Conflicts.ModConflicts( modIdx ) ?? Array.Empty< ConflictCache.Conflict >();
|
||||
|
||||
// Update the effective file list for the given cache.
|
||||
// Creates a cache if necessary.
|
||||
public void CalculateEffectiveFileList( bool withMetaManipulations, bool reloadResident )
|
||||
{
|
||||
// Skip the empty collection.
|
||||
if( Index == 0 )
|
||||
{
|
||||
return;
|
||||
|
|
@ -87,8 +94,84 @@ public partial class ModCollection
|
|||
{
|
||||
Penumbra.ResidentResources.Reload();
|
||||
}
|
||||
}
|
||||
|
||||
_cache.Conflicts.Sort();
|
||||
// Set Metadata files.
|
||||
[Conditional( "USE_EQP" )]
|
||||
public void SetEqpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.MetaManagerEqp.ResetFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.Eqp.SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional( "USE_EQDP" )]
|
||||
public void SetEqdpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.MetaManagerEqdp.ResetFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.Eqdp.SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional( "USE_GMP" )]
|
||||
public void SetGmpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.MetaManagerGmp.ResetFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.Gmp.SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional( "USE_EST" )]
|
||||
public void SetEstFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.MetaManagerEst.ResetFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.Est.SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional( "USE_CMP" )]
|
||||
public void SetCmpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.MetaManagerCmp.ResetFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.Cmp.SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
Penumbra.CharacterUtility.ResetAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -106,8 +189,9 @@ public partial class ModCollection
|
|||
public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new();
|
||||
public readonly HashSet< FullPath > MissingFiles = new();
|
||||
public readonly MetaManager MetaManipulations;
|
||||
public ConflictCache Conflicts;
|
||||
public ConflictCache Conflicts = new();
|
||||
|
||||
// Obtain currently changed items. Computes them if they haven't been computed before.
|
||||
public IReadOnlyDictionary< string, object? > ChangedItems
|
||||
{
|
||||
get
|
||||
|
|
@ -117,6 +201,7 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
// The cache reacts through events on its collection changing.
|
||||
public Cache( ModCollection collection )
|
||||
{
|
||||
_collection = collection;
|
||||
|
|
@ -133,6 +218,8 @@ public partial class ModCollection
|
|||
|
||||
private void OnModSettingChange( ModSettingChange type, int modIdx, int oldValue, string? optionName, bool _ )
|
||||
{
|
||||
// Recompute the file list if it was not just a non-conflicting priority change
|
||||
// or a setting change for a disabled mod.
|
||||
if( type == ModSettingChange.Priority && !Conflicts.ModConflicts( modIdx ).Any()
|
||||
|| type == ModSettingChange.Setting && !_collection[ modIdx ].Settings!.Enabled )
|
||||
{
|
||||
|
|
@ -143,9 +230,12 @@ public partial class ModCollection
|
|||
_collection.CalculateEffectiveFileList( hasMeta, Penumbra.CollectionManager.Default == _collection );
|
||||
}
|
||||
|
||||
// Inheritance changes are too big to check for relevance,
|
||||
// just recompute everything.
|
||||
private void OnInheritanceChange( bool _ )
|
||||
=> _collection.CalculateEffectiveFileList( true, true );
|
||||
|
||||
// Reset the shared file-seen cache.
|
||||
private static void ResetFileSeen( int size )
|
||||
{
|
||||
if( size < FileSeen.Length )
|
||||
|
|
@ -160,6 +250,8 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Clear all local and global caches to prepare for recomputation.
|
||||
private void ClearStorageAndPrepare()
|
||||
{
|
||||
ResolvedFiles.Clear();
|
||||
|
|
@ -167,15 +259,15 @@ public partial class ModCollection
|
|||
RegisteredFiles.Clear();
|
||||
_changedItems.Clear();
|
||||
ResolvedSettings.Clear();
|
||||
Conflicts.ClearFileConflicts();
|
||||
// Obtains actual settings for this collection with all inheritances.
|
||||
ResolvedSettings.AddRange( _collection.ActualSettings );
|
||||
}
|
||||
|
||||
public void CalculateEffectiveFileList()
|
||||
{
|
||||
ClearStorageAndPrepare();
|
||||
|
||||
Conflicts.ClearFileConflicts();
|
||||
for( var i = 0; i < Penumbra.ModManager.Mods.Count; ++i )
|
||||
for( var i = 0; i < Penumbra.ModManager.Count; ++i )
|
||||
{
|
||||
if( ResolvedSettings[ i ]?.Enabled == true )
|
||||
{
|
||||
|
|
@ -185,7 +277,6 @@ public partial class ModCollection
|
|||
}
|
||||
|
||||
AddMetaFiles();
|
||||
Conflicts.Sort();
|
||||
}
|
||||
|
||||
private void SetChangedItems()
|
||||
|
|
@ -204,6 +295,7 @@ public partial class ModCollection
|
|||
{
|
||||
identifier.Identify( _changedItems, resolved.ToGamePath() );
|
||||
}
|
||||
// TODO: Meta Manipulations
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
|
|
@ -211,12 +303,12 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private void AddFiles( int idx )
|
||||
{
|
||||
var mod = Penumbra.ModManager.Mods[ idx ];
|
||||
ResetFileSeen( mod.Resources.ModFiles.Count );
|
||||
// Iterate in reverse so that later groups take precedence before earlier ones.
|
||||
// TODO: add group priorities.
|
||||
foreach( var group in mod.Meta.Groups.Values.Reverse() )
|
||||
{
|
||||
switch( group.SelectionType )
|
||||
|
|
@ -240,7 +332,6 @@ public partial class ModCollection
|
|||
=> !Penumbra.Config.DisableSoundStreaming
|
||||
&& gamePath.Path.EndsWith( '.', 's', 'c', 'd' );
|
||||
|
||||
|
||||
private void AddFile( int modIdx, Utf8GamePath gamePath, FullPath file )
|
||||
{
|
||||
if( FilterFile( gamePath ) )
|
||||
|
|
@ -250,11 +341,13 @@ public partial class ModCollection
|
|||
|
||||
if( !RegisteredFiles.TryGetValue( gamePath, out var oldModIdx ) )
|
||||
{
|
||||
// No current conflict, just add.
|
||||
RegisteredFiles.Add( gamePath, modIdx );
|
||||
ResolvedFiles[ gamePath ] = file;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Conflict, check which mod has higher priority, replace if necessary, add conflict.
|
||||
var priority = ResolvedSettings[ modIdx ]!.Priority;
|
||||
var oldPriority = ResolvedSettings[ oldModIdx ]!.Priority;
|
||||
Conflicts.AddConflict( oldModIdx, modIdx, oldPriority, priority, gamePath );
|
||||
|
|
@ -270,6 +363,8 @@ public partial class ModCollection
|
|||
{
|
||||
switch( file.Extension.ToLowerInvariant() )
|
||||
{
|
||||
// We do not care for those file types
|
||||
case ".scp" when !Penumbra.Config.DisableSoundStreaming:
|
||||
case ".meta":
|
||||
case ".rgsp":
|
||||
return;
|
||||
|
|
@ -279,10 +374,11 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
private void AddPathsForOption( Option option, Mod.Mod mod, int modIdx, bool enabled )
|
||||
private void AddPathsForOption( Option option, Mod mod, int modIdx, bool enabled )
|
||||
{
|
||||
foreach( var (file, paths) in option.OptionFiles )
|
||||
{
|
||||
// TODO: complete rework of options.
|
||||
var fullPath = new FullPath( mod.BasePath, file );
|
||||
var idx = mod.Resources.ModFiles.IndexOf( f => f.Equals( fullPath ) );
|
||||
if( idx < 0 )
|
||||
|
|
@ -309,7 +405,7 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
private void AddFilesForSingle( OptionGroup singleGroup, Mod.Mod mod, int modIdx )
|
||||
private void AddFilesForSingle( OptionGroup singleGroup, Mod mod, int modIdx )
|
||||
{
|
||||
Debug.Assert( singleGroup.SelectionType == SelectType.Single );
|
||||
var settings = ResolvedSettings[ modIdx ]!;
|
||||
|
|
@ -324,7 +420,7 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
private void AddFilesForMulti( OptionGroup multiGroup, Mod.Mod mod, int modIdx )
|
||||
private void AddFilesForMulti( OptionGroup multiGroup, Mod mod, int modIdx )
|
||||
{
|
||||
Debug.Assert( multiGroup.SelectionType == SelectType.Multi );
|
||||
var settings = ResolvedSettings[ modIdx ]!;
|
||||
|
|
@ -340,7 +436,7 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
private void AddRemainingFiles( Mod.Mod mod, int modIdx )
|
||||
private void AddRemainingFiles( Mod mod, int modIdx )
|
||||
{
|
||||
for( var i = 0; i < mod.Resources.ModFiles.Count; ++i )
|
||||
{
|
||||
|
|
@ -431,81 +527,4 @@ public partial class ModCollection
|
|||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional( "USE_EQP" )]
|
||||
public void SetEqpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.MetaManagerEqp.ResetFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.Eqp.SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional( "USE_EQDP" )]
|
||||
public void SetEqdpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.MetaManagerEqdp.ResetFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.Eqdp.SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional( "USE_GMP" )]
|
||||
public void SetGmpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.MetaManagerGmp.ResetFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.Gmp.SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional( "USE_EST" )]
|
||||
public void SetEstFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.MetaManagerEst.ResetFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.Est.SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional( "USE_CMP" )]
|
||||
public void SetCmpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.MetaManagerCmp.ResetFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.Cmp.SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
Penumbra.CharacterUtility.ResetAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.SetFiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,21 @@
|
|||
using System;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
// Different types a mod setting can change:
|
||||
public enum ModSettingChange
|
||||
{
|
||||
Inheritance,
|
||||
EnableState,
|
||||
Priority,
|
||||
Setting,
|
||||
Inheritance, // it was set to inherit from other collections or not inherit anymore
|
||||
EnableState, // it was enabled or disabled
|
||||
Priority, // its priority was changed
|
||||
Setting, // a specific setting was changed
|
||||
}
|
||||
|
||||
public partial class ModCollection
|
||||
{
|
||||
// If the change type is a bool, oldValue will be 1 for true and 0 for false.
|
||||
// optionName will only be set for type == Setting.
|
||||
public delegate void ModSettingChangeDelegate( ModSettingChange type, int modIdx, int oldValue, string? optionName, bool inherited );
|
||||
public event ModSettingChangeDelegate ModSettingChanged;
|
||||
|
||||
|
|
@ -99,13 +102,13 @@ public partial class ModCollection
|
|||
private bool FixInheritance( int idx, bool inherit )
|
||||
{
|
||||
var settings = _settings[ idx ];
|
||||
if( inherit != ( settings == null ) )
|
||||
if( inherit == ( settings == null ) )
|
||||
{
|
||||
_settings[ idx ] = inherit ? null : this[ idx ].Settings ?? ModSettings.DefaultSettings( Penumbra.ModManager.Mods[ idx ].Meta );
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
_settings[ idx ] = inherit ? null : this[ idx ].Settings ?? ModSettings.DefaultSettings( Penumbra.ModManager.Mods[ idx ].Meta );
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SaveOnChange( ModSettingChange _1, int _2, int _3, string? _4, bool inherited )
|
||||
|
|
|
|||
127
Penumbra/Collections/ModCollection.File.cs
Normal file
127
Penumbra/Collections/ModCollection.File.cs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Dalamud.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
// File operations like saving, loading and deleting for a collection.
|
||||
public partial class ModCollection
|
||||
{
|
||||
public static string CollectionDirectory
|
||||
=> Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(), "collections" );
|
||||
|
||||
// We need to remove all invalid path symbols from the collection name to be able to save it to file.
|
||||
public FileInfo FileName
|
||||
=> new(Path.Combine( CollectionDirectory, $"{Name.RemoveInvalidPathSymbols()}.json" ));
|
||||
|
||||
// Custom serialization due to shared mod information across managers.
|
||||
public void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = FileName;
|
||||
file.Directory?.Create();
|
||||
using var s = file.Exists ? file.Open( FileMode.Truncate ) : file.Open( FileMode.CreateNew );
|
||||
using var w = new StreamWriter( s, Encoding.UTF8 );
|
||||
using var j = new JsonTextWriter( w );
|
||||
j.Formatting = Formatting.Indented;
|
||||
var x = JsonSerializer.Create( new JsonSerializerSettings { Formatting = Formatting.Indented } );
|
||||
j.WriteStartObject();
|
||||
j.WritePropertyName( nameof( Version ) );
|
||||
j.WriteValue( Version );
|
||||
j.WritePropertyName( nameof( Name ) );
|
||||
j.WriteValue( Name );
|
||||
j.WritePropertyName( nameof( Settings ) );
|
||||
|
||||
// Write all used and unused settings by mod directory name.
|
||||
j.WriteStartObject();
|
||||
for( var i = 0; i < _settings.Count; ++i )
|
||||
{
|
||||
var settings = _settings[ i ];
|
||||
if( settings != null )
|
||||
{
|
||||
j.WritePropertyName( Penumbra.ModManager[ i ].BasePath.Name );
|
||||
x.Serialize( j, settings );
|
||||
}
|
||||
}
|
||||
|
||||
foreach( var (modDir, settings) in _unusedSettings )
|
||||
{
|
||||
j.WritePropertyName( modDir );
|
||||
x.Serialize( j, settings );
|
||||
}
|
||||
|
||||
j.WriteEndObject();
|
||||
|
||||
// Inherit by collection name.
|
||||
j.WritePropertyName( nameof( Inheritance ) );
|
||||
x.Serialize( j, Inheritance.Select( c => c.Name ) );
|
||||
j.WriteEndObject();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not save collection {Name}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
if( Index == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var file = FileName;
|
||||
if( !file.Exists )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not delete collection file {file.FullName} for {Name}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
// Since inheritances depend on other collections existing,
|
||||
// we return them as a list to be applied after reading all collections.
|
||||
public static ModCollection? LoadFromFile( FileInfo file, out IReadOnlyList< string > inheritance )
|
||||
{
|
||||
inheritance = Array.Empty< string >();
|
||||
if( !file.Exists )
|
||||
{
|
||||
PluginLog.Error( $"Could not read collection because {file.FullName} does not exist." );
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var obj = JObject.Parse( File.ReadAllText( file.FullName ) );
|
||||
var name = obj[ nameof( Name ) ]?.ToObject< string >() ?? string.Empty;
|
||||
var version = obj[ nameof( Version ) ]?.ToObject< int >() ?? 0;
|
||||
// Custom deserialization that is converted with the constructor.
|
||||
var settings = obj[ nameof( Settings ) ]?.ToObject< Dictionary< string, ModSettings > >()
|
||||
?? new Dictionary< string, ModSettings >();
|
||||
inheritance = obj[ nameof( Inheritance ) ]?.ToObject< List< string > >() ?? ( IReadOnlyList< string > )Array.Empty< string >();
|
||||
|
||||
return new ModCollection( name, version, settings );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not read collection information from {file.FullName}:\n{e}" );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
// ModCollections can inherit from an arbitrary number of other collections.
|
||||
// This is transitive, so a collection A inheriting from B also inherits from everything B inherits.
|
||||
// Circular dependencies are resolved by distinctness.
|
||||
public partial class ModCollection
|
||||
{
|
||||
private readonly List< ModCollection > _inheritance = new();
|
||||
|
||||
// A change in inheritance usually requires complete recomputation.
|
||||
public event Action< bool > InheritanceChanged;
|
||||
|
||||
private readonly List< ModCollection > _inheritance = new();
|
||||
|
||||
public IReadOnlyList< ModCollection > Inheritance
|
||||
=> _inheritance;
|
||||
|
||||
// Iterate over all collections inherited from in depth-first order.
|
||||
// Skip already visited collections to avoid circular dependencies.
|
||||
public IEnumerable< ModCollection > GetFlattenedInheritance()
|
||||
{
|
||||
yield return this;
|
||||
|
|
@ -27,6 +33,9 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
// Add a new collection to the inheritance list.
|
||||
// We do not check if this collection would be visited before,
|
||||
// only that it is unique in the list itself.
|
||||
public bool AddInheritance( ModCollection collection )
|
||||
{
|
||||
if( ReferenceEquals( collection, this ) || _inheritance.Contains( collection ) )
|
||||
|
|
@ -35,6 +44,7 @@ public partial class ModCollection
|
|||
}
|
||||
|
||||
_inheritance.Add( collection );
|
||||
// Changes in inherited collections may need to trigger further changes here.
|
||||
collection.ModSettingChanged += OnInheritedModSettingChange;
|
||||
collection.InheritanceChanged += OnInheritedInheritanceChange;
|
||||
InheritanceChanged.Invoke( false );
|
||||
|
|
@ -50,6 +60,7 @@ public partial class ModCollection
|
|||
InheritanceChanged.Invoke( false );
|
||||
}
|
||||
|
||||
// Order in the inheritance list is relevant.
|
||||
public void MoveInheritance( int from, int to )
|
||||
{
|
||||
if( _inheritance.Move( from, to ) )
|
||||
|
|
@ -58,6 +69,7 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
// Carry changes in collections inherited from forward if they are relevant for this collection.
|
||||
private void OnInheritedModSettingChange( ModSettingChange type, int modIdx, int oldValue, string? optionName, bool _ )
|
||||
{
|
||||
if( _settings[ modIdx ] == null )
|
||||
|
|
@ -69,13 +81,16 @@ public partial class ModCollection
|
|||
private void OnInheritedInheritanceChange( bool _ )
|
||||
=> InheritanceChanged.Invoke( true );
|
||||
|
||||
// Obtain the actual settings for a given mod via index.
|
||||
// Also returns the collection the settings are taken from.
|
||||
// If no collection provides settings for this mod, this collection is returned together with null.
|
||||
public (ModSettings? Settings, ModCollection Collection) this[ Index idx ]
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach( var collection in GetFlattenedInheritance() )
|
||||
{
|
||||
var settings = _settings[ idx ];
|
||||
var settings = collection._settings[ idx ];
|
||||
if( settings != null )
|
||||
{
|
||||
return ( settings, collection );
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
public sealed partial class ModCollection
|
||||
{
|
||||
// Migration to convert ModCollections from older versions to newer.
|
||||
private static class Migration
|
||||
{
|
||||
public static void Migrate( ModCollection collection )
|
||||
|
|
@ -24,9 +26,10 @@ public sealed partial class ModCollection
|
|||
}
|
||||
|
||||
collection.Version = 1;
|
||||
|
||||
// Remove all completely defaulted settings from active and inactive mods.
|
||||
for( var i = 0; i < collection._settings.Count; ++i )
|
||||
{
|
||||
var setting = collection._settings[ i ];
|
||||
if( SettingIsDefaultV0( collection._settings[ i ] ) )
|
||||
{
|
||||
collection._settings[ i ] = null;
|
||||
|
|
@ -41,7 +44,11 @@ public sealed partial class ModCollection
|
|||
return true;
|
||||
}
|
||||
|
||||
// We treat every completely defaulted setting as inheritance-ready.
|
||||
private static bool SettingIsDefaultV0( ModSettings? setting )
|
||||
=> setting is { Enabled: false, Priority: 0 } && setting.Settings.Values.All( s => s == 0 );
|
||||
}
|
||||
|
||||
internal static ModCollection MigrateFromV0( string name, Dictionary< string, ModSettings > allSettings )
|
||||
=> new(name, 0, allSettings);
|
||||
}
|
||||
|
|
@ -1,21 +1,28 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Dalamud.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Util;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
public partial class ModCollection
|
||||
{
|
||||
// Create the always available Empty Collection that will always sit at index 0,
|
||||
// can not be deleted and does never create a cache.
|
||||
private static ModCollection CreateEmpty()
|
||||
{
|
||||
var collection = CreateNewEmpty( EmptyCollection );
|
||||
collection.Index = 0;
|
||||
collection._settings.Clear();
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
// A ModCollection is a named set of ModSettings to all of the users' installed mods.
|
||||
// It is meant to be local only, and thus should always contain settings for every mod, not just the enabled ones.
|
||||
// Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made.
|
||||
// Active ModCollections build a cache of currently relevant data.
|
||||
// Invariants:
|
||||
// - Index is the collections index in the ModCollection.Manager
|
||||
// - Settings has the same size as ModManager.Mods.
|
||||
// - any change in settings or inheritance of the collection causes a Save.
|
||||
public partial class ModCollection
|
||||
{
|
||||
public const int CurrentVersion = 1;
|
||||
|
|
@ -24,29 +31,28 @@ public partial class ModCollection
|
|||
|
||||
public static readonly ModCollection Empty = CreateEmpty();
|
||||
|
||||
private static ModCollection CreateEmpty()
|
||||
{
|
||||
var collection = CreateNewEmpty( EmptyCollection );
|
||||
collection.Index = 0;
|
||||
collection._settings.Clear();
|
||||
return collection;
|
||||
}
|
||||
|
||||
// The collection name can contain invalid path characters,
|
||||
// but after removing those and going to lower case it has to be unique.
|
||||
public string Name { get; private init; }
|
||||
public int Version { get; private set; }
|
||||
public int Index { get; private set; } = -1;
|
||||
|
||||
// If a ModSetting is null, it can be inherited from other collections.
|
||||
// If no collection provides a setting for the mod, it is just disabled.
|
||||
private readonly List< ModSettings? > _settings;
|
||||
|
||||
public IReadOnlyList< ModSettings? > Settings
|
||||
=> _settings;
|
||||
|
||||
// Evaluates the settings along the whole inheritance tree.
|
||||
public IEnumerable< ModSettings? > ActualSettings
|
||||
=> Enumerable.Range( 0, _settings.Count ).Select( i => this[ i ].Settings );
|
||||
|
||||
// Settings for deleted mods will be kept via directory name.
|
||||
private readonly Dictionary< string, ModSettings > _unusedSettings;
|
||||
|
||||
|
||||
// Constructor for duplication.
|
||||
private ModCollection( string name, ModCollection duplicate )
|
||||
{
|
||||
Name = name;
|
||||
|
|
@ -58,6 +64,7 @@ public partial class ModCollection
|
|||
InheritanceChanged += SaveOnChange;
|
||||
}
|
||||
|
||||
// Constructor for reading from files.
|
||||
private ModCollection( string name, int version, Dictionary< string, ModSettings > allSettings )
|
||||
{
|
||||
Name = name;
|
||||
|
|
@ -79,15 +86,15 @@ public partial class ModCollection
|
|||
InheritanceChanged += SaveOnChange;
|
||||
}
|
||||
|
||||
// Create a new, unique empty collection of a given name.
|
||||
public static ModCollection CreateNewEmpty( string name )
|
||||
=> new(name, CurrentVersion, new Dictionary< string, ModSettings >());
|
||||
|
||||
// Duplicate the calling collection to a new, unique collection of a given name.
|
||||
public ModCollection Duplicate( string name )
|
||||
=> new(name, this);
|
||||
|
||||
internal static ModCollection MigrateFromV0( string name, Dictionary< string, ModSettings > allSettings )
|
||||
=> new(name, 0, allSettings);
|
||||
|
||||
// Remove all settings for not currently-installed mods.
|
||||
public void CleanUnavailableSettings()
|
||||
{
|
||||
var any = _unusedSettings.Count > 0;
|
||||
|
|
@ -98,7 +105,8 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
public void AddMod( Mod.Mod mod )
|
||||
// Add settings for a new appended mod, by checking if the mod had settings from a previous deletion.
|
||||
private void AddMod( Mods.Mod mod )
|
||||
{
|
||||
if( _unusedSettings.TryGetValue( mod.BasePath.Name, out var settings ) )
|
||||
{
|
||||
|
|
@ -111,7 +119,8 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
public void RemoveMod( Mod.Mod mod, int idx )
|
||||
// Move settings from the current mod list to the unused mod settings.
|
||||
private void RemoveMod( Mods.Mod mod, int idx )
|
||||
{
|
||||
var settings = _settings[ idx ];
|
||||
if( settings != null )
|
||||
|
|
@ -121,104 +130,4 @@ public partial class ModCollection
|
|||
|
||||
_settings.RemoveAt( idx );
|
||||
}
|
||||
|
||||
public static string CollectionDirectory
|
||||
=> Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(), "collections" );
|
||||
|
||||
public FileInfo FileName
|
||||
=> new(Path.Combine( CollectionDirectory, $"{Name.RemoveInvalidPathSymbols()}.json" ));
|
||||
|
||||
public void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = FileName;
|
||||
file.Directory?.Create();
|
||||
using var s = file.Open( FileMode.Truncate );
|
||||
using var w = new StreamWriter( s, Encoding.UTF8 );
|
||||
using var j = new JsonTextWriter( w );
|
||||
j.Formatting = Formatting.Indented;
|
||||
var x = JsonSerializer.Create( new JsonSerializerSettings { Formatting = Formatting.Indented } );
|
||||
j.WriteStartObject();
|
||||
j.WritePropertyName( nameof( Version ) );
|
||||
j.WriteValue( Version );
|
||||
j.WritePropertyName( nameof( Name ) );
|
||||
j.WriteValue( Name );
|
||||
j.WritePropertyName( nameof( Settings ) );
|
||||
j.WriteStartObject();
|
||||
for( var i = 0; i < _settings.Count; ++i )
|
||||
{
|
||||
var settings = _settings[ i ];
|
||||
if( settings != null )
|
||||
{
|
||||
j.WritePropertyName( Penumbra.ModManager[ i ].BasePath.Name );
|
||||
x.Serialize( j, settings );
|
||||
}
|
||||
}
|
||||
|
||||
foreach( var settings in _unusedSettings )
|
||||
{
|
||||
j.WritePropertyName( settings.Key );
|
||||
x.Serialize( j, settings.Value );
|
||||
}
|
||||
|
||||
j.WriteEndObject();
|
||||
j.WritePropertyName( nameof( Inheritance ) );
|
||||
x.Serialize( j, Inheritance );
|
||||
j.WriteEndObject();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not save collection {Name}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
if( Index == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var file = FileName;
|
||||
if( file.Exists )
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not delete collection file {file.FullName} for {Name}:\n{e}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ModCollection? LoadFromFile( FileInfo file, out IReadOnlyList< string > inheritance )
|
||||
{
|
||||
inheritance = Array.Empty< string >();
|
||||
if( !file.Exists )
|
||||
{
|
||||
PluginLog.Error( $"Could not read collection because {file.FullName} does not exist." );
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var obj = JObject.Parse( File.ReadAllText( file.FullName ) );
|
||||
var name = obj[ nameof( Name ) ]?.ToObject< string >() ?? string.Empty;
|
||||
var version = obj[ nameof( Version ) ]?.ToObject< int >() ?? 0;
|
||||
var settings = obj[ nameof( Settings ) ]?.ToObject< Dictionary< string, ModSettings > >()
|
||||
?? new Dictionary< string, ModSettings >();
|
||||
inheritance = obj[ nameof( Inheritance ) ]?.ToObject< List< string > >() ?? ( IReadOnlyList< string > )Array.Empty< string >();
|
||||
|
||||
return new ModCollection( name, version, settings );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not read collection information from {file.FullName}:\n{e}" );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,9 @@ using Dalamud.Logging;
|
|||
|
||||
namespace Penumbra;
|
||||
|
||||
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
public partial class Configuration : IPluginConfiguration
|
||||
{
|
||||
private const int CurrentVersion = 1;
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ public class Configuration : IPluginConfiguration
|
|||
|
||||
public string CurrentCollection { get; set; } = "Default";
|
||||
public string DefaultCollection { get; set; } = "Default";
|
||||
public string ForcedCollection { get; set; } = "";
|
||||
|
||||
|
||||
public bool SortFoldersFirst { get; set; } = false;
|
||||
public bool HasReadCharacterCollectionDesc { get; set; } = false;
|
||||
|
|
@ -44,7 +45,6 @@ public class Configuration : IPluginConfiguration
|
|||
public Dictionary< string, string > CharacterCollections { get; set; } = new();
|
||||
public Dictionary< string, string > ModSortOrder { get; set; } = new();
|
||||
|
||||
public bool InvertModListOrder { internal get; set; }
|
||||
|
||||
public static Configuration Load()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Importer.Models
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using ICSharpCode.SharpZipLib.Zip;
|
|||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Importer.Models;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
using FileMode = System.IO.FileMode;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
|||
using FFXIVClientStructs.STD;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Resolver;
|
||||
|
||||
namespace Penumbra.Interop.Loader;
|
||||
|
||||
|
|
@ -30,8 +29,7 @@ public unsafe partial class ResourceLoader
|
|||
public ResourceType Extension;
|
||||
}
|
||||
|
||||
private readonly SortedDictionary< FullPath, DebugData > _debugList = new();
|
||||
private readonly List< (FullPath, DebugData?) > _deleteList = new();
|
||||
private readonly SortedList< FullPath, DebugData > _debugList = new();
|
||||
|
||||
public IReadOnlyDictionary< FullPath, DebugData > DebugList
|
||||
=> _debugList;
|
||||
|
|
@ -161,35 +159,22 @@ public unsafe partial class ResourceLoader
|
|||
|
||||
public void UpdateDebugInfo()
|
||||
{
|
||||
var manager = *ResourceManager;
|
||||
_deleteList.Clear();
|
||||
foreach( var data in _debugList.Values )
|
||||
for( var i = 0; i < _debugList.Count; ++i )
|
||||
{
|
||||
var data = _debugList.Values[ i ];
|
||||
var regularResource = FindResource( data.Category, data.Extension, ( uint )data.OriginalPath.Path.Crc32 );
|
||||
var modifiedResource = FindResource( data.Category, data.Extension, ( uint )data.ManipulatedPath.InternalName.Crc32 );
|
||||
if( modifiedResource == null )
|
||||
{
|
||||
_deleteList.Add( ( data.ManipulatedPath, null ) );
|
||||
_debugList.RemoveAt( i-- );
|
||||
}
|
||||
else if( regularResource != data.OriginalResource || modifiedResource != data.ManipulatedResource )
|
||||
{
|
||||
_deleteList.Add( ( data.ManipulatedPath, data with
|
||||
_debugList[ _debugList.Keys[ i ] ] = data with
|
||||
{
|
||||
OriginalResource = ( Structs.ResourceHandle* )regularResource,
|
||||
ManipulatedResource = ( Structs.ResourceHandle* )modifiedResource,
|
||||
} ) );
|
||||
}
|
||||
}
|
||||
|
||||
foreach( var (path, data) in _deleteList )
|
||||
{
|
||||
if( data == null )
|
||||
{
|
||||
_debugList.Remove( path );
|
||||
}
|
||||
else
|
||||
{
|
||||
_debugList[ path ] = data.Value;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ 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;
|
||||
|
|
@ -93,7 +92,7 @@ public unsafe partial class ResourceLoader
|
|||
// Use the default method of path replacement.
|
||||
public static (FullPath?, object?) DefaultResolver( Utf8GamePath path )
|
||||
{
|
||||
var resolved = Mod.Mod.Manager.ResolvePath( path );
|
||||
var resolved = Mods.Mod.Manager.ResolvePath( path );
|
||||
return ( resolved, null );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
|||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Mods;
|
||||
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||
|
||||
namespace Penumbra.Interop.Resolver;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using Penumbra.Collections;
|
|||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Interop.Resolver;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Dalamud.Utility.Signatures;
|
|||
using Penumbra.Collections;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Interop.Resolver;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Penumbra.Collections;
|
|||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Loader;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Interop.Resolver;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Newtonsoft.Json;
|
|||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Importer;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Meta;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,16 @@ using System.Linq;
|
|||
using Dalamud.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra;
|
||||
|
||||
public partial class Configuration
|
||||
{
|
||||
public string ForcedCollection { internal get; set; } = "";
|
||||
public bool InvertModListOrder { internal get; set; }
|
||||
}
|
||||
|
||||
public static class MigrateConfiguration
|
||||
{
|
||||
public static void Version0To1( Configuration config )
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// A complete Mod containing settings (i.e. dependent on a collection)
|
||||
// and the resulting cache.
|
||||
|
|
@ -4,7 +4,7 @@ using Newtonsoft.Json;
|
|||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public enum SelectType
|
||||
{
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class Mod
|
||||
{
|
||||
|
|
@ -3,9 +3,8 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// Mod contains all permanent information about a mod,
|
||||
// and is independent of collections or settings.
|
||||
|
|
@ -23,7 +22,7 @@ public partial class Mod
|
|||
public FileInfo MetaFile { get; set; }
|
||||
public int Index { get; private set; } = -1;
|
||||
|
||||
private Mod( ModFolder parentFolder, DirectoryInfo basePath, ModMeta meta, ModResources resources )
|
||||
private Mod( ModFolder parentFolder, DirectoryInfo basePath, ModMeta meta, ModResources resources)
|
||||
{
|
||||
BasePath = basePath;
|
||||
Meta = meta;
|
||||
|
|
@ -8,10 +8,9 @@ using System.Security.Cryptography;
|
|||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Importer;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public class ModCleanup
|
||||
{
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Penumbra.Mod;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
|
|
@ -37,7 +36,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 Mod.Mod mod, string newName )
|
||||
public static bool Rename( this global::Penumbra.Mods.Mod mod, string newName )
|
||||
{
|
||||
if( RenameNoSave( mod, newName ) )
|
||||
{
|
||||
|
|
@ -63,7 +62,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 Mod.Mod mod, ModFolder target )
|
||||
public static bool Move( this global::Penumbra.Mods.Mod mod, ModFolder target )
|
||||
{
|
||||
if( MoveNoSave( mod, target ) )
|
||||
{
|
||||
|
|
@ -76,7 +75,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 Mod.Mod mod, string sortOrder )
|
||||
public static void Move( this global::Penumbra.Mods.Mod mod, string sortOrder )
|
||||
{
|
||||
var split = sortOrder.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries );
|
||||
var folder = Root;
|
||||
|
|
@ -137,10 +136,10 @@ 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( Mod.Mod mod )
|
||||
private static void SaveMod( global::Penumbra.Mods.Mod mod )
|
||||
{
|
||||
if( ReferenceEquals( mod.Order.ParentFolder, Root )
|
||||
&& string.Equals( mod.Order.SortOrderName, mod.Meta.Name.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) )
|
||||
&& string.Equals( mod.Order.SortOrderName, mod.Meta.Name.Text.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) )
|
||||
{
|
||||
Penumbra.Config.ModSortOrder.Remove( mod.BasePath.Name );
|
||||
}
|
||||
|
|
@ -184,7 +183,7 @@ public static partial class ModFileSystem
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool RenameNoSave( Mod.Mod mod, string newName )
|
||||
private static bool RenameNoSave( global::Penumbra.Mods.Mod mod, string newName )
|
||||
{
|
||||
newName = newName.Replace( '/', '\\' );
|
||||
if( mod.Order.SortOrderName == newName )
|
||||
|
|
@ -193,12 +192,12 @@ public static partial class ModFileSystem
|
|||
}
|
||||
|
||||
mod.Order.ParentFolder.RemoveModIgnoreEmpty( mod );
|
||||
mod.Order = new Mod.Mod.SortOrder( mod.Order.ParentFolder, newName );
|
||||
mod.Order = new global::Penumbra.Mods.Mod.SortOrder( mod.Order.ParentFolder, newName );
|
||||
mod.Order.ParentFolder.AddMod( mod );
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool MoveNoSave( Mod.Mod mod, ModFolder target )
|
||||
private static bool MoveNoSave( global::Penumbra.Mods.Mod mod, ModFolder target )
|
||||
{
|
||||
var oldParent = mod.Order.ParentFolder;
|
||||
if( ReferenceEquals( target, oldParent ) )
|
||||
|
|
@ -207,7 +206,7 @@ public static partial class ModFileSystem
|
|||
}
|
||||
|
||||
oldParent.RemoveMod( mod );
|
||||
mod.Order = new Mod.Mod.SortOrder( target, mod.Order.SortOrderName );
|
||||
mod.Order = new global::Penumbra.Mods.Mod.SortOrder( target, mod.Order.SortOrderName );
|
||||
target.AddMod( mod );
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,247 +1,245 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.Mod;
|
||||
|
||||
namespace Penumbra.Mods
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class ModFolder
|
||||
{
|
||||
public partial class ModFolder
|
||||
public ModFolder? Parent;
|
||||
|
||||
public string FullName
|
||||
{
|
||||
public ModFolder? Parent;
|
||||
|
||||
public string FullName
|
||||
get
|
||||
{
|
||||
get
|
||||
{
|
||||
var parentPath = Parent?.FullName ?? string.Empty;
|
||||
return parentPath.Any() ? $"{parentPath}/{Name}" : Name;
|
||||
}
|
||||
}
|
||||
|
||||
private string _name = string.Empty;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => _name = value.Replace( '/', '\\' );
|
||||
}
|
||||
|
||||
public List< ModFolder > SubFolders { get; } = new();
|
||||
public List< Mod.Mod > Mods { get; } = new();
|
||||
|
||||
public ModFolder( ModFolder parent, string name )
|
||||
{
|
||||
Parent = parent;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> FullName;
|
||||
|
||||
public int TotalDescendantMods()
|
||||
=> Mods.Count + SubFolders.Sum( f => f.TotalDescendantMods() );
|
||||
|
||||
public int TotalDescendantFolders()
|
||||
=> SubFolders.Sum( f => f.TotalDescendantFolders() );
|
||||
|
||||
// Return all descendant mods in the specified order.
|
||||
public IEnumerable< Mod.Mod > AllMods( bool foldersFirst )
|
||||
{
|
||||
if( foldersFirst )
|
||||
{
|
||||
return SubFolders.SelectMany( f => f.AllMods( foldersFirst ) ).Concat( Mods );
|
||||
}
|
||||
|
||||
return GetSortedEnumerator().SelectMany( f =>
|
||||
{
|
||||
if( f is ModFolder folder )
|
||||
{
|
||||
return folder.AllMods( false );
|
||||
}
|
||||
|
||||
return new[] { ( Mod.Mod )f };
|
||||
} );
|
||||
}
|
||||
|
||||
// Return all descendant subfolders.
|
||||
public IEnumerable< ModFolder > AllFolders()
|
||||
=> SubFolders.SelectMany( f => f.AllFolders() ).Prepend( this );
|
||||
|
||||
// Iterate through all descendants in the specified order, returning subfolders as well as mods.
|
||||
public IEnumerable< object > GetItems( bool foldersFirst )
|
||||
=> foldersFirst ? SubFolders.Cast< object >().Concat( Mods ) : GetSortedEnumerator();
|
||||
|
||||
// Find a subfolder by name. Returns true and sets folder to it if it exists.
|
||||
public bool FindSubFolder( string name, out ModFolder folder )
|
||||
{
|
||||
var subFolder = new ModFolder( this, name );
|
||||
var idx = SubFolders.BinarySearch( subFolder, FolderComparer );
|
||||
folder = idx >= 0 ? SubFolders[ idx ] : this;
|
||||
return idx >= 0;
|
||||
}
|
||||
|
||||
// Checks if an equivalent subfolder as folder already exists and returns its index.
|
||||
// If it does not exist, inserts folder as a subfolder and returns the new index.
|
||||
// Also sets this as folders parent.
|
||||
public int FindOrAddSubFolder( ModFolder folder )
|
||||
{
|
||||
var idx = SubFolders.BinarySearch( folder, FolderComparer );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
return idx;
|
||||
}
|
||||
|
||||
idx = ~idx;
|
||||
SubFolders.Insert( idx, folder );
|
||||
folder.Parent = this;
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Checks if a subfolder with the given name already exists and returns it and its index.
|
||||
// If it does not exists, creates and inserts it and returns the new subfolder and its index.
|
||||
public (ModFolder, int) FindOrCreateSubFolder( string name )
|
||||
{
|
||||
var subFolder = new ModFolder( this, name );
|
||||
var idx = FindOrAddSubFolder( subFolder );
|
||||
return ( SubFolders[ idx ], idx );
|
||||
}
|
||||
|
||||
// Remove folder as a subfolder if it exists.
|
||||
// If this folder is empty afterwards, remove it from its parent.
|
||||
public void RemoveSubFolder( ModFolder folder )
|
||||
{
|
||||
RemoveFolderIgnoreEmpty( folder );
|
||||
CheckEmpty();
|
||||
}
|
||||
|
||||
// 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( Mod.Mod mod )
|
||||
{
|
||||
var idx = Mods.BinarySearch( mod, ModComparer );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
return idx;
|
||||
}
|
||||
|
||||
idx = ~idx;
|
||||
Mods.Insert( idx, mod );
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Remove mod as a child if it exists.
|
||||
// If this folder is empty afterwards, remove it from its parent.
|
||||
public void RemoveMod( Mod.Mod mod )
|
||||
{
|
||||
RemoveModIgnoreEmpty( mod );
|
||||
CheckEmpty();
|
||||
var parentPath = Parent?.FullName ?? string.Empty;
|
||||
return parentPath.Any() ? $"{parentPath}/{Name}" : Name;
|
||||
}
|
||||
}
|
||||
|
||||
// Internals
|
||||
public partial class ModFolder
|
||||
private string _name = string.Empty;
|
||||
|
||||
public string Name
|
||||
{
|
||||
// Create a Root folder without parent.
|
||||
internal static ModFolder CreateRoot()
|
||||
=> new( null!, string.Empty );
|
||||
get => _name;
|
||||
set => _name = value.Replace( '/', '\\' );
|
||||
}
|
||||
|
||||
internal class ModFolderComparer : IComparer< ModFolder >
|
||||
public List< ModFolder > SubFolders { get; } = new();
|
||||
public List< Mod > Mods { get; } = new();
|
||||
|
||||
public ModFolder( ModFolder parent, string name )
|
||||
{
|
||||
Parent = parent;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> FullName;
|
||||
|
||||
public int TotalDescendantMods()
|
||||
=> Mods.Count + SubFolders.Sum( f => f.TotalDescendantMods() );
|
||||
|
||||
public int TotalDescendantFolders()
|
||||
=> SubFolders.Sum( f => f.TotalDescendantFolders() );
|
||||
|
||||
// Return all descendant mods in the specified order.
|
||||
public IEnumerable< Mod > AllMods( bool foldersFirst )
|
||||
{
|
||||
if( foldersFirst )
|
||||
{
|
||||
public StringComparison CompareType = StringComparison.InvariantCultureIgnoreCase;
|
||||
|
||||
// Compare only the direct folder names since this is only used inside an enumeration of subfolders of one folder.
|
||||
public int Compare( ModFolder? x, ModFolder? y )
|
||||
=> ReferenceEquals( x, y )
|
||||
? 0
|
||||
: string.Compare( x?.Name ?? string.Empty, y?.Name ?? string.Empty, CompareType );
|
||||
return SubFolders.SelectMany( f => f.AllMods( foldersFirst ) ).Concat( Mods );
|
||||
}
|
||||
|
||||
internal class ModDataComparer : IComparer< Mod.Mod >
|
||||
return GetSortedEnumerator().SelectMany( f =>
|
||||
{
|
||||
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( Mod.Mod? x, Mod.Mod? y )
|
||||
if( f is ModFolder folder )
|
||||
{
|
||||
if( ReferenceEquals( x, y ) )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var cmp = string.Compare( x?.Order.SortOrderName, y?.Order.SortOrderName, CompareType );
|
||||
if( cmp != 0 )
|
||||
{
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return string.Compare( x?.BasePath.Name, y?.BasePath.Name, StringComparison.InvariantCulture );
|
||||
return folder.AllMods( false );
|
||||
}
|
||||
|
||||
return new[] { ( Mod )f };
|
||||
} );
|
||||
}
|
||||
|
||||
// Return all descendant subfolders.
|
||||
public IEnumerable< ModFolder > AllFolders()
|
||||
=> SubFolders.SelectMany( f => f.AllFolders() ).Prepend( this );
|
||||
|
||||
// Iterate through all descendants in the specified order, returning subfolders as well as mods.
|
||||
public IEnumerable< object > GetItems( bool foldersFirst )
|
||||
=> foldersFirst ? SubFolders.Cast< object >().Concat( Mods ) : GetSortedEnumerator();
|
||||
|
||||
// Find a subfolder by name. Returns true and sets folder to it if it exists.
|
||||
public bool FindSubFolder( string name, out ModFolder folder )
|
||||
{
|
||||
var subFolder = new ModFolder( this, name );
|
||||
var idx = SubFolders.BinarySearch( subFolder, FolderComparer );
|
||||
folder = idx >= 0 ? SubFolders[ idx ] : this;
|
||||
return idx >= 0;
|
||||
}
|
||||
|
||||
// Checks if an equivalent subfolder as folder already exists and returns its index.
|
||||
// If it does not exist, inserts folder as a subfolder and returns the new index.
|
||||
// Also sets this as folders parent.
|
||||
public int FindOrAddSubFolder( ModFolder folder )
|
||||
{
|
||||
var idx = SubFolders.BinarySearch( folder, FolderComparer );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
return idx;
|
||||
}
|
||||
|
||||
internal static readonly ModFolderComparer FolderComparer = new();
|
||||
internal static readonly ModDataComparer ModComparer = new();
|
||||
idx = ~idx;
|
||||
SubFolders.Insert( idx, folder );
|
||||
folder.Parent = this;
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Get an enumerator for actually sorted objects instead of folder-first objects.
|
||||
private IEnumerable< object > GetSortedEnumerator()
|
||||
// Checks if a subfolder with the given name already exists and returns it and its index.
|
||||
// If it does not exists, creates and inserts it and returns the new subfolder and its index.
|
||||
public (ModFolder, int) FindOrCreateSubFolder( string name )
|
||||
{
|
||||
var subFolder = new ModFolder( this, name );
|
||||
var idx = FindOrAddSubFolder( subFolder );
|
||||
return ( SubFolders[ idx ], idx );
|
||||
}
|
||||
|
||||
// Remove folder as a subfolder if it exists.
|
||||
// If this folder is empty afterwards, remove it from its parent.
|
||||
public void RemoveSubFolder( ModFolder folder )
|
||||
{
|
||||
RemoveFolderIgnoreEmpty( folder );
|
||||
CheckEmpty();
|
||||
}
|
||||
|
||||
// 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( Mod mod )
|
||||
{
|
||||
var idx = Mods.BinarySearch( mod, ModComparer );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
var modIdx = 0;
|
||||
foreach( var folder in SubFolders )
|
||||
{
|
||||
var folderString = folder.Name;
|
||||
for( ; modIdx < Mods.Count; ++modIdx )
|
||||
{
|
||||
var mod = Mods[ modIdx ];
|
||||
var modString = mod.Order.SortOrderName;
|
||||
if( string.Compare( folderString, modString, StringComparison.InvariantCultureIgnoreCase ) > 0 )
|
||||
{
|
||||
yield return mod;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
yield return folder;
|
||||
idx = ~idx;
|
||||
Mods.Insert( idx, mod );
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Remove mod as a child if it exists.
|
||||
// If this folder is empty afterwards, remove it from its parent.
|
||||
public void RemoveMod( Mod mod )
|
||||
{
|
||||
RemoveModIgnoreEmpty( mod );
|
||||
CheckEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
// Internals
|
||||
public partial class ModFolder
|
||||
{
|
||||
// Create a Root folder without parent.
|
||||
internal static ModFolder CreateRoot()
|
||||
=> new(null!, string.Empty);
|
||||
|
||||
internal class ModFolderComparer : IComparer< ModFolder >
|
||||
{
|
||||
public StringComparison CompareType = StringComparison.InvariantCultureIgnoreCase;
|
||||
|
||||
// Compare only the direct folder names since this is only used inside an enumeration of subfolders of one folder.
|
||||
public int Compare( ModFolder? x, ModFolder? y )
|
||||
=> ReferenceEquals( x, y )
|
||||
? 0
|
||||
: string.Compare( x?.Name ?? string.Empty, y?.Name ?? string.Empty, CompareType );
|
||||
}
|
||||
|
||||
internal class ModDataComparer : IComparer< 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( Mod? x, Mod? y )
|
||||
{
|
||||
if( ReferenceEquals( x, y ) )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var cmp = string.Compare( x?.Order.SortOrderName, y?.Order.SortOrderName, CompareType );
|
||||
if( cmp != 0 )
|
||||
{
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return string.Compare( x?.BasePath.Name, y?.BasePath.Name, StringComparison.InvariantCulture );
|
||||
}
|
||||
}
|
||||
|
||||
internal static readonly ModFolderComparer FolderComparer = new();
|
||||
internal static readonly ModDataComparer ModComparer = new();
|
||||
|
||||
// Get an enumerator for actually sorted objects instead of folder-first objects.
|
||||
private IEnumerable< object > GetSortedEnumerator()
|
||||
{
|
||||
var modIdx = 0;
|
||||
foreach( var folder in SubFolders )
|
||||
{
|
||||
var folderString = folder.Name;
|
||||
for( ; modIdx < Mods.Count; ++modIdx )
|
||||
{
|
||||
yield return Mods[ modIdx ];
|
||||
var mod = Mods[ modIdx ];
|
||||
var modString = mod.Order.SortOrderName;
|
||||
if( string.Compare( folderString, modString, StringComparison.InvariantCultureIgnoreCase ) > 0 )
|
||||
{
|
||||
yield return mod;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
yield return folder;
|
||||
}
|
||||
|
||||
private void CheckEmpty()
|
||||
for( ; modIdx < Mods.Count; ++modIdx )
|
||||
{
|
||||
if( Mods.Count == 0 && SubFolders.Count == 0 )
|
||||
{
|
||||
Parent?.RemoveSubFolder( this );
|
||||
}
|
||||
yield return Mods[ modIdx ];
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckEmpty()
|
||||
{
|
||||
if( Mods.Count == 0 && SubFolders.Count == 0 )
|
||||
{
|
||||
Parent?.RemoveSubFolder( this );
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a subfolder but do not remove this folder from its parent if it is empty afterwards.
|
||||
internal void RemoveFolderIgnoreEmpty( ModFolder folder )
|
||||
{
|
||||
var idx = SubFolders.BinarySearch( folder, FolderComparer );
|
||||
if( idx < 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove a subfolder but do not remove this folder from its parent if it is empty afterwards.
|
||||
internal void RemoveFolderIgnoreEmpty( ModFolder folder )
|
||||
{
|
||||
var idx = SubFolders.BinarySearch( folder, FolderComparer );
|
||||
if( idx < 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
SubFolders[ idx ].Parent = null;
|
||||
SubFolders.RemoveAt( idx );
|
||||
}
|
||||
|
||||
SubFolders[ idx ].Parent = null;
|
||||
SubFolders.RemoveAt( idx );
|
||||
}
|
||||
|
||||
// Remove a mod, but do not remove this folder from its parent if it is empty afterwards.
|
||||
internal void RemoveModIgnoreEmpty( Mod.Mod mod )
|
||||
// Remove a mod, but do not remove this folder from its parent if it is empty afterwards.
|
||||
internal void RemoveModIgnoreEmpty( Mod mod )
|
||||
{
|
||||
var idx = Mods.BinarySearch( mod, ModComparer );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
var idx = Mods.BinarySearch( mod, ModComparer );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
Mods.RemoveAt( idx );
|
||||
}
|
||||
Mods.RemoveAt( idx );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// Functions that do not really depend on only one component of a mod.
|
||||
public static class ModFunctions
|
||||
|
|
@ -2,16 +2,14 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mod;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class ModManagerNew
|
||||
{
|
||||
private readonly List< Mod.Mod > _mods = new();
|
||||
private readonly List< Mod > _mods = new();
|
||||
|
||||
public IReadOnlyList< Mod.Mod > Mods
|
||||
public IReadOnlyList< Mod > Mods
|
||||
=> _mods;
|
||||
|
||||
public void DiscoverMods()
|
||||
|
|
@ -37,6 +35,7 @@ public partial class ModManagerNew
|
|||
//Collections.RecreateCaches();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ModManagerNew
|
||||
{
|
||||
public DirectoryInfo BasePath { get; private set; } = null!;
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@ 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;
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class Mod
|
||||
{
|
||||
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
|
@ -12,7 +11,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 Mod.Mod.Manager manager, string newName, Mod.Mod mod )
|
||||
public static bool RenameMod( this Mod.Manager manager, string newName, Mod mod )
|
||||
{
|
||||
if( newName.Length == 0 || string.Equals( newName, mod.Meta.Name, StringComparison.InvariantCulture ) )
|
||||
{
|
||||
|
|
@ -25,14 +24,14 @@ public static class ModManagerEditExtensions
|
|||
return true;
|
||||
}
|
||||
|
||||
public static bool ChangeSortOrder( this Mod.Mod.Manager manager, Mod.Mod mod, string newSortOrder )
|
||||
public static bool ChangeSortOrder( this Mod.Manager manager, Mod mod, string newSortOrder )
|
||||
{
|
||||
if( string.Equals( mod.Order.FullPath, newSortOrder, StringComparison.InvariantCultureIgnoreCase ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inRoot = new Mod.Mod.SortOrder( manager.StructuredMods, mod.Meta.Name );
|
||||
var inRoot = new Mod.SortOrder( manager.StructuredMods, mod.Meta.Name );
|
||||
if( newSortOrder == string.Empty || newSortOrder == inRoot.SortOrderName )
|
||||
{
|
||||
mod.Order = inRoot;
|
||||
|
|
@ -49,7 +48,7 @@ public static class ModManagerEditExtensions
|
|||
return true;
|
||||
}
|
||||
|
||||
public static bool RenameModFolder( this Mod.Mod.Manager manager, Mod.Mod mod, DirectoryInfo newDir, bool move = true )
|
||||
public static bool RenameModFolder( this Mod.Manager manager, Mod mod, DirectoryInfo newDir, bool move = true )
|
||||
{
|
||||
if( move )
|
||||
{
|
||||
|
|
@ -73,7 +72,7 @@ public static class ModManagerEditExtensions
|
|||
|
||||
var oldBasePath = mod.BasePath;
|
||||
mod.BasePath = newDir;
|
||||
mod.MetaFile = Mod.Mod.MetaFileInfo( newDir );
|
||||
mod.MetaFile = Mod.MetaFileInfo( newDir );
|
||||
manager.UpdateMod( mod );
|
||||
|
||||
if( manager.Config.ModSortOrder.ContainsKey( oldBasePath.Name ) )
|
||||
|
|
@ -95,7 +94,7 @@ public static class ModManagerEditExtensions
|
|||
return true;
|
||||
}
|
||||
|
||||
public static bool ChangeModGroup( this Mod.Mod.Manager manager, string oldGroupName, string newGroupName, Mod.Mod mod,
|
||||
public static bool ChangeModGroup( this Mod.Manager manager, string oldGroupName, string newGroupName, Mod mod,
|
||||
SelectType type = SelectType.Single )
|
||||
{
|
||||
if( newGroupName == oldGroupName || mod.Meta.Groups.ContainsKey( newGroupName ) )
|
||||
|
|
@ -157,7 +156,7 @@ public static class ModManagerEditExtensions
|
|||
return true;
|
||||
}
|
||||
|
||||
public static bool RemoveModOption( this Mod.Mod.Manager manager, int optionIdx, OptionGroup group, Mod.Mod mod )
|
||||
public static bool RemoveModOption( this Mod.Manager manager, int optionIdx, OptionGroup group, Mod mod )
|
||||
{
|
||||
if( optionIdx < 0 || optionIdx >= group.Options.Count )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,47 +5,20 @@ using System.Linq;
|
|||
using Dalamud.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// Contains descriptive data about the mod as well as possible settings and fileswaps.
|
||||
public class ModMeta
|
||||
{
|
||||
public uint FileVersion { get; set; }
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
_name = value;
|
||||
LowerName = value.ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private string _name = "Mod";
|
||||
|
||||
[JsonIgnore]
|
||||
public string LowerName { get; private set; } = "mod";
|
||||
|
||||
private string _author = "";
|
||||
|
||||
public string Author
|
||||
{
|
||||
get => _author;
|
||||
set
|
||||
{
|
||||
_author = value;
|
||||
LowerAuthor = value.ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string LowerAuthor { get; private set; } = "";
|
||||
|
||||
public string Description { get; set; } = "";
|
||||
public string Version { get; set; } = "";
|
||||
public string Website { get; set; } = "";
|
||||
public LowerString Name { get; set; } = "Mod";
|
||||
public LowerString Author { get; set; } = LowerString.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Version { get; set; } = string.Empty;
|
||||
public string Website { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty( ItemConverterType = typeof( FullPath.FullPathConverter ) )]
|
||||
public Dictionary< Utf8GamePath, FullPath > FileSwaps { get; set; } = new();
|
||||
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
|||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
[Flags]
|
||||
public enum ResourceChange
|
||||
|
|
@ -2,7 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// Contains the settings for a given mod.
|
||||
public class ModSettings
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// Contains settings with the option selections stored by names instead of index.
|
||||
// This is meant to make them possibly more portable when we support importing collections from other users.
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
|
|
@ -10,13 +9,12 @@ using Lumina.Excel.GeneratedSheets;
|
|||
using Penumbra.Api;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI;
|
||||
using Penumbra.Util;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Interop.Loader;
|
||||
using Penumbra.Interop.Resolver;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra;
|
||||
|
||||
|
|
@ -34,8 +32,9 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
public static ResidentResourceManager ResidentResources { get; private set; } = null!;
|
||||
public static CharacterUtility CharacterUtility { get; private set; } = null!;
|
||||
public static MetaFileManager MetaFileManager { get; private set; } = null!;
|
||||
|
||||
public static Mod.Mod.Manager ModManager { get; private set; } = null!;
|
||||
public static Mod.Manager ModManager { get; private set; } = null!;
|
||||
public static ModCollection.Manager CollectionManager { get; private set; } = null!;
|
||||
|
||||
public static ResourceLoader ResourceLoader { get; set; } = null!;
|
||||
|
|
@ -65,9 +64,10 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
ResidentResources = new ResidentResourceManager();
|
||||
CharacterUtility = new CharacterUtility();
|
||||
MetaFileManager = new MetaFileManager();
|
||||
ResourceLoader = new ResourceLoader( this );
|
||||
ResourceLogger = new ResourceLogger( ResourceLoader );
|
||||
ModManager = new Mod.Mod.Manager();
|
||||
ModManager = new Mod.Manager();
|
||||
ModManager.DiscoverMods();
|
||||
CollectionManager = new ModCollection.Manager( ModManager );
|
||||
ObjectReloader = new ObjectReloader();
|
||||
|
|
@ -213,6 +213,7 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
PathResolver.Dispose();
|
||||
ResourceLogger.Dispose();
|
||||
MetaFileManager.Dispose();
|
||||
ResourceLoader.Dispose();
|
||||
CharacterUtility.Dispose();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using Dalamud.Interface.Components;
|
|||
using Dalamud.Logging;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI.Custom;
|
||||
using Penumbra.Util;
|
||||
|
|
@ -22,7 +21,7 @@ public partial class SettingsInterface
|
|||
private readonly Selector _selector;
|
||||
private string _collectionNames = null!;
|
||||
private string _collectionNamesWithNone = null!;
|
||||
private ModCollection[] _collections = null!;
|
||||
private ModCollection[] _collections = null!;
|
||||
private int _currentCollectionIndex;
|
||||
private int _currentDefaultIndex;
|
||||
private readonly Dictionary< string, int > _currentCharacterIndices = new();
|
||||
|
|
@ -192,6 +191,65 @@ public partial class SettingsInterface
|
|||
}
|
||||
}
|
||||
|
||||
private static void DrawInheritance( ModCollection collection )
|
||||
{
|
||||
ImGui.PushID( collection.Index );
|
||||
if( ImGui.TreeNodeEx( collection.Name, ImGuiTreeNodeFlags.DefaultOpen ) )
|
||||
{
|
||||
foreach( var inheritance in collection.Inheritance )
|
||||
{
|
||||
DrawInheritance( inheritance );
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.PopID();
|
||||
}
|
||||
|
||||
private void DrawCurrentCollectionInheritance()
|
||||
{
|
||||
if( !ImGui.BeginListBox( "##inheritanceList",
|
||||
new Vector2( SettingsMenu.InputTextWidth, ImGui.GetTextLineHeightWithSpacing() * 10 ) ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var end = ImGuiRaii.DeferredEnd( ImGui.EndListBox );
|
||||
DrawInheritance( _collections[ _currentCollectionIndex + 1 ] );
|
||||
}
|
||||
|
||||
private static int _newInheritanceIdx = 0;
|
||||
|
||||
private void DrawNewInheritanceSelection()
|
||||
{
|
||||
ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth - ImGui.GetFrameHeight() - ImGui.GetStyle().ItemSpacing.X );
|
||||
if( ImGui.BeginCombo( "##newInheritance", Penumbra.CollectionManager[ _newInheritanceIdx ].Name ) )
|
||||
{
|
||||
using var end = ImGuiRaii.DeferredEnd( ImGui.EndCombo );
|
||||
foreach( var collection in Penumbra.CollectionManager )
|
||||
{
|
||||
if( ImGui.Selectable( collection.Name, _newInheritanceIdx == collection.Index ) )
|
||||
{
|
||||
_newInheritanceIdx = collection.Index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
var valid = _newInheritanceIdx > ModCollection.Empty.Index
|
||||
&& _collections[ _currentCollectionIndex + 1 ].Index != _newInheritanceIdx
|
||||
&& _collections[ _currentCollectionIndex + 1 ].Inheritance.All( c => c.Index != _newInheritanceIdx );
|
||||
using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, !valid );
|
||||
using var font = ImGuiRaii.PushFont( UiBuilder.IconFont );
|
||||
if( ImGui.Button( $"{FontAwesomeIcon.Plus.ToIconString()}##newInheritanceAdd", ImGui.GetFrameHeight() * Vector2.One ) && valid )
|
||||
{
|
||||
_collections[ _currentCollectionIndex + 1 ].AddInheritance( Penumbra.CollectionManager[ _newInheritanceIdx ] );
|
||||
}
|
||||
|
||||
style.Pop();
|
||||
font.Pop();
|
||||
ImGuiComponents.HelpMarker( "Add a new inheritance to the collection." );
|
||||
}
|
||||
|
||||
private void DrawDefaultCollectionSelector()
|
||||
{
|
||||
var index = _currentDefaultIndex;
|
||||
|
|
@ -344,12 +402,14 @@ public partial class SettingsInterface
|
|||
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem )
|
||||
.Push( ImGui.EndChild );
|
||||
|
||||
if( ImGui.BeginChild( "##CollectionHandling", new Vector2( -1, ImGui.GetTextLineHeightWithSpacing() * 6 ), true ) )
|
||||
if( ImGui.BeginChild( "##CollectionHandling", new Vector2( -1, ImGui.GetTextLineHeightWithSpacing() * 17 ), true ) )
|
||||
{
|
||||
DrawCurrentCollectionSelector( true );
|
||||
|
||||
ImGuiHelpers.ScaledDummy( 0, 10 );
|
||||
DrawNewCollectionInput();
|
||||
ImGuiHelpers.ScaledDummy( 0, 10 );
|
||||
DrawCurrentCollectionInheritance();
|
||||
DrawNewInheritanceSelection();
|
||||
}
|
||||
|
||||
raii.Pop();
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ using ImGuiNET;
|
|||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI.Custom;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
|
|
@ -17,10 +17,8 @@ public partial class SettingsInterface
|
|||
{
|
||||
private const string LabelTab = "Effective Changes";
|
||||
|
||||
private string _gamePathFilter = string.Empty;
|
||||
private string _gamePathFilterLower = string.Empty;
|
||||
private string _filePathFilter = string.Empty;
|
||||
private string _filePathFilterLower = string.Empty;
|
||||
private LowerString _gamePathFilter = LowerString.Empty;
|
||||
private LowerString _filePathFilter = LowerString.Empty;
|
||||
|
||||
private const float LeftTextLength = 600;
|
||||
|
||||
|
|
@ -57,47 +55,49 @@ public partial class SettingsInterface
|
|||
}
|
||||
|
||||
ImGui.SetNextItemWidth( LeftTextLength * ImGuiHelpers.GlobalScale );
|
||||
if( ImGui.InputTextWithHint( "##effective_changes_gfilter", "Filter game path...", ref _gamePathFilter, 256 ) )
|
||||
var tmp = _gamePathFilter.Text;
|
||||
if( ImGui.InputTextWithHint( "##effective_changes_gfilter", "Filter game path...", ref tmp, 256 ) )
|
||||
{
|
||||
_gamePathFilterLower = _gamePathFilter.ToLowerInvariant();
|
||||
_gamePathFilter = tmp;
|
||||
}
|
||||
|
||||
ImGui.SameLine( ( LeftTextLength + _arrowLength ) * ImGuiHelpers.GlobalScale + 3 * ImGui.GetStyle().ItemSpacing.X );
|
||||
ImGui.SetNextItemWidth( -1 );
|
||||
if( ImGui.InputTextWithHint( "##effective_changes_ffilter", "Filter file path...", ref _filePathFilter, 256 ) )
|
||||
tmp = _filePathFilter.Text;
|
||||
if( ImGui.InputTextWithHint( "##effective_changes_ffilter", "Filter file path...", ref tmp, 256 ) )
|
||||
{
|
||||
_filePathFilterLower = _filePathFilter.ToLowerInvariant();
|
||||
_filePathFilter = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckFilters( KeyValuePair< Utf8GamePath, FullPath > kvp )
|
||||
{
|
||||
if( _gamePathFilter.Any() && !kvp.Key.ToString().Contains( _gamePathFilterLower ) )
|
||||
if( _gamePathFilter.Length > 0 && !kvp.Key.ToString().Contains( _gamePathFilter.Lower ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !_filePathFilter.Any() || kvp.Value.FullName.ToLowerInvariant().Contains( _filePathFilterLower );
|
||||
return _filePathFilter.Length == 0 || kvp.Value.FullName.ToLowerInvariant().Contains( _filePathFilter.Lower );
|
||||
}
|
||||
|
||||
private bool CheckFilters( KeyValuePair< Utf8GamePath, Utf8GamePath > kvp )
|
||||
{
|
||||
if( _gamePathFilter.Any() && !kvp.Key.ToString().Contains( _gamePathFilterLower ) )
|
||||
if( _gamePathFilter.Length > 0 && !kvp.Key.ToString().Contains( _gamePathFilter.Lower ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !_filePathFilter.Any() || kvp.Value.ToString().Contains( _filePathFilterLower );
|
||||
return _filePathFilter.Length == 0 || kvp.Value.ToString().Contains( _filePathFilter.Lower );
|
||||
}
|
||||
|
||||
private bool CheckFilters( (string, string, string) kvp )
|
||||
private bool CheckFilters( (string, LowerString) kvp )
|
||||
{
|
||||
if( _gamePathFilter.Any() && !kvp.Item1.ToLowerInvariant().Contains( _gamePathFilterLower ) )
|
||||
if( _gamePathFilter.Length > 0 && !kvp.Item1.ToLowerInvariant().Contains( _gamePathFilter.Lower ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !_filePathFilter.Any() || kvp.Item3.Contains( _filePathFilterLower );
|
||||
return _filePathFilter.Length == 0 || kvp.Item2.Contains( _filePathFilter.Lower );
|
||||
}
|
||||
|
||||
private void DrawFilteredRows( ModCollection active )
|
||||
|
|
@ -113,49 +113,43 @@ public partial class SettingsInterface
|
|||
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 ) )
|
||||
foreach( var (mp, mod) in cache.Cmp.Manipulations
|
||||
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) )
|
||||
.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 ) )
|
||||
foreach( var (mp, mod) in cache.Eqp.Manipulations
|
||||
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) )
|
||||
.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 ) )
|
||||
foreach( var (mp, mod) in cache.Eqdp.Manipulations
|
||||
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) )
|
||||
.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 ) )
|
||||
foreach( var (mp, mod) in cache.Gmp.Manipulations
|
||||
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) )
|
||||
.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 ) )
|
||||
foreach( var (mp, mod) in cache.Est.Manipulations
|
||||
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) )
|
||||
.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 ) )
|
||||
foreach( var (mp, mod) in cache.Imc.Manipulations
|
||||
.Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) )
|
||||
.Where( CheckFilters ) )
|
||||
{
|
||||
DrawLine( mp, mod );
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
|
|
@ -15,19 +14,19 @@ public class ModListCache : IDisposable
|
|||
public const uint ConflictingModColor = 0xFFAAAAFFu;
|
||||
public const uint HandledConflictModColor = 0xFF88DDDDu;
|
||||
|
||||
private readonly Mod.Mod.Manager _manager;
|
||||
private readonly Mods.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;
|
||||
private LowerString _modFilter = LowerString.Empty;
|
||||
private LowerString _modFilterAuthor = LowerString.Empty;
|
||||
private LowerString _modFilterChanges = LowerString.Empty;
|
||||
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
|
||||
private bool _listResetNecessary;
|
||||
private bool _filterResetNecessary;
|
||||
|
||||
|
||||
public ModFilter StateFilter
|
||||
|
|
@ -44,7 +43,7 @@ public class ModListCache : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public ModListCache( Mod.Mod.Manager manager, IReadOnlySet< string > newMods )
|
||||
public ModListCache( Mods.Mod.Manager manager, IReadOnlySet< string > newMods )
|
||||
{
|
||||
_manager = manager;
|
||||
_newMods = newMods;
|
||||
|
|
@ -123,20 +122,20 @@ public class ModListCache : IDisposable
|
|||
if( lower.StartsWith( "c:" ) )
|
||||
{
|
||||
_modFilterChanges = lower[ 2.. ];
|
||||
_modFilter = string.Empty;
|
||||
_modFilterAuthor = string.Empty;
|
||||
_modFilter = LowerString.Empty;
|
||||
_modFilterAuthor = LowerString.Empty;
|
||||
}
|
||||
else if( lower.StartsWith( "a:" ) )
|
||||
{
|
||||
_modFilterAuthor = lower[ 2.. ];
|
||||
_modFilter = string.Empty;
|
||||
_modFilterChanges = string.Empty;
|
||||
_modFilter = LowerString.Empty;
|
||||
_modFilterChanges = LowerString.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
_modFilter = lower;
|
||||
_modFilterAuthor = string.Empty;
|
||||
_modFilterChanges = string.Empty;
|
||||
_modFilterAuthor = LowerString.Empty;
|
||||
_modFilterChanges = LowerString.Empty;
|
||||
}
|
||||
|
||||
ResetFilters();
|
||||
|
|
@ -233,12 +232,12 @@ public class ModListCache : IDisposable
|
|||
{
|
||||
var ret = ( false, 0u );
|
||||
|
||||
if( _modFilter.Length > 0 && !mod.Data.Meta.LowerName.Contains( _modFilter ) )
|
||||
if( _modFilter.Length > 0 && !mod.Data.Meta.Name.Contains( _modFilter ) )
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
if( _modFilterAuthor.Length > 0 && !mod.Data.Meta.LowerAuthor.Contains( _modFilterAuthor ) )
|
||||
if( _modFilterAuthor.Length > 0 && !mod.Data.Meta.Author.Contains( _modFilterAuthor ) )
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ 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;
|
||||
using Penumbra.Util;
|
||||
|
|
@ -201,7 +200,7 @@ public partial class SettingsInterface
|
|||
|
||||
raii.Push( ImGui.EndListBox );
|
||||
using var indent = ImGuiRaii.PushIndent( 0 );
|
||||
Mod.Mod? oldBadMod = null;
|
||||
Mods.Mod? oldBadMod = null;
|
||||
foreach( var conflict in conflicts )
|
||||
{
|
||||
var badMod = Penumbra.ModManager[ conflict.Mod2 ];
|
||||
|
|
@ -224,14 +223,14 @@ public partial class SettingsInterface
|
|||
indent.Push( 30f );
|
||||
}
|
||||
|
||||
if( conflict.Conflict is Utf8GamePath p )
|
||||
if( conflict.Data is Utf8GamePath p )
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
ImGuiNative.igSelectable_Bool( p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero );
|
||||
}
|
||||
}
|
||||
else if( conflict.Conflict is MetaManipulation m )
|
||||
else if( conflict.Data is MetaManipulation m )
|
||||
{
|
||||
ImGui.Selectable( m.Manipulation?.ToString() ?? string.Empty );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Dalamud.Interface;
|
|||
using ImGuiNET;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI.Custom;
|
||||
using Penumbra.Util;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Numerics;
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Logging;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI.Custom;
|
||||
using Penumbra.Util;
|
||||
|
|
@ -69,7 +68,7 @@ public partial class SettingsInterface
|
|||
_currentWebsite = Meta?.Website ?? "";
|
||||
}
|
||||
|
||||
private Mod.FullMod? Mod
|
||||
private Mods.FullMod? Mod
|
||||
=> _selector.Mod;
|
||||
|
||||
private ModMeta? Meta
|
||||
|
|
@ -77,7 +76,7 @@ public partial class SettingsInterface
|
|||
|
||||
private void DrawName()
|
||||
{
|
||||
var name = Meta!.Name;
|
||||
var name = Meta!.Name.Text;
|
||||
var modManager = Penumbra.ModManager;
|
||||
if( ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 ) && modManager.RenameMod( name, Mod!.Data ) )
|
||||
{
|
||||
|
|
@ -122,7 +121,7 @@ public partial class SettingsInterface
|
|||
ImGui.TextColored( GreyColor, "by" );
|
||||
|
||||
ImGui.SameLine();
|
||||
var author = Meta!.Author;
|
||||
var author = Meta!.Author.Text;
|
||||
if( ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 )
|
||||
&& author != Meta.Author )
|
||||
{
|
||||
|
|
@ -228,7 +227,7 @@ public partial class SettingsInterface
|
|||
}
|
||||
}
|
||||
|
||||
public static bool DrawSortOrder( Mod.Mod mod, Mod.Mod.Manager manager, Selector selector )
|
||||
public static bool DrawSortOrder( Mods.Mod mod, Mods.Mod.Manager manager, Selector selector )
|
||||
{
|
||||
var currentSortOrder = mod.Order.FullPath;
|
||||
ImGui.SetNextItemWidth( 300 * ImGuiHelpers.GlobalScale );
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using Dalamud.Logging;
|
|||
using ImGuiNET;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Importer;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI.Custom;
|
||||
using Penumbra.Util;
|
||||
|
|
@ -410,11 +409,11 @@ public partial class SettingsInterface
|
|||
// Selection
|
||||
private partial class Selector
|
||||
{
|
||||
public Mod.FullMod? Mod { get; private set; }
|
||||
public Mods.FullMod? Mod { get; private set; }
|
||||
private int _index;
|
||||
private string _nextDir = string.Empty;
|
||||
|
||||
private void SetSelection( int idx, Mod.FullMod? info )
|
||||
private void SetSelection( int idx, Mods.FullMod? info )
|
||||
{
|
||||
Mod = info;
|
||||
if( idx != _index )
|
||||
|
|
@ -480,7 +479,7 @@ public partial class SettingsInterface
|
|||
private partial class Selector
|
||||
{
|
||||
// === Mod ===
|
||||
private void DrawModOrderPopup( string popupName, Mod.FullMod mod, bool firstOpen )
|
||||
private void DrawModOrderPopup( string popupName, Mods.FullMod mod, bool firstOpen )
|
||||
{
|
||||
if( !ImGui.BeginPopup( popupName ) )
|
||||
{
|
||||
|
|
@ -664,7 +663,7 @@ public partial class SettingsInterface
|
|||
idx += sub.TotalDescendantMods();
|
||||
}
|
||||
}
|
||||
else if( item is Mod.Mod _ )
|
||||
else if( item is Mods.Mod _ )
|
||||
{
|
||||
var (mod, visible, color) = Cache.GetMod( idx );
|
||||
if( mod != null && visible )
|
||||
|
|
@ -721,7 +720,7 @@ public partial class SettingsInterface
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawMod( Mod.FullMod mod, int modIndex, uint color )
|
||||
private void DrawMod( Mods.FullMod mod, int modIndex, uint color )
|
||||
{
|
||||
using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Text, color, color != 0 );
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using Dalamud.Logging;
|
|||
using ImGuiNET;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Interop;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI.Custom;
|
||||
using Penumbra.Util;
|
||||
|
||||
|
|
|
|||
122
Penumbra/Util/LowerString.cs
Normal file
122
Penumbra/Util/LowerString.cs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
using System;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Penumbra.Util;
|
||||
|
||||
[JsonConverter( typeof( Converter ) )]
|
||||
public readonly struct LowerString : IEquatable< LowerString >, IComparable< LowerString >
|
||||
{
|
||||
public static readonly LowerString Empty = new(string.Empty);
|
||||
|
||||
public readonly string Text = string.Empty;
|
||||
public readonly string Lower = string.Empty;
|
||||
|
||||
public LowerString( string text )
|
||||
{
|
||||
Text = string.Intern( text );
|
||||
Lower = string.Intern( text.ToLowerInvariant() );
|
||||
}
|
||||
|
||||
|
||||
public int Length
|
||||
=> Text.Length;
|
||||
|
||||
public int Count
|
||||
=> Length;
|
||||
|
||||
public bool Equals( LowerString other )
|
||||
=> string.Equals( Lower, other.Lower, StringComparison.InvariantCulture );
|
||||
|
||||
public bool Equals( string other )
|
||||
=> string.Equals( Lower, other, StringComparison.InvariantCultureIgnoreCase );
|
||||
|
||||
public int CompareTo( LowerString other )
|
||||
=> string.Compare( Lower, other.Lower, StringComparison.InvariantCulture );
|
||||
|
||||
public int CompareTo( string other )
|
||||
=> string.Compare( Lower, other, StringComparison.InvariantCultureIgnoreCase );
|
||||
|
||||
public bool Contains( LowerString other )
|
||||
=> Lower.Contains( other.Lower, StringComparison.InvariantCulture );
|
||||
|
||||
public bool Contains( string other )
|
||||
=> Lower.Contains( other, StringComparison.InvariantCultureIgnoreCase );
|
||||
|
||||
public bool StartsWith( LowerString other )
|
||||
=> Lower.StartsWith( other.Lower, StringComparison.InvariantCulture );
|
||||
|
||||
public bool StartsWith( string other )
|
||||
=> Lower.StartsWith( other, StringComparison.InvariantCultureIgnoreCase );
|
||||
|
||||
public bool EndsWith( LowerString other )
|
||||
=> Lower.EndsWith( other.Lower, StringComparison.InvariantCulture );
|
||||
|
||||
public bool EndsWith( string other )
|
||||
=> Lower.EndsWith( other, StringComparison.InvariantCultureIgnoreCase );
|
||||
|
||||
public override string ToString()
|
||||
=> Text;
|
||||
|
||||
public static implicit operator string( LowerString s )
|
||||
=> s.Text;
|
||||
|
||||
public static implicit operator LowerString( string s )
|
||||
=> new(s);
|
||||
|
||||
private class Converter : JsonConverter< LowerString >
|
||||
{
|
||||
public override void WriteJson( JsonWriter writer, LowerString value, JsonSerializer serializer )
|
||||
{
|
||||
writer.WriteValue( value.Text );
|
||||
}
|
||||
|
||||
public override LowerString ReadJson( JsonReader reader, Type objectType, LowerString existingValue, bool hasExistingValue,
|
||||
JsonSerializer serializer )
|
||||
{
|
||||
if( reader.Value is string text )
|
||||
{
|
||||
return new LowerString( text );
|
||||
}
|
||||
|
||||
return existingValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool InputWithHint( string label, string hint, ref LowerString s, uint maxLength = 128,
|
||||
ImGuiInputTextFlags flags = ImGuiInputTextFlags.None )
|
||||
{
|
||||
var tmp = s.Text;
|
||||
if( !ImGui.InputTextWithHint( label, hint, ref tmp, maxLength, flags ) || tmp == s.Text )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
s = new LowerString( tmp );
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is LowerString lowerString && Equals( lowerString );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Text.GetHashCode();
|
||||
|
||||
public static bool operator ==( LowerString lhs, LowerString rhs )
|
||||
=> lhs.Equals( rhs );
|
||||
|
||||
public static bool operator !=( LowerString lhs, LowerString rhs )
|
||||
=> lhs.Equals( rhs );
|
||||
|
||||
public static bool operator ==( LowerString lhs, string rhs )
|
||||
=> lhs.Equals( rhs );
|
||||
|
||||
public static bool operator !=( LowerString lhs, string rhs )
|
||||
=> lhs.Equals( rhs );
|
||||
|
||||
public static bool operator ==( string lhs, LowerString rhs )
|
||||
=> rhs.Equals( lhs );
|
||||
|
||||
public static bool operator !=( string lhs, LowerString rhs )
|
||||
=> rhs.Equals( lhs );
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using System.Text.RegularExpressions;
|
|||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Util;
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ public static class ModelChanger
|
|||
}
|
||||
}
|
||||
|
||||
public static bool ChangeModMaterials( Mod.Mod mod, string from, string to )
|
||||
public static bool ChangeModMaterials( Mods.Mod mod, string from, string to )
|
||||
{
|
||||
if( ValidStrings( from, to ) )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Penumbra.Util;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue