mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Add Mare Synchronos and MUI API/IPC functions for testing. Not tested myself because how.
This commit is contained in:
parent
8422d36e4e
commit
d6d13594e0
15 changed files with 857 additions and 240 deletions
|
|
@ -1,13 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Lumina.Data;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
|
@ -24,7 +20,7 @@ public delegate void GameObjectRedrawn( IntPtr objectPtr, int objectTableIndex )
|
|||
|
||||
public enum PenumbraApiEc
|
||||
{
|
||||
Okay = 0,
|
||||
Success = 0,
|
||||
NothingChanged = 1,
|
||||
CollectionMissing = 2,
|
||||
ModMissing = 3,
|
||||
|
|
@ -37,6 +33,7 @@ public enum PenumbraApiEc
|
|||
FileMissing = 9,
|
||||
InvalidManipulation = 10,
|
||||
InvalidArgument = 11,
|
||||
UnknownError = 255,
|
||||
}
|
||||
|
||||
public interface IPenumbraApi : IPenumbraApiBase
|
||||
|
|
@ -136,32 +133,36 @@ public interface IPenumbraApi : IPenumbraApiBase
|
|||
// 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,
|
||||
public PenumbraApiEc TrySetModSettings( string collectionName, string modDirectory, string modName, string optionGroupName,
|
||||
IReadOnlyList< string > options );
|
||||
|
||||
|
||||
// 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.
|
||||
// Can return Okay, CharacterCollectionExists or NothingChanged.
|
||||
public PenumbraApiEc CreateTemporaryCollection( string collectionName, string? character, bool forceOverwriteCharacter );
|
||||
// Can return Okay, CharacterCollectionExists or NothingChanged, as well as the name of the new temporary collection on success.
|
||||
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.
|
||||
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
|
||||
// for a given collection, which may be temporary.
|
||||
// Can return Okay, CollectionMissing, InvalidPath, FileMissing, LowerPriority, or NothingChanged.
|
||||
public PenumbraApiEc SetFileRedirection( string tag, string collectionName, string gamePath, string fullPath, int 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.
|
||||
// Can return Okay, MissingCollection InvalidGamePath, or InvalidManipulation.
|
||||
public PenumbraApiEc AddTemporaryMod( string tag, string collectionName, IReadOnlyDictionary< string, string > paths,
|
||||
IReadOnlySet< string > manipCodes,
|
||||
int priority );
|
||||
|
||||
// Can return Okay, CollectionMissing, InvalidManipulation, LowerPriority, or NothingChanged.
|
||||
public PenumbraApiEc SetMetaManipulation( string tag, string collectionName, string manipulationBase64, int priority );
|
||||
// Remove the temporary mod with the given tag and priority from the temporary mods applying to all collections, if it exists.
|
||||
// Can return Okay or NothingDone.
|
||||
public PenumbraApiEc RemoveTemporaryModAll( string tag, int priority );
|
||||
|
||||
// Can return Okay, CollectionMissing, InvalidPath, or NothingChanged.
|
||||
public PenumbraApiEc RemoveFileRedirection( string tag, string collectionName, string gamePath );
|
||||
|
||||
// Can return Okay, CollectionMissing, InvalidManipulation, or NothingChanged.
|
||||
public PenumbraApiEc RemoveMetaManipulation( string tag, string collectionName, string manipulationBase64 );
|
||||
// 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.
|
||||
// Can return Okay or NothingDone.
|
||||
public PenumbraApiEc RemoveTemporaryMod( string tag, string collectionName, int priority );
|
||||
}
|
||||
|
|
@ -37,7 +37,6 @@ public class ModsController : WebApiController
|
|||
return Penumbra.CollectionManager.Current.ResolvedFiles.ToDictionary(
|
||||
o => o.Key.ToString(),
|
||||
o => o.Value.Path.FullName
|
||||
)
|
||||
?? new Dictionary< string, string >();
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,11 @@ using Dalamud.Game.ClientState.Objects.Types;
|
|||
using Dalamud.Logging;
|
||||
using Lumina.Data;
|
||||
using Newtonsoft.Json;
|
||||
using OtterGui;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
|
|
@ -61,24 +63,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
|
||||
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 )
|
||||
{
|
||||
CheckInitialized();
|
||||
|
|
@ -97,29 +81,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
_penumbra!.ObjectReloader.RedrawObject( gameObject, setting );
|
||||
}
|
||||
|
||||
private void OnGameObjectRedrawn( IntPtr objectAddress, int objectTableIndex )
|
||||
{
|
||||
GameObjectRedrawn?.Invoke( objectAddress, objectTableIndex );
|
||||
}
|
||||
|
||||
public void RedrawAll( RedrawType setting )
|
||||
{
|
||||
CheckInitialized();
|
||||
_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 )
|
||||
{
|
||||
CheckInitialized();
|
||||
|
|
@ -145,25 +112,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
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
|
||||
=> GetFileIntern< T >( ResolvePath( gamePath ) );
|
||||
|
||||
|
|
@ -259,11 +207,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
var settings = allowInheritance ? collection.Settings[ mod.Index ] : collection[ mod.Index ].Settings;
|
||||
if( settings == null )
|
||||
{
|
||||
return ( PenumbraApiEc.Okay, null );
|
||||
return ( PenumbraApiEc.Success, null );
|
||||
}
|
||||
|
||||
var shareSettings = settings.ConvertToShareable( mod );
|
||||
return ( PenumbraApiEc.Okay,
|
||||
return ( PenumbraApiEc.Success,
|
||||
( 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 )
|
||||
|
|
@ -297,7 +245,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
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 )
|
||||
|
|
@ -313,7 +261,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
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,
|
||||
|
|
@ -344,10 +292,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
|
||||
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 )
|
||||
{
|
||||
CheckInitialized();
|
||||
|
|
@ -378,7 +326,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
if( group.Type == SelectType.Single )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
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 )
|
||||
=> throw new NotImplementedException();
|
||||
public (PenumbraApiEc, string) CreateTemporaryCollection( string tag, string character, bool forceOverwriteCharacter )
|
||||
{
|
||||
CheckInitialized();
|
||||
if( !forceOverwriteCharacter && Penumbra.CollectionManager.Characters.ContainsKey( character )
|
||||
|| Penumbra.TempMods.Collections.ContainsKey( character ) )
|
||||
{
|
||||
return ( PenumbraApiEc.CharacterCollectionExists, string.Empty );
|
||||
}
|
||||
|
||||
public PenumbraApiEc RemoveTemporaryCollection( string collectionName )
|
||||
=> throw new NotImplementedException();
|
||||
var name = Penumbra.TempMods.SetTemporaryCollection( tag, character );
|
||||
return ( PenumbraApiEc.Success, name );
|
||||
}
|
||||
|
||||
public PenumbraApiEc SetFileRedirection( string tag, string collectionName, string gamePath, string fullPath, int priority )
|
||||
=> throw new NotImplementedException();
|
||||
public PenumbraApiEc RemoveTemporaryCollection( string character )
|
||||
{
|
||||
CheckInitialized();
|
||||
if( !Penumbra.TempMods.Collections.ContainsKey( character ) )
|
||||
{
|
||||
return PenumbraApiEc.NothingChanged;
|
||||
}
|
||||
|
||||
public PenumbraApiEc SetMetaManipulation( string tag, string collectionName, string manipulationBase64, int priority )
|
||||
=> throw new NotImplementedException();
|
||||
Penumbra.TempMods.RemoveTemporaryCollection( character );
|
||||
return PenumbraApiEc.Success;
|
||||
}
|
||||
|
||||
public PenumbraApiEc RemoveFileRedirection( string tag, string collectionName, string gamePath )
|
||||
=> throw new NotImplementedException();
|
||||
public PenumbraApiEc AddTemporaryModAll( string tag, IReadOnlyDictionary< string, string > paths, IReadOnlySet< string > manipCodes,
|
||||
int priority )
|
||||
{
|
||||
CheckInitialized();
|
||||
if( !ConvertPaths( paths, out var p ) )
|
||||
{
|
||||
return PenumbraApiEc.InvalidGamePath;
|
||||
}
|
||||
|
||||
public PenumbraApiEc RemoveMetaManipulation( string tag, string collectionName, string manipulationBase64 )
|
||||
=> throw new NotImplementedException();
|
||||
if( !ConvertManips( manipCodes, out var m ) )
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,8 @@ public partial class PenumbraIpc : IDisposable
|
|||
InitializeRedrawProviders( pi );
|
||||
InitializeChangedItemProviders( pi );
|
||||
InitializeDataProviders( pi );
|
||||
InitializeSettingProviders( pi );
|
||||
InitializeTempProviders( pi );
|
||||
ProviderInitialized?.SendMessage();
|
||||
}
|
||||
|
||||
|
|
@ -32,6 +34,8 @@ public partial class PenumbraIpc : IDisposable
|
|||
DisposeRedrawProviders();
|
||||
DisposeResolveProviders();
|
||||
DisposeGeneralProviders();
|
||||
DisposeSettingProviders();
|
||||
DisposeTempProviders();
|
||||
ProviderDisposed?.SendMessage();
|
||||
}
|
||||
}
|
||||
|
|
@ -402,4 +406,217 @@ public partial class PenumbraIpc
|
|||
ProviderDefaultCollectionName?.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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
261
Penumbra/Api/TempModManager.cs
Normal file
261
Penumbra/Api/TempModManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,24 @@ public partial class ModCollection
|
|||
private void ForceCacheUpdate()
|
||||
=> 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.
|
||||
private void ClearCache()
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ public partial class ModCollection
|
|||
// It will only be setup if a collection gets activated in any way.
|
||||
private class Cache : IDisposable
|
||||
{
|
||||
private readonly ModCollection _collection;
|
||||
private readonly ModCollection _collection;
|
||||
private readonly SortedList< string, (SingleArray< IMod >, object?) > _changedItems = new();
|
||||
public readonly Dictionary< Utf8GamePath, ModPath > ResolvedFiles = new();
|
||||
public readonly MetaManager MetaManipulations;
|
||||
public readonly Dictionary< Utf8GamePath, ModPath > ResolvedFiles = new();
|
||||
public readonly MetaManager MetaManipulations;
|
||||
private readonly Dictionary< IMod, SingleArray< ModConflicts > > _conflicts = new();
|
||||
|
||||
public IEnumerable< SingleArray< ModConflicts > > AllConflicts
|
||||
|
|
@ -160,7 +160,11 @@ public partial class ModCollection
|
|||
_conflicts.Clear();
|
||||
|
||||
// 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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -74,6 +74,17 @@ public partial class ModCollection
|
|||
public static ModCollection CreateNewEmpty( string name )
|
||||
=> 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.
|
||||
public ModCollection Duplicate( string name )
|
||||
=> new(name, this);
|
||||
|
|
|
|||
|
|
@ -309,7 +309,8 @@ public unsafe partial class PathResolver
|
|||
}
|
||||
?? 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.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ public interface IMetaManipulation< T >
|
|||
[StructLayout( LayoutKind.Explicit, Pack = 1, Size = 16 )]
|
||||
public readonly struct MetaManipulation : IEquatable< MetaManipulation >, IComparable< MetaManipulation >
|
||||
{
|
||||
public const int CurrentVersion = 0;
|
||||
|
||||
public enum Type : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
|
|
@ -15,12 +17,27 @@ public sealed partial class Mod
|
|||
public int TotalManipulations
|
||||
=> Default.Manipulations.Count;
|
||||
|
||||
public ISubMod Default { get; } = new SubMod();
|
||||
public ISubMod Default
|
||||
=> _default;
|
||||
|
||||
public IReadOnlyList< IModGroup > Groups
|
||||
=> Array.Empty< IModGroup >();
|
||||
|
||||
public IEnumerable< ISubMod > AllSubMods
|
||||
=> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ public class MainClass : IDalamudPlugin
|
|||
{
|
||||
#if !DEBUG
|
||||
var path = Path.Combine( Dalamud.PluginInterface.DalamudAssetDirectory.Parent?.FullName ?? "INVALIDPATH", "devPlugins", "Penumbra" );
|
||||
var dir = new DirectoryInfo( path );
|
||||
var dir = new DirectoryInfo( path );
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -78,7 +78,7 @@ public class MainClass : IDalamudPlugin
|
|||
{
|
||||
#if !DEBUG
|
||||
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)
|
||||
PluginLog.Error($"Penumbra is not correctly installed. Application loaded from \"{Dalamud.PluginInterface.AssemblyLocation.Directory!.FullName}\"." );
|
||||
return !ret;
|
||||
|
|
@ -105,7 +105,7 @@ public class Penumbra : IDisposable
|
|||
public static MetaFileManager MetaFileManager { get; private set; } = null!;
|
||||
public static Mod.Manager ModManager { 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 FrameworkManager Framework { get; private set; } = null!;
|
||||
public static int ImcExceptions = 0;
|
||||
|
|
@ -138,7 +138,7 @@ public class Penumbra : IDisposable
|
|||
}
|
||||
|
||||
ResidentResources = new ResidentResourceManager();
|
||||
Redirects = new SimpleRedirectManager();
|
||||
TempMods = new TempModManager();
|
||||
MetaFileManager = new MetaFileManager();
|
||||
ResourceLoader = new ResourceLoader( this );
|
||||
ResourceLogger = new ResourceLogger( ResourceLoader );
|
||||
|
|
|
|||
|
|
@ -856,8 +856,6 @@ public partial class ModEditWindow
|
|||
return newValue != currentValue;
|
||||
}
|
||||
|
||||
private const byte CurrentManipulationVersion = 0;
|
||||
|
||||
private static void CopyToClipboardButton( string tooltip, Vector2 iconSize, IEnumerable< MetaManipulation > manipulations )
|
||||
{
|
||||
if( !ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Clipboard.ToIconString(), iconSize, tooltip, false, true ) )
|
||||
|
|
@ -865,7 +863,7 @@ public partial class ModEditWindow
|
|||
return;
|
||||
}
|
||||
|
||||
var text = Functions.ToCompressedBase64( manipulations, CurrentManipulationVersion );
|
||||
var text = Functions.ToCompressedBase64( manipulations, MetaManipulation.CurrentVersion );
|
||||
if( text.Length > 0 )
|
||||
{
|
||||
ImGui.SetClipboardText( text );
|
||||
|
|
@ -878,7 +876,7 @@ public partial class ModEditWindow
|
|||
{
|
||||
var clipboard = ImGui.GetClipboardText();
|
||||
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 )
|
||||
{
|
||||
|
|
@ -897,7 +895,7 @@ public partial class ModEditWindow
|
|||
{
|
||||
var clipboard = ImGui.GetClipboardText();
|
||||
var version = Functions.FromCompressedBase64< MetaManipulation[] >( clipboard, out var manips );
|
||||
if( version == CurrentManipulationVersion && manips != null )
|
||||
if( version == MetaManipulation.CurrentVersion && manips != null )
|
||||
{
|
||||
_editor!.Meta.Clear();
|
||||
foreach( var manip in manips )
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
|
@ -10,8 +11,10 @@ using ImGuiNET;
|
|||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Interop.Loader;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Mods;
|
||||
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
|
@ -413,11 +416,84 @@ public partial class ConfigWindow
|
|||
foreach( var provider in ipc.GetType().GetFields( BindingFlags.Instance | BindingFlags.NonPublic ) )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue