mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
tmp
This commit is contained in:
parent
48e442a9fd
commit
da73feacf4
59 changed files with 2115 additions and 3428 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit 5968fc8dde7867ec9b7216deeed93d7b59a41ab8
|
||||
Subproject commit 05619f966e6acfe8b0b6e947243c5d930c7525a4
|
||||
|
|
@ -16,15 +16,17 @@ public class ModsController : WebApiController
|
|||
[Route( HttpVerbs.Get, "/mods" )]
|
||||
public object? GetMods()
|
||||
{
|
||||
return Penumbra.ModManager.Mods.Zip( Penumbra.CollectionManager.Current.ActualSettings ).Select( x => new
|
||||
{
|
||||
x.Second?.Enabled,
|
||||
x.Second?.Priority,
|
||||
x.First.BasePath.Name,
|
||||
x.First.Meta,
|
||||
BasePath = x.First.BasePath.FullName,
|
||||
Files = x.First.Resources.ModFiles.Select( fi => fi.FullName ),
|
||||
} );
|
||||
// TODO
|
||||
return null;
|
||||
//return Penumbra.ModManager.Mods.Zip( Penumbra.CollectionManager.Current.ActualSettings ).Select( x => new
|
||||
//{
|
||||
// x.Second?.Enabled,
|
||||
// x.Second?.Priority,
|
||||
// x.First.BasePath.Name,
|
||||
// x.First.Name,
|
||||
// BasePath = x.First.BasePath.FullName,
|
||||
// Files = x.First.Resources.ModFiles.Select( fi => fi.FullName ),
|
||||
//} );
|
||||
}
|
||||
|
||||
[Route( HttpVerbs.Post, "/mods" )]
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
_penumbra!.ObjectReloader.RedrawAll( setting );
|
||||
}
|
||||
|
||||
private static string ResolvePath( string path, Mods.Mod.Manager _, ModCollection collection )
|
||||
private static string ResolvePath( string path, Mods.Mod2.Manager _, ModCollection collection )
|
||||
{
|
||||
if( !Penumbra.Config.EnableMods )
|
||||
{
|
||||
|
|
|
|||
124
Penumbra/Api/SimpleRedirectManager.cs
Normal file
124
Penumbra/Api/SimpleRedirectManager.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
public enum RedirectResult
|
||||
{
|
||||
Registered = 0,
|
||||
Success = 0,
|
||||
IdenticalFileRegistered = 1,
|
||||
InvalidGamePath = 2,
|
||||
OtherOwner = 3,
|
||||
NotRegistered = 4,
|
||||
NoPermission = 5,
|
||||
FilteredGamePath = 6,
|
||||
UnknownError = 7,
|
||||
}
|
||||
|
||||
public class SimpleRedirectManager
|
||||
{
|
||||
internal readonly Dictionary< Utf8GamePath, (FullPath File, string Tag) > Replacements = new();
|
||||
public readonly HashSet< string > AllowedTags = new();
|
||||
|
||||
public void Apply( IDictionary< Utf8GamePath, FullPath > dict )
|
||||
{
|
||||
foreach( var (gamePath, (file, _)) in Replacements )
|
||||
{
|
||||
dict.TryAdd( gamePath, file );
|
||||
}
|
||||
}
|
||||
|
||||
private RedirectResult? CheckPermission( string tag )
|
||||
=> AllowedTags.Contains( tag ) ? null : RedirectResult.NoPermission;
|
||||
|
||||
public RedirectResult IsRegistered( Utf8GamePath path, string tag )
|
||||
=> CheckPermission( tag )
|
||||
?? ( Replacements.TryGetValue( path, out var pair )
|
||||
? pair.Tag == tag ? RedirectResult.Registered : RedirectResult.OtherOwner
|
||||
: RedirectResult.NotRegistered );
|
||||
|
||||
public RedirectResult Register( Utf8GamePath path, FullPath file, string tag )
|
||||
{
|
||||
if( CheckPermission( tag ) != null )
|
||||
{
|
||||
return RedirectResult.NoPermission;
|
||||
}
|
||||
|
||||
if( Mod2.FilterFile( path ) )
|
||||
{
|
||||
return RedirectResult.FilteredGamePath;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if( Replacements.TryGetValue( path, out var pair ) )
|
||||
{
|
||||
if( file.Equals( pair.File ) )
|
||||
{
|
||||
return RedirectResult.IdenticalFileRegistered;
|
||||
}
|
||||
|
||||
if( tag != pair.Tag )
|
||||
{
|
||||
return RedirectResult.OtherOwner;
|
||||
}
|
||||
}
|
||||
|
||||
Replacements[ path ] = ( file, tag );
|
||||
return RedirectResult.Success;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"[{tag}] Unknown Error registering simple redirect {path} -> {file}:\n{e}" );
|
||||
return RedirectResult.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
public RedirectResult Unregister( Utf8GamePath path, string tag )
|
||||
{
|
||||
if( CheckPermission( tag ) != null )
|
||||
{
|
||||
return RedirectResult.NoPermission;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if( !Replacements.TryGetValue( path, out var pair ) )
|
||||
{
|
||||
return RedirectResult.NotRegistered;
|
||||
}
|
||||
|
||||
if( tag != pair.Tag )
|
||||
{
|
||||
return RedirectResult.OtherOwner;
|
||||
}
|
||||
|
||||
Replacements.Remove( path );
|
||||
return RedirectResult.Success;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"[{tag}] Unknown Error unregistering simple redirect {path}:\n{e}" );
|
||||
return RedirectResult.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
public RedirectResult Register( string path, string file, string tag )
|
||||
=> Utf8GamePath.FromString( path, out var gamePath, true )
|
||||
? Register( gamePath, new FullPath( file ), tag )
|
||||
: RedirectResult.InvalidGamePath;
|
||||
|
||||
public RedirectResult Unregister( string path, string tag )
|
||||
=> Utf8GamePath.FromString( path, out var gamePath, true )
|
||||
? Unregister( gamePath, tag )
|
||||
: RedirectResult.InvalidGamePath;
|
||||
|
||||
public RedirectResult IsRegistered( string path, string tag )
|
||||
=> Utf8GamePath.FromString( path, out var gamePath, true )
|
||||
? IsRegistered( gamePath, tag )
|
||||
: RedirectResult.InvalidGamePath;
|
||||
}
|
||||
|
|
@ -275,7 +275,7 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
private void OnModRemovedActive( bool meta, IEnumerable< ModSettings? > settings )
|
||||
private void OnModRemovedActive( bool meta, IEnumerable< ModSettings2? > settings )
|
||||
{
|
||||
foreach( var (collection, _) in this.Zip( settings ).Where( c => c.First.HasCache && c.Second?.Enabled == true ) )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public partial class ModCollection
|
|||
public delegate void CollectionChangeDelegate( Type type, ModCollection? oldCollection, ModCollection? newCollection,
|
||||
string? characterName = null );
|
||||
|
||||
private readonly Mod.Manager _modManager;
|
||||
private readonly Mod2.Manager _modManager;
|
||||
|
||||
// The empty collection is always available and always has index 0.
|
||||
// It can not be deleted or moved.
|
||||
|
|
@ -56,14 +56,15 @@ public partial class ModCollection
|
|||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public Manager( Mod.Manager manager )
|
||||
public Manager( Mod2.Manager manager )
|
||||
{
|
||||
_modManager = manager;
|
||||
|
||||
// The collection manager reacts to changes in mods by itself.
|
||||
_modManager.ModDiscoveryStarted += OnModDiscoveryStarted;
|
||||
_modManager.ModDiscoveryFinished += OnModDiscoveryFinished;
|
||||
_modManager.ModChange += OnModChanged;
|
||||
_modManager.ModOptionChanged += OnModOptionsChanged;
|
||||
_modManager.ModPathChanged += OnModPathChanged;
|
||||
CollectionChanged += SaveOnChange;
|
||||
ReadCollections();
|
||||
LoadCollections();
|
||||
|
|
@ -73,7 +74,8 @@ public partial class ModCollection
|
|||
{
|
||||
_modManager.ModDiscoveryStarted -= OnModDiscoveryStarted;
|
||||
_modManager.ModDiscoveryFinished -= OnModDiscoveryFinished;
|
||||
_modManager.ModChange -= OnModChanged;
|
||||
_modManager.ModOptionChanged -= OnModOptionsChanged;
|
||||
_modManager.ModPathChanged -= OnModPathChanged;
|
||||
}
|
||||
|
||||
// Add a new collection of the given name.
|
||||
|
|
@ -171,42 +173,64 @@ public partial class ModCollection
|
|||
}
|
||||
|
||||
|
||||
// A changed mod forces changes for all collections, active and inactive.
|
||||
private void OnModChanged( Mod.ChangeType type, Mod mod )
|
||||
// A changed mod path forces changes for all collections, active and inactive.
|
||||
private void OnModPathChanged( ModPathChangeType type, Mod2 mod, DirectoryInfo? oldDirectory,
|
||||
DirectoryInfo? newDirectory )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case Mod.ChangeType.Added:
|
||||
case ModPathChangeType.Added:
|
||||
foreach( var collection in this )
|
||||
{
|
||||
collection.AddMod( mod );
|
||||
}
|
||||
|
||||
OnModAddedActive( mod.Resources.MetaManipulations.Count > 0 );
|
||||
OnModAddedActive( mod.TotalManipulations > 0 );
|
||||
break;
|
||||
case Mod.ChangeType.Removed:
|
||||
var settings = new List< ModSettings? >( _collections.Count );
|
||||
case ModPathChangeType.Deleted:
|
||||
var settings = new List< ModSettings2? >( _collections.Count );
|
||||
foreach( var collection in this )
|
||||
{
|
||||
settings.Add( collection[ mod.Index ].Settings );
|
||||
collection.RemoveMod( mod, mod.Index );
|
||||
}
|
||||
|
||||
OnModRemovedActive( mod.Resources.MetaManipulations.Count > 0, settings );
|
||||
OnModRemovedActive( mod.TotalManipulations > 0, settings );
|
||||
break;
|
||||
case Mod.ChangeType.Changed:
|
||||
foreach( var collection in this.Where(
|
||||
collection => collection.Settings[ mod.Index ]?.FixInvalidSettings( mod.Meta ) ?? false ) )
|
||||
case ModPathChangeType.Moved:
|
||||
foreach( var collection in this.Where( collection => collection.Settings[ mod.Index ] != null ) )
|
||||
{
|
||||
collection.Save();
|
||||
}
|
||||
|
||||
OnModChangedActive( mod.Resources.MetaManipulations.Count > 0, mod.Index );
|
||||
OnModChangedActive( mod.TotalManipulations > 0, mod.Index );
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnModOptionsChanged( ModOptionChangeType type, Mod2 mod, int groupIdx, int optionIdx )
|
||||
{
|
||||
if( type == ModOptionChangeType.DisplayChange )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO
|
||||
switch( type )
|
||||
{
|
||||
case ModOptionChangeType.GroupRenamed:
|
||||
case ModOptionChangeType.GroupAdded:
|
||||
case ModOptionChangeType.GroupDeleted:
|
||||
case ModOptionChangeType.PriorityChanged:
|
||||
case ModOptionChangeType.OptionAdded:
|
||||
case ModOptionChangeType.OptionDeleted:
|
||||
case ModOptionChangeType.OptionChanged:
|
||||
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.
|
||||
|
|
|
|||
168
Penumbra/Collections/ModCollection.Cache.Access.cs
Normal file
168
Penumbra/Collections/ModCollection.Cache.Access.cs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta.Manager;
|
||||
|
||||
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( _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;
|
||||
|
||||
internal IReadOnlyDictionary< Utf8GamePath, FullPath > ResolvedFiles
|
||||
=> _cache?.ResolvedFiles ?? new Dictionary< Utf8GamePath, FullPath >();
|
||||
|
||||
internal IReadOnlySet< FullPath > MissingFiles
|
||||
=> _cache?.MissingFiles ?? new HashSet< FullPath >();
|
||||
|
||||
internal IReadOnlyDictionary< string, object? > ChangedItems
|
||||
=> _cache?.ChangedItems ?? new Dictionary< string, object? >();
|
||||
|
||||
internal IReadOnlyList< ConflictCache.Conflict > Conflicts
|
||||
=> _cache?.Conflicts.Conflicts ?? Array.Empty< ConflictCache.Conflict >();
|
||||
|
||||
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 reloadDefault )
|
||||
{
|
||||
// Skip the empty collection.
|
||||
if( Index == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}] [{ReloadDefault}]", Name,
|
||||
withMetaManipulations, reloadDefault );
|
||||
_cache ??= new Cache( this );
|
||||
_cache.CalculateEffectiveFileList( withMetaManipulations );
|
||||
if( reloadDefault )
|
||||
{
|
||||
SetFiles();
|
||||
Penumbra.ResidentResources.Reload();
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,193 +1,24 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta.Manager;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
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( _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;
|
||||
|
||||
internal IReadOnlyDictionary< Utf8GamePath, FullPath > ResolvedFiles
|
||||
=> _cache?.ResolvedFiles ?? new Dictionary< Utf8GamePath, FullPath >();
|
||||
|
||||
internal IReadOnlySet< FullPath > MissingFiles
|
||||
=> _cache?.MissingFiles ?? new HashSet< FullPath >();
|
||||
|
||||
internal IReadOnlyDictionary< string, object? > ChangedItems
|
||||
=> _cache?.ChangedItems ?? new Dictionary< string, object? >();
|
||||
|
||||
internal IReadOnlyList< ConflictCache.Conflict > Conflicts
|
||||
=> _cache?.Conflicts.Conflicts ?? Array.Empty< ConflictCache.Conflict >();
|
||||
|
||||
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 reloadDefault )
|
||||
{
|
||||
// Skip the empty collection.
|
||||
if( Index == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}] [{ReloadDefault}]", Name,
|
||||
withMetaManipulations, reloadDefault );
|
||||
_cache ??= new Cache( this );
|
||||
_cache.CalculateEffectiveFileList();
|
||||
if( withMetaManipulations )
|
||||
{
|
||||
_cache.UpdateMetaManipulations();
|
||||
if( reloadDefault )
|
||||
{
|
||||
SetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
if( reloadDefault )
|
||||
{
|
||||
Penumbra.ResidentResources.Reload();
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The ModCollectionCache contains all required temporary data to use a collection.
|
||||
// The Cache contains all required temporary data to use a collection.
|
||||
// It will only be setup if a collection gets activated in any way.
|
||||
private class Cache : IDisposable
|
||||
{
|
||||
// Shared caches to avoid allocations.
|
||||
private static readonly BitArray FileSeen = new(256);
|
||||
private static readonly Dictionary< Utf8GamePath, int > RegisteredFiles = new(256);
|
||||
private static readonly List< ModSettings? > ResolvedSettings = new(128);
|
||||
private static readonly Dictionary< Utf8GamePath, FileRegister > RegisteredFiles = new(1024);
|
||||
private static readonly Dictionary< MetaManipulation, FileRegister > RegisteredManipulations = new(1024);
|
||||
private static readonly List< ModSettings2? > ResolvedSettings = new(128);
|
||||
|
||||
private readonly ModCollection _collection;
|
||||
private readonly SortedList< string, object? > _changedItems = new();
|
||||
|
|
@ -221,7 +52,24 @@ public partial class ModCollection
|
|||
_collection.InheritanceChanged -= OnInheritanceChange;
|
||||
}
|
||||
|
||||
private void OnModSettingChange( ModSettingChange type, int modIdx, int oldValue, string? optionName, bool _ )
|
||||
// Resolve a given game path according to this collection.
|
||||
public FullPath? ResolvePath( Utf8GamePath gameResourcePath )
|
||||
{
|
||||
if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if( candidate.InternalName.Length > Utf8GamePath.MaxGamePathLength
|
||||
|| candidate.IsRooted && !candidate.Exists )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private void OnModSettingChange( ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool _ )
|
||||
{
|
||||
// Recompute the file list if it was not just a non-conflicting priority change
|
||||
// or a setting change for a disabled mod.
|
||||
|
|
@ -232,7 +80,7 @@ public partial class ModCollection
|
|||
}
|
||||
|
||||
var hasMeta = type is ModSettingChange.MultiEnableState or ModSettingChange.MultiInheritance
|
||||
|| Penumbra.ModManager[ modIdx ].Resources.MetaManipulations.Count > 0;
|
||||
|| Penumbra.ModManager[ modIdx ].AllManipulations.Any();
|
||||
_collection.CalculateEffectiveFileList( hasMeta, Penumbra.CollectionManager.Default == _collection );
|
||||
}
|
||||
|
||||
|
|
@ -241,22 +89,6 @@ public partial class ModCollection
|
|||
private void OnInheritanceChange( bool _ )
|
||||
=> _collection.CalculateEffectiveFileList( true, true );
|
||||
|
||||
// Reset the shared file-seen cache.
|
||||
private static void ResetFileSeen( int size )
|
||||
{
|
||||
if( size < FileSeen.Length )
|
||||
{
|
||||
FileSeen.Length = size;
|
||||
FileSeen.SetAll( false );
|
||||
}
|
||||
else
|
||||
{
|
||||
FileSeen.SetAll( false );
|
||||
FileSeen.Length = size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Clear all local and global caches to prepare for recomputation.
|
||||
private void ClearStorageAndPrepare()
|
||||
{
|
||||
|
|
@ -270,21 +102,27 @@ public partial class ModCollection
|
|||
ResolvedSettings.AddRange( _collection.ActualSettings );
|
||||
}
|
||||
|
||||
public void CalculateEffectiveFileList()
|
||||
// Recalculate all file changes from current settings. Include all fixed custom redirects.
|
||||
// Recalculate meta manipulations only if withManipulations is true.
|
||||
public void CalculateEffectiveFileList( bool withManipulations )
|
||||
{
|
||||
ClearStorageAndPrepare();
|
||||
if( withManipulations )
|
||||
{
|
||||
RegisteredManipulations.Clear();
|
||||
MetaManipulations.Reset();
|
||||
}
|
||||
|
||||
AddCustomRedirects();
|
||||
for( var i = 0; i < Penumbra.ModManager.Count; ++i )
|
||||
{
|
||||
if( ResolvedSettings[ i ]?.Enabled == true )
|
||||
{
|
||||
AddFiles( i );
|
||||
AddSwaps( i );
|
||||
}
|
||||
AddMod( i, withManipulations );
|
||||
}
|
||||
|
||||
AddMetaFiles();
|
||||
}
|
||||
|
||||
// Identify and record all manipulated objects for this entire collection.
|
||||
private void SetChangedItems()
|
||||
{
|
||||
if( _changedItems.Count > 0 || ResolvedFiles.Count + MetaManipulations.Count == 0 )
|
||||
|
|
@ -309,228 +147,185 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
private void AddFiles( int idx )
|
||||
// Add a specific file redirection, handling potential conflicts.
|
||||
// For different mods, higher mod priority takes precedence before option group priority,
|
||||
// which takes precedence before option priority, which takes precedence before ordering.
|
||||
// Inside the same mod, conflicts are not recorded.
|
||||
private void AddFile( Utf8GamePath path, FullPath file, FileRegister priority )
|
||||
{
|
||||
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() )
|
||||
if( RegisteredFiles.TryGetValue( path, out var register ) )
|
||||
{
|
||||
switch( group.SelectionType )
|
||||
if( register.SameMod( priority, out var less ) )
|
||||
{
|
||||
case SelectType.Single:
|
||||
AddFilesForSingle( group, mod, idx );
|
||||
break;
|
||||
case SelectType.Multi:
|
||||
AddFilesForMulti( group, mod, idx );
|
||||
break;
|
||||
default: throw new InvalidEnumArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
AddRemainingFiles( mod, idx );
|
||||
}
|
||||
|
||||
// If audio streaming is not disabled, replacing .scd files crashes the game,
|
||||
// so only add those files if it is disabled.
|
||||
private static bool FilterFile( Utf8GamePath gamePath )
|
||||
=> !Penumbra.Config.DisableSoundStreaming
|
||||
&& gamePath.Path.EndsWith( '.', 's', 'c', 'd' );
|
||||
|
||||
private void AddFile( int modIdx, Utf8GamePath gamePath, FullPath file )
|
||||
{
|
||||
if( FilterFile( gamePath ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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 );
|
||||
if( priority > oldPriority )
|
||||
{
|
||||
ResolvedFiles[ gamePath ] = file;
|
||||
RegisteredFiles[ gamePath ] = modIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddMissingFile( FullPath file )
|
||||
{
|
||||
switch( file.Extension.ToLowerInvariant() )
|
||||
{
|
||||
// We do not care for those file types
|
||||
case ".scp" when !Penumbra.Config.DisableSoundStreaming:
|
||||
case ".meta":
|
||||
case ".rgsp":
|
||||
return;
|
||||
default:
|
||||
MissingFiles.Add( file );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
AddMissingFile( fullPath );
|
||||
continue;
|
||||
}
|
||||
|
||||
var registeredFile = mod.Resources.ModFiles[ idx ];
|
||||
if( !registeredFile.Exists )
|
||||
{
|
||||
AddMissingFile( registeredFile );
|
||||
continue;
|
||||
}
|
||||
|
||||
FileSeen.Set( idx, true );
|
||||
if( enabled )
|
||||
{
|
||||
foreach( var path in paths )
|
||||
Conflicts.AddConflict( register.ModIdx, priority.ModIdx, register.ModPriority, priority.ModPriority, path );
|
||||
if( less )
|
||||
{
|
||||
AddFile( modIdx, path, registeredFile );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddFilesForSingle( OptionGroup singleGroup, Mod mod, int modIdx )
|
||||
{
|
||||
Debug.Assert( singleGroup.SelectionType == SelectType.Single );
|
||||
var settings = ResolvedSettings[ modIdx ]!;
|
||||
if( !settings.Settings.TryGetValue( singleGroup.GroupName, out var setting ) )
|
||||
{
|
||||
setting = 0;
|
||||
}
|
||||
|
||||
for( var i = 0; i < singleGroup.Options.Count; ++i )
|
||||
{
|
||||
AddPathsForOption( singleGroup.Options[ i ], mod, modIdx, setting == i );
|
||||
}
|
||||
}
|
||||
|
||||
private void AddFilesForMulti( OptionGroup multiGroup, Mod mod, int modIdx )
|
||||
{
|
||||
Debug.Assert( multiGroup.SelectionType == SelectType.Multi );
|
||||
var settings = ResolvedSettings[ modIdx ]!;
|
||||
if( !settings.Settings.TryGetValue( multiGroup.GroupName, out var setting ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Also iterate options in reverse so that later options take precedence before earlier ones.
|
||||
for( var i = multiGroup.Options.Count - 1; i >= 0; --i )
|
||||
{
|
||||
AddPathsForOption( multiGroup.Options[ i ], mod, modIdx, ( setting & ( 1 << i ) ) != 0 );
|
||||
}
|
||||
}
|
||||
|
||||
private void AddRemainingFiles( Mod mod, int modIdx )
|
||||
{
|
||||
for( var i = 0; i < mod.Resources.ModFiles.Count; ++i )
|
||||
{
|
||||
if( FileSeen.Get( i ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var file = mod.Resources.ModFiles[ i ];
|
||||
if( file.Exists )
|
||||
{
|
||||
if( file.ToGamePath( mod.BasePath, out var gamePath ) )
|
||||
{
|
||||
AddFile( modIdx, gamePath, file );
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Warning( $"Could not convert {file} in {mod.BasePath.FullName} to GamePath." );
|
||||
RegisteredFiles[ path ] = priority;
|
||||
ResolvedFiles[ path ] = file;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MissingFiles.Add( file );
|
||||
// File seen before in the same mod:
|
||||
// use higher priority or earlier recurrences in case of same priority.
|
||||
// Do not add conflicts.
|
||||
if( less )
|
||||
{
|
||||
RegisteredFiles[ path ] = priority;
|
||||
ResolvedFiles[ path ] = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // File not seen before, just add it.
|
||||
{
|
||||
RegisteredFiles.Add( path, priority );
|
||||
ResolvedFiles.Add( path, file );
|
||||
}
|
||||
}
|
||||
|
||||
// Add a specific manipulation, handling potential conflicts.
|
||||
// For different mods, higher mod priority takes precedence before option group priority,
|
||||
// which takes precedence before option priority, which takes precedence before ordering.
|
||||
// Inside the same mod, conflicts are not recorded.
|
||||
private void AddManipulation( MetaManipulation manip, FileRegister priority )
|
||||
{
|
||||
if( RegisteredManipulations.TryGetValue( manip, out var register ) )
|
||||
{
|
||||
if( register.SameMod( priority, out var less ) )
|
||||
{
|
||||
Conflicts.AddConflict( register.ModIdx, priority.ModIdx, register.ModPriority, priority.ModPriority, manip );
|
||||
if( less )
|
||||
{
|
||||
RegisteredManipulations[ manip ] = priority;
|
||||
MetaManipulations.ApplyMod( manip, priority.ModIdx );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Manipulation seen before in the same mod:
|
||||
// use higher priority or earlier occurrences in case of same priority.
|
||||
// Do not add conflicts.
|
||||
if( less )
|
||||
{
|
||||
RegisteredManipulations[ manip ] = priority;
|
||||
MetaManipulations.ApplyMod( manip, priority.ModIdx );
|
||||
}
|
||||
}
|
||||
}
|
||||
else // Manipulation not seen before, just add it.
|
||||
{
|
||||
RegisteredManipulations[ manip ] = priority;
|
||||
MetaManipulations.ApplyMod( manip, priority.ModIdx );
|
||||
}
|
||||
}
|
||||
|
||||
// Add all files and possibly manipulations of a specific submod with the given priorities.
|
||||
private void AddSubMod( ISubMod mod, FileRegister priority, bool withManipulations )
|
||||
{
|
||||
foreach( var (path, file) in mod.Files.Concat( mod.FileSwaps ) )
|
||||
{
|
||||
// Skip all filtered files
|
||||
if( Mod2.FilterFile( path ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AddFile( path, file, priority );
|
||||
}
|
||||
|
||||
if( withManipulations )
|
||||
{
|
||||
foreach( var manip in mod.Manipulations )
|
||||
{
|
||||
AddManipulation( manip, priority );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all files and possibly manipulations of a given mod according to its settings in this collection.
|
||||
private void AddMod( int modIdx, bool withManipulations )
|
||||
{
|
||||
var settings = ResolvedSettings[ modIdx ];
|
||||
if( settings is not { Enabled: true } )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mod = Penumbra.ModManager.Mods[ modIdx ];
|
||||
AddSubMod( mod.Default, new FileRegister( modIdx, settings.Priority, 0, 0 ), withManipulations );
|
||||
for( var idx = 0; idx < mod.Groups.Count; ++idx )
|
||||
{
|
||||
var config = settings.Settings[ idx ];
|
||||
var group = mod.Groups[ idx ];
|
||||
switch( group.Type )
|
||||
{
|
||||
case SelectType.Single:
|
||||
var singlePriority = new FileRegister( modIdx, settings.Priority, group.Priority, group.Priority );
|
||||
AddSubMod( group[ ( int )config ], singlePriority, withManipulations );
|
||||
break;
|
||||
case SelectType.Multi:
|
||||
{
|
||||
for( var optionIdx = 0; optionIdx < group.Count; ++optionIdx )
|
||||
{
|
||||
if( ( ( 1 << optionIdx ) & config ) != 0 )
|
||||
{
|
||||
var priority = new FileRegister( modIdx, settings.Priority, group.Priority, group.OptionPriority( optionIdx ) );
|
||||
AddSubMod( group[ optionIdx ], priority, withManipulations );
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all necessary meta file redirects.
|
||||
private void AddMetaFiles()
|
||||
=> MetaManipulations.Imc.SetFiles();
|
||||
|
||||
private void AddSwaps( int modIdx )
|
||||
// Add all API redirects.
|
||||
private void AddCustomRedirects()
|
||||
{
|
||||
var mod = Penumbra.ModManager.Mods[ modIdx ];
|
||||
foreach( var (gamePath, swapPath) in mod.Meta.FileSwaps.Where( kvp => !FilterFile( kvp.Key ) ) )
|
||||
Penumbra.Redirects.Apply( ResolvedFiles );
|
||||
foreach( var gamePath in ResolvedFiles.Keys )
|
||||
{
|
||||
AddFile( modIdx, gamePath, swapPath );
|
||||
RegisteredFiles.Add( gamePath, new FileRegister( -1, int.MaxValue, 0, 0 ) );
|
||||
}
|
||||
}
|
||||
|
||||
private void AddManipulations( int modIdx )
|
||||
|
||||
// Struct to keep track of all priorities involved in a mod and register and compare accordingly.
|
||||
private readonly record struct FileRegister( int ModIdx, int ModPriority, int GroupPriority, int OptionPriority )
|
||||
{
|
||||
var mod = Penumbra.ModManager.Mods[ modIdx ];
|
||||
foreach( var manip in mod.Resources.MetaManipulations.GetManipulationsForConfig( ResolvedSettings[ modIdx ]!, mod.Meta ) )
|
||||
public readonly int ModIdx = ModIdx;
|
||||
public readonly int ModPriority = ModPriority;
|
||||
public readonly int GroupPriority = GroupPriority;
|
||||
public readonly int OptionPriority = OptionPriority;
|
||||
|
||||
public bool SameMod( FileRegister other, out bool less )
|
||||
{
|
||||
if( !MetaManipulations.TryGetValue( manip, out var oldModIdx ) )
|
||||
if( ModIdx != other.ModIdx )
|
||||
{
|
||||
MetaManipulations.ApplyMod( manip, modIdx );
|
||||
less = ModPriority < other.ModPriority;
|
||||
return true;
|
||||
}
|
||||
|
||||
if( GroupPriority < other.GroupPriority )
|
||||
{
|
||||
less = true;
|
||||
}
|
||||
else if( GroupPriority == other.GroupPriority )
|
||||
{
|
||||
less = OptionPriority < other.OptionPriority;
|
||||
}
|
||||
else
|
||||
{
|
||||
var priority = ResolvedSettings[ modIdx ]!.Priority;
|
||||
var oldPriority = ResolvedSettings[ oldModIdx ]!.Priority;
|
||||
Conflicts.AddConflict( oldModIdx, modIdx, oldPriority, priority, manip );
|
||||
if( priority > oldPriority )
|
||||
{
|
||||
MetaManipulations.ApplyMod( manip, modIdx );
|
||||
}
|
||||
less = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateMetaManipulations()
|
||||
{
|
||||
MetaManipulations.Reset();
|
||||
Conflicts.ClearMetaConflicts();
|
||||
|
||||
foreach( var mod in Penumbra.ModManager.Mods.Zip( ResolvedSettings )
|
||||
.Select( ( m, i ) => ( m.First, m.Second, i ) )
|
||||
.Where( m => m.Second?.Enabled == true && m.First.Resources.MetaManipulations.Count > 0 ) )
|
||||
{
|
||||
AddManipulations( mod.i );
|
||||
}
|
||||
}
|
||||
|
||||
public FullPath? ResolvePath( Utf8GamePath gameResourcePath )
|
||||
{
|
||||
if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if( candidate.InternalName.Length > Utf8GamePath.MaxGamePathLength
|
||||
|| candidate.IsRooted && !candidate.Exists )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ 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 delegate void ModSettingChangeDelegate( ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited );
|
||||
public event ModSettingChangeDelegate ModSettingChanged;
|
||||
|
||||
// Enable or disable the mod inheritance of mod idx.
|
||||
|
|
@ -28,7 +28,7 @@ public partial class ModCollection
|
|||
{
|
||||
if( FixInheritance( idx, inherit ) )
|
||||
{
|
||||
ModSettingChanged.Invoke( ModSettingChange.Inheritance, idx, inherit ? 0 : 1, null, false );
|
||||
ModSettingChanged.Invoke( ModSettingChange.Inheritance, idx, inherit ? 0 : 1, 0, false );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -41,22 +41,22 @@ public partial class ModCollection
|
|||
{
|
||||
var inheritance = FixInheritance( idx, false );
|
||||
_settings[ idx ]!.Enabled = newValue;
|
||||
ModSettingChanged.Invoke( ModSettingChange.EnableState, idx, inheritance ? -1 : newValue ? 0 : 1, null, false );
|
||||
ModSettingChanged.Invoke( ModSettingChange.EnableState, idx, inheritance ? -1 : newValue ? 0 : 1, 0, false );
|
||||
}
|
||||
}
|
||||
|
||||
// Enable or disable the mod inheritance of every mod in mods.
|
||||
public void SetMultipleModInheritances( IEnumerable< Mod > mods, bool inherit )
|
||||
public void SetMultipleModInheritances( IEnumerable< Mod2 > mods, bool inherit )
|
||||
{
|
||||
if( mods.Aggregate( false, ( current, mod ) => current | FixInheritance( mod.Index, inherit ) ) )
|
||||
{
|
||||
ModSettingChanged.Invoke( ModSettingChange.MultiInheritance, -1, -1, null, false );
|
||||
ModSettingChanged.Invoke( ModSettingChange.MultiInheritance, -1, -1, 0, false );
|
||||
}
|
||||
}
|
||||
|
||||
// Set the enabled state of every mod in mods to the new value.
|
||||
// If the mod is currently inherited, stop the inheritance.
|
||||
public void SetMultipleModStates( IEnumerable< Mod > mods, bool newValue )
|
||||
public void SetMultipleModStates( IEnumerable< Mod2 > mods, bool newValue )
|
||||
{
|
||||
var changes = false;
|
||||
foreach( var mod in mods )
|
||||
|
|
@ -72,7 +72,7 @@ public partial class ModCollection
|
|||
|
||||
if( changes )
|
||||
{
|
||||
ModSettingChanged.Invoke( ModSettingChange.MultiEnableState, -1, -1, null, false );
|
||||
ModSettingChanged.Invoke( ModSettingChange.MultiEnableState, -1, -1, 0, false );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,26 +85,21 @@ public partial class ModCollection
|
|||
{
|
||||
var inheritance = FixInheritance( idx, false );
|
||||
_settings[ idx ]!.Priority = newValue;
|
||||
ModSettingChanged.Invoke( ModSettingChange.Priority, idx, inheritance ? -1 : oldValue, null, false );
|
||||
ModSettingChanged.Invoke( ModSettingChange.Priority, idx, inheritance ? -1 : oldValue, 0, false );
|
||||
}
|
||||
}
|
||||
|
||||
// Set a given setting group settingName of mod idx to newValue if it differs from the current value and fix it if necessary.
|
||||
// If mod idx is currently inherited, stop the inheritance.
|
||||
public void SetModSetting( int idx, string settingName, int newValue )
|
||||
public void SetModSetting( int idx, int groupIdx, uint newValue )
|
||||
{
|
||||
var settings = _settings[ idx ] != null ? _settings[ idx ]!.Settings : this[ idx ].Settings?.Settings;
|
||||
var oldValue = settings != null
|
||||
? settings.TryGetValue( settingName, out var v ) ? v : newValue
|
||||
: Penumbra.ModManager.Mods[ idx ].Meta.Groups.ContainsKey( settingName )
|
||||
? 0
|
||||
: newValue;
|
||||
var oldValue = settings?[ groupIdx ] ?? 0;
|
||||
if( oldValue != newValue )
|
||||
{
|
||||
var inheritance = FixInheritance( idx, false );
|
||||
_settings[ idx ]!.Settings[ settingName ] = newValue;
|
||||
_settings[ idx ]!.FixSpecificSetting( settingName, Penumbra.ModManager.Mods[ idx ].Meta );
|
||||
ModSettingChanged.Invoke( ModSettingChange.Setting, idx, inheritance ? -1 : oldValue, settingName, false );
|
||||
_settings[ idx ]!.SetValue( Penumbra.ModManager.Mods[ idx ], groupIdx, newValue );
|
||||
ModSettingChanged.Invoke( ModSettingChange.Setting, idx, inheritance ? -1 : ( int )oldValue, groupIdx, false );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +107,7 @@ public partial class ModCollection
|
|||
// If type == Setting, settingName should be a valid setting for that mod, otherwise it will be ignored.
|
||||
// The setting will also be automatically fixed if it is invalid for that setting group.
|
||||
// For boolean parameters, newValue == 0 will be treated as false and != 0 as true.
|
||||
public void ChangeModSetting( ModSettingChange type, int idx, int newValue, string? settingName = null )
|
||||
public void ChangeModSetting( ModSettingChange type, int idx, int newValue, int groupIdx )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
|
|
@ -126,7 +121,7 @@ public partial class ModCollection
|
|||
SetModPriority( idx, newValue );
|
||||
break;
|
||||
case ModSettingChange.Setting:
|
||||
SetModSetting( idx, settingName ?? string.Empty, newValue );
|
||||
SetModSetting( idx, groupIdx, ( uint )newValue );
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
|
||||
}
|
||||
|
|
@ -142,11 +137,11 @@ public partial class ModCollection
|
|||
return false;
|
||||
}
|
||||
|
||||
_settings[ idx ] = inherit ? null : this[ idx ].Settings ?? ModSettings.DefaultSettings( Penumbra.ModManager.Mods[ idx ].Meta );
|
||||
_settings[ idx ] = inherit ? null : this[ idx ].Settings ?? ModSettings2.DefaultSettings( Penumbra.ModManager.Mods[ idx ] );
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SaveOnChange( ModSettingChange _1, int _2, int _3, string? _4, bool inherited )
|
||||
private void SaveOnChange( ModSettingChange _1, int _2, int _3, int _4, bool inherited )
|
||||
=> SaveOnChange( inherited );
|
||||
|
||||
private void SaveOnChange( bool inherited )
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public partial class ModCollection
|
|||
if( settings != null )
|
||||
{
|
||||
j.WritePropertyName( Penumbra.ModManager[ i ].BasePath.Name );
|
||||
x.Serialize( j, settings );
|
||||
x.Serialize( j, new ModSettings2.SavedSettings( settings, Penumbra.ModManager[ i ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,8 +111,8 @@ public partial class ModCollection
|
|||
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 >();
|
||||
var settings = obj[ nameof( Settings ) ]?.ToObject< Dictionary< string, ModSettings2.SavedSettings > >()
|
||||
?? new Dictionary< string, ModSettings2.SavedSettings >();
|
||||
inheritance = obj[ nameof( Inheritance ) ]?.ToObject< List< string > >() ?? ( IReadOnlyList< string > )Array.Empty< string >();
|
||||
|
||||
return new ModCollection( name, version, settings );
|
||||
|
|
|
|||
|
|
@ -71,11 +71,11 @@ 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 _ )
|
||||
private void OnInheritedModSettingChange( ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool _ )
|
||||
{
|
||||
if( _settings[ modIdx ] == null )
|
||||
{
|
||||
ModSettingChanged.Invoke( type, modIdx, oldValue, optionName, true );
|
||||
ModSettingChanged.Invoke( type, modIdx, oldValue, groupIdx, true );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ public partial class ModCollection
|
|||
// 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 ]
|
||||
public (ModSettings2? Settings, ModCollection Collection) this[ Index idx ]
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
|
|||
|
|
@ -45,10 +45,13 @@ public sealed partial class ModCollection
|
|||
}
|
||||
|
||||
// We treat every completely defaulted setting as inheritance-ready.
|
||||
private static bool SettingIsDefaultV0( ModSettings? setting )
|
||||
private static bool SettingIsDefaultV0( ModSettings2.SavedSettings setting )
|
||||
=> setting is { Enabled: false, Priority: 0 } && setting.Settings.Values.All( s => s == 0 );
|
||||
|
||||
private static bool SettingIsDefaultV0( ModSettings2? setting )
|
||||
=> setting is { Enabled: false, Priority: 0 } && setting.Settings.All( s => s == 0 );
|
||||
}
|
||||
|
||||
internal static ModCollection MigrateFromV0( string name, Dictionary< string, ModSettings > allSettings )
|
||||
internal static ModCollection MigrateFromV0( string name, Dictionary< string, ModSettings2.SavedSettings > allSettings )
|
||||
=> new(name, 0, allSettings);
|
||||
}
|
||||
|
|
@ -27,17 +27,17 @@ public partial class ModCollection
|
|||
|
||||
// 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;
|
||||
private readonly List< ModSettings2? > _settings;
|
||||
|
||||
public IReadOnlyList< ModSettings? > Settings
|
||||
public IReadOnlyList< ModSettings2? > Settings
|
||||
=> _settings;
|
||||
|
||||
// Evaluates the settings along the whole inheritance tree.
|
||||
public IEnumerable< ModSettings? > ActualSettings
|
||||
public IEnumerable< ModSettings2? > 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;
|
||||
private readonly Dictionary< string, ModSettings2.SavedSettings > _unusedSettings;
|
||||
|
||||
// Constructor for duplication.
|
||||
private ModCollection( string name, ModCollection duplicate )
|
||||
|
|
@ -52,13 +52,13 @@ public partial class ModCollection
|
|||
}
|
||||
|
||||
// Constructor for reading from files.
|
||||
private ModCollection( string name, int version, Dictionary< string, ModSettings > allSettings )
|
||||
private ModCollection( string name, int version, Dictionary< string, ModSettings2.SavedSettings > allSettings )
|
||||
{
|
||||
Name = name;
|
||||
Version = version;
|
||||
_unusedSettings = allSettings;
|
||||
|
||||
_settings = new List< ModSettings? >();
|
||||
_settings = new List< ModSettings2? >();
|
||||
ApplyModSettings();
|
||||
|
||||
Migration.Migrate( this );
|
||||
|
|
@ -68,7 +68,7 @@ public partial class ModCollection
|
|||
|
||||
// Create a new, unique empty collection of a given name.
|
||||
public static ModCollection CreateNewEmpty( string name )
|
||||
=> new(name, CurrentVersion, new Dictionary< string, ModSettings >());
|
||||
=> new(name, CurrentVersion, new Dictionary< string, ModSettings2.SavedSettings >());
|
||||
|
||||
// Duplicate the calling collection to a new, unique collection of a given name.
|
||||
public ModCollection Duplicate( string name )
|
||||
|
|
@ -86,26 +86,27 @@ public partial class ModCollection
|
|||
}
|
||||
|
||||
// Add settings for a new appended mod, by checking if the mod had settings from a previous deletion.
|
||||
private void AddMod( Mod mod )
|
||||
private bool AddMod( Mod2 mod )
|
||||
{
|
||||
if( _unusedSettings.TryGetValue( mod.BasePath.Name, out var settings ) )
|
||||
if( _unusedSettings.TryGetValue( mod.BasePath.Name, out var save ) )
|
||||
{
|
||||
var ret = save.ToSettings( mod, out var settings );
|
||||
_settings.Add( settings );
|
||||
_unusedSettings.Remove( mod.BasePath.Name );
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
_settings.Add( null );
|
||||
}
|
||||
|
||||
_settings.Add( null );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move settings from the current mod list to the unused mod settings.
|
||||
private void RemoveMod( Mod mod, int idx )
|
||||
private void RemoveMod( Mod2 mod, int idx )
|
||||
{
|
||||
var settings = _settings[ idx ];
|
||||
if( settings != null )
|
||||
{
|
||||
_unusedSettings.Add( mod.BasePath.Name, settings );
|
||||
_unusedSettings.Add( mod.BasePath.Name, new ModSettings2.SavedSettings( settings, mod ) );
|
||||
}
|
||||
|
||||
_settings.RemoveAt( idx );
|
||||
|
|
@ -126,7 +127,7 @@ public partial class ModCollection
|
|||
{
|
||||
foreach( var (mod, setting) in Penumbra.ModManager.Zip( _settings ).Where( s => s.Second != null ) )
|
||||
{
|
||||
_unusedSettings[ mod.BasePath.Name ] = setting!;
|
||||
_unusedSettings[ mod.BasePath.Name ] = new ModSettings2.SavedSettings( setting!, mod );
|
||||
}
|
||||
|
||||
_settings.Clear();
|
||||
|
|
@ -137,22 +138,7 @@ public partial class ModCollection
|
|||
private void ApplyModSettings()
|
||||
{
|
||||
_settings.Capacity = Math.Max( _settings.Capacity, Penumbra.ModManager.Count );
|
||||
var changes = false;
|
||||
foreach( var mod in Penumbra.ModManager )
|
||||
{
|
||||
if( _unusedSettings.TryGetValue( mod.BasePath.Name, out var s ) )
|
||||
{
|
||||
changes |= s.FixInvalidSettings( mod.Meta );
|
||||
_settings.Add( s );
|
||||
_unusedSettings.Remove( mod.BasePath.Name );
|
||||
}
|
||||
else
|
||||
{
|
||||
_settings.Add( null );
|
||||
}
|
||||
}
|
||||
|
||||
if( changes )
|
||||
if( Penumbra.ModManager.Aggregate( false, ( current, mod ) => current | AddMod( mod ) ) )
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Text;
|
|||
using Dalamud.Logging;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Importer.Models;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
|
@ -148,23 +147,13 @@ internal class TexToolsImport
|
|||
|
||||
var modList = modListRaw.Select( JsonConvert.DeserializeObject< SimpleMod > );
|
||||
|
||||
// Create a new ModMeta from the TTMP modlist info
|
||||
var modMeta = new ModMeta
|
||||
{
|
||||
Author = "Unknown",
|
||||
Name = modPackFile.Name,
|
||||
Description = "Mod imported from TexTools mod pack",
|
||||
};
|
||||
|
||||
// Open the mod data file from the modpack as a SqPackStream
|
||||
using var modData = GetMagicSqPackDeleterStream( extractedModPack, "TTMPD.mpd" );
|
||||
|
||||
ExtractedDirectory = CreateModFolder( _outDirectory, Path.GetFileNameWithoutExtension( modPackFile.Name ) );
|
||||
|
||||
File.WriteAllText(
|
||||
Path.Combine( ExtractedDirectory.FullName, "meta.json" ),
|
||||
JsonConvert.SerializeObject( modMeta )
|
||||
);
|
||||
// Create a new ModMeta from the TTMP modlist info
|
||||
Mod2.CreateMeta( ExtractedDirectory, string.IsNullOrEmpty( modPackFile.Name ) ? "New Mod" : modPackFile.Name, "Unknown",
|
||||
"Mod imported from TexTools mod pack.", null, null );
|
||||
|
||||
ExtractSimpleModList( ExtractedDirectory, modList, modData );
|
||||
|
||||
|
|
@ -173,7 +162,7 @@ internal class TexToolsImport
|
|||
|
||||
private DirectoryInfo ImportV2ModPack( FileInfo _, ZipFile extractedModPack, string modRaw )
|
||||
{
|
||||
var modList = JsonConvert.DeserializeObject<SimpleModPack>( modRaw );
|
||||
var modList = JsonConvert.DeserializeObject< SimpleModPack >( modRaw );
|
||||
|
||||
if( modList.TTMPVersion?.EndsWith( "s" ) ?? false )
|
||||
{
|
||||
|
|
@ -233,23 +222,13 @@ internal class TexToolsImport
|
|||
{
|
||||
PluginLog.Log( " -> Importing Simple V2 ModPack" );
|
||||
|
||||
// Create a new ModMeta from the TTMP modlist info
|
||||
var modMeta = new ModMeta
|
||||
{
|
||||
Author = modList.Author ?? "Unknown",
|
||||
Name = modList.Name ?? "New Mod",
|
||||
Description = string.IsNullOrEmpty( modList.Description )
|
||||
? "Mod imported from TexTools mod pack"
|
||||
: modList.Description!,
|
||||
};
|
||||
|
||||
// Open the mod data file from the modpack as a SqPackStream
|
||||
using var modData = GetMagicSqPackDeleterStream( extractedModPack, "TTMPD.mpd" );
|
||||
|
||||
ExtractedDirectory = CreateModFolder( _outDirectory, modList.Name ?? "New Mod" );
|
||||
|
||||
File.WriteAllText( Path.Combine( ExtractedDirectory.FullName, "meta.json" ),
|
||||
JsonConvert.SerializeObject( modMeta ) );
|
||||
Mod2.CreateMeta( ExtractedDirectory, modList.Name ?? "New Mod", modList.Author ?? "Unknown", string.IsNullOrEmpty( modList.Description )
|
||||
? "Mod imported from TexTools mod pack"
|
||||
: modList.Description, null, null );
|
||||
|
||||
ExtractSimpleModList( ExtractedDirectory, modList.SimpleModsList ?? Enumerable.Empty< SimpleMod >(), modData );
|
||||
return ExtractedDirectory;
|
||||
|
|
@ -261,21 +240,12 @@ internal class TexToolsImport
|
|||
|
||||
var modList = JsonConvert.DeserializeObject< ExtendedModPack >( modRaw );
|
||||
|
||||
// Create a new ModMeta from the TTMP modlist info
|
||||
var modMeta = new ModMeta
|
||||
{
|
||||
Author = modList.Author ?? "Unknown",
|
||||
Name = modList.Name ?? "New Mod",
|
||||
Description = string.IsNullOrEmpty( modList.Description )
|
||||
? "Mod imported from TexTools mod pack"
|
||||
: modList.Description ?? "",
|
||||
Version = modList.Version ?? "",
|
||||
};
|
||||
|
||||
// Open the mod data file from the modpack as a SqPackStream
|
||||
using var modData = GetMagicSqPackDeleterStream( extractedModPack, "TTMPD.mpd" );
|
||||
|
||||
ExtractedDirectory = CreateModFolder( _outDirectory, modList.Name ?? "New Mod" );
|
||||
Mod2.CreateMeta( ExtractedDirectory, modList.Name ?? "New Mod", modList.Author ?? "Unknown",
|
||||
string.IsNullOrEmpty( modList.Description ) ? "Mod imported from TexTools mod pack" : modList.Description, modList.Version, null );
|
||||
|
||||
if( modList.SimpleModsList != null )
|
||||
{
|
||||
|
|
@ -288,6 +258,8 @@ internal class TexToolsImport
|
|||
}
|
||||
|
||||
// Iterate through all pages
|
||||
var options = new List< ISubMod >();
|
||||
var groupPriority = 0;
|
||||
foreach( var page in modList.ModPackPages )
|
||||
{
|
||||
if( page.ModGroups == null )
|
||||
|
|
@ -297,6 +269,8 @@ internal class TexToolsImport
|
|||
|
||||
foreach( var group in page.ModGroups.Where( group => group.GroupName != null && group.OptionList != null ) )
|
||||
{
|
||||
options.Clear();
|
||||
var description = new StringBuilder();
|
||||
var groupFolder = NewOptionDirectory( ExtractedDirectory, group.GroupName! );
|
||||
if( groupFolder.Exists )
|
||||
{
|
||||
|
|
@ -308,52 +282,19 @@ internal class TexToolsImport
|
|||
{
|
||||
var optionFolder = NewOptionDirectory( groupFolder, option.Name! );
|
||||
ExtractSimpleModList( optionFolder, option.ModsJsons!, modData );
|
||||
}
|
||||
|
||||
AddMeta( ExtractedDirectory, groupFolder, group, modMeta );
|
||||
}
|
||||
}
|
||||
|
||||
File.WriteAllText(
|
||||
Path.Combine( ExtractedDirectory.FullName, "meta.json" ),
|
||||
JsonConvert.SerializeObject( modMeta, Formatting.Indented )
|
||||
);
|
||||
return ExtractedDirectory;
|
||||
}
|
||||
|
||||
private static void AddMeta( DirectoryInfo baseFolder, DirectoryInfo groupFolder, ModGroup group, ModMeta meta )
|
||||
{
|
||||
var inf = new OptionGroup
|
||||
{
|
||||
SelectionType = group.SelectionType,
|
||||
GroupName = group.GroupName!,
|
||||
Options = new List< Option >(),
|
||||
};
|
||||
foreach( var opt in group.OptionList! )
|
||||
{
|
||||
var option = new Option
|
||||
{
|
||||
OptionName = opt.Name!,
|
||||
OptionDesc = string.IsNullOrEmpty( opt.Description ) ? "" : opt.Description!,
|
||||
OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(),
|
||||
};
|
||||
var optDir = NewOptionDirectory( groupFolder, opt.Name! );
|
||||
if( optDir.Exists )
|
||||
{
|
||||
foreach( var file in optDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
||||
{
|
||||
if( Utf8RelPath.FromFile( file, baseFolder, out var rel )
|
||||
&& Utf8GamePath.FromFile( file, optDir, out var game, true ) )
|
||||
options.Add( Mod2.CreateSubMod( ExtractedDirectory, optionFolder, option ) );
|
||||
description.Append( option.Description );
|
||||
if( !string.IsNullOrEmpty( option.Description ) )
|
||||
{
|
||||
option.AddFile( rel, game );
|
||||
description.Append( '\n' );
|
||||
}
|
||||
}
|
||||
|
||||
Mod2.CreateOptionGroup( ExtractedDirectory, group, groupPriority++, description.ToString(), options );
|
||||
}
|
||||
|
||||
inf.Options.Add( option );
|
||||
}
|
||||
|
||||
meta.Groups.Add( inf.GroupName, inf );
|
||||
Mod2.CreateDefaultFiles( ExtractedDirectory );
|
||||
return ExtractedDirectory;
|
||||
}
|
||||
|
||||
private void ImportMetaModPack( FileInfo file )
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ public unsafe partial class ResourceLoader
|
|||
// Use the default method of path replacement.
|
||||
public static (FullPath?, object?) DefaultResolver( Utf8GamePath path )
|
||||
{
|
||||
var resolved = Mods.Mod.Manager.ResolvePath( path );
|
||||
var resolved = Penumbra.CollectionManager.Default.ResolvePath( path );
|
||||
return ( resolved, null );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ public partial class MetaManager
|
|||
public bool ApplyMod( RspManipulation m, int modIdx )
|
||||
{
|
||||
#if USE_CMP
|
||||
Manipulations[ m ] = modIdx;
|
||||
File ??= new CmpFile();
|
||||
Manipulations[ m ] = modIdx;
|
||||
File ??= new CmpFile();
|
||||
return m.Apply( File );
|
||||
#else
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ public partial class MetaManager
|
|||
public bool ApplyMod( EqpManipulation m, int modIdx )
|
||||
{
|
||||
#if USE_EQP
|
||||
Manipulations[ m ] = modIdx;
|
||||
File ??= new ExpandedEqpFile();
|
||||
Manipulations[ m ] = modIdx;
|
||||
File ??= new ExpandedEqpFile();
|
||||
return m.Apply( File );
|
||||
#else
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public partial class MetaManager
|
|||
public readonly Dictionary< ImcManipulation, int > Manipulations = new();
|
||||
|
||||
private readonly ModCollection _collection;
|
||||
private static int _imcManagerCount;
|
||||
private static int _imcManagerCount;
|
||||
|
||||
|
||||
public MetaManagerImc( ModCollection collection )
|
||||
|
|
@ -102,6 +102,7 @@ public partial class MetaManager
|
|||
|
||||
Files.Clear();
|
||||
Manipulations.Clear();
|
||||
RestoreDelegate();
|
||||
}
|
||||
|
||||
[Conditional( "USE_IMC" )]
|
||||
|
|
@ -141,11 +142,11 @@ public partial class MetaManager
|
|||
if( Penumbra.CollectionManager.ByName( split.ToString(), out var collection )
|
||||
&& collection.HasCache
|
||||
&& collection.MetaCache!.Imc.Files.TryGetValue(
|
||||
Utf8GamePath.FromSpan( path.Span, out var p, false ) ? p : Utf8GamePath.Empty, out var file ) )
|
||||
Utf8GamePath.FromSpan( path.Span, out var p ) ? p : Utf8GamePath.Empty, out var file ) )
|
||||
{
|
||||
PluginLog.Debug( "Loaded {GamePath:l} from file and replaced with IMC from collection {Collection:l}.", path,
|
||||
collection.Name );
|
||||
file.Replace( fileDescriptor->ResourceHandle, true);
|
||||
file.Replace( fileDescriptor->ResourceHandle, true );
|
||||
file.ChangesSinceLoad = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,239 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Importer;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Meta;
|
||||
|
||||
// Corresponds meta manipulations of any kind with the settings for a mod.
|
||||
// DefaultData contains all manipulations that are active regardless of option groups.
|
||||
// GroupData contains a mapping of Group -> { Options -> {Manipulations} }.
|
||||
public class MetaCollection
|
||||
{
|
||||
public List< MetaManipulation > DefaultData = new();
|
||||
public Dictionary< string, Dictionary< string, List< MetaManipulation > > > GroupData = new();
|
||||
|
||||
|
||||
// Store total number of manipulations for some ease of access.
|
||||
[JsonIgnore]
|
||||
internal int Count;
|
||||
|
||||
|
||||
// Return an enumeration of all active meta manipulations for a given mod with given settings.
|
||||
public IEnumerable< MetaManipulation > GetManipulationsForConfig( ModSettings settings, ModMeta modMeta )
|
||||
{
|
||||
if( Count == DefaultData.Count )
|
||||
{
|
||||
return DefaultData;
|
||||
}
|
||||
|
||||
IEnumerable< MetaManipulation > ret = DefaultData;
|
||||
|
||||
foreach( var group in modMeta.Groups )
|
||||
{
|
||||
if( !GroupData.TryGetValue( group.Key, out var metas ) || !settings.Settings.TryGetValue( group.Key, out var setting ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if( group.Value.SelectionType == SelectType.Single )
|
||||
{
|
||||
var settingName = group.Value.Options[ setting ].OptionName;
|
||||
if( metas.TryGetValue( settingName, out var meta ) )
|
||||
{
|
||||
ret = ret.Concat( meta );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( var i = 0; i < group.Value.Options.Count; ++i )
|
||||
{
|
||||
var flag = 1 << i;
|
||||
if( ( setting & flag ) == 0 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var settingName = group.Value.Options[ i ].OptionName;
|
||||
if( metas.TryGetValue( settingName, out var meta ) )
|
||||
{
|
||||
ret = ret.Concat( meta );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Check that the collection is still basically valid,
|
||||
// i.e. keep it sorted, and verify that the options stored by name are all still part of the mod,
|
||||
// and that the contained manipulations are still valid and non-default manipulations.
|
||||
public bool Validate( ModMeta modMeta )
|
||||
{
|
||||
SortLists();
|
||||
foreach( var group in GroupData )
|
||||
{
|
||||
if( !modMeta.Groups.TryGetValue( group.Key, out var options ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach( var option in group.Value )
|
||||
{
|
||||
if( options.Options.All( o => o.OptionName != option.Key ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//if( option.Value.Any( manip => defaultFiles.CheckAgainstDefault( manip ) ) )
|
||||
//{
|
||||
// return false;
|
||||
//}
|
||||
}
|
||||
} // TODO
|
||||
|
||||
return true; //DefaultData.All( manip => !defaultFiles.CheckAgainstDefault( manip ) );
|
||||
}
|
||||
|
||||
// Re-sort all manipulations.
|
||||
private void SortLists()
|
||||
{
|
||||
DefaultData.Sort();
|
||||
foreach( var list in GroupData.Values.SelectMany( g => g.Values ) )
|
||||
{
|
||||
list.Sort();
|
||||
}
|
||||
}
|
||||
|
||||
// Add a parsed TexTools .meta file to a given option group and option. If group is the empty string, add it to default.
|
||||
// Creates the option group and the option if necessary.
|
||||
private void AddMeta( string group, string option, TexToolsMeta meta )
|
||||
{
|
||||
var manipulations = meta.EqpManipulations.Select( m => new MetaManipulation( m ) )
|
||||
.Concat( meta.EqdpManipulations.Select( m => new MetaManipulation( m ) ) )
|
||||
.Concat( meta.EstManipulations.Select( m => new MetaManipulation( m ) ) )
|
||||
.Concat( meta.GmpManipulations.Select( m => new MetaManipulation( m ) ) )
|
||||
.Concat( meta.RspManipulations.Select( m => new MetaManipulation( m ) ) )
|
||||
.Concat( meta.ImcManipulations.Select( m => new MetaManipulation( m ) ) ).ToList();
|
||||
|
||||
if( group.Length == 0 )
|
||||
{
|
||||
DefaultData.AddRange( manipulations );
|
||||
}
|
||||
else if( option.Length == 0 )
|
||||
{ }
|
||||
else if( !GroupData.TryGetValue( group, out var options ) )
|
||||
{
|
||||
GroupData.Add( group, new Dictionary< string, List< MetaManipulation > >() { { option, manipulations } } );
|
||||
}
|
||||
else if( !options.TryGetValue( option, out var list ) )
|
||||
{
|
||||
options.Add( option, manipulations );
|
||||
}
|
||||
else
|
||||
{
|
||||
list.AddRange( manipulations );
|
||||
}
|
||||
|
||||
Count += manipulations.Count;
|
||||
}
|
||||
|
||||
// Update the whole meta collection by reading all TexTools .meta files in a mod directory anew,
|
||||
// combining them with the given ModMeta.
|
||||
public void Update( IEnumerable< FullPath > files, DirectoryInfo basePath, ModMeta modMeta )
|
||||
{
|
||||
DefaultData.Clear();
|
||||
GroupData.Clear();
|
||||
Count = 0;
|
||||
foreach( var file in files )
|
||||
{
|
||||
var metaData = file.Extension.ToLowerInvariant() switch
|
||||
{
|
||||
".meta" => new TexToolsMeta( File.ReadAllBytes( file.FullName ) ),
|
||||
".rgsp" => TexToolsMeta.FromRgspFile( file.FullName, File.ReadAllBytes( file.FullName ) ),
|
||||
_ => TexToolsMeta.Invalid,
|
||||
};
|
||||
|
||||
if( metaData.FilePath == string.Empty )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Utf8RelPath.FromFile( file, basePath, out var path );
|
||||
var foundAny = false;
|
||||
foreach( var (name, group) in modMeta.Groups )
|
||||
{
|
||||
foreach( var option in group.Options.Where( o => o.OptionFiles.ContainsKey( path ) ) )
|
||||
{
|
||||
foundAny = true;
|
||||
AddMeta( name, option.OptionName, metaData );
|
||||
}
|
||||
}
|
||||
|
||||
if( !foundAny )
|
||||
{
|
||||
AddMeta( string.Empty, string.Empty, metaData );
|
||||
}
|
||||
}
|
||||
|
||||
SortLists();
|
||||
}
|
||||
|
||||
public static FileInfo FileName( DirectoryInfo basePath )
|
||||
=> new(Path.Combine( basePath.FullName, "metadata_manipulations.json" ));
|
||||
|
||||
public void SaveToFile( FileInfo file )
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = JsonConvert.SerializeObject( this, Formatting.Indented );
|
||||
File.WriteAllText( file.FullName, text );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not write metadata manipulations file to {file.FullName}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
public static MetaCollection? LoadFromFile( FileInfo file )
|
||||
{
|
||||
if( !file.Exists )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText( file.FullName );
|
||||
|
||||
var collection = JsonConvert.DeserializeObject< MetaCollection >( text,
|
||||
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore } );
|
||||
|
||||
if( collection != null )
|
||||
{
|
||||
if( collection.DefaultData.Concat( collection.GroupData.Values.SelectMany( kvp => kvp.Values.SelectMany( l => l ) ) )
|
||||
.Any( m => m.ManipulationType == MetaManipulation.Type.Unknown || !Enum.IsDefined( m.ManipulationType ) ) )
|
||||
{
|
||||
throw new Exception( "Invalid collection" );
|
||||
}
|
||||
|
||||
collection.Count = collection.DefaultData.Count
|
||||
+ collection.GroupData.Values.SelectMany( kvp => kvp.Values ).Sum( l => l.Count );
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not load mod metadata manipulations from {file.FullName}:\n{e}" );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ using System.Linq;
|
|||
using Dalamud.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Filesystem;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Mods;
|
||||
|
||||
|
|
@ -21,7 +20,7 @@ public class MigrateConfiguration
|
|||
public string ForcedCollection = string.Empty;
|
||||
public Dictionary< string, string > CharacterCollections = new();
|
||||
public Dictionary< string, string > ModSortOrder = new();
|
||||
public bool InvertModListOrder = false;
|
||||
public bool InvertModListOrder;
|
||||
|
||||
|
||||
public static void Migrate( Configuration config )
|
||||
|
|
@ -143,32 +142,28 @@ public class MigrateConfiguration
|
|||
var data = JArray.Parse( text );
|
||||
|
||||
var maxPriority = 0;
|
||||
var dict = new Dictionary< string, ModSettings >();
|
||||
var dict = new Dictionary< string, ModSettings2.SavedSettings >();
|
||||
foreach( var setting in data.Cast< JObject >() )
|
||||
{
|
||||
var modName = ( string )setting[ "FolderName" ]!;
|
||||
var enabled = ( bool )setting[ "Enabled" ]!;
|
||||
var priority = ( int )setting[ "Priority" ]!;
|
||||
var settings = setting[ "Settings" ]!.ToObject< Dictionary< string, int > >()
|
||||
?? setting[ "Conf" ]!.ToObject< Dictionary< string, int > >();
|
||||
var settings = setting[ "Settings" ]!.ToObject< Dictionary< string, uint > >()
|
||||
?? setting[ "Conf" ]!.ToObject< Dictionary< string, uint > >();
|
||||
|
||||
dict[ modName ] = new ModSettings()
|
||||
dict[ modName ] = new ModSettings2.SavedSettings()
|
||||
{
|
||||
Enabled = enabled,
|
||||
Priority = priority,
|
||||
Settings = settings!,
|
||||
};
|
||||
;
|
||||
maxPriority = Math.Max( maxPriority, priority );
|
||||
}
|
||||
|
||||
InvertModListOrder = _data[ nameof( InvertModListOrder ) ]?.ToObject< bool >() ?? InvertModListOrder;
|
||||
if( !InvertModListOrder )
|
||||
{
|
||||
foreach( var setting in dict.Values )
|
||||
{
|
||||
setting.Priority = maxPriority - setting.Priority;
|
||||
}
|
||||
dict = dict.ToDictionary( kvp => kvp.Key, kvp => kvp.Value with { Priority = maxPriority - kvp.Value.Priority } );
|
||||
}
|
||||
|
||||
defaultCollection = ModCollection.MigrateFromV0( ModCollection.DefaultCollection, dict );
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// A complete Mod containing settings (i.e. dependent on a collection)
|
||||
// and the resulting cache.
|
||||
public class FullMod
|
||||
{
|
||||
public ModSettings Settings { get; }
|
||||
public Mod Data { get; }
|
||||
|
||||
public FullMod( ModSettings settings, Mod data )
|
||||
{
|
||||
Settings = settings;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public bool FixSettings()
|
||||
=> Settings.FixInvalidSettings( Data.Meta );
|
||||
|
||||
public HashSet< Utf8GamePath > GetFiles( FileInfo file )
|
||||
{
|
||||
var relPath = Utf8RelPath.FromFile( file, Data.BasePath, out var p ) ? p : Utf8RelPath.Empty;
|
||||
return ModFunctions.GetFilesForConfig( relPath, Settings, Data.Meta );
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Data.Meta.Name;
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public enum SelectType
|
||||
{
|
||||
Single,
|
||||
Multi,
|
||||
}
|
||||
|
||||
public struct Option
|
||||
{
|
||||
public string OptionName;
|
||||
public string OptionDesc;
|
||||
|
||||
[JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< Utf8GamePath > ) )]
|
||||
public Dictionary< Utf8RelPath, HashSet< Utf8GamePath > > OptionFiles;
|
||||
|
||||
public bool AddFile( Utf8RelPath filePath, Utf8GamePath gamePath )
|
||||
{
|
||||
if( OptionFiles.TryGetValue( filePath, out var set ) )
|
||||
{
|
||||
return set.Add( gamePath );
|
||||
}
|
||||
|
||||
OptionFiles[ filePath ] = new HashSet< Utf8GamePath > { gamePath };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public struct OptionGroup
|
||||
{
|
||||
public string GroupName;
|
||||
|
||||
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
|
||||
public SelectType SelectionType;
|
||||
|
||||
public List< Option > Options;
|
||||
|
||||
private bool ApplySingleGroupFiles( Utf8RelPath relPath, int selection, HashSet< Utf8GamePath > paths )
|
||||
{
|
||||
// Selection contains the path, merge all GamePaths for this config.
|
||||
if( Options[ selection ].OptionFiles.TryGetValue( relPath, out var groupPaths ) )
|
||||
{
|
||||
paths.UnionWith( groupPaths );
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the group contains the file in another selection, return true to skip it for default files.
|
||||
for( var i = 0; i < Options.Count; ++i )
|
||||
{
|
||||
if( i == selection )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if( Options[ i ].OptionFiles.ContainsKey( relPath ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ApplyMultiGroupFiles( Utf8RelPath relPath, int selection, HashSet< Utf8GamePath > paths )
|
||||
{
|
||||
var doNotAdd = false;
|
||||
for( var i = 0; i < Options.Count; ++i )
|
||||
{
|
||||
if( ( selection & ( 1 << i ) ) != 0 )
|
||||
{
|
||||
if( Options[ i ].OptionFiles.TryGetValue( relPath, out var groupPaths ) )
|
||||
{
|
||||
paths.UnionWith( groupPaths );
|
||||
doNotAdd = true;
|
||||
}
|
||||
}
|
||||
else if( Options[ i ].OptionFiles.ContainsKey( relPath ) )
|
||||
{
|
||||
doNotAdd = true;
|
||||
}
|
||||
}
|
||||
|
||||
return doNotAdd;
|
||||
}
|
||||
|
||||
// Adds all game paths from the given option that correspond to the given RelPath to paths, if any exist.
|
||||
internal bool ApplyGroupFiles( Utf8RelPath relPath, int selection, HashSet< Utf8GamePath > paths )
|
||||
{
|
||||
return SelectionType switch
|
||||
{
|
||||
SelectType.Single => ApplySingleGroupFiles( relPath, selection, paths ),
|
||||
SelectType.Multi => ApplyMultiGroupFiles( relPath, selection, paths ),
|
||||
_ => throw new InvalidEnumArgumentException( "Invalid option group type." ),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ public sealed partial class Mod2
|
|||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
|
||||
public Manager( string modDirectory )
|
||||
{
|
||||
SetBaseDirectory( modDirectory, true );
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class Mod
|
||||
{
|
||||
public struct SortOrder : IComparable< SortOrder >
|
||||
{
|
||||
public ModFolder ParentFolder { get; set; }
|
||||
|
||||
private string _sortOrderName;
|
||||
|
||||
public string SortOrderName
|
||||
{
|
||||
get => _sortOrderName;
|
||||
set => _sortOrderName = value.Replace( '/', '\\' );
|
||||
}
|
||||
|
||||
public string SortOrderPath
|
||||
=> ParentFolder.FullName;
|
||||
|
||||
public string FullName
|
||||
{
|
||||
get
|
||||
{
|
||||
var path = SortOrderPath;
|
||||
return path.Length > 0 ? $"{path}/{SortOrderName}" : SortOrderName;
|
||||
}
|
||||
}
|
||||
|
||||
public SortOrder( ModFolder parentFolder, string name )
|
||||
{
|
||||
ParentFolder = parentFolder;
|
||||
_sortOrderName = name.Replace( '/', '\\' );
|
||||
}
|
||||
|
||||
public string FullPath
|
||||
=> SortOrderPath.Length > 0 ? $"{SortOrderPath}/{SortOrderName}" : SortOrderName;
|
||||
|
||||
public int CompareTo( SortOrder other )
|
||||
=> string.Compare( FullPath, other.FullPath, StringComparison.InvariantCultureIgnoreCase );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// Mod contains all permanent information about a mod,
|
||||
// and is independent of collections or settings.
|
||||
// It only changes when the user actively changes the mod or their filesystem.
|
||||
public sealed partial class Mod
|
||||
{
|
||||
public DirectoryInfo BasePath;
|
||||
public ModMeta Meta;
|
||||
public ModResources Resources;
|
||||
|
||||
public SortOrder Order;
|
||||
|
||||
public SortedList< string, object? > ChangedItems { get; } = new();
|
||||
public string LowerChangedItemsString { get; private set; } = string.Empty;
|
||||
public FileInfo MetaFile { get; set; }
|
||||
public int Index { get; private set; } = -1;
|
||||
|
||||
private Mod( ModFolder parentFolder, DirectoryInfo basePath, ModMeta meta, ModResources resources)
|
||||
{
|
||||
BasePath = basePath;
|
||||
Meta = meta;
|
||||
Resources = resources;
|
||||
MetaFile = MetaFileInfo( basePath );
|
||||
Order = new SortOrder( parentFolder, Meta.Name );
|
||||
Order.ParentFolder.AddMod( this );
|
||||
ComputeChangedItems();
|
||||
}
|
||||
|
||||
public void ComputeChangedItems()
|
||||
{
|
||||
var identifier = GameData.GameData.GetIdentifier();
|
||||
ChangedItems.Clear();
|
||||
foreach( var file in Resources.ModFiles.Select( f => f.ToRelPath( BasePath, out var p ) ? p : Utf8RelPath.Empty ) )
|
||||
{
|
||||
foreach( var path in ModFunctions.GetAllFiles( file, Meta ) )
|
||||
{
|
||||
identifier.Identify( ChangedItems, path.ToGamePath() );
|
||||
}
|
||||
}
|
||||
|
||||
foreach( var path in Meta.FileSwaps.Keys )
|
||||
{
|
||||
identifier.Identify( ChangedItems, path.ToGamePath() );
|
||||
}
|
||||
|
||||
LowerChangedItemsString = string.Join( "\0", ChangedItems.Keys.Select( k => k.ToLowerInvariant() ) );
|
||||
}
|
||||
|
||||
public static FileInfo MetaFileInfo( DirectoryInfo basePath )
|
||||
=> new(Path.Combine( basePath.FullName, "meta.json" ));
|
||||
|
||||
public static Mod? LoadMod( ModFolder parentFolder, DirectoryInfo basePath )
|
||||
{
|
||||
basePath.Refresh();
|
||||
if( !basePath.Exists )
|
||||
{
|
||||
PluginLog.Error( $"Supplied mod directory {basePath} does not exist." );
|
||||
return null;
|
||||
}
|
||||
|
||||
var metaFile = MetaFileInfo( basePath );
|
||||
if( !metaFile.Exists )
|
||||
{
|
||||
PluginLog.Debug( "No mod meta found for {ModLocation}.", basePath.Name );
|
||||
return null;
|
||||
}
|
||||
|
||||
var meta = ModMeta.LoadFromFile( metaFile );
|
||||
if( meta == null )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = new ModResources();
|
||||
if( data.RefreshModFiles( basePath ).HasFlag( ResourceChange.Meta ) )
|
||||
{
|
||||
data.SetManipulations( meta, basePath );
|
||||
}
|
||||
|
||||
return new Mod( parentFolder, basePath, meta, data );
|
||||
}
|
||||
|
||||
public void SaveMeta()
|
||||
=> Meta.SaveToFile( MetaFile );
|
||||
|
||||
public override string ToString()
|
||||
=> Order.FullPath;
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ public partial class Mod2
|
|||
mod.LoadDefaultOption();
|
||||
mod.LoadAllGroups();
|
||||
mod.ComputeChangedItems();
|
||||
mod.SetHasOptions();
|
||||
mod.SetCounts();
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
|
|
|||
108
Penumbra/Mods/Mod2.Creation.cs
Normal file
108
Penumbra/Mods/Mod2.Creation.cs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Importer.Models;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class Mod2
|
||||
{
|
||||
internal static void CreateMeta( DirectoryInfo directory, string? name, string? author, string? description, string? version,
|
||||
string? website )
|
||||
{
|
||||
var mod = new Mod2( directory );
|
||||
if( name is { Length: 0 } )
|
||||
{
|
||||
mod.Name = name;
|
||||
}
|
||||
|
||||
if( author != null )
|
||||
{
|
||||
mod.Author = author;
|
||||
}
|
||||
|
||||
if( description != null )
|
||||
{
|
||||
mod.Description = description;
|
||||
}
|
||||
|
||||
if( version != null )
|
||||
{
|
||||
mod.Version = version;
|
||||
}
|
||||
|
||||
if( website != null )
|
||||
{
|
||||
mod.Website = website;
|
||||
}
|
||||
|
||||
mod.SaveMeta();
|
||||
}
|
||||
|
||||
internal static void CreateOptionGroup( DirectoryInfo baseFolder, ModGroup groupData,
|
||||
int priority, string desc, List< ISubMod > subMods )
|
||||
{
|
||||
switch( groupData.SelectionType )
|
||||
{
|
||||
case SelectType.Multi:
|
||||
{
|
||||
var group = new MultiModGroup()
|
||||
{
|
||||
Name = groupData.GroupName!,
|
||||
Description = desc,
|
||||
Priority = priority,
|
||||
};
|
||||
group.PrioritizedOptions.AddRange( subMods.OfType< SubMod >().Select( ( s, idx ) => ( s, idx ) ) );
|
||||
IModGroup.SaveModGroup( group, baseFolder );
|
||||
break;
|
||||
}
|
||||
case SelectType.Single:
|
||||
{
|
||||
var group = new SingleModGroup()
|
||||
{
|
||||
Name = groupData.GroupName!,
|
||||
Description = desc,
|
||||
Priority = priority,
|
||||
};
|
||||
group.OptionData.AddRange( subMods.OfType< SubMod >() );
|
||||
IModGroup.SaveModGroup( group, baseFolder );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static ISubMod CreateSubMod( DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option )
|
||||
{
|
||||
var list = optionFolder.EnumerateFiles( "*.*", SearchOption.AllDirectories )
|
||||
.Select( f => ( Utf8GamePath.FromFile( f, optionFolder, out var gamePath, true ), gamePath, new FullPath( f ) ) )
|
||||
.Where( t => t.Item1 );
|
||||
|
||||
var mod = new SubMod()
|
||||
{
|
||||
Name = option.Name!,
|
||||
};
|
||||
foreach( var (_, gamePath, file) in list )
|
||||
{
|
||||
mod.FileData.TryAdd( gamePath, file );
|
||||
}
|
||||
|
||||
mod.IncorporateMetaChanges( baseFolder, true );
|
||||
return mod;
|
||||
}
|
||||
|
||||
internal static void CreateDefaultFiles( DirectoryInfo directory )
|
||||
{
|
||||
var mod = new Mod2( directory );
|
||||
foreach( var file in mod.FindUnusedFiles() )
|
||||
{
|
||||
if( Utf8GamePath.FromFile( new FileInfo( file.FullName ), directory, out var gamePath, true ) )
|
||||
{
|
||||
mod._default.FileData.TryAdd( gamePath, file );
|
||||
}
|
||||
}
|
||||
|
||||
mod._default.IncorporateMetaChanges( directory, true );
|
||||
mod.SaveDefaultMod();
|
||||
}
|
||||
}
|
||||
|
|
@ -17,19 +17,31 @@ public partial class Mod2
|
|||
public IReadOnlyList< IModGroup > Groups
|
||||
=> _groups;
|
||||
|
||||
private readonly SubMod _default = new();
|
||||
private readonly List< IModGroup > _groups = new();
|
||||
|
||||
public int TotalFileCount { get; private set; }
|
||||
public int TotalSwapCount { get; private set; }
|
||||
public int TotalManipulations { get; private set; }
|
||||
public bool HasOptions { get; private set; }
|
||||
|
||||
private void SetHasOptions()
|
||||
private void SetCounts()
|
||||
{
|
||||
TotalFileCount = 0;
|
||||
TotalSwapCount = 0;
|
||||
TotalManipulations = 0;
|
||||
foreach( var s in AllSubMods )
|
||||
{
|
||||
TotalFileCount += s.Files.Count;
|
||||
TotalSwapCount += s.FileSwaps.Count;
|
||||
TotalManipulations += s.Manipulations.Count;
|
||||
}
|
||||
|
||||
HasOptions = _groups.Any( o
|
||||
=> o is MultiModGroup m && m.PrioritizedOptions.Count > 0
|
||||
|| o is SingleModGroup s && s.OptionData.Count > 1 );
|
||||
}
|
||||
|
||||
|
||||
private readonly SubMod _default = new();
|
||||
private readonly List< IModGroup > _groups = new();
|
||||
|
||||
public IEnumerable< ISubMod > AllSubMods
|
||||
=> _groups.SelectMany( o => o ).Prepend( _default );
|
||||
|
||||
|
|
@ -56,6 +68,13 @@ public partial class Mod2
|
|||
.ToList();
|
||||
}
|
||||
|
||||
// Filter invalid files.
|
||||
// If audio streaming is not disabled, replacing .scd files crashes the game,
|
||||
// so only add those files if it is disabled.
|
||||
public static bool FilterFile( Utf8GamePath gamePath )
|
||||
=> !Penumbra.Config.DisableSoundStreaming
|
||||
&& gamePath.Path.EndsWith( '.', 's', 'c', 'd' );
|
||||
|
||||
public List< FullPath > FindMissingFiles()
|
||||
=> AllFiles.Where( f => !f.Exists ).ToList();
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public sealed partial class Mod2
|
|||
mod._default.FileSwapData.Add( gamePath, swapPath );
|
||||
}
|
||||
|
||||
HandleMetaChanges( mod._default, mod.BasePath );
|
||||
mod._default.IncorporateMetaChanges( mod.BasePath, false );
|
||||
foreach( var group in mod.Groups )
|
||||
{
|
||||
IModGroup.SaveModGroup( group, mod.BasePath );
|
||||
|
|
@ -119,78 +119,11 @@ public sealed partial class Mod2
|
|||
}
|
||||
}
|
||||
|
||||
private static void HandleMetaChanges( SubMod subMod, DirectoryInfo basePath )
|
||||
{
|
||||
foreach( var (key, file) in subMod.Files.ToList() )
|
||||
{
|
||||
try
|
||||
{
|
||||
switch( file.Extension )
|
||||
{
|
||||
case ".meta":
|
||||
subMod.FileData.Remove( key );
|
||||
if( !file.Exists )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var meta = new TexToolsMeta( File.ReadAllBytes( file.FullName ) );
|
||||
foreach( var manip in meta.EqpManipulations )
|
||||
{
|
||||
subMod.ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
foreach( var manip in meta.EqdpManipulations )
|
||||
{
|
||||
subMod.ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
foreach( var manip in meta.EstManipulations )
|
||||
{
|
||||
subMod.ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
foreach( var manip in meta.GmpManipulations )
|
||||
{
|
||||
subMod.ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
foreach( var manip in meta.ImcManipulations )
|
||||
{
|
||||
subMod.ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
break;
|
||||
case ".rgsp":
|
||||
subMod.FileData.Remove( key );
|
||||
if( !file.Exists )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var rgsp = TexToolsMeta.FromRgspFile( file.FullName, File.ReadAllBytes( file.FullName ) );
|
||||
foreach( var manip in rgsp.RspManipulations )
|
||||
{
|
||||
subMod.ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
break;
|
||||
default: continue;
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not migrate meta changes in mod {basePath} from file {file.FullName}:\n{e}" );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SubMod SubModFromOption( DirectoryInfo basePath, OptionV0 option )
|
||||
{
|
||||
var subMod = new SubMod() { Name = option.OptionName };
|
||||
AddFilesToSubMod( subMod, basePath, option );
|
||||
HandleMetaChanges( subMod, basePath );
|
||||
subMod.IncorporateMetaChanges( basePath, false );
|
||||
return subMod;
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,260 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public delegate void OnModFileSystemChange();
|
||||
|
||||
public static partial class ModFileSystem
|
||||
{
|
||||
// The root folder that should be used as the base for all structured mods.
|
||||
public static ModFolder Root = ModFolder.CreateRoot();
|
||||
|
||||
// Gets invoked every time the file system changes.
|
||||
public static event OnModFileSystemChange? ModFileSystemChanged;
|
||||
|
||||
internal static void InvokeChange()
|
||||
=> ModFileSystemChanged?.Invoke();
|
||||
|
||||
// Find a specific mod folder by its path from Root.
|
||||
// Returns true if the folder was found, and false if not.
|
||||
// The out parameter will contain the furthest existing folder.
|
||||
public static bool Find( string path, out ModFolder folder )
|
||||
{
|
||||
var split = path.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries );
|
||||
folder = Root;
|
||||
foreach( var part in split )
|
||||
{
|
||||
if( !folder.FindSubFolder( part, out folder ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Rename the SortOrderName of a single mod. Slashes are replaced by Backslashes.
|
||||
// Saves and returns true if anything changed.
|
||||
public static bool Rename( this global::Penumbra.Mods.Mod mod, string newName )
|
||||
{
|
||||
if( RenameNoSave( mod, newName ) )
|
||||
{
|
||||
SaveMod( mod );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rename the target folder, merging it and its subfolders if the new name already exists.
|
||||
// Saves all mods manipulated thus, and returns true if anything changed.
|
||||
public static bool Rename( this ModFolder target, string newName )
|
||||
{
|
||||
if( RenameNoSave( target, newName ) )
|
||||
{
|
||||
SaveModChildren( target );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move a single mod to the target folder.
|
||||
// Returns true and saves if anything changed.
|
||||
public static bool Move( this global::Penumbra.Mods.Mod mod, ModFolder target )
|
||||
{
|
||||
if( MoveNoSave( mod, target ) )
|
||||
{
|
||||
SaveMod( mod );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move a mod to the filesystem location specified by sortOrder and rename its SortOrderName.
|
||||
// Creates all necessary Subfolders.
|
||||
public static void Move( this global::Penumbra.Mods.Mod mod, string sortOrder )
|
||||
{
|
||||
var split = sortOrder.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries );
|
||||
var folder = Root;
|
||||
for( var i = 0; i < split.Length - 1; ++i )
|
||||
{
|
||||
folder = folder.FindOrCreateSubFolder( split[ i ] ).Item1;
|
||||
}
|
||||
|
||||
if( MoveNoSave( mod, folder ) | RenameNoSave( mod, split.Last() ) )
|
||||
{
|
||||
SaveMod( mod );
|
||||
}
|
||||
}
|
||||
|
||||
// Moves folder to target.
|
||||
// If an identically named subfolder of target already exists, merges instead.
|
||||
// Root is not movable.
|
||||
public static bool Move( this ModFolder folder, ModFolder target )
|
||||
{
|
||||
if( MoveNoSave( folder, target ) )
|
||||
{
|
||||
SaveModChildren( target );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Merge source with target, moving all direct mod children of source to target,
|
||||
// and moving all subfolders of source to target, or merging them with targets subfolders if they exist.
|
||||
// Returns true and saves if anything changed.
|
||||
public static bool Merge( this ModFolder source, ModFolder target )
|
||||
{
|
||||
if( MergeNoSave( source, target ) )
|
||||
{
|
||||
SaveModChildren( target );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal stuff.
|
||||
public static partial class ModFileSystem
|
||||
{
|
||||
// Reset all sort orders for all descendants of the given folder.
|
||||
// Assumes that it is not called on Root, and thus does not remove unnecessary SortOrder entries.
|
||||
private static void SaveModChildren( ModFolder target )
|
||||
{
|
||||
foreach( var mod in target.AllMods( true ) )
|
||||
{
|
||||
Penumbra.ModManager.TemporaryModSortOrder[ mod.BasePath.Name ] = mod.Order.FullName;
|
||||
}
|
||||
|
||||
Penumbra.Config.Save();
|
||||
InvokeChange();
|
||||
}
|
||||
|
||||
// Sets and saves the sort order of a single mod, removing the entry if it is unnecessary.
|
||||
private static void SaveMod( Mod mod )
|
||||
{
|
||||
if( ReferenceEquals( mod.Order.ParentFolder, Root )
|
||||
&& string.Equals( mod.Order.SortOrderName, mod.Meta.Name.Text.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) )
|
||||
{
|
||||
Penumbra.ModManager.TemporaryModSortOrder.Remove( mod.BasePath.Name );
|
||||
}
|
||||
else
|
||||
{
|
||||
Penumbra.ModManager.TemporaryModSortOrder[ mod.BasePath.Name ] = mod.Order.FullName;
|
||||
}
|
||||
|
||||
Penumbra.Config.Save();
|
||||
InvokeChange();
|
||||
}
|
||||
|
||||
private static bool RenameNoSave( this ModFolder target, string newName )
|
||||
{
|
||||
if( ReferenceEquals( target, Root ) )
|
||||
{
|
||||
throw new InvalidOperationException( "Can not rename root." );
|
||||
}
|
||||
|
||||
newName = newName.Replace( '/', '\\' );
|
||||
if( target.Name == newName )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ModFolder.FolderComparer.CompareType = StringComparison.InvariantCulture;
|
||||
if( target.Parent!.FindSubFolder( newName, out var preExisting ) )
|
||||
{
|
||||
MergeNoSave( target, preExisting );
|
||||
ModFolder.FolderComparer.CompareType = StringComparison.InvariantCultureIgnoreCase;
|
||||
}
|
||||
else
|
||||
{
|
||||
ModFolder.FolderComparer.CompareType = StringComparison.InvariantCultureIgnoreCase;
|
||||
var parent = target.Parent;
|
||||
parent.RemoveFolderIgnoreEmpty( target );
|
||||
target.Name = newName;
|
||||
parent.FindOrAddSubFolder( target );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool RenameNoSave( Mod mod, string newName )
|
||||
{
|
||||
newName = newName.Replace( '/', '\\' );
|
||||
if( mod.Order.SortOrderName == newName )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mod.Order.ParentFolder.RemoveModIgnoreEmpty( mod );
|
||||
mod.Order = new Mod.SortOrder( mod.Order.ParentFolder, newName );
|
||||
mod.Order.ParentFolder.AddMod( mod );
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool MoveNoSave( Mod mod, ModFolder target )
|
||||
{
|
||||
var oldParent = mod.Order.ParentFolder;
|
||||
if( ReferenceEquals( target, oldParent ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
oldParent.RemoveMod( mod );
|
||||
mod.Order = new Mod.SortOrder( target, mod.Order.SortOrderName );
|
||||
target.AddMod( mod );
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool MergeNoSave( ModFolder source, ModFolder target )
|
||||
{
|
||||
if( ReferenceEquals( source, target ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var any = false;
|
||||
while( source.SubFolders.Count > 0 )
|
||||
{
|
||||
any |= MoveNoSave( source.SubFolders.First(), target );
|
||||
}
|
||||
|
||||
while( source.Mods.Count > 0 )
|
||||
{
|
||||
any |= MoveNoSave( source.Mods.First(), target );
|
||||
}
|
||||
|
||||
source.Parent?.RemoveSubFolder( source );
|
||||
|
||||
return any || source.Parent != null;
|
||||
}
|
||||
|
||||
private static bool MoveNoSave( ModFolder folder, ModFolder target )
|
||||
{
|
||||
// Moving a folder into itself is not permitted.
|
||||
if( ReferenceEquals( folder, target ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( ReferenceEquals( target, folder.Parent! ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
folder.Parent!.RemoveSubFolder( folder );
|
||||
var subFolderIdx = target.FindOrAddSubFolder( folder );
|
||||
if( subFolderIdx > 0 )
|
||||
{
|
||||
var main = target.SubFolders[ subFolderIdx ];
|
||||
MergeNoSave( folder, main );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,13 +4,13 @@ using OtterGui.Filesystem;
|
|||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public sealed class ModFileSystemA : FileSystem< Mod >, IDisposable
|
||||
public sealed class ModFileSystemA : FileSystem< Mod2 >, IDisposable
|
||||
{
|
||||
// Save the current sort order.
|
||||
// Does not save or copy the backup in the current mod directory,
|
||||
// as this is done on mod directory changes only.
|
||||
public void Save()
|
||||
=> SaveToFile( new FileInfo( Mod.Manager.SortOrderFile ), SaveMod, true );
|
||||
=> SaveToFile( new FileInfo( Mod2.Manager.ModFileSystemFile ), SaveMod, true );
|
||||
|
||||
// Create a new ModFileSystem from the currently loaded mods and the current sort order file.
|
||||
public static ModFileSystemA Load()
|
||||
|
|
@ -31,7 +31,7 @@ public sealed class ModFileSystemA : FileSystem< Mod >, IDisposable
|
|||
// Used on construction and on mod rediscoveries.
|
||||
private void Reload()
|
||||
{
|
||||
if( Load( new FileInfo( Mod.Manager.SortOrderFile ), Penumbra.ModManager.Mods, ModToIdentifier, ModToName ) )
|
||||
if( Load( new FileInfo( Mod2.Manager.ModFileSystemFile ), Penumbra.ModManager.Mods, ModToIdentifier, ModToName ) )
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
|
@ -47,13 +47,13 @@ public sealed class ModFileSystemA : FileSystem< Mod >, IDisposable
|
|||
}
|
||||
|
||||
// Used for saving and loading.
|
||||
private static string ModToIdentifier( Mod mod )
|
||||
private static string ModToIdentifier( Mod2 mod )
|
||||
=> mod.BasePath.Name;
|
||||
|
||||
private static string ModToName( Mod mod )
|
||||
=> mod.Meta.Name.Text;
|
||||
private static string ModToName( Mod2 mod )
|
||||
=> mod.Name.Text;
|
||||
|
||||
private static (string, bool) SaveMod( Mod mod, string fullPath )
|
||||
private static (string, bool) SaveMod( Mod2 mod, string fullPath )
|
||||
{
|
||||
// Only save pairs with non-default paths.
|
||||
if( fullPath == ModToName( mod ) )
|
||||
|
|
|
|||
|
|
@ -1,245 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class ModFolder
|
||||
{
|
||||
public ModFolder? Parent;
|
||||
|
||||
public string FullName
|
||||
{
|
||||
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 > 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 )
|
||||
{
|
||||
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 )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 )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
for( ; modIdx < Mods.Count; ++modIdx )
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
var idx = Mods.BinarySearch( mod, ModComparer );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
Mods.RemoveAt( idx );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// Functions that do not really depend on only one component of a mod.
|
||||
public static class ModFunctions
|
||||
{
|
||||
public static bool CleanUpCollection( Dictionary< string, ModSettings > settings, IEnumerable< DirectoryInfo > modPaths )
|
||||
{
|
||||
var hashes = modPaths.Select( p => p.Name ).ToHashSet();
|
||||
var missingMods = settings.Keys.Where( k => !hashes.Contains( k ) ).ToArray();
|
||||
var anyChanges = false;
|
||||
foreach( var toRemove in missingMods )
|
||||
{
|
||||
anyChanges |= settings.Remove( toRemove );
|
||||
}
|
||||
|
||||
return anyChanges;
|
||||
}
|
||||
|
||||
public static HashSet< Utf8GamePath > GetFilesForConfig( Utf8RelPath relPath, ModSettings settings, ModMeta meta )
|
||||
{
|
||||
var doNotAdd = false;
|
||||
var files = new HashSet< Utf8GamePath >();
|
||||
foreach( var group in meta.Groups.Values.Where( g => g.Options.Count > 0 ) )
|
||||
{
|
||||
doNotAdd |= group.ApplyGroupFiles( relPath, settings.Settings[ group.GroupName ], files );
|
||||
}
|
||||
|
||||
if( !doNotAdd )
|
||||
{
|
||||
files.Add( relPath.ToGamePath() );
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public static HashSet< Utf8GamePath > GetAllFiles( Utf8RelPath relPath, ModMeta meta )
|
||||
{
|
||||
var ret = new HashSet< Utf8GamePath >();
|
||||
foreach( var option in meta.Groups.Values.SelectMany( g => g.Options ) )
|
||||
{
|
||||
if( option.OptionFiles.TryGetValue( relPath, out var files ) )
|
||||
{
|
||||
ret.UnionWith( files );
|
||||
}
|
||||
}
|
||||
|
||||
if( ret.Count == 0 )
|
||||
{
|
||||
ret.Add( relPath.ToGamePath() );
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static ModSettings ConvertNamedSettings( NamedModSettings namedSettings, ModMeta meta )
|
||||
{
|
||||
ModSettings ret = new()
|
||||
{
|
||||
Priority = namedSettings.Priority,
|
||||
Settings = namedSettings.Settings.Keys.ToDictionary( k => k, _ => 0 ),
|
||||
};
|
||||
|
||||
foreach( var setting in namedSettings.Settings.Keys )
|
||||
{
|
||||
if( !meta.Groups.TryGetValue( setting, out var info ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if( info.SelectionType == SelectType.Single )
|
||||
{
|
||||
if( namedSettings.Settings[ setting ].Count == 0 )
|
||||
{
|
||||
ret.Settings[ setting ] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var idx = info.Options.FindIndex( o => o.OptionName == namedSettings.Settings[ setting ].Last() );
|
||||
ret.Settings[ setting ] = idx < 0 ? 0 : idx;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach( var idx in namedSettings.Settings[ setting ]
|
||||
.Select( option => info.Options.FindIndex( o => o.OptionName == option ) )
|
||||
.Where( idx => idx >= 0 ) )
|
||||
{
|
||||
ret.Settings[ setting ] |= 1 << idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,335 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class Mod
|
||||
{
|
||||
public enum ChangeType
|
||||
{
|
||||
Added,
|
||||
Removed,
|
||||
Changed,
|
||||
}
|
||||
|
||||
// The ModManager handles the basic mods installed to the mod directory.
|
||||
// It also contains the CollectionManager that handles all collections.
|
||||
public class Manager : IEnumerable< Mod >
|
||||
{
|
||||
public DirectoryInfo BasePath { get; private set; } = null!;
|
||||
|
||||
private readonly List< Mod > _mods = new();
|
||||
|
||||
public Mod this[ int idx ]
|
||||
=> _mods[ idx ];
|
||||
|
||||
public IReadOnlyList< Mod > Mods
|
||||
=> _mods;
|
||||
|
||||
public IEnumerator< Mod > GetEnumerator()
|
||||
=> _mods.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public ModFolder StructuredMods { get; } = ModFileSystem.Root;
|
||||
|
||||
public delegate void ModChangeDelegate( ChangeType type, Mod mod );
|
||||
|
||||
public event ModChangeDelegate? ModChange;
|
||||
public event Action? ModDiscoveryStarted;
|
||||
public event Action? ModDiscoveryFinished;
|
||||
|
||||
public bool Valid { get; private set; }
|
||||
|
||||
public int Count
|
||||
=> _mods.Count;
|
||||
|
||||
public Configuration Config
|
||||
=> Penumbra.Config;
|
||||
|
||||
public void DiscoverMods( string newDir )
|
||||
{
|
||||
SetBaseDirectory( newDir, false );
|
||||
DiscoverMods();
|
||||
}
|
||||
|
||||
private void SetBaseDirectory( string newPath, bool firstTime )
|
||||
{
|
||||
if( !firstTime && string.Equals( newPath, Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( newPath.Length == 0 )
|
||||
{
|
||||
Valid = false;
|
||||
BasePath = new DirectoryInfo( "." );
|
||||
}
|
||||
else
|
||||
{
|
||||
var newDir = new DirectoryInfo( newPath );
|
||||
if( !newDir.Exists )
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory( newDir.FullName );
|
||||
newDir.Refresh();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not create specified mod directory {newDir.FullName}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
if( !firstTime )
|
||||
{
|
||||
HandleSortOrderFiles( newDir );
|
||||
}
|
||||
|
||||
BasePath = newDir;
|
||||
|
||||
Valid = true;
|
||||
if( Config.ModDirectory != BasePath.FullName )
|
||||
{
|
||||
Config.ModDirectory = BasePath.FullName;
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const string SortOrderFileName = "sort_order.json";
|
||||
public static string SortOrderFile = Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(), SortOrderFileName );
|
||||
|
||||
private void HandleSortOrderFiles( DirectoryInfo newDir )
|
||||
{
|
||||
try
|
||||
{
|
||||
var mainFile = SortOrderFile;
|
||||
// Copy old sort order to backup.
|
||||
var oldSortOrderFile = Path.Combine( BasePath.FullName, SortOrderFileName );
|
||||
PluginLog.Debug( "Copying current sort older file to {BackupFile}...", oldSortOrderFile );
|
||||
File.Copy( mainFile, oldSortOrderFile, true );
|
||||
BasePath = newDir;
|
||||
var newSortOrderFile = Path.Combine( newDir.FullName, SortOrderFileName );
|
||||
// Copy new sort order to main, if it exists.
|
||||
if( File.Exists( newSortOrderFile ) )
|
||||
{
|
||||
File.Copy( newSortOrderFile, mainFile, true );
|
||||
PluginLog.Debug( "Copying stored sort order file from {BackupFile}...", newSortOrderFile );
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Delete( mainFile );
|
||||
PluginLog.Debug( "Deleting current sort order file...", newSortOrderFile );
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not swap Sort Order files:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
public Manager()
|
||||
{
|
||||
SetBaseDirectory( Config.ModDirectory, true );
|
||||
// TODO
|
||||
try
|
||||
{
|
||||
var data = JObject.Parse( File.ReadAllText( SortOrderFile ) );
|
||||
TemporaryModSortOrder = data[ "Data" ]?.ToObject< Dictionary< string, string > >() ?? new Dictionary< string, string >();
|
||||
}
|
||||
catch
|
||||
{
|
||||
TemporaryModSortOrder = new Dictionary< string, string >();
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary< string, string > TemporaryModSortOrder;
|
||||
|
||||
private bool SetSortOrderPath( Mod mod, string path )
|
||||
{
|
||||
mod.Move( path );
|
||||
var fixedPath = mod.Order.FullPath;
|
||||
if( fixedPath.Length == 0 || string.Equals( fixedPath, mod.Meta.Name, StringComparison.InvariantCultureIgnoreCase ) )
|
||||
{
|
||||
Penumbra.ModManager.TemporaryModSortOrder.Remove( mod.BasePath.Name );
|
||||
return true;
|
||||
}
|
||||
|
||||
if( path != fixedPath )
|
||||
{
|
||||
TemporaryModSortOrder[ mod.BasePath.Name ] = fixedPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetModStructure( bool removeOldPaths = false )
|
||||
{
|
||||
var changes = false;
|
||||
|
||||
foreach( var (folder, path) in TemporaryModSortOrder.ToArray() )
|
||||
{
|
||||
if( path.Length > 0 && _mods.FindFirst( m => m.BasePath.Name == folder, out var mod ) )
|
||||
{
|
||||
changes |= SetSortOrderPath( mod, path );
|
||||
}
|
||||
else if( removeOldPaths )
|
||||
{
|
||||
changes = true;
|
||||
TemporaryModSortOrder.Remove( folder );
|
||||
}
|
||||
}
|
||||
|
||||
if( changes )
|
||||
{
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public void DiscoverMods()
|
||||
{
|
||||
ModDiscoveryStarted?.Invoke();
|
||||
_mods.Clear();
|
||||
BasePath.Refresh();
|
||||
|
||||
StructuredMods.SubFolders.Clear();
|
||||
StructuredMods.Mods.Clear();
|
||||
if( Valid && BasePath.Exists )
|
||||
{
|
||||
foreach( var modFolder in BasePath.EnumerateDirectories() )
|
||||
{
|
||||
var mod = LoadMod( StructuredMods, modFolder );
|
||||
if( mod == null )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
mod.Index = _mods.Count;
|
||||
_mods.Add( mod );
|
||||
}
|
||||
|
||||
SetModStructure();
|
||||
}
|
||||
|
||||
ModDiscoveryFinished?.Invoke();
|
||||
}
|
||||
|
||||
public void DeleteMod( DirectoryInfo modFolder )
|
||||
{
|
||||
if( Directory.Exists( modFolder.FullName ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete( modFolder.FullName, true );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not delete the mod {modFolder.Name}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
var idx = _mods.FindIndex( m => m.BasePath.Name == modFolder.Name );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
var mod = _mods[ idx ];
|
||||
mod.Order.ParentFolder.RemoveMod( mod );
|
||||
_mods.RemoveAt( idx );
|
||||
for( var i = idx; i < _mods.Count; ++i )
|
||||
{
|
||||
--_mods[ i ].Index;
|
||||
}
|
||||
|
||||
ModChange?.Invoke( ChangeType.Removed, mod );
|
||||
}
|
||||
}
|
||||
|
||||
public int AddMod( DirectoryInfo modFolder )
|
||||
{
|
||||
var mod = LoadMod( StructuredMods, modFolder );
|
||||
if( mod == null )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if( TemporaryModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
|
||||
{
|
||||
if( SetSortOrderPath( mod, sortOrder ) )
|
||||
{
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
if( _mods.Any( m => m.BasePath.Name == modFolder.Name ) )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
_mods.Add( mod );
|
||||
ModChange?.Invoke( ChangeType.Added, mod );
|
||||
|
||||
return _mods.Count - 1;
|
||||
}
|
||||
|
||||
public bool UpdateMod( int idx, bool reloadMeta = false, bool recomputeMeta = false, bool force = false )
|
||||
{
|
||||
var mod = Mods[ idx ];
|
||||
var oldName = mod.Meta.Name;
|
||||
var metaChanges = mod.Meta.RefreshFromFile( mod.MetaFile ) != 0 || force;
|
||||
var fileChanges = mod.Resources.RefreshModFiles( mod.BasePath );
|
||||
|
||||
if( !recomputeMeta && !reloadMeta && !metaChanges && fileChanges == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( metaChanges || fileChanges.HasFlag( ResourceChange.Files ) )
|
||||
{
|
||||
mod.ComputeChangedItems();
|
||||
if( TemporaryModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
|
||||
{
|
||||
mod.Move( sortOrder );
|
||||
var path = mod.Order.FullPath;
|
||||
if( path != sortOrder )
|
||||
{
|
||||
TemporaryModSortOrder[ mod.BasePath.Name ] = path;
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mod.Order = new SortOrder( StructuredMods, mod.Meta.Name );
|
||||
}
|
||||
}
|
||||
|
||||
var nameChange = !string.Equals( oldName, mod.Meta.Name, StringComparison.InvariantCulture );
|
||||
|
||||
recomputeMeta |= fileChanges.HasFlag( ResourceChange.Meta );
|
||||
if( recomputeMeta )
|
||||
{
|
||||
mod.Resources.MetaManipulations.Update( mod.Resources.MetaFiles, mod.BasePath, mod.Meta );
|
||||
mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) );
|
||||
}
|
||||
|
||||
// TODO: more specific mod changes?
|
||||
ModChange?.Invoke( ChangeType.Changed, mod );
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool UpdateMod( Mod mod, bool reloadMeta = false, bool recomputeMeta = false, bool force = false )
|
||||
=> UpdateMod( Mods.IndexOf( mod ), reloadMeta, recomputeMeta, force );
|
||||
|
||||
public static FullPath? ResolvePath( Utf8GamePath gameResourcePath )
|
||||
=> Penumbra.CollectionManager.Default.ResolvePath( gameResourcePath );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,211 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// Extracted to keep the main file a bit more clean.
|
||||
// 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.Manager manager, string newName, Mod mod )
|
||||
{
|
||||
if( newName.Length == 0 || string.Equals( newName, mod.Meta.Name, StringComparison.InvariantCulture ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mod.Meta.Name = newName;
|
||||
mod.SaveMeta();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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.SortOrder( manager.StructuredMods, mod.Meta.Name );
|
||||
if( newSortOrder == string.Empty || newSortOrder == inRoot.SortOrderName )
|
||||
{
|
||||
mod.Order = inRoot;
|
||||
manager.TemporaryModSortOrder.Remove( mod.BasePath.Name );
|
||||
}
|
||||
else
|
||||
{
|
||||
mod.Move( newSortOrder );
|
||||
manager.TemporaryModSortOrder[ mod.BasePath.Name ] = mod.Order.FullPath;
|
||||
}
|
||||
|
||||
Penumbra.Config.Save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool RenameModFolder( this Mod.Manager manager, Mod mod, DirectoryInfo newDir, bool move = true )
|
||||
{
|
||||
if( move )
|
||||
{
|
||||
newDir.Refresh();
|
||||
if( newDir.Exists )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var oldDir = new DirectoryInfo( mod.BasePath.FullName );
|
||||
try
|
||||
{
|
||||
oldDir.MoveTo( newDir.FullName );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Error while renaming directory {oldDir.FullName} to {newDir.FullName}:\n{e}" );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var oldBasePath = mod.BasePath;
|
||||
mod.BasePath = newDir;
|
||||
mod.MetaFile = Mod.MetaFileInfo( newDir );
|
||||
manager.UpdateMod( mod );
|
||||
|
||||
if( manager.TemporaryModSortOrder.ContainsKey( oldBasePath.Name ) )
|
||||
{
|
||||
manager.TemporaryModSortOrder[ newDir.Name ] = manager.TemporaryModSortOrder[ oldBasePath.Name ];
|
||||
manager.TemporaryModSortOrder.Remove( oldBasePath.Name );
|
||||
Penumbra.Config.Save();
|
||||
}
|
||||
|
||||
var idx = manager.Mods.IndexOf( mod );
|
||||
foreach( var collection in Penumbra.CollectionManager )
|
||||
{
|
||||
if( collection.Settings[ idx ] != null )
|
||||
{
|
||||
collection.Save();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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 ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( mod.Meta.Groups.TryGetValue( oldGroupName, out var oldGroup ) )
|
||||
{
|
||||
if( newGroupName.Length > 0 )
|
||||
{
|
||||
mod.Meta.Groups[ newGroupName ] = new OptionGroup()
|
||||
{
|
||||
GroupName = newGroupName,
|
||||
SelectionType = oldGroup.SelectionType,
|
||||
Options = oldGroup.Options,
|
||||
};
|
||||
}
|
||||
|
||||
mod.Meta.Groups.Remove( oldGroupName );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( newGroupName.Length == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mod.Meta.Groups[ newGroupName ] = new OptionGroup()
|
||||
{
|
||||
GroupName = newGroupName,
|
||||
SelectionType = type,
|
||||
Options = new List< Option >(),
|
||||
};
|
||||
}
|
||||
|
||||
mod.SaveMeta();
|
||||
|
||||
// TODO to indices
|
||||
var idx = Penumbra.ModManager.Mods.IndexOf( mod );
|
||||
|
||||
foreach( var collection in Penumbra.CollectionManager )
|
||||
{
|
||||
var settings = collection.Settings[ idx ];
|
||||
if( settings == null )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if( newGroupName.Length > 0 )
|
||||
{
|
||||
settings.Settings[ newGroupName ] = settings.Settings.TryGetValue( oldGroupName, out var value ) ? value : 0;
|
||||
}
|
||||
|
||||
settings.Settings.Remove( oldGroupName );
|
||||
collection.Save();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool RemoveModOption( this Mod.Manager manager, int optionIdx, OptionGroup group, Mod mod )
|
||||
{
|
||||
if( optionIdx < 0 || optionIdx >= group.Options.Count )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
group.Options.RemoveAt( optionIdx );
|
||||
mod.SaveMeta();
|
||||
|
||||
static int MoveMultiSetting( int oldSetting, int idx )
|
||||
{
|
||||
var bitmaskFront = ( 1 << idx ) - 1;
|
||||
var bitmaskBack = ~( bitmaskFront | ( 1 << idx ) );
|
||||
return ( oldSetting & bitmaskFront ) | ( ( oldSetting & bitmaskBack ) >> 1 );
|
||||
}
|
||||
|
||||
var idx = Penumbra.ModManager.Mods.IndexOf( mod ); // TODO
|
||||
foreach( var collection in Penumbra.CollectionManager )
|
||||
{
|
||||
var settings = collection.Settings[ idx ];
|
||||
if( settings == null )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if( !settings.Settings.TryGetValue( group.GroupName, out var setting ) )
|
||||
{
|
||||
setting = 0;
|
||||
}
|
||||
|
||||
var newSetting = group.SelectionType switch
|
||||
{
|
||||
SelectType.Single => setting >= optionIdx ? setting - 1 : setting,
|
||||
SelectType.Multi => MoveMultiSetting( setting, optionIdx ),
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
|
||||
if( newSetting != setting )
|
||||
{
|
||||
settings.Settings[ group.GroupName ] = newSetting;
|
||||
collection.Save();
|
||||
if( collection.HasCache && settings.Enabled )
|
||||
{
|
||||
collection.CalculateEffectiveFileList( mod.Resources.MetaManipulations.Count > 0,
|
||||
Penumbra.CollectionManager.Default == collection );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
// Contains descriptive data about the mod as well as possible settings and fileswaps.
|
||||
public class ModMeta
|
||||
{
|
||||
public const uint CurrentFileVersion = 1;
|
||||
|
||||
[Flags]
|
||||
public enum ChangeType : byte
|
||||
{
|
||||
Name = 0x01,
|
||||
Author = 0x02,
|
||||
Description = 0x04,
|
||||
Version = 0x08,
|
||||
Website = 0x10,
|
||||
Deletion = 0x20,
|
||||
}
|
||||
|
||||
public uint FileVersion { get; set; } = CurrentFileVersion;
|
||||
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;
|
||||
|
||||
public bool HasGroupsWithConfig = false;
|
||||
|
||||
public bool RefreshHasGroupsWithConfig()
|
||||
{
|
||||
var oldValue = HasGroupsWithConfig;
|
||||
HasGroupsWithConfig = Groups.Values.Any( g => g.Options.Count > 1 || g.SelectionType == SelectType.Multi && g.Options.Count == 1 );
|
||||
return oldValue != HasGroupsWithConfig;
|
||||
}
|
||||
|
||||
public ChangeType RefreshFromFile( FileInfo filePath )
|
||||
{
|
||||
var newMeta = LoadFromFile( filePath );
|
||||
if( newMeta == null )
|
||||
{
|
||||
return ChangeType.Deletion;
|
||||
}
|
||||
|
||||
ChangeType changes = 0;
|
||||
|
||||
if( Name != newMeta.Name )
|
||||
{
|
||||
changes |= ChangeType.Name;
|
||||
Name = newMeta.Name;
|
||||
}
|
||||
|
||||
if( Author != newMeta.Author )
|
||||
{
|
||||
changes |= ChangeType.Author;
|
||||
Author = newMeta.Author;
|
||||
}
|
||||
|
||||
if( Description != newMeta.Description )
|
||||
{
|
||||
changes |= ChangeType.Description;
|
||||
Description = newMeta.Description;
|
||||
}
|
||||
|
||||
if( Version != newMeta.Version )
|
||||
{
|
||||
changes |= ChangeType.Version;
|
||||
Version = newMeta.Version;
|
||||
}
|
||||
|
||||
if( Website != newMeta.Website )
|
||||
{
|
||||
changes |= ChangeType.Website;
|
||||
Website = newMeta.Website;
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
[JsonProperty( ItemConverterType = typeof( FullPath.FullPathConverter ) )]
|
||||
public Dictionary< Utf8GamePath, FullPath > FileSwaps { get; set; } = new();
|
||||
|
||||
public Dictionary< string, OptionGroup > Groups { get; set; } = new();
|
||||
|
||||
public static ModMeta? LoadFromFile( FileInfo filePath )
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText( filePath.FullName );
|
||||
|
||||
var meta = JsonConvert.DeserializeObject< ModMeta >( text,
|
||||
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore } );
|
||||
if( meta != null )
|
||||
{
|
||||
meta.RefreshHasGroupsWithConfig();
|
||||
Migration.Migrate( meta, text );
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not load mod meta:\n{e}" );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SaveToFile( FileInfo filePath )
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = JsonConvert.SerializeObject( this, Formatting.Indented );
|
||||
File.WriteAllText( filePath.FullName, text );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not write meta file for mod {Name} to {filePath.FullName}:\n{e}" );
|
||||
}
|
||||
}
|
||||
|
||||
private static class Migration
|
||||
{
|
||||
public static void Migrate( ModMeta meta, string text )
|
||||
{
|
||||
MigrateV0ToV1( meta, text );
|
||||
}
|
||||
|
||||
private static void MigrateV0ToV1( ModMeta meta, string text )
|
||||
{
|
||||
if( meta.FileVersion > 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var data = JObject.Parse( text );
|
||||
var swaps = data[ "FileSwaps" ]?.ToObject< Dictionary< Utf8GamePath, FullPath > >()
|
||||
?? new Dictionary< Utf8GamePath, FullPath >();
|
||||
var groups = data[ "Groups" ]?.ToObject< Dictionary< string, OptionGroupV0 > >() ?? new Dictionary< string, OptionGroupV0 >();
|
||||
foreach( var group in groups.Values )
|
||||
{ }
|
||||
|
||||
foreach( var swap in swaps )
|
||||
{ }
|
||||
|
||||
//var meta =
|
||||
}
|
||||
|
||||
|
||||
private struct OptionV0
|
||||
{
|
||||
public string OptionName = string.Empty;
|
||||
public string OptionDesc = string.Empty;
|
||||
|
||||
[JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< Utf8GamePath > ) )]
|
||||
public Dictionary< Utf8RelPath, HashSet< Utf8GamePath > > OptionFiles = new();
|
||||
|
||||
public OptionV0()
|
||||
{ }
|
||||
}
|
||||
|
||||
private struct OptionGroupV0
|
||||
{
|
||||
public string GroupName = string.Empty;
|
||||
|
||||
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
|
||||
public SelectType SelectionType = SelectType.Single;
|
||||
|
||||
public List< OptionV0 > Options = new();
|
||||
|
||||
public OptionGroupV0()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
[Flags]
|
||||
public enum ResourceChange
|
||||
{
|
||||
None = 0,
|
||||
Files = 1,
|
||||
Meta = 2,
|
||||
}
|
||||
|
||||
// Contains static mod data that should only change on filesystem changes.
|
||||
public class ModResources
|
||||
{
|
||||
public List< FullPath > ModFiles { get; private set; } = new();
|
||||
public List< FullPath > MetaFiles { get; private set; } = new();
|
||||
|
||||
public MetaCollection MetaManipulations { get; private set; } = new();
|
||||
|
||||
|
||||
private void ForceManipulationsUpdate( ModMeta meta, DirectoryInfo basePath )
|
||||
{
|
||||
MetaManipulations.Update( MetaFiles, basePath, meta );
|
||||
MetaManipulations.SaveToFile( MetaCollection.FileName( basePath ) );
|
||||
}
|
||||
|
||||
public void SetManipulations( ModMeta meta, DirectoryInfo basePath, bool validate = true )
|
||||
{
|
||||
var newManipulations = MetaCollection.LoadFromFile( MetaCollection.FileName( basePath ) );
|
||||
if( newManipulations == null )
|
||||
{
|
||||
ForceManipulationsUpdate( meta, basePath );
|
||||
}
|
||||
else
|
||||
{
|
||||
MetaManipulations = newManipulations;
|
||||
if( validate && !MetaManipulations.Validate( meta ) )
|
||||
{
|
||||
ForceManipulationsUpdate( meta, basePath );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the current set of files used by the mod,
|
||||
// returns true if anything changed.
|
||||
public ResourceChange RefreshModFiles( DirectoryInfo basePath )
|
||||
{
|
||||
List< FullPath > tmpFiles = new(ModFiles.Count);
|
||||
List< FullPath > tmpMetas = new(MetaFiles.Count);
|
||||
// we don't care about any _files_ in the root dir, but any folders should be a game folder/file combo
|
||||
foreach( var file in basePath.EnumerateDirectories()
|
||||
.SelectMany( dir => dir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
||||
.Select( f => new FullPath( f ) )
|
||||
.OrderBy( f => f.FullName ) )
|
||||
{
|
||||
switch( file.Extension.ToLowerInvariant() )
|
||||
{
|
||||
case ".meta":
|
||||
case ".rgsp":
|
||||
tmpMetas.Add( file );
|
||||
break;
|
||||
default:
|
||||
tmpFiles.Add( file );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ResourceChange changes = 0;
|
||||
if( !tmpFiles.Select( f => f.FullName ).SequenceEqual( ModFiles.Select( f => f.FullName ) ) )
|
||||
{
|
||||
ModFiles = tmpFiles;
|
||||
changes |= ResourceChange.Files;
|
||||
}
|
||||
|
||||
if( !tmpMetas.Select( f => f.FullName ).SequenceEqual( MetaFiles.Select( f => f.FullName ) ) )
|
||||
{
|
||||
MetaFiles = tmpMetas;
|
||||
changes |= ResourceChange.Meta;
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
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.
|
||||
// Enabled does not exist, because disabled mods would not be exported in this way.
|
||||
public class NamedModSettings
|
||||
{
|
||||
public int Priority { get; set; }
|
||||
public Dictionary< string, HashSet< string > > Settings { get; set; } = new();
|
||||
|
||||
public void AddFromModSetting( ModSettings s, ModMeta meta )
|
||||
{
|
||||
Priority = s.Priority;
|
||||
Settings = s.Settings.Keys.ToDictionary( k => k, _ => new HashSet< string >() );
|
||||
|
||||
foreach( var kvp in Settings )
|
||||
{
|
||||
if( !meta.Groups.TryGetValue( kvp.Key, out var info ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var setting = s.Settings[ kvp.Key ];
|
||||
if( info.SelectionType == SelectType.Single )
|
||||
{
|
||||
var name = setting < info.Options.Count
|
||||
? info.Options[ setting ].OptionName
|
||||
: info.Options[ 0 ].OptionName;
|
||||
kvp.Value.Add( name );
|
||||
}
|
||||
else
|
||||
{
|
||||
for( var i = 0; i < info.Options.Count; ++i )
|
||||
{
|
||||
if( ( ( setting >> i ) & 1 ) != 0 )
|
||||
{
|
||||
kvp.Value.Add( info.Options[ i ].OptionName );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ using Dalamud.Logging;
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Importer;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
|
@ -113,5 +114,82 @@ public partial class Mod2
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void IncorporateMetaChanges( DirectoryInfo basePath, bool delete )
|
||||
{
|
||||
foreach( var (key, file) in Files.ToList() )
|
||||
{
|
||||
try
|
||||
{
|
||||
switch( file.Extension )
|
||||
{
|
||||
case ".meta":
|
||||
FileData.Remove( key );
|
||||
if( !file.Exists )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var meta = new TexToolsMeta( File.ReadAllBytes( file.FullName ) );
|
||||
if( delete )
|
||||
{
|
||||
File.Delete( file.FullName );
|
||||
}
|
||||
|
||||
foreach( var manip in meta.EqpManipulations )
|
||||
{
|
||||
ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
foreach( var manip in meta.EqdpManipulations )
|
||||
{
|
||||
ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
foreach( var manip in meta.EstManipulations )
|
||||
{
|
||||
ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
foreach( var manip in meta.GmpManipulations )
|
||||
{
|
||||
ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
foreach( var manip in meta.ImcManipulations )
|
||||
{
|
||||
ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
break;
|
||||
case ".rgsp":
|
||||
FileData.Remove( key );
|
||||
if( !file.Exists )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var rgsp = TexToolsMeta.FromRgspFile( file.FullName, File.ReadAllBytes( file.FullName ) );
|
||||
if( delete )
|
||||
{
|
||||
File.Delete( file.FullName );
|
||||
}
|
||||
|
||||
foreach( var manip in rgsp.RspManipulations )
|
||||
{
|
||||
ManipulationData.Add( manip );
|
||||
}
|
||||
|
||||
break;
|
||||
default: continue;
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not incorporate meta changes in mod {basePath} from file {file.FullName}:\n{e}" );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,72 +4,140 @@ using System.Linq;
|
|||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
|
||||
// Contains the settings for a given mod.
|
||||
public class ModSettings
|
||||
public class ModSettings2
|
||||
{
|
||||
public static readonly ModSettings Empty = new();
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
public static readonly ModSettings2 Empty = new();
|
||||
public List< uint > Settings { get; init; } = new();
|
||||
public int Priority { get; set; }
|
||||
public Dictionary< string, int > Settings { get; set; } = new();
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
// For backwards compatibility
|
||||
private Dictionary< string, int > Conf
|
||||
{
|
||||
set => Settings = value;
|
||||
}
|
||||
|
||||
public ModSettings DeepCopy()
|
||||
{
|
||||
var settings = new ModSettings
|
||||
public ModSettings2 DeepCopy()
|
||||
=> new()
|
||||
{
|
||||
Enabled = Enabled,
|
||||
Priority = Priority,
|
||||
Settings = Settings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value ),
|
||||
Settings = Settings.ToList(),
|
||||
};
|
||||
return settings;
|
||||
}
|
||||
|
||||
public static ModSettings DefaultSettings( ModMeta meta )
|
||||
{
|
||||
return new ModSettings
|
||||
public static ModSettings2 DefaultSettings( Mod2 mod )
|
||||
=> new()
|
||||
{
|
||||
Enabled = false,
|
||||
Priority = 0,
|
||||
Settings = meta.Groups.ToDictionary( kvp => kvp.Key, _ => 0 ),
|
||||
Settings = Enumerable.Repeat( 0u, mod.Groups.Count ).ToList(),
|
||||
};
|
||||
|
||||
|
||||
|
||||
public void HandleChanges( ModOptionChangeType type, Mod2 mod, int groupIdx, int optionIdx )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case ModOptionChangeType.GroupAdded:
|
||||
Settings.Insert( groupIdx, 0 );
|
||||
break;
|
||||
case ModOptionChangeType.GroupDeleted:
|
||||
Settings.RemoveAt( groupIdx );
|
||||
break;
|
||||
case ModOptionChangeType.OptionDeleted:
|
||||
var group = mod.Groups[ groupIdx ];
|
||||
var config = Settings[ groupIdx ];
|
||||
Settings[ groupIdx ] = group.Type switch
|
||||
{
|
||||
SelectType.Single => config >= optionIdx ? Math.Max( 0, config - 1 ) : config,
|
||||
SelectType.Multi => RemoveBit( config, optionIdx ),
|
||||
_ => config,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetValue( Mod2 mod, int groupIdx, uint newValue )
|
||||
{
|
||||
AddMissingSettings( groupIdx + 1 );
|
||||
var group = mod.Groups[ groupIdx ];
|
||||
Settings[ groupIdx ] = group.Type switch
|
||||
{
|
||||
SelectType.Single => ( uint )Math.Max( newValue, group.Count ),
|
||||
SelectType.Multi => ( ( 1u << group.Count ) - 1 ) & newValue,
|
||||
_ => newValue,
|
||||
};
|
||||
}
|
||||
|
||||
public bool FixSpecificSetting( string name, ModMeta meta )
|
||||
private static uint RemoveBit( uint config, int bit )
|
||||
{
|
||||
if( !meta.Groups.TryGetValue( name, out var group ) )
|
||||
{
|
||||
return Settings.Remove( name );
|
||||
}
|
||||
|
||||
if( Settings.TryGetValue( name, out var oldSetting ) )
|
||||
{
|
||||
Settings[ name ] = group.SelectionType switch
|
||||
{
|
||||
SelectType.Single => Math.Min( Math.Max( oldSetting, 0 ), group.Options.Count - 1 ),
|
||||
SelectType.Multi => Math.Min( Math.Max( oldSetting, 0 ), ( 1 << group.Options.Count ) - 1 ),
|
||||
_ => Settings[ group.GroupName ],
|
||||
};
|
||||
return oldSetting != Settings[ group.GroupName ];
|
||||
}
|
||||
|
||||
Settings[ name ] = 0;
|
||||
return true;
|
||||
var lowMask = ( 1u << bit ) - 1u;
|
||||
var highMask = ~( ( 1u << ( bit + 1 ) ) - 1u );
|
||||
var low = config & lowMask;
|
||||
var high = ( config & highMask ) >> 1;
|
||||
return low | high;
|
||||
}
|
||||
|
||||
public bool FixInvalidSettings( ModMeta meta )
|
||||
internal bool AddMissingSettings( int totalCount )
|
||||
{
|
||||
if( meta.Groups.Count == 0 )
|
||||
if( totalCount <= Settings.Count )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Settings.Keys.ToArray().Union( meta.Groups.Keys )
|
||||
.Aggregate( false, ( current, name ) => current | FixSpecificSetting( name, meta ) );
|
||||
Settings.AddRange( Enumerable.Repeat( 0u, totalCount - Settings.Count ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
public struct SavedSettings
|
||||
{
|
||||
public Dictionary< string, uint > Settings;
|
||||
public int Priority;
|
||||
public bool Enabled;
|
||||
|
||||
public SavedSettings DeepCopy()
|
||||
=> new()
|
||||
{
|
||||
Enabled = Enabled,
|
||||
Priority = Priority,
|
||||
Settings = Settings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value ),
|
||||
};
|
||||
|
||||
public SavedSettings( ModSettings2 settings, Mod2 mod )
|
||||
{
|
||||
Priority = settings.Priority;
|
||||
Enabled = settings.Enabled;
|
||||
Settings = new Dictionary< string, uint >( mod.Groups.Count );
|
||||
settings.AddMissingSettings( mod.Groups.Count );
|
||||
|
||||
foreach( var (group, setting) in mod.Groups.Zip( settings.Settings ) )
|
||||
{
|
||||
Settings.Add( group.Name, setting );
|
||||
}
|
||||
}
|
||||
|
||||
public bool ToSettings( Mod2 mod, out ModSettings2 settings )
|
||||
{
|
||||
var list = new List< uint >( mod.Groups.Count );
|
||||
var changes = Settings.Count != mod.Groups.Count;
|
||||
foreach( var group in mod.Groups )
|
||||
{
|
||||
if( Settings.TryGetValue( group.Name, out var config ) )
|
||||
{
|
||||
list.Add( config );
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add( 0 );
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
settings = new ModSettings2
|
||||
{
|
||||
Enabled = Enabled,
|
||||
Priority = Priority,
|
||||
Settings = list,
|
||||
};
|
||||
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Penumbra/Mods/Subclasses/SelectType.cs
Normal file
7
Penumbra/Mods/Subclasses/SelectType.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace Penumbra.Mods;
|
||||
|
||||
public enum SelectType
|
||||
{
|
||||
Single,
|
||||
Multi,
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using EmbedIO;
|
||||
|
|
@ -36,20 +37,22 @@ 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.Manager ModManager { get; private set; } = null!;
|
||||
public static Mod2.Manager ModManager { get; private set; } = null!;
|
||||
public static ModCollection.Manager CollectionManager { get; private set; } = null!;
|
||||
public static SimpleRedirectManager Redirects { get; private set; } = null!;
|
||||
public static ResourceLoader ResourceLoader { get; private set; } = null!;
|
||||
|
||||
public static ResourceLoader ResourceLoader { get; set; } = null!;
|
||||
public ResourceLogger ResourceLogger { get; }
|
||||
|
||||
public PathResolver PathResolver { get; }
|
||||
public SettingsInterface SettingsInterface { get; }
|
||||
public MusicManager MusicManager { get; }
|
||||
public ObjectReloader ObjectReloader { get; }
|
||||
|
||||
public PenumbraApi Api { get; }
|
||||
public PenumbraIpc Ipc { get; }
|
||||
public readonly ResourceLogger ResourceLogger;
|
||||
public readonly PathResolver PathResolver;
|
||||
public readonly MusicManager MusicManager;
|
||||
public readonly ObjectReloader ObjectReloader;
|
||||
public readonly ModFileSystemA ModFileSystem;
|
||||
public readonly PenumbraApi Api;
|
||||
public readonly PenumbraIpc Ipc;
|
||||
private readonly ConfigWindow _configWindow;
|
||||
private readonly LaunchButton _launchButton;
|
||||
private readonly WindowSystem _windowSystem;
|
||||
|
||||
private WebServer? _webServer;
|
||||
|
||||
|
|
@ -68,12 +71,14 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
ResidentResources = new ResidentResourceManager();
|
||||
CharacterUtility = new CharacterUtility();
|
||||
Redirects = new SimpleRedirectManager();
|
||||
MetaFileManager = new MetaFileManager();
|
||||
ResourceLoader = new ResourceLoader( this );
|
||||
ResourceLogger = new ResourceLogger( ResourceLoader );
|
||||
ModManager = new Mod.Manager();
|
||||
ModManager = new Mod2.Manager( Config.ModDirectory );
|
||||
ModManager.DiscoverMods();
|
||||
CollectionManager = new ModCollection.Manager( ModManager );
|
||||
ModFileSystem = ModFileSystemA.Load();
|
||||
ObjectReloader = new ObjectReloader();
|
||||
PathResolver = new PathResolver( ResourceLoader );
|
||||
|
||||
|
|
@ -92,8 +97,7 @@ public class Penumbra : IDalamudPlugin
|
|||
Api = new PenumbraApi( this );
|
||||
Ipc = new PenumbraIpc( pluginInterface, Api );
|
||||
SubscribeItemLinks();
|
||||
|
||||
SettingsInterface = new SettingsInterface( this );
|
||||
SetupInterface( out _configWindow, out _launchButton, out _windowSystem );
|
||||
|
||||
if( Config.EnableHttpApi )
|
||||
{
|
||||
|
|
@ -129,6 +133,24 @@ public class Penumbra : IDalamudPlugin
|
|||
}
|
||||
}
|
||||
|
||||
private void SetupInterface( out ConfigWindow cfg, out LaunchButton btn, out WindowSystem system )
|
||||
{
|
||||
cfg = new ConfigWindow( this );
|
||||
btn = new LaunchButton( _configWindow );
|
||||
system = new WindowSystem( Name );
|
||||
system.AddWindow( _configWindow );
|
||||
Dalamud.PluginInterface.UiBuilder.Draw += system.Draw;
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += cfg.Toggle;
|
||||
}
|
||||
|
||||
private void DisposeInterface()
|
||||
{
|
||||
Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= _configWindow.Toggle;
|
||||
_launchButton.Dispose();
|
||||
_configWindow.Dispose();
|
||||
}
|
||||
|
||||
public bool Enable()
|
||||
{
|
||||
if( Config.EnableMods )
|
||||
|
|
@ -209,10 +231,11 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeInterface();
|
||||
Ipc.Dispose();
|
||||
Api.Dispose();
|
||||
SettingsInterface.Dispose();
|
||||
ObjectReloader.Dispose();
|
||||
ModFileSystem.Dispose();
|
||||
CollectionManager.Dispose();
|
||||
|
||||
Dalamud.Commands.RemoveHandler( CommandName );
|
||||
|
|
@ -251,7 +274,6 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
CollectionManager.SetCollection( collection, ModCollection.Type.Default );
|
||||
Dalamud.Chat.Print( $"Set {collection.Name} as default collection." );
|
||||
SettingsInterface.ResetDefaultCollection();
|
||||
return true;
|
||||
default:
|
||||
Dalamud.Chat.Print(
|
||||
|
|
@ -293,7 +315,7 @@ public class Penumbra : IDalamudPlugin
|
|||
}
|
||||
case "debug":
|
||||
{
|
||||
SettingsInterface.MakeDebugTabVisible();
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
case "enable":
|
||||
|
|
@ -341,7 +363,7 @@ public class Penumbra : IDalamudPlugin
|
|||
return;
|
||||
}
|
||||
|
||||
SettingsInterface.FlipVisibility();
|
||||
_configWindow.Toggle();
|
||||
}
|
||||
|
||||
// Collect all relevant files for penumbra configuration.
|
||||
|
|
@ -349,7 +371,7 @@ public class Penumbra : IDalamudPlugin
|
|||
{
|
||||
var list = new DirectoryInfo( ModCollection.CollectionDirectory ).EnumerateFiles( "*.json" ).ToList();
|
||||
list.Add( Dalamud.PluginInterface.ConfigFile );
|
||||
list.Add( new FileInfo( Mod.Manager.SortOrderFile ) );
|
||||
list.Add( new FileInfo( Mod2.Manager.ModFileSystemFile ) );
|
||||
list.Add( new FileInfo( ModCollection.Manager.ActiveCollectionFile ) );
|
||||
return list;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@
|
|||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="UI\MenuTabs\**" />
|
||||
<EmbeddedResource Remove="UI\MenuTabs\**" />
|
||||
<None Remove="UI\MenuTabs\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="tsmLogo.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ public enum ColorId
|
|||
|
||||
public static class Colors
|
||||
{
|
||||
public const uint PressEnterWarningBg = 0xFF202080;
|
||||
|
||||
public static (uint DefaultColor, string Name, string Description) Data( this ColorId color )
|
||||
=> color switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ public partial class ModFileSystemSelector
|
|||
public uint Color;
|
||||
}
|
||||
|
||||
private const StringComparison IgnoreCase = StringComparison.InvariantCultureIgnoreCase;
|
||||
private readonly IReadOnlySet< Mod > _newMods = new HashSet< Mod >();
|
||||
private LowerString _modFilter = LowerString.Empty;
|
||||
private int _filterType = -1;
|
||||
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
|
||||
private const StringComparison IgnoreCase = StringComparison.InvariantCultureIgnoreCase;
|
||||
private readonly IReadOnlySet< Mod2 > _newMods = new HashSet< Mod2 >();
|
||||
private LowerString _modFilter = LowerString.Empty;
|
||||
private int _filterType = -1;
|
||||
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
|
||||
|
||||
private void SetFilterTooltip()
|
||||
{
|
||||
|
|
@ -75,7 +75,7 @@ public partial class ModFileSystemSelector
|
|||
// Folders have default state and are filtered out on the direct string instead of the other options.
|
||||
// If any filter is set, they should be hidden by default unless their children are visible,
|
||||
// or they contain the path search string.
|
||||
protected override bool ApplyFiltersAndState( FileSystem< Mod >.IPath path, out ModState state )
|
||||
protected override bool ApplyFiltersAndState( FileSystem< Mod2 >.IPath path, out ModState state )
|
||||
{
|
||||
if( path is ModFileSystemA.Folder f )
|
||||
{
|
||||
|
|
@ -88,21 +88,21 @@ public partial class ModFileSystemSelector
|
|||
}
|
||||
|
||||
// Apply the string filters.
|
||||
private bool ApplyStringFilters( ModFileSystemA.Leaf leaf, Mod mod )
|
||||
private bool ApplyStringFilters( ModFileSystemA.Leaf leaf, Mod2 mod )
|
||||
{
|
||||
return _filterType switch
|
||||
{
|
||||
-1 => false,
|
||||
0 => !( leaf.FullName().Contains( _modFilter.Lower, IgnoreCase ) || mod.Meta.Name.Contains( _modFilter ) ),
|
||||
1 => !mod.Meta.Name.Contains( _modFilter ),
|
||||
2 => !mod.Meta.Author.Contains( _modFilter ),
|
||||
0 => !( leaf.FullName().Contains( _modFilter.Lower, IgnoreCase ) || mod.Name.Contains( _modFilter ) ),
|
||||
1 => !mod.Name.Contains( _modFilter ),
|
||||
2 => !mod.Author.Contains( _modFilter ),
|
||||
3 => !mod.LowerChangedItemsString.Contains( _modFilter ),
|
||||
_ => false, // Should never happen
|
||||
};
|
||||
}
|
||||
|
||||
// Only get the text color for a mod if no filters are set.
|
||||
private uint GetTextColor( Mod mod, ModSettings? settings, ModCollection collection )
|
||||
private uint GetTextColor( Mod2 mod, ModSettings2? settings, ModCollection collection )
|
||||
{
|
||||
if( _newMods.Contains( mod ) )
|
||||
{
|
||||
|
|
@ -130,14 +130,14 @@ public partial class ModFileSystemSelector
|
|||
: ColorId.HandledConflictMod.Value();
|
||||
}
|
||||
|
||||
private bool CheckStateFilters( Mod mod, ModSettings? settings, ModCollection collection, ref ModState state )
|
||||
private bool CheckStateFilters( Mod2 mod, ModSettings2? settings, ModCollection collection, ref ModState state )
|
||||
{
|
||||
var isNew = _newMods.Contains( mod );
|
||||
// Handle mod details.
|
||||
if( CheckFlags( mod.Resources.ModFiles.Count, ModFilter.HasNoFiles, ModFilter.HasFiles )
|
||||
|| CheckFlags( mod.Meta.FileSwaps.Count, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps )
|
||||
|| CheckFlags( mod.Resources.MetaManipulations.Count, ModFilter.HasNoMetaManipulations, ModFilter.HasMetaManipulations )
|
||||
|| CheckFlags( mod.Meta.HasGroupsWithConfig ? 1 : 0, ModFilter.HasNoConfig, ModFilter.HasConfig )
|
||||
if( CheckFlags( mod.TotalFileCount, ModFilter.HasNoFiles, ModFilter.HasFiles )
|
||||
|| CheckFlags( mod.TotalSwapCount, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps )
|
||||
|| CheckFlags( mod.TotalManipulations, ModFilter.HasNoMetaManipulations, ModFilter.HasMetaManipulations )
|
||||
|| CheckFlags( mod.HasOptions ? 1 : 0, ModFilter.HasNoConfig, ModFilter.HasConfig )
|
||||
|| CheckFlags( isNew ? 1 : 0, ModFilter.NotNew, ModFilter.IsNew ) )
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ using Penumbra.Mods;
|
|||
|
||||
namespace Penumbra.UI.Classes;
|
||||
|
||||
public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, ModFileSystemSelector.ModState >
|
||||
public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, ModFileSystemSelector.ModState >
|
||||
{
|
||||
public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty;
|
||||
public ModSettings2 SelectedSettings { get; private set; } = ModSettings2.Empty;
|
||||
public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty;
|
||||
|
||||
public ModFileSystemSelector( ModFileSystemA fileSystem, IReadOnlySet<Mod> newMods )
|
||||
public ModFileSystemSelector( ModFileSystemA fileSystem, IReadOnlySet< Mod2 > newMods )
|
||||
: base( fileSystem )
|
||||
{
|
||||
_newMods = newMods;
|
||||
|
|
@ -29,13 +29,19 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
|||
AddButton( DeleteModButton, 1000 );
|
||||
SetFilterTooltip();
|
||||
|
||||
Penumbra.CollectionManager.CollectionChanged += OnCollectionChange;
|
||||
Penumbra.CollectionManager.CollectionChanged += OnCollectionChange;
|
||||
Penumbra.CollectionManager.Current.ModSettingChanged += OnSettingChange;
|
||||
Penumbra.CollectionManager.Current.InheritanceChanged += OnInheritanceChange;
|
||||
Penumbra.ModManager.ModDiscoveryStarted += StoreCurrentSelection;
|
||||
Penumbra.ModManager.ModDiscoveryFinished += RestoreLastSelection;
|
||||
OnCollectionChange( ModCollection.Type.Current, null, Penumbra.CollectionManager.Current, null );
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
Penumbra.ModManager.ModDiscoveryStarted -= StoreCurrentSelection;
|
||||
Penumbra.ModManager.ModDiscoveryFinished -= RestoreLastSelection;
|
||||
Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange;
|
||||
Penumbra.CollectionManager.Current.InheritanceChanged -= OnInheritanceChange;
|
||||
Penumbra.CollectionManager.CollectionChanged -= OnCollectionChange;
|
||||
|
|
@ -54,11 +60,11 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
|||
protected override uint FolderLineColor
|
||||
=> ColorId.FolderLine.Value();
|
||||
|
||||
protected override void DrawLeafName( FileSystem< Mod >.Leaf leaf, in ModState state, bool selected )
|
||||
protected override void DrawLeafName( FileSystem< Mod2 >.Leaf leaf, in ModState state, bool selected )
|
||||
{
|
||||
var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
|
||||
using var c = ImRaii.PushColor( ImGuiCol.Text, state.Color );
|
||||
using var _ = ImRaii.TreeNode( leaf.Value.Meta.Name, flags );
|
||||
using var _ = ImRaii.TreeNode( leaf.Value.Name, flags );
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -126,7 +132,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
|||
}
|
||||
|
||||
// Automatic cache update functions.
|
||||
private void OnSettingChange( ModSettingChange type, int modIdx, int oldValue, string? optionName, bool inherited )
|
||||
private void OnSettingChange( ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited )
|
||||
{
|
||||
// TODO: maybe make more efficient
|
||||
SetFilterDirty();
|
||||
|
|
@ -169,13 +175,32 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
|||
{
|
||||
if( newSelection == null )
|
||||
{
|
||||
SelectedSettings = ModSettings.Empty;
|
||||
SelectedSettings = ModSettings2.Empty;
|
||||
SelectedSettingCollection = ModCollection.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
( var settings, SelectedSettingCollection ) = Penumbra.CollectionManager.Current[ newSelection.Value.Index ];
|
||||
SelectedSettings = settings ?? ModSettings.Empty;
|
||||
SelectedSettings = settings ?? ModSettings2.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep selections across rediscoveries if possible.
|
||||
private string _lastSelectedDirectory = string.Empty;
|
||||
|
||||
private void StoreCurrentSelection()
|
||||
{
|
||||
_lastSelectedDirectory = Selected?.BasePath.FullName ?? string.Empty;
|
||||
ClearSelection();
|
||||
}
|
||||
|
||||
private void RestoreLastSelection()
|
||||
{
|
||||
if( _lastSelectedDirectory.Length > 0 )
|
||||
{
|
||||
SelectedLeaf = ( ModFileSystemA.Leaf? )FileSystem.Root.GetAllDescendants( SortMode.Lexicographical )
|
||||
.FirstOrDefault( l => l is ModFileSystemA.Leaf m && m.Value.BasePath.FullName == _lastSelectedDirectory );
|
||||
_lastSelectedDirectory = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Penumbra/UI/ConfigWindow.CollectionsTab.cs
Normal file
22
Penumbra/UI/ConfigWindow.CollectionsTab.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using System.Numerics;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
public void DrawCollectionsTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem( "Collections" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child( "##CollectionsTab", -Vector2.One );
|
||||
if( !child )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Penumbra/UI/ConfigWindow.DebugTab.cs
Normal file
35
Penumbra/UI/ConfigWindow.DebugTab.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using System.Numerics;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
#if DEBUG
|
||||
private const bool DefaultVisibility = true;
|
||||
#else
|
||||
private const bool DefaultVisibility = false;
|
||||
#endif
|
||||
|
||||
public bool DebugTabVisible = DefaultVisibility;
|
||||
|
||||
public void DrawDebugTab()
|
||||
{
|
||||
if( !DebugTabVisible )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var tab = ImRaii.TabItem( "Debug" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child( "##DebugTab", -Vector2.One );
|
||||
if( !child )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Penumbra/UI/ConfigWindow.EffectiveTab.cs
Normal file
27
Penumbra/UI/ConfigWindow.EffectiveTab.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System.Numerics;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
public void DrawEffectiveChangesTab()
|
||||
{
|
||||
if( !Penumbra.Config.ShowAdvanced )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var tab = ImRaii.TabItem( "Effective Changes" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child( "##EffectiveChangesTab", -Vector2.One );
|
||||
if( !child )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,19 @@ using System.Numerics;
|
|||
using ImGuiNET;
|
||||
using Lumina.Data.Parsing;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.UI.Custom;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class SettingsInterface
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
internal static unsafe void Text( Utf8String s )
|
||||
{
|
||||
ImGuiNative.igTextUnformatted( s.Path, s.Path + s.Length );
|
||||
}
|
||||
|
||||
internal void DrawChangedItem( string name, object? data, float itemIdOffset = 0 )
|
||||
{
|
||||
var ret = ImGui.Selectable( name ) ? MouseButton.Left : MouseButton.None;
|
||||
22
Penumbra/UI/ConfigWindow.ModsTab.cs
Normal file
22
Penumbra/UI/ConfigWindow.ModsTab.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using System.Numerics;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
public void DrawModsTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem( "Mods" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child( "##ModsTab", -Vector2.One );
|
||||
if( !child )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Penumbra/UI/ConfigWindow.ResourceTab.cs
Normal file
27
Penumbra/UI/ConfigWindow.ResourceTab.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System.Numerics;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
public void DrawResourceManagerTab()
|
||||
{
|
||||
if( !DebugTabVisible )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var tab = ImRaii.TabItem( "Resource Manager" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child( "##ResourceManagerTab", -Vector2.One );
|
||||
if( !child )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
317
Penumbra/UI/ConfigWindow.SettingsTab.cs
Normal file
317
Penumbra/UI/ConfigWindow.SettingsTab.cs
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Components;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
private string _newModDirectory = string.Empty;
|
||||
|
||||
private static bool DrawPressEnterWarning( string old )
|
||||
{
|
||||
using var color = ImRaii.PushColor( ImGuiCol.Button, Colors.PressEnterWarningBg );
|
||||
var w = new Vector2( ImGui.CalcItemWidth(), 0 );
|
||||
return ImGui.Button( $"Press Enter or Click Here to Save (Current Directory: {old})", w );
|
||||
}
|
||||
|
||||
private static void DrawOpenDirectoryButton( int id, DirectoryInfo directory, bool condition )
|
||||
{
|
||||
using var _ = ImRaii.PushId( id );
|
||||
var ret = ImGui.Button( "Open Directory" );
|
||||
ImGuiUtil.HoverTooltip( "Open this directory in your configured file explorer." );
|
||||
if( ret && condition && Directory.Exists( directory.FullName ) )
|
||||
{
|
||||
Process.Start( new ProcessStartInfo( directory.FullName )
|
||||
{
|
||||
UseShellExecute = true,
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRootFolder()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
ImGui.SetNextItemWidth( _inputTextWidth.X );
|
||||
var save = ImGui.InputText( "Root Directory", ref _newModDirectory, 255, ImGuiInputTextFlags.EnterReturnsTrue );
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker( "This is where Penumbra will store your extracted mod files.\n"
|
||||
+ "TTMP files are not copied, just extracted.\n"
|
||||
+ "This directory needs to be accessible and you need write access here.\n"
|
||||
+ "It is recommended that this directory is placed on a fast hard drive, preferably an SSD.\n"
|
||||
+ "It should also be placed near the root of a logical drive - the shorter the total path to this folder, the better.\n"
|
||||
+ "Definitely do not place it in your Dalamud directory or any sub-directory thereof." );
|
||||
ImGui.SameLine();
|
||||
DrawOpenDirectoryButton( 0, Penumbra.ModManager.BasePath, Penumbra.ModManager.Valid );
|
||||
group.Dispose();
|
||||
|
||||
if( Penumbra.Config.ModDirectory == _newModDirectory || _newModDirectory.Length == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( save || DrawPressEnterWarning( Penumbra.Config.ModDirectory ) )
|
||||
{
|
||||
Penumbra.ModManager.DiscoverMods( _newModDirectory );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void DrawRediscoverButton()
|
||||
{
|
||||
if( ImGui.Button( "Rediscover Mods" ) )
|
||||
{
|
||||
Penumbra.ModManager.DiscoverMods();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker( "Force Penumbra to completely re-scan your root directory as if it was restarted." );
|
||||
}
|
||||
|
||||
private void DrawEnabledBox()
|
||||
{
|
||||
var enabled = Penumbra.Config.EnableMods;
|
||||
if( ImGui.Checkbox( "Enable Mods", ref enabled ) )
|
||||
{
|
||||
_penumbra.SetEnabled( enabled );
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawShowAdvancedBox()
|
||||
{
|
||||
var showAdvanced = Penumbra.Config.ShowAdvanced;
|
||||
if( ImGui.Checkbox( "Show Advanced Settings", ref showAdvanced ) )
|
||||
{
|
||||
Penumbra.Config.ShowAdvanced = showAdvanced;
|
||||
Penumbra.Config.Save();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker( "Enable some advanced options in this window and in the mod selector.\n"
|
||||
+ "This is required to enable manually editing any mod information." );
|
||||
}
|
||||
|
||||
private void DrawFolderSortType()
|
||||
{
|
||||
// TODO provide all options
|
||||
var foldersFirst = Penumbra.Config.SortFoldersFirst;
|
||||
if( ImGui.Checkbox( "Sort Mod-Folders Before Mods", ref foldersFirst ) )
|
||||
{
|
||||
Penumbra.Config.SortFoldersFirst = foldersFirst;
|
||||
Selector.SetFilterDirty();
|
||||
Penumbra.Config.Save();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker(
|
||||
"Prioritizes all mod-folders in the mod-selector in the Installed Mods tab so that folders come before single mods, instead of being sorted completely alphabetically" );
|
||||
}
|
||||
|
||||
private void DrawScaleModSelectorBox()
|
||||
{
|
||||
// TODO set scale
|
||||
var scaleModSelector = Penumbra.Config.ScaleModSelector;
|
||||
if( ImGui.Checkbox( "Scale Mod Selector With Window Size", ref scaleModSelector ) )
|
||||
{
|
||||
Penumbra.Config.ScaleModSelector = scaleModSelector;
|
||||
Penumbra.Config.Save();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker(
|
||||
"Instead of keeping the mod-selector in the Installed Mods tab a fixed width, this will let it scale with the total size of the Penumbra window." );
|
||||
}
|
||||
|
||||
private void DrawDisableSoundStreamingBox()
|
||||
{
|
||||
var tmp = Penumbra.Config.DisableSoundStreaming;
|
||||
if( ImGui.Checkbox( "Disable Audio Streaming", ref tmp ) && tmp != Penumbra.Config.DisableSoundStreaming )
|
||||
{
|
||||
Penumbra.Config.DisableSoundStreaming = tmp;
|
||||
Penumbra.Config.Save();
|
||||
if( tmp )
|
||||
{
|
||||
_penumbra.MusicManager.DisableStreaming();
|
||||
}
|
||||
else
|
||||
{
|
||||
_penumbra.MusicManager.EnableStreaming();
|
||||
}
|
||||
|
||||
Penumbra.ModManager.DiscoverMods();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker(
|
||||
"Disable streaming in the games audio engine.\n"
|
||||
+ "If you do not disable streaming, you can not replace sound files in the game (*.scd files), they will be ignored by Penumbra.\n\n"
|
||||
+ "Only touch this if you experience sound problems.\n"
|
||||
+ "If you toggle this, make sure no modified or to-be-modified sound file is currently playing or was recently playing, else you might crash." );
|
||||
}
|
||||
|
||||
|
||||
private void DrawEnableHttpApiBox()
|
||||
{
|
||||
var http = Penumbra.Config.EnableHttpApi;
|
||||
if( ImGui.Checkbox( "Enable HTTP API", ref http ) )
|
||||
{
|
||||
if( http )
|
||||
{
|
||||
_penumbra.CreateWebServer();
|
||||
}
|
||||
else
|
||||
{
|
||||
_penumbra.ShutdownWebServer();
|
||||
}
|
||||
|
||||
Penumbra.Config.EnableHttpApi = http;
|
||||
Penumbra.Config.Save();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker(
|
||||
"Enables other applications, e.g. Anamnesis, to use some Penumbra functions, like requesting redraws." );
|
||||
}
|
||||
|
||||
private static void DrawReloadResourceButton()
|
||||
{
|
||||
if( ImGui.Button( "Reload Resident Resources" ) )
|
||||
{
|
||||
Penumbra.ResidentResources.Reload();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker( "Reload some specific files that the game keeps in memory at all times.\n"
|
||||
+ "You usually should not need to do this." );
|
||||
}
|
||||
|
||||
private void DrawEnableFullResourceLoggingBox()
|
||||
{
|
||||
var tmp = Penumbra.Config.EnableFullResourceLogging;
|
||||
if( ImGui.Checkbox( "Enable Full Resource Logging", ref tmp ) && tmp != Penumbra.Config.EnableFullResourceLogging )
|
||||
{
|
||||
if( tmp )
|
||||
{
|
||||
Penumbra.ResourceLoader.EnableFullLogging();
|
||||
}
|
||||
else
|
||||
{
|
||||
Penumbra.ResourceLoader.DisableFullLogging();
|
||||
}
|
||||
|
||||
Penumbra.Config.EnableFullResourceLogging = tmp;
|
||||
Penumbra.Config.Save();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker( "[DEBUG] Enable the logging of all ResourceLoader events indiscriminately." );
|
||||
}
|
||||
|
||||
private void DrawEnableDebugModeBox()
|
||||
{
|
||||
var tmp = Penumbra.Config.DebugMode;
|
||||
if( ImGui.Checkbox( "Enable Debug Mode", ref tmp ) && tmp != Penumbra.Config.DebugMode )
|
||||
{
|
||||
if( tmp )
|
||||
{
|
||||
Penumbra.ResourceLoader.EnableDebug();
|
||||
}
|
||||
else
|
||||
{
|
||||
Penumbra.ResourceLoader.DisableDebug();
|
||||
}
|
||||
|
||||
Penumbra.Config.DebugMode = tmp;
|
||||
Penumbra.Config.Save();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker( "[DEBUG] Enable the Debug Tab and Resource Manager Tab as well as some additional data collection." );
|
||||
}
|
||||
|
||||
private void DrawRequestedResourceLogging()
|
||||
{
|
||||
var tmp = Penumbra.Config.EnableResourceLogging;
|
||||
if( ImGui.Checkbox( "Enable Requested Resource Logging", ref tmp ) )
|
||||
{
|
||||
_penumbra.ResourceLogger.SetState( tmp );
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker( "Log all game paths FFXIV requests to the plugin log.\n"
|
||||
+ "You can filter the logged paths for those containing the entered string or matching the regex, if the entered string compiles to a valid regex.\n"
|
||||
+ "Red boundary indicates invalid regex." );
|
||||
ImGui.SameLine();
|
||||
var tmpString = Penumbra.Config.ResourceLoggingFilter;
|
||||
using var color = ImRaii.PushColor( ImGuiCol.Border, 0xFF0000B0, !_penumbra.ResourceLogger.ValidRegex );
|
||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale, !_penumbra.ResourceLogger.ValidRegex );
|
||||
if( ImGui.InputTextWithHint( "##ResourceLogFilter", "Filter...", ref tmpString, Utf8GamePath.MaxGamePathLength ) )
|
||||
{
|
||||
_penumbra.ResourceLogger.SetFilter( tmpString );
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAdvancedSettings()
|
||||
{
|
||||
DrawRequestedResourceLogging();
|
||||
DrawDisableSoundStreamingBox();
|
||||
DrawEnableHttpApiBox();
|
||||
DrawReloadResourceButton();
|
||||
DrawEnableDebugModeBox();
|
||||
DrawEnableFullResourceLoggingBox();
|
||||
}
|
||||
|
||||
public void DrawSettingsTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem( "Settings" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child( "##SettingsTab", -Vector2.One, false );
|
||||
if( !child )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawRootFolder();
|
||||
|
||||
DrawRediscoverButton();
|
||||
|
||||
ImGui.Dummy( _verticalSpace );
|
||||
DrawEnabledBox();
|
||||
|
||||
ImGui.Dummy( _verticalSpace );
|
||||
DrawFolderSortType();
|
||||
DrawScaleModSelectorBox();
|
||||
DrawShowAdvancedBox();
|
||||
|
||||
if( Penumbra.Config.ShowAdvanced )
|
||||
{
|
||||
DrawAdvancedSettings();
|
||||
}
|
||||
|
||||
if( ImGui.CollapsingHeader( "Colors" ) )
|
||||
{
|
||||
foreach( var color in Enum.GetValues< ColorId >() )
|
||||
{
|
||||
var (defaultColor, name, description) = color.Data();
|
||||
var currentColor = Penumbra.Config.Colors.TryGetValue( color, out var current ) ? current : defaultColor;
|
||||
if( ImGuiUtil.ColorPicker( name, description, currentColor, c => Penumbra.Config.Colors[ color ] = c, defaultColor ) )
|
||||
{
|
||||
Penumbra.Config.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Penumbra/UI/ConfigWindow.cs
Normal file
68
Penumbra/UI/ConfigWindow.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public sealed partial class ConfigWindow : Window, IDisposable
|
||||
{
|
||||
private readonly Penumbra _penumbra;
|
||||
public readonly ModFileSystemSelector Selector;
|
||||
|
||||
public ConfigWindow( Penumbra penumbra )
|
||||
: base( GetLabel() )
|
||||
{
|
||||
_penumbra = penumbra;
|
||||
Selector = new ModFileSystemSelector( _penumbra.ModFileSystem, new HashSet< Mod2 >() ); // TODO
|
||||
Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||
Dalamud.PluginInterface.UiBuilder.DisableCutsceneUiHide = true;
|
||||
Dalamud.PluginInterface.UiBuilder.DisableUserUiHide = true;
|
||||
RespectCloseHotkey = true;
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
{
|
||||
MinimumSize = new Vector2( 1024, 768 ),
|
||||
MaximumSize = new Vector2( 4096, 2160 ),
|
||||
};
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
using var bar = ImRaii.TabBar( string.Empty, ImGuiTabBarFlags.NoTooltip );
|
||||
SetupSizes();
|
||||
DrawSettingsTab();
|
||||
DrawModsTab();
|
||||
DrawCollectionsTab();
|
||||
DrawEffectiveChangesTab();
|
||||
DrawDebugTab();
|
||||
DrawResourceManagerTab();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Selector.Dispose();
|
||||
}
|
||||
|
||||
private static string GetLabel()
|
||||
{
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "";
|
||||
return version.Length == 0
|
||||
? "Penumbra###PenumbraConfigWindow"
|
||||
: $"Penumbra v{version}###PenumbraConfigWindow";
|
||||
}
|
||||
|
||||
private Vector2 _verticalSpace;
|
||||
private Vector2 _inputTextWidth;
|
||||
|
||||
private void SetupSizes()
|
||||
{
|
||||
_verticalSpace = new Vector2( 0, 20f * ImGuiHelpers.GlobalScale );
|
||||
_inputTextWidth = new Vector2( 450f * ImGuiHelpers.GlobalScale, 0 );
|
||||
}
|
||||
}
|
||||
|
|
@ -5,38 +5,33 @@ using ImGuiScene;
|
|||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class SettingsInterface
|
||||
public class LaunchButton : IDisposable
|
||||
{
|
||||
private class ManageModsButton : IDisposable
|
||||
private readonly ConfigWindow _configWindow;
|
||||
private readonly TextureWrap? _icon;
|
||||
private readonly TitleScreenMenu.TitleScreenMenuEntry? _entry;
|
||||
|
||||
public LaunchButton( ConfigWindow ui )
|
||||
{
|
||||
private readonly SettingsInterface _base;
|
||||
private readonly TextureWrap? _icon;
|
||||
private readonly TitleScreenMenu.TitleScreenMenuEntry? _entry;
|
||||
_configWindow = ui;
|
||||
|
||||
public ManageModsButton( SettingsInterface ui )
|
||||
_icon = Dalamud.PluginInterface.UiBuilder.LoadImage( Path.Combine( Dalamud.PluginInterface.AssemblyLocation.DirectoryName!,
|
||||
"tsmLogo.png" ) );
|
||||
if( _icon != null )
|
||||
{
|
||||
_base = ui;
|
||||
|
||||
_icon = Dalamud.PluginInterface.UiBuilder.LoadImage( Path.Combine( Dalamud.PluginInterface.AssemblyLocation.DirectoryName!,
|
||||
"tsmLogo.png" ) );
|
||||
if( _icon != null )
|
||||
{
|
||||
_entry = Dalamud.TitleScreenMenu.AddEntry( "Manage Penumbra", _icon, OnTriggered );
|
||||
}
|
||||
_entry = Dalamud.TitleScreenMenu.AddEntry( "Manage Penumbra", _icon, OnTriggered );
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggered()
|
||||
{
|
||||
_base.FlipVisibility();
|
||||
}
|
||||
private void OnTriggered()
|
||||
=> _configWindow.Toggle();
|
||||
|
||||
public void Dispose()
|
||||
public void Dispose()
|
||||
{
|
||||
_icon?.Dispose();
|
||||
if( _entry != null )
|
||||
{
|
||||
_icon?.Dispose();
|
||||
if( _entry != null )
|
||||
{
|
||||
Dalamud.TitleScreenMenu.RemoveEntry( _entry );
|
||||
}
|
||||
Dalamud.TitleScreenMenu.RemoveEntry( _entry );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class SettingsInterface : IDisposable
|
||||
{
|
||||
private const float DefaultVerticalSpace = 20f;
|
||||
|
||||
private static readonly Vector2 AutoFillSize = new(-1, -1);
|
||||
private static readonly Vector2 ZeroVector = new(0, 0);
|
||||
|
||||
private readonly Penumbra _penumbra;
|
||||
|
||||
private readonly ManageModsButton _manageModsButton;
|
||||
private readonly SettingsMenu _menu;
|
||||
|
||||
public SettingsInterface( Penumbra penumbra )
|
||||
{
|
||||
_penumbra = penumbra;
|
||||
_manageModsButton = new ManageModsButton( this );
|
||||
_menu = new SettingsMenu( this );
|
||||
|
||||
Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||
Dalamud.PluginInterface.UiBuilder.Draw += Draw;
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += OpenConfig;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_manageModsButton.Dispose();
|
||||
_menu.InstalledTab.Selector.Cache.Dispose();
|
||||
Dalamud.PluginInterface.UiBuilder.Draw -= Draw;
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= OpenConfig;
|
||||
}
|
||||
|
||||
private void OpenConfig()
|
||||
=> _menu.Visible = true;
|
||||
|
||||
public void FlipVisibility()
|
||||
=> _menu.Visible = !_menu.Visible;
|
||||
|
||||
public void MakeDebugTabVisible()
|
||||
=> _menu.DebugTabVisible = true;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
_menu.Draw();
|
||||
}
|
||||
|
||||
private void ReloadMods()
|
||||
{
|
||||
_menu.InstalledTab.Selector.ClearSelection();
|
||||
Penumbra.ModManager.DiscoverMods( Penumbra.Config.ModDirectory );
|
||||
_menu.InstalledTab.Selector.Cache.TriggerListReset();
|
||||
}
|
||||
|
||||
public void ResetDefaultCollection()
|
||||
=> _menu.CollectionsTab.UpdateDefaultIndex();
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Penumbra.UI.Custom;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class SettingsInterface
|
||||
{
|
||||
private class SettingsMenu
|
||||
{
|
||||
public static float InputTextWidth
|
||||
=> 450 * ImGuiHelpers.GlobalScale;
|
||||
|
||||
private const string PenumbraSettingsLabel = "PenumbraSettings";
|
||||
|
||||
public static readonly Vector2 MinSettingsSize = new(800, 450);
|
||||
public static readonly Vector2 MaxSettingsSize = new(69420, 42069);
|
||||
|
||||
private readonly SettingsInterface _base;
|
||||
private readonly TabSettings _settingsTab;
|
||||
private readonly TabImport _importTab;
|
||||
private readonly TabBrowser _browserTab;
|
||||
private readonly TabEffective _effectiveTab;
|
||||
private readonly TabChangedItems _changedItems;
|
||||
internal readonly TabCollections CollectionsTab;
|
||||
internal readonly TabInstalled InstalledTab;
|
||||
|
||||
public SettingsMenu( SettingsInterface ui )
|
||||
{
|
||||
_base = ui;
|
||||
_settingsTab = new TabSettings( _base );
|
||||
_importTab = new TabImport( _base );
|
||||
_browserTab = new TabBrowser();
|
||||
InstalledTab = new TabInstalled( _base, _importTab.NewMods );
|
||||
CollectionsTab = new TabCollections( InstalledTab.Selector );
|
||||
_effectiveTab = new TabEffective();
|
||||
_changedItems = new TabChangedItems( _base );
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private const bool DefaultVisibility = true;
|
||||
#else
|
||||
private const bool DefaultVisibility = false;
|
||||
#endif
|
||||
public bool Visible = DefaultVisibility;
|
||||
public bool DebugTabVisible = DefaultVisibility;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
if( !Visible )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.SetNextWindowSizeConstraints( MinSettingsSize, MaxSettingsSize );
|
||||
#if DEBUG
|
||||
var ret = ImGui.Begin( _base._penumbra.PluginDebugTitleStr, ref Visible );
|
||||
#else
|
||||
var ret = ImGui.Begin( _base._penumbra.Name, ref Visible );
|
||||
#endif
|
||||
using var raii = ImGuiRaii.DeferredEnd( ImGui.End );
|
||||
if( !ret )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.BeginTabBar( PenumbraSettingsLabel );
|
||||
raii.Push( ImGui.EndTabBar );
|
||||
|
||||
_settingsTab.Draw();
|
||||
CollectionsTab.Draw();
|
||||
_importTab.Draw();
|
||||
|
||||
if( Penumbra.ModManager.Valid && !_importTab.IsImporting() )
|
||||
{
|
||||
_browserTab.Draw();
|
||||
InstalledTab.Draw();
|
||||
_changedItems.Draw();
|
||||
if( Penumbra.Config.ShowAdvanced )
|
||||
{
|
||||
_effectiveTab.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
if( DebugTabVisible )
|
||||
{
|
||||
_base.DrawDebugTab();
|
||||
_base.DrawResourceManagerTab();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,11 +74,11 @@ public static class ModelChanger
|
|||
}
|
||||
}
|
||||
|
||||
public static bool ChangeModMaterials( Mods.Mod mod, string from, string to )
|
||||
public static bool ChangeModMaterials( Mod2 mod, string from, string to )
|
||||
{
|
||||
if( ValidStrings( from, to ) )
|
||||
{
|
||||
return mod.Resources.ModFiles
|
||||
return mod.AllFiles
|
||||
.Where( f => f.Extension.Equals( ".mdl", StringComparison.InvariantCultureIgnoreCase ) )
|
||||
.All( file => ChangeMtrl( file, from, to ) >= 0 );
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue