mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
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:
parent
ac19a599ed
commit
d7214cd851
8 changed files with 279 additions and 56 deletions
|
|
@ -44,7 +44,7 @@ namespace Penumbra.Meta
|
|||
|
||||
private readonly MetaDefaults _default;
|
||||
private readonly DirectoryInfo _dir;
|
||||
private readonly ResidentResources _resourceManagement;
|
||||
private readonly ResidentResources _resourceManagement;
|
||||
private readonly Dictionary< GamePath, FileInfo > _resolvedFiles;
|
||||
|
||||
private readonly Dictionary< MetaManipulation, Mod.Mod > _currentManipulations = new();
|
||||
|
|
@ -53,6 +53,10 @@ namespace Penumbra.Meta
|
|||
public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations
|
||||
=> _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
|
||||
=> _currentManipulations.Count;
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ namespace Penumbra.Mods
|
|||
}
|
||||
}
|
||||
|
||||
public void UpdateSettings()
|
||||
public void UpdateSettings( bool forceSave )
|
||||
{
|
||||
if( Cache == null )
|
||||
{
|
||||
|
|
@ -122,7 +122,7 @@ namespace Penumbra.Mods
|
|||
changes |= mod.FixSettings();
|
||||
}
|
||||
|
||||
if( changes )
|
||||
if( forceSave || changes )
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
|
@ -133,7 +133,7 @@ namespace Penumbra.Mods
|
|||
PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}] [{IsActiveCollection}]", Name,
|
||||
withMetaManipulations, activeCollection );
|
||||
Cache ??= new ModCollectionCache( Name, modDir );
|
||||
UpdateSettings();
|
||||
UpdateSettings( false );
|
||||
Cache.CalculateEffectiveFileList();
|
||||
if( withMetaManipulations )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Lumina.Data.Parsing;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods
|
||||
{
|
||||
|
|
@ -13,42 +18,206 @@ namespace Penumbra.Mods
|
|||
// It will only be setup if a collection gets activated in any way.
|
||||
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< GamePath, FileInfo > ResolvedFiles = new();
|
||||
public readonly Dictionary< GamePath, GamePath > SwappedFiles = new();
|
||||
public readonly HashSet< FileInfo > MissingFiles = new();
|
||||
public readonly MetaManager MetaManipulations;
|
||||
|
||||
public ModCollectionCache( string collectionName, DirectoryInfo 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 );
|
||||
foreach( var gamePath in gamePaths )
|
||||
FileSeen.Length = size;
|
||||
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 );
|
||||
ResolvedFiles[ gamePath ] = file;
|
||||
}
|
||||
else
|
||||
{
|
||||
mod.Cache.AddConflict( oldMod, gamePath );
|
||||
AddFile( mod, path, registeredFile );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
else
|
||||
|
|
@ -86,22 +255,6 @@ namespace Penumbra.Mods
|
|||
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 )
|
||||
{
|
||||
if( AvailableMods.TryGetValue( basePath.Name, out var mod ) )
|
||||
|
|
@ -121,7 +274,7 @@ namespace Penumbra.Mods
|
|||
private class PriorityComparer : IComparer< Mod.Mod >
|
||||
{
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -43,12 +43,14 @@ namespace Penumbra.Structs
|
|||
|
||||
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 ) )
|
||||
{
|
||||
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 )
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Numerics;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Windows.Forms;
|
||||
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 void PrintIcon( FontAwesomeIcon icon )
|
||||
|
|
|
|||
|
|
@ -87,13 +87,25 @@ namespace Penumbra.UI
|
|||
{
|
||||
if( _manager.Collections.AddCollection( _newCollectionName, settings ) )
|
||||
{
|
||||
_manager.Collections.SetCurrentCollection( _manager.Collections.Collections[ _newCollectionName ] );
|
||||
UpdateNames();
|
||||
SetCurrentCollection( _manager.Collections.Collections[_newCollectionName], true );
|
||||
}
|
||||
|
||||
_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()
|
||||
{
|
||||
ImGui.InputTextWithHint( "##New Collection", "New Collection", ref _newCollectionName, 64 );
|
||||
|
|
@ -113,26 +125,31 @@ namespace Penumbra.UI
|
|||
|
||||
style.Pop();
|
||||
|
||||
if( _manager.Collections.Collections.Count > 1
|
||||
&& _manager.Collections.CurrentCollection.Name != ModCollection.DefaultCollection )
|
||||
var deleteCondition = _manager.Collections.Collections.Count > 1
|
||||
&& _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();
|
||||
if( ImGui.Button( "Delete Current Collection" ) )
|
||||
{
|
||||
_manager.Collections.RemoveCollection( _manager.Collections.CurrentCollection.Name );
|
||||
UpdateNames();
|
||||
}
|
||||
DrawCleanCollectionButton();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetCurrentCollection( int idx )
|
||||
private void SetCurrentCollection( int idx, bool force )
|
||||
{
|
||||
if( idx == _currentCollectionIndex )
|
||||
if( !force && idx == _currentCollectionIndex )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_manager.Collections.SetCurrentCollection( _collections[ idx + 1 ] );
|
||||
_manager.Collections.SetCurrentCollection( _collections[idx + 1] );
|
||||
_currentCollectionIndex = idx;
|
||||
_selector.Cache.TriggerListReset();
|
||||
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;
|
||||
if( idx >= 0 )
|
||||
{
|
||||
SetCurrentCollection( idx );
|
||||
SetCurrentCollection( idx, force );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +176,7 @@ namespace Penumbra.UI
|
|||
|
||||
if( combo )
|
||||
{
|
||||
SetCurrentCollection( index );
|
||||
SetCurrentCollection( index, false );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -205,18 +222,14 @@ namespace Penumbra.UI
|
|||
{
|
||||
ImGui.InputTextWithHint( "##New Character", "New Character Name", ref _newCharacterName, 32 );
|
||||
|
||||
using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, _newCharacterName.Length == 0 );
|
||||
|
||||
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 );
|
||||
_currentCharacterIndices[ _newCharacterName ] = 0;
|
||||
_newCharacterName = string.Empty;
|
||||
}
|
||||
|
||||
style.Pop();
|
||||
|
||||
ImGuiCustom.HoverTooltip(
|
||||
"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" );
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
if( !ImGui.BeginTabItem( "Debug Tab" ) )
|
||||
|
|
@ -353,6 +381,8 @@ namespace Penumbra.UI
|
|||
|
||||
DrawDebugTabGeneral();
|
||||
ImGui.NewLine();
|
||||
DrawDebugTabMissingFiles();
|
||||
ImGui.NewLine();
|
||||
DrawDebugTabRedraw();
|
||||
ImGui.NewLine();
|
||||
DrawDebugTabPlayers();
|
||||
|
|
|
|||
|
|
@ -72,5 +72,16 @@ namespace Penumbra.Util
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue