diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 19af38d8..895d6af9 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -378,7 +378,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi if( group.Type == SelectType.Single ) { var name = optionNames[ ^1 ]; - var optionIdx = group.IndexOf( o => o.Name == name ); + var optionIdx = group.IndexOf( o => o.Name == optionNames[^1] ); if( optionIdx < 0 ) { return PenumbraApiEc.OptionMissing; diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 5cf71fc2..24324553 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -65,8 +65,8 @@ public partial class ModCollection internal IReadOnlyDictionary< Utf8GamePath, ModPath > ResolvedFiles => _cache?.ResolvedFiles ?? new Dictionary< Utf8GamePath, ModPath >(); - internal IReadOnlyDictionary< string, (SingleArray< Mod >, object?) > ChangedItems - => _cache?.ChangedItems ?? new Dictionary< string, (SingleArray< Mod >, object?) >(); + internal IReadOnlyDictionary< string, (SingleArray< IMod >, object?) > ChangedItems + => _cache?.ChangedItems ?? new Dictionary< string, (SingleArray< IMod >, object?) >(); internal IEnumerable< SingleArray< ModConflicts > > AllConflicts => _cache?.AllConflicts ?? Array.Empty< SingleArray< ModConflicts > >(); diff --git a/Penumbra/Collections/ModCollection.Cache.cs b/Penumbra/Collections/ModCollection.Cache.cs index d4e75a2c..7ffdfb61 100644 --- a/Penumbra/Collections/ModCollection.Cache.cs +++ b/Penumbra/Collections/ModCollection.Cache.cs @@ -11,8 +11,8 @@ using Penumbra.Util; namespace Penumbra.Collections; -public record struct ModPath( Mod Mod, FullPath Path ); -public record ModConflicts( Mod Mod2, List< object > Conflicts, bool HasPriority, bool Solved ); +public record struct ModPath( IMod Mod, FullPath Path ); +public record ModConflicts( IMod Mod2, List< object > Conflicts, bool HasPriority, bool Solved ); public partial class ModCollection { @@ -21,15 +21,15 @@ public partial class ModCollection private class Cache : IDisposable { private readonly ModCollection _collection; - private readonly SortedList< string, (SingleArray< Mod >, object?) > _changedItems = new(); + private readonly SortedList< string, (SingleArray< IMod >, object?) > _changedItems = new(); public readonly Dictionary< Utf8GamePath, ModPath > ResolvedFiles = new(); public readonly MetaManager MetaManipulations; - private readonly Dictionary< Mod, SingleArray< ModConflicts > > _conflicts = new(); + private readonly Dictionary< IMod, SingleArray< ModConflicts > > _conflicts = new(); public IEnumerable< SingleArray< ModConflicts > > AllConflicts => _conflicts.Values; - public SingleArray< ModConflicts > Conflicts( Mod mod ) + public SingleArray< ModConflicts > Conflicts( IMod mod ) => _conflicts.TryGetValue( mod, out var c ) ? c : new SingleArray< ModConflicts >(); // Count the number of changes of the effective file list. @@ -38,7 +38,7 @@ public partial class ModCollection private int _changedItemsSaveCounter = -1; // Obtain currently changed items. Computes them if they haven't been computed before. - public IReadOnlyDictionary< string, (SingleArray< Mod >, object?) > ChangedItems + public IReadOnlyDictionary< string, (SingleArray< IMod >, object?) > ChangedItems { get { @@ -178,13 +178,13 @@ public partial class ModCollection } } - public void ReloadMod( Mod mod, bool addMetaChanges ) + public void ReloadMod( IMod mod, bool addMetaChanges ) { RemoveMod( mod, addMetaChanges ); AddMod( mod, addMetaChanges ); } - public void RemoveMod( Mod mod, bool addMetaChanges ) + public void RemoveMod( IMod mod, bool addMetaChanges ) { var conflicts = Conflicts( mod ); @@ -243,37 +243,40 @@ public partial class ModCollection // Add all files and possibly manipulations of a given mod according to its settings in this collection. - public void AddMod( Mod mod, bool addMetaChanges ) + public void AddMod( IMod mod, bool addMetaChanges ) { - var settings = _collection[ mod.Index ].Settings; - if( settings is not { Enabled: true } ) + if( mod.Index >= 0 ) { - return; - } - - foreach( var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending( g => g.Item1.Priority ) ) - { - if( group.Count == 0 ) + var settings = _collection[ mod.Index ].Settings; + if( settings is not { Enabled: true } ) { - continue; + return; } - var config = settings.Settings[ groupIndex ]; - switch( group.Type ) + foreach( var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending( g => g.Item1.Priority ) ) { - case SelectType.Single: - AddSubMod( group[ ( int )config ], mod ); - break; - case SelectType.Multi: + if( group.Count == 0 ) { - foreach( var (option, _) in group.WithIndex() - .OrderByDescending( p => group.OptionPriority( p.Item2 ) ) - .Where( p => ( ( 1 << p.Item2 ) & config ) != 0 ) ) - { - AddSubMod( option, mod ); - } + continue; + } - break; + var config = settings.Settings[ groupIndex ]; + switch( group.Type ) + { + case SelectType.Single: + AddSubMod( group[ ( int )config ], mod ); + break; + case SelectType.Multi: + { + foreach( var (option, _) in group.WithIndex() + .OrderByDescending( p => group.OptionPriority( p.Item2 ) ) + .Where( p => ( ( 1 << p.Item2 ) & config ) != 0 ) ) + { + AddSubMod( option, mod ); + } + + break; + } } } } @@ -297,7 +300,7 @@ public partial class ModCollection } // Add all files and possibly manipulations of a specific submod - private void AddSubMod( ISubMod subMod, Mod parentMod ) + private void AddSubMod( ISubMod subMod, IMod parentMod ) { foreach( var (path, file) in subMod.Files.Concat( subMod.FileSwaps ) ) { @@ -320,7 +323,7 @@ public partial class ModCollection // For different mods, higher mod priority takes precedence before option group priority, // which takes precedence before option priority, which takes precedence before ordering. // Inside the same mod, conflicts are not recorded. - private void AddFile( Utf8GamePath path, FullPath file, Mod mod ) + private void AddFile( Utf8GamePath path, FullPath file, IMod mod ) { if( ResolvedFiles.TryAdd( path, new ModPath( mod, file ) ) ) { @@ -343,7 +346,7 @@ public partial class ModCollection // Remove all empty conflict sets for a given mod with the given conflicts. // If transitive is true, also removes the corresponding version of the other mod. - private void RemoveEmptyConflicts( Mod mod, SingleArray< ModConflicts > oldConflicts, bool transitive ) + private void RemoveEmptyConflicts( IMod mod, SingleArray< ModConflicts > oldConflicts, bool transitive ) { var changedConflicts = oldConflicts.Remove( c => { @@ -372,10 +375,10 @@ public partial class ModCollection // Add a new conflict between the added mod and the existing mod. // Update all other existing conflicts between the existing mod and other mods if necessary. // Returns if the added mod takes priority before the existing mod. - private bool AddConflict( object data, Mod addedMod, Mod existingMod ) + private bool AddConflict( object data, IMod addedMod, IMod existingMod ) { - var addedPriority = addedMod.Index >= 0 ? _collection[ addedMod.Index ].Settings!.Priority : int.MaxValue; - var existingPriority = existingMod.Index >= 0 ? _collection[ existingMod.Index ].Settings!.Priority : int.MaxValue; + var addedPriority = addedMod.Index >= 0 ? _collection[ addedMod.Index ].Settings!.Priority : addedMod.Priority; + var existingPriority = existingMod.Index >= 0 ? _collection[ existingMod.Index ].Settings!.Priority : existingMod.Priority; if( existingPriority < addedPriority ) { @@ -417,7 +420,7 @@ public partial class ModCollection // For different mods, higher mod priority takes precedence before option group priority, // which takes precedence before option priority, which takes precedence before ordering. // Inside the same mod, conflicts are not recorded. - private void AddManipulation( MetaManipulation manip, Mod mod ) + private void AddManipulation( MetaManipulation manip, IMod mod ) { if( !MetaManipulations.TryGetValue( manip, out var existingMod ) ) { @@ -463,7 +466,7 @@ public partial class ModCollection { if( !_changedItems.TryGetValue( name, out var data ) ) { - _changedItems.Add( name, ( new SingleArray< Mod >( modPath.Mod ), obj ) ); + _changedItems.Add( name, ( new SingleArray< IMod >( modPath.Mod ), obj ) ); } else if( !data.Item1.Contains( modPath.Mod ) ) { diff --git a/Penumbra/Meta/Manager/MetaManager.Cmp.cs b/Penumbra/Meta/Manager/MetaManager.Cmp.cs index eae00788..d9371c41 100644 --- a/Penumbra/Meta/Manager/MetaManager.Cmp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Cmp.cs @@ -14,7 +14,7 @@ public partial class MetaManager public struct MetaManagerCmp : IDisposable { public CmpFile? File = null; - public readonly Dictionary< RspManipulation, Mod > Manipulations = new(); + public readonly Dictionary< RspManipulation, IMod > Manipulations = new(); public MetaManagerCmp() { } @@ -39,7 +39,7 @@ public partial class MetaManager Manipulations.Clear(); } - public bool ApplyMod( RspManipulation m, Mod mod ) + public bool ApplyMod( RspManipulation m, IMod mod ) { #if USE_CMP Manipulations[ m ] = mod; diff --git a/Penumbra/Meta/Manager/MetaManager.Eqdp.cs b/Penumbra/Meta/Manager/MetaManager.Eqdp.cs index bb5ec9d0..92a60470 100644 --- a/Penumbra/Meta/Manager/MetaManager.Eqdp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Eqdp.cs @@ -17,7 +17,7 @@ public partial class MetaManager { public readonly ExpandedEqdpFile?[] Files = new ExpandedEqdpFile?[CharacterUtility.NumEqdpFiles - 2]; // TODO: female Hrothgar - public readonly Dictionary< EqdpManipulation, Mod > Manipulations = new(); + public readonly Dictionary< EqdpManipulation, IMod > Manipulations = new(); public MetaManagerEqdp() { } @@ -51,7 +51,7 @@ public partial class MetaManager Manipulations.Clear(); } - public bool ApplyMod( EqdpManipulation m, Mod mod ) + public bool ApplyMod( EqdpManipulation m, IMod mod ) { #if USE_EQDP Manipulations[ m ] = mod; diff --git a/Penumbra/Meta/Manager/MetaManager.Eqp.cs b/Penumbra/Meta/Manager/MetaManager.Eqp.cs index 9c980d67..831e26d9 100644 --- a/Penumbra/Meta/Manager/MetaManager.Eqp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Eqp.cs @@ -14,7 +14,7 @@ public partial class MetaManager public struct MetaManagerEqp : IDisposable { public ExpandedEqpFile? File = null; - public readonly Dictionary< EqpManipulation, Mod > Manipulations = new(); + public readonly Dictionary< EqpManipulation, IMod > Manipulations = new(); public MetaManagerEqp() { } @@ -39,7 +39,7 @@ public partial class MetaManager Manipulations.Clear(); } - public bool ApplyMod( EqpManipulation m, Mod mod ) + public bool ApplyMod( EqpManipulation m, IMod mod ) { #if USE_EQP Manipulations[ m ] = mod; diff --git a/Penumbra/Meta/Manager/MetaManager.Est.cs b/Penumbra/Meta/Manager/MetaManager.Est.cs index ccc5f926..7af2609c 100644 --- a/Penumbra/Meta/Manager/MetaManager.Est.cs +++ b/Penumbra/Meta/Manager/MetaManager.Est.cs @@ -18,7 +18,7 @@ public partial class MetaManager public EstFile? BodyFile = null; public EstFile? HeadFile = null; - public readonly Dictionary< EstManipulation, Mod > Manipulations = new(); + public readonly Dictionary< EstManipulation, IMod > Manipulations = new(); public MetaManagerEst() { } @@ -51,7 +51,7 @@ public partial class MetaManager Manipulations.Clear(); } - public bool ApplyMod( EstManipulation m, Mod mod ) + public bool ApplyMod( EstManipulation m, IMod mod ) { #if USE_EST Manipulations[ m ] = mod; diff --git a/Penumbra/Meta/Manager/MetaManager.Gmp.cs b/Penumbra/Meta/Manager/MetaManager.Gmp.cs index 49c29076..82833017 100644 --- a/Penumbra/Meta/Manager/MetaManager.Gmp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Gmp.cs @@ -14,7 +14,7 @@ public partial class MetaManager public struct MetaManagerGmp : IDisposable { public ExpandedGmpFile? File = null; - public readonly Dictionary< GmpManipulation, Mod > Manipulations = new(); + public readonly Dictionary< GmpManipulation, IMod > Manipulations = new(); public MetaManagerGmp() { } @@ -38,7 +38,7 @@ public partial class MetaManager } } - public bool ApplyMod( GmpManipulation m, Mod mod ) + public bool ApplyMod( GmpManipulation m, IMod mod ) { #if USE_GMP Manipulations[ m ] = mod; diff --git a/Penumbra/Meta/Manager/MetaManager.Imc.cs b/Penumbra/Meta/Manager/MetaManager.Imc.cs index 23bdf125..35e7434f 100644 --- a/Penumbra/Meta/Manager/MetaManager.Imc.cs +++ b/Penumbra/Meta/Manager/MetaManager.Imc.cs @@ -18,7 +18,7 @@ public partial class MetaManager public readonly struct MetaManagerImc : IDisposable { public readonly Dictionary< Utf8GamePath, ImcFile > Files = new(); - public readonly Dictionary< ImcManipulation, Mod > Manipulations = new(); + public readonly Dictionary< ImcManipulation, IMod > Manipulations = new(); private readonly ModCollection _collection; private static int _imcManagerCount; @@ -65,7 +65,7 @@ public partial class MetaManager Manipulations.Clear(); } - public bool ApplyMod( ImcManipulation m, Mod mod ) + public bool ApplyMod( ImcManipulation m, IMod mod ) { #if USE_IMC Manipulations[ m ] = mod; diff --git a/Penumbra/Meta/Manager/MetaManager.cs b/Penumbra/Meta/Manager/MetaManager.cs index 5d6c0a5b..a71e37b1 100644 --- a/Penumbra/Meta/Manager/MetaManager.cs +++ b/Penumbra/Meta/Manager/MetaManager.cs @@ -30,7 +30,7 @@ public partial class MetaManager : IDisposable } } - public bool TryGetValue( MetaManipulation manip, [NotNullWhen(true)] out Mod? mod ) + public bool TryGetValue( MetaManipulation manip, [NotNullWhen(true)] out IMod? mod ) { mod = manip.ManipulationType switch { @@ -86,7 +86,7 @@ public partial class MetaManager : IDisposable Imc.Dispose(); } - public bool ApplyMod( MetaManipulation m, Mod mod ) + public bool ApplyMod( MetaManipulation m, IMod mod ) { return m.ManipulationType switch { diff --git a/Penumbra/Mods/Editor/IMod.cs b/Penumbra/Mods/Editor/IMod.cs new file mode 100644 index 00000000..5b7b0f20 --- /dev/null +++ b/Penumbra/Mods/Editor/IMod.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using OtterGui.Classes; + +namespace Penumbra.Mods; + +public interface IMod +{ + LowerString Name { get; } + + public int Index { get; } + public int Priority { get; } + + public int TotalManipulations { get; } + + public ISubMod Default { get; } + public IReadOnlyList< IModGroup > Groups { get; } + + public IEnumerable< ISubMod > AllSubMods { get; } +} \ No newline at end of file diff --git a/Penumbra/Mods/Editor/Mod.Editor.cs b/Penumbra/Mods/Editor/Mod.Editor.cs index 74158a4e..455dcf8e 100644 --- a/Penumbra/Mods/Editor/Mod.Editor.cs +++ b/Penumbra/Mods/Editor/Mod.Editor.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using Penumbra.GameData.ByteString; using Penumbra.Util; namespace Penumbra.Mods; -public partial class Mod +public partial class Mod : IMod { public partial class Editor : IDisposable { @@ -15,7 +12,7 @@ public partial class Mod public Editor( Mod mod, int groupIdx, int optionIdx ) { - _mod = mod; + _mod = mod; SetSubMod( groupIdx, optionIdx ); GroupIdx = groupIdx; _subMod = _mod._default; diff --git a/Penumbra/Mods/Mod.BasePath.cs b/Penumbra/Mods/Mod.BasePath.cs index 43581735..0add9356 100644 --- a/Penumbra/Mods/Mod.BasePath.cs +++ b/Penumbra/Mods/Mod.BasePath.cs @@ -17,6 +17,10 @@ public partial class Mod public DirectoryInfo ModPath { get; private set; } public int Index { get; private set; } = -1; + // Unused if Index < 0 but used for special temporary mods. + public int Priority + => 0; + private Mod( DirectoryInfo modPath ) => ModPath = modPath; @@ -30,7 +34,7 @@ public partial class Mod } var mod = new Mod( modPath ); - if( !mod.Reload(out _) ) + if( !mod.Reload( out _ ) ) { // Can not be base path not existing because that is checked before. PluginLog.Error( $"Mod at {modPath} without name is not supported." ); @@ -40,15 +44,17 @@ public partial class Mod return mod; } - private bool Reload(out MetaChangeType metaChange) + private bool Reload( out MetaChangeType metaChange ) { metaChange = MetaChangeType.Deletion; ModPath.Refresh(); if( !ModPath.Exists ) + { return false; + } metaChange = LoadMeta(); - if( metaChange.HasFlag(MetaChangeType.Deletion) || Name.Length == 0 ) + if( metaChange.HasFlag( MetaChangeType.Deletion ) || Name.Length == 0 ) { return false; } diff --git a/Penumbra/Mods/Mod.Meta.cs b/Penumbra/Mods/Mod.Meta.cs index 3e052b3c..615e0dc1 100644 --- a/Penumbra/Mods/Mod.Meta.cs +++ b/Penumbra/Mods/Mod.Meta.cs @@ -24,10 +24,11 @@ public enum MetaChangeType : ushort public sealed partial class Mod { - public static readonly Mod ForcedFiles = new(new DirectoryInfo( "." )) + public static readonly TemporaryMod ForcedFiles = new() { - Name = "Forced Files", - Index = -1, + Name = "Forced Files", + Index = -1, + Priority = int.MaxValue, }; public const uint CurrentFileVersion = 1; diff --git a/Penumbra/Mods/Mod.TemporaryMod.cs b/Penumbra/Mods/Mod.TemporaryMod.cs new file mode 100644 index 00000000..6662f826 --- /dev/null +++ b/Penumbra/Mods/Mod.TemporaryMod.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using OtterGui.Classes; + +namespace Penumbra.Mods; + +public sealed partial class Mod +{ + public class TemporaryMod : IMod + { + public LowerString Name { get; init; } = LowerString.Empty; + public int Index { get; init; } = -2; + public int Priority { get; init; } = int.MaxValue; + + public int TotalManipulations + => Default.Manipulations.Count; + + public ISubMod Default { get; } = new SubMod(); + + public IReadOnlyList< IModGroup > Groups + => Array.Empty< IModGroup >(); + + public IEnumerable< ISubMod > AllSubMods + => new[] { Default }; + } +} \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs b/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs index 6eb6c5ca..b9105bda 100644 --- a/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs +++ b/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs @@ -23,13 +23,13 @@ public partial class ConfigWindow private void DrawChangedItemTab() { // Functions in here for less pollution. - bool FilterChangedItem( KeyValuePair< string, (SingleArray< Mod >, object?) > item ) + bool FilterChangedItem( KeyValuePair< string, (SingleArray< IMod >, object?) > item ) => ( _changedItemFilter.IsEmpty || ChangedItemName( item.Key, item.Value.Item2 ) .Contains( _changedItemFilter.Lower, StringComparison.InvariantCultureIgnoreCase ) ) && ( _changedItemModFilter.IsEmpty || item.Value.Item1.Any( m => m.Name.Contains( _changedItemModFilter ) ) ); - void DrawChangedItemColumn( KeyValuePair< string, (SingleArray< Mod >, object?) > item ) + void DrawChangedItemColumn( KeyValuePair< string, (SingleArray< IMod >, object?) > item ) { ImGui.TableNextColumn(); DrawChangedItem( item.Key, item.Value.Item2, false ); diff --git a/Penumbra/UI/ConfigWindow.EffectiveTab.cs b/Penumbra/UI/ConfigWindow.EffectiveTab.cs index 38c37485..b9a2213f 100644 --- a/Penumbra/UI/ConfigWindow.EffectiveTab.cs +++ b/Penumbra/UI/ConfigWindow.EffectiveTab.cs @@ -112,7 +112,7 @@ public partial class ConfigWindow { // We can treat all meta manipulations the same, // we are only really interested in their ToString function here. - static (object, Mod) Convert< T >( KeyValuePair< T, Mod > kvp ) + static (object, IMod) Convert< T >( KeyValuePair< T, IMod > kvp ) => ( kvp.Key!, kvp.Value ); var it = m.Cmp.Manipulations.Select( Convert ) @@ -183,7 +183,7 @@ public partial class ConfigWindow } // Draw a line for a unfiltered/unconverted manipulation and mod-index pair. - private static void DrawLine( (object, Mod) pair ) + private static void DrawLine( (object, IMod) pair ) { var (manipulation, mod) = pair; ImGui.TableNextColumn(); diff --git a/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs b/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs index d1c4a838..a7af5ae4 100644 --- a/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs +++ b/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs @@ -128,16 +128,19 @@ public partial class ConfigWindow foreach( var conflict in Penumbra.CollectionManager.Current.Conflicts( _mod ) ) { - if( ImGui.Selectable( conflict.Mod2.Name ) ) + if( ImGui.Selectable( conflict.Mod2.Name ) && conflict.Mod2 is Mod mod ) { - _window._selector.SelectByValue( conflict.Mod2 ); + _window._selector.SelectByValue( mod ); } ImGui.SameLine(); using( var color = ImRaii.PushColor( ImGuiCol.Text, conflict.HasPriority ? ColorId.HandledConflictMod.Value() : ColorId.ConflictingMod.Value() ) ) { - ImGui.TextUnformatted( $"(Priority {Penumbra.CollectionManager.Current[ conflict.Mod2.Index ].Settings!.Priority})" ); + var priority = conflict.Mod2.Index < 0 + ? conflict.Mod2.Priority + : Penumbra.CollectionManager.Current[conflict.Mod2.Index].Settings!.Priority; + ImGui.TextUnformatted( $"(Priority {priority})" ); } using var indent = ImRaii.PushIndent( 30f );