From ec91755065d0d086054955d7994778b3e18ed9d6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jun 2022 18:13:03 +0200 Subject: [PATCH] Add date sort methods. --- OtterGui | 2 +- Penumbra/Configuration.Migration.cs | 40 ++++++++++- Penumbra/Configuration.cs | 72 +++++++++++++++++-- .../Resolver/PathResolver.Animation.cs | 30 +++++++- .../Interop/Resolver/PathResolver.Data.cs | 6 ++ Penumbra/Interop/Resolver/PathResolver.cs | 7 ++ Penumbra/Mods/ModFileSystem.cs | 29 +++++++- Penumbra/UI/Classes/ModFileSystemSelector.cs | 10 +-- .../UI/ConfigWindow.SettingsTab.General.cs | 11 ++- 9 files changed, 185 insertions(+), 22 deletions(-) diff --git a/OtterGui b/OtterGui index 03934d3a..8053b24b 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 03934d3a19cb610898412045ad5ea7dad9766a59 +Subproject commit 8053b24bb6b77a13853455c08a89df4894b3f2ee diff --git a/Penumbra/Configuration.Migration.cs b/Penumbra/Configuration.Migration.cs index 3df24ae7..dc93d966 100644 --- a/Penumbra/Configuration.Migration.cs +++ b/Penumbra/Configuration.Migration.cs @@ -27,6 +27,7 @@ public partial class Configuration public Dictionary< string, string > ModSortOrder = new(); public bool InvertModListOrder; public bool SortFoldersFirst; + public SortModeV3 SortMode = SortModeV3.FoldersFirst; public static void Migrate( Configuration config ) { @@ -45,6 +46,31 @@ public partial class Configuration m.Version0To1(); m.Version1To2(); m.Version2To3(); + m.Version3To4(); + } + + // SortMode was changed from an enum to a type. + private void Version3To4() + { + if( _config.Version != 3 ) + { + return; + } + + SortMode = _data[ nameof( SortMode ) ]?.ToObject< SortModeV3 >() ?? SortMode; + _config.SortMode = SortMode switch + { + SortModeV3.FoldersFirst => ISortMode< Mod >.FoldersFirst, + SortModeV3.Lexicographical => ISortMode< Mod >.Lexicographical, + SortModeV3.InverseFoldersFirst => ISortMode< Mod >.InverseFoldersFirst, + SortModeV3.InverseLexicographical => ISortMode< Mod >.InverseLexicographical, + SortModeV3.FoldersLast => ISortMode< Mod >.FoldersLast, + SortModeV3.InverseFoldersLast => ISortMode< Mod >.InverseFoldersLast, + SortModeV3.InternalOrder => ISortMode< Mod >.InternalOrder, + SortModeV3.InternalOrderInverse => ISortMode< Mod >.InverseInternalOrder, + _ => ISortMode< Mod >.FoldersFirst, + }; + _config.Version = 4; } // SortFoldersFirst was changed from a bool to the enum SortMode. @@ -56,7 +82,7 @@ public partial class Configuration } SortFoldersFirst = _data[ nameof( SortFoldersFirst ) ]?.ToObject< bool >() ?? false; - _config.SortMode = SortFoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical; + SortMode = SortFoldersFirst ? SortModeV3.FoldersFirst : SortModeV3.Lexicographical; _config.Version = 3; } @@ -242,5 +268,17 @@ public partial class Configuration PluginLog.Error( $"Could not create backup copy of config at {bakName}:\n{e}" ); } } + + public enum SortModeV3 : byte + { + FoldersFirst = 0x00, + Lexicographical = 0x01, + InverseFoldersFirst = 0x02, + InverseLexicographical = 0x03, + FoldersLast = 0x04, + InverseFoldersLast = 0x05, + InternalOrder = 0x06, + InternalOrderInverse = 0x07, + } } } \ No newline at end of file diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 14879c2e..8ef57b86 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -1,12 +1,17 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Dalamud.Configuration; using Dalamud.Logging; +using Newtonsoft.Json; using OtterGui.Classes; using OtterGui.Filesystem; using Penumbra.Import; +using Penumbra.Mods; using Penumbra.UI.Classes; +using Penumbra.Util; +using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; namespace Penumbra; @@ -40,8 +45,10 @@ public partial class Configuration : IPluginConfiguration public bool EnableResourceLogging { get; set; } = false; public string ResourceLoggingFilter { get; set; } = string.Empty; + [JsonConverter( typeof( SortModeConverter ) )] + [JsonProperty( Order = int.MaxValue )] + public ISortMode< Mod > SortMode = ISortMode< Mod >.FoldersFirst; - public SortMode SortMode { get; set; } = SortMode.FoldersFirst; public bool ScaleModSelector { get; set; } = false; public float ModSelectorAbsoluteSize { get; set; } = Constants.DefaultAbsoluteSize; public int ModSelectorScaledSize { get; set; } = Constants.DefaultScaledSize; @@ -65,9 +72,25 @@ public partial class Configuration : IPluginConfiguration // Includes adding new colors and migrating from old versions. public static Configuration Load() { - var iConfiguration = Dalamud.PluginInterface.GetPluginConfig(); - var configuration = iConfiguration as Configuration ?? new Configuration(); - if( iConfiguration is { Version: Constants.CurrentVersion } ) + void HandleDeserializationError( object? sender, ErrorEventArgs errorArgs ) + { + PluginLog.Error( + $"Error parsing Configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}" ); + errorArgs.ErrorContext.Handled = true; + } + + Configuration? configuration = null; + if( File.Exists( Dalamud.PluginInterface.ConfigFile.FullName ) ) + { + var text = File.ReadAllText( Dalamud.PluginInterface.ConfigFile.FullName ); + configuration = JsonConvert.DeserializeObject< Configuration >( text, new JsonSerializerSettings + { + Error = HandleDeserializationError, + } ); + } + + configuration ??= new Configuration(); + if( configuration.Version == Constants.CurrentVersion ) { configuration.AddColors( false ); return configuration; @@ -84,7 +107,8 @@ public partial class Configuration : IPluginConfiguration { try { - Dalamud.PluginInterface.SavePluginConfig( this ); + var text = JsonConvert.SerializeObject( this, Formatting.Indented ); + File.WriteAllText( Dalamud.PluginInterface.ConfigFile.FullName, text ); } catch( Exception e ) { @@ -113,12 +137,48 @@ public partial class Configuration : IPluginConfiguration // Contains some default values or boundaries for config values. public static class Constants { - public const int CurrentVersion = 3; + public const int CurrentVersion = 4; public const float MaxAbsoluteSize = 600; public const int DefaultAbsoluteSize = 250; public const float MinAbsoluteSize = 50; public const int MaxScaledSize = 80; public const int DefaultScaledSize = 20; public const int MinScaledSize = 5; + + public static readonly ISortMode< Mod >[] ValidSortModes = + { + ISortMode< Mod >.FoldersFirst, + ISortMode< Mod >.Lexicographical, + new ModFileSystem.ImportDate(), + new ModFileSystem.InverseImportDate(), + ISortMode< Mod >.InverseFoldersFirst, + ISortMode< Mod >.InverseLexicographical, + ISortMode< Mod >.FoldersLast, + ISortMode< Mod >.InverseFoldersLast, + ISortMode< Mod >.InternalOrder, + ISortMode< Mod >.InverseInternalOrder, + }; + } + + private class SortModeConverter : JsonConverter< ISortMode< Mod > > + { + public override void WriteJson( JsonWriter writer, ISortMode< Mod >? value, JsonSerializer serializer ) + { + value ??= ISortMode< Mod >.FoldersFirst; + serializer.Serialize( writer, value.GetType().Name ); + } + + public override ISortMode< Mod > ReadJson( JsonReader reader, Type objectType, ISortMode< Mod >? existingValue, + bool hasExistingValue, + JsonSerializer serializer ) + { + var name = serializer.Deserialize< string >( reader ); + if( name == null || !Constants.ValidSortModes.FindFirst( s => s.GetType().Name == name, out var mode ) ) + { + return existingValue ?? ISortMode< Mod >.FoldersFirst; + } + + return mode; + } } } \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Animation.cs b/Penumbra/Interop/Resolver/PathResolver.Animation.cs index fcab01e3..4b2992a2 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Animation.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Animation.cs @@ -1,9 +1,12 @@ using System; +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Hooking; using Dalamud.Logging; using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Penumbra.Collections; +using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; namespace Penumbra.Interop.Resolver; @@ -21,7 +24,7 @@ public unsafe partial class PathResolver private ulong LoadTimelineResourcesDetour( IntPtr timeline ) { ulong ret; - var old = _animationLoadCollection; + var old = _animationLoadCollection; try { var getGameObjectIdx = ( ( delegate* unmanaged < IntPtr, int>** )timeline )[ 0 ][ 28 ]; @@ -92,6 +95,7 @@ public unsafe partial class PathResolver _animationLoadCollection = IdentifyCollection( ( GameObject* )( Dalamud.Objects[ actorIdx ]?.Address ?? IntPtr.Zero ) ); } } + LoadSomePapHook!.Original( a1, a2, a3, a4 ); _animationLoadCollection = last; } @@ -107,4 +111,28 @@ public unsafe partial class PathResolver SomeActionLoadHook!.Original( gameObject ); _animationLoadCollection = last; } + + [Signature( "E8 ?? ?? ?? ?? 44 84 BB", DetourName = nameof( SomeOtherAvfxDetour ) )] + public Hook< CharacterBaseDestructorDelegate >? SomeOtherAvfxHook; + + private void SomeOtherAvfxDetour( IntPtr unk ) + { + var last = _animationLoadCollection; + var gameObject = ( GameObject* )( unk - 0x8B0 ); + _animationLoadCollection = IdentifyCollection( gameObject ); + SomeOtherAvfxHook!.Original( unk ); + _animationLoadCollection = last; + } + + public delegate IntPtr SomeAtexDelegate( IntPtr a1, IntPtr a2, IntPtr a3, IntPtr a4, uint a5, IntPtr a6 ); + + [Signature( "E8 ?? ?? ?? ?? 84 C0 75 ?? 48 8B CE 41 B6" )] + public Hook< SomeAtexDelegate >? SomeAtexHook; + + public IntPtr SomeAtexDetour( IntPtr a1, IntPtr a2, IntPtr a3, IntPtr a4, uint a5, IntPtr a6 ) + { + var ret = SomeAtexHook!.Original( a1, a2, a3, a4, a5, a6 ); + PluginLog.Information( $"{a1:X} {a2:X} {a3:X} {a4:X} {a5:X} {a6:X} {ret}" ); + return ret; + } } \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Data.cs b/Penumbra/Interop/Resolver/PathResolver.Data.cs index 8614bbc4..e7f95a6d 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Data.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Data.cs @@ -97,6 +97,8 @@ public unsafe partial class PathResolver LoadSomeAvfxHook?.Enable(); LoadSomePapHook?.Enable(); SomeActionLoadHook?.Enable(); + SomeOtherAvfxHook?.Enable(); + SomeAtexHook?.Enable(); } private void DisableDataHooks() @@ -111,6 +113,8 @@ public unsafe partial class PathResolver LoadSomeAvfxHook?.Disable(); LoadSomePapHook?.Disable(); SomeActionLoadHook?.Disable(); + SomeOtherAvfxHook?.Disable(); + SomeAtexHook?.Disable(); } private void DisposeDataHooks() @@ -124,6 +128,8 @@ public unsafe partial class PathResolver LoadSomeAvfxHook?.Dispose(); LoadSomePapHook?.Dispose(); SomeActionLoadHook?.Dispose(); + SomeOtherAvfxHook?.Dispose(); + SomeAtexHook?.Dispose(); } // This map links DrawObjects directly to Actors (by ObjectTable index) and their collections. diff --git a/Penumbra/Interop/Resolver/PathResolver.cs b/Penumbra/Interop/Resolver/PathResolver.cs index 9c8111b7..7af31623 100644 --- a/Penumbra/Interop/Resolver/PathResolver.cs +++ b/Penumbra/Interop/Resolver/PathResolver.cs @@ -76,6 +76,13 @@ public partial class PathResolver : IDisposable private bool HandleAnimationFile( ResourceType type, Utf8GamePath _, [NotNullWhen(true)] out ModCollection? collection ) { + if( type == ResourceType.Atex ) + if (_animationLoadCollection == null) + PluginLog.Information( $"ATEX {_} Default" ); + else + { + PluginLog.Information( $"ATEX {_} {_animationLoadCollection?.Name}" ); + } if( _animationLoadCollection != null ) { switch( type ) diff --git a/Penumbra/Mods/ModFileSystem.cs b/Penumbra/Mods/ModFileSystem.cs index fef26f5e..0b186ea4 100644 --- a/Penumbra/Mods/ModFileSystem.cs +++ b/Penumbra/Mods/ModFileSystem.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -45,6 +46,30 @@ public sealed class ModFileSystem : FileSystem< Mod >, IDisposable Penumbra.ModManager.ModMetaChanged -= OnMetaChange; } + public struct ImportDate : ISortMode< Mod > + { + public string Name + => "Import Date (Older First)"; + + public string Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their import date."; + + public IEnumerable< IPath > GetChildren( Folder f ) + => f.GetSubFolders().Cast< IPath >().Concat( f.GetLeaves().OrderBy( l => l.Value.ImportDate ) ); + } + + public struct InverseImportDate : ISortMode< Mod > + { + public string Name + => "Import Date (Newer First)"; + + public string Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse import date."; + + public IEnumerable< IPath > GetChildren( Folder f ) + => f.GetSubFolders().Cast< IPath >().Concat( f.GetLeaves().OrderByDescending( l => l.Value.ImportDate ) ); + } + // Reload the whole filesystem from currently loaded mods and the current sort order file. // Used on construction and on mod rediscoveries. private void Reload() @@ -72,7 +97,7 @@ public sealed class ModFileSystem : FileSystem< Mod >, IDisposable if( type.HasFlag( MetaChangeType.Name ) && oldName != null ) { var old = oldName.FixName(); - if( Find( old, out var child ) && child is not Folder) + if( Find( old, out var child ) && child is not Folder ) { Rename( child, mod.Name.Text ); } @@ -97,7 +122,7 @@ public sealed class ModFileSystem : FileSystem< Mod >, IDisposable CreateLeaf( Root, name, mod ); break; case ModPathChangeType.Deleted: - var leaf = Root.GetAllDescendants( SortMode.Lexicographical ).OfType< Leaf >().FirstOrDefault( l => l.Value == mod ); + var leaf = Root.GetAllDescendants( ISortMode< Mod >.Lexicographical ).OfType< Leaf >().FirstOrDefault( l => l.Value == mod ); if( leaf != null ) { Delete( leaf ); diff --git a/Penumbra/UI/Classes/ModFileSystemSelector.cs b/Penumbra/UI/Classes/ModFileSystemSelector.cs index a062ff89..ab5de6b5 100644 --- a/Penumbra/UI/Classes/ModFileSystemSelector.cs +++ b/Penumbra/UI/Classes/ModFileSystemSelector.cs @@ -14,7 +14,7 @@ using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Numerics; -using OtterGui.Classes; +using Penumbra.Util; namespace Penumbra.UI.Classes; @@ -67,7 +67,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod => base.SelectedLeaf; // Customization points. - public override SortMode SortMode + public override ISortMode< Mod > SortMode => Penumbra.Config.SortMode; protected override uint ExpandedFolderColor @@ -315,7 +315,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod // Helpers. private static void SetDescendants( ModFileSystem.Folder folder, bool enabled, bool inherit = false ) { - var mods = folder.GetAllDescendants( SortMode.Lexicographical ).OfType< ModFileSystem.Leaf >().Select( l => l.Value ); + var mods = folder.GetAllDescendants( ISortMode< Mod >.Lexicographical ).OfType< ModFileSystem.Leaf >().Select( l => l.Value ); if( inherit ) { Penumbra.CollectionManager.Current.SetMultipleModInheritances( mods, enabled ); @@ -404,7 +404,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod { if( _lastSelectedDirectory.Length > 0 ) { - base.SelectedLeaf = ( ModFileSystem.Leaf? )FileSystem.Root.GetAllDescendants( SortMode.Lexicographical ) + base.SelectedLeaf = ( ModFileSystem.Leaf? )FileSystem.Root.GetAllDescendants( ISortMode< Mod >.Lexicographical ) .FirstOrDefault( l => l is ModFileSystem.Leaf m && m.Value.ModPath.FullName == _lastSelectedDirectory ); OnSelectionChange( null, base.SelectedLeaf?.Value, default ); _lastSelectedDirectory = string.Empty; @@ -422,7 +422,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod try { - var leaf = FileSystem.Root.GetChildren( SortMode.Lexicographical ) + var leaf = FileSystem.Root.GetChildren( ISortMode< Mod >.Lexicographical ) .FirstOrDefault( f => f is FileSystem< Mod >.Leaf l && l.Value == mod ); if( leaf == null ) { diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.General.cs b/Penumbra/UI/ConfigWindow.SettingsTab.General.cs index 22319bfc..56e1a7e9 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.General.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.General.cs @@ -4,9 +4,9 @@ using System.Numerics; using Dalamud.Interface; using ImGuiNET; using OtterGui; -using OtterGui.Filesystem; using OtterGui.Raii; using OtterGui.Widgets; +using Penumbra.Util; namespace Penumbra.UI; @@ -119,20 +119,19 @@ public partial class ConfigWindow { var sortMode = Penumbra.Config.SortMode; ImGui.SetNextItemWidth( _window._inputTextWidth.X ); - using var combo = ImRaii.Combo( "##sortMode", sortMode.Data().Name ); + using var combo = ImRaii.Combo( "##sortMode", sortMode.Name ); if( combo ) { - foreach( var val in Enum.GetValues< SortMode >() ) + foreach( var val in Configuration.Constants.ValidSortModes ) { - var (name, desc) = val.Data(); - if( ImGui.Selectable( name, val == sortMode ) && val != sortMode ) + if( ImGui.Selectable( val.Name, val.GetType() == sortMode.GetType() ) && val.GetType() != sortMode.GetType() ) { Penumbra.Config.SortMode = val; _window._selector.SetFilterDirty(); Penumbra.Config.Save(); } - ImGuiUtil.HoverTooltip( desc ); + ImGuiUtil.HoverTooltip( val.Description ); } }