From 85970700631da25c3445c31b1f42b03dfaa80254 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 17 Sep 2022 21:45:24 +0200 Subject: [PATCH 1/8] Add redraw buttons and tutorial. --- OtterGui | 2 +- Penumbra/UI/ConfigWindow.SettingsTab.cs | 31 +++++++++++++++++++++++++ Penumbra/UI/ConfigWindow.Tutorial.cs | 13 ++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/OtterGui b/OtterGui index b92dbe60..41581a5d 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit b92dbe60887503a77a89aeae80729236fb2bfa10 +Subproject commit 41581a5d035d46a89819aaefaa8d01c815788eee diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.cs b/Penumbra/UI/ConfigWindow.SettingsTab.cs index 2cbd2c24..2f6f39c1 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.cs @@ -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; @@ -52,6 +53,8 @@ public partial class ConfigWindow : _window.Flags & ~( ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize ); } ); + DrawRedrawButton(); + ImGui.NewLine(); DrawRootFolder(); DrawRediscoverButton(); @@ -335,6 +338,34 @@ public partial class ConfigWindow + "Not directly affiliated and potentially, but not usually out of date." ); } + private void DrawRedrawButton() + { + using( var group = ImRaii.Group() ) + { + using var disabled = ImRaii.Disabled( Dalamud.ClientState.LocalPlayer == null ); + + if( ImGui.Button( "Redraw Self" ) ) + { + _window._penumbra.ObjectReloader.RedrawObject( "self", RedrawType.Redraw ); + } + + ImGuiUtil.HoverTooltip( "Executes '/penumbra redraw self'." ); + + ImGui.SameLine(); + if( ImGui.Button( "Redraw All" ) ) + { + _window._penumbra.ObjectReloader.RedrawAll( RedrawType.Redraw ); + } + ImGuiUtil.HoverTooltip( "Executes '/penumbra redraw'." ); + + ImGui.SameLine(); + + ImGuiComponents.HelpMarker( $"The supported modifiers for '/penumbra redraw' are:\n{SupportedRedrawModifiers}" ); + } + + OpenTutorial( BasicTutorialSteps.Redrawing ); + } + private void DrawSupportButtons() { var width = ImGui.CalcTextSize( SupportInfoButtonText ).X + ImGui.GetStyle().FramePadding.X * 2; diff --git a/Penumbra/UI/ConfigWindow.Tutorial.cs b/Penumbra/UI/ConfigWindow.Tutorial.cs index ef75566d..f4973eb8 100644 --- a/Penumbra/UI/ConfigWindow.Tutorial.cs +++ b/Penumbra/UI/ConfigWindow.Tutorial.cs @@ -18,6 +18,12 @@ public partial class ConfigWindow public const string ConditionalIndividual = "Character"; public const string IndividualAssignments = "Individual Assignments"; + public const string SupportedRedrawModifiers = " - 'self' or '': your own character\n" + + " - 'target' or '': your target\n" + + " - 'focus' or ': your focus target\n" + + " - 'mouseover' or '': the actor you are currently hovering\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 ); @@ -43,6 +49,7 @@ public partial class ConfigWindow EnableMods, AdvancedSettings, GeneralSettings, + Redrawing, Collections, EditingCollections, CurrentCollection, @@ -82,6 +89,10 @@ public partial class ConfigWindow + "If you need to do any editing of your mods, you will have to turn it on later." ) .Register( "General Settings", "Look through all of these settings before starting, they might help you a lot!\n\n" + "If you do not know what some of these do yet, return to this later!" ) + .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 3: Collections", "Collections are lists of settings for your installed mods.\n\n" + "This is our next stop!\n\n" + "Go here after setting up your root folder to continue the tutorial!" ) @@ -104,7 +115,7 @@ public partial class ConfigWindow .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" ) From 273111775c03787a387c264d18e38b86744fc32d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 17 Sep 2022 22:53:14 +0200 Subject: [PATCH 2/8] Add interface collection. --- .../Collections/CollectionManager.Active.cs | 42 ++++++++++++++++--- Penumbra/Collections/CollectionType.cs | 4 +- Penumbra/Configuration.Migration.cs | 25 ++++++++++- Penumbra/Configuration.cs | 2 +- .../Loader/ResourceLoader.Replacement.cs | 6 +++ Penumbra/Penumbra.cs | 4 +- Penumbra/UI/ConfigWindow.CollectionsTab.cs | 13 +++++- Penumbra/UI/ConfigWindow.Tutorial.cs | 9 +++- 8 files changed, 91 insertions(+), 14 deletions(-) diff --git a/Penumbra/Collections/CollectionManager.Active.cs b/Penumbra/Collections/CollectionManager.Active.cs index 6885cdee..7a8bb658 100644 --- a/Penumbra/Collections/CollectionManager.Active.cs +++ b/Penumbra/Collections/CollectionManager.Active.cs @@ -26,6 +26,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 +56,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 +69,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 +102,9 @@ public partial class ModCollection Default.SetFiles(); } + break; + case CollectionType.Interface: + Interface = newCollection; break; case CollectionType.Current: Current = newCollection; @@ -118,6 +126,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 +201,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 +214,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; } @@ -268,13 +293,14 @@ public partial class ModCollection 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 +313,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 ) @@ -350,6 +378,7 @@ public partial class ModCollection private void CreateNecessaryCaches() { Default.CreateCache(); + Interface.CreateCache(); Current.CreateCache(); foreach( var collection in _specialCollections.OfType< ModCollection >().Concat( _characters.Values ) ) @@ -362,6 +391,7 @@ public partial class ModCollection { 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 ) ) diff --git a/Penumbra/Collections/CollectionType.cs b/Penumbra/Collections/CollectionType.cs index 2946498d..e71c0433 100644 --- a/Penumbra/Collections/CollectionType.cs +++ b/Penumbra/Collections/CollectionType.cs @@ -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, diff --git a/Penumbra/Configuration.Migration.cs b/Penumbra/Configuration.Migration.cs index cd11594b..2a2335c3 100644 --- a/Penumbra/Configuration.Migration.cs +++ b/Penumbra/Configuration.Migration.cs @@ -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) >() ); } diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 65eeb5db..bd4231b9 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -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; diff --git a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs index f0cd656b..3732dd9a 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -121,6 +121,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() ) diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 62e70d7a..fa5b3fe7 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -276,6 +276,7 @@ public class Penumbra : IDalamudPlugin public void Dispose() { + ShutdownWebServer(); DisposeInterface(); Ipc?.Dispose(); Api?.Dispose(); @@ -289,8 +290,6 @@ public class Penumbra : IDalamudPlugin ResourceLogger?.Dispose(); ResourceLoader?.Dispose(); CharacterUtility?.Dispose(); - - ShutdownWebServer(); } public static bool SetCollection( string type, string collectionName ) @@ -481,6 +480,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 ) { diff --git a/Penumbra/UI/ConfigWindow.CollectionsTab.cs b/Penumbra/UI/ConfigWindow.CollectionsTab.cs index 9c4a4547..ffd7adf3 100644 --- a/Penumbra/UI/ConfigWindow.CollectionsTab.cs +++ b/Penumbra/UI/ConfigWindow.CollectionsTab.cs @@ -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(); diff --git a/Penumbra/UI/ConfigWindow.Tutorial.cs b/Penumbra/UI/ConfigWindow.Tutorial.cs index f4973eb8..97941f7b 100644 --- a/Penumbra/UI/ConfigWindow.Tutorial.cs +++ b/Penumbra/UI/ConfigWindow.Tutorial.cs @@ -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"; @@ -21,7 +22,7 @@ public partial class ConfigWindow public const string SupportedRedrawModifiers = " - 'self' or '': your own character\n" + " - 'target' or '': your target\n" + " - 'focus' or ': your focus target\n" - + " - 'mouseover' or '': the actor you are currently hovering\n" + + " - 'mouseover' or '': 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() @@ -56,6 +57,7 @@ public partial class ConfigWindow Inheritance, ActiveCollections, DefaultCollection, + InterfaceCollection, SpecialCollections1, SpecialCollections2, Mods, @@ -111,7 +113,10 @@ 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" From 5538c5704dbf3bcfe5d094dfa5385aa7f26fc666 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 17 Sep 2022 23:27:38 +0200 Subject: [PATCH 3/8] Add IPC for Interface Collection. --- Penumbra/Api/IPenumbraApi.cs | 11 +++++++++-- Penumbra/Api/IpcTester.cs | 9 +++++++++ Penumbra/Api/PenumbraApi.cs | 18 ++++++++++++++--- Penumbra/Api/PenumbraIpc.cs | 28 ++++++++++++++++++++++++++- Penumbra/UI/ConfigWindow.Changelog.cs | 17 ++++++++++++++-- Penumbra/UI/ConfigWindow.Misc.cs | 5 ++++- 6 files changed, 79 insertions(+), 9 deletions(-) diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index cfc373df..257cfbde 100644 --- a/Penumbra/Api/IPenumbraApi.cs +++ b/Penumbra/Api/IPenumbraApi.cs @@ -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 ); diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index 133f5f7e..1fd29dec 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -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 ); diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index eb1a189b..fad36b1c 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -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(); diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index eb9035e1..ab9496c5 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -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( 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(); } diff --git a/Penumbra/UI/ConfigWindow.Changelog.cs b/Penumbra/UI/ConfigWindow.Changelog.cs index d4c6cb04..013e7357 100644 --- a/Penumbra/UI/ConfigWindow.Changelog.cs +++ b/Penumbra/UI/ConfigWindow.Changelog.cs @@ -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" ) diff --git a/Penumbra/UI/ConfigWindow.Misc.cs b/Penumbra/UI/ConfigWindow.Misc.cs index c4141b73..5b302ade 100644 --- a/Penumbra/UI/ConfigWindow.Misc.cs +++ b/Penumbra/UI/ConfigWindow.Misc.cs @@ -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 ) ) From 358064cd5fc63100ab5fe5ec1fd6bb0a2ead7bfa Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 18 Sep 2022 02:39:16 +0200 Subject: [PATCH 4/8] Move redraw buttons to mod panel. --- OtterGui | 2 +- Penumbra/UI/ConfigWindow.ModsTab.cs | 74 +++++++++++++++++++++++-- Penumbra/UI/ConfigWindow.SettingsTab.cs | 30 ---------- Penumbra/UI/ConfigWindow.Tutorial.cs | 15 ++--- 4 files changed, 78 insertions(+), 43 deletions(-) diff --git a/OtterGui b/OtterGui index 41581a5d..28c4d856 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 41581a5d035d46a89819aaefaa8d01c815788eee +Subproject commit 28c4d8564296484b3d0fc0d2ea275cded8b1daa2 diff --git a/Penumbra/UI/ConfigWindow.ModsTab.cs b/Penumbra/UI/ConfigWindow.ModsTab.cs index c3288214..2a0e193e 100644 --- a/Penumbra/UI/ConfigWindow.ModsTab.cs +++ b/Penumbra/UI/ConfigWindow.ModsTab.cs @@ -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(); diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.cs b/Penumbra/UI/ConfigWindow.SettingsTab.cs index 2f6f39c1..10f5e1b3 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.cs @@ -53,8 +53,6 @@ public partial class ConfigWindow : _window.Flags & ~( ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize ); } ); - DrawRedrawButton(); - ImGui.NewLine(); DrawRootFolder(); DrawRediscoverButton(); @@ -338,34 +336,6 @@ public partial class ConfigWindow + "Not directly affiliated and potentially, but not usually out of date." ); } - private void DrawRedrawButton() - { - using( var group = ImRaii.Group() ) - { - using var disabled = ImRaii.Disabled( Dalamud.ClientState.LocalPlayer == null ); - - if( ImGui.Button( "Redraw Self" ) ) - { - _window._penumbra.ObjectReloader.RedrawObject( "self", RedrawType.Redraw ); - } - - ImGuiUtil.HoverTooltip( "Executes '/penumbra redraw self'." ); - - ImGui.SameLine(); - if( ImGui.Button( "Redraw All" ) ) - { - _window._penumbra.ObjectReloader.RedrawAll( RedrawType.Redraw ); - } - ImGuiUtil.HoverTooltip( "Executes '/penumbra redraw'." ); - - ImGui.SameLine(); - - ImGuiComponents.HelpMarker( $"The supported modifiers for '/penumbra redraw' are:\n{SupportedRedrawModifiers}" ); - } - - OpenTutorial( BasicTutorialSteps.Redrawing ); - } - private void DrawSupportButtons() { var width = ImGui.CalcTextSize( SupportInfoButtonText ).X + ImGui.GetStyle().FramePadding.X * 2; diff --git a/Penumbra/UI/ConfigWindow.Tutorial.cs b/Penumbra/UI/ConfigWindow.Tutorial.cs index 97941f7b..04fa3513 100644 --- a/Penumbra/UI/ConfigWindow.Tutorial.cs +++ b/Penumbra/UI/ConfigWindow.Tutorial.cs @@ -19,7 +19,8 @@ public partial class ConfigWindow public const string ConditionalIndividual = "Character"; public const string IndividualAssignments = "Individual Assignments"; - public const string SupportedRedrawModifiers = " - 'self' or '': your own character\n" + public const string SupportedRedrawModifiers = " - nothing, to redraw all characters\n" + + " - 'self' or '': your own character\n" + " - 'target' or '': your target\n" + " - 'focus' or ': your focus target\n" + " - 'mouseover' or '': the actor you are currently hovering over\n" @@ -50,7 +51,6 @@ public partial class ConfigWindow EnableMods, AdvancedSettings, GeneralSettings, - Redrawing, Collections, EditingCollections, CurrentCollection, @@ -65,6 +65,7 @@ public partial class ConfigWindow AdvancedHelp, ModFilters, CollectionSelectors, + Redrawing, EnablingMods, Priority, ModOptions, @@ -91,10 +92,6 @@ public partial class ConfigWindow + "If you need to do any editing of your mods, you will have to turn it on later." ) .Register( "General Settings", "Look through all of these settings before starting, they might help you a lot!\n\n" + "If you do not know what some of these do yet, return to this later!" ) - .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 3: Collections", "Collections are lists of settings for your installed mods.\n\n" + "This is our next stop!\n\n" + "Go here after setting up your root folder to continue the tutorial!" ) @@ -116,7 +113,7 @@ public partial class ConfigWindow + "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.") + + $"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" @@ -137,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." ) From 257c0d390b695480244f29310b11f0623156fdd8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 18 Sep 2022 13:40:56 +0200 Subject: [PATCH 5/8] Let Eqdp change all files in the racial tree instead of just the own race code. --- OtterGui | 2 +- .../Interop/Resolver/PathResolver.Meta.cs | 80 +++++++++++++++++-- .../Resolver/PathResolver.ResolverHooks.cs | 12 +-- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/OtterGui b/OtterGui index 28c4d856..98064e79 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 28c4d8564296484b3d0fc0d2ea275cded8b1daa2 +Subproject commit 98064e790042c90c82a58fbfa79201bd69800758 diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index ba52182a..d8f1db0a 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -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 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 ) @@ -203,5 +203,69 @@ public unsafe partial class PathResolver using var eqp = resolveData.Valid ? resolveData.ModCollection.TemporarilySetEqpFile() : 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().AsEnumerable(); + var accessoryEnumerable = + accessory + ? races.Select( r => collection.TemporarilySetEqdpFile( r, true ) ) + : Array.Empty().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, + }; + } + } } \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs index b5335dc8..7841027a 100644 --- a/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs +++ b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs @@ -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(); From 57e66f9b66486fda20619393bfd8ece4a33eb532 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 19 Sep 2022 13:19:08 +0200 Subject: [PATCH 6/8] Fix some problems with super early files and meta files. --- .../Collections/CollectionManager.Active.cs | 28 +++---- .../Collections/ModCollection.Cache.Access.cs | 76 ++++-------------- Penumbra/Interop/CharacterUtility.List.cs | 10 ++- Penumbra/Interop/CharacterUtility.cs | 4 +- .../Resolver/PathResolver.DrawObjectState.cs | 2 +- .../Interop/Resolver/PathResolver.Meta.cs | 77 +++++++++---------- Penumbra/Meta/Manager/MetaManager.cs | 55 ++++++++++--- Penumbra/Penumbra.cs | 1 + 8 files changed, 120 insertions(+), 133 deletions(-) diff --git a/Penumbra/Collections/CollectionManager.Active.cs b/Penumbra/Collections/CollectionManager.Active.cs index 7a8bb658..12b42e91 100644 --- a/Penumbra/Collections/CollectionManager.Active.cs +++ b/Penumbra/Collections/CollectionManager.Active.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; namespace Penumbra.Collections; @@ -215,7 +216,7 @@ public partial class ModCollection } // Load the interface collection. - var interfaceName = jObject[ nameof( Interface ) ]?.ToObject< string >() ?? (configChanged ? Empty.Name : Default.Name); + var interfaceName = jObject[ nameof( Interface ) ]?.ToObject< string >() ?? ( configChanged ? Empty.Name : Default.Name ); var interfaceIdx = GetIndexForCollectionName( interfaceName ); if( interfaceIdx < 0 ) { @@ -285,8 +286,6 @@ public partial class ModCollection { SaveActiveCollections(); } - - CreateNecessaryCaches(); } @@ -363,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 ) { @@ -373,18 +371,20 @@ 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(); - Interface.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 ) diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 351ae2fc..7f379dc5 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -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 ); - -} +} \ No newline at end of file diff --git a/Penumbra/Interop/CharacterUtility.List.cs b/Penumbra/Interop/CharacterUtility.List.cs index 27146af6..e6f412ae 100644 --- a/Penumbra/Interop/CharacterUtility.List.cs +++ b/Penumbra/Interop/CharacterUtility.List.cs @@ -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 { diff --git a/Penumbra/Interop/CharacterUtility.cs b/Penumbra/Interop/CharacterUtility.cs index a5b155c1..52e842c9 100644 --- a/Penumbra/Interop/CharacterUtility.cs +++ b/Penumbra/Interop/CharacterUtility.cs @@ -78,9 +78,9 @@ public unsafe partial class CharacterUtility : IDisposable if( !anyMissing ) { - Ready = true; - LoadingFinished.Invoke(); + Ready = true; Dalamud.Framework.Update -= LoadDefaultResources; + LoadingFinished.Invoke(); } } diff --git a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs index d6e6ec0e..9c71a121 100644 --- a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs @@ -56,7 +56,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; diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index d8f1db0a..23d7e21c 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -81,8 +81,8 @@ public unsafe partial class PathResolver var collection = GetResolveData( drawObject ); if( collection.Valid ) { - using var eqp = collection.ModCollection.TemporarilySetEqpFile(); - using var eqdp = ResolveEqdpData( collection.ModCollection, GetDrawObjectGenderRace( drawObject ), true, true ); + using var eqp = collection.ModCollection.TemporarilySetEqpFile(); + using var eqdp = ResolveEqdpData( collection.ModCollection, GetDrawObjectGenderRace( drawObject ), true, true ); _onModelLoadCompleteHook.Original.Invoke( drawObject ); } else @@ -211,61 +211,60 @@ public unsafe partial class PathResolver var equipmentEnumerable = equipment ? races.Select( r => collection.TemporarilySetEqdpFile( r, false ) ) - : Array.Empty().AsEnumerable(); + : Array.Empty< IDisposable? >().AsEnumerable(); var accessoryEnumerable = accessory ? races.Select( r => collection.TemporarilySetEqdpFile( r, true ) ) - : Array.Empty().AsEnumerable(); + : Array.Empty< IDisposable? >().AsEnumerable(); return new DisposableContainer( equipmentEnumerable.Concat( accessoryEnumerable ) ); } return race switch { - MidlanderMale => Convert( MidlanderMale ), + 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 ), + 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 ), + 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 ), + 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 ), + 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 ), + 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 ), + 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 ), + 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 ), + UnknownMaleNpc => Convert( MidlanderMale, UnknownMaleNpc ), UnknownFemaleNpc => Convert( MidlanderMale, MidlanderFemale, UnknownFemaleNpc ), - _ => DisposableContainer.Empty, + _ => DisposableContainer.Empty, }; } - } } \ No newline at end of file diff --git a/Penumbra/Meta/Manager/MetaManager.cs b/Penumbra/Meta/Manager/MetaManager.cs index 701b77c0..d440e106 100644 --- a/Penumbra/Meta/Manager/MetaManager.cs +++ b/Penumbra/Meta/Manager/MetaManager.cs @@ -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 ) { diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index fa5b3fe7..b8ae544d 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -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 ); From ea023ebb5cd00eb06d74ab8e482437c65b56662d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 19 Sep 2022 18:52:49 +0200 Subject: [PATCH 7/8] Add handling for the 1.0 Decal texture. --- Penumbra.GameData/ByteString/FullPath.cs | 7 ++ .../Interop/CharacterUtility.DecalReverter.cs | 65 +++++++++++++++++++ Penumbra/Interop/CharacterUtility.cs | 25 +++++-- .../Loader/ResourceLoader.Replacement.cs | 10 ++- .../Interop/Loader/ResourceLoader.TexMdl.cs | 6 +- .../Resolver/PathResolver.DrawObjectState.cs | 23 ++++--- .../Interop/Resolver/PathResolver.Meta.cs | 3 + Penumbra/Interop/Structs/CharacterUtility.cs | 9 +++ Penumbra/Interop/Structs/ResourceHandle.cs | 16 +++++ 9 files changed, 147 insertions(+), 17 deletions(-) create mode 100644 Penumbra/Interop/CharacterUtility.DecalReverter.cs diff --git a/Penumbra.GameData/ByteString/FullPath.cs b/Penumbra.GameData/ByteString/FullPath.cs index 2284bf98..6d3cc0bd 100644 --- a/Penumbra.GameData/ByteString/FullPath.cs +++ b/Penumbra.GameData/ByteString/FullPath.cs @@ -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 ); diff --git a/Penumbra/Interop/CharacterUtility.DecalReverter.cs b/Penumbra/Interop/CharacterUtility.DecalReverter.cs new file mode 100644 index 00000000..a439ac48 --- /dev/null +++ b/Penumbra/Interop/CharacterUtility.DecalReverter.cs @@ -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; + } + } + } +} \ No newline at end of file diff --git a/Penumbra/Interop/CharacterUtility.cs b/Penumbra/Interop/CharacterUtility.cs index 52e842c9..44b9204e 100644 --- a/Penumbra/Interop/CharacterUtility.cs +++ b/Penumbra/Interop/CharacterUtility.cs @@ -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,6 +78,18 @@ 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; @@ -86,15 +100,15 @@ public unsafe partial class CharacterUtility : IDisposable 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() diff --git a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs index 3732dd9a..a0ca377f 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -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 = diff --git a/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs b/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs index 2393a87d..67454300 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs @@ -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 ) diff --git a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs index 9c71a121..d30248f9 100644 --- a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs @@ -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; @@ -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 ); diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index 23d7e21c..ba0c453b 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -201,6 +201,9 @@ 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 ); } diff --git a/Penumbra/Interop/Structs/CharacterUtility.cs b/Penumbra/Interop/Structs/CharacterUtility.cs index 40e346c1..e491de7b 100644 --- a/Penumbra/Interop/Structs/CharacterUtility.cs +++ b/Penumbra/Interop/Structs/CharacterUtility.cs @@ -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. } \ No newline at end of file diff --git a/Penumbra/Interop/Structs/ResourceHandle.cs b/Penumbra/Interop/Structs/ResourceHandle.cs index 6b3a8a72..c8b3522e 100644 --- a/Penumbra/Interop/Structs/ResourceHandle.cs +++ b/Penumbra/Interop/Structs/ResourceHandle.cs @@ -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 { From 1c97b5217988bf21cb65c2792fba6f46df4143dd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 20 Sep 2022 15:42:09 +0200 Subject: [PATCH 8/8] Automatically incorporate all .meta and .rgsp files when adding mods via API. --- .../Interop/Resolver/PathResolver.Meta.cs | 2 +- Penumbra/Mods/Editor/Mod.Editor.Duplicates.cs | 2 +- Penumbra/Mods/Editor/Mod.Editor.Files.cs | 2 +- Penumbra/Mods/Manager/Mod.Manager.BasePath.cs | 6 ++--- Penumbra/Mods/Manager/Mod.Manager.Root.cs | 2 +- Penumbra/Mods/Mod.BasePath.cs | 22 ++++++++++++++++--- Penumbra/Mods/Mod.Creation.cs | 2 +- 7 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index ba0c453b..1121cb29 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -202,7 +202,7 @@ public unsafe partial class PathResolver 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) ) + ? new CharacterUtility.DecalReverter( resolveData.ModCollection, DrawObjectState.UsesDecal( 0, data ) ) : null; return _changeCustomize.Original( human, data, skipEquipment ); } diff --git a/Penumbra/Mods/Editor/Mod.Editor.Duplicates.cs b/Penumbra/Mods/Editor/Mod.Editor.Duplicates.cs index 0f9ef463..b0a136af 100644 --- a/Penumbra/Mods/Editor/Mod.Editor.Duplicates.cs +++ b/Penumbra/Mods/Editor/Mod.Editor.Duplicates.cs @@ -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() ); diff --git a/Penumbra/Mods/Editor/Mod.Editor.Files.cs b/Penumbra/Mods/Editor/Mod.Editor.Files.cs index 480261ee..9f1d8fa5 100644 --- a/Penumbra/Mods/Editor/Mod.Editor.Files.cs +++ b/Penumbra/Mods/Editor/Mod.Editor.Files.cs @@ -267,7 +267,7 @@ public partial class Mod if( deletions > 0 ) { - _mod.Reload( out _ ); + _mod.Reload( false, out _ ); UpdateFiles(); } } diff --git a/Penumbra/Mods/Manager/Mod.Manager.BasePath.cs b/Penumbra/Mods/Manager/Mod.Manager.BasePath.cs index 0d908866..a169cecc 100644 --- a/Penumbra/Mods/Manager/Mod.Manager.BasePath.cs +++ b/Penumbra/Mods/Manager/Mod.Manager.BasePath.cs @@ -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; diff --git a/Penumbra/Mods/Manager/Mod.Manager.Root.cs b/Penumbra/Mods/Manager/Mod.Manager.Root.cs index 2bf658c2..7ad2140f 100644 --- a/Penumbra/Mods/Manager/Mod.Manager.Root.cs +++ b/Penumbra/Mods/Manager/Mod.Manager.Root.cs @@ -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; diff --git a/Penumbra/Mods/Mod.BasePath.cs b/Penumbra/Mods/Mod.BasePath.cs index 2456dec2..22760adf 100644 --- a/Penumbra/Mods/Mod.BasePath.cs +++ b/Penumbra/Mods/Mod.BasePath.cs @@ -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 ); + } + } } \ No newline at end of file diff --git a/Penumbra/Mods/Mod.Creation.cs b/Penumbra/Mods/Mod.Creation.cs index d9c64cc1..35095002 100644 --- a/Penumbra/Mods/Mod.Creation.cs +++ b/Penumbra/Mods/Mod.Creation.cs @@ -122,7 +122,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 ) )