Fixes regarding collection settings, change self-conflict behaviour by iterating through options instead of through files, button to clean up collection settings.

This commit is contained in:
Ottermandias 2021-09-16 16:29:20 +02:00
parent ac19a599ed
commit d7214cd851
8 changed files with 279 additions and 56 deletions

View file

@ -44,7 +44,7 @@ namespace Penumbra.Meta
private readonly MetaDefaults _default; private readonly MetaDefaults _default;
private readonly DirectoryInfo _dir; private readonly DirectoryInfo _dir;
private readonly ResidentResources _resourceManagement; private readonly ResidentResources _resourceManagement;
private readonly Dictionary< GamePath, FileInfo > _resolvedFiles; private readonly Dictionary< GamePath, FileInfo > _resolvedFiles;
private readonly Dictionary< MetaManipulation, Mod.Mod > _currentManipulations = new(); private readonly Dictionary< MetaManipulation, Mod.Mod > _currentManipulations = new();
@ -53,6 +53,10 @@ namespace Penumbra.Meta
public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations
=> _currentManipulations.Select( kvp => ( kvp.Key, kvp.Value ) ); => _currentManipulations.Select( kvp => ( kvp.Key, kvp.Value ) );
public IEnumerable< (GamePath, FileInfo) > Files
=> _currentFiles.Where( kvp => kvp.Value.CurrentFile != null )
.Select( kvp => ( kvp.Key, kvp.Value.CurrentFile! ) );
public int Count public int Count
=> _currentManipulations.Count; => _currentManipulations.Count;

View file

@ -109,7 +109,7 @@ namespace Penumbra.Mods
} }
} }
public void UpdateSettings() public void UpdateSettings( bool forceSave )
{ {
if( Cache == null ) if( Cache == null )
{ {
@ -122,7 +122,7 @@ namespace Penumbra.Mods
changes |= mod.FixSettings(); changes |= mod.FixSettings();
} }
if( changes ) if( forceSave || changes )
{ {
Save(); Save();
} }
@ -133,7 +133,7 @@ namespace Penumbra.Mods
PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}] [{IsActiveCollection}]", Name, PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}] [{IsActiveCollection}]", Name,
withMetaManipulations, activeCollection ); withMetaManipulations, activeCollection );
Cache ??= new ModCollectionCache( Name, modDir ); Cache ??= new ModCollectionCache( Name, modDir );
UpdateSettings(); UpdateSettings( false );
Cache.CalculateEffectiveFileList(); Cache.CalculateEffectiveFileList();
if( withMetaManipulations ) if( withMetaManipulations )
{ {

View file

@ -1,11 +1,16 @@
using System; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Lumina.Data.Parsing; using System.Runtime.InteropServices;
using Dalamud.Logging;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Mod; using Penumbra.Mod;
using Penumbra.Structs;
using Penumbra.Util;
namespace Penumbra.Mods namespace Penumbra.Mods
{ {
@ -13,42 +18,206 @@ namespace Penumbra.Mods
// It will only be setup if a collection gets activated in any way. // It will only be setup if a collection gets activated in any way.
public class ModCollectionCache public class ModCollectionCache
{ {
// Shared caches to avoid allocations.
private static readonly BitArray FileSeen = new( 256 );
private static readonly Dictionary< GamePath, Mod.Mod > RegisteredFiles = new( 256 );
public readonly Dictionary< string, Mod.Mod > AvailableMods = new(); public readonly Dictionary< string, Mod.Mod > AvailableMods = new();
public readonly Dictionary< GamePath, FileInfo > ResolvedFiles = new(); public readonly Dictionary< GamePath, FileInfo > ResolvedFiles = new();
public readonly Dictionary< GamePath, GamePath > SwappedFiles = new(); public readonly Dictionary< GamePath, GamePath > SwappedFiles = new();
public readonly HashSet< FileInfo > MissingFiles = new();
public readonly MetaManager MetaManipulations; public readonly MetaManager MetaManipulations;
public ModCollectionCache( string collectionName, DirectoryInfo tempDir ) public ModCollectionCache( string collectionName, DirectoryInfo tempDir )
=> MetaManipulations = new MetaManager( collectionName, ResolvedFiles, tempDir ); => MetaManipulations = new MetaManager( collectionName, ResolvedFiles, tempDir );
private void AddFiles( Dictionary< GamePath, Mod.Mod > registeredFiles, Mod.Mod mod ) private static void ResetFileSeen( int size )
{ {
foreach( var file in mod.Data.Resources.ModFiles ) if( size < FileSeen.Length )
{ {
var gamePaths = mod.GetFiles( file ); FileSeen.Length = size;
foreach( var gamePath in gamePaths ) FileSeen.SetAll( false );
}
else
{
FileSeen.SetAll( false );
FileSeen.Length = size;
}
}
public void CalculateEffectiveFileList()
{
ResolvedFiles.Clear();
SwappedFiles.Clear();
MissingFiles.Clear();
RegisteredFiles.Clear();
foreach( var mod in AvailableMods.Values
.Where( m => m.Settings.Enabled )
.OrderByDescending( m => m.Settings.Priority ) )
{
mod.Cache.ClearFileConflicts();
AddFiles( mod );
AddSwaps( mod );
}
AddMetaFiles();
}
private void AddFiles( Mod.Mod mod )
{
ResetFileSeen( mod.Data.Resources.ModFiles.Count );
// Iterate in reverse so that later groups take precedence before earlier ones.
foreach( var group in mod.Data.Meta.Groups.Values.Reverse() )
{
switch( group.SelectionType )
{ {
if( !registeredFiles.TryGetValue( gamePath, out var oldMod ) ) case SelectType.Single:
AddFilesForSingle( group, mod );
break;
case SelectType.Multi:
AddFilesForMulti( group, mod );
break;
default: throw new InvalidEnumArgumentException();
}
}
AddRemainingFiles( mod );
}
private void AddFile( Mod.Mod mod, GamePath gamePath, FileInfo file )
{
if( !RegisteredFiles.TryGetValue( gamePath, out var oldMod ) )
{
RegisteredFiles.Add( gamePath, mod );
ResolvedFiles[ gamePath ] = file;
}
else
{
mod.Cache.AddConflict( oldMod, gamePath );
}
}
private void AddMissingFile( FileInfo file )
{
switch( file.Extension.ToLowerInvariant() )
{
case ".meta":
case ".rgsp":
return;
default:
MissingFiles.Add( file );
return;
}
}
private void AddPathsForOption( Option option, Mod.Mod mod, bool enabled )
{
foreach( var (file, paths) in option.OptionFiles )
{
var fullPath = Path.Combine( mod.Data.BasePath.FullName, file );
var idx = mod.Data.Resources.ModFiles.IndexOf( f => f.FullName == fullPath );
if( idx < 0 )
{
AddMissingFile( new FileInfo( fullPath ) );
continue;
}
var registeredFile = mod.Data.Resources.ModFiles[ idx ];
registeredFile.Refresh();
if( !registeredFile.Exists )
{
AddMissingFile( registeredFile );
continue;
}
FileSeen.Set( idx, true );
if( enabled )
{
foreach( var path in paths )
{ {
registeredFiles.Add( gamePath, mod ); AddFile( mod, path, registeredFile );
ResolvedFiles[ gamePath ] = file;
}
else
{
mod.Cache.AddConflict( oldMod, gamePath );
} }
} }
} }
} }
private void AddSwaps( Dictionary< GamePath, Mod.Mod > registeredFiles, Mod.Mod mod ) private void AddFilesForSingle( OptionGroup singleGroup, Mod.Mod mod )
{
Debug.Assert( singleGroup.SelectionType == SelectType.Single );
if( !mod.Settings.Settings.TryGetValue( singleGroup.GroupName, out var setting ) )
{
setting = 0;
}
for( var i = 0; i < singleGroup.Options.Count; ++i )
{
AddPathsForOption( singleGroup.Options[ i ], mod, setting == i );
}
}
private void AddFilesForMulti( OptionGroup multiGroup, Mod.Mod mod )
{
Debug.Assert( multiGroup.SelectionType == SelectType.Multi );
if( !mod.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, ( setting & ( 1 << i ) ) != 0 );
}
}
private void AddRemainingFiles( Mod.Mod mod )
{
for( var i = 0; i < mod.Data.Resources.ModFiles.Count; ++i )
{
if( FileSeen.Get( i ) )
{
continue;
}
var file = mod.Data.Resources.ModFiles[ i ];
file.Refresh();
if( file.Exists )
{
AddFile( mod, new GamePath( file, mod.Data.BasePath ), file );
}
else
{
MissingFiles.Add( file );
}
}
}
private void AddMetaFiles()
{
foreach( var (gamePath, file) in MetaManipulations.Files )
{
if( RegisteredFiles.TryGetValue( gamePath, out var mod ) )
{
PluginLog.Warning(
$"The meta manipulation file {gamePath} was already completely replaced by {mod.Data.Meta.Name}. This is probably a mistake. Using the custom file {file.FullName}." );
}
ResolvedFiles[ gamePath ] = file;
}
}
private void AddSwaps( Mod.Mod mod )
{ {
foreach( var swap in mod.Data.Meta.FileSwaps ) foreach( var swap in mod.Data.Meta.FileSwaps )
{ {
if( !registeredFiles.TryGetValue( swap.Key, out var oldMod ) ) if( !RegisteredFiles.TryGetValue( swap.Key, out var oldMod ) )
{ {
registeredFiles.Add( swap.Key, mod ); RegisteredFiles.Add( swap.Key, mod );
SwappedFiles.Add( swap.Key, swap.Value ); SwappedFiles.Add( swap.Key, swap.Value );
} }
else else
@ -86,22 +255,6 @@ namespace Penumbra.Mods
MetaManipulations.WriteNewFiles(); MetaManipulations.WriteNewFiles();
} }
public void CalculateEffectiveFileList()
{
ResolvedFiles.Clear();
SwappedFiles.Clear();
var registeredFiles = new Dictionary< GamePath, Mod.Mod >();
foreach( var mod in AvailableMods.Values
.Where( m => m.Settings.Enabled )
.OrderByDescending( m => m.Settings.Priority ) )
{
mod.Cache.ClearFileConflicts();
AddFiles( registeredFiles, mod );
AddSwaps( registeredFiles, mod );
}
}
public void RemoveMod( DirectoryInfo basePath ) public void RemoveMod( DirectoryInfo basePath )
{ {
if( AvailableMods.TryGetValue( basePath.Name, out var mod ) ) if( AvailableMods.TryGetValue( basePath.Name, out var mod ) )
@ -121,7 +274,7 @@ namespace Penumbra.Mods
private class PriorityComparer : IComparer< Mod.Mod > private class PriorityComparer : IComparer< Mod.Mod >
{ {
public int Compare( Mod.Mod? x, Mod.Mod? y ) public int Compare( Mod.Mod? x, Mod.Mod? y )
=> (x?.Settings.Priority ?? 0).CompareTo( y?.Settings.Priority ?? 0 ); => ( x?.Settings.Priority ?? 0 ).CompareTo( y?.Settings.Priority ?? 0 );
} }
private static readonly PriorityComparer Comparer = new(); private static readonly PriorityComparer Comparer = new();

View file

@ -43,12 +43,14 @@ namespace Penumbra.Structs
private bool ApplySingleGroupFiles( RelPath relPath, int selection, HashSet< GamePath > paths ) private bool ApplySingleGroupFiles( RelPath relPath, int selection, HashSet< GamePath > paths )
{ {
// Selection contains the path, merge all GamePaths for this config.
if( Options[ selection ].OptionFiles.TryGetValue( relPath, out var groupPaths ) ) if( Options[ selection ].OptionFiles.TryGetValue( relPath, out var groupPaths ) )
{ {
paths.UnionWith( groupPaths ); paths.UnionWith( groupPaths );
return true; 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 ) for( var i = 0; i < Options.Count; ++i )
{ {
if( i == selection ) if( i == selection )

View file

@ -1,3 +1,4 @@
using System.Numerics;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Windows.Forms; using System.Windows.Forms;
using Dalamud.Interface; using Dalamud.Interface;
@ -53,6 +54,15 @@ namespace Penumbra.UI.Custom
} }
} }
public static partial class ImGuiCustom
{
public static bool DisableButton( string label, bool condition )
{
using var alpha = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, !condition );
return ImGui.Button( label ) && condition;
}
}
public static partial class ImGuiCustom public static partial class ImGuiCustom
{ {
public static void PrintIcon( FontAwesomeIcon icon ) public static void PrintIcon( FontAwesomeIcon icon )

View file

@ -87,13 +87,25 @@ namespace Penumbra.UI
{ {
if( _manager.Collections.AddCollection( _newCollectionName, settings ) ) if( _manager.Collections.AddCollection( _newCollectionName, settings ) )
{ {
_manager.Collections.SetCurrentCollection( _manager.Collections.Collections[ _newCollectionName ] );
UpdateNames(); UpdateNames();
SetCurrentCollection( _manager.Collections.Collections[_newCollectionName], true );
} }
_newCollectionName = string.Empty; _newCollectionName = string.Empty;
} }
private void DrawCleanCollectionButton()
{
if( ImGui.Button( "Clean Settings" ) )
{
var changes = ModFunctions.CleanUpCollection( _manager.Collections.CurrentCollection.Settings,
_manager.BasePath.EnumerateDirectories() );
_manager.Collections.CurrentCollection.UpdateSettings( forceSave: changes );
}
ImGuiCustom.HoverTooltip( "Remove all stored settings for mods not currently available and fix invalid settings.\nUse at own risk." );
}
private void DrawNewCollectionInput() private void DrawNewCollectionInput()
{ {
ImGui.InputTextWithHint( "##New Collection", "New Collection", ref _newCollectionName, 64 ); ImGui.InputTextWithHint( "##New Collection", "New Collection", ref _newCollectionName, 64 );
@ -113,26 +125,31 @@ namespace Penumbra.UI
style.Pop(); style.Pop();
if( _manager.Collections.Collections.Count > 1 var deleteCondition = _manager.Collections.Collections.Count > 1
&& _manager.Collections.CurrentCollection.Name != ModCollection.DefaultCollection ) && _manager.Collections.CurrentCollection.Name != ModCollection.DefaultCollection;
ImGui.SameLine();
if( ImGuiCustom.DisableButton( "Delete Current Collection", deleteCondition) )
{
_manager.Collections.RemoveCollection( _manager.Collections.CurrentCollection.Name );
SetCurrentCollection( _manager.Collections.CurrentCollection, true );
UpdateNames();
}
if( Penumbra.Config.ShowAdvanced )
{ {
ImGui.SameLine(); ImGui.SameLine();
if( ImGui.Button( "Delete Current Collection" ) ) DrawCleanCollectionButton();
{
_manager.Collections.RemoveCollection( _manager.Collections.CurrentCollection.Name );
UpdateNames();
}
} }
} }
private void SetCurrentCollection( int idx ) private void SetCurrentCollection( int idx, bool force )
{ {
if( idx == _currentCollectionIndex ) if( !force && idx == _currentCollectionIndex )
{ {
return; return;
} }
_manager.Collections.SetCurrentCollection( _collections[ idx + 1 ] ); _manager.Collections.SetCurrentCollection( _collections[idx + 1] );
_currentCollectionIndex = idx; _currentCollectionIndex = idx;
_selector.Cache.TriggerListReset(); _selector.Cache.TriggerListReset();
if( _selector.Mod != null ) if( _selector.Mod != null )
@ -141,12 +158,12 @@ namespace Penumbra.UI
} }
} }
public void SetCurrentCollection( ModCollection collection ) public void SetCurrentCollection( ModCollection collection, bool force = false )
{ {
var idx = Array.IndexOf( _collections, collection ) - 1; var idx = Array.IndexOf( _collections, collection ) - 1;
if( idx >= 0 ) if( idx >= 0 )
{ {
SetCurrentCollection( idx ); SetCurrentCollection( idx, force );
} }
} }
@ -159,7 +176,7 @@ namespace Penumbra.UI
if( combo ) if( combo )
{ {
SetCurrentCollection( index ); SetCurrentCollection( index, false );
} }
} }
@ -205,18 +222,14 @@ namespace Penumbra.UI
{ {
ImGui.InputTextWithHint( "##New Character", "New Character Name", ref _newCharacterName, 32 ); ImGui.InputTextWithHint( "##New Character", "New Character Name", ref _newCharacterName, 32 );
using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, _newCharacterName.Length == 0 );
ImGui.SameLine(); ImGui.SameLine();
if( ImGui.Button( "Create New Character Collection" ) && _newCharacterName.Length > 0 ) if( ImGuiCustom.DisableButton( "Create New Character Collection", _newCharacterName.Length > 0 ))
{ {
_manager.Collections.CreateCharacterCollection( _newCharacterName ); _manager.Collections.CreateCharacterCollection( _newCharacterName );
_currentCharacterIndices[ _newCharacterName ] = 0; _currentCharacterIndices[ _newCharacterName ] = 0;
_newCharacterName = string.Empty; _newCharacterName = string.Empty;
} }
style.Pop();
ImGuiCustom.HoverTooltip( ImGuiCustom.HoverTooltip(
"A character collection will be used whenever you manually redraw a character with the Name you have set up.\n" "A character collection will be used whenever you manually redraw a character with the Name you have set up.\n"
+ "If you enable automatic character redraws in the Settings tab, penumbra will try to use Character collections for corresponding characters automatically.\n" ); + "If you enable automatic character redraws in the Settings tab, penumbra will try to use Character collections for corresponding characters automatically.\n" );

View file

@ -342,6 +342,34 @@ namespace Penumbra.UI
} }
} }
private void DrawDebugTabMissingFiles()
{
if( !ImGui.CollapsingHeader( "Missing Files##Debug" ) )
{
return;
}
var manager = Service<ModManager>.Get();
var cache = manager.Collections.CurrentCollection.Cache;
if( cache == null || !ImGui.BeginTable( "##MissingFilesDebugList", 1, ImGuiTableFlags.RowBg, -Vector2.UnitX))
{
return;
}
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable );
foreach( var file in cache.MissingFiles )
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
if( ImGui.Selectable( file.FullName ) )
{
ImGui.SetClipboardText( file.FullName );
}
ImGuiCustom.HoverTooltip( "Click to copy to clipboard." );
}
}
private void DrawDebugTab() private void DrawDebugTab()
{ {
if( !ImGui.BeginTabItem( "Debug Tab" ) ) if( !ImGui.BeginTabItem( "Debug Tab" ) )
@ -353,6 +381,8 @@ namespace Penumbra.UI
DrawDebugTabGeneral(); DrawDebugTabGeneral();
ImGui.NewLine(); ImGui.NewLine();
DrawDebugTabMissingFiles();
ImGui.NewLine();
DrawDebugTabRedraw(); DrawDebugTabRedraw();
ImGui.NewLine(); ImGui.NewLine();
DrawDebugTabPlayers(); DrawDebugTabPlayers();

View file

@ -72,5 +72,16 @@ namespace Penumbra.Util
array.Swap( idx1, idx2 ); array.Swap( idx1, idx2 );
} }
public static int IndexOf< T >( this IList< T > array, Func< T, bool > predicate )
{
for( var i = 0; i < array.Count; ++i )
{
if( predicate.Invoke( array[ i ] ) )
return i;
}
return -1;
}
} }
} }