mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Merge branch 'master' into Textures
This commit is contained in:
commit
aeb2e9facd
35 changed files with 581 additions and 166 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit b92dbe60887503a77a89aeae80729236fb2bfa10
|
||||
Subproject commit 98064e790042c90c82a58fbfa79201bd69800758
|
||||
|
|
@ -31,6 +31,13 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
|
|||
Crc64 = Functions.ComputeCrc64( InternalName.Span );
|
||||
}
|
||||
|
||||
public FullPath( Utf8GamePath path )
|
||||
{
|
||||
FullName = path.ToString().Replace( '/', '\\' );
|
||||
InternalName = path.Path;
|
||||
Crc64 = Functions.ComputeCrc64( InternalName.Span );
|
||||
}
|
||||
|
||||
public bool Exists
|
||||
=> File.Exists( FullName );
|
||||
|
||||
|
|
|
|||
|
|
@ -96,9 +96,13 @@ public interface IPenumbraApi : IPenumbraApiBase
|
|||
// Queue redrawing of all currently available actors with the given RedrawType.
|
||||
public void RedrawAll( RedrawType setting );
|
||||
|
||||
// Resolve a given gamePath via Penumbra using the Default and Forced collections.
|
||||
// Resolve a given gamePath via Penumbra using the Default collection.
|
||||
// Returns the given gamePath if penumbra would not manipulate it.
|
||||
public string ResolvePath( string gamePath );
|
||||
public string ResolveDefaultPath( string gamePath );
|
||||
|
||||
// Resolve a given gamePath via Penumbra using the Interface collection.
|
||||
// Returns the given gamePath if penumbra would not manipulate it.
|
||||
public string ResolveInterfacePath( string gamePath );
|
||||
|
||||
// Resolve a given gamePath via Penumbra using the character collection for the given name (if it exists) and the Forced collections.
|
||||
// Returns the given gamePath if penumbra would not manipulate it.
|
||||
|
|
@ -133,6 +137,9 @@ public interface IPenumbraApi : IPenumbraApiBase
|
|||
// Obtain the name of the default collection.
|
||||
public string GetDefaultCollection();
|
||||
|
||||
// Obtain the name of the interface collection.
|
||||
public string GetInterfaceCollection();
|
||||
|
||||
// Obtain the name of the collection associated with characterName and whether it is configured or inferred from default.
|
||||
public (string, bool) GetCharacterCollection( string characterName );
|
||||
|
||||
|
|
|
|||
|
|
@ -311,6 +311,13 @@ public class IpcTester : IDisposable
|
|||
.InvokeFunc( _currentResolvePath ) );
|
||||
}
|
||||
|
||||
DrawIntro( PenumbraIpc.LabelProviderResolveInterface, "Interface Collection Resolve" );
|
||||
if( _currentResolvePath.Length != 0 )
|
||||
{
|
||||
ImGui.TextUnformatted( _pi.GetIpcSubscriber< string, string >( PenumbraIpc.LabelProviderResolveInterface )
|
||||
.InvokeFunc( _currentResolvePath ) );
|
||||
}
|
||||
|
||||
DrawIntro( PenumbraIpc.LabelProviderResolveCharacter, "Character Collection Resolve" );
|
||||
if( _currentResolvePath.Length != 0 && _currentResolveCharacter.Length != 0 )
|
||||
{
|
||||
|
|
@ -568,6 +575,8 @@ public class IpcTester : IDisposable
|
|||
ImGui.TextUnformatted( _pi.GetIpcSubscriber< string >( PenumbraIpc.LabelProviderCurrentCollectionName ).InvokeFunc() );
|
||||
DrawIntro( PenumbraIpc.LabelProviderDefaultCollectionName, "Default Collection" );
|
||||
ImGui.TextUnformatted( _pi.GetIpcSubscriber< string >( PenumbraIpc.LabelProviderDefaultCollectionName ).InvokeFunc() );
|
||||
DrawIntro( PenumbraIpc.LabelProviderInterfaceCollectionName, "Interface Collection" );
|
||||
ImGui.TextUnformatted( _pi.GetIpcSubscriber< string >( PenumbraIpc.LabelProviderInterfaceCollectionName ).InvokeFunc() );
|
||||
DrawIntro( PenumbraIpc.LabelProviderCharacterCollectionName, "Character" );
|
||||
ImGui.SetNextItemWidth( 200 * ImGuiHelpers.GlobalScale );
|
||||
ImGui.InputTextWithHint( "##characterCollectionName", "Character Name...", ref _characterCollectionName, 64 );
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Penumbra.Api;
|
|||
public class PenumbraApi : IDisposable, IPenumbraApi
|
||||
{
|
||||
public (int, int) ApiVersion
|
||||
=> ( 4, 13 );
|
||||
=> ( 4, 14 );
|
||||
|
||||
private Penumbra? _penumbra;
|
||||
private Lumina.GameData? _lumina;
|
||||
|
|
@ -141,12 +141,18 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
_penumbra!.ObjectReloader.RedrawAll( setting );
|
||||
}
|
||||
|
||||
public string ResolvePath( string path )
|
||||
public string ResolveDefaultPath( string path )
|
||||
{
|
||||
CheckInitialized();
|
||||
return ResolvePath( path, Penumbra.ModManager, Penumbra.CollectionManager.Default );
|
||||
}
|
||||
|
||||
public string ResolveInterfacePath( string path )
|
||||
{
|
||||
CheckInitialized();
|
||||
return ResolvePath( path, Penumbra.ModManager, Penumbra.CollectionManager.Interface );
|
||||
}
|
||||
|
||||
public string ResolvePlayerPath( string path )
|
||||
{
|
||||
CheckInitialized();
|
||||
|
|
@ -185,7 +191,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
}
|
||||
|
||||
public T? GetFile< T >( string gamePath ) where T : FileResource
|
||||
=> GetFileIntern< T >( ResolvePath( gamePath ) );
|
||||
=> GetFileIntern< T >( ResolveDefaultPath( gamePath ) );
|
||||
|
||||
public T? GetFile< T >( string gamePath, string characterName ) where T : FileResource
|
||||
=> GetFileIntern< T >( ResolvePath( gamePath, characterName ) );
|
||||
|
|
@ -233,6 +239,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
return Penumbra.CollectionManager.Default.Name;
|
||||
}
|
||||
|
||||
public string GetInterfaceCollection()
|
||||
{
|
||||
CheckInitialized();
|
||||
return Penumbra.CollectionManager.Interface.Name;
|
||||
}
|
||||
|
||||
public (string, bool) GetCharacterCollection( string characterName )
|
||||
{
|
||||
CheckInitialized();
|
||||
|
|
|
|||
|
|
@ -274,6 +274,7 @@ public partial class PenumbraIpc
|
|||
public partial class PenumbraIpc
|
||||
{
|
||||
public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath";
|
||||
public const string LabelProviderResolveInterface = "Penumbra.ResolveInterfacePath";
|
||||
public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath";
|
||||
public const string LabelProviderResolvePlayer = "Penumbra.ResolvePlayerPath";
|
||||
public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo";
|
||||
|
|
@ -285,6 +286,7 @@ public partial class PenumbraIpc
|
|||
public const string LabelProviderGameObjectResourcePathResolved = "Penumbra.GameObjectResourcePathResolved";
|
||||
|
||||
internal ICallGateProvider< string, string >? ProviderResolveDefault;
|
||||
internal ICallGateProvider< string, string >? ProviderResolveInterface;
|
||||
internal ICallGateProvider< string, string, string >? ProviderResolveCharacter;
|
||||
internal ICallGateProvider< string, string >? ProviderResolvePlayer;
|
||||
internal ICallGateProvider< IntPtr, (IntPtr, string) >? ProviderGetDrawObjectInfo;
|
||||
|
|
@ -300,13 +302,23 @@ public partial class PenumbraIpc
|
|||
try
|
||||
{
|
||||
ProviderResolveDefault = pi.GetIpcProvider< string, string >( LabelProviderResolveDefault );
|
||||
ProviderResolveDefault.RegisterFunc( Api.ResolvePath );
|
||||
ProviderResolveDefault.RegisterFunc( Api.ResolveDefaultPath );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Error registering IPC provider for {LabelProviderResolveDefault}:\n{e}" );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderResolveInterface = pi.GetIpcProvider< string, string >( LabelProviderResolveInterface );
|
||||
ProviderResolveInterface.RegisterFunc( Api.ResolveInterfacePath );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Error registering IPC provider for {LabelProviderResolveInterface}:\n{e}" );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderResolveCharacter = pi.GetIpcProvider< string, string, string >( LabelProviderResolveCharacter );
|
||||
|
|
@ -411,6 +423,7 @@ public partial class PenumbraIpc
|
|||
ProviderGetDrawObjectInfo?.UnregisterFunc();
|
||||
ProviderGetCutsceneParentIndex?.UnregisterFunc();
|
||||
ProviderResolveDefault?.UnregisterFunc();
|
||||
ProviderResolveInterface?.UnregisterFunc();
|
||||
ProviderResolveCharacter?.UnregisterFunc();
|
||||
ProviderReverseResolvePath?.UnregisterFunc();
|
||||
ProviderReverseResolvePathPlayer?.UnregisterFunc();
|
||||
|
|
@ -499,6 +512,7 @@ public partial class PenumbraIpc
|
|||
public const string LabelProviderGetCollections = "Penumbra.GetCollections";
|
||||
public const string LabelProviderCurrentCollectionName = "Penumbra.GetCurrentCollectionName";
|
||||
public const string LabelProviderDefaultCollectionName = "Penumbra.GetDefaultCollectionName";
|
||||
public const string LabelProviderInterfaceCollectionName = "Penumbra.GetInterfaceCollectionName";
|
||||
public const string LabelProviderCharacterCollectionName = "Penumbra.GetCharacterCollectionName";
|
||||
public const string LabelProviderGetPlayerMetaManipulations = "Penumbra.GetPlayerMetaManipulations";
|
||||
public const string LabelProviderGetMetaManipulations = "Penumbra.GetMetaManipulations";
|
||||
|
|
@ -507,6 +521,7 @@ public partial class PenumbraIpc
|
|||
internal ICallGateProvider< IList< string > >? ProviderGetCollections;
|
||||
internal ICallGateProvider< string >? ProviderCurrentCollectionName;
|
||||
internal ICallGateProvider< string >? ProviderDefaultCollectionName;
|
||||
internal ICallGateProvider< string >? ProviderInterfaceCollectionName;
|
||||
internal ICallGateProvider< string, (string, bool) >? ProviderCharacterCollectionName;
|
||||
internal ICallGateProvider< string >? ProviderGetPlayerMetaManipulations;
|
||||
internal ICallGateProvider< string, string >? ProviderGetMetaManipulations;
|
||||
|
|
@ -553,6 +568,16 @@ public partial class PenumbraIpc
|
|||
Penumbra.Log.Error( $"Error registering IPC provider for {LabelProviderDefaultCollectionName}:\n{e}" );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderInterfaceCollectionName = pi.GetIpcProvider<string>( LabelProviderInterfaceCollectionName );
|
||||
ProviderInterfaceCollectionName.RegisterFunc( Api.GetInterfaceCollection );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Penumbra.Log.Error( $"Error registering IPC provider for {LabelProviderInterfaceCollectionName}:\n{e}" );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderCharacterCollectionName = pi.GetIpcProvider< string, (string, bool) >( LabelProviderCharacterCollectionName );
|
||||
|
|
@ -590,6 +615,7 @@ public partial class PenumbraIpc
|
|||
ProviderGetCollections?.UnregisterFunc();
|
||||
ProviderCurrentCollectionName?.UnregisterFunc();
|
||||
ProviderDefaultCollectionName?.UnregisterFunc();
|
||||
ProviderInterfaceCollectionName?.UnregisterFunc();
|
||||
ProviderCharacterCollectionName?.UnregisterFunc();
|
||||
ProviderGetMetaManipulations?.UnregisterFunc();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
|
|
@ -26,6 +27,9 @@ public partial class ModCollection
|
|||
// The collection used for general file redirections and all characters not specifically named.
|
||||
public ModCollection Default { get; private set; } = Empty;
|
||||
|
||||
// The collection used for all files categorized as UI files.
|
||||
public ModCollection Interface { get; private set; } = Empty;
|
||||
|
||||
// A single collection that can not be deleted as a fallback for the current collection.
|
||||
private ModCollection DefaultName { get; set; } = Empty;
|
||||
|
||||
|
|
@ -53,6 +57,7 @@ public partial class ModCollection
|
|||
return type switch
|
||||
{
|
||||
CollectionType.Default => Default,
|
||||
CollectionType.Interface => Interface,
|
||||
CollectionType.Current => Current,
|
||||
CollectionType.Character => name != null ? _characters.TryGetValue( name, out var c ) ? c : null : null,
|
||||
CollectionType.Inactive => name != null ? ByName( name, out var c ) ? c : null : null,
|
||||
|
|
@ -65,8 +70,9 @@ public partial class ModCollection
|
|||
{
|
||||
var oldCollectionIdx = collectionType switch
|
||||
{
|
||||
CollectionType.Default => Default.Index,
|
||||
CollectionType.Current => Current.Index,
|
||||
CollectionType.Default => Default.Index,
|
||||
CollectionType.Interface => Interface.Index,
|
||||
CollectionType.Current => Current.Index,
|
||||
CollectionType.Character => characterName?.Length > 0
|
||||
? _characters.TryGetValue( characterName, out var c )
|
||||
? c.Index
|
||||
|
|
@ -97,6 +103,9 @@ public partial class ModCollection
|
|||
Default.SetFiles();
|
||||
}
|
||||
|
||||
break;
|
||||
case CollectionType.Interface:
|
||||
Interface = newCollection;
|
||||
break;
|
||||
case CollectionType.Current:
|
||||
Current = newCollection;
|
||||
|
|
@ -118,6 +127,7 @@ public partial class ModCollection
|
|||
private void UpdateCurrentCollectionInUse()
|
||||
=> CurrentCollectionInUse = _specialCollections
|
||||
.OfType< ModCollection >()
|
||||
.Prepend( Interface )
|
||||
.Prepend( Default )
|
||||
.Concat( Characters.Values )
|
||||
.SelectMany( c => c.GetFlattenedInheritance() ).Contains( Current );
|
||||
|
|
@ -192,7 +202,7 @@ public partial class ModCollection
|
|||
var configChanged = !ReadActiveCollections( out var jObject );
|
||||
|
||||
// Load the default collection.
|
||||
var defaultName = jObject[ nameof( Default ) ]?.ToObject< string >() ?? ( configChanged ? DefaultCollection : Empty.Name );
|
||||
var defaultName = jObject[ nameof( Default ) ]?.ToObject< string >() ?? ( configChanged ? Empty.Name : DefaultCollection );
|
||||
var defaultIdx = GetIndexForCollectionName( defaultName );
|
||||
if( defaultIdx < 0 )
|
||||
{
|
||||
|
|
@ -205,12 +215,28 @@ public partial class ModCollection
|
|||
Default = this[ defaultIdx ];
|
||||
}
|
||||
|
||||
// Load the interface collection.
|
||||
var interfaceName = jObject[ nameof( Interface ) ]?.ToObject< string >() ?? ( configChanged ? Empty.Name : Default.Name );
|
||||
var interfaceIdx = GetIndexForCollectionName( interfaceName );
|
||||
if( interfaceIdx < 0 )
|
||||
{
|
||||
Penumbra.Log.Error(
|
||||
$"Last choice of {ConfigWindow.InterfaceCollection} {interfaceName} is not available, reset to {Empty.Name}." );
|
||||
Interface = Empty;
|
||||
configChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Interface = this[ interfaceIdx ];
|
||||
}
|
||||
|
||||
// Load the current collection.
|
||||
var currentName = jObject[ nameof( Current ) ]?.ToObject< string >() ?? DefaultCollection;
|
||||
var currentIdx = GetIndexForCollectionName( currentName );
|
||||
if( currentIdx < 0 )
|
||||
{
|
||||
Penumbra.Log.Error( $"Last choice of {ConfigWindow.SelectedCollection} {currentName} is not available, reset to {DefaultCollection}." );
|
||||
Penumbra.Log.Error(
|
||||
$"Last choice of {ConfigWindow.SelectedCollection} {currentName} is not available, reset to {DefaultCollection}." );
|
||||
Current = DefaultName;
|
||||
configChanged = true;
|
||||
}
|
||||
|
|
@ -260,21 +286,20 @@ public partial class ModCollection
|
|||
{
|
||||
SaveActiveCollections();
|
||||
}
|
||||
|
||||
CreateNecessaryCaches();
|
||||
}
|
||||
|
||||
|
||||
public void SaveActiveCollections()
|
||||
{
|
||||
Penumbra.Framework.RegisterDelayed( nameof( SaveActiveCollections ),
|
||||
() => SaveActiveCollections( Default.Name, Current.Name, Characters.Select( kvp => ( kvp.Key, kvp.Value.Name ) ),
|
||||
() => SaveActiveCollections( Default.Name, Interface.Name, Current.Name,
|
||||
Characters.Select( kvp => ( kvp.Key, kvp.Value.Name ) ),
|
||||
_specialCollections.WithIndex()
|
||||
.Where( c => c.Item1 != null )
|
||||
.Select( c => ( ( CollectionType )c.Item2, c.Item1!.Name ) ) ) );
|
||||
}
|
||||
|
||||
internal static void SaveActiveCollections( string def, string current, IEnumerable< (string, string) > characters,
|
||||
internal static void SaveActiveCollections( string def, string ui, string current, IEnumerable< (string, string) > characters,
|
||||
IEnumerable< (CollectionType, string) > special )
|
||||
{
|
||||
var file = ActiveCollectionFile;
|
||||
|
|
@ -287,6 +312,8 @@ public partial class ModCollection
|
|||
j.WriteStartObject();
|
||||
j.WritePropertyName( nameof( Default ) );
|
||||
j.WriteValue( def );
|
||||
j.WritePropertyName( nameof( Interface ) );
|
||||
j.WriteValue( ui );
|
||||
j.WritePropertyName( nameof( Current ) );
|
||||
j.WriteValue( current );
|
||||
foreach( var (type, collection) in special )
|
||||
|
|
@ -335,7 +362,6 @@ public partial class ModCollection
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Save if any of the active collections is changed.
|
||||
private void SaveOnChange( CollectionType collectionType, ModCollection? _1, ModCollection? _2, string? _3 )
|
||||
{
|
||||
|
|
@ -345,23 +371,27 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Cache handling.
|
||||
private void CreateNecessaryCaches()
|
||||
// Cache handling. Usually recreate caches on the next framework tick,
|
||||
// but at launch create all of them at once.
|
||||
public void CreateNecessaryCaches()
|
||||
{
|
||||
Default.CreateCache();
|
||||
Current.CreateCache();
|
||||
var tasks = _specialCollections.OfType< ModCollection >()
|
||||
.Concat( _characters.Values )
|
||||
.Prepend( Current )
|
||||
.Prepend( Default )
|
||||
.Prepend( Interface )
|
||||
.Distinct()
|
||||
.Select( c => Task.Run( c.CalculateEffectiveFileListInternal ) )
|
||||
.ToArray();
|
||||
|
||||
foreach( var collection in _specialCollections.OfType< ModCollection >().Concat( _characters.Values ) )
|
||||
{
|
||||
collection.CreateCache();
|
||||
}
|
||||
Task.WaitAll( tasks );
|
||||
}
|
||||
|
||||
private void RemoveCache( int idx )
|
||||
{
|
||||
if( idx != Empty.Index
|
||||
&& idx != Default.Index
|
||||
&& idx != Interface.Index
|
||||
&& idx != Current.Index
|
||||
&& _specialCollections.All( c => c == null || c.Index != idx )
|
||||
&& _characters.Values.All( c => c.Index != idx ) )
|
||||
|
|
|
|||
|
|
@ -45,8 +45,9 @@ public enum CollectionType : byte
|
|||
|
||||
Inactive, // A collection was added or removed
|
||||
Default, // The default collection was changed
|
||||
Interface, // The ui collection was changed
|
||||
Character, // A character collection was changed
|
||||
Current, // The current collection was changed.
|
||||
Current, // The current collection was changed
|
||||
}
|
||||
|
||||
public static class CollectionTypeExtensions
|
||||
|
|
@ -96,6 +97,7 @@ public static class CollectionTypeExtensions
|
|||
CollectionType.VeenaNpc => SubRace.Veena.ToName() + " (NPC)",
|
||||
CollectionType.Inactive => "Collection",
|
||||
CollectionType.Default => "Default",
|
||||
CollectionType.Interface => "Interface",
|
||||
CollectionType.Character => "Character",
|
||||
CollectionType.Current => "Current",
|
||||
_ => string.Empty,
|
||||
|
|
|
|||
|
|
@ -133,67 +133,6 @@ public partial class ModCollection
|
|||
Penumbra.Log.Debug( $"[{Thread.CurrentThread.ManagedThreadId}] Recalculation of effective file list for {AnonymizedName} finished." );
|
||||
}
|
||||
|
||||
// Set Metadata files.
|
||||
public void SetEqpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.ResetEqpFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.SetEqpFiles();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEqdpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.ResetEqdpFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.SetEqdpFiles();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetGmpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.ResetGmpFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.SetGmpFiles();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEstFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.ResetEstFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.SetEstFiles();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCmpFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
MetaManager.ResetCmpFiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.SetCmpFiles();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFiles()
|
||||
{
|
||||
if( _cache == null )
|
||||
|
|
@ -207,6 +146,18 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
public void SetMetaFile( Interop.Structs.CharacterUtility.Index idx )
|
||||
{
|
||||
if( _cache == null )
|
||||
{
|
||||
Penumbra.CharacterUtility.ResetResource( idx );
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.MetaManipulations.SetFile( idx );
|
||||
}
|
||||
}
|
||||
|
||||
// Used for short periods of changed files.
|
||||
public CharacterUtility.List.MetaReverter? TemporarilySetEqdpFile( GenderRace genderRace, bool accessory )
|
||||
=> _cache?.MetaManipulations.TemporarilySetEqdpFile( genderRace, accessory );
|
||||
|
|
@ -222,5 +173,4 @@ public partial class ModCollection
|
|||
|
||||
public CharacterUtility.List.MetaReverter? TemporarilySetEstFile( EstManipulation.EstType type )
|
||||
=> _cache?.MetaManipulations.TemporarilySetEstFile( type );
|
||||
|
||||
}
|
||||
|
|
@ -47,12 +47,35 @@ public partial class Configuration
|
|||
m.Version2To3();
|
||||
m.Version3To4();
|
||||
m.Version4To5();
|
||||
m.Version5To6();
|
||||
}
|
||||
|
||||
// A new tutorial step was inserted in the middle.
|
||||
// The UI collection and a new tutorial for it was added.
|
||||
// The migration for the UI collection itself happens in the ActiveCollections file.
|
||||
private void Version5To6()
|
||||
{
|
||||
if( _config.Version != 5 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
if( _config.TutorialStep == 25 )
|
||||
{
|
||||
_config.TutorialStep = 27;
|
||||
}
|
||||
|
||||
_config.Version = 6;
|
||||
}
|
||||
|
||||
// Mod backup extension was changed from .zip to .pmp.
|
||||
// Actual migration takes place in ModManager.
|
||||
private void Version4To5()
|
||||
{
|
||||
if( _config.Version != 4 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Mod.Manager.MigrateModBackups = true;
|
||||
_config.Version = 5;
|
||||
}
|
||||
|
|
@ -189,7 +212,7 @@ public partial class Configuration
|
|||
CurrentCollection = _data[ nameof( CurrentCollection ) ]?.ToObject< string >() ?? CurrentCollection;
|
||||
DefaultCollection = _data[ nameof( DefaultCollection ) ]?.ToObject< string >() ?? DefaultCollection;
|
||||
CharacterCollections = _data[ nameof( CharacterCollections ) ]?.ToObject< Dictionary< string, string > >() ?? CharacterCollections;
|
||||
ModCollection.Manager.SaveActiveCollections( DefaultCollection, CurrentCollection,
|
||||
ModCollection.Manager.SaveActiveCollections( DefaultCollection, CurrentCollection, DefaultCollection,
|
||||
CharacterCollections.Select( kvp => ( kvp.Key, kvp.Value ) ), Array.Empty< (CollectionType, string) >() );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ public partial class Configuration : IPluginConfiguration
|
|||
// Contains some default values or boundaries for config values.
|
||||
public static class Constants
|
||||
{
|
||||
public const int CurrentVersion = 5;
|
||||
public const int CurrentVersion = 6;
|
||||
public const float MaxAbsoluteSize = 600;
|
||||
public const int DefaultAbsoluteSize = 250;
|
||||
public const float MinAbsoluteSize = 50;
|
||||
|
|
|
|||
65
Penumbra/Interop/CharacterUtility.DecalReverter.cs
Normal file
65
Penumbra/Interop/CharacterUtility.DecalReverter.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.Interop;
|
||||
|
||||
public unsafe partial class CharacterUtility
|
||||
{
|
||||
public sealed class DecalReverter : IDisposable
|
||||
{
|
||||
public static readonly Utf8GamePath DecalPath =
|
||||
Utf8GamePath.FromString( "chara/common/texture/decal_equip/_stigma.tex", out var p ) ? p : Utf8GamePath.Empty;
|
||||
|
||||
public static readonly Utf8GamePath TransparentPath =
|
||||
Utf8GamePath.FromString( "chara/common/texture/transparent.tex", out var p ) ? p : Utf8GamePath.Empty;
|
||||
|
||||
private readonly Structs.TextureResourceHandle* _decal;
|
||||
private readonly Structs.TextureResourceHandle* _transparent;
|
||||
|
||||
public DecalReverter( ModCollection? collection, bool doDecal )
|
||||
{
|
||||
var ptr = Penumbra.CharacterUtility.Address;
|
||||
_decal = null;
|
||||
_transparent = null;
|
||||
if( doDecal )
|
||||
{
|
||||
var decalPath = collection?.ResolvePath( DecalPath )?.InternalName ?? DecalPath.Path;
|
||||
var decalHandle = Penumbra.ResourceLoader.ResolvePathSync( ResourceCategory.Chara, ResourceType.Tex, decalPath );
|
||||
_decal = ( Structs.TextureResourceHandle* )decalHandle;
|
||||
if( _decal != null )
|
||||
{
|
||||
ptr->DecalTexResource = _decal;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var transparentPath = collection?.ResolvePath( TransparentPath )?.InternalName ?? TransparentPath.Path;
|
||||
var transparentHandle = Penumbra.ResourceLoader.ResolvePathSync( ResourceCategory.Chara, ResourceType.Tex, transparentPath );
|
||||
_transparent = ( Structs.TextureResourceHandle* )transparentHandle;
|
||||
if( _transparent != null )
|
||||
{
|
||||
ptr->TransparentTexResource = _transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var ptr = Penumbra.CharacterUtility.Address;
|
||||
if( _decal != null )
|
||||
{
|
||||
ptr->DecalTexResource = ( Structs.TextureResourceHandle* )Penumbra.CharacterUtility._defaultDecalResource;
|
||||
--_decal->Handle.RefCount;
|
||||
}
|
||||
|
||||
if( _transparent != null )
|
||||
{
|
||||
ptr->TransparentTexResource = ( Structs.TextureResourceHandle* )Penumbra.CharacterUtility._defaultTransparentResource;
|
||||
--_transparent->Handle.RefCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -50,7 +50,8 @@ public unsafe partial class CharacterUtility
|
|||
|
||||
public MetaReverter TemporarilyResetResource()
|
||||
{
|
||||
Penumbra.Log.Verbose( $"Temporarily reset resource {GlobalIndex} to default at 0x{_defaultResourceData:X} ({_defaultResourceSize} bytes)." );
|
||||
Penumbra.Log.Verbose(
|
||||
$"Temporarily reset resource {GlobalIndex} to default at 0x{_defaultResourceData:X} ({_defaultResourceSize} bytes)." );
|
||||
var reverter = new MetaReverter( this );
|
||||
_entries.AddFirst( reverter );
|
||||
ResetResourceInternal();
|
||||
|
|
@ -84,6 +85,9 @@ public unsafe partial class CharacterUtility
|
|||
private void ResetResourceInternal()
|
||||
=> SetResourceInternal( _defaultResourceData, _defaultResourceSize );
|
||||
|
||||
private void SetResourceToDefaultCollection()
|
||||
=> Penumbra.CollectionManager.Default.SetMetaFile( GlobalIndex );
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if( _entries.Count > 0 )
|
||||
|
|
@ -127,14 +131,14 @@ public unsafe partial class CharacterUtility
|
|||
|
||||
if( list.Count == 0 )
|
||||
{
|
||||
List.ResetResourceInternal();
|
||||
List.SetResourceToDefaultCollection();
|
||||
}
|
||||
else
|
||||
{
|
||||
var next = list.First!.Value;
|
||||
if( next.Resetter )
|
||||
{
|
||||
List.ResetResourceInternal();
|
||||
List.SetResourceToDefaultCollection();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ public unsafe partial class CharacterUtility : IDisposable
|
|||
|
||||
public bool Ready { get; private set; }
|
||||
public event Action LoadingFinished;
|
||||
private IntPtr _defaultTransparentResource;
|
||||
private IntPtr _defaultDecalResource;
|
||||
|
||||
// The relevant indices depend on which meta manipulations we allow for.
|
||||
// The defines are set in the project configuration.
|
||||
|
|
@ -76,25 +78,37 @@ public unsafe partial class CharacterUtility : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
if( _defaultTransparentResource == IntPtr.Zero )
|
||||
{
|
||||
_defaultTransparentResource = ( IntPtr )Address->TransparentTexResource;
|
||||
anyMissing |= _defaultTransparentResource == IntPtr.Zero;
|
||||
}
|
||||
|
||||
if( _defaultDecalResource == IntPtr.Zero )
|
||||
{
|
||||
_defaultDecalResource = ( IntPtr )Address->DecalTexResource;
|
||||
anyMissing |= _defaultDecalResource == IntPtr.Zero;
|
||||
}
|
||||
|
||||
if( !anyMissing )
|
||||
{
|
||||
Ready = true;
|
||||
LoadingFinished.Invoke();
|
||||
Ready = true;
|
||||
Dalamud.Framework.Update -= LoadDefaultResources;
|
||||
LoadingFinished.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetResource( Structs.CharacterUtility.Index resourceIdx, IntPtr data, int length )
|
||||
{
|
||||
var idx = ReverseIndices[( int )resourceIdx];
|
||||
var list = _lists[idx.Value];
|
||||
var idx = ReverseIndices[ ( int )resourceIdx ];
|
||||
var list = _lists[ idx.Value ];
|
||||
list.SetResource( data, length );
|
||||
}
|
||||
|
||||
public void ResetResource( Structs.CharacterUtility.Index resourceIdx )
|
||||
{
|
||||
var idx = ReverseIndices[( int )resourceIdx];
|
||||
var list = _lists[idx.Value];
|
||||
var idx = ReverseIndices[ ( int )resourceIdx ];
|
||||
var list = _lists[ idx.Value ];
|
||||
list.ResetResource();
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +133,9 @@ public unsafe partial class CharacterUtility : IDisposable
|
|||
{
|
||||
list.Dispose();
|
||||
}
|
||||
|
||||
Address->TransparentTexResource = ( Structs.TextureResourceHandle* )_defaultTransparentResource;
|
||||
Address->DecalTexResource = ( Structs.TextureResourceHandle* )_defaultDecalResource;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -71,7 +71,13 @@ public unsafe partial class ResourceLoader
|
|||
|
||||
private event Action< Utf8GamePath, ResourceType, FullPath?, object? >? PathResolved;
|
||||
|
||||
private ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId,
|
||||
public ResourceHandle* ResolvePathSync( ResourceCategory category, ResourceType type, Utf8String path )
|
||||
{
|
||||
var hash = path.Crc32;
|
||||
return GetResourceHandler( true, *ResourceManager, &category, &type, &hash, path.Path, null, false );
|
||||
}
|
||||
|
||||
internal ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId,
|
||||
ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk )
|
||||
{
|
||||
if( !Utf8GamePath.FromPointer( path, out var gamePath ) )
|
||||
|
|
@ -86,7 +92,7 @@ public unsafe partial class ResourceLoader
|
|||
|
||||
// If no replacements are being made, we still want to be able to trigger the event.
|
||||
var (resolvedPath, data) = ResolvePath( gamePath, *categoryId, *resourceType, *resourceHash );
|
||||
PathResolved?.Invoke( gamePath, *resourceType, resolvedPath, data );
|
||||
PathResolved?.Invoke( gamePath, *resourceType, resolvedPath ?? ( gamePath.IsRooted() ? new FullPath( gamePath ) : null ), data );
|
||||
if( resolvedPath == null )
|
||||
{
|
||||
var retUnmodified =
|
||||
|
|
@ -121,6 +127,12 @@ public unsafe partial class ResourceLoader
|
|||
}
|
||||
|
||||
path = path.ToLower();
|
||||
if( category == ResourceCategory.Ui )
|
||||
{
|
||||
var resolved = Penumbra.CollectionManager.Interface.ResolvePath( path );
|
||||
return ( resolved, Penumbra.CollectionManager.Interface.ToResolveData() );
|
||||
}
|
||||
|
||||
if( ResolvePathCustomization != null )
|
||||
{
|
||||
foreach( var resolver in ResolvePathCustomization.GetInvocationList() )
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public unsafe partial class ResourceLoader
|
|||
// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag.
|
||||
public delegate IntPtr CheckFileStatePrototype( IntPtr unk1, ulong crc64 );
|
||||
|
||||
[Signature( "E8 ?? ?? ?? ?? 48 85 c0 74 ?? 45 0f b6 ce 48 89 44 24", DetourName = "CheckFileStateDetour" )]
|
||||
[Signature( "E8 ?? ?? ?? ?? 48 85 c0 74 ?? 45 0f b6 ce 48 89 44 24", DetourName = nameof(CheckFileStateDetour) )]
|
||||
public Hook< CheckFileStatePrototype > CheckFileStateHook = null!;
|
||||
|
||||
private IntPtr CheckFileStateDetour( IntPtr ptr, ulong crc64 )
|
||||
|
|
@ -48,7 +48,7 @@ public unsafe partial class ResourceLoader
|
|||
// We hook the extern functions to just return the local one if given the custom flag as last argument.
|
||||
public delegate byte LoadTexFileExternPrototype( ResourceHandle* handle, int unk1, IntPtr unk2, bool unk3, IntPtr unk4 );
|
||||
|
||||
[Signature( "E8 ?? ?? ?? ?? 0F B6 E8 48 8B CB E8", DetourName = "LoadTexFileExternDetour" )]
|
||||
[Signature( "E8 ?? ?? ?? ?? 0F B6 E8 48 8B CB E8", DetourName = nameof(LoadTexFileExternDetour) )]
|
||||
public Hook< LoadTexFileExternPrototype > LoadTexFileExternHook = null!;
|
||||
|
||||
private byte LoadTexFileExternDetour( ResourceHandle* resourceHandle, int unk1, IntPtr unk2, bool unk3, IntPtr ptr )
|
||||
|
|
@ -59,7 +59,7 @@ public unsafe partial class ResourceLoader
|
|||
public delegate byte LoadMdlFileExternPrototype( ResourceHandle* handle, IntPtr unk1, bool unk2, IntPtr unk3 );
|
||||
|
||||
|
||||
[Signature( "E8 ?? ?? ?? ?? EB 02 B0 F1", DetourName = "LoadMdlFileExternDetour" )]
|
||||
[Signature( "E8 ?? ?? ?? ?? EB 02 B0 F1", DetourName = nameof(LoadMdlFileExternDetour) )]
|
||||
public Hook< LoadMdlFileExternPrototype > LoadMdlFileExternHook = null!;
|
||||
|
||||
private byte LoadMdlFileExternDetour( ResourceHandle* resourceHandle, IntPtr unk1, bool unk2, IntPtr ptr )
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Penumbra.Api;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ public unsafe partial class PathResolver
|
|||
{
|
||||
if( type == ResourceType.Tex
|
||||
&& LastCreatedCollection.Valid
|
||||
&& gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( 'd', 'e', 'c', 'a', 'l', '_', 'f', 'a', 'c', 'e' ) )
|
||||
&& gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( 'd', 'e', 'c', 'a', 'l' ) )
|
||||
{
|
||||
resolveData = LastCreatedCollection;
|
||||
return true;
|
||||
|
|
@ -135,18 +136,19 @@ public unsafe partial class PathResolver
|
|||
|
||||
private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d )
|
||||
{
|
||||
CharacterUtility.List.MetaReverter? cmp = null;
|
||||
var meta = DisposableContainer.Empty;
|
||||
if( LastGameObject != null )
|
||||
{
|
||||
_lastCreatedCollection = IdentifyCollection( LastGameObject );
|
||||
var modelPtr = &a;
|
||||
if( _lastCreatedCollection.ModCollection != Penumbra.CollectionManager.Default )
|
||||
{
|
||||
cmp = _lastCreatedCollection.ModCollection.TemporarilySetCmpFile();
|
||||
}
|
||||
|
||||
// Change the transparent or 1.0 Decal if necessary.
|
||||
var decal = new CharacterUtility.DecalReverter( _lastCreatedCollection.ModCollection, UsesDecal( a, c ) );
|
||||
// Change the rsp parameters if necessary.
|
||||
meta = new DisposableContainer( _lastCreatedCollection.ModCollection != Penumbra.CollectionManager.Default
|
||||
? _lastCreatedCollection.ModCollection.TemporarilySetCmpFile()
|
||||
: null, decal );
|
||||
try
|
||||
{
|
||||
var modelPtr = &a;
|
||||
CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ( IntPtr )modelPtr, b, c );
|
||||
}
|
||||
catch( Exception e )
|
||||
|
|
@ -156,7 +158,7 @@ public unsafe partial class PathResolver
|
|||
}
|
||||
|
||||
var ret = _characterBaseCreateHook.Original( a, b, c, d );
|
||||
using( cmp )
|
||||
using( meta )
|
||||
{
|
||||
if( LastGameObject != null )
|
||||
{
|
||||
|
|
@ -168,6 +170,11 @@ public unsafe partial class PathResolver
|
|||
}
|
||||
}
|
||||
|
||||
// Check the customize array for the FaceCustomization byte and the last bit of that.
|
||||
// Also check for humans.
|
||||
public static bool UsesDecal( uint modelId, IntPtr customizeData )
|
||||
=> modelId == 0 && ( ( byte* )customizeData )[ 12 ] > 0x7F;
|
||||
|
||||
|
||||
// Remove DrawObjects from the list when they are destroyed.
|
||||
private delegate void CharacterBaseDestructorDelegate( IntPtr drawBase );
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.Enums;
|
||||
using ObjectType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType;
|
||||
using static Penumbra.GameData.Enums.GenderRace;
|
||||
|
||||
namespace Penumbra.Interop.Resolver;
|
||||
|
||||
|
|
@ -77,10 +81,8 @@ public unsafe partial class PathResolver
|
|||
var collection = GetResolveData( drawObject );
|
||||
if( collection.Valid )
|
||||
{
|
||||
var race = GetDrawObjectGenderRace( drawObject );
|
||||
using var eqp = collection.ModCollection.TemporarilySetEqpFile();
|
||||
using var eqdp1 = collection.ModCollection.TemporarilySetEqdpFile( race, false );
|
||||
using var eqdp2 = collection.ModCollection.TemporarilySetEqdpFile( race, true );
|
||||
using var eqp = collection.ModCollection.TemporarilySetEqpFile();
|
||||
using var eqdp = ResolveEqdpData( collection.ModCollection, GetDrawObjectGenderRace( drawObject ), true, true );
|
||||
_onModelLoadCompleteHook.Original.Invoke( drawObject );
|
||||
}
|
||||
else
|
||||
|
|
@ -106,10 +108,8 @@ public unsafe partial class PathResolver
|
|||
var collection = GetResolveData( drawObject );
|
||||
if( collection.Valid )
|
||||
{
|
||||
var race = GetDrawObjectGenderRace( drawObject );
|
||||
using var eqp = collection.ModCollection.TemporarilySetEqpFile();
|
||||
using var eqdp1 = collection.ModCollection.TemporarilySetEqdpFile( race, false );
|
||||
using var eqdp2 = collection.ModCollection.TemporarilySetEqdpFile( race, true );
|
||||
using var eqp = collection.ModCollection.TemporarilySetEqpFile();
|
||||
using var eqdp = ResolveEqdpData( collection.ModCollection, GetDrawObjectGenderRace( drawObject ), true, true );
|
||||
_updateModelsHook.Original.Invoke( drawObject );
|
||||
}
|
||||
else
|
||||
|
|
@ -130,7 +130,7 @@ public unsafe partial class PathResolver
|
|||
}
|
||||
}
|
||||
|
||||
return GenderRace.Unknown;
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
public static GenderRace GetHumanGenderRace( IntPtr human )
|
||||
|
|
@ -201,7 +201,73 @@ public unsafe partial class PathResolver
|
|||
_inChangeCustomize = true;
|
||||
var resolveData = GetResolveData( human );
|
||||
using var eqp = resolveData.Valid ? resolveData.ModCollection.TemporarilySetEqpFile() : null;
|
||||
using var decals = resolveData.Valid
|
||||
? new CharacterUtility.DecalReverter( resolveData.ModCollection, DrawObjectState.UsesDecal( 0, data ) )
|
||||
: null;
|
||||
return _changeCustomize.Original( human, data, skipEquipment );
|
||||
}
|
||||
|
||||
public static DisposableContainer ResolveEqdpData( ModCollection collection, GenderRace race, bool equipment, bool accessory )
|
||||
{
|
||||
DisposableContainer Convert( params GenderRace[] races )
|
||||
{
|
||||
var equipmentEnumerable =
|
||||
equipment
|
||||
? races.Select( r => collection.TemporarilySetEqdpFile( r, false ) )
|
||||
: Array.Empty< IDisposable? >().AsEnumerable();
|
||||
var accessoryEnumerable =
|
||||
accessory
|
||||
? races.Select( r => collection.TemporarilySetEqdpFile( r, true ) )
|
||||
: Array.Empty< IDisposable? >().AsEnumerable();
|
||||
return new DisposableContainer( equipmentEnumerable.Concat( accessoryEnumerable ) );
|
||||
}
|
||||
|
||||
return race switch
|
||||
{
|
||||
MidlanderMale => Convert( MidlanderMale ),
|
||||
HighlanderMale => Convert( MidlanderMale, HighlanderMale ),
|
||||
ElezenMale => Convert( MidlanderMale, ElezenMale ),
|
||||
MiqoteMale => Convert( MidlanderMale, MiqoteMale ),
|
||||
RoegadynMale => Convert( MidlanderMale, RoegadynMale ),
|
||||
LalafellMale => Convert( MidlanderMale, LalafellMale ),
|
||||
AuRaMale => Convert( MidlanderMale, AuRaMale ),
|
||||
HrothgarMale => Convert( MidlanderMale, RoegadynMale, HrothgarMale ),
|
||||
VieraMale => Convert( MidlanderMale, VieraMale ),
|
||||
|
||||
MidlanderFemale => Convert( MidlanderMale, MidlanderFemale ),
|
||||
HighlanderFemale => Convert( MidlanderMale, MidlanderFemale, HighlanderFemale ),
|
||||
ElezenFemale => Convert( MidlanderMale, MidlanderFemale, ElezenFemale ),
|
||||
MiqoteFemale => Convert( MidlanderMale, MidlanderFemale, MiqoteFemale ),
|
||||
RoegadynFemale => Convert( MidlanderMale, MidlanderFemale, RoegadynFemale ),
|
||||
LalafellFemale => Convert( MidlanderMale, LalafellMale, LalafellFemale ),
|
||||
AuRaFemale => Convert( MidlanderMale, MidlanderFemale, AuRaFemale ),
|
||||
HrothgarFemale => Convert( MidlanderMale, MidlanderFemale, RoegadynFemale, HrothgarFemale ),
|
||||
VieraFemale => Convert( MidlanderMale, MidlanderFemale, VieraFemale ),
|
||||
|
||||
MidlanderMaleNpc => Convert( MidlanderMale, MidlanderMaleNpc ),
|
||||
HighlanderMaleNpc => Convert( MidlanderMale, HighlanderMale, HighlanderMaleNpc ),
|
||||
ElezenMaleNpc => Convert( MidlanderMale, ElezenMale, ElezenMaleNpc ),
|
||||
MiqoteMaleNpc => Convert( MidlanderMale, MiqoteMale, MiqoteMaleNpc ),
|
||||
RoegadynMaleNpc => Convert( MidlanderMale, RoegadynMale, RoegadynMaleNpc ),
|
||||
LalafellMaleNpc => Convert( MidlanderMale, LalafellMale, LalafellMaleNpc ),
|
||||
AuRaMaleNpc => Convert( MidlanderMale, AuRaMale, AuRaMaleNpc ),
|
||||
HrothgarMaleNpc => Convert( MidlanderMale, RoegadynMale, HrothgarMale, HrothgarMaleNpc ),
|
||||
VieraMaleNpc => Convert( MidlanderMale, VieraMale, VieraMaleNpc ),
|
||||
|
||||
MidlanderFemaleNpc => Convert( MidlanderMale, MidlanderFemale, MidlanderFemaleNpc ),
|
||||
HighlanderFemaleNpc => Convert( MidlanderMale, MidlanderFemale, HighlanderFemale, HighlanderFemaleNpc ),
|
||||
ElezenFemaleNpc => Convert( MidlanderMale, MidlanderFemale, ElezenFemale, ElezenFemaleNpc ),
|
||||
MiqoteFemaleNpc => Convert( MidlanderMale, MidlanderFemale, MiqoteFemale, MiqoteFemaleNpc ),
|
||||
RoegadynFemaleNpc => Convert( MidlanderMale, MidlanderFemale, RoegadynFemale, RoegadynFemaleNpc ),
|
||||
LalafellFemaleNpc => Convert( MidlanderMale, LalafellMale, LalafellFemale, LalafellFemaleNpc ),
|
||||
AuRaFemaleNpc => Convert( MidlanderMale, MidlanderFemale, AuRaFemale, AuRaFemaleNpc ),
|
||||
HrothgarFemaleNpc => Convert( MidlanderMale, MidlanderFemale, RoegadynFemale, HrothgarFemale, HrothgarFemaleNpc ),
|
||||
VieraFemaleNpc => Convert( MidlanderMale, MidlanderFemale, VieraFemale, VieraFemaleNpc ),
|
||||
|
||||
UnknownMaleNpc => Convert( MidlanderMale, UnknownMaleNpc ),
|
||||
UnknownFemaleNpc => Convert( MidlanderMale, MidlanderFemale, UnknownFemaleNpc ),
|
||||
_ => DisposableContainer.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -143,21 +143,15 @@ public partial class PathResolver
|
|||
|
||||
private IntPtr ResolveMdlHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType )
|
||||
{
|
||||
CharacterUtility.List.MetaReverter? Get()
|
||||
DisposableContainer Get()
|
||||
{
|
||||
if( modelType > 9 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var race = MetaState.GetHumanGenderRace( drawObject );
|
||||
if( race == GenderRace.Unknown )
|
||||
{
|
||||
return null;
|
||||
return DisposableContainer.Empty;
|
||||
}
|
||||
|
||||
var data = GetResolveData( drawObject );
|
||||
return !data.Valid ? null : data.ModCollection.TemporarilySetEqdpFile( race, modelType > 4 );
|
||||
return !data.Valid ? DisposableContainer.Empty : MetaState.ResolveEqdpData(data.ModCollection, MetaState.GetHumanGenderRace( drawObject ), modelType < 5, modelType > 4);
|
||||
}
|
||||
|
||||
using var eqdp = Get();
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ public unsafe struct CharacterUtility
|
|||
BodyEst,
|
||||
}
|
||||
|
||||
public const int IndexTransparentTex = 72;
|
||||
public const int IndexDecalTex = 73;
|
||||
|
||||
public static readonly Index[] EqdpIndices = Enum.GetNames< Index >()
|
||||
.Zip( Enum.GetValues< Index >() )
|
||||
.Where( n => n.First.StartsWith( "Eqdp" ) )
|
||||
|
|
@ -157,5 +160,11 @@ public unsafe struct CharacterUtility
|
|||
[FieldOffset( 8 + ( int )Index.HeadEst * 8 )]
|
||||
public ResourceHandle* HeadEstResource;
|
||||
|
||||
[FieldOffset( 8 + IndexTransparentTex * 8 )]
|
||||
public TextureResourceHandle* TransparentTexResource;
|
||||
|
||||
[FieldOffset( 8 + IndexDecalTex * 8 )]
|
||||
public TextureResourceHandle* DecalTexResource;
|
||||
|
||||
// not included resources have no known use case.
|
||||
}
|
||||
|
|
@ -5,6 +5,22 @@ using Penumbra.GameData.Enums;
|
|||
|
||||
namespace Penumbra.Interop.Structs;
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public unsafe struct TextureResourceHandle
|
||||
{
|
||||
[FieldOffset( 0x0 )]
|
||||
public ResourceHandle Handle;
|
||||
|
||||
[FieldOffset( 0x38 )]
|
||||
public IntPtr Unk;
|
||||
|
||||
[FieldOffset( 0x118 )]
|
||||
public IntPtr KernelTexture;
|
||||
|
||||
[FieldOffset( 0x20 )]
|
||||
public IntPtr NewKernelTexture;
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public unsafe struct ResourceHandle
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using OtterGui;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
|
|
@ -83,17 +84,14 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM
|
|||
}
|
||||
|
||||
_manipulations[ manip ] = mod;
|
||||
// Imc manipulations do not require character utility.
|
||||
if( manip.ManipulationType == MetaManipulation.Type.Imc )
|
||||
{
|
||||
return ApplyMod( manip.Imc );
|
||||
}
|
||||
|
||||
if( !Penumbra.CharacterUtility.Ready )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Imc manipulations do not require character utility,
|
||||
// but they do require the file space to be ready.
|
||||
return manip.ManipulationType switch
|
||||
{
|
||||
MetaManipulation.Type.Eqp => ApplyMod( manip.Eqp ),
|
||||
|
|
@ -101,6 +99,7 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM
|
|||
MetaManipulation.Type.Eqdp => ApplyMod( manip.Eqdp ),
|
||||
MetaManipulation.Type.Est => ApplyMod( manip.Est ),
|
||||
MetaManipulation.Type.Rsp => ApplyMod( manip.Rsp ),
|
||||
MetaManipulation.Type.Imc => ApplyMod( manip.Imc ),
|
||||
MetaManipulation.Type.Unknown => false,
|
||||
_ => false,
|
||||
};
|
||||
|
|
@ -109,17 +108,13 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM
|
|||
public bool RevertMod( MetaManipulation manip )
|
||||
{
|
||||
var ret = _manipulations.Remove( manip );
|
||||
// Imc manipulations do not require character utility.
|
||||
if( manip.ManipulationType == MetaManipulation.Type.Imc )
|
||||
{
|
||||
return RevertMod( manip.Imc );
|
||||
}
|
||||
|
||||
if( !Penumbra.CharacterUtility.Ready )
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Imc manipulations do not require character utility,
|
||||
// but they do require the file space to be ready.
|
||||
return manip.ManipulationType switch
|
||||
{
|
||||
MetaManipulation.Type.Eqp => RevertMod( manip.Eqp ),
|
||||
|
|
@ -127,6 +122,7 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM
|
|||
MetaManipulation.Type.Eqdp => RevertMod( manip.Eqdp ),
|
||||
MetaManipulation.Type.Est => RevertMod( manip.Est ),
|
||||
MetaManipulation.Type.Rsp => RevertMod( manip.Rsp ),
|
||||
MetaManipulation.Type.Imc => RevertMod( manip.Imc ),
|
||||
MetaManipulation.Type.Unknown => false,
|
||||
_ => false,
|
||||
};
|
||||
|
|
@ -150,6 +146,7 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM
|
|||
MetaManipulation.Type.Eqdp => ApplyMod( manip.Eqdp ),
|
||||
MetaManipulation.Type.Est => ApplyMod( manip.Est ),
|
||||
MetaManipulation.Type.Rsp => ApplyMod( manip.Rsp ),
|
||||
MetaManipulation.Type.Imc => ApplyMod( manip.Imc ),
|
||||
MetaManipulation.Type.Unknown => false,
|
||||
_ => false,
|
||||
}
|
||||
|
|
@ -167,6 +164,42 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM
|
|||
Penumbra.Log.Debug( $"{_collection.AnonymizedName}: Loaded {loaded} delayed meta manipulations." );
|
||||
}
|
||||
|
||||
public void SetFile( CharacterUtility.Index index )
|
||||
{
|
||||
switch( index )
|
||||
{
|
||||
case CharacterUtility.Index.Eqp:
|
||||
SetFile( _eqpFile, index );
|
||||
break;
|
||||
case CharacterUtility.Index.Gmp:
|
||||
SetFile( _gmpFile, index );
|
||||
break;
|
||||
case CharacterUtility.Index.HumanCmp:
|
||||
SetFile( _cmpFile, index );
|
||||
break;
|
||||
case CharacterUtility.Index.FaceEst:
|
||||
SetFile( _estFaceFile, index );
|
||||
break;
|
||||
case CharacterUtility.Index.HairEst:
|
||||
SetFile( _estHairFile, index );
|
||||
break;
|
||||
case CharacterUtility.Index.HeadEst:
|
||||
SetFile( _estHeadFile, index );
|
||||
break;
|
||||
case CharacterUtility.Index.BodyEst:
|
||||
SetFile( _estBodyFile, index );
|
||||
break;
|
||||
default:
|
||||
var i = CharacterUtility.EqdpIndices.IndexOf( index );
|
||||
if( i != -1 )
|
||||
{
|
||||
SetFile( _eqdpFiles[ i ], index );
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
|
||||
private static unsafe void SetFile( MetaBaseFile? file, CharacterUtility.Index index )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ public partial class Mod
|
|||
try
|
||||
{
|
||||
var mod = new Mod( modDirectory );
|
||||
mod.Reload( out _ );
|
||||
mod.Reload( true, out _ );
|
||||
var editor = new Editor( mod, mod.Default );
|
||||
editor.DuplicatesFinished = false;
|
||||
editor.CheckDuplicates( editor.AvailableFiles.OrderByDescending( f => f.FileSize ).ToArray() );
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ public partial class Mod
|
|||
|
||||
if( deletions > 0 )
|
||||
{
|
||||
_mod.Reload( out _ );
|
||||
_mod.Reload( false, out _ );
|
||||
UpdateFiles();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public partial class Mod
|
|||
|
||||
dir.Refresh();
|
||||
mod.ModPath = dir;
|
||||
if( !mod.Reload( out var metaChange ) )
|
||||
if( !mod.Reload( false, out var metaChange ) )
|
||||
{
|
||||
Penumbra.Log.Error( $"Error reloading moved mod {mod.Name}." );
|
||||
return;
|
||||
|
|
@ -81,7 +81,7 @@ public partial class Mod
|
|||
var oldName = mod.Name;
|
||||
|
||||
ModPathChanged.Invoke( ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath );
|
||||
if( !mod.Reload( out var metaChange ) )
|
||||
if( !mod.Reload( true, out var metaChange ) )
|
||||
{
|
||||
Penumbra.Log.Warning( mod.Name.Length == 0
|
||||
? $"Reloading mod {oldName} has failed, new name is empty. Deleting instead."
|
||||
|
|
@ -135,7 +135,7 @@ public partial class Mod
|
|||
return;
|
||||
}
|
||||
|
||||
var mod = LoadMod( modFolder );
|
||||
var mod = LoadMod( modFolder, true );
|
||||
if( mod == null )
|
||||
{
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ public sealed partial class Mod
|
|||
{
|
||||
foreach( var modFolder in BasePath.EnumerateDirectories() )
|
||||
{
|
||||
var mod = LoadMod( modFolder );
|
||||
var mod = LoadMod( modFolder, false );
|
||||
if( mod == null )
|
||||
{
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
|
|
@ -26,7 +27,7 @@ public partial class Mod
|
|||
_default = new SubMod( this );
|
||||
}
|
||||
|
||||
private static Mod? LoadMod( DirectoryInfo modPath )
|
||||
private static Mod? LoadMod( DirectoryInfo modPath, bool incorporateMetaChanges )
|
||||
{
|
||||
modPath.Refresh();
|
||||
if( !modPath.Exists )
|
||||
|
|
@ -36,7 +37,7 @@ public partial class Mod
|
|||
}
|
||||
|
||||
var mod = new Mod( modPath );
|
||||
if( !mod.Reload( out _ ) )
|
||||
if( !mod.Reload( incorporateMetaChanges, out _ ) )
|
||||
{
|
||||
// Can not be base path not existing because that is checked before.
|
||||
Penumbra.Log.Error( $"Mod at {modPath} without name is not supported." );
|
||||
|
|
@ -46,7 +47,7 @@ public partial class Mod
|
|||
return mod;
|
||||
}
|
||||
|
||||
private bool Reload( out MetaChangeType metaChange )
|
||||
private bool Reload( bool incorporateMetaChanges, out MetaChangeType metaChange )
|
||||
{
|
||||
metaChange = MetaChangeType.Deletion;
|
||||
ModPath.Refresh();
|
||||
|
|
@ -63,8 +64,23 @@ public partial class Mod
|
|||
|
||||
LoadDefaultOption();
|
||||
LoadAllGroups();
|
||||
if( incorporateMetaChanges )
|
||||
{
|
||||
IncorporateAllMetaChanges( true );
|
||||
}
|
||||
|
||||
ComputeChangedItems();
|
||||
SetCounts();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convert all .meta and .rgsp files to their respective meta changes and add them to their options.
|
||||
// Deletes the source files if delete is true.
|
||||
private void IncorporateAllMetaChanges( bool delete )
|
||||
{
|
||||
foreach( var subMod in AllSubMods.OfType< SubMod >() )
|
||||
{
|
||||
subMod.IncorporateMetaChanges( ModPath, delete );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ public partial class Mod
|
|||
internal static void CreateDefaultFiles( DirectoryInfo directory )
|
||||
{
|
||||
var mod = new Mod( directory );
|
||||
mod.Reload( out _ );
|
||||
mod.Reload( false, out _ );
|
||||
foreach( var file in mod.FindUnusedFiles() )
|
||||
{
|
||||
if( Utf8GamePath.FromFile( new FileInfo( file.FullName ), directory, out var gamePath, true ) )
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ public class Penumbra : IDalamudPlugin
|
|||
ModManager = new Mod.Manager( Config.ModDirectory );
|
||||
ModManager.DiscoverMods();
|
||||
CollectionManager = new ModCollection.Manager( ModManager );
|
||||
CollectionManager.CreateNecessaryCaches();
|
||||
ModFileSystem = ModFileSystem.Load();
|
||||
ObjectReloader = new ObjectReloader();
|
||||
PathResolver = new PathResolver( ResourceLoader );
|
||||
|
|
@ -276,6 +277,7 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
ShutdownWebServer();
|
||||
DisposeInterface();
|
||||
Ipc?.Dispose();
|
||||
Api?.Dispose();
|
||||
|
|
@ -289,8 +291,6 @@ public class Penumbra : IDalamudPlugin
|
|||
ResourceLogger?.Dispose();
|
||||
ResourceLoader?.Dispose();
|
||||
CharacterUtility?.Dispose();
|
||||
|
||||
ShutdownWebServer();
|
||||
}
|
||||
|
||||
public static bool SetCollection( string type, string collectionName )
|
||||
|
|
@ -481,6 +481,7 @@ public class Penumbra : IDalamudPlugin
|
|||
sb.AppendFormat( "> **`#Collections: `** {0}\n", CollectionManager.Count - 1 );
|
||||
sb.AppendFormat( "> **`Active Collections: `** {0}\n", CollectionManager.Count( c => c.HasCache ) );
|
||||
sb.AppendFormat( "> **`Base Collection: `** {0}\n", CollectionManager.Default.AnonymizedName );
|
||||
sb.AppendFormat( "> **`Interface Collection: `** {0}\n", CollectionManager.Interface.AnonymizedName );
|
||||
sb.AppendFormat( "> **`Selected Collection: `** {0}\n", CollectionManager.Current.AnonymizedName );
|
||||
foreach( var type in CollectionTypeExtensions.Special )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,16 +18,29 @@ public partial class ConfigWindow
|
|||
|
||||
Add5_7_0( ret );
|
||||
Add5_7_1( ret );
|
||||
Add5_8_0( ret );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void Add5_8_0( Changelog log )
|
||||
=> log.NextVersion( "Version 0.5.8.0" )
|
||||
.RegisterEntry( "Added choices what Change Logs are to be displayed. It is recommended to just keep showing all." )
|
||||
.RegisterEntry( "Added choices what Change Logs are to be displayed. It is recommended to just keep showing all." )
|
||||
.RegisterEntry( "Added an Interface Collection assignment." )
|
||||
.RegisterEntry( "All your UI mods will have to be in the interface collection.", 1 )
|
||||
.RegisterEntry( "Files that are categorized as UI files by the game will only check for redirections in this collection.", 1 )
|
||||
.RegisterHighlight(
|
||||
"Migration should have set your currently assigned Base Collection to the Interface Collection, please verify that.", 1 )
|
||||
.RegisterEntry( "New API / IPC for the Interface Collection added.", 1 )
|
||||
.RegisterHighlight( "API / IPC consumers should verify whether they need to change resolving to the new collection.", 1 )
|
||||
.RegisterEntry(
|
||||
"Added buttons for redrawing self or all as well as a tooltip to describe redraw options and a tutorial step for it." )
|
||||
.RegisterEntry( "Collection Selectors now display None at the top if available." )
|
||||
.RegisterEntry( "Fixed an issue with Actor 201 using Your Character collections in cutscenes." )
|
||||
.RegisterEntry( "Fixed issues with and improved mod option editing." )
|
||||
.RegisterEntry( "Backend optimizations." );
|
||||
.RegisterEntry( "Backend optimizations." )
|
||||
.RegisterEntry( "Changed metadata change system again.", 1 )
|
||||
.RegisterEntry( "Improved logging efficiency.", 1 );
|
||||
|
||||
private static void Add5_7_1( Changelog log )
|
||||
=> log.NextVersion( "Version 0.5.7.1" )
|
||||
|
|
|
|||
|
|
@ -129,10 +129,19 @@ public partial class ConfigWindow
|
|||
DrawCollectionSelector( "##default", _window._inputTextWidth.X, CollectionType.Default, true, null );
|
||||
ImGui.SameLine();
|
||||
ImGuiUtil.LabeledHelpMarker( DefaultCollection,
|
||||
$"Mods in the {DefaultCollection} are loaded for anything that is not associated with a character in the game "
|
||||
$"Mods in the {DefaultCollection} are loaded for anything that is not associated with the user interface or a character in the game,"
|
||||
+ "as well as any character for whom no more specific conditions from below apply." );
|
||||
}
|
||||
|
||||
private void DrawInterfaceCollectionSelector()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
DrawCollectionSelector( "##interface", _window._inputTextWidth.X, CollectionType.Interface, true, null );
|
||||
ImGui.SameLine();
|
||||
ImGuiUtil.LabeledHelpMarker( InterfaceCollection,
|
||||
$"Mods in the {InterfaceCollection} are loaded for any file that the game categorizes as an UI file. This is mostly icons as well as the tiles that generate the user interface windows themselves." );
|
||||
}
|
||||
|
||||
// We do not check for valid character names.
|
||||
private void DrawNewSpecialCollection()
|
||||
{
|
||||
|
|
@ -272,6 +281,8 @@ public partial class ConfigWindow
|
|||
ImGui.Dummy( _window._defaultSpace );
|
||||
DrawDefaultCollectionSelector();
|
||||
OpenTutorial( BasicTutorialSteps.DefaultCollection );
|
||||
DrawInterfaceCollectionSelector();
|
||||
OpenTutorial( BasicTutorialSteps.InterfaceCollection );
|
||||
ImGui.Dummy( _window._defaultSpace );
|
||||
|
||||
DrawSpecialAssignments();
|
||||
|
|
|
|||
|
|
@ -100,7 +100,10 @@ public partial class ConfigWindow
|
|||
using var combo = ImRaii.Combo( label, current?.Name ?? string.Empty );
|
||||
if( combo )
|
||||
{
|
||||
foreach( var collection in Penumbra.CollectionManager.GetEnumeratorWithEmpty().Skip( withEmpty ? 0 : 1 ).OrderBy( c => c.Name ) )
|
||||
var enumerator = Penumbra.CollectionManager.OrderBy( c => c.Name ).AsEnumerable();
|
||||
if( withEmpty )
|
||||
enumerator = enumerator.Prepend( ModCollection.Empty );
|
||||
foreach( var collection in enumerator )
|
||||
{
|
||||
using var id = ImRaii.PushId( collection.Index );
|
||||
if( ImGui.Selectable( collection.Name, collection == current ) )
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ using Penumbra.UI.Classes;
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
|
|
@ -33,11 +35,22 @@ public partial class ConfigWindow
|
|||
using var group = ImRaii.Group();
|
||||
DrawHeaderLine();
|
||||
|
||||
using var child = ImRaii.Child( "##ModsTabMod", -Vector2.One, true, ImGuiWindowFlags.HorizontalScrollbar );
|
||||
if( child )
|
||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, Vector2.Zero );
|
||||
|
||||
using( var child = ImRaii.Child( "##ModsTabMod", new Vector2( -1, -ImGui.GetFrameHeight() ), true,
|
||||
ImGuiWindowFlags.HorizontalScrollbar ) )
|
||||
{
|
||||
_modPanel.Draw( _selector );
|
||||
style.Pop();
|
||||
if( child )
|
||||
{
|
||||
_modPanel.Draw( _selector );
|
||||
}
|
||||
|
||||
style.Push( ImGuiStyleVar.ItemSpacing, Vector2.Zero );
|
||||
}
|
||||
|
||||
style.Push( ImGuiStyleVar.FrameRounding, 0 );
|
||||
DrawRedrawLine();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
|
|
@ -48,18 +61,69 @@ public partial class ConfigWindow
|
|||
+ $"{_selector.SortMode.Name} Sort Mode\n"
|
||||
+ $"{_selector.SelectedLeaf?.Name ?? "NULL"} Selected Leaf\n"
|
||||
+ $"{_selector.Selected?.Name ?? "NULL"} Selected Mod\n"
|
||||
+ $"{string.Join( ", ", Penumbra.CollectionManager.Current.Inheritance.Select(c => c.AnonymizedName) )} Inheritances\n"
|
||||
+ $"{string.Join( ", ", Penumbra.CollectionManager.Current.Inheritance.Select( c => c.AnonymizedName ) )} Inheritances\n"
|
||||
+ $"{_selector.SelectedSettingCollection.AnonymizedName} Collection\n" );
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRedrawLine()
|
||||
{
|
||||
var frameHeight = new Vector2( 0, ImGui.GetFrameHeight() );
|
||||
var frameColor = ImGui.GetColorU32( ImGuiCol.FrameBg );
|
||||
using( var _ = ImRaii.Group() )
|
||||
{
|
||||
using( var font = ImRaii.PushFont( UiBuilder.IconFont ) )
|
||||
{
|
||||
ImGuiUtil.DrawTextButton( FontAwesomeIcon.InfoCircle.ToIconString(), frameHeight, frameColor );
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGuiUtil.DrawTextButton( "Redraw: ", frameHeight, frameColor );
|
||||
}
|
||||
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
OpenTutorial( BasicTutorialSteps.Redrawing );
|
||||
if( hovered )
|
||||
{
|
||||
ImGui.SetTooltip( $"The supported modifiers for '/penumbra redraw' are:\n{SupportedRedrawModifiers}" );
|
||||
}
|
||||
|
||||
void DrawButton( Vector2 size, string label, string lower )
|
||||
{
|
||||
if( ImGui.Button( label, size ) )
|
||||
{
|
||||
if( lower.Length > 0 )
|
||||
{
|
||||
_penumbra.ObjectReloader.RedrawObject( lower, RedrawType.Redraw );
|
||||
}
|
||||
else
|
||||
{
|
||||
_penumbra.ObjectReloader.RedrawAll( RedrawType.Redraw );
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( lower.Length > 0 ? $"Execute '/penumbra redraw {lower}'." : $"Execute '/penumbra redraw'." );
|
||||
}
|
||||
|
||||
using var disabled = ImRaii.Disabled( Dalamud.ClientState.LocalPlayer == null );
|
||||
ImGui.SameLine();
|
||||
var buttonWidth = frameHeight with { X = ImGui.GetContentRegionAvail().X / 4 };
|
||||
DrawButton( buttonWidth, "All", string.Empty );
|
||||
ImGui.SameLine();
|
||||
DrawButton( buttonWidth, "Self", "self" );
|
||||
ImGui.SameLine();
|
||||
DrawButton( buttonWidth, "Target", "target" );
|
||||
ImGui.SameLine();
|
||||
DrawButton( frameHeight with { X = ImGui.GetContentRegionAvail().X - 1 }, "Focus", "focus" );
|
||||
}
|
||||
|
||||
// Draw the header line that can quick switch between collections.
|
||||
private void DrawHeaderLine()
|
||||
{
|
||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameRounding, 0 ).Push( ImGuiStyleVar.ItemSpacing, Vector2.Zero );
|
||||
var buttonSize = new Vector2( ImGui.GetContentRegionAvail().X / 8f, 0 );
|
||||
|
||||
using( var group = ImRaii.Group() )
|
||||
using( var _ = ImRaii.Group() )
|
||||
{
|
||||
DrawDefaultCollectionButton( 3 * buttonSize );
|
||||
ImGui.SameLine();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using ImGuiNET;
|
|||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ public partial class ConfigWindow
|
|||
{
|
||||
public const string SelectedCollection = "Selected Collection";
|
||||
public const string DefaultCollection = "Base Collection";
|
||||
public const string InterfaceCollection = "Interface Collection";
|
||||
public const string ActiveCollections = "Active Collections";
|
||||
public const string AssignedCollections = "Assigned Collections";
|
||||
public const string GroupAssignment = "Group Assignment";
|
||||
|
|
@ -18,6 +19,13 @@ public partial class ConfigWindow
|
|||
public const string ConditionalIndividual = "Character";
|
||||
public const string IndividualAssignments = "Individual Assignments";
|
||||
|
||||
public const string SupportedRedrawModifiers = " - nothing, to redraw all characters\n"
|
||||
+ " - 'self' or '<me>': your own character\n"
|
||||
+ " - 'target' or '<t>': your target\n"
|
||||
+ " - 'focus' or '<f>: your focus target\n"
|
||||
+ " - 'mouseover' or '<mo>': the actor you are currently hovering over\n"
|
||||
+ " - any specific actor name to redraw all actors of that exactly matching name.";
|
||||
|
||||
private static void UpdateTutorialStep()
|
||||
{
|
||||
var tutorial = Tutorial.CurrentEnabledId( Penumbra.Config.TutorialStep );
|
||||
|
|
@ -49,6 +57,7 @@ public partial class ConfigWindow
|
|||
Inheritance,
|
||||
ActiveCollections,
|
||||
DefaultCollection,
|
||||
InterfaceCollection,
|
||||
SpecialCollections1,
|
||||
SpecialCollections2,
|
||||
Mods,
|
||||
|
|
@ -56,6 +65,7 @@ public partial class ConfigWindow
|
|||
AdvancedHelp,
|
||||
ModFilters,
|
||||
CollectionSelectors,
|
||||
Redrawing,
|
||||
EnablingMods,
|
||||
Priority,
|
||||
ModOptions,
|
||||
|
|
@ -100,11 +110,14 @@ public partial class ConfigWindow
|
|||
.Register( $"Initial Setup, Step 7: {DefaultCollection}",
|
||||
$"The {DefaultCollection} - which should currently be set to a collection named {ModCollection.DefaultCollection} - is the main one.\n\n"
|
||||
+ $"As long as no more specific conditions apply to an object in the game, the mods from the {DefaultCollection} will be used.\n\n"
|
||||
+ "This is also the collection you need to use for all UI mods, music mods or any mods not associated with a character in the game at all." )
|
||||
+ "This is also the collection you need to use for all mods that are not directly associated with any character in the game or the user interface, like music mods." )
|
||||
.Register( "Interface Collection",
|
||||
$"The {InterfaceCollection} - which should currently be set to None - is used exclusively for files categorized as 'UI' files by the game, which is mostly icons and the backgrounds for different UI windows etc.\n\n"
|
||||
+ $"If you have mods manipulating your interface, they should be enabled in the collection assigned to this slot. You can of course assign the same collection you assigned to the {DefaultCollection} to the {InterfaceCollection}, too, and enable all your UI mods in this one." )
|
||||
.Register( GroupAssignment + 's',
|
||||
"Collections assigned here are used for groups of characters for which specific conditions are met.\n\n"
|
||||
+ "The more specific the condition, the higher its priority (i.e. Your Character > Player Characters > Race).\n\n"
|
||||
+ $"{IndividualAssignments} always take precedence before groups.")
|
||||
+ $"{IndividualAssignments} always take precedence before groups." )
|
||||
.Register( IndividualAssignments,
|
||||
"Collections assigned here are used only for individual characters or NPCs that have the specified name.\n\n"
|
||||
+ "They may also apply to objects 'owned' by those characters, e.g. minions or mounts - see the general settings for options on this.\n\n" )
|
||||
|
|
@ -121,6 +134,10 @@ public partial class ConfigWindow
|
|||
+ $"The first button sets it to your {DefaultCollection} (if any).\n\n"
|
||||
+ "The second button sets it to the collection the settings of the currently selected mod are inherited from (if any).\n\n"
|
||||
+ "The third is a regular collection selector to let you choose among all your collections." )
|
||||
.Register( "Redrawing",
|
||||
"Whenever you change your mod configuration, changes do not immediately take effect. You will need to force the game to reload the relevant files (or if this is not possible, restart the game).\n\n"
|
||||
+ "For this, Penumbra has these buttons as well as the '/penumbra redraw' command, which redraws all actors at once. You can also use several modifiers described in the help marker instead.\n\n"
|
||||
+ "Feel free to use these slash commands (e.g. '/penumbra redraw self') as a macro, too." )
|
||||
.Register( "Initial Setup, Step 11: Enabling Mods",
|
||||
"Enable a mod here. Disabled mods will not apply to anything in the current collection.\n\n"
|
||||
+ "Mods can be enabled or disabled in a collection, or they can be unconfigured, in which case they will use Inheritance." )
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue