mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Merge branch 'xivdev:master' into master
This commit is contained in:
commit
81b3a12341
44 changed files with 1384 additions and 381 deletions
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
|
@ -75,10 +75,10 @@ jobs:
|
||||||
git config --global user.name "Actions User"
|
git config --global user.name "Actions User"
|
||||||
git config --global user.email "actions@github.com"
|
git config --global user.email "actions@github.com"
|
||||||
|
|
||||||
git fetch origin master && git fetch origin test && git branch -f test origin/master && git checkout master
|
git fetch origin master && git fetch origin test && git checkout master
|
||||||
git add repo.json
|
git add repo.json
|
||||||
git commit -m "[CI] Updating repo.json for ${{ github.ref }}" || true
|
git commit -m "[CI] Updating repo.json for ${{ github.ref }}" || true
|
||||||
|
|
||||||
git push origin master || true
|
git push origin master || true
|
||||||
git checkout test
|
git branch -f test origin/master && git checkout test
|
||||||
git push origin test -f || true
|
git push origin test -f || true
|
||||||
|
|
|
||||||
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit d97a26923981db2a27d0172367c9e2841767f9b1
|
Subproject commit 6ce8ca816678e7a363f9f4a6f43f009f8d79c070
|
||||||
|
|
@ -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,18 +20,20 @@ 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,
|
||||||
OptionGroupMissing = 4,
|
OptionGroupMissing = 4,
|
||||||
SettingMissing = 5,
|
OptionMissing = 5,
|
||||||
|
|
||||||
CharacterCollectionExists = 6,
|
CharacterCollectionExists = 6,
|
||||||
LowerPriority = 7,
|
LowerPriority = 7,
|
||||||
InvalidGamePath = 8,
|
InvalidGamePath = 8,
|
||||||
FileMissing = 9,
|
FileMissing = 9,
|
||||||
InvalidManipulation = 10,
|
InvalidManipulation = 10,
|
||||||
|
InvalidArgument = 11,
|
||||||
|
UnknownError = 255,
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IPenumbraApi : IPenumbraApiBase
|
public interface IPenumbraApi : IPenumbraApiBase
|
||||||
|
|
@ -75,7 +73,7 @@ public interface IPenumbraApi : IPenumbraApiBase
|
||||||
public string ResolvePath( string gamePath, string characterName );
|
public string ResolvePath( string gamePath, string characterName );
|
||||||
|
|
||||||
// Reverse resolves a given modded local path into its replacement in form of all applicable game path for given character
|
// Reverse resolves a given modded local path into its replacement in form of all applicable game path for given character
|
||||||
public IList<string> ReverseResolvePath( string moddedPath, string characterName );
|
public IList< string > ReverseResolvePath( string moddedPath, string characterName );
|
||||||
|
|
||||||
// Try to load a given gamePath with the resolved path from Penumbra.
|
// Try to load a given gamePath with the resolved path from Penumbra.
|
||||||
public T? GetFile< T >( string gamePath ) where T : FileResource;
|
public T? GetFile< T >( string gamePath ) where T : FileResource;
|
||||||
|
|
@ -109,12 +107,12 @@ public interface IPenumbraApi : IPenumbraApiBase
|
||||||
|
|
||||||
// Obtain the potential settings of a mod specified by its directory name first or mod name second.
|
// Obtain the potential settings of a mod specified by its directory name first or mod name second.
|
||||||
// Returns null if the mod could not be found.
|
// Returns null if the mod could not be found.
|
||||||
public IDictionary< string, (IList<string>, SelectType) >? GetAvailableModSettings( string modDirectory, string modName );
|
public IDictionary< string, (IList< string >, SelectType) >? GetAvailableModSettings( string modDirectory, string modName );
|
||||||
|
|
||||||
// Obtain the enabled state, the priority, the settings of a mod specified by its directory name first or mod name second,
|
// Obtain the enabled state, the priority, the settings of a mod specified by its directory name first or mod name second,
|
||||||
// and whether these settings are inherited, or null if the collection does not set them at all.
|
// and whether these settings are inherited, or null if the collection does not set them at all.
|
||||||
// If allowInheritance is false, only the collection itself will be checked.
|
// If allowInheritance is false, only the collection itself will be checked.
|
||||||
public (PenumbraApiEc, (bool, int, IDictionary< string, IList<string> >, bool)?) GetCurrentModSettings( string collectionName,
|
public (PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)?) GetCurrentModSettings( string collectionName,
|
||||||
string modDirectory, string modName, bool allowInheritance );
|
string modDirectory, string modName, bool allowInheritance );
|
||||||
|
|
||||||
// Try to set the inheritance state in the given collection of a mod specified by its directory name first or mod name second.
|
// Try to set the inheritance state in the given collection of a mod specified by its directory name first or mod name second.
|
||||||
|
|
@ -135,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 );
|
|
||||||
}
|
}
|
||||||
|
|
@ -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 >();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -8,16 +9,21 @@ 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;
|
||||||
|
|
||||||
namespace Penumbra.Api;
|
namespace Penumbra.Api;
|
||||||
|
|
||||||
public class PenumbraApi : IDisposable, IPenumbraApi
|
public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
public int ApiVersion { get; } = 4;
|
public int ApiVersion
|
||||||
|
=> 4;
|
||||||
|
|
||||||
private Penumbra? _penumbra;
|
private Penumbra? _penumbra;
|
||||||
private Lumina.GameData? _lumina;
|
private Lumina.GameData? _lumina;
|
||||||
public event GameObjectRedrawn? GameObjectRedrawn;
|
public event GameObjectRedrawn? GameObjectRedrawn;
|
||||||
|
|
@ -57,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();
|
||||||
|
|
@ -93,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();
|
||||||
|
|
@ -141,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 ) );
|
||||||
|
|
||||||
|
|
@ -231,44 +183,378 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDictionary< string, (IList< string >, SelectType) >? GetAvailableModSettings( string modDirectory, string modName )
|
public IDictionary< string, (IList< string >, SelectType) >? GetAvailableModSettings( string modDirectory, string modName )
|
||||||
=> throw new NotImplementedException();
|
{
|
||||||
|
CheckInitialized();
|
||||||
|
return Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod )
|
||||||
|
? mod.Groups.ToDictionary( g => g.Name, g => ( ( IList< string > )g.Select( o => o.Name ).ToList(), g.Type ) )
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
public (PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)?) GetCurrentModSettings( string collectionName,
|
public (PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)?) GetCurrentModSettings( string collectionName,
|
||||||
string modDirectory, string modName,
|
string modDirectory, string modName, bool allowInheritance )
|
||||||
bool allowInheritance )
|
{
|
||||||
=> throw new NotImplementedException();
|
CheckInitialized();
|
||||||
|
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
|
||||||
|
{
|
||||||
|
return ( PenumbraApiEc.CollectionMissing, null );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
|
||||||
|
{
|
||||||
|
return ( PenumbraApiEc.ModMissing, null );
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings = allowInheritance ? collection.Settings[ mod.Index ] : collection[ mod.Index ].Settings;
|
||||||
|
if( settings == null )
|
||||||
|
{
|
||||||
|
return ( PenumbraApiEc.Success, null );
|
||||||
|
}
|
||||||
|
|
||||||
|
var shareSettings = settings.ConvertToShareable( mod );
|
||||||
|
return ( PenumbraApiEc.Success,
|
||||||
|
( shareSettings.Enabled, shareSettings.Priority, shareSettings.Settings, collection.Settings[ mod.Index ] != null ) );
|
||||||
|
}
|
||||||
|
|
||||||
public PenumbraApiEc TryInheritMod( string collectionName, string modDirectory, string modName, bool inherit )
|
public PenumbraApiEc TryInheritMod( string collectionName, string modDirectory, string modName, bool inherit )
|
||||||
=> throw new NotImplementedException();
|
{
|
||||||
|
CheckInitialized();
|
||||||
|
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.CollectionMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.ModMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 )
|
||||||
=> throw new NotImplementedException();
|
{
|
||||||
|
CheckInitialized();
|
||||||
|
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.CollectionMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.ModMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 )
|
||||||
=> throw new NotImplementedException();
|
{
|
||||||
|
CheckInitialized();
|
||||||
|
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.CollectionMissing;
|
||||||
|
}
|
||||||
|
|
||||||
public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, string option )
|
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
|
||||||
=> throw new NotImplementedException();
|
{
|
||||||
|
return PenumbraApiEc.ModMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
IReadOnlyList< string > options )
|
string optionName )
|
||||||
=> throw new NotImplementedException();
|
{
|
||||||
|
CheckInitialized();
|
||||||
|
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.CollectionMissing;
|
||||||
|
}
|
||||||
|
|
||||||
public PenumbraApiEc CreateTemporaryCollection( string collectionName, string? character, bool forceOverwriteCharacter )
|
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
|
||||||
=> throw new NotImplementedException();
|
{
|
||||||
|
return PenumbraApiEc.ModMissing;
|
||||||
|
}
|
||||||
|
|
||||||
public PenumbraApiEc RemoveTemporaryCollection( string collectionName )
|
var groupIdx = mod.Groups.IndexOf( g => g.Name == optionGroupName );
|
||||||
=> throw new NotImplementedException();
|
if( groupIdx < 0 )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.OptionGroupMissing;
|
||||||
|
}
|
||||||
|
|
||||||
public PenumbraApiEc SetFileRedirection( string tag, string collectionName, string gamePath, string fullPath, int priority )
|
var optionIdx = mod.Groups[ groupIdx ].IndexOf( o => o.Name == optionName );
|
||||||
=> throw new NotImplementedException();
|
if( optionIdx < 0 )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.OptionMissing;
|
||||||
|
}
|
||||||
|
|
||||||
public PenumbraApiEc SetMetaManipulation( string tag, string collectionName, string manipulationBase64, int priority )
|
var setting = mod.Groups[ groupIdx ].Type == SelectType.Multi ? 1u << optionIdx : ( uint )optionIdx;
|
||||||
=> throw new NotImplementedException();
|
|
||||||
|
|
||||||
public PenumbraApiEc RemoveFileRedirection( string tag, string collectionName, string gamePath )
|
return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
|
||||||
=> throw new NotImplementedException();
|
}
|
||||||
|
|
||||||
public PenumbraApiEc RemoveMetaManipulation( string tag, string collectionName, string manipulationBase64 )
|
public PenumbraApiEc TrySetModSettings( string collectionName, string modDirectory, string modName, string optionGroupName,
|
||||||
=> throw new NotImplementedException();
|
IReadOnlyList< string > optionNames )
|
||||||
|
{
|
||||||
|
CheckInitialized();
|
||||||
|
if( optionNames.Count == 0 )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.InvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.CollectionMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !Penumbra.ModManager.TryGetMod( modDirectory, modName, out var mod ) )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.ModMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupIdx = mod.Groups.IndexOf( g => g.Name == optionGroupName );
|
||||||
|
if( groupIdx < 0 )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.OptionGroupMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
var group = mod.Groups[ groupIdx ];
|
||||||
|
|
||||||
|
uint setting = 0;
|
||||||
|
if( group.Type == SelectType.Single )
|
||||||
|
{
|
||||||
|
var name = optionNames[ ^1 ];
|
||||||
|
var optionIdx = group.IndexOf( o => o.Name == optionNames[ ^1 ] );
|
||||||
|
if( optionIdx < 0 )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.OptionMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
setting = ( uint )optionIdx;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach( var name in optionNames )
|
||||||
|
{
|
||||||
|
var optionIdx = group.IndexOf( o => o.Name == name );
|
||||||
|
if( optionIdx < 0 )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.OptionMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
setting |= 1u << optionIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = Penumbra.TempMods.SetTemporaryCollection( tag, character );
|
||||||
|
return ( PenumbraApiEc.Success, name );
|
||||||
|
}
|
||||||
|
|
||||||
|
public PenumbraApiEc RemoveTemporaryCollection( string character )
|
||||||
|
{
|
||||||
|
CheckInitialized();
|
||||||
|
if( !Penumbra.TempMods.Collections.ContainsKey( character ) )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.NothingChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
Penumbra.TempMods.RemoveTemporaryCollection( character );
|
||||||
|
return PenumbraApiEc.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PenumbraApiEc AddTemporaryModAll( string tag, IReadOnlyDictionary< string, string > paths, IReadOnlySet< string > manipCodes,
|
||||||
|
int priority )
|
||||||
|
{
|
||||||
|
CheckInitialized();
|
||||||
|
if( !ConvertPaths( paths, out var p ) )
|
||||||
|
{
|
||||||
|
return PenumbraApiEc.InvalidGamePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 );
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -81,11 +81,14 @@ public partial class ModCollection
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentCollectionInUse = Characters.Values.Prepend( Default ).SelectMany( c => c.GetFlattenedInheritance() ).Contains( Current );
|
|
||||||
|
UpdateCurrentCollectionInUse();
|
||||||
CollectionChanged.Invoke( type, this[ oldCollectionIdx ], newCollection, characterName );
|
CollectionChanged.Invoke( type, this[ oldCollectionIdx ], newCollection, characterName );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateCurrentCollectionInUse()
|
||||||
|
=> CurrentCollectionInUse = Characters.Values.Prepend( Default ).SelectMany( c => c.GetFlattenedInheritance() ).Contains( Current );
|
||||||
|
|
||||||
public void SetCollection( ModCollection collection, Type type, string? characterName = null )
|
public void SetCollection( ModCollection collection, Type type, string? characterName = null )
|
||||||
=> SetCollection( collection.Index, type, characterName );
|
=> SetCollection( collection.Index, type, characterName );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ public partial class ModCollection
|
||||||
CollectionChanged += SaveOnChange;
|
CollectionChanged += SaveOnChange;
|
||||||
ReadCollections();
|
ReadCollections();
|
||||||
LoadCollections();
|
LoadCollections();
|
||||||
|
UpdateCurrentCollectionInUse();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -65,8 +83,8 @@ public partial class ModCollection
|
||||||
internal IReadOnlyDictionary< Utf8GamePath, ModPath > ResolvedFiles
|
internal IReadOnlyDictionary< Utf8GamePath, ModPath > ResolvedFiles
|
||||||
=> _cache?.ResolvedFiles ?? new Dictionary< Utf8GamePath, ModPath >();
|
=> _cache?.ResolvedFiles ?? new Dictionary< Utf8GamePath, ModPath >();
|
||||||
|
|
||||||
internal IReadOnlyDictionary< string, (SingleArray< Mod >, object?) > ChangedItems
|
internal IReadOnlyDictionary< string, (SingleArray< IMod >, object?) > ChangedItems
|
||||||
=> _cache?.ChangedItems ?? new Dictionary< string, (SingleArray< Mod >, object?) >();
|
=> _cache?.ChangedItems ?? new Dictionary< string, (SingleArray< IMod >, object?) >();
|
||||||
|
|
||||||
internal IEnumerable< SingleArray< ModConflicts > > AllConflicts
|
internal IEnumerable< SingleArray< ModConflicts > > AllConflicts
|
||||||
=> _cache?.AllConflicts ?? Array.Empty< SingleArray< ModConflicts > >();
|
=> _cache?.AllConflicts ?? Array.Empty< SingleArray< ModConflicts > >();
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Collections;
|
namespace Penumbra.Collections;
|
||||||
|
|
||||||
public record struct ModPath( Mod Mod, FullPath Path );
|
public record struct ModPath( IMod Mod, FullPath Path );
|
||||||
public record ModConflicts( Mod Mod2, List< object > Conflicts, bool HasPriority, bool Solved );
|
public record ModConflicts( IMod Mod2, List< object > Conflicts, bool HasPriority, bool Solved );
|
||||||
|
|
||||||
public partial class ModCollection
|
public partial class ModCollection
|
||||||
{
|
{
|
||||||
|
|
@ -20,16 +20,16 @@ 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< Mod >, 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< Mod, SingleArray< ModConflicts > > _conflicts = new();
|
private readonly Dictionary< IMod, SingleArray< ModConflicts > > _conflicts = new();
|
||||||
|
|
||||||
public IEnumerable< SingleArray< ModConflicts > > AllConflicts
|
public IEnumerable< SingleArray< ModConflicts > > AllConflicts
|
||||||
=> _conflicts.Values;
|
=> _conflicts.Values;
|
||||||
|
|
||||||
public SingleArray< ModConflicts > Conflicts( Mod mod )
|
public SingleArray< ModConflicts > Conflicts( IMod mod )
|
||||||
=> _conflicts.TryGetValue( mod, out var c ) ? c : new SingleArray< ModConflicts >();
|
=> _conflicts.TryGetValue( mod, out var c ) ? c : new SingleArray< ModConflicts >();
|
||||||
|
|
||||||
// Count the number of changes of the effective file list.
|
// Count the number of changes of the effective file list.
|
||||||
|
|
@ -38,7 +38,7 @@ public partial class ModCollection
|
||||||
private int _changedItemsSaveCounter = -1;
|
private int _changedItemsSaveCounter = -1;
|
||||||
|
|
||||||
// Obtain currently changed items. Computes them if they haven't been computed before.
|
// Obtain currently changed items. Computes them if they haven't been computed before.
|
||||||
public IReadOnlyDictionary< string, (SingleArray< Mod >, object?) > ChangedItems
|
public IReadOnlyDictionary< string, (SingleArray< IMod >, object?) > ChangedItems
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -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 )
|
||||||
{
|
{
|
||||||
|
|
@ -178,13 +182,13 @@ public partial class ModCollection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadMod( Mod mod, bool addMetaChanges )
|
public void ReloadMod( IMod mod, bool addMetaChanges )
|
||||||
{
|
{
|
||||||
RemoveMod( mod, addMetaChanges );
|
RemoveMod( mod, addMetaChanges );
|
||||||
AddMod( mod, addMetaChanges );
|
AddMod( mod, addMetaChanges );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveMod( Mod mod, bool addMetaChanges )
|
public void RemoveMod( IMod mod, bool addMetaChanges )
|
||||||
{
|
{
|
||||||
var conflicts = Conflicts( mod );
|
var conflicts = Conflicts( mod );
|
||||||
|
|
||||||
|
|
@ -243,37 +247,40 @@ public partial class ModCollection
|
||||||
|
|
||||||
|
|
||||||
// Add all files and possibly manipulations of a given mod according to its settings in this collection.
|
// Add all files and possibly manipulations of a given mod according to its settings in this collection.
|
||||||
public void AddMod( Mod mod, bool addMetaChanges )
|
public void AddMod( IMod mod, bool addMetaChanges )
|
||||||
{
|
{
|
||||||
var settings = _collection[ mod.Index ].Settings;
|
if( mod.Index >= 0 )
|
||||||
if( settings is not { Enabled: true } )
|
|
||||||
{
|
{
|
||||||
return;
|
var settings = _collection[ mod.Index ].Settings;
|
||||||
}
|
if( settings is not { Enabled: true } )
|
||||||
|
|
||||||
foreach( var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending( g => g.Item1.Priority ) )
|
|
||||||
{
|
|
||||||
if( group.Count == 0 )
|
|
||||||
{
|
{
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = settings.Settings[ groupIndex ];
|
foreach( var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending( g => g.Item1.Priority ) )
|
||||||
switch( group.Type )
|
|
||||||
{
|
{
|
||||||
case SelectType.Single:
|
if( group.Count == 0 )
|
||||||
AddSubMod( group[ ( int )config ], mod );
|
|
||||||
break;
|
|
||||||
case SelectType.Multi:
|
|
||||||
{
|
{
|
||||||
foreach( var (option, _) in group.WithIndex()
|
continue;
|
||||||
.OrderByDescending( p => group.OptionPriority( p.Item2 ) )
|
}
|
||||||
.Where( p => ( ( 1 << p.Item2 ) & config ) != 0 ) )
|
|
||||||
{
|
|
||||||
AddSubMod( option, mod );
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
var config = settings.Settings[ groupIndex ];
|
||||||
|
switch( group.Type )
|
||||||
|
{
|
||||||
|
case SelectType.Single:
|
||||||
|
AddSubMod( group[ ( int )config ], mod );
|
||||||
|
break;
|
||||||
|
case SelectType.Multi:
|
||||||
|
{
|
||||||
|
foreach( var (option, _) in group.WithIndex()
|
||||||
|
.OrderByDescending( p => group.OptionPriority( p.Item2 ) )
|
||||||
|
.Where( p => ( ( 1 << p.Item2 ) & config ) != 0 ) )
|
||||||
|
{
|
||||||
|
AddSubMod( option, mod );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -297,7 +304,7 @@ public partial class ModCollection
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all files and possibly manipulations of a specific submod
|
// Add all files and possibly manipulations of a specific submod
|
||||||
private void AddSubMod( ISubMod subMod, Mod parentMod )
|
private void AddSubMod( ISubMod subMod, IMod parentMod )
|
||||||
{
|
{
|
||||||
foreach( var (path, file) in subMod.Files.Concat( subMod.FileSwaps ) )
|
foreach( var (path, file) in subMod.Files.Concat( subMod.FileSwaps ) )
|
||||||
{
|
{
|
||||||
|
|
@ -320,7 +327,7 @@ public partial class ModCollection
|
||||||
// For different mods, higher mod priority takes precedence before option group priority,
|
// For different mods, higher mod priority takes precedence before option group priority,
|
||||||
// which takes precedence before option priority, which takes precedence before ordering.
|
// which takes precedence before option priority, which takes precedence before ordering.
|
||||||
// Inside the same mod, conflicts are not recorded.
|
// Inside the same mod, conflicts are not recorded.
|
||||||
private void AddFile( Utf8GamePath path, FullPath file, Mod mod )
|
private void AddFile( Utf8GamePath path, FullPath file, IMod mod )
|
||||||
{
|
{
|
||||||
if( ResolvedFiles.TryAdd( path, new ModPath( mod, file ) ) )
|
if( ResolvedFiles.TryAdd( path, new ModPath( mod, file ) ) )
|
||||||
{
|
{
|
||||||
|
|
@ -343,7 +350,7 @@ public partial class ModCollection
|
||||||
|
|
||||||
// Remove all empty conflict sets for a given mod with the given conflicts.
|
// Remove all empty conflict sets for a given mod with the given conflicts.
|
||||||
// If transitive is true, also removes the corresponding version of the other mod.
|
// If transitive is true, also removes the corresponding version of the other mod.
|
||||||
private void RemoveEmptyConflicts( Mod mod, SingleArray< ModConflicts > oldConflicts, bool transitive )
|
private void RemoveEmptyConflicts( IMod mod, SingleArray< ModConflicts > oldConflicts, bool transitive )
|
||||||
{
|
{
|
||||||
var changedConflicts = oldConflicts.Remove( c =>
|
var changedConflicts = oldConflicts.Remove( c =>
|
||||||
{
|
{
|
||||||
|
|
@ -372,10 +379,10 @@ public partial class ModCollection
|
||||||
// Add a new conflict between the added mod and the existing mod.
|
// Add a new conflict between the added mod and the existing mod.
|
||||||
// Update all other existing conflicts between the existing mod and other mods if necessary.
|
// Update all other existing conflicts between the existing mod and other mods if necessary.
|
||||||
// Returns if the added mod takes priority before the existing mod.
|
// Returns if the added mod takes priority before the existing mod.
|
||||||
private bool AddConflict( object data, Mod addedMod, Mod existingMod )
|
private bool AddConflict( object data, IMod addedMod, IMod existingMod )
|
||||||
{
|
{
|
||||||
var addedPriority = addedMod.Index >= 0 ? _collection[ addedMod.Index ].Settings!.Priority : int.MaxValue;
|
var addedPriority = addedMod.Index >= 0 ? _collection[ addedMod.Index ].Settings!.Priority : addedMod.Priority;
|
||||||
var existingPriority = existingMod.Index >= 0 ? _collection[ existingMod.Index ].Settings!.Priority : int.MaxValue;
|
var existingPriority = existingMod.Index >= 0 ? _collection[ existingMod.Index ].Settings!.Priority : existingMod.Priority;
|
||||||
|
|
||||||
if( existingPriority < addedPriority )
|
if( existingPriority < addedPriority )
|
||||||
{
|
{
|
||||||
|
|
@ -417,7 +424,7 @@ public partial class ModCollection
|
||||||
// For different mods, higher mod priority takes precedence before option group priority,
|
// For different mods, higher mod priority takes precedence before option group priority,
|
||||||
// which takes precedence before option priority, which takes precedence before ordering.
|
// which takes precedence before option priority, which takes precedence before ordering.
|
||||||
// Inside the same mod, conflicts are not recorded.
|
// Inside the same mod, conflicts are not recorded.
|
||||||
private void AddManipulation( MetaManipulation manip, Mod mod )
|
private void AddManipulation( MetaManipulation manip, IMod mod )
|
||||||
{
|
{
|
||||||
if( !MetaManipulations.TryGetValue( manip, out var existingMod ) )
|
if( !MetaManipulations.TryGetValue( manip, out var existingMod ) )
|
||||||
{
|
{
|
||||||
|
|
@ -463,7 +470,7 @@ public partial class ModCollection
|
||||||
{
|
{
|
||||||
if( !_changedItems.TryGetValue( name, out var data ) )
|
if( !_changedItems.TryGetValue( name, out var data ) )
|
||||||
{
|
{
|
||||||
_changedItems.Add( name, ( new SingleArray< Mod >( modPath.Mod ), obj ) );
|
_changedItems.Add( name, ( new SingleArray< IMod >( modPath.Mod ), obj ) );
|
||||||
}
|
}
|
||||||
else if( !data.Item1.Contains( modPath.Mod ) )
|
else if( !data.Item1.Contains( modPath.Mod ) )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -24,17 +24,20 @@ public partial class ModCollection
|
||||||
public event ModSettingChangeDelegate ModSettingChanged;
|
public event ModSettingChangeDelegate ModSettingChanged;
|
||||||
|
|
||||||
// Enable or disable the mod inheritance of mod idx.
|
// Enable or disable the mod inheritance of mod idx.
|
||||||
public void SetModInheritance( int idx, bool inherit )
|
public bool SetModInheritance( int idx, bool inherit )
|
||||||
{
|
{
|
||||||
if( FixInheritance( idx, inherit ) )
|
if( FixInheritance( idx, inherit ) )
|
||||||
{
|
{
|
||||||
ModSettingChanged.Invoke( ModSettingChange.Inheritance, idx, inherit ? 0 : 1, 0, false );
|
ModSettingChanged.Invoke( ModSettingChange.Inheritance, idx, inherit ? 0 : 1, 0, false );
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the enabled state mod idx to newValue if it differs from the current enabled state.
|
// Set the enabled state mod idx to newValue if it differs from the current enabled state.
|
||||||
// If mod idx is currently inherited, stop the inheritance.
|
// If mod idx is currently inherited, stop the inheritance.
|
||||||
public void SetModState( int idx, bool newValue )
|
public bool SetModState( int idx, bool newValue )
|
||||||
{
|
{
|
||||||
var oldValue = _settings[ idx ]?.Enabled ?? this[ idx ].Settings?.Enabled ?? false;
|
var oldValue = _settings[ idx ]?.Enabled ?? this[ idx ].Settings?.Enabled ?? false;
|
||||||
if( newValue != oldValue )
|
if( newValue != oldValue )
|
||||||
|
|
@ -42,7 +45,10 @@ public partial class ModCollection
|
||||||
var inheritance = FixInheritance( idx, false );
|
var inheritance = FixInheritance( idx, false );
|
||||||
_settings[ idx ]!.Enabled = newValue;
|
_settings[ idx ]!.Enabled = newValue;
|
||||||
ModSettingChanged.Invoke( ModSettingChange.EnableState, idx, inheritance ? -1 : newValue ? 0 : 1, 0, false );
|
ModSettingChanged.Invoke( ModSettingChange.EnableState, idx, inheritance ? -1 : newValue ? 0 : 1, 0, false );
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable or disable the mod inheritance of every mod in mods.
|
// Enable or disable the mod inheritance of every mod in mods.
|
||||||
|
|
@ -78,7 +84,7 @@ public partial class ModCollection
|
||||||
|
|
||||||
// Set the priority of mod idx to newValue if it differs from the current priority.
|
// Set the priority of mod idx to newValue if it differs from the current priority.
|
||||||
// If mod idx is currently inherited, stop the inheritance.
|
// If mod idx is currently inherited, stop the inheritance.
|
||||||
public void SetModPriority( int idx, int newValue )
|
public bool SetModPriority( int idx, int newValue )
|
||||||
{
|
{
|
||||||
var oldValue = _settings[ idx ]?.Priority ?? this[ idx ].Settings?.Priority ?? 0;
|
var oldValue = _settings[ idx ]?.Priority ?? this[ idx ].Settings?.Priority ?? 0;
|
||||||
if( newValue != oldValue )
|
if( newValue != oldValue )
|
||||||
|
|
@ -86,12 +92,15 @@ public partial class ModCollection
|
||||||
var inheritance = FixInheritance( idx, false );
|
var inheritance = FixInheritance( idx, false );
|
||||||
_settings[ idx ]!.Priority = newValue;
|
_settings[ idx ]!.Priority = newValue;
|
||||||
ModSettingChanged.Invoke( ModSettingChange.Priority, idx, inheritance ? -1 : oldValue, 0, false );
|
ModSettingChanged.Invoke( ModSettingChange.Priority, idx, inheritance ? -1 : oldValue, 0, false );
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a given setting group settingName of mod idx to newValue if it differs from the current value and fix it if necessary.
|
// Set a given setting group settingName of mod idx to newValue if it differs from the current value and fix it if necessary.
|
||||||
// If mod idx is currently inherited, stop the inheritance.
|
// If mod idx is currently inherited, stop the inheritance.
|
||||||
public void SetModSetting( int idx, int groupIdx, uint newValue )
|
public bool SetModSetting( int idx, int groupIdx, uint newValue )
|
||||||
{
|
{
|
||||||
var settings = _settings[ idx ] != null ? _settings[ idx ]!.Settings : this[ idx ].Settings?.Settings;
|
var settings = _settings[ idx ] != null ? _settings[ idx ]!.Settings : this[ idx ].Settings?.Settings;
|
||||||
var oldValue = settings?[ groupIdx ] ?? 0;
|
var oldValue = settings?[ groupIdx ] ?? 0;
|
||||||
|
|
@ -100,31 +109,26 @@ public partial class ModCollection
|
||||||
var inheritance = FixInheritance( idx, false );
|
var inheritance = FixInheritance( idx, false );
|
||||||
_settings[ idx ]!.SetValue( Penumbra.ModManager[ idx ], groupIdx, newValue );
|
_settings[ idx ]!.SetValue( Penumbra.ModManager[ idx ], groupIdx, newValue );
|
||||||
ModSettingChanged.Invoke( ModSettingChange.Setting, idx, inheritance ? -1 : ( int )oldValue, groupIdx, false );
|
ModSettingChanged.Invoke( ModSettingChange.Setting, idx, inheritance ? -1 : ( int )oldValue, groupIdx, false );
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change one of the available mod settings for mod idx discerned by type.
|
// Change one of the available mod settings for mod idx discerned by type.
|
||||||
// If type == Setting, settingName should be a valid setting for that mod, otherwise it will be ignored.
|
// If type == Setting, settingName should be a valid setting for that mod, otherwise it will be ignored.
|
||||||
// The setting will also be automatically fixed if it is invalid for that setting group.
|
// The setting will also be automatically fixed if it is invalid for that setting group.
|
||||||
// For boolean parameters, newValue == 0 will be treated as false and != 0 as true.
|
// For boolean parameters, newValue == 0 will be treated as false and != 0 as true.
|
||||||
public void ChangeModSetting( ModSettingChange type, int idx, int newValue, int groupIdx )
|
public bool ChangeModSetting( ModSettingChange type, int idx, int newValue, int groupIdx )
|
||||||
{
|
{
|
||||||
switch( type )
|
return type switch
|
||||||
{
|
{
|
||||||
case ModSettingChange.Inheritance:
|
ModSettingChange.Inheritance => SetModInheritance( idx, newValue != 0 ),
|
||||||
SetModInheritance( idx, newValue != 0 );
|
ModSettingChange.EnableState => SetModState( idx, newValue != 0 ),
|
||||||
break;
|
ModSettingChange.Priority => SetModPriority( idx, newValue ),
|
||||||
case ModSettingChange.EnableState:
|
ModSettingChange.Setting => SetModSetting( idx, groupIdx, ( uint )newValue ),
|
||||||
SetModState( idx, newValue != 0 );
|
_ => throw new ArgumentOutOfRangeException( nameof( type ), type, null ),
|
||||||
break;
|
};
|
||||||
case ModSettingChange.Priority:
|
|
||||||
SetModPriority( idx, newValue );
|
|
||||||
break;
|
|
||||||
case ModSettingChange.Setting:
|
|
||||||
SetModSetting( idx, groupIdx, ( uint )newValue );
|
|
||||||
break;
|
|
||||||
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set inheritance of a mod without saving,
|
// Set inheritance of a mod without saving,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Configuration;
|
using Dalamud.Configuration;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
|
using OtterGui.Classes;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.Import;
|
using Penumbra.Import;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
@ -46,6 +47,7 @@ public partial class Configuration : IPluginConfiguration
|
||||||
public int ModSelectorScaledSize { get; set; } = Constants.DefaultScaledSize;
|
public int ModSelectorScaledSize { get; set; } = Constants.DefaultScaledSize;
|
||||||
public bool OpenFoldersByDefault { get; set; } = false;
|
public bool OpenFoldersByDefault { get; set; } = false;
|
||||||
public string DefaultImportFolder { get; set; } = string.Empty;
|
public string DefaultImportFolder { get; set; } = string.Empty;
|
||||||
|
public DoubleModifier DeleteModModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
||||||
|
|
||||||
public bool FixMainWindow { get; set; } = false;
|
public bool FixMainWindow { get; set; } = false;
|
||||||
public bool ShowAdvanced { get; set; }
|
public bool ShowAdvanced { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -72,4 +72,38 @@ public unsafe partial class PathResolver
|
||||||
_animationLoadCollection = last;
|
_animationLoadCollection = last;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unknown what exactly this is but it seems to load a bunch of paps.
|
||||||
|
public delegate void LoadSomePap( IntPtr a1, int a2, IntPtr a3, int a4 );
|
||||||
|
|
||||||
|
[Signature( "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 41 8B D9 89 51" )]
|
||||||
|
public Hook< LoadSomePap >? LoadSomePapHook;
|
||||||
|
|
||||||
|
private void LoadSomePapDetour( IntPtr a1, int a2, IntPtr a3, int a4 )
|
||||||
|
{
|
||||||
|
var timelinePtr = a1 + 0x50;
|
||||||
|
var last = _animationLoadCollection;
|
||||||
|
if( timelinePtr != IntPtr.Zero )
|
||||||
|
{
|
||||||
|
var actorIdx = ( int )( *( *( ulong** )timelinePtr + 1 ) >> 3 );
|
||||||
|
if( actorIdx >= 0 && actorIdx < Dalamud.Objects.Length )
|
||||||
|
{
|
||||||
|
_animationLoadCollection = IdentifyCollection( ( GameObject* )( Dalamud.Objects[ actorIdx ]?.Address ?? IntPtr.Zero ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoadSomePapHook!.Original( a1, a2, a3, a4 );
|
||||||
|
_animationLoadCollection = last;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seems to load character actions when zoning or changing class, maybe.
|
||||||
|
[Signature( "E8 ?? ?? ?? ?? C6 83 ?? ?? ?? ?? ?? 8B 8E", DetourName = nameof( SomeActionLoadDetour ) )]
|
||||||
|
public Hook< CharacterBaseDestructorDelegate >? SomeActionLoadHook;
|
||||||
|
|
||||||
|
private void SomeActionLoadDetour( IntPtr gameObject )
|
||||||
|
{
|
||||||
|
var last = _animationLoadCollection;
|
||||||
|
_animationLoadCollection = IdentifyCollection( ( GameObject* )gameObject );
|
||||||
|
SomeActionLoadHook!.Original( gameObject );
|
||||||
|
_animationLoadCollection = last;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +93,8 @@ public unsafe partial class PathResolver
|
||||||
LoadTimelineResourcesHook?.Enable();
|
LoadTimelineResourcesHook?.Enable();
|
||||||
CharacterBaseLoadAnimationHook?.Enable();
|
CharacterBaseLoadAnimationHook?.Enable();
|
||||||
LoadSomeAvfxHook?.Enable();
|
LoadSomeAvfxHook?.Enable();
|
||||||
|
LoadSomePapHook?.Enable();
|
||||||
|
SomeActionLoadHook?.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisableDataHooks()
|
private void DisableDataHooks()
|
||||||
|
|
@ -105,6 +107,8 @@ public unsafe partial class PathResolver
|
||||||
LoadTimelineResourcesHook?.Disable();
|
LoadTimelineResourcesHook?.Disable();
|
||||||
CharacterBaseLoadAnimationHook?.Disable();
|
CharacterBaseLoadAnimationHook?.Disable();
|
||||||
LoadSomeAvfxHook?.Disable();
|
LoadSomeAvfxHook?.Disable();
|
||||||
|
LoadSomePapHook?.Disable();
|
||||||
|
SomeActionLoadHook?.Disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeDataHooks()
|
private void DisposeDataHooks()
|
||||||
|
|
@ -116,6 +120,8 @@ public unsafe partial class PathResolver
|
||||||
LoadTimelineResourcesHook?.Dispose();
|
LoadTimelineResourcesHook?.Dispose();
|
||||||
CharacterBaseLoadAnimationHook?.Dispose();
|
CharacterBaseLoadAnimationHook?.Dispose();
|
||||||
LoadSomeAvfxHook?.Dispose();
|
LoadSomeAvfxHook?.Dispose();
|
||||||
|
LoadSomePapHook?.Dispose();
|
||||||
|
SomeActionLoadHook?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This map links DrawObjects directly to Actors (by ObjectTable index) and their collections.
|
// This map links DrawObjects directly to Actors (by ObjectTable index) and their collections.
|
||||||
|
|
@ -272,8 +278,12 @@ public unsafe partial class PathResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
// Housing Retainers
|
// Housing Retainers
|
||||||
if( Penumbra.Config.UseDefaultCollectionForRetainers && gameObject->ObjectKind == (byte) ObjectKind.EventNpc && gameObject->DataID == 1011832 )
|
if( Penumbra.Config.UseDefaultCollectionForRetainers
|
||||||
|
&& gameObject->ObjectKind == ( byte )ObjectKind.EventNpc
|
||||||
|
&& gameObject->DataID == 1011832 )
|
||||||
|
{
|
||||||
return Penumbra.CollectionManager.Default;
|
return Penumbra.CollectionManager.Default;
|
||||||
|
}
|
||||||
|
|
||||||
string? actorName = null;
|
string? actorName = null;
|
||||||
if( Penumbra.Config.PreferNamedCollectionsOverOwners )
|
if( Penumbra.Config.PreferNamedCollectionsOverOwners )
|
||||||
|
|
@ -299,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.
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ public partial class PathResolver : IDisposable
|
||||||
case ResourceType.Pap:
|
case ResourceType.Pap:
|
||||||
case ResourceType.Avfx:
|
case ResourceType.Avfx:
|
||||||
case ResourceType.Atex:
|
case ResourceType.Atex:
|
||||||
|
case ResourceType.Scd:
|
||||||
collection = _animationLoadCollection;
|
collection = _animationLoadCollection;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public partial class MetaManager
|
||||||
public struct MetaManagerCmp : IDisposable
|
public struct MetaManagerCmp : IDisposable
|
||||||
{
|
{
|
||||||
public CmpFile? File = null;
|
public CmpFile? File = null;
|
||||||
public readonly Dictionary< RspManipulation, Mod > Manipulations = new();
|
public readonly Dictionary< RspManipulation, IMod > Manipulations = new();
|
||||||
|
|
||||||
public MetaManagerCmp()
|
public MetaManagerCmp()
|
||||||
{ }
|
{ }
|
||||||
|
|
@ -39,7 +39,7 @@ public partial class MetaManager
|
||||||
Manipulations.Clear();
|
Manipulations.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ApplyMod( RspManipulation m, Mod mod )
|
public bool ApplyMod( RspManipulation m, IMod mod )
|
||||||
{
|
{
|
||||||
#if USE_CMP
|
#if USE_CMP
|
||||||
Manipulations[ m ] = mod;
|
Manipulations[ m ] = mod;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public partial class MetaManager
|
||||||
{
|
{
|
||||||
public readonly ExpandedEqdpFile?[] Files = new ExpandedEqdpFile?[CharacterUtility.NumEqdpFiles - 2]; // TODO: female Hrothgar
|
public readonly ExpandedEqdpFile?[] Files = new ExpandedEqdpFile?[CharacterUtility.NumEqdpFiles - 2]; // TODO: female Hrothgar
|
||||||
|
|
||||||
public readonly Dictionary< EqdpManipulation, Mod > Manipulations = new();
|
public readonly Dictionary< EqdpManipulation, IMod > Manipulations = new();
|
||||||
|
|
||||||
public MetaManagerEqdp()
|
public MetaManagerEqdp()
|
||||||
{ }
|
{ }
|
||||||
|
|
@ -51,7 +51,7 @@ public partial class MetaManager
|
||||||
Manipulations.Clear();
|
Manipulations.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ApplyMod( EqdpManipulation m, Mod mod )
|
public bool ApplyMod( EqdpManipulation m, IMod mod )
|
||||||
{
|
{
|
||||||
#if USE_EQDP
|
#if USE_EQDP
|
||||||
Manipulations[ m ] = mod;
|
Manipulations[ m ] = mod;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public partial class MetaManager
|
||||||
public struct MetaManagerEqp : IDisposable
|
public struct MetaManagerEqp : IDisposable
|
||||||
{
|
{
|
||||||
public ExpandedEqpFile? File = null;
|
public ExpandedEqpFile? File = null;
|
||||||
public readonly Dictionary< EqpManipulation, Mod > Manipulations = new();
|
public readonly Dictionary< EqpManipulation, IMod > Manipulations = new();
|
||||||
|
|
||||||
public MetaManagerEqp()
|
public MetaManagerEqp()
|
||||||
{ }
|
{ }
|
||||||
|
|
@ -39,7 +39,7 @@ public partial class MetaManager
|
||||||
Manipulations.Clear();
|
Manipulations.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ApplyMod( EqpManipulation m, Mod mod )
|
public bool ApplyMod( EqpManipulation m, IMod mod )
|
||||||
{
|
{
|
||||||
#if USE_EQP
|
#if USE_EQP
|
||||||
Manipulations[ m ] = mod;
|
Manipulations[ m ] = mod;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public partial class MetaManager
|
||||||
public EstFile? BodyFile = null;
|
public EstFile? BodyFile = null;
|
||||||
public EstFile? HeadFile = null;
|
public EstFile? HeadFile = null;
|
||||||
|
|
||||||
public readonly Dictionary< EstManipulation, Mod > Manipulations = new();
|
public readonly Dictionary< EstManipulation, IMod > Manipulations = new();
|
||||||
|
|
||||||
public MetaManagerEst()
|
public MetaManagerEst()
|
||||||
{ }
|
{ }
|
||||||
|
|
@ -51,7 +51,7 @@ public partial class MetaManager
|
||||||
Manipulations.Clear();
|
Manipulations.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ApplyMod( EstManipulation m, Mod mod )
|
public bool ApplyMod( EstManipulation m, IMod mod )
|
||||||
{
|
{
|
||||||
#if USE_EST
|
#if USE_EST
|
||||||
Manipulations[ m ] = mod;
|
Manipulations[ m ] = mod;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public partial class MetaManager
|
||||||
public struct MetaManagerGmp : IDisposable
|
public struct MetaManagerGmp : IDisposable
|
||||||
{
|
{
|
||||||
public ExpandedGmpFile? File = null;
|
public ExpandedGmpFile? File = null;
|
||||||
public readonly Dictionary< GmpManipulation, Mod > Manipulations = new();
|
public readonly Dictionary< GmpManipulation, IMod > Manipulations = new();
|
||||||
|
|
||||||
public MetaManagerGmp()
|
public MetaManagerGmp()
|
||||||
{ }
|
{ }
|
||||||
|
|
@ -38,7 +38,7 @@ public partial class MetaManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ApplyMod( GmpManipulation m, Mod mod )
|
public bool ApplyMod( GmpManipulation m, IMod mod )
|
||||||
{
|
{
|
||||||
#if USE_GMP
|
#if USE_GMP
|
||||||
Manipulations[ m ] = mod;
|
Manipulations[ m ] = mod;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public partial class MetaManager
|
||||||
public readonly struct MetaManagerImc : IDisposable
|
public readonly struct MetaManagerImc : IDisposable
|
||||||
{
|
{
|
||||||
public readonly Dictionary< Utf8GamePath, ImcFile > Files = new();
|
public readonly Dictionary< Utf8GamePath, ImcFile > Files = new();
|
||||||
public readonly Dictionary< ImcManipulation, Mod > Manipulations = new();
|
public readonly Dictionary< ImcManipulation, IMod > Manipulations = new();
|
||||||
|
|
||||||
private readonly ModCollection _collection;
|
private readonly ModCollection _collection;
|
||||||
private static int _imcManagerCount;
|
private static int _imcManagerCount;
|
||||||
|
|
@ -65,7 +65,7 @@ public partial class MetaManager
|
||||||
Manipulations.Clear();
|
Manipulations.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ApplyMod( ImcManipulation m, Mod mod )
|
public bool ApplyMod( ImcManipulation m, IMod mod )
|
||||||
{
|
{
|
||||||
#if USE_IMC
|
#if USE_IMC
|
||||||
Manipulations[ m ] = mod;
|
Manipulations[ m ] = mod;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ public partial class MetaManager : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetValue( MetaManipulation manip, [NotNullWhen(true)] out Mod? mod )
|
public bool TryGetValue( MetaManipulation manip, [NotNullWhen(true)] out IMod? mod )
|
||||||
{
|
{
|
||||||
mod = manip.ManipulationType switch
|
mod = manip.ManipulationType switch
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +86,7 @@ public partial class MetaManager : IDisposable
|
||||||
Imc.Dispose();
|
Imc.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ApplyMod( MetaManipulation m, Mod mod )
|
public bool ApplyMod( MetaManipulation m, IMod mod )
|
||||||
{
|
{
|
||||||
return m.ManipulationType switch
|
return m.ManipulationType switch
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
19
Penumbra/Mods/Editor/IMod.cs
Normal file
19
Penumbra/Mods/Editor/IMod.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
public interface IMod
|
||||||
|
{
|
||||||
|
LowerString Name { get; }
|
||||||
|
|
||||||
|
public int Index { get; }
|
||||||
|
public int Priority { get; }
|
||||||
|
|
||||||
|
public int TotalManipulations { get; }
|
||||||
|
|
||||||
|
public ISubMod Default { get; }
|
||||||
|
public IReadOnlyList< IModGroup > Groups { get; }
|
||||||
|
|
||||||
|
public IEnumerable< ISubMod > AllSubMods { get; }
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
public partial class Mod
|
public partial class Mod : IMod
|
||||||
{
|
{
|
||||||
public partial class Editor : IDisposable
|
public partial class Editor : IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -15,7 +12,7 @@ public partial class Mod
|
||||||
|
|
||||||
public Editor( Mod mod, int groupIdx, int optionIdx )
|
public Editor( Mod mod, int groupIdx, int optionIdx )
|
||||||
{
|
{
|
||||||
_mod = mod;
|
_mod = mod;
|
||||||
SetSubMod( groupIdx, optionIdx );
|
SetSubMod( groupIdx, optionIdx );
|
||||||
GroupIdx = groupIdx;
|
GroupIdx = groupIdx;
|
||||||
_subMod = _mod._default;
|
_subMod = _mod._default;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
@ -37,5 +38,28 @@ public sealed partial class Mod
|
||||||
ModOptionChanged += OnModOptionChange;
|
ModOptionChanged += OnModOptionChange;
|
||||||
ModPathChanged += OnModPathChange;
|
ModPathChanged += OnModPathChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Try to obtain a mod by its directory name (unique identifier, preferred),
|
||||||
|
// or the first mod of the given name if no directory fits.
|
||||||
|
public bool TryGetMod( string modDirectory, string modName, [NotNullWhen( true )] out Mod? mod )
|
||||||
|
{
|
||||||
|
mod = null;
|
||||||
|
foreach( var m in _mods )
|
||||||
|
{
|
||||||
|
if( m.ModPath.Name == modDirectory )
|
||||||
|
{
|
||||||
|
mod = m;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( m.Name == modName )
|
||||||
|
{
|
||||||
|
mod ??= m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mod != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,6 +17,10 @@ public partial class Mod
|
||||||
public DirectoryInfo ModPath { get; private set; }
|
public DirectoryInfo ModPath { get; private set; }
|
||||||
public int Index { get; private set; } = -1;
|
public int Index { get; private set; } = -1;
|
||||||
|
|
||||||
|
// Unused if Index < 0 but used for special temporary mods.
|
||||||
|
public int Priority
|
||||||
|
=> 0;
|
||||||
|
|
||||||
private Mod( DirectoryInfo modPath )
|
private Mod( DirectoryInfo modPath )
|
||||||
=> ModPath = modPath;
|
=> ModPath = modPath;
|
||||||
|
|
||||||
|
|
@ -30,7 +34,7 @@ public partial class Mod
|
||||||
}
|
}
|
||||||
|
|
||||||
var mod = new Mod( modPath );
|
var mod = new Mod( modPath );
|
||||||
if( !mod.Reload(out _) )
|
if( !mod.Reload( out _ ) )
|
||||||
{
|
{
|
||||||
// Can not be base path not existing because that is checked before.
|
// Can not be base path not existing because that is checked before.
|
||||||
PluginLog.Error( $"Mod at {modPath} without name is not supported." );
|
PluginLog.Error( $"Mod at {modPath} without name is not supported." );
|
||||||
|
|
@ -40,15 +44,17 @@ public partial class Mod
|
||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool Reload(out MetaChangeType metaChange)
|
private bool Reload( out MetaChangeType metaChange )
|
||||||
{
|
{
|
||||||
metaChange = MetaChangeType.Deletion;
|
metaChange = MetaChangeType.Deletion;
|
||||||
ModPath.Refresh();
|
ModPath.Refresh();
|
||||||
if( !ModPath.Exists )
|
if( !ModPath.Exists )
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
metaChange = LoadMeta();
|
metaChange = LoadMeta();
|
||||||
if( metaChange.HasFlag(MetaChangeType.Deletion) || Name.Length == 0 )
|
if( metaChange.HasFlag( MetaChangeType.Deletion ) || Name.Length == 0 )
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,11 @@ public enum MetaChangeType : ushort
|
||||||
|
|
||||||
public sealed partial class Mod
|
public sealed partial class Mod
|
||||||
{
|
{
|
||||||
public static readonly Mod ForcedFiles = new(new DirectoryInfo( "." ))
|
public static readonly TemporaryMod ForcedFiles = new()
|
||||||
{
|
{
|
||||||
Name = "Forced Files",
|
Name = "Forced Files",
|
||||||
Index = -1,
|
Index = -1,
|
||||||
|
Priority = int.MaxValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
public const uint CurrentFileVersion = 1;
|
public const uint CurrentFileVersion = 1;
|
||||||
|
|
|
||||||
43
Penumbra/Mods/Mod.TemporaryMod.cs
Normal file
43
Penumbra/Mods/Mod.TemporaryMod.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.ByteString;
|
||||||
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
public sealed partial class Mod
|
||||||
|
{
|
||||||
|
public class TemporaryMod : IMod
|
||||||
|
{
|
||||||
|
public LowerString Name { get; init; } = LowerString.Empty;
|
||||||
|
public int Index { get; init; } = -2;
|
||||||
|
public int Priority { get; init; } = int.MaxValue;
|
||||||
|
|
||||||
|
public int TotalManipulations
|
||||||
|
=> Default.Manipulations.Count;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
@ -10,7 +11,7 @@ namespace Penumbra.Mods;
|
||||||
public class ModSettings
|
public class ModSettings
|
||||||
{
|
{
|
||||||
public static readonly ModSettings Empty = new();
|
public static readonly ModSettings Empty = new();
|
||||||
public List< uint > Settings { get; init; } = new();
|
public List< uint > Settings { get; private init; } = new();
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
|
|
@ -100,7 +101,7 @@ public class ModSettings
|
||||||
private static uint FixSetting( IModGroup group, uint value )
|
private static uint FixSetting( IModGroup group, uint value )
|
||||||
=> group.Type switch
|
=> group.Type switch
|
||||||
{
|
{
|
||||||
SelectType.Single => ( uint )Math.Min( value, group.Count - 1 ),
|
SelectType.Single => ( uint )Math.Min( value, group.Count - 1 ),
|
||||||
SelectType.Multi => ( uint )( value & ( ( 1ul << group.Count ) - 1 ) ),
|
SelectType.Multi => ( uint )( value & ( ( 1ul << group.Count ) - 1 ) ),
|
||||||
_ => value,
|
_ => value,
|
||||||
};
|
};
|
||||||
|
|
@ -208,4 +209,31 @@ public class ModSettings
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the settings for a given mod in a shareable format, using the names of groups and options instead of indices.
|
||||||
|
// Does not repair settings but ignores settings not fitting to the given mod.
|
||||||
|
public (bool Enabled, int Priority, Dictionary< string, IList< string > > Settings) ConvertToShareable( Mod mod )
|
||||||
|
{
|
||||||
|
var dict = new Dictionary< string, IList< string > >( Settings.Count );
|
||||||
|
foreach( var (setting, idx) in Settings.WithIndex() )
|
||||||
|
{
|
||||||
|
if( idx >= mod.Groups.Count )
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var group = mod.Groups[ idx ];
|
||||||
|
if( group.Type == SelectType.Single && setting < group.Count )
|
||||||
|
{
|
||||||
|
dict.Add( group.Name, new[] { group[ ( int )setting ].Name } );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var list = group.Where( ( _, optionIdx ) => ( setting & ( 1 << optionIdx ) ) != 0 ).Select( o => o.Name ).ToList();
|
||||||
|
dict.Add( group.Name, list );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( Enabled, Priority, dict );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,10 +29,14 @@ public class MainClass : IDalamudPlugin
|
||||||
{
|
{
|
||||||
private Penumbra? _penumbra;
|
private Penumbra? _penumbra;
|
||||||
private readonly CharacterUtility _characterUtility;
|
private readonly CharacterUtility _characterUtility;
|
||||||
|
public static bool DevPenumbraExists;
|
||||||
|
public static bool IsNotInstalledPenumbra;
|
||||||
|
|
||||||
public MainClass( DalamudPluginInterface pluginInterface )
|
public MainClass( DalamudPluginInterface pluginInterface )
|
||||||
{
|
{
|
||||||
Dalamud.Initialize( pluginInterface );
|
Dalamud.Initialize( pluginInterface );
|
||||||
|
DevPenumbraExists = CheckDevPluginPenumbra();
|
||||||
|
IsNotInstalledPenumbra = CheckIsNotInstalled();
|
||||||
GameData.GameData.GetIdentifier( Dalamud.GameData, Dalamud.ClientState.ClientLanguage );
|
GameData.GameData.GetIdentifier( Dalamud.GameData, Dalamud.ClientState.ClientLanguage );
|
||||||
_characterUtility = new CharacterUtility();
|
_characterUtility = new CharacterUtility();
|
||||||
_characterUtility.LoadingFinished += ()
|
_characterUtility.LoadingFinished += ()
|
||||||
|
|
@ -47,6 +51,41 @@ public class MainClass : IDalamudPlugin
|
||||||
|
|
||||||
public string Name
|
public string Name
|
||||||
=> Penumbra.Name;
|
=> Penumbra.Name;
|
||||||
|
|
||||||
|
// Because remnants of penumbra in devPlugins cause issues, we check for them to warn users to remove them.
|
||||||
|
private static bool CheckDevPluginPenumbra()
|
||||||
|
{
|
||||||
|
#if !DEBUG
|
||||||
|
var path = Path.Combine( Dalamud.PluginInterface.DalamudAssetDirectory.Parent?.FullName ?? "INVALIDPATH", "devPlugins", "Penumbra" );
|
||||||
|
var dir = new DirectoryInfo( path );
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return dir.Exists && dir.EnumerateFiles( "*.dll", SearchOption.AllDirectories ).Any();
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
PluginLog.Error( $"Could not check for dev plugin Penumbra:\n{e}" );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the loaded version of penumbra itself is in devPlugins.
|
||||||
|
private static bool CheckIsNotInstalled()
|
||||||
|
{
|
||||||
|
#if !DEBUG
|
||||||
|
var checkedDirectory = Dalamud.PluginInterface.AssemblyLocation.Directory?.Parent?.Parent?.Name;
|
||||||
|
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;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Penumbra : IDisposable
|
public class Penumbra : IDisposable
|
||||||
|
|
@ -66,12 +105,11 @@ 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;
|
||||||
|
|
||||||
|
|
||||||
public readonly ResourceLogger ResourceLogger;
|
public readonly ResourceLogger ResourceLogger;
|
||||||
public readonly PathResolver PathResolver;
|
public readonly PathResolver PathResolver;
|
||||||
public readonly MusicManager MusicManager;
|
public readonly MusicManager MusicManager;
|
||||||
|
|
@ -100,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 );
|
||||||
|
|
|
||||||
|
|
@ -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 )
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using System.Collections.Concurrent;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI.Classes;
|
namespace Penumbra.UI.Classes;
|
||||||
|
|
||||||
|
|
@ -286,14 +287,14 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
|
|
||||||
private void DeleteModButton( Vector2 size )
|
private void DeleteModButton( Vector2 size )
|
||||||
{
|
{
|
||||||
var keys = ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift;
|
var keys = Penumbra.Config.DeleteModModifier.IsActive();
|
||||||
var tt = SelectedLeaf == null
|
var tt = SelectedLeaf == null
|
||||||
? "No mod selected."
|
? "No mod selected."
|
||||||
: "Delete the currently selected mod entirely from your drive.\n"
|
: "Delete the currently selected mod entirely from your drive.\n"
|
||||||
+ "This can not be undone.";
|
+ "This can not be undone.";
|
||||||
if( !keys )
|
if( !keys )
|
||||||
{
|
{
|
||||||
tt += "\nHold Control and Shift while clicking to delete the mod.";
|
tt += $"\nHold {Penumbra.Config.DeleteModModifier} while clicking to delete the mod.";
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true )
|
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true )
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,13 @@ public partial class ConfigWindow
|
||||||
private void DrawChangedItemTab()
|
private void DrawChangedItemTab()
|
||||||
{
|
{
|
||||||
// Functions in here for less pollution.
|
// Functions in here for less pollution.
|
||||||
bool FilterChangedItem( KeyValuePair< string, (SingleArray< Mod >, object?) > item )
|
bool FilterChangedItem( KeyValuePair< string, (SingleArray< IMod >, object?) > item )
|
||||||
=> ( _changedItemFilter.IsEmpty
|
=> ( _changedItemFilter.IsEmpty
|
||||||
|| ChangedItemName( item.Key, item.Value.Item2 )
|
|| ChangedItemName( item.Key, item.Value.Item2 )
|
||||||
.Contains( _changedItemFilter.Lower, StringComparison.InvariantCultureIgnoreCase ) )
|
.Contains( _changedItemFilter.Lower, StringComparison.InvariantCultureIgnoreCase ) )
|
||||||
&& ( _changedItemModFilter.IsEmpty || item.Value.Item1.Any( m => m.Name.Contains( _changedItemModFilter ) ) );
|
&& ( _changedItemModFilter.IsEmpty || item.Value.Item1.Any( m => m.Name.Contains( _changedItemModFilter ) ) );
|
||||||
|
|
||||||
void DrawChangedItemColumn( KeyValuePair< string, (SingleArray< Mod >, object?) > item )
|
void DrawChangedItemColumn( KeyValuePair< string, (SingleArray< IMod >, object?) > item )
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
DrawChangedItem( item.Key, item.Value.Item2, false );
|
DrawChangedItem( item.Key, item.Value.Item2, false );
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ public partial class ConfigWindow
|
||||||
{
|
{
|
||||||
// We can treat all meta manipulations the same,
|
// We can treat all meta manipulations the same,
|
||||||
// we are only really interested in their ToString function here.
|
// we are only really interested in their ToString function here.
|
||||||
static (object, Mod) Convert< T >( KeyValuePair< T, Mod > kvp )
|
static (object, IMod) Convert< T >( KeyValuePair< T, IMod > kvp )
|
||||||
=> ( kvp.Key!, kvp.Value );
|
=> ( kvp.Key!, kvp.Value );
|
||||||
|
|
||||||
var it = m.Cmp.Manipulations.Select( Convert )
|
var it = m.Cmp.Manipulations.Select( Convert )
|
||||||
|
|
@ -183,7 +183,7 @@ public partial class ConfigWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw a line for a unfiltered/unconverted manipulation and mod-index pair.
|
// Draw a line for a unfiltered/unconverted manipulation and mod-index pair.
|
||||||
private static void DrawLine( (object, Mod) pair )
|
private static void DrawLine( (object, IMod) pair )
|
||||||
{
|
{
|
||||||
var (manipulation, mod) = pair;
|
var (manipulation, mod) = pair;
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ public partial class ConfigWindow
|
||||||
_ => throw new ArgumentOutOfRangeException( nameof( type ), type, null ),
|
_ => throw new ArgumentOutOfRangeException( nameof( type ), type, null ),
|
||||||
};
|
};
|
||||||
|
|
||||||
using var combo = ImRaii.Combo( label, current.Name );
|
using var combo = ImRaii.Combo( label, current.Name );
|
||||||
if( combo )
|
if( combo )
|
||||||
{
|
{
|
||||||
foreach( var collection in Penumbra.CollectionManager.GetEnumeratorWithEmpty().Skip( withEmpty ? 0 : 1 ).OrderBy( c => c.Name ) )
|
foreach( var collection in Penumbra.CollectionManager.GetEnumeratorWithEmpty().Skip( withEmpty ? 0 : 1 ).OrderBy( c => c.Name ) )
|
||||||
|
|
@ -128,20 +128,23 @@ public partial class ConfigWindow
|
||||||
|
|
||||||
if( Functions.GetDownloadsFolder( out var downloadsFolder ) )
|
if( Functions.GetDownloadsFolder( out var downloadsFolder ) )
|
||||||
{
|
{
|
||||||
fileManager.CustomSideBarItems.Add( ("Downloads", downloadsFolder, FontAwesomeIcon.Download, -1) );
|
fileManager.CustomSideBarItems.Add( ( "Downloads", downloadsFolder, FontAwesomeIcon.Download, -1 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( Functions.GetQuickAccessFolders( out var folders ) )
|
if( Functions.GetQuickAccessFolders( out var folders ) )
|
||||||
{
|
{
|
||||||
foreach( var ((name, path), idx) in folders.WithIndex() )
|
foreach( var ((name, path), idx) in folders.WithIndex() )
|
||||||
{
|
{
|
||||||
fileManager.CustomSideBarItems.Add( ($"{name}##{idx}", path, FontAwesomeIcon.Folder, -1) );
|
fileManager.CustomSideBarItems.Add( ( $"{name}##{idx}", path, FontAwesomeIcon.Folder, -1 ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add Penumbra Root. This is not updated if the root changes right now.
|
||||||
|
fileManager.CustomSideBarItems.Add( ("Root Directory", Penumbra.Config.ModDirectory, FontAwesomeIcon.Gamepad, 0) );
|
||||||
|
|
||||||
// Remove Videos and Music.
|
// Remove Videos and Music.
|
||||||
fileManager.CustomSideBarItems.Add( ("Videos", string.Empty, 0, -1) );
|
fileManager.CustomSideBarItems.Add( ( "Videos", string.Empty, 0, -1 ) );
|
||||||
fileManager.CustomSideBarItems.Add( ("Music", string.Empty, 0, -1) );
|
fileManager.CustomSideBarItems.Add( ( "Music", string.Empty, 0, -1 ) );
|
||||||
|
|
||||||
return fileManager;
|
return fileManager;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,16 +128,19 @@ public partial class ConfigWindow
|
||||||
|
|
||||||
foreach( var conflict in Penumbra.CollectionManager.Current.Conflicts( _mod ) )
|
foreach( var conflict in Penumbra.CollectionManager.Current.Conflicts( _mod ) )
|
||||||
{
|
{
|
||||||
if( ImGui.Selectable( conflict.Mod2.Name ) )
|
if( ImGui.Selectable( conflict.Mod2.Name ) && conflict.Mod2 is Mod mod )
|
||||||
{
|
{
|
||||||
_window._selector.SelectByValue( conflict.Mod2 );
|
_window._selector.SelectByValue( mod );
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using( var color = ImRaii.PushColor( ImGuiCol.Text,
|
using( var color = ImRaii.PushColor( ImGuiCol.Text,
|
||||||
conflict.HasPriority ? ColorId.HandledConflictMod.Value() : ColorId.ConflictingMod.Value() ) )
|
conflict.HasPriority ? ColorId.HandledConflictMod.Value() : ColorId.ConflictingMod.Value() ) )
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted( $"(Priority {Penumbra.CollectionManager.Current[ conflict.Mod2.Index ].Settings!.Priority})" );
|
var priority = conflict.Mod2.Index < 0
|
||||||
|
? conflict.Mod2.Priority
|
||||||
|
: Penumbra.CollectionManager.Current[conflict.Mod2.Index].Settings!.Priority;
|
||||||
|
ImGui.TextUnformatted( $"(Priority {priority})" );
|
||||||
}
|
}
|
||||||
|
|
||||||
using var indent = ImRaii.PushIndent( 30f );
|
using var indent = ImRaii.PushIndent( 30f );
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Widgets;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
|
|
@ -89,6 +90,15 @@ public partial class ConfigWindow
|
||||||
Penumbra.Config.OpenFoldersByDefault = v;
|
Penumbra.Config.OpenFoldersByDefault = v;
|
||||||
_window._selector.SetFilterDirty();
|
_window._selector.SetFilterDirty();
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
Widget.DoubleModifierSelector( "Mod Deletion Modifier",
|
||||||
|
"A modifier you need to hold while clicking the Delete Mod button for it to take effect.", _window._inputTextWidth.X,
|
||||||
|
Penumbra.Config.DeleteModModifier,
|
||||||
|
v =>
|
||||||
|
{
|
||||||
|
Penumbra.Config.DeleteModModifier = v;
|
||||||
|
Penumbra.Config.Save();
|
||||||
|
} );
|
||||||
ImGui.Dummy( _window._defaultSpace );
|
ImGui.Dummy( _window._defaultSpace );
|
||||||
Checkbox( "Always Open Import at Default Directory",
|
Checkbox( "Always Open Import at Default Directory",
|
||||||
"Open the import window at the location specified here every time, forgetting your previous path.",
|
"Open the import window at the location specified here every time, forgetting your previous path.",
|
||||||
|
|
|
||||||
|
|
@ -55,32 +55,39 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
if( Penumbra.ImcExceptions > 0 )
|
if( Penumbra.ImcExceptions > 0 )
|
||||||
{
|
{
|
||||||
using var color = ImRaii.PushColor( ImGuiCol.Text, Colors.RegexWarningBorder );
|
DrawProblemWindow( $"There were {Penumbra.ImcExceptions} errors while trying to load IMC files from the game data.\n"
|
||||||
ImGui.NewLine();
|
|
||||||
ImGui.NewLine();
|
|
||||||
ImGui.TextWrapped( $"There were {Penumbra.ImcExceptions} errors while trying to load IMC files from the game data.\n"
|
|
||||||
+ "This usually means that your game installation was corrupted by updating the game while having TexTools mods still active.\n"
|
+ "This usually means that your game installation was corrupted by updating the game while having TexTools mods still active.\n"
|
||||||
+ "It is recommended to not use TexTools and Penumbra (or other Lumina-based tools) at the same time.\n\n"
|
+ "It is recommended to not use TexTools and Penumbra (or other Lumina-based tools) at the same time.\n\n"
|
||||||
+ "Please use the Launcher's Repair Game Files function to repair your client installation." );
|
+ "Please use the Launcher's Repair Game Files function to repair your client installation." );
|
||||||
color.Pop();
|
|
||||||
|
|
||||||
ImGui.NewLine();
|
|
||||||
ImGui.NewLine();
|
|
||||||
SettingsTab.DrawDiscordButton( 0 );
|
|
||||||
ImGui.SameLine();
|
|
||||||
SettingsTab.DrawSupportButton();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else if( MainClass.IsNotInstalledPenumbra )
|
||||||
using var bar = ImRaii.TabBar( string.Empty, ImGuiTabBarFlags.NoTooltip );
|
{
|
||||||
SetupSizes();
|
DrawProblemWindow(
|
||||||
_settingsTab.Draw();
|
$"You are loading a release version of Penumbra from \"{Dalamud.PluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\" instead of the installedPlugins directory.\n\n"
|
||||||
DrawModsTab();
|
+ "You should not install Penumbra manually, but rather add the plugin repository under settings and then install it via the plugin installer.\n\n"
|
||||||
_collectionsTab.Draw();
|
+ "If you do not know how to do this, please take a look at the readme in Penumbras github repository or join us in discord.\n"
|
||||||
DrawChangedItemTab();
|
+ "If you are developing for Penumbra and see this, you should compile your version in debug mode to avoid it." );
|
||||||
_effectiveTab.Draw();
|
}
|
||||||
_debugTab.Draw();
|
else if( MainClass.DevPenumbraExists )
|
||||||
_resourceTab.Draw();
|
{
|
||||||
|
DrawProblemWindow(
|
||||||
|
$"You are loading a installed version of Penumbra from \"{Dalamud.PluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\", "
|
||||||
|
+ "but also still have some remnants of a custom install of Penumbra in your devPlugins folder.\n\n"
|
||||||
|
+ "This can cause some issues, so please go to your \"%%appdata%%\\XIVLauncher\\devPlugins\" folder and delete the Penumbra folder from there.\n\n"
|
||||||
|
+ "If you are developing for Penumbra, try to avoid mixing versions. This warning will not appear if compiled in Debug mode." );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var bar = ImRaii.TabBar( string.Empty, ImGuiTabBarFlags.NoTooltip );
|
||||||
|
SetupSizes();
|
||||||
|
_settingsTab.Draw();
|
||||||
|
DrawModsTab();
|
||||||
|
_collectionsTab.Draw();
|
||||||
|
DrawChangedItemTab();
|
||||||
|
_effectiveTab.Draw();
|
||||||
|
_debugTab.Draw();
|
||||||
|
_resourceTab.Draw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
|
|
@ -88,6 +95,21 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void DrawProblemWindow( string text )
|
||||||
|
{
|
||||||
|
using var color = ImRaii.PushColor( ImGuiCol.Text, Colors.RegexWarningBorder );
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGui.TextWrapped( text );
|
||||||
|
color.Pop();
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGui.NewLine();
|
||||||
|
SettingsTab.DrawDiscordButton( 0 );
|
||||||
|
ImGui.SameLine();
|
||||||
|
SettingsTab.DrawSupportButton();
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_selector.Dispose();
|
_selector.Dispose();
|
||||||
|
|
|
||||||
10
repo.json
10
repo.json
|
|
@ -4,8 +4,8 @@
|
||||||
"Name": "Penumbra",
|
"Name": "Penumbra",
|
||||||
"Description": "Runtime mod loader and manager.",
|
"Description": "Runtime mod loader and manager.",
|
||||||
"InternalName": "Penumbra",
|
"InternalName": "Penumbra",
|
||||||
"AssemblyVersion": "0.5.1.0",
|
"AssemblyVersion": "0.5.1.2",
|
||||||
"TestingAssemblyVersion": "0.5.1.0",
|
"TestingAssemblyVersion": "0.5.1.2",
|
||||||
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"DalamudApiLevel": 6,
|
"DalamudApiLevel": 6,
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
"DownloadCount": 0,
|
"DownloadCount": 0,
|
||||||
"LastUpdate": 0,
|
"LastUpdate": 0,
|
||||||
"LoadPriority": 69420,
|
"LoadPriority": 69420,
|
||||||
"DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/0.5.1.0/Penumbra.zip",
|
"DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/0.5.1.2/Penumbra.zip",
|
||||||
"DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/0.5.1.0/Penumbra.zip",
|
"DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/0.5.1.2/Penumbra.zip",
|
||||||
"DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/0.5.1.0/Penumbra.zip",
|
"DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/0.5.1.2/Penumbra.zip",
|
||||||
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue