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()
{
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.Priority,
@ -39,7 +39,7 @@ namespace Penumbra.API
public object GetFiles()
{
var modManager = Service< ModManager >.Get();
return modManager.CurrentCollection.Cache?.ResolvedFiles.ToDictionary(
return modManager.Collections.CurrentCollection.Cache?.ResolvedFiles.ToDictionary(
o => ( string )o.Key,
o => o.Value.FullName
)

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Dalamud.Configuration;
using Dalamud.Plugin;
using Penumbra.Util;
@ -24,7 +25,8 @@ namespace Penumbra
public string ModDirectory { get; set; } = @"D:/ffxiv/fs_mods/";
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 static Configuration Load( DalamudPluginInterface pi )

View file

@ -23,6 +23,7 @@ namespace Penumbra.Interop
{
private const int RenderModeOffset = 0x0104;
private const int ModelInvisibilityFlag = 0b10;
private const int ModelIsLoadingFlag = 0x800;
private const int UnloadAllRedrawDelay = 250;
private const int NpcActorId = -536870912;
@ -30,10 +31,11 @@ namespace Penumbra.Interop
private readonly ModManager _mods;
private readonly Queue< (int actorId, string name, Redraw s) > _actorIds = new();
private int _currentFrame = 0;
private bool _changedSettings = false;
private int _currentActorId = -1;
private string? _currentActorName = null;
private int _currentFrame = 0;
private bool _changedSettings = false;
private int _currentActorId = -1;
private string? _currentActorName = null;
private Redraw _currentActorRedraw = Redraw.Unload;
public ActorRefresher( DalamudPluginInterface pi, ModManager mods )
{
@ -42,10 +44,19 @@ namespace Penumbra.Interop
}
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()
=> _changedSettings = false;
{
_mods.Collections.ActiveCollection = _mods.Collections.CurrentCollection;
_changedSettings = false;
}
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 )
{
if( renderPtr != IntPtr.Zero )
@ -81,54 +102,21 @@ namespace Penumbra.Interop
private Actor? FindCurrentActor()
=> _pi.ClientState.Actors.FirstOrDefault( CheckActor );
private void ChangeSettingsAndUndraw()
private void PopActor()
{
if( _actorIds.Count > 0 )
{
var (id, name, s) = _actorIds.Dequeue();
_currentActorName = name;
_currentActorId = id;
var (id, name, s) = _actorIds.Dequeue();
_currentActorName = name;
_currentActorId = id;
_currentActorRedraw = s;
var actor = FindCurrentActor();
if( actor == null )
{
return;
}
switch( s )
{
case Redraw.Unload:
WriteInvisible( actor.Address + RenderModeOffset );
_currentFrame = 0;
break;
case Redraw.RedrawWithSettings:
ChangeSettings();
++_currentFrame;
break;
case Redraw.RedrawWithoutSettings:
WriteVisible( actor.Address + RenderModeOffset );
_currentFrame = 0;
break;
case Redraw.WithoutSettings:
WriteInvisible( actor.Address + RenderModeOffset );
++_currentFrame;
break;
case Redraw.WithSettings:
ChangeSettings();
WriteInvisible( actor.Address + RenderModeOffset );
++_currentFrame;
break;
case Redraw.OnlyWithSettings:
ChangeSettings();
if( !_changedSettings )
{
return;
}
WriteInvisible( actor.Address + RenderModeOffset );
++_currentFrame;
break;
default: throw new InvalidEnumArgumentException();
}
++_currentFrame;
}
else
{
@ -136,7 +124,57 @@ namespace Penumbra.Interop
}
}
private void StartRedraw()
private void ApplySettingsOrRedraw()
{
var actor = FindCurrentActor();
if( actor == null )
{
return;
}
if( StillLoading( actor.Address + RenderModeOffset ) )
{
return;
}
switch( _currentActorRedraw )
{
case Redraw.Unload:
WriteInvisible( actor.Address + RenderModeOffset );
_currentFrame = 0;
break;
case Redraw.RedrawWithSettings:
ChangeSettings();
++_currentFrame;
break;
case Redraw.RedrawWithoutSettings:
WriteVisible( actor.Address + RenderModeOffset );
_currentFrame = 0;
break;
case Redraw.WithoutSettings:
WriteInvisible( actor.Address + RenderModeOffset );
++_currentFrame;
break;
case Redraw.WithSettings:
ChangeSettings();
WriteInvisible( actor.Address + RenderModeOffset );
++_currentFrame;
break;
case Redraw.OnlyWithSettings:
ChangeSettings();
if( !_changedSettings )
{
return;
}
WriteInvisible( actor.Address + RenderModeOffset );
++_currentFrame;
break;
default: throw new InvalidEnumArgumentException();
}
}
private void StartRedrawAndWait()
{
var actor = FindCurrentActor();
if( actor == null )
@ -151,8 +189,19 @@ namespace Penumbra.Interop
private void RevertSettings()
{
RestoreSettings();
_currentFrame = 0;
var actor = FindCurrentActor();
if( actor != null )
{
if( !StillLoading( actor.Address + RenderModeOffset ) )
{
RestoreSettings();
_currentFrame = 0;
}
}
else
{
_currentFrame = 0;
}
}
private void OnUpdateEvent( object framework )
@ -160,12 +209,15 @@ namespace Penumbra.Interop
switch( _currentFrame )
{
case 0:
ChangeSettingsAndUndraw();
PopActor();
break;
case 1:
StartRedraw();
ApplySettingsOrRedraw();
break;
case 2:
StartRedrawAndWait();
break;
case 3:
RevertSettings();
break;
default:

View file

@ -9,7 +9,7 @@ namespace Penumbra.Interop
{
public class PlayerWatcher : IDisposable
{
private const int ActorsPerFrame = 4;
private const int ActorsPerFrame = 8;
private readonly DalamudPluginInterface _pi;
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 )
{
if( on )
@ -58,10 +63,10 @@ namespace Penumbra.Interop
public void Dispose()
=> DisableActorWatch();
public void OnTerritoryChange( object _1, ushort _2 )
private void OnTerritoryChange( object _1, ushort _2 )
=> Clear();
public void OnLogout( object _1, object _2 )
private void OnLogout( object _1, object _2 )
=> Clear();
public void Clear()
@ -74,7 +79,7 @@ namespace Penumbra.Interop
_frameTicker = 0;
}
public void OnFrameworkUpdate( object framework )
private void OnFrameworkUpdate( object framework )
{
var actors = _pi.ClientState.Actors;
for( var i = 0; i < ActorsPerFrame; ++i )

View file

@ -164,7 +164,7 @@ namespace Penumbra.Interop
file = Marshal.PtrToStringAnsi( new IntPtr( pPath ) )!;
var gameFsPath = GamePath.GenerateUncheckedLower( file );
var replacementPath = modManager.CurrentCollection.ResolveSwappedOrReplacementPath( gameFsPath );
var replacementPath = modManager.ResolveSwappedOrReplacementPath( gameFsPath );
if( LogAllFiles && ( LogFileFilter == null || LogFileFilter.IsMatch( 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 );
}
public void ClearCache()
=> Cache = null;
public void UpdateSetting( ModData mod )
{
if( !Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
@ -236,5 +239,7 @@ namespace Penumbra.Mods
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
=> Cache?.ResolveSwappedOrReplacementPath( gameResourcePath );
public static readonly ModCollection Empty = new(){ Name = "" };
}
}

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Plugin;
using Penumbra.Meta;
using Penumbra.Mod;
@ -9,132 +8,19 @@ using Penumbra.Util;
namespace Penumbra.Mods
{
// The ModManager handles the basic mods installed to the mod directory,
// as well as all saved 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.
// The ModManager handles the basic mods installed to the mod directory.
// It also contains the CollectionManager that handles all collections.
public class ModManager
{
private readonly Plugin _plugin;
public DirectoryInfo BasePath { get; private set; }
public Dictionary< string, ModData > Mods { get; } = new();
public Dictionary< string, ModCollection > Collections { get; } = new();
public ModCollection CurrentCollection { get; private set; }
public CollectionManager Collections { get; }
public ModManager( Plugin plugin )
{
_plugin = plugin;
BasePath = new DirectoryInfo( plugin.Configuration!.ModDirectory );
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;
BasePath = new DirectoryInfo( plugin.Configuration.ModDirectory );
Collections = new CollectionManager( plugin, this );
}
public void DiscoverMods( DirectoryInfo basePath )
@ -171,10 +57,7 @@ namespace Penumbra.Mods
Mods.Add( modFolder.Name, mod );
}
foreach( var collection in Collections.Values.Where( c => c.Cache != null ) )
{
collection.CreateCache( BasePath, Mods );
}
Collections.RecreateCaches();
}
public void DeleteMod( DirectoryInfo modFolder )
@ -192,10 +75,7 @@ namespace Penumbra.Mods
}
Mods.Remove( modFolder.Name );
foreach( var collection in Collections.Values.Where( c => c.Cache != null ) )
{
collection.Cache!.RemoveMod( modFolder );
}
Collections.RemoveModFromCaches( modFolder );
}
}
@ -213,7 +93,7 @@ namespace Penumbra.Mods
}
Mods.Add( modFolder.Name, mod );
foreach( var collection in Collections.Values )
foreach( var collection in Collections.Collections.Values )
{
collection.AddMod( mod );
}
@ -241,77 +121,62 @@ namespace Penumbra.Mods
mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) );
}
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();
}
}
Collections.UpdateCollections( mod, metaChanges, fileChanges, nameChange, recomputeMeta );
return true;
}
// private void FileSystemWatcherOnChanged( object sender, FileSystemEventArgs e )
// {
// #if DEBUG
// PluginLog.Verbose( "file changed: {FullPath}", e.FullPath );
// #endif
//
// if( _plugin.ImportInProgress )
// {
// return;
// }
//
// if( _plugin.Configuration.DisableFileSystemNotifications )
// {
// return;
// }
//
// var file = e.FullPath;
//
// if( !ResolvedFiles.Any( x => x.Value.FullName == file ) )
// {
// return;
// }
//
// PluginLog.Log( "a loaded file has been modified - file: {FullPath}", file );
// _plugin.GameUtils.ReloadPlayerResources();
// }
//
// private void FileSystemPasta()
// {
// haha spaghet
// _fileSystemWatcher?.Dispose();
// _fileSystemWatcher = new FileSystemWatcher( _basePath.FullName )
// {
// NotifyFilter = NotifyFilters.LastWrite |
// NotifyFilters.FileName |
// NotifyFilters.DirectoryName,
// IncludeSubdirectories = true,
// EnableRaisingEvents = true
// };
//
// _fileSystemWatcher.Changed += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Created += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Deleted += FileSystemWatcherOnChanged;
// _fileSystemWatcher.Renamed += FileSystemWatcherOnChanged;
// }
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
{
var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
ret ??= Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
return ret;
}
// private void FileSystemWatcherOnChanged( object sender, FileSystemEventArgs e )
// {
// #if DEBUG
// PluginLog.Verbose( "file changed: {FullPath}", e.FullPath );
// #endif
//
// if( _plugin.ImportInProgress )
// {
// return;
// }
//
// if( _plugin.Configuration.DisableFileSystemNotifications )
// {
// return;
// }
//
// var file = e.FullPath;
//
// if( !ResolvedFiles.Any( x => x.Value.FullName == file ) )
// {
// return;
// }
//
// PluginLog.Log( "a loaded file has been modified - file: {FullPath}", file );
// _plugin.GameUtils.ReloadPlayerResources();
// }
//
// private void FileSystemPasta()
// {
// haha spaghet
// _fileSystemWatcher?.Dispose();
// _fileSystemWatcher = new FileSystemWatcher( _basePath.FullName )
// {
// NotifyFilter = NotifyFilters.LastWrite |
// NotifyFilters.FileName |
// NotifyFilters.DirectoryName,
// 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.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();
}
@ -60,13 +60,13 @@ namespace Penumbra.Mods
mod.MetaFile = ModData.MetaFileInfo( newDir );
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 ) )
{
collection.Settings[ newDir.Name ] = settings;
collection.Settings.Remove( oldBasePath.Name );
manager.SaveCollection( collection );
manager.Collections.SaveCollection( collection );
}
if( collection.Cache != null )
@ -118,7 +118,7 @@ namespace Penumbra.Mods
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 ) )
{
@ -131,7 +131,7 @@ namespace Penumbra.Mods
}
settings.Settings.Remove( oldGroupName );
manager.SaveCollection( collection );
manager.Collections.SaveCollection( collection );
}
return true;
@ -154,7 +154,7 @@ namespace Penumbra.Mods
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 ) )
{
@ -176,7 +176,7 @@ namespace Penumbra.Mods
if( newSetting != setting )
{
settings.Settings[ group.GroupName ] = newSetting;
manager.SaveCollection( collection );
manager.Collections.SaveCollection( collection );
if( collection.Cache != null && settings.Enabled )
{
collection.CalculateEffectiveFileList( manager.BasePath, mod.Resources.MetaManipulations.Count > 0 );

View file

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

View file

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Plugin;
using ImGuiNET;
using Penumbra.Interop;
@ -14,41 +16,76 @@ namespace Penumbra.UI
{
private class TabCollections
{
private readonly Selector _selector;
private readonly ModManager _manager;
private string[] _collectionNames = null!;
private int _currentIndex = 0;
private string _newCollectionName = string.Empty;
private readonly Selector _selector;
private readonly ModManager _manager;
private string _collectionNames = null!;
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 _newCharacterName = string.Empty;
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()
=> _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 );
if( _currentIndex < 0 )
_currentCharacterIndices.Clear();
foreach( var kvp in _manager.Collections.CharacterCollection )
{
PluginLog.Error( $"Current Collection {_manager.CurrentCollection.Name} is not found in collections." );
_currentIndex = 0;
_currentCharacterIndices[ kvp.Key ] = GetIndex( kvp.Value );
}
}
private void UpdateIndices()
{
UpdateIndex();
UpdateForcedIndex();
UpdateCharacterIndices();
}
public TabCollections( Selector selector )
{
_selector = selector;
_manager = Service< ModManager >.Get();
UpdateNames();
UpdateIndex();
}
private void CreateNewCollection( Dictionary< string, ModSettings > settings )
{
_manager.AddCollection( _newCollectionName, settings );
_manager.SetCurrentCollection( _newCollectionName );
if( _manager.Collections.AddCollection( _newCollectionName, settings ) )
{
_manager.Collections.SetCurrentCollection( _manager.Collections.Collections[ _newCollectionName ] );
UpdateNames();
}
_newCollectionName = string.Empty;
UpdateNames();
UpdateIndex();
}
private void DrawNewCollectionInput()
@ -62,7 +99,6 @@ namespace Penumbra.UI
ImGui.PushStyleVar( ImGuiStyleVar.Alpha, 0.5f );
}
if( ImGui.Button( "Create New Empty Collection" ) && _newCollectionName.Length > 0 )
{
CreateNewCollection( new Dictionary< string, ModSettings >() );
@ -71,7 +107,7 @@ namespace Penumbra.UI
ImGui.SameLine();
if( ImGui.Button( "Duplicate Current Collection" ) && _newCollectionName.Length > 0 )
{
CreateNewCollection( _manager.CurrentCollection.Settings );
CreateNewCollection( _manager.Collections.CurrentCollection.Settings );
}
if( changedStyle )
@ -79,18 +115,119 @@ namespace Penumbra.UI
ImGui.PopStyleVar();
}
if( _manager.Collections.Count > 1 )
if( _manager.Collections.Collections.Count > 1
&& _manager.Collections.CurrentCollection.Name != ModCollection.DefaultCollection )
{
ImGui.SameLine();
if( ImGui.Button( "Delete Current Collection" ) )
{
_manager.RemoveCollection( _manager.CurrentCollection.Name );
_manager.Collections.RemoveCollection( _manager.Collections.CurrentCollection.Name );
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()
{
if( !ImGui.BeginTabItem( "Collections" ) )
@ -98,20 +235,20 @@ namespace Penumbra.UI
return;
}
var index = _currentIndex;
if( ImGui.Combo( "Current Collection", ref index, _collectionNames, _collectionNames.Length ) )
if( !ImGui.BeginChild( "##CollectionHandling", new Vector2( -1, ImGui.GetTextLineHeightWithSpacing() * 6 ), true ) )
{
if( index != _currentIndex && _manager.SetCurrentCollection( _collectionNames[ index ] ) )
{
_currentIndex = index;
_selector.ReloadSelection();
var resourceManager = Service< GameResourceManagement >.Get();
resourceManager.ReloadPlayerResources();
}
ImGui.EndTabItem();
return;
}
ImGui.Dummy( new Vector2( 0, 5 ) );
DrawCurrentCollectionSelector();
ImGui.Dummy( new Vector2( 0, 10 ) );
DrawNewCollectionInput();
ImGui.EndChild();
DrawCharacterCollectionSelectors();
ImGui.EndTabItem();
}

View file

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

View file

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

View file

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