Add Mare Synchronos and MUI API/IPC functions for testing. Not tested myself because how.

This commit is contained in:
Ottermandias 2022-06-19 19:20:02 +02:00
parent 8422d36e4e
commit d6d13594e0
15 changed files with 857 additions and 240 deletions

View file

@ -1,13 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Design;
using Dalamud.Configuration; using Dalamud.Configuration;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Lumina.Data; using Lumina.Data;
using OtterGui.Classes;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods; using Penumbra.Mods;
namespace Penumbra.Api; namespace Penumbra.Api;
@ -24,7 +20,7 @@ public delegate void GameObjectRedrawn( IntPtr objectPtr, int objectTableIndex )
public enum PenumbraApiEc public enum PenumbraApiEc
{ {
Okay = 0, Success = 0,
NothingChanged = 1, NothingChanged = 1,
CollectionMissing = 2, CollectionMissing = 2,
ModMissing = 3, ModMissing = 3,
@ -37,6 +33,7 @@ public enum PenumbraApiEc
FileMissing = 9, FileMissing = 9,
InvalidManipulation = 10, InvalidManipulation = 10,
InvalidArgument = 11, InvalidArgument = 11,
UnknownError = 255,
} }
public interface IPenumbraApi : IPenumbraApiBase public interface IPenumbraApi : IPenumbraApiBase
@ -136,32 +133,36 @@ public interface IPenumbraApi : IPenumbraApiBase
// If any setting can not be found, it will not change anything. // If any setting can not be found, it will not change anything.
public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, string option ); public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, string option );
public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, public PenumbraApiEc TrySetModSettings( string collectionName, string modDirectory, string modName, string optionGroupName,
IReadOnlyList< string > options ); IReadOnlyList< string > options );
// Create a temporary collection without actual settings but with a cache. // Create a temporary collection without actual settings but with a cache.
// If character is non-zero and either no character collection for this character exists or forceOverwriteCharacter is true, // If no character collection for this character exists or forceOverwriteCharacter is true,
// associate this collection to a specific character. // associate this collection to a specific character.
// Can return Okay, CharacterCollectionExists or NothingChanged. // Can return Okay, CharacterCollectionExists or NothingChanged, as well as the name of the new temporary collection on success.
public PenumbraApiEc CreateTemporaryCollection( string collectionName, string? character, bool forceOverwriteCharacter ); public (PenumbraApiEc, string) CreateTemporaryCollection( string tag, string character, bool forceOverwriteCharacter );
// Remove a temporary collection if it exists. // Remove the temporary collection associated with characterName if it exists.
// Can return Okay or NothingChanged. // Can return Okay or NothingChanged.
public PenumbraApiEc RemoveTemporaryCollection( string collectionName ); public PenumbraApiEc RemoveTemporaryCollection( string characterName );
// Set a temporary mod with the given paths, manipulations and priority and the name tag to all collections.
// Can return Okay, InvalidGamePath, or InvalidManipulation.
public PenumbraApiEc AddTemporaryModAll( string tag, IReadOnlyDictionary< string, string > paths, IReadOnlySet< string > manipCodes,
int priority );
// Set or remove a specific file redirection or meta manipulation under the name of Tag and with a given priority // Set a temporary mod with the given paths, manipulations and priority and the name tag to the collection with the given name, which can be temporary.
// for a given collection, which may be temporary. // Can return Okay, MissingCollection InvalidGamePath, or InvalidManipulation.
// Can return Okay, CollectionMissing, InvalidPath, FileMissing, LowerPriority, or NothingChanged. public PenumbraApiEc AddTemporaryMod( string tag, string collectionName, IReadOnlyDictionary< string, string > paths,
public PenumbraApiEc SetFileRedirection( string tag, string collectionName, string gamePath, string fullPath, int priority ); IReadOnlySet< string > manipCodes,
int priority );
// Can return Okay, CollectionMissing, InvalidManipulation, LowerPriority, or NothingChanged. // Remove the temporary mod with the given tag and priority from the temporary mods applying to all collections, if it exists.
public PenumbraApiEc SetMetaManipulation( string tag, string collectionName, string manipulationBase64, int priority ); // Can return Okay or NothingDone.
public PenumbraApiEc RemoveTemporaryModAll( string tag, int priority );
// Can return Okay, CollectionMissing, InvalidPath, or NothingChanged. // Remove the temporary mod with the given tag and priority from the temporary mods applying to the collection of the given name, which can be temporary.
public PenumbraApiEc RemoveFileRedirection( string tag, string collectionName, string gamePath ); // Can return Okay or NothingDone.
public PenumbraApiEc RemoveTemporaryMod( string tag, string collectionName, int priority );
// Can return Okay, CollectionMissing, InvalidManipulation, or NothingChanged.
public PenumbraApiEc RemoveMetaManipulation( string tag, string collectionName, string manipulationBase64 );
} }

View file

@ -37,7 +37,6 @@ public class ModsController : WebApiController
return Penumbra.CollectionManager.Current.ResolvedFiles.ToDictionary( return Penumbra.CollectionManager.Current.ResolvedFiles.ToDictionary(
o => o.Key.ToString(), o => o.Key.ToString(),
o => o.Value.Path.FullName o => o.Value.Path.FullName
) );
?? new Dictionary< string, string >();
} }
} }

View file

@ -9,9 +9,11 @@ using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging; using Dalamud.Logging;
using Lumina.Data; using Lumina.Data;
using Newtonsoft.Json; using Newtonsoft.Json;
using OtterGui;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Util; using Penumbra.Util;
@ -61,24 +63,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public event ChangedItemHover? ChangedItemTooltip; public event ChangedItemHover? ChangedItemTooltip;
internal bool HasTooltip
=> ChangedItemTooltip != null;
internal void InvokeTooltip( object? it )
=> ChangedItemTooltip?.Invoke( it );
internal void InvokeClick( MouseButton button, object? it )
=> ChangedItemClicked?.Invoke( button, it );
private void CheckInitialized()
{
if( !Valid )
{
throw new Exception( "PluginShare is not initialized." );
}
}
public void RedrawObject( int tableIndex, RedrawType setting ) public void RedrawObject( int tableIndex, RedrawType setting )
{ {
CheckInitialized(); CheckInitialized();
@ -97,29 +81,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi
_penumbra!.ObjectReloader.RedrawObject( gameObject, setting ); _penumbra!.ObjectReloader.RedrawObject( gameObject, setting );
} }
private void OnGameObjectRedrawn( IntPtr objectAddress, int objectTableIndex )
{
GameObjectRedrawn?.Invoke( objectAddress, objectTableIndex );
}
public void RedrawAll( RedrawType setting ) public void RedrawAll( RedrawType setting )
{ {
CheckInitialized(); CheckInitialized();
_penumbra!.ObjectReloader.RedrawAll( setting ); _penumbra!.ObjectReloader.RedrawAll( setting );
} }
private static string ResolvePath( string path, Mod.Manager _, ModCollection collection )
{
if( !Penumbra.Config.EnableMods )
{
return path;
}
var gamePath = Utf8GamePath.FromString( path, out var p, true ) ? p : Utf8GamePath.Empty;
var ret = collection.ResolvePath( gamePath );
return ret?.ToString() ?? path;
}
public string ResolvePath( string path ) public string ResolvePath( string path )
{ {
CheckInitialized(); CheckInitialized();
@ -145,25 +112,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi
return ret.Select( r => r.ToString() ).ToList(); return ret.Select( r => r.ToString() ).ToList();
} }
private T? GetFileIntern< T >( string resolvedPath ) where T : FileResource
{
CheckInitialized();
try
{
if( Path.IsPathRooted( resolvedPath ) )
{
return _lumina?.GetFileFromDisk< T >( resolvedPath );
}
return Dalamud.GameData.GetFile< T >( resolvedPath );
}
catch( Exception e )
{
PluginLog.Warning( $"Could not load file {resolvedPath}:\n{e}" );
return null;
}
}
public T? GetFile< T >( string gamePath ) where T : FileResource public T? GetFile< T >( string gamePath ) where T : FileResource
=> GetFileIntern< T >( ResolvePath( gamePath ) ); => GetFileIntern< T >( ResolvePath( gamePath ) );
@ -259,11 +207,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi
var settings = allowInheritance ? collection.Settings[ mod.Index ] : collection[ mod.Index ].Settings; var settings = allowInheritance ? collection.Settings[ mod.Index ] : collection[ mod.Index ].Settings;
if( settings == null ) if( settings == null )
{ {
return ( PenumbraApiEc.Okay, null ); return ( PenumbraApiEc.Success, null );
} }
var shareSettings = settings.ConvertToShareable( mod ); var shareSettings = settings.ConvertToShareable( mod );
return ( PenumbraApiEc.Okay, return ( PenumbraApiEc.Success,
( shareSettings.Enabled, shareSettings.Priority, shareSettings.Settings, collection.Settings[ mod.Index ] != null ) ); ( shareSettings.Enabled, shareSettings.Priority, shareSettings.Settings, collection.Settings[ mod.Index ] != null ) );
} }
@ -281,7 +229,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
} }
return collection.SetModInheritance( mod.Index, inherit ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged; return collection.SetModInheritance( mod.Index, inherit ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
} }
public PenumbraApiEc TrySetMod( string collectionName, string modDirectory, string modName, bool enabled ) public PenumbraApiEc TrySetMod( string collectionName, string modDirectory, string modName, bool enabled )
@ -297,7 +245,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
return PenumbraApiEc.ModMissing; return PenumbraApiEc.ModMissing;
} }
return collection.SetModState( mod.Index, enabled ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged; return collection.SetModState( mod.Index, enabled ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
} }
public PenumbraApiEc TrySetModPriority( string collectionName, string modDirectory, string modName, int priority ) public PenumbraApiEc TrySetModPriority( string collectionName, string modDirectory, string modName, int priority )
@ -313,7 +261,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
return PenumbraApiEc.ModMissing; return PenumbraApiEc.ModMissing;
} }
return collection.SetModPriority( mod.Index, priority ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged; return collection.SetModPriority( mod.Index, priority ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
} }
public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName,
@ -344,10 +292,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
var setting = mod.Groups[ groupIdx ].Type == SelectType.Multi ? 1u << optionIdx : ( uint )optionIdx; var setting = mod.Groups[ groupIdx ].Type == SelectType.Multi ? 1u << optionIdx : ( uint )optionIdx;
return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged; return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
} }
public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, public PenumbraApiEc TrySetModSettings( string collectionName, string modDirectory, string modName, string optionGroupName,
IReadOnlyList< string > optionNames ) IReadOnlyList< string > optionNames )
{ {
CheckInitialized(); CheckInitialized();
@ -378,7 +326,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
if( group.Type == SelectType.Single ) if( group.Type == SelectType.Single )
{ {
var name = optionNames[ ^1 ]; var name = optionNames[ ^1 ];
var optionIdx = group.IndexOf( o => o.Name == optionNames[^1] ); var optionIdx = group.IndexOf( o => o.Name == optionNames[ ^1 ] );
if( optionIdx < 0 ) if( optionIdx < 0 )
{ {
return PenumbraApiEc.OptionMissing; return PenumbraApiEc.OptionMissing;
@ -400,24 +348,213 @@ public class PenumbraApi : IDisposable, IPenumbraApi
} }
} }
return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged; return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
} }
public PenumbraApiEc CreateTemporaryCollection( string collectionName, string? character, bool forceOverwriteCharacter ) public (PenumbraApiEc, string) CreateTemporaryCollection( string tag, string character, bool forceOverwriteCharacter )
=> throw new NotImplementedException(); {
CheckInitialized();
if( !forceOverwriteCharacter && Penumbra.CollectionManager.Characters.ContainsKey( character )
|| Penumbra.TempMods.Collections.ContainsKey( character ) )
{
return ( PenumbraApiEc.CharacterCollectionExists, string.Empty );
}
public PenumbraApiEc RemoveTemporaryCollection( string collectionName ) var name = Penumbra.TempMods.SetTemporaryCollection( tag, character );
=> throw new NotImplementedException(); return ( PenumbraApiEc.Success, name );
}
public PenumbraApiEc SetFileRedirection( string tag, string collectionName, string gamePath, string fullPath, int priority ) public PenumbraApiEc RemoveTemporaryCollection( string character )
=> throw new NotImplementedException(); {
CheckInitialized();
if( !Penumbra.TempMods.Collections.ContainsKey( character ) )
{
return PenumbraApiEc.NothingChanged;
}
public PenumbraApiEc SetMetaManipulation( string tag, string collectionName, string manipulationBase64, int priority ) Penumbra.TempMods.RemoveTemporaryCollection( character );
=> throw new NotImplementedException(); return PenumbraApiEc.Success;
}
public PenumbraApiEc RemoveFileRedirection( string tag, string collectionName, string gamePath ) public PenumbraApiEc AddTemporaryModAll( string tag, IReadOnlyDictionary< string, string > paths, IReadOnlySet< string > manipCodes,
=> throw new NotImplementedException(); int priority )
{
CheckInitialized();
if( !ConvertPaths( paths, out var p ) )
{
return PenumbraApiEc.InvalidGamePath;
}
public PenumbraApiEc RemoveMetaManipulation( string tag, string collectionName, string manipulationBase64 ) if( !ConvertManips( manipCodes, out var m ) )
=> throw new NotImplementedException(); {
return PenumbraApiEc.InvalidManipulation;
}
return Penumbra.TempMods.Register( tag, null, p, m, priority ) switch
{
RedirectResult.Success => PenumbraApiEc.Success,
_ => PenumbraApiEc.UnknownError,
};
}
public PenumbraApiEc AddTemporaryMod( string tag, string collectionName, IReadOnlyDictionary< string, string > paths,
IReadOnlySet< string > manipCodes, int priority )
{
CheckInitialized();
if( !Penumbra.TempMods.Collections.TryGetValue( collectionName, out var collection )
&& !Penumbra.CollectionManager.ByName( collectionName, out collection ) )
{
return PenumbraApiEc.CollectionMissing;
}
if( !ConvertPaths( paths, out var p ) )
{
return PenumbraApiEc.InvalidGamePath;
}
if( !ConvertManips( manipCodes, out var m ) )
{
return PenumbraApiEc.InvalidManipulation;
}
return Penumbra.TempMods.Register( tag, collection, p, m, priority ) switch
{
RedirectResult.Success => PenumbraApiEc.Success,
_ => PenumbraApiEc.UnknownError,
};
}
public PenumbraApiEc RemoveTemporaryModAll( string tag, int priority )
{
CheckInitialized();
return Penumbra.TempMods.Unregister( tag, null, priority ) switch
{
RedirectResult.Success => PenumbraApiEc.Success,
RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged,
_ => PenumbraApiEc.UnknownError,
};
}
public PenumbraApiEc RemoveTemporaryMod( string tag, string collectionName, int priority )
{
CheckInitialized();
if( !Penumbra.TempMods.Collections.TryGetValue( collectionName, out var collection )
&& !Penumbra.CollectionManager.ByName( collectionName, out collection ) )
{
return PenumbraApiEc.CollectionMissing;
}
return Penumbra.TempMods.Unregister( tag, collection, priority ) switch
{
RedirectResult.Success => PenumbraApiEc.Success,
RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged,
_ => PenumbraApiEc.UnknownError,
};
}
internal bool HasTooltip
=> ChangedItemTooltip != null;
internal void InvokeTooltip( object? it )
=> ChangedItemTooltip?.Invoke( it );
internal void InvokeClick( MouseButton button, object? it )
=> ChangedItemClicked?.Invoke( button, it );
private void CheckInitialized()
{
if( !Valid )
{
throw new Exception( "PluginShare is not initialized." );
}
}
private void OnGameObjectRedrawn( IntPtr objectAddress, int objectTableIndex )
{
GameObjectRedrawn?.Invoke( objectAddress, objectTableIndex );
}
// Resolve a path given by string for a specific collection.
private static string ResolvePath( string path, Mod.Manager _, ModCollection collection )
{
if( !Penumbra.Config.EnableMods )
{
return path;
}
var gamePath = Utf8GamePath.FromString( path, out var p, true ) ? p : Utf8GamePath.Empty;
var ret = collection.ResolvePath( gamePath );
return ret?.ToString() ?? path;
}
// Get a file for a resolved path.
private T? GetFileIntern< T >( string resolvedPath ) where T : FileResource
{
CheckInitialized();
try
{
if( Path.IsPathRooted( resolvedPath ) )
{
return _lumina?.GetFileFromDisk< T >( resolvedPath );
}
return Dalamud.GameData.GetFile< T >( resolvedPath );
}
catch( Exception e )
{
PluginLog.Warning( $"Could not load file {resolvedPath}:\n{e}" );
return null;
}
}
// Convert a dictionary of strings to a dictionary of gamepaths to full paths.
// Only returns true if all paths can successfully be converted and added.
private static bool ConvertPaths( IReadOnlyDictionary< string, string > redirections,
[NotNullWhen( true )] out Dictionary< Utf8GamePath, FullPath >? paths )
{
paths = new Dictionary< Utf8GamePath, FullPath >( redirections.Count );
foreach( var (gString, fString) in redirections )
{
if( !Utf8GamePath.FromString( gString, out var path, false ) )
{
paths = null;
return false;
}
var fullPath = new FullPath( fString );
if( !paths.TryAdd( path, fullPath ) )
{
paths = null;
return false;
}
}
return true;
}
// Convert manipulations from transmitted base64 strings to actual manipulations.
// Only returns true if all conversions are successful and distinct.
private static bool ConvertManips( IReadOnlyCollection< string > manipStrings,
[NotNullWhen( true )] out HashSet< MetaManipulation >? manips )
{
manips = new HashSet< MetaManipulation >( manipStrings.Count );
foreach( var m in manipStrings )
{
if( Functions.FromCompressedBase64< MetaManipulation >( m, out var manip ) != MetaManipulation.CurrentVersion )
{
manips = null;
return false;
}
if( !manips.Add( manip ) )
{
manips = null;
return false;
}
}
return true;
}
} }

View file

@ -22,6 +22,8 @@ public partial class PenumbraIpc : IDisposable
InitializeRedrawProviders( pi ); InitializeRedrawProviders( pi );
InitializeChangedItemProviders( pi ); InitializeChangedItemProviders( pi );
InitializeDataProviders( pi ); InitializeDataProviders( pi );
InitializeSettingProviders( pi );
InitializeTempProviders( pi );
ProviderInitialized?.SendMessage(); ProviderInitialized?.SendMessage();
} }
@ -32,6 +34,8 @@ public partial class PenumbraIpc : IDisposable
DisposeRedrawProviders(); DisposeRedrawProviders();
DisposeResolveProviders(); DisposeResolveProviders();
DisposeGeneralProviders(); DisposeGeneralProviders();
DisposeSettingProviders();
DisposeTempProviders();
ProviderDisposed?.SendMessage(); ProviderDisposed?.SendMessage();
} }
} }
@ -402,4 +406,217 @@ public partial class PenumbraIpc
ProviderDefaultCollectionName?.UnregisterFunc(); ProviderDefaultCollectionName?.UnregisterFunc();
ProviderCharacterCollectionName?.UnregisterFunc(); ProviderCharacterCollectionName?.UnregisterFunc();
} }
}
public partial class PenumbraIpc
{
public const string LabelProviderGetAvailableModSettings = "Penumbra.GetAvailableModSettings";
public const string LabelProviderGetCurrentModSettings = "Penumbra.GetCurrentModSettings";
public const string LabelProviderTryInheritMod = "Penumbra.TryInheritMod";
public const string LabelProviderTrySetMod = "Penumbra.TrySetMod";
public const string LabelProviderTrySetModPriority = "Penumbra.TrySetModPriority";
public const string LabelProviderTrySetModSetting = "Penumbra.TrySetModSetting";
public const string LabelProviderTrySetModSettings = "Penumbra.TrySetModSettings";
internal ICallGateProvider< string, string, IDictionary< string, (IList< string >, Mods.SelectType) >? >? ProviderGetAvailableModSettings;
internal ICallGateProvider< string, string, string, bool, (PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)?) >?
ProviderGetCurrentModSettings;
internal ICallGateProvider< string, string, string, bool, PenumbraApiEc >? ProviderTryInheritMod;
internal ICallGateProvider< string, string, string, bool, PenumbraApiEc >? ProviderTrySetMod;
internal ICallGateProvider< string, string, string, int, PenumbraApiEc >? ProviderTrySetModPriority;
internal ICallGateProvider< string, string, string, string, string, PenumbraApiEc >? ProviderTrySetModSetting;
internal ICallGateProvider< string, string, string, string, IReadOnlyList< string >, PenumbraApiEc >? ProviderTrySetModSettings;
private void InitializeSettingProviders( DalamudPluginInterface pi )
{
try
{
ProviderGetAvailableModSettings =
pi.GetIpcProvider< string, string, IDictionary< string, (IList< string >, Mods.SelectType) >? >(
LabelProviderGetAvailableModSettings );
ProviderGetAvailableModSettings.RegisterFunc( Api.GetAvailableModSettings );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetAvailableModSettings}:\n{e}" );
}
try
{
ProviderGetCurrentModSettings =
pi.GetIpcProvider< string, string, string, bool, (PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)?) >(
LabelProviderGetCurrentModSettings );
ProviderGetCurrentModSettings.RegisterFunc( Api.GetCurrentModSettings );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetCurrentModSettings}:\n{e}" );
}
try
{
ProviderTryInheritMod = pi.GetIpcProvider< string, string, string, bool, PenumbraApiEc >( LabelProviderTryInheritMod );
ProviderTryInheritMod.RegisterFunc( Api.TryInheritMod );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderTryInheritMod}:\n{e}" );
}
try
{
ProviderTrySetMod = pi.GetIpcProvider< string, string, string, bool, PenumbraApiEc >( LabelProviderTrySetMod );
ProviderTrySetMod.RegisterFunc( Api.TrySetMod );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderTrySetMod}:\n{e}" );
}
try
{
ProviderTrySetModPriority = pi.GetIpcProvider< string, string, string, int, PenumbraApiEc >( LabelProviderTrySetModPriority );
ProviderTrySetModPriority.RegisterFunc( Api.TrySetModPriority );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderTrySetModPriority}:\n{e}" );
}
try
{
ProviderTrySetModSetting =
pi.GetIpcProvider< string, string, string, string, string, PenumbraApiEc >( LabelProviderTrySetModSetting );
ProviderTrySetModSetting.RegisterFunc( Api.TrySetModSetting );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderTrySetModSetting}:\n{e}" );
}
try
{
ProviderTrySetModSettings =
pi.GetIpcProvider< string, string, string, string, IReadOnlyList< string >, PenumbraApiEc >( LabelProviderTrySetModSettings );
ProviderTrySetModSettings.RegisterFunc( Api.TrySetModSettings );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderTrySetModSettings}:\n{e}" );
}
}
private void DisposeSettingProviders()
{
ProviderGetAvailableModSettings?.UnregisterFunc();
ProviderGetCurrentModSettings?.UnregisterFunc();
ProviderTryInheritMod?.UnregisterFunc();
ProviderTrySetMod?.UnregisterFunc();
ProviderTrySetModPriority?.UnregisterFunc();
ProviderTrySetModSetting?.UnregisterFunc();
ProviderTrySetModSettings?.UnregisterFunc();
}
}
public partial class PenumbraIpc
{
public const string LabelProviderCreateTemporaryCollection = "Penumbra.CreateTemporaryCollection";
public const string LabelProviderRemoveTemporaryCollection = "Penumbra.RemoveTemporaryCollection";
public const string LabelProviderAddTemporaryModAll = "Penumbra.AddTemporaryModAll";
public const string LabelProviderAddTemporaryMod = "Penumbra.AddTemporaryMod";
public const string LabelProviderRemoveTemporaryModAll = "Penumbra.RemoveTemporaryModAll";
public const string LabelProviderRemoveTemporaryMod = "Penumbra.RemoveTemporaryMod";
internal ICallGateProvider< string, string, bool, (PenumbraApiEc, string) >? ProviderCreateTemporaryCollection;
internal ICallGateProvider< string, PenumbraApiEc >? ProviderRemoveTemporaryCollection;
internal ICallGateProvider< string, IReadOnlyDictionary< string, string >, IReadOnlySet< string >, int, PenumbraApiEc >?
ProviderAddTemporaryModAll;
internal ICallGateProvider< string, string, IReadOnlyDictionary< string, string >, IReadOnlySet< string >, int, PenumbraApiEc >?
ProviderAddTemporaryMod;
internal ICallGateProvider< string, int, PenumbraApiEc >? ProviderRemoveTemporaryModAll;
internal ICallGateProvider< string, string, int, PenumbraApiEc >? ProviderRemoveTemporaryMod;
private void InitializeTempProviders( DalamudPluginInterface pi )
{
try
{
ProviderCreateTemporaryCollection =
pi.GetIpcProvider< string, string, bool, (PenumbraApiEc, string) >( LabelProviderCreateTemporaryCollection );
ProviderCreateTemporaryCollection.RegisterFunc( Api.CreateTemporaryCollection );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderCreateTemporaryCollection}:\n{e}" );
}
try
{
ProviderRemoveTemporaryCollection =
pi.GetIpcProvider< string, PenumbraApiEc >( LabelProviderRemoveTemporaryCollection );
ProviderRemoveTemporaryCollection.RegisterFunc( Api.RemoveTemporaryCollection );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderRemoveTemporaryCollection}:\n{e}" );
}
try
{
ProviderAddTemporaryModAll =
pi.GetIpcProvider< string, IReadOnlyDictionary< string, string >, IReadOnlySet< string >, int, PenumbraApiEc >(
LabelProviderAddTemporaryModAll );
ProviderAddTemporaryModAll.RegisterFunc( Api.AddTemporaryModAll );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderAddTemporaryModAll}:\n{e}" );
}
try
{
ProviderAddTemporaryMod =
pi.GetIpcProvider< string, string, IReadOnlyDictionary< string, string >, IReadOnlySet< string >, int, PenumbraApiEc >(
LabelProviderAddTemporaryMod );
ProviderAddTemporaryMod.RegisterFunc( Api.AddTemporaryMod );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderAddTemporaryMod}:\n{e}" );
}
try
{
ProviderRemoveTemporaryModAll = pi.GetIpcProvider< string, int, PenumbraApiEc >( LabelProviderRemoveTemporaryModAll );
ProviderRemoveTemporaryModAll.RegisterFunc( Api.RemoveTemporaryModAll );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderRemoveTemporaryModAll}:\n{e}" );
}
try
{
ProviderRemoveTemporaryMod = pi.GetIpcProvider< string, string, int, PenumbraApiEc >( LabelProviderRemoveTemporaryMod );
ProviderRemoveTemporaryMod.RegisterFunc( Api.RemoveTemporaryMod );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderRemoveTemporaryMod}:\n{e}" );
}
}
private void DisposeTempProviders()
{
ProviderCreateTemporaryCollection?.UnregisterFunc();
ProviderRemoveTemporaryCollection?.UnregisterFunc();
ProviderAddTemporaryModAll?.UnregisterFunc();
ProviderAddTemporaryMod?.UnregisterFunc();
ProviderRemoveTemporaryModAll?.UnregisterFunc();
ProviderRemoveTemporaryMod?.UnregisterFunc();
}
} }

View file

@ -1,125 +0,0 @@
using System;
using System.Collections.Generic;
using Dalamud.Logging;
using Penumbra.Collections;
using Penumbra.GameData.ByteString;
using Penumbra.Mods;
namespace Penumbra.Api;
public enum RedirectResult
{
Registered = 0,
Success = 0,
IdenticalFileRegistered = 1,
InvalidGamePath = 2,
OtherOwner = 3,
NotRegistered = 4,
NoPermission = 5,
FilteredGamePath = 6,
UnknownError = 7,
}
public class SimpleRedirectManager
{
internal readonly Dictionary< Utf8GamePath, (FullPath File, string Tag) > Replacements = new();
public readonly HashSet< string > AllowedTags = new();
public void Apply( IDictionary< Utf8GamePath, ModPath > dict )
{
foreach( var (gamePath, (file, _)) in Replacements )
{
dict.TryAdd( gamePath, new ModPath(Mod.ForcedFiles, file) );
}
}
private RedirectResult? CheckPermission( string tag )
=> AllowedTags.Contains( tag ) ? null : RedirectResult.NoPermission;
public RedirectResult IsRegistered( Utf8GamePath path, string tag )
=> CheckPermission( tag )
?? ( Replacements.TryGetValue( path, out var pair )
? pair.Tag == tag ? RedirectResult.Registered : RedirectResult.OtherOwner
: RedirectResult.NotRegistered );
public RedirectResult Register( Utf8GamePath path, FullPath file, string tag )
{
if( CheckPermission( tag ) != null )
{
return RedirectResult.NoPermission;
}
if( Mod.FilterFile( path ) )
{
return RedirectResult.FilteredGamePath;
}
try
{
if( Replacements.TryGetValue( path, out var pair ) )
{
if( file.Equals( pair.File ) )
{
return RedirectResult.IdenticalFileRegistered;
}
if( tag != pair.Tag )
{
return RedirectResult.OtherOwner;
}
}
Replacements[ path ] = ( file, tag );
return RedirectResult.Success;
}
catch( Exception e )
{
PluginLog.Error( $"[{tag}] Unknown Error registering simple redirect {path} -> {file}:\n{e}" );
return RedirectResult.UnknownError;
}
}
public RedirectResult Unregister( Utf8GamePath path, string tag )
{
if( CheckPermission( tag ) != null )
{
return RedirectResult.NoPermission;
}
try
{
if( !Replacements.TryGetValue( path, out var pair ) )
{
return RedirectResult.NotRegistered;
}
if( tag != pair.Tag )
{
return RedirectResult.OtherOwner;
}
Replacements.Remove( path );
return RedirectResult.Success;
}
catch( Exception e )
{
PluginLog.Error( $"[{tag}] Unknown Error unregistering simple redirect {path}:\n{e}" );
return RedirectResult.UnknownError;
}
}
public RedirectResult Register( string path, string file, string tag )
=> Utf8GamePath.FromString( path, out var gamePath, true )
? Register( gamePath, new FullPath( file ), tag )
: RedirectResult.InvalidGamePath;
public RedirectResult Unregister( string path, string tag )
=> Utf8GamePath.FromString( path, out var gamePath, true )
? Unregister( gamePath, tag )
: RedirectResult.InvalidGamePath;
public RedirectResult IsRegistered( string path, string tag )
=> Utf8GamePath.FromString( path, out var gamePath, true )
? IsRegistered( gamePath, tag )
: RedirectResult.InvalidGamePath;
}

View file

@ -0,0 +1,261 @@
using System.Collections.Generic;
using Penumbra.Collections;
using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
namespace Penumbra.Api;
public enum RedirectResult
{
Success = 0,
IdenticalFileRegistered = 1,
NotRegistered = 2,
FilteredGamePath = 3,
}
public class TempModManager
{
private readonly Dictionary< ModCollection, List< Mod.TemporaryMod > > _mods = new();
private readonly List< Mod.TemporaryMod > _modsForAllCollections = new();
private readonly Dictionary< string, ModCollection > _collections = new();
public IReadOnlyDictionary< ModCollection, List< Mod.TemporaryMod > > Mods
=> _mods;
public IReadOnlyList< Mod.TemporaryMod > ModsForAllCollections
=> _modsForAllCollections;
public IReadOnlyDictionary< string, ModCollection > Collections
=> _collections;
// These functions to check specific redirections or meta manipulations for existence are currently unused.
//public bool IsRegistered( string tag, ModCollection? collection, Utf8GamePath gamePath, out FullPath? fullPath, out int priority )
//{
// var mod = GetExistingMod( tag, collection, null );
// if( mod == null )
// {
// priority = 0;
// fullPath = null;
// return false;
// }
//
// priority = mod.Priority;
// if( mod.Default.Files.TryGetValue( gamePath, out var f ) )
// {
// fullPath = f;
// return true;
// }
//
// fullPath = null;
// return false;
//}
//
//public bool IsRegistered( string tag, ModCollection? collection, MetaManipulation meta, out MetaManipulation? manipulation,
// out int priority )
//{
// var mod = GetExistingMod( tag, collection, null );
// if( mod == null )
// {
// priority = 0;
// manipulation = null;
// return false;
// }
//
// priority = mod.Priority;
// // IReadOnlySet has no TryGetValue for some reason.
// if( ( ( HashSet< MetaManipulation > )mod.Default.Manipulations ).TryGetValue( meta, out var manip ) )
// {
// manipulation = manip;
// return true;
// }
//
// manipulation = null;
// return false;
//}
// These functions for setting single redirections or manips are currently unused.
//public RedirectResult Register( string tag, ModCollection? collection, Utf8GamePath path, FullPath file, int priority )
//{
// if( Mod.FilterFile( path ) )
// {
// return RedirectResult.FilteredGamePath;
// }
//
// var mod = GetOrCreateMod( tag, collection, priority, out var created );
//
// var changes = !mod.Default.Files.TryGetValue( path, out var oldFile ) || !oldFile.Equals( file );
// mod.SetFile( path, file );
// ApplyModChange( mod, collection, created, false );
// return changes ? RedirectResult.IdenticalFileRegistered : RedirectResult.Success;
//}
//
//public RedirectResult Register( string tag, ModCollection? collection, MetaManipulation meta, int priority )
//{
// var mod = GetOrCreateMod( tag, collection, priority, out var created );
// var changes = !( ( HashSet< MetaManipulation > )mod.Default.Manipulations ).TryGetValue( meta, out var oldMeta )
// || !oldMeta.Equals( meta );
// mod.SetManipulation( meta );
// ApplyModChange( mod, collection, created, false );
// return changes ? RedirectResult.IdenticalFileRegistered : RedirectResult.Success;
//}
public RedirectResult Register( string tag, ModCollection? collection, Dictionary< Utf8GamePath, FullPath > dict,
HashSet< MetaManipulation > manips, int priority )
{
var mod = GetOrCreateMod( tag, collection, priority, out var created );
mod.SetAll( dict, manips );
ApplyModChange( mod, collection, created, false );
return RedirectResult.Success;
}
public RedirectResult Unregister( string tag, ModCollection? collection, int? priority )
{
var list = collection == null ? _modsForAllCollections : _mods.TryGetValue( collection, out var l ) ? l : null;
if( list == null )
{
return RedirectResult.NotRegistered;
}
var removed = _modsForAllCollections.RemoveAll( m =>
{
if( m.Name != tag || priority != null && m.Priority != priority.Value )
{
return false;
}
ApplyModChange( m, collection, false, true );
return true;
} );
if( removed == 0 )
{
return RedirectResult.NotRegistered;
}
if( list.Count == 0 && collection != null )
{
_mods.Remove( collection );
}
return RedirectResult.Success;
}
public string SetTemporaryCollection( string tag, string characterName )
{
var collection = ModCollection.CreateNewTemporary( tag, characterName );
_collections[ characterName ] = collection;
return collection.Name;
}
public bool RemoveTemporaryCollection( string characterName )
{
if( _collections.Remove( characterName, out var c ) )
{
_mods.Remove( c );
return true;
}
return false;
}
// Apply any new changes to the temporary mod.
private static void ApplyModChange( Mod.TemporaryMod mod, ModCollection? collection, bool created, bool removed )
{
if( collection == null )
{
if( removed )
{
foreach( var c in Penumbra.CollectionManager )
{
c.Remove( mod );
}
}
else
{
foreach( var c in Penumbra.CollectionManager )
{
c.Apply( mod, created );
}
}
}
else
{
if( removed )
{
collection.Remove( mod );
}
else
{
collection.Apply( mod, created );
}
}
}
// Only find already existing mods, currently unused.
//private Mod.TemporaryMod? GetExistingMod( string tag, ModCollection? collection, int? priority )
//{
// var list = collection == null ? _modsForAllCollections : _mods.TryGetValue( collection, out var l ) ? l : null;
// if( list == null )
// {
// return null;
// }
//
// if( priority != null )
// {
// return list.Find( m => m.Priority == priority.Value && m.Name == tag );
// }
//
// Mod.TemporaryMod? highestMod = null;
// var highestPriority = int.MinValue;
// foreach( var m in list )
// {
// if( highestPriority < m.Priority && m.Name == tag )
// {
// highestPriority = m.Priority;
// highestMod = m;
// }
// }
//
// return highestMod;
//}
// Find or create a mod with the given tag as name and the given priority, for the given collection (or all collections).
// Returns the found or created mod and whether it was newly created.
private Mod.TemporaryMod GetOrCreateMod( string tag, ModCollection? collection, int priority, out bool created )
{
List< Mod.TemporaryMod > list;
if( collection == null )
{
list = _modsForAllCollections;
}
else if( _mods.TryGetValue( collection, out var l ) )
{
list = l;
}
else
{
list = new List< Mod.TemporaryMod >();
_mods.Add( collection, list );
}
var mod = list.Find( m => m.Priority == priority && m.Name == tag );
if( mod == null )
{
mod = new Mod.TemporaryMod()
{
Name = tag,
Priority = priority,
};
list.Add( mod );
created = true;
}
else
{
created = false;
}
return mod;
}
}

View file

@ -35,6 +35,24 @@ public partial class ModCollection
private void ForceCacheUpdate() private void ForceCacheUpdate()
=> CalculateEffectiveFileList(); => CalculateEffectiveFileList();
// Handle temporary mods for this collection.
public void Apply( Mod.TemporaryMod tempMod, bool created )
{
if( created )
{
_cache?.AddMod( tempMod, tempMod.TotalManipulations > 0 );
}
else
{
_cache?.ReloadMod( tempMod, tempMod.TotalManipulations > 0 );
}
}
public void Remove( Mod.TemporaryMod tempMod )
{
_cache?.RemoveMod( tempMod, tempMod.TotalManipulations > 0 );
}
// Clear the current cache. // Clear the current cache.
private void ClearCache() private void ClearCache()

View file

@ -20,10 +20,10 @@ public partial class ModCollection
// It will only be setup if a collection gets activated in any way. // It will only be setup if a collection gets activated in any way.
private class Cache : IDisposable private class Cache : IDisposable
{ {
private readonly ModCollection _collection; private readonly ModCollection _collection;
private readonly SortedList< string, (SingleArray< IMod >, object?) > _changedItems = new(); private readonly SortedList< string, (SingleArray< IMod >, object?) > _changedItems = new();
public readonly Dictionary< Utf8GamePath, ModPath > ResolvedFiles = new(); public readonly Dictionary< Utf8GamePath, ModPath > ResolvedFiles = new();
public readonly MetaManager MetaManipulations; public readonly MetaManager MetaManipulations;
private readonly Dictionary< IMod, SingleArray< ModConflicts > > _conflicts = new(); private readonly Dictionary< IMod, SingleArray< ModConflicts > > _conflicts = new();
public IEnumerable< SingleArray< ModConflicts > > AllConflicts public IEnumerable< SingleArray< ModConflicts > > AllConflicts
@ -160,7 +160,11 @@ public partial class ModCollection
_conflicts.Clear(); _conflicts.Clear();
// Add all forced redirects. // Add all forced redirects.
Penumbra.Redirects.Apply( ResolvedFiles ); foreach( var tempMod in Penumbra.TempMods.ModsForAllCollections.Concat(
Penumbra.TempMods.Mods.TryGetValue( _collection, out var list ) ? list : Array.Empty< Mod.TemporaryMod >() ) )
{
AddMod( tempMod, false );
}
foreach( var mod in Penumbra.ModManager ) foreach( var mod in Penumbra.ModManager )
{ {

View file

@ -74,6 +74,17 @@ public partial class ModCollection
public static ModCollection CreateNewEmpty( string name ) public static ModCollection CreateNewEmpty( string name )
=> new(name, CurrentVersion, new Dictionary< string, ModSettings.SavedSettings >()); => new(name, CurrentVersion, new Dictionary< string, ModSettings.SavedSettings >());
// Create a new temporary collection that does not save and has a negative index.
public static ModCollection CreateNewTemporary(string tag, string characterName)
{
var collection = new ModCollection($"{tag}_{characterName}_temporary", Empty);
collection.ModSettingChanged -= collection.SaveOnChange;
collection.InheritanceChanged -= collection.SaveOnChange;
collection.Index = ~Penumbra.TempMods.Collections.Count;
collection.CreateCache();
return collection;
}
// Duplicate the calling collection to a new, unique collection of a given name. // Duplicate the calling collection to a new, unique collection of a given name.
public ModCollection Duplicate( string name ) public ModCollection Duplicate( string name )
=> new(name, this); => new(name, this);

View file

@ -309,7 +309,8 @@ public unsafe partial class PathResolver
} }
?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString(); ?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString();
return Penumbra.CollectionManager.Character( actualName ); // First check temporary character collections, then the own configuration.
return Penumbra.TempMods.Collections.TryGetValue(actualName, out var c) ? c : Penumbra.CollectionManager.Character( actualName );
} }
// Update collections linked to Game/DrawObjects due to a change in collection configuration. // Update collections linked to Game/DrawObjects due to a change in collection configuration.

View file

@ -19,6 +19,8 @@ public interface IMetaManipulation< T >
[StructLayout( LayoutKind.Explicit, Pack = 1, Size = 16 )] [StructLayout( LayoutKind.Explicit, Pack = 1, Size = 16 )]
public readonly struct MetaManipulation : IEquatable< MetaManipulation >, IComparable< MetaManipulation > public readonly struct MetaManipulation : IEquatable< MetaManipulation >, IComparable< MetaManipulation >
{ {
public const int CurrentVersion = 0;
public enum Type : byte public enum Type : byte
{ {
Unknown = 0, Unknown = 0,

View file

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Mods; namespace Penumbra.Mods;
@ -15,12 +17,27 @@ public sealed partial class Mod
public int TotalManipulations public int TotalManipulations
=> Default.Manipulations.Count; => Default.Manipulations.Count;
public ISubMod Default { get; } = new SubMod(); public ISubMod Default
=> _default;
public IReadOnlyList< IModGroup > Groups public IReadOnlyList< IModGroup > Groups
=> Array.Empty< IModGroup >(); => Array.Empty< IModGroup >();
public IEnumerable< ISubMod > AllSubMods public IEnumerable< ISubMod > AllSubMods
=> new[] { Default }; => new[] { Default };
private readonly SubMod _default = new();
public void SetFile( Utf8GamePath gamePath, FullPath fullPath )
=> _default.FileData[ gamePath ] = fullPath;
public bool SetManipulation( MetaManipulation manip )
=> _default.ManipulationData.Remove( manip ) | _default.ManipulationData.Add( manip );
public void SetAll( Dictionary< Utf8GamePath, FullPath > dict, HashSet< MetaManipulation > manips )
{
_default.FileData = dict;
_default.ManipulationData = manips;
}
} }
} }

View file

@ -57,7 +57,7 @@ public class MainClass : IDalamudPlugin
{ {
#if !DEBUG #if !DEBUG
var path = Path.Combine( Dalamud.PluginInterface.DalamudAssetDirectory.Parent?.FullName ?? "INVALIDPATH", "devPlugins", "Penumbra" ); var path = Path.Combine( Dalamud.PluginInterface.DalamudAssetDirectory.Parent?.FullName ?? "INVALIDPATH", "devPlugins", "Penumbra" );
var dir = new DirectoryInfo( path ); var dir = new DirectoryInfo( path );
try try
{ {
@ -78,7 +78,7 @@ public class MainClass : IDalamudPlugin
{ {
#if !DEBUG #if !DEBUG
var checkedDirectory = Dalamud.PluginInterface.AssemblyLocation.Directory?.Parent?.Parent?.Name; var checkedDirectory = Dalamud.PluginInterface.AssemblyLocation.Directory?.Parent?.Parent?.Name;
var ret = checkedDirectory?.Equals( "installedPlugins", StringComparison.InvariantCultureIgnoreCase ) ?? false; var ret = checkedDirectory?.Equals( "installedPlugins", StringComparison.InvariantCultureIgnoreCase ) ?? false;
if (!ret) if (!ret)
PluginLog.Error($"Penumbra is not correctly installed. Application loaded from \"{Dalamud.PluginInterface.AssemblyLocation.Directory!.FullName}\"." ); PluginLog.Error($"Penumbra is not correctly installed. Application loaded from \"{Dalamud.PluginInterface.AssemblyLocation.Directory!.FullName}\"." );
return !ret; return !ret;
@ -105,7 +105,7 @@ public class Penumbra : IDisposable
public static MetaFileManager MetaFileManager { get; private set; } = null!; public static MetaFileManager MetaFileManager { get; private set; } = null!;
public static Mod.Manager ModManager { get; private set; } = null!; public static Mod.Manager ModManager { get; private set; } = null!;
public static ModCollection.Manager CollectionManager { get; private set; } = null!; public static ModCollection.Manager CollectionManager { get; private set; } = null!;
public static SimpleRedirectManager Redirects { get; private set; } = null!; public static TempModManager TempMods { get; private set; } = null!;
public static ResourceLoader ResourceLoader { get; private set; } = null!; public static ResourceLoader ResourceLoader { get; private set; } = null!;
public static FrameworkManager Framework { get; private set; } = null!; public static FrameworkManager Framework { get; private set; } = null!;
public static int ImcExceptions = 0; public static int ImcExceptions = 0;
@ -138,7 +138,7 @@ public class Penumbra : IDisposable
} }
ResidentResources = new ResidentResourceManager(); ResidentResources = new ResidentResourceManager();
Redirects = new SimpleRedirectManager(); TempMods = new TempModManager();
MetaFileManager = new MetaFileManager(); MetaFileManager = new MetaFileManager();
ResourceLoader = new ResourceLoader( this ); ResourceLoader = new ResourceLoader( this );
ResourceLogger = new ResourceLogger( ResourceLoader ); ResourceLogger = new ResourceLogger( ResourceLoader );

View file

@ -856,8 +856,6 @@ public partial class ModEditWindow
return newValue != currentValue; return newValue != currentValue;
} }
private const byte CurrentManipulationVersion = 0;
private static void CopyToClipboardButton( string tooltip, Vector2 iconSize, IEnumerable< MetaManipulation > manipulations ) private static void CopyToClipboardButton( string tooltip, Vector2 iconSize, IEnumerable< MetaManipulation > manipulations )
{ {
if( !ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Clipboard.ToIconString(), iconSize, tooltip, false, true ) ) if( !ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Clipboard.ToIconString(), iconSize, tooltip, false, true ) )
@ -865,7 +863,7 @@ public partial class ModEditWindow
return; return;
} }
var text = Functions.ToCompressedBase64( manipulations, CurrentManipulationVersion ); var text = Functions.ToCompressedBase64( manipulations, MetaManipulation.CurrentVersion );
if( text.Length > 0 ) if( text.Length > 0 )
{ {
ImGui.SetClipboardText( text ); ImGui.SetClipboardText( text );
@ -878,7 +876,7 @@ public partial class ModEditWindow
{ {
var clipboard = ImGui.GetClipboardText(); var clipboard = ImGui.GetClipboardText();
var version = Functions.FromCompressedBase64< MetaManipulation[] >( clipboard, out var manips ); var version = Functions.FromCompressedBase64< MetaManipulation[] >( clipboard, out var manips );
if( version == CurrentManipulationVersion && manips != null ) if( version == MetaManipulation.CurrentVersion && manips != null )
{ {
foreach( var manip in manips ) foreach( var manip in manips )
{ {
@ -897,7 +895,7 @@ public partial class ModEditWindow
{ {
var clipboard = ImGui.GetClipboardText(); var clipboard = ImGui.GetClipboardText();
var version = Functions.FromCompressedBase64< MetaManipulation[] >( clipboard, out var manips ); var version = Functions.FromCompressedBase64< MetaManipulation[] >( clipboard, out var manips );
if( version == CurrentManipulationVersion && manips != null ) if( version == MetaManipulation.CurrentVersion && manips != null )
{ {
_editor!.Meta.Clear(); _editor!.Meta.Clear();
foreach( var manip in manips ) foreach( var manip in manips )

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
@ -10,8 +11,10 @@ using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.Api; using Penumbra.Api;
using Penumbra.Collections;
using Penumbra.Interop.Loader; using Penumbra.Interop.Loader;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.Mods;
using CharacterUtility = Penumbra.Interop.CharacterUtility; using CharacterUtility = Penumbra.Interop.CharacterUtility;
namespace Penumbra.UI; namespace Penumbra.UI;
@ -413,11 +416,84 @@ public partial class ConfigWindow
foreach( var provider in ipc.GetType().GetFields( BindingFlags.Instance | BindingFlags.NonPublic ) ) foreach( var provider in ipc.GetType().GetFields( BindingFlags.Instance | BindingFlags.NonPublic ) )
{ {
var value = provider.GetValue( ipc ); var value = provider.GetValue( ipc );
if( value != null && dict.TryGetValue( "Label" + provider.Name, out var label )) if( value != null && dict.TryGetValue( "Label" + provider.Name, out var label ) )
{ {
ImGui.TextUnformatted( label ); ImGui.TextUnformatted( label );
} }
} }
using( var collTree = ImRaii.TreeNode( "Collections" ) )
{
if( collTree )
{
using var table = ImRaii.Table( "##collTree", 4 );
if( table )
{
foreach( var (character, collection) in Penumbra.TempMods.Collections )
{
ImGui.TableNextColumn();
ImGui.TextUnformatted( character );
ImGui.TableNextColumn();
ImGui.TextUnformatted( collection.Name );
ImGui.TableNextColumn();
ImGui.TextUnformatted( collection.ResolvedFiles.Count.ToString() );
ImGui.TableNextColumn();
ImGui.TextUnformatted( collection.MetaCache?.Count.ToString() ?? "0" );
}
}
}
}
using( var modTree = ImRaii.TreeNode( "Mods" ) )
{
if( modTree )
{
using var table = ImRaii.Table( "##modTree", 5 );
void PrintList( string collectionName, IReadOnlyList< Mod.TemporaryMod > list )
{
foreach( var mod in list )
{
ImGui.TableNextColumn();
ImGui.TextUnformatted( mod.Name );
ImGui.TableNextColumn();
ImGui.TextUnformatted( mod.Priority.ToString() );
ImGui.TableNextColumn();
ImGui.TextUnformatted( collectionName );
ImGui.TableNextColumn();
ImGui.TextUnformatted( mod.Default.Files.Count.ToString() );
if( ImGui.IsItemHovered() )
{
using var tt = ImRaii.Tooltip();
foreach( var (path, file) in mod.Default.Files )
{
ImGui.TextUnformatted( $"{path} -> {file}" );
}
}
ImGui.TableNextColumn();
ImGui.TextUnformatted( mod.TotalManipulations.ToString() );
if( ImGui.IsItemHovered() )
{
using var tt = ImRaii.Tooltip();
foreach( var manip in mod.Default.Manipulations )
{
ImGui.TextUnformatted( manip.ToString() );
}
}
}
}
if( table )
{
PrintList( "All", Penumbra.TempMods.ModsForAllCollections );
foreach( var (collection, list) in Penumbra.TempMods.Mods )
{
PrintList( collection.Name, list );
}
}
}
}
} }
// Helper to print a property and its value in a 2-column table. // Helper to print a property and its value in a 2-column table.