Setup the CollectionsManager and introduced first version of character based collection loading and automatic redrawing of characters.

This commit is contained in:
Ottermandias 2021-06-26 15:23:11 +02:00
parent b0d14751cd
commit 3c9c892a83
15 changed files with 704 additions and 299 deletions

View file

@ -19,7 +19,7 @@ namespace Penumbra.API
public object? GetMods() public object? GetMods()
{ {
var modManager = Service< ModManager >.Get(); var modManager = Service< ModManager >.Get();
return modManager.CurrentCollection.Cache?.AvailableMods.Select( x => new return modManager.Collections.CurrentCollection.Cache?.AvailableMods.Select( x => new
{ {
x.Settings.Enabled, x.Settings.Enabled,
x.Settings.Priority, x.Settings.Priority,
@ -39,7 +39,7 @@ namespace Penumbra.API
public object GetFiles() public object GetFiles()
{ {
var modManager = Service< ModManager >.Get(); var modManager = Service< ModManager >.Get();
return modManager.CurrentCollection.Cache?.ResolvedFiles.ToDictionary( return modManager.Collections.CurrentCollection.Cache?.ResolvedFiles.ToDictionary(
o => ( string )o.Key, o => ( string )o.Key,
o => o.Value.FullName o => o.Value.FullName
) )

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Dalamud.Configuration; using Dalamud.Configuration;
using Dalamud.Plugin; using Dalamud.Plugin;
using Penumbra.Util; using Penumbra.Util;
@ -24,7 +25,8 @@ namespace Penumbra
public string ModDirectory { get; set; } = @"D:/ffxiv/fs_mods/"; public string ModDirectory { get; set; } = @"D:/ffxiv/fs_mods/";
public string CurrentCollection { get; set; } = "Default"; public string CurrentCollection { get; set; } = "Default";
public string ForcedCollection { get; set; } = "";
public Dictionary< string, string > CharacterCollections { get; set; } = new();
public bool InvertModListOrder { internal get; set; } public bool InvertModListOrder { internal get; set; }
public static Configuration Load( DalamudPluginInterface pi ) public static Configuration Load( DalamudPluginInterface pi )

View file

@ -23,6 +23,7 @@ namespace Penumbra.Interop
{ {
private const int RenderModeOffset = 0x0104; private const int RenderModeOffset = 0x0104;
private const int ModelInvisibilityFlag = 0b10; private const int ModelInvisibilityFlag = 0b10;
private const int ModelIsLoadingFlag = 0x800;
private const int UnloadAllRedrawDelay = 250; private const int UnloadAllRedrawDelay = 250;
private const int NpcActorId = -536870912; private const int NpcActorId = -536870912;
@ -34,6 +35,7 @@ namespace Penumbra.Interop
private bool _changedSettings = false; private bool _changedSettings = false;
private int _currentActorId = -1; private int _currentActorId = -1;
private string? _currentActorName = null; private string? _currentActorName = null;
private Redraw _currentActorRedraw = Redraw.Unload;
public ActorRefresher( DalamudPluginInterface pi, ModManager mods ) public ActorRefresher( DalamudPluginInterface pi, ModManager mods )
{ {
@ -42,10 +44,19 @@ namespace Penumbra.Interop
} }
private void ChangeSettings() private void ChangeSettings()
=> _changedSettings = true; {
if( _currentActorName != null && _mods.Collections.CharacterCollection.TryGetValue( _currentActorName, out var collection ) )
{
_changedSettings = true;
_mods.Collections.ActiveCollection = collection;
}
}
private void RestoreSettings() private void RestoreSettings()
=> _changedSettings = false; {
_mods.Collections.ActiveCollection = _mods.Collections.CurrentCollection;
_changedSettings = false;
}
private static unsafe void WriteInvisible( IntPtr renderPtr ) private static unsafe void WriteInvisible( IntPtr renderPtr )
{ {
@ -55,6 +66,16 @@ namespace Penumbra.Interop
} }
} }
private static unsafe bool StillLoading( IntPtr renderPtr )
{
if( renderPtr != IntPtr.Zero )
{
return *( int* )renderPtr != 0;
}
return false;
}
private static unsafe void WriteVisible( IntPtr renderPtr ) private static unsafe void WriteVisible( IntPtr renderPtr )
{ {
if( renderPtr != IntPtr.Zero ) if( renderPtr != IntPtr.Zero )
@ -81,20 +102,42 @@ namespace Penumbra.Interop
private Actor? FindCurrentActor() private Actor? FindCurrentActor()
=> _pi.ClientState.Actors.FirstOrDefault( CheckActor ); => _pi.ClientState.Actors.FirstOrDefault( CheckActor );
private void ChangeSettingsAndUndraw() private void PopActor()
{ {
if( _actorIds.Count > 0 ) if( _actorIds.Count > 0 )
{ {
var (id, name, s) = _actorIds.Dequeue(); var (id, name, s) = _actorIds.Dequeue();
_currentActorName = name; _currentActorName = name;
_currentActorId = id; _currentActorId = id;
_currentActorRedraw = s;
var actor = FindCurrentActor(); var actor = FindCurrentActor();
if( actor == null ) if( actor == null )
{ {
return; return;
} }
switch( s ) ++_currentFrame;
}
else
{
_pi.Framework.OnUpdateEvent -= OnUpdateEvent;
}
}
private void ApplySettingsOrRedraw()
{
var actor = FindCurrentActor();
if( actor == null )
{
return;
}
if( StillLoading( actor.Address + RenderModeOffset ) )
{
return;
}
switch( _currentActorRedraw )
{ {
case Redraw.Unload: case Redraw.Unload:
WriteInvisible( actor.Address + RenderModeOffset ); WriteInvisible( actor.Address + RenderModeOffset );
@ -130,13 +173,8 @@ namespace Penumbra.Interop
default: throw new InvalidEnumArgumentException(); default: throw new InvalidEnumArgumentException();
} }
} }
else
{
_pi.Framework.OnUpdateEvent -= OnUpdateEvent;
}
}
private void StartRedraw() private void StartRedrawAndWait()
{ {
var actor = FindCurrentActor(); var actor = FindCurrentActor();
if( actor == null ) if( actor == null )
@ -150,22 +188,36 @@ namespace Penumbra.Interop
} }
private void RevertSettings() private void RevertSettings()
{
var actor = FindCurrentActor();
if( actor != null )
{
if( !StillLoading( actor.Address + RenderModeOffset ) )
{ {
RestoreSettings(); RestoreSettings();
_currentFrame = 0; _currentFrame = 0;
} }
}
else
{
_currentFrame = 0;
}
}
private void OnUpdateEvent( object framework ) private void OnUpdateEvent( object framework )
{ {
switch( _currentFrame ) switch( _currentFrame )
{ {
case 0: case 0:
ChangeSettingsAndUndraw(); PopActor();
break; break;
case 1: case 1:
StartRedraw(); ApplySettingsOrRedraw();
break; break;
case 2: case 2:
StartRedrawAndWait();
break;
case 3:
RevertSettings(); RevertSettings();
break; break;
default: default:

View file

@ -9,7 +9,7 @@ namespace Penumbra.Interop
{ {
public class PlayerWatcher : IDisposable public class PlayerWatcher : IDisposable
{ {
private const int ActorsPerFrame = 4; private const int ActorsPerFrame = 8;
private readonly DalamudPluginInterface _pi; private readonly DalamudPluginInterface _pi;
private readonly Dictionary< string, CharEquipment > _equip = new(); private readonly Dictionary< string, CharEquipment > _equip = new();
@ -29,6 +29,11 @@ namespace Penumbra.Interop
} }
} }
public void RemovePlayerFromWatch( string playerName )
{
_equip.Remove( playerName );
}
public void SetActorWatch( bool on ) public void SetActorWatch( bool on )
{ {
if( on ) if( on )
@ -58,10 +63,10 @@ namespace Penumbra.Interop
public void Dispose() public void Dispose()
=> DisableActorWatch(); => DisableActorWatch();
public void OnTerritoryChange( object _1, ushort _2 ) private void OnTerritoryChange( object _1, ushort _2 )
=> Clear(); => Clear();
public void OnLogout( object _1, object _2 ) private void OnLogout( object _1, object _2 )
=> Clear(); => Clear();
public void Clear() public void Clear()
@ -74,7 +79,7 @@ namespace Penumbra.Interop
_frameTicker = 0; _frameTicker = 0;
} }
public void OnFrameworkUpdate( object framework ) private void OnFrameworkUpdate( object framework )
{ {
var actors = _pi.ClientState.Actors; var actors = _pi.ClientState.Actors;
for( var i = 0; i < ActorsPerFrame; ++i ) for( var i = 0; i < ActorsPerFrame; ++i )

View file

@ -164,7 +164,7 @@ namespace Penumbra.Interop
file = Marshal.PtrToStringAnsi( new IntPtr( pPath ) )!; file = Marshal.PtrToStringAnsi( new IntPtr( pPath ) )!;
var gameFsPath = GamePath.GenerateUncheckedLower( file ); var gameFsPath = GamePath.GenerateUncheckedLower( file );
var replacementPath = modManager.CurrentCollection.ResolveSwappedOrReplacementPath( gameFsPath ); var replacementPath = modManager.ResolveSwappedOrReplacementPath( gameFsPath );
if( LogAllFiles && ( LogFileFilter == null || LogFileFilter.IsMatch( file ) ) ) if( LogAllFiles && ( LogFileFilter == null || LogFileFilter.IsMatch( file ) ) )
{ {
PluginLog.Log( "[GetResourceHandler] {0}", file ); PluginLog.Log( "[GetResourceHandler] {0}", file );

View file

@ -0,0 +1,328 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Plugin;
using Penumbra.Interop;
using Penumbra.Mod;
using Penumbra.Util;
namespace Penumbra.Mods
{
// Contains all collections and respective functions, as well as the collection settings.
public class CollectionManager
{
private readonly Plugin _plugin;
private readonly ModManager _manager;
public Dictionary< string, ModCollection > Collections { get; } = new();
public ModCollection CurrentCollection { get; private set; } = null!;
public ModCollection ForcedCollection { get; private set; } = ModCollection.Empty;
public Dictionary< string, ModCollection > CharacterCollection { get; } = new();
public ModCollection ActiveCollection { get; set; }
public CollectionManager( Plugin plugin, ModManager manager )
{
_plugin = plugin;
_manager = manager;
ReadCollections();
LoadConfigCollections( _plugin.Configuration );
ActiveCollection = CurrentCollection;
}
public void RecreateCaches()
{
foreach( var collection in Collections.Values.Where( c => c.Cache != null ) )
{
collection.CreateCache( _manager.BasePath, _manager.Mods, false );
}
}
public void RemoveModFromCaches( DirectoryInfo modDir )
{
foreach( var collection in Collections.Values )
{
collection.Cache?.RemoveMod( modDir );
}
}
internal void UpdateCollections( ModData mod, bool metaChanges, ResourceChange fileChanges, bool nameChange, bool recomputeMeta )
{
foreach( var collection in Collections.Values )
{
if( metaChanges )
{
collection.UpdateSetting( mod );
if( nameChange )
{
collection.Cache?.SortMods();
}
}
if( fileChanges.HasFlag( ResourceChange.Files )
&& collection.Settings.TryGetValue( mod.BasePath.Name, out var settings )
&& settings.Enabled )
{
collection.Cache?.CalculateEffectiveFileList();
}
if( recomputeMeta )
{
collection.Cache?.UpdateMetaManipulations();
}
}
}
public bool AddCollection( string name, Dictionary< string, ModSettings > settings )
{
var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant();
if( Collections.Values.Any( c => c.Name.RemoveInvalidPathSymbols().ToLowerInvariant() == nameFixed ) )
{
PluginLog.Warning( $"The new collection {name} would lead to the same path as one that already exists." );
return false;
}
var newCollection = new ModCollection( name, settings );
Collections.Add( name, newCollection );
SaveCollection( newCollection );
SetCurrentCollection( newCollection );
return true;
}
public bool RemoveCollection( string name )
{
if( name == ModCollection.DefaultCollection )
{
PluginLog.Error( "Can not remove the default collection." );
return false;
}
if( Collections.TryGetValue( name, out var collection ) )
{
if( CurrentCollection == collection )
{
SetCurrentCollection( Collections[ ModCollection.DefaultCollection ] );
}
if( ForcedCollection == collection )
{
SetForcedCollection( ModCollection.Empty );
}
foreach( var kvp in CharacterCollection.ToArray() )
{
if( kvp.Value == collection )
{
SetCharacterCollection( kvp.Key, ModCollection.Empty );
}
}
collection.Delete( _plugin.PluginInterface! );
Collections.Remove( name );
return true;
}
return false;
}
private void AddCache( ModCollection collection )
{
if( collection.Cache == null && collection.Name != string.Empty )
{
collection.CreateCache( _manager.BasePath, _manager.Mods, false );
}
}
private void RemoveCache( ModCollection collection )
{
if( collection.Name != ForcedCollection.Name
&& collection.Name != CurrentCollection.Name
&& CharacterCollection.All( kvp => kvp.Value.Name != collection.Name ) )
{
collection.ClearCache();
}
}
private void SetCollection( ModCollection newCollection, ModCollection oldCollection, Action< ModCollection > setter,
Action< string > configSetter )
{
if( newCollection.Name == oldCollection.Name )
{
return;
}
AddCache( newCollection );
setter( newCollection );
RemoveCache( oldCollection );
configSetter( newCollection.Name );
_plugin.Configuration.Save();
}
public void SetCurrentCollection( ModCollection newCollection )
=> SetCollection( newCollection, CurrentCollection, c =>
{
if( ActiveCollection == CurrentCollection )
{
ActiveCollection = c;
}
CurrentCollection = c;
var resourceManager = Service< GameResourceManagement >.Get();
resourceManager.ReloadPlayerResources();
}, s => _plugin.Configuration.CurrentCollection = s );
public void SetForcedCollection( ModCollection newCollection )
=> SetCollection( newCollection, ForcedCollection, c => ForcedCollection = c, s => _plugin.Configuration.ForcedCollection = s );
public void SetCharacterCollection( string characterName, ModCollection newCollection )
=> SetCollection( newCollection,
CharacterCollection.TryGetValue( characterName, out var oldCollection ) ? oldCollection : ModCollection.Empty,
c => CharacterCollection[ characterName ] = c, s => _plugin.Configuration.CharacterCollections[ characterName ] = s );
public bool CreateCharacterCollection( string characterName )
{
if( !CharacterCollection.ContainsKey( characterName ) )
{
CharacterCollection[ characterName ] = ModCollection.Empty;
_plugin.Configuration.CharacterCollections[ characterName ] = string.Empty;
_plugin.Configuration.Save();
_plugin.PlayerWatcher.AddPlayerToWatch( characterName );
return true;
}
return false;
}
public void RemoveCharacterCollection( string characterName )
{
if( CharacterCollection.TryGetValue( characterName, out var collection ) )
{
RemoveCache( collection );
CharacterCollection.Remove( characterName );
_plugin.PlayerWatcher.RemovePlayerFromWatch( characterName );
}
if( _plugin.Configuration.CharacterCollections.Remove( characterName ) )
{
_plugin.Configuration.Save();
}
}
private bool LoadCurrentCollection( Configuration config )
{
if( Collections.TryGetValue( config.CurrentCollection, out var currentCollection ) )
{
CurrentCollection = currentCollection;
AddCache( CurrentCollection );
return false;
}
PluginLog.Error( $"Last choice of CurrentCollection {config.CurrentCollection} is not available, reset to Default." );
CurrentCollection = Collections[ ModCollection.DefaultCollection ];
config.CurrentCollection = ModCollection.DefaultCollection;
return true;
}
private bool LoadForcedCollection( Configuration config )
{
if( config.ForcedCollection == string.Empty )
{
ForcedCollection = ModCollection.Empty;
return false;
}
if( Collections.TryGetValue( config.ForcedCollection, out var forcedCollection ) )
{
ForcedCollection = forcedCollection;
AddCache( ForcedCollection );
return false;
}
PluginLog.Error( $"Last choice of ForcedCollection {config.ForcedCollection} is not available, reset to None." );
ForcedCollection = ModCollection.Empty;
config.ForcedCollection = string.Empty;
return true;
}
private bool LoadCharacterCollections( Configuration config )
{
var configChanged = false;
foreach( var kvp in config.CharacterCollections.ToArray() )
{
_plugin.PlayerWatcher.AddPlayerToWatch( kvp.Key );
if( kvp.Value == string.Empty )
{
CharacterCollection.Add( kvp.Key, ModCollection.Empty );
}
else if( Collections.TryGetValue( kvp.Value, out var charCollection ) )
{
AddCache( charCollection );
CharacterCollection.Add( kvp.Key, charCollection );
}
else
{
PluginLog.Error( $"Last choice of <{kvp.Key}>'s Collection {kvp.Value} is not available, reset to None." );
CharacterCollection.Add( kvp.Key, ModCollection.Empty );
config.CharacterCollections[ kvp.Key ] = string.Empty;
configChanged = true;
}
}
return configChanged;
}
private void LoadConfigCollections( Configuration config )
{
var configChanged = LoadCurrentCollection( config );
configChanged |= LoadForcedCollection( config );
configChanged |= LoadCharacterCollections( config );
if( configChanged )
{
config.Save();
}
}
private void ReadCollections()
{
var collectionDir = ModCollection.CollectionDir( _plugin.PluginInterface! );
if( collectionDir.Exists )
{
foreach( var file in collectionDir.EnumerateFiles( "*.json" ) )
{
var collection = ModCollection.LoadFromFile( file );
if( collection != null )
{
if( file.Name != $"{collection.Name.RemoveInvalidPathSymbols()}.json" )
{
PluginLog.Warning( $"Collection {file.Name} does not correspond to {collection.Name}." );
}
if( Collections.ContainsKey( collection.Name ) )
{
PluginLog.Warning( $"Duplicate collection found: {collection.Name} already exists." );
}
else
{
Collections.Add( collection.Name, collection );
}
}
}
}
if( !Collections.ContainsKey( ModCollection.DefaultCollection ) )
{
var defaultCollection = new ModCollection();
SaveCollection( defaultCollection );
Collections.Add( defaultCollection.Name, defaultCollection );
}
}
public void SaveCollection( ModCollection collection )
=> collection.Save( _plugin.PluginInterface! );
}
}

View file

@ -90,6 +90,9 @@ namespace Penumbra.Mods
CalculateEffectiveFileList( modDirectory, true ); CalculateEffectiveFileList( modDirectory, true );
} }
public void ClearCache()
=> Cache = null;
public void UpdateSetting( ModData mod ) public void UpdateSetting( ModData mod )
{ {
if( !Settings.TryGetValue( mod.BasePath.Name, out var settings ) ) if( !Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
@ -236,5 +239,7 @@ namespace Penumbra.Mods
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath ) public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
=> Cache?.ResolveSwappedOrReplacementPath( gameResourcePath ); => Cache?.ResolveSwappedOrReplacementPath( gameResourcePath );
public static readonly ModCollection Empty = new(){ Name = "" };
} }
} }

View file

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using Dalamud.Plugin; using Dalamud.Plugin;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Mod; using Penumbra.Mod;
@ -9,132 +8,19 @@ using Penumbra.Util;
namespace Penumbra.Mods namespace Penumbra.Mods
{ {
// The ModManager handles the basic mods installed to the mod directory, // The ModManager handles the basic mods installed to the mod directory.
// as well as all saved collections. // It also contains the CollectionManager that handles all collections.
// It also handles manual changes to mods that require changes in all collections,
// updating the state of a mod from the filesystem,
// and collection swapping.
public class ModManager public class ModManager
{ {
private readonly Plugin _plugin;
public DirectoryInfo BasePath { get; private set; } public DirectoryInfo BasePath { get; private set; }
public Dictionary< string, ModData > Mods { get; } = new(); public Dictionary< string, ModData > Mods { get; } = new();
public Dictionary< string, ModCollection > Collections { get; } = new(); public CollectionManager Collections { get; }
public ModCollection CurrentCollection { get; private set; }
public ModManager( Plugin plugin ) public ModManager( Plugin plugin )
{ {
_plugin = plugin; BasePath = new DirectoryInfo( plugin.Configuration.ModDirectory );
BasePath = new DirectoryInfo( plugin.Configuration!.ModDirectory ); Collections = new CollectionManager( plugin, this );
ReadCollections();
CurrentCollection = Collections.Values.First();
if( !SetCurrentCollection( plugin.Configuration!.CurrentCollection ) )
{
PluginLog.Debug( "Last choice of collection {Name} is not available, reset to Default.",
plugin.Configuration!.CurrentCollection );
if( SetCurrentCollection( ModCollection.DefaultCollection ) )
{
PluginLog.Error( "Could not load any collection. Default collection unavailable." );
CurrentCollection = new ModCollection();
}
}
}
public bool SetCurrentCollection( string name )
{
if( Collections.TryGetValue( name, out var collection ) )
{
CurrentCollection = collection;
if( CurrentCollection.Cache == null )
{
CurrentCollection.CreateCache( BasePath, Mods );
}
return true;
}
return false;
}
public void ReadCollections()
{
var collectionDir = ModCollection.CollectionDir( _plugin.PluginInterface! );
if( collectionDir.Exists )
{
foreach( var file in collectionDir.EnumerateFiles( "*.json" ) )
{
var collection = ModCollection.LoadFromFile( file );
if( collection != null )
{
if( file.Name != $"{collection.Name.RemoveInvalidPathSymbols()}.json" )
{
PluginLog.Warning( $"Collection {file.Name} does not correspond to {collection.Name}." );
}
if( Collections.ContainsKey( collection.Name ) )
{
PluginLog.Warning( $"Duplicate collection found: {collection.Name} already exists." );
}
else
{
Collections.Add( collection.Name, collection );
}
}
}
}
if( !Collections.ContainsKey( ModCollection.DefaultCollection ) )
{
var defaultCollection = new ModCollection();
SaveCollection( defaultCollection );
Collections.Add( defaultCollection.Name, defaultCollection );
}
}
public void SaveCollection( ModCollection collection )
=> collection.Save( _plugin.PluginInterface! );
public bool AddCollection( string name, Dictionary< string, ModSettings > settings )
{
var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant();
if( Collections.Values.Any( c => c.Name.RemoveInvalidPathSymbols().ToLowerInvariant() == nameFixed ) )
{
PluginLog.Warning( $"The new collection {name} would lead to the same path as one that already exists." );
return false;
}
var newCollection = new ModCollection( name, settings );
Collections.Add( name, newCollection );
SaveCollection( newCollection );
CurrentCollection = newCollection;
return true;
}
public bool RemoveCollection( string name )
{
if( name == ModCollection.DefaultCollection )
{
PluginLog.Error( "Can not remove the default collection." );
return false;
}
if( Collections.TryGetValue( name, out var collection ) )
{
if( CurrentCollection == collection )
{
SetCurrentCollection( ModCollection.DefaultCollection );
}
collection.Delete( _plugin.PluginInterface! );
Collections.Remove( name );
return true;
}
return false;
} }
public void DiscoverMods( DirectoryInfo basePath ) public void DiscoverMods( DirectoryInfo basePath )
@ -171,10 +57,7 @@ namespace Penumbra.Mods
Mods.Add( modFolder.Name, mod ); Mods.Add( modFolder.Name, mod );
} }
foreach( var collection in Collections.Values.Where( c => c.Cache != null ) ) Collections.RecreateCaches();
{
collection.CreateCache( BasePath, Mods );
}
} }
public void DeleteMod( DirectoryInfo modFolder ) public void DeleteMod( DirectoryInfo modFolder )
@ -192,10 +75,7 @@ namespace Penumbra.Mods
} }
Mods.Remove( modFolder.Name ); Mods.Remove( modFolder.Name );
foreach( var collection in Collections.Values.Where( c => c.Cache != null ) ) Collections.RemoveModFromCaches( modFolder );
{
collection.Cache!.RemoveMod( modFolder );
}
} }
} }
@ -213,7 +93,7 @@ namespace Penumbra.Mods
} }
Mods.Add( modFolder.Name, mod ); Mods.Add( modFolder.Name, mod );
foreach( var collection in Collections.Values ) foreach( var collection in Collections.Collections.Values )
{ {
collection.AddMod( mod ); collection.AddMod( mod );
} }
@ -241,77 +121,62 @@ namespace Penumbra.Mods
mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) ); mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) );
} }
foreach( var collection in Collections.Values ) Collections.UpdateCollections( mod, metaChanges, fileChanges, nameChange, recomputeMeta );
{
if( metaChanges )
{
collection.UpdateSetting( mod );
if( nameChange )
{
collection.Cache?.SortMods();
}
}
if( fileChanges.HasFlag( ResourceChange.Files )
&& collection.Settings.TryGetValue( mod.BasePath.Name, out var settings )
&& settings.Enabled )
{
collection.Cache?.CalculateEffectiveFileList();
}
if( recomputeMeta )
{
collection.Cache?.UpdateMetaManipulations();
}
}
return true; return true;
} }
// private void FileSystemWatcherOnChanged( object sender, FileSystemEventArgs e ) public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
// { {
// #if DEBUG var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
// PluginLog.Verbose( "file changed: {FullPath}", e.FullPath ); ret ??= Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
// #endif return ret;
// }
// if( _plugin.ImportInProgress )
// { // private void FileSystemWatcherOnChanged( object sender, FileSystemEventArgs e )
// return; // {
// } // #if DEBUG
// // PluginLog.Verbose( "file changed: {FullPath}", e.FullPath );
// if( _plugin.Configuration.DisableFileSystemNotifications ) // #endif
// { //
// return; // if( _plugin.ImportInProgress )
// } // {
// // return;
// var file = e.FullPath; // }
// //
// if( !ResolvedFiles.Any( x => x.Value.FullName == file ) ) // if( _plugin.Configuration.DisableFileSystemNotifications )
// { // {
// return; // return;
// } // }
// //
// PluginLog.Log( "a loaded file has been modified - file: {FullPath}", file ); // var file = e.FullPath;
// _plugin.GameUtils.ReloadPlayerResources(); //
// } // if( !ResolvedFiles.Any( x => x.Value.FullName == file ) )
// // {
// private void FileSystemPasta() // return;
// { // }
// haha spaghet //
// _fileSystemWatcher?.Dispose(); // PluginLog.Log( "a loaded file has been modified - file: {FullPath}", file );
// _fileSystemWatcher = new FileSystemWatcher( _basePath.FullName ) // _plugin.GameUtils.ReloadPlayerResources();
// { // }
// NotifyFilter = NotifyFilters.LastWrite | //
// NotifyFilters.FileName | // private void FileSystemPasta()
// NotifyFilters.DirectoryName, // {
// IncludeSubdirectories = true, // haha spaghet
// EnableRaisingEvents = true // _fileSystemWatcher?.Dispose();
// }; // _fileSystemWatcher = new FileSystemWatcher( _basePath.FullName )
// // {
// _fileSystemWatcher.Changed += FileSystemWatcherOnChanged; // NotifyFilter = NotifyFilters.LastWrite |
// _fileSystemWatcher.Created += FileSystemWatcherOnChanged; // NotifyFilters.FileName |
// _fileSystemWatcher.Deleted += FileSystemWatcherOnChanged; // NotifyFilters.DirectoryName,
// _fileSystemWatcher.Renamed += FileSystemWatcherOnChanged; // IncludeSubdirectories = true,
// } // EnableRaisingEvents = true
// };
//
// _fileSystemWatcher.Changed += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Created += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Deleted += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Renamed += FileSystemWatcherOnChanged;
// }
} }
} }

View file

@ -22,7 +22,7 @@ namespace Penumbra.Mods
mod.Meta.Name = newName; mod.Meta.Name = newName;
mod.SaveMeta(); mod.SaveMeta();
foreach( var collection in manager.Collections.Values.Where( c => c.Cache != null ) ) foreach( var collection in manager.Collections.Collections.Values.Where( c => c.Cache != null ) )
{ {
collection.Cache!.SortMods(); collection.Cache!.SortMods();
} }
@ -60,13 +60,13 @@ namespace Penumbra.Mods
mod.MetaFile = ModData.MetaFileInfo( newDir ); mod.MetaFile = ModData.MetaFileInfo( newDir );
manager.UpdateMod( mod ); manager.UpdateMod( mod );
foreach( var collection in manager.Collections.Values ) foreach( var collection in manager.Collections.Collections.Values )
{ {
if( collection.Settings.TryGetValue( oldBasePath.Name, out var settings ) ) if( collection.Settings.TryGetValue( oldBasePath.Name, out var settings ) )
{ {
collection.Settings[ newDir.Name ] = settings; collection.Settings[ newDir.Name ] = settings;
collection.Settings.Remove( oldBasePath.Name ); collection.Settings.Remove( oldBasePath.Name );
manager.SaveCollection( collection ); manager.Collections.SaveCollection( collection );
} }
if( collection.Cache != null ) if( collection.Cache != null )
@ -118,7 +118,7 @@ namespace Penumbra.Mods
mod.SaveMeta(); mod.SaveMeta();
foreach( var collection in manager.Collections.Values ) foreach( var collection in manager.Collections.Collections.Values )
{ {
if( !collection.Settings.TryGetValue( mod.BasePath.Name, out var settings ) ) if( !collection.Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
{ {
@ -131,7 +131,7 @@ namespace Penumbra.Mods
} }
settings.Settings.Remove( oldGroupName ); settings.Settings.Remove( oldGroupName );
manager.SaveCollection( collection ); manager.Collections.SaveCollection( collection );
} }
return true; return true;
@ -154,7 +154,7 @@ namespace Penumbra.Mods
return ( oldSetting & bitmaskFront ) | ( ( oldSetting & bitmaskBack ) >> 1 ); return ( oldSetting & bitmaskFront ) | ( ( oldSetting & bitmaskBack ) >> 1 );
} }
foreach( var collection in manager.Collections.Values ) foreach( var collection in manager.Collections.Collections.Values )
{ {
if( !collection.Settings.TryGetValue( mod.BasePath.Name, out var settings ) ) if( !collection.Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
{ {
@ -176,7 +176,7 @@ namespace Penumbra.Mods
if( newSetting != setting ) if( newSetting != setting )
{ {
settings.Settings[ group.GroupName ] = newSetting; settings.Settings[ group.GroupName ] = newSetting;
manager.SaveCollection( collection ); manager.Collections.SaveCollection( collection );
if( collection.Cache != null && settings.Enabled ) if( collection.Cache != null && settings.Enabled )
{ {
collection.CalculateEffectiveFileList( manager.BasePath, mod.Resources.MetaManipulations.Count > 0 ); collection.CalculateEffectiveFileList( manager.BasePath, mod.Resources.MetaManipulations.Count > 0 );

View file

@ -48,13 +48,13 @@ namespace Penumbra
SoundShit.DisableStreaming(); SoundShit.DisableStreaming();
var gameUtils = Service< GameResourceManagement >.Set( PluginInterface ); var gameUtils = Service< GameResourceManagement >.Set( PluginInterface );
PlayerWatcher = new PlayerWatcher( PluginInterface );
Service< MetaDefaults >.Set( PluginInterface ); Service< MetaDefaults >.Set( PluginInterface );
var modManager = Service< ModManager >.Set( this ); var modManager = Service< ModManager >.Set( this );
modManager.DiscoverMods(); modManager.DiscoverMods();
ActorRefresher = new ActorRefresher( PluginInterface, modManager ); ActorRefresher = new ActorRefresher( PluginInterface, modManager );
PlayerWatcher = new PlayerWatcher( PluginInterface );
ResourceLoader = new ResourceLoader( this ); ResourceLoader = new ResourceLoader( this );
@ -77,10 +77,16 @@ namespace Penumbra
CreateWebServer(); CreateWebServer();
} }
if( Configuration.EnableActorWatch ) if( Configuration.EnableActorWatch && Configuration.IsEnabled )
{ {
PlayerWatcher.EnableActorWatch(); PlayerWatcher.EnableActorWatch();
} }
PlayerWatcher.ActorChanged += a =>
{
PluginLog.Debug( "Triggered Redraw of {Actor}.", a.Name );
ActorRefresher.RedrawActor( a, Redraw.OnlyWithSettings );
};
} }
public void CreateWebServer() public void CreateWebServer()

View file

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Interface;
using Dalamud.Plugin; using Dalamud.Plugin;
using ImGuiNET; using ImGuiNET;
using Penumbra.Interop; using Penumbra.Interop;
@ -16,39 +18,74 @@ namespace Penumbra.UI
{ {
private readonly Selector _selector; private readonly Selector _selector;
private readonly ModManager _manager; private readonly ModManager _manager;
private string[] _collectionNames = null!; private string _collectionNames = null!;
private int _currentIndex = 0; private string _collectionNamesWithNone = null!;
private ModCollection[] _collections = null!;
private int _currentCollectionIndex = 0;
private int _currentForcedIndex = 0;
private readonly Dictionary< string, int > _currentCharacterIndices = new();
private string _newCollectionName = string.Empty; private string _newCollectionName = string.Empty;
private string _newCharacterName = string.Empty;
private void UpdateNames() private void UpdateNames()
=> _collectionNames = _manager.Collections.Values.Select( c => c.Name ).ToArray(); {
_collections = _manager.Collections.Collections.Values.Prepend( ModCollection.Empty ).ToArray();
_collectionNames = string.Join( "\0", _collections.Skip( 1 ).Select( c => c.Name ) );
_collectionNamesWithNone = "None\0" + _collectionNames;
UpdateIndices();
}
private int GetIndex( ModCollection collection )
{
var ret = _collections.IndexOf( c => c.Name == collection.Name );
if( ret < 0 )
{
PluginLog.Error( $"Collection {collection.Name} is not found in collections." );
return 0;
}
return ret;
}
private void UpdateIndex() private void UpdateIndex()
=> _currentCollectionIndex = GetIndex( _manager.Collections.CurrentCollection ) - 1;
private void UpdateForcedIndex()
=> _currentForcedIndex = GetIndex( _manager.Collections.ForcedCollection );
private void UpdateCharacterIndices()
{ {
_currentIndex = _collectionNames.IndexOf( c => c == _manager.CurrentCollection.Name ); _currentCharacterIndices.Clear();
if( _currentIndex < 0 ) foreach( var kvp in _manager.Collections.CharacterCollection )
{ {
PluginLog.Error( $"Current Collection {_manager.CurrentCollection.Name} is not found in collections." ); _currentCharacterIndices[ kvp.Key ] = GetIndex( kvp.Value );
_currentIndex = 0;
} }
} }
private void UpdateIndices()
{
UpdateIndex();
UpdateForcedIndex();
UpdateCharacterIndices();
}
public TabCollections( Selector selector ) public TabCollections( Selector selector )
{ {
_selector = selector; _selector = selector;
_manager = Service< ModManager >.Get(); _manager = Service< ModManager >.Get();
UpdateNames(); UpdateNames();
UpdateIndex();
} }
private void CreateNewCollection( Dictionary< string, ModSettings > settings ) private void CreateNewCollection( Dictionary< string, ModSettings > settings )
{ {
_manager.AddCollection( _newCollectionName, settings ); if( _manager.Collections.AddCollection( _newCollectionName, settings ) )
_manager.SetCurrentCollection( _newCollectionName ); {
_newCollectionName = string.Empty; _manager.Collections.SetCurrentCollection( _manager.Collections.Collections[ _newCollectionName ] );
UpdateNames(); UpdateNames();
UpdateIndex(); }
_newCollectionName = string.Empty;
} }
private void DrawNewCollectionInput() private void DrawNewCollectionInput()
@ -62,7 +99,6 @@ namespace Penumbra.UI
ImGui.PushStyleVar( ImGuiStyleVar.Alpha, 0.5f ); ImGui.PushStyleVar( ImGuiStyleVar.Alpha, 0.5f );
} }
if( ImGui.Button( "Create New Empty Collection" ) && _newCollectionName.Length > 0 ) if( ImGui.Button( "Create New Empty Collection" ) && _newCollectionName.Length > 0 )
{ {
CreateNewCollection( new Dictionary< string, ModSettings >() ); CreateNewCollection( new Dictionary< string, ModSettings >() );
@ -71,7 +107,7 @@ namespace Penumbra.UI
ImGui.SameLine(); ImGui.SameLine();
if( ImGui.Button( "Duplicate Current Collection" ) && _newCollectionName.Length > 0 ) if( ImGui.Button( "Duplicate Current Collection" ) && _newCollectionName.Length > 0 )
{ {
CreateNewCollection( _manager.CurrentCollection.Settings ); CreateNewCollection( _manager.Collections.CurrentCollection.Settings );
} }
if( changedStyle ) if( changedStyle )
@ -79,18 +115,119 @@ namespace Penumbra.UI
ImGui.PopStyleVar(); ImGui.PopStyleVar();
} }
if( _manager.Collections.Count > 1 ) if( _manager.Collections.Collections.Count > 1
&& _manager.Collections.CurrentCollection.Name != ModCollection.DefaultCollection )
{ {
ImGui.SameLine(); ImGui.SameLine();
if( ImGui.Button( "Delete Current Collection" ) ) if( ImGui.Button( "Delete Current Collection" ) )
{ {
_manager.RemoveCollection( _manager.CurrentCollection.Name ); _manager.Collections.RemoveCollection( _manager.Collections.CurrentCollection.Name );
UpdateNames(); UpdateNames();
UpdateIndex();
} }
} }
} }
private void DrawCurrentCollectionSelector()
{
var index = _currentCollectionIndex;
if( !ImGui.Combo( "Current Collection", ref index, _collectionNames ) )
{
return;
}
if( index != _currentCollectionIndex )
{
_manager.Collections.SetCurrentCollection( _collections[ index + 1 ] );
_currentCollectionIndex = index;
_selector.ReloadSelection();
}
}
private void DrawForcedCollectionSelector()
{
var index = _currentForcedIndex;
if( ImGui.Combo( "##Forced Collection", ref index, _collectionNamesWithNone ) && index != _currentForcedIndex )
{
_manager.Collections.SetForcedCollection( _collections[ index ] );
_currentForcedIndex = index;
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip(
"Mods in the forced collection are always loaded if not overwritten by anything in the current or character-based collection.\n"
+ "Please avoid mixing meta-manipulating mods in Forced and other collections, as this will probably not work correctly." );
}
ImGui.SameLine();
ImGui.Dummy( new Vector2( 24, 0 ) );
ImGui.SameLine();
ImGui.Text( "Forced Collection" );
}
private void DrawNewCharacterCollection()
{
ImGui.InputTextWithHint( "##New Character", "New Character Name", ref _newCharacterName, 32 );
var changedStyle = false;
if( _newCharacterName.Length == 0 )
{
changedStyle = true;
ImGui.PushStyleVar( ImGuiStyleVar.Alpha, 0.5f );
}
ImGui.SameLine();
if( ImGui.Button( "Create New Character Collection" ) && _newCharacterName.Length > 0 )
{
_manager.Collections.CreateCharacterCollection( _newCharacterName );
_currentCharacterIndices[ _newCharacterName ] = 0;
}
if( changedStyle )
{
ImGui.PopStyleVar();
}
}
private void DrawCharacterCollectionSelectors()
{
if( !ImGui.BeginChild( "##CollectionChild", AutoFillSize, true ) )
{
return;
}
DrawForcedCollectionSelector();
foreach( var name in _manager.Collections.CharacterCollection.Keys.ToArray() )
{
var idx = _currentCharacterIndices[ name ];
var tmp = idx;
if( ImGui.Combo( $"##{name}collection", ref tmp, _collectionNamesWithNone ) && idx != tmp )
{
_manager.Collections.SetCharacterCollection( name, _collections[ tmp ] );
_currentCharacterIndices[ name ] = tmp;
}
ImGui.SameLine();
ImGui.PushFont( UiBuilder.IconFont );
if( ImGui.Button( $"{FontAwesomeIcon.Trash.ToIconString()}##{name}" ) )
{
_manager.Collections.RemoveCharacterCollection( name );
}
ImGui.PopFont();
ImGui.SameLine();
ImGui.Text( name );
}
DrawNewCharacterCollection();
ImGui.EndChild();
}
public void Draw() public void Draw()
{ {
if( !ImGui.BeginTabItem( "Collections" ) ) if( !ImGui.BeginTabItem( "Collections" ) )
@ -98,20 +235,20 @@ namespace Penumbra.UI
return; return;
} }
var index = _currentIndex; if( !ImGui.BeginChild( "##CollectionHandling", new Vector2( -1, ImGui.GetTextLineHeightWithSpacing() * 6 ), true ) )
if( ImGui.Combo( "Current Collection", ref index, _collectionNames, _collectionNames.Length ) )
{ {
if( index != _currentIndex && _manager.SetCurrentCollection( _collectionNames[ index ] ) ) ImGui.EndTabItem();
{ return;
_currentIndex = index;
_selector.ReloadSelection();
var resourceManager = Service< GameResourceManagement >.Get();
resourceManager.ReloadPlayerResources();
}
} }
ImGui.Dummy( new Vector2( 0, 5 ) ); DrawCurrentCollectionSelector();
ImGui.Dummy( new Vector2( 0, 10 ) );
DrawNewCollectionInput(); DrawNewCollectionInput();
ImGui.EndChild();
DrawCharacterCollectionSelectors();
ImGui.EndTabItem(); ImGui.EndTabItem();
} }

View file

@ -59,7 +59,7 @@ namespace Penumbra.UI
if( ImGui.BeginTable( "##effective_changes", 3, flags, AutoFillSize ) ) if( ImGui.BeginTable( "##effective_changes", 3, flags, AutoFillSize ) )
{ {
var currentCollection = _modManager.CurrentCollection.Cache!; var currentCollection = _modManager.Collections.CurrentCollection.Cache!;
foreach( var file in currentCollection.ResolvedFiles ) foreach( var file in currentCollection.ResolvedFiles )
{ {
DrawFileLine( file.Value, file.Key ); DrawFileLine( file.Value, file.Key );

View file

@ -200,7 +200,7 @@ namespace Penumbra.UI
if( ImGui.InputInt( "Priority", ref priority, 0 ) && priority != Mod!.Settings.Priority ) if( ImGui.InputInt( "Priority", ref priority, 0 ) && priority != Mod!.Settings.Priority )
{ {
Mod.Settings.Priority = priority; Mod.Settings.Priority = priority;
var collection = _modManager.CurrentCollection; var collection = _modManager.Collections.CurrentCollection;
collection.Save( _base._plugin.PluginInterface! ); collection.Save( _base._plugin.PluginInterface! );
collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0 ); collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0 );
} }
@ -218,7 +218,7 @@ namespace Penumbra.UI
if( ImGui.Checkbox( LabelModEnabled, ref enabled ) ) if( ImGui.Checkbox( LabelModEnabled, ref enabled ) )
{ {
Mod.Settings.Enabled = enabled; Mod.Settings.Enabled = enabled;
var collection = _modManager.CurrentCollection; var collection = _modManager.Collections.CurrentCollection;
collection.Save( _base._plugin.PluginInterface! ); collection.Save( _base._plugin.PluginInterface! );
collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0 ); collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0 );
} }

View file

@ -75,7 +75,7 @@ namespace Penumbra.UI
private readonly ModManager _modManager; private readonly ModManager _modManager;
private List< Mod.Mod >? Mods private List< Mod.Mod >? Mods
=> _modManager.CurrentCollection.Cache?.AvailableMods; => _modManager.Collections.CurrentCollection.Cache?.AvailableMods;
public Mod.Mod? Mod { get; private set; } public Mod.Mod? Mod { get; private set; }
private int _index; private int _index;
@ -269,6 +269,7 @@ namespace Penumbra.UI
{ {
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();
_modManager.DeleteMod( Mod.Data.BasePath ); _modManager.DeleteMod( Mod.Data.BasePath );
ResetModNamesLower();
ClearSelection(); ClearSelection();
} }

View file

@ -69,7 +69,11 @@ namespace Penumbra.UI
{ {
_config.IsEnabled = enabled; _config.IsEnabled = enabled;
_configChanged = true; _configChanged = true;
_base._plugin.ActorRefresher.RedrawAll( ); _base._plugin.ActorRefresher.RedrawAll( enabled ? Redraw.WithSettings : Redraw.WithoutSettings );
if( _config.EnableActorWatch )
{
_base._plugin.PlayerWatcher.SetActorWatch( enabled );
}
} }
} }