diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 63ff92f4..40b135e5 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -31,8 +31,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi public (int, int) ApiVersion => (4, 19); - private readonly Dictionary _delegates = new(); - public event Action? PreSettingsPanelDraw; public event Action? PostSettingsPanelDraw; @@ -110,10 +108,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi private CollectionResolver _collectionResolver; private CutsceneService _cutsceneService; private ModImportManager _modImportManager; + private CollectionEditor _collectionEditor; public unsafe PenumbraApi(CommunicatorService communicator, Penumbra penumbra, ModManager modManager, ResourceLoader resourceLoader, Configuration config, CollectionManager collectionManager, DalamudServices dalamud, TempCollectionManager tempCollections, - TempModManager tempMods, ActorService actors, CollectionResolver collectionResolver, CutsceneService cutsceneService, ModImportManager modImportManager) + TempModManager tempMods, ActorService actors, CollectionResolver collectionResolver, CutsceneService cutsceneService, ModImportManager modImportManager, CollectionEditor collectionEditor) { _communicator = communicator; _penumbra = penumbra; @@ -127,17 +126,16 @@ public class PenumbraApi : IDisposable, IPenumbraApi _actors = actors; _collectionResolver = collectionResolver; _cutsceneService = cutsceneService; - _modImportManager = modImportManager; + _modImportManager = modImportManager; + _collectionEditor = collectionEditor; _lumina = (Lumina.GameData?)_dalamud.GameData.GetType() .GetField("gameData", BindingFlags.Instance | BindingFlags.NonPublic) ?.GetValue(_dalamud.GameData); - foreach (var collection in _collectionManager.Storage) - SubscribeToCollection(collection); - _communicator.CollectionChange.Subscribe(SubscribeToNewCollections); _resourceLoader.ResourceLoaded += OnResourceLoaded; - _communicator.ModPathChanged.Subscribe(ModPathChangeSubscriber); + _communicator.ModPathChanged.Subscribe(ModPathChangeSubscriber); + _communicator.ModSettingChanged.Subscribe(OnModSettingChange, -1000); } public unsafe void Dispose() @@ -145,15 +143,9 @@ public class PenumbraApi : IDisposable, IPenumbraApi if (!Valid) return; - foreach (var collection in _collectionManager.Storage) - { - if (_delegates.TryGetValue(collection, out var del)) - collection.ModSettingChanged -= del; - } - _resourceLoader.ResourceLoaded -= OnResourceLoaded; - _communicator.CollectionChange.Unsubscribe(SubscribeToNewCollections); - _communicator.ModPathChanged.Unsubscribe(ModPathChangeSubscriber); + _communicator.ModPathChanged.Unsubscribe(ModPathChangeSubscriber); + _communicator.ModSettingChanged.Unsubscribe(OnModSettingChange); _lumina = null; _communicator = null!; _penumbra = null!; @@ -167,6 +159,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi _actors = null!; _collectionResolver = null!; _cutsceneService = null!; + _modImportManager = null!; + _collectionEditor = null!; } public event ChangedItemClick? ChangedItemClicked; @@ -702,7 +696,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi return PenumbraApiEc.ModMissing; - return collection.SetModInheritance(mod.Index, inherit) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; + return _collectionEditor.SetModInheritance(collection, mod, inherit) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; } public PenumbraApiEc TrySetMod(string collectionName, string modDirectory, string modName, bool enabled) @@ -714,7 +708,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) return PenumbraApiEc.ModMissing; - return collection.SetModState(mod.Index, enabled) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; + return _collectionEditor.SetModState(collection, mod, enabled) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; } public PenumbraApiEc TrySetModPriority(string collectionName, string modDirectory, string modName, int priority) @@ -726,7 +720,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) return PenumbraApiEc.ModMissing; - return collection.SetModPriority(mod.Index, priority) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; + return _collectionEditor.SetModPriority(collection, mod, priority) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; } public PenumbraApiEc TrySetModSetting(string collectionName, string modDirectory, string modName, string optionGroupName, @@ -749,7 +743,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi var setting = mod.Groups[groupIdx].Type == GroupType.Multi ? 1u << optionIdx : (uint)optionIdx; - return collection.SetModSetting(mod.Index, groupIdx, setting) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; + return _collectionEditor.SetModSetting(collection, mod, groupIdx, setting) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; } public PenumbraApiEc TrySetModSettings(string collectionName, string modDirectory, string modName, string optionGroupName, @@ -789,7 +783,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi } } - return collection.SetModSetting(mod.Index, groupIdx, setting) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; + return _collectionEditor.SetModSetting(collection, mod, groupIdx, setting) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; } @@ -797,17 +791,13 @@ public class PenumbraApi : IDisposable, IPenumbraApi { CheckInitialized(); - var sourceModIdx = _modManager - .FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryFrom, StringComparison.OrdinalIgnoreCase))?.Index - ?? -1; - var targetModIdx = _modManager - .FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryTo, StringComparison.OrdinalIgnoreCase))?.Index - ?? -1; + var sourceMod = _modManager.FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryFrom, StringComparison.OrdinalIgnoreCase)); + var targetMod = _modManager.FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryTo, StringComparison.OrdinalIgnoreCase)); if (string.IsNullOrEmpty(collectionName)) foreach (var collection in _collectionManager.Storage) - collection.CopyModSettings(sourceModIdx, modDirectoryFrom, targetModIdx, modDirectoryTo); + _collectionEditor.CopyModSettings(collection, sourceMod, modDirectoryFrom, targetMod, modDirectoryTo); else if (_collectionManager.Storage.ByName(collectionName, out var collection)) - collection.CopyModSettings(sourceModIdx, modDirectoryFrom, targetModIdx, modDirectoryTo); + _collectionEditor.CopyModSettings(collection, sourceMod, modDirectoryFrom, targetMod, modDirectoryTo); else return PenumbraApiEc.CollectionMissing; @@ -1127,29 +1117,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi return true; } - private void SubscribeToCollection(ModCollection c) - { - var name = c.Name; - - void Del(ModSettingChange type, int idx, int _, int _2, bool inherited) - => ModSettingChanged?.Invoke(type, name, idx >= 0 ? _modManager[idx].ModPath.Name : string.Empty, inherited); - - _delegates[c] = Del; - c.ModSettingChanged += Del; - } - - private void SubscribeToNewCollections(CollectionType type, ModCollection? oldCollection, ModCollection? newCollection, string _) - { - if (type != CollectionType.Inactive) - return; - - if (oldCollection != null && _delegates.TryGetValue(oldCollection, out var del)) - oldCollection.ModSettingChanged -= del; - - if (newCollection != null) - SubscribeToCollection(newCollection); - } - public void InvokePreSettingsPanel(string modDirectory) => PreSettingsPanelDraw?.Invoke(modDirectory); @@ -1166,4 +1133,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi var b = ByteString.FromStringUnsafe(name, false); return _actors.AwaitedService.CreatePlayer(b, worldId); } + + private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int _1, int _2, bool inherited) + => ModSettingChanged?.Invoke(type, collection.Name, mod?.ModPath.Name ?? string.Empty, inherited); } diff --git a/Penumbra/Collections/Manager/CollectionCacheManager.cs b/Penumbra/Collections/Manager/CollectionCacheManager.cs index a7ace6c1..56a6e3a3 100644 --- a/Penumbra/Collections/Manager/CollectionCacheManager.cs +++ b/Penumbra/Collections/Manager/CollectionCacheManager.cs @@ -6,6 +6,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Penumbra.Api; +using Penumbra.Api.Enums; +using Penumbra.Interop.Services; using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Services; @@ -16,6 +18,7 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary _cache = new(); @@ -46,17 +49,23 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary Active => _cache.Keys.Where(c => c.Index > ModCollection.Empty.Index); - public CollectionCacheManager(ActiveCollections active, CommunicatorService communicator) + public CollectionCacheManager(ActiveCollections active, CommunicatorService communicator, CharacterUtility characterUtility) { - _active = active; - _communicator = communicator; + _active = active; + _communicator = communicator; + _characterUtility = characterUtility; _communicator.CollectionChange.Subscribe(OnCollectionChange); _communicator.ModPathChanged.Subscribe(OnModChangeAddition, -100); _communicator.ModPathChanged.Subscribe(OnModChangeRemoval, 100); _communicator.TemporaryGlobalModChange.Subscribe(OnGlobalModChange); _communicator.ModOptionChanged.Subscribe(OnModOptionChange, -100); + _communicator.ModSettingChanged.Subscribe(OnModSettingChange); + _communicator.CollectionInheritanceChanged.Subscribe(OnCollectionInheritanceChange); CreateNecessaryCaches(); + + if (!_characterUtility.Ready) + _characterUtility.LoadingFinished += IncrementCounters; } public void Dispose() @@ -66,6 +75,9 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary @@ -170,4 +182,57 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary Increment the counter to ensure new files are loaded after applying meta changes. + private void IncrementCounters() + { + foreach (var (collection, _) in _cache) + ++collection.ChangeCounter; + _characterUtility.LoadingFinished -= IncrementCounters; + } + + private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool _) + { + if (collection._cache == null) + return; + + switch (type) + { + case ModSettingChange.Inheritance: + collection._cache.ReloadMod(mod!, true); + break; + case ModSettingChange.EnableState: + if (oldValue == 0) + collection._cache.AddMod(mod!, true); + else if (oldValue == 1) + collection._cache.RemoveMod(mod!, true); + else if (collection[mod!.Index].Settings?.Enabled == true) + collection._cache.ReloadMod(mod!, true); + else + collection._cache.RemoveMod(mod!, true); + + break; + case ModSettingChange.Priority: + if (collection._cache.Conflicts(mod!).Count > 0) + collection._cache.ReloadMod(mod!, true); + + break; + case ModSettingChange.Setting: + if (collection[mod!.Index].Settings?.Enabled == true) + collection._cache.ReloadMod(mod!, true); + + break; + case ModSettingChange.MultiInheritance: + case ModSettingChange.MultiEnableState: + collection._cache.FullRecalculation(collection == _active.Default); + break; + } + } + + /// + /// Inheritance changes are too big to check for relevance, + /// just recompute everything. + /// + private void OnCollectionInheritanceChange(ModCollection collection, bool _) + => collection._cache?.FullRecalculation(collection == _active.Default); } diff --git a/Penumbra/Collections/Manager/CollectionEditor.cs b/Penumbra/Collections/Manager/CollectionEditor.cs new file mode 100644 index 00000000..57a7b8f6 --- /dev/null +++ b/Penumbra/Collections/Manager/CollectionEditor.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using OtterGui; +using Penumbra.Api.Enums; +using Penumbra.Mods; +using Penumbra.Services; +using Penumbra.Util; + +namespace Penumbra.Collections.Manager; + +public class CollectionEditor +{ + private readonly CommunicatorService _communicator; + private readonly SaveService _saveService; + + public CollectionEditor(SaveService saveService, CommunicatorService communicator) + { + _saveService = saveService; + _communicator = communicator; + } + + /// Enable or disable the mod inheritance of mod idx. + public bool SetModInheritance(ModCollection collection, Mod mod, bool inherit) + { + if (!FixInheritance(collection, mod, inherit)) + return false; + + InvokeChange(collection, ModSettingChange.Inheritance, mod, inherit ? 0 : 1, 0); + return true; + } + + /// + /// Set the enabled state mod idx to newValue if it differs from the current enabled state. + /// If the mod is currently inherited, stop the inheritance. + /// + public bool SetModState(ModCollection collection, Mod mod, bool newValue) + { + var oldValue = collection._settings[mod.Index]?.Enabled ?? collection[mod.Index].Settings?.Enabled ?? false; + if (newValue == oldValue) + return false; + + var inheritance = FixInheritance(collection, mod, false); + collection._settings[mod.Index]!.Enabled = newValue; + InvokeChange(collection, ModSettingChange.EnableState, mod, inheritance ? -1 : newValue ? 0 : 1, 0); + return true; + } + + /// Enable or disable the mod inheritance of every mod in mods. + public void SetMultipleModInheritances(ModCollection collection, IEnumerable mods, bool inherit) + { + if (!mods.Aggregate(false, (current, mod) => current | FixInheritance(collection, mod, inherit))) + return; + + InvokeChange(collection, ModSettingChange.MultiInheritance, null, -1, 0); + } + + /// + /// Set the enabled state of every mod in mods to the new value. + /// If the mod is currently inherited, stop the inheritance. + /// + public void SetMultipleModStates(ModCollection collection, IEnumerable mods, bool newValue) + { + var changes = false; + foreach (var mod in mods) + { + var oldValue = collection._settings[mod.Index]?.Enabled; + if (newValue == oldValue) + continue; + + FixInheritance(collection, mod, false); + collection._settings[mod.Index]!.Enabled = newValue; + changes = true; + } + + if (!changes) + return; + + InvokeChange(collection, ModSettingChange.MultiEnableState, null, -1, 0); + } + + /// + /// Set the priority of mod idx to newValue if it differs from the current priority. + /// If the mod is currently inherited, stop the inheritance. + /// + public bool SetModPriority(ModCollection collection, Mod mod, int newValue) + { + var oldValue = collection._settings[mod.Index]?.Priority ?? collection[mod.Index].Settings?.Priority ?? 0; + if (newValue == oldValue) + return false; + + var inheritance = FixInheritance(collection, mod, false); + collection._settings[mod.Index]!.Priority = newValue; + InvokeChange(collection, ModSettingChange.Priority, mod, inheritance ? -1 : oldValue, 0); + return true; + } + + /// + /// Set a given setting group settingName of mod idx to newValue if it differs from the current value and fix it if necessary. + /// /// If the mod is currently inherited, stop the inheritance. + /// + public bool SetModSetting(ModCollection collection, Mod mod, int groupIdx, uint newValue) + { + var settings = collection._settings[mod.Index] != null + ? collection._settings[mod.Index]!.Settings + : collection[mod.Index].Settings?.Settings; + var oldValue = settings?[groupIdx] ?? mod.Groups[groupIdx].DefaultSettings; + if (oldValue == newValue) + return false; + + var inheritance = FixInheritance(collection, mod, false); + collection._settings[mod.Index]!.SetValue(mod, groupIdx, newValue); + InvokeChange(collection, ModSettingChange.Setting, mod, inheritance ? -1 : (int)oldValue, groupIdx); + return true; + } + + /// Copy the settings of an existing (sourceMod != null) or stored (sourceName) mod to another mod, if they exist. + public bool CopyModSettings(ModCollection collection, Mod? sourceMod, string sourceName, Mod? targetMod, string targetName) + { + if (targetName.Length == 0 && targetMod == null || sourceName.Length == 0) + return false; + + // If the source mod exists, convert its settings to saved settings or null if its inheriting. + // If it does not exist, check unused settings. + // If it does not exist and has no unused settings, also use null. + ModSettings.SavedSettings? savedSettings = sourceMod != null + ? collection._settings[sourceMod.Index] != null + ? new ModSettings.SavedSettings(collection._settings[sourceMod.Index]!, sourceMod) + : null + : collection._unusedSettings.TryGetValue(sourceName, out var s) + ? s + : null; + + if (targetMod != null) + { + if (savedSettings != null) + { + // The target mod exists and the source settings are not inheriting, convert and fix the settings and copy them. + // This triggers multiple events. + savedSettings.Value.ToSettings(targetMod, out var settings); + SetModState(collection, targetMod, settings.Enabled); + SetModPriority(collection, targetMod, settings.Priority); + foreach (var (value, index) in settings.Settings.WithIndex()) + SetModSetting(collection, targetMod, index, value); + } + else + { + // The target mod exists, but the source is inheriting, set the target to inheriting. + // This triggers events. + SetModInheritance(collection, targetMod, true); + } + } + else + { + // The target mod does not exist. + // Either copy the unused source settings directly if they are not inheriting, + // or remove any unused settings for the target if they are inheriting. + if (savedSettings != null) + collection._unusedSettings[targetName] = savedSettings.Value; + else + collection._unusedSettings.Remove(targetName); + } + + return true; + } + + /// + /// Change one of the available mod settings for mod idx discerned by type. + /// If type == Setting, settingName should be a valid setting for that mod, otherwise it will be ignored. + /// The setting will also be automatically fixed if it is invalid for that setting group. + /// For boolean parameters, newValue == 0 will be treated as false and != 0 as true. + /// + public bool ChangeModSetting(ModCollection collection, ModSettingChange type, Mod mod, int newValue, int groupIdx) + { + return type switch + { + ModSettingChange.Inheritance => SetModInheritance(collection, mod, newValue != 0), + ModSettingChange.EnableState => SetModState(collection, mod, newValue != 0), + ModSettingChange.Priority => SetModPriority(collection, mod, newValue), + ModSettingChange.Setting => SetModSetting(collection, mod, groupIdx, (uint)newValue), + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null), + }; + } + + /// + /// Set inheritance of a mod without saving, + /// to be used as an intermediary. + /// + private static bool FixInheritance(ModCollection collection, Mod mod, bool inherit) + { + var settings = collection._settings[mod.Index]; + if (inherit == (settings == null)) + return false; + + collection._settings[mod.Index] = inherit ? null : collection[mod.Index].Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod); + return true; + } + + /// Queue saves and trigger changes for any non-inherited change in a collection, then trigger changes for all inheritors. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void InvokeChange(ModCollection changedCollection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx) + { + _saveService.QueueSave(changedCollection); + _communicator.ModSettingChanged.Invoke(changedCollection, type, mod, oldValue, groupIdx, false); + RecurseInheritors(changedCollection, type, mod, oldValue, groupIdx); + } + + /// Trigger changes in all inherited collections. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void RecurseInheritors(ModCollection directParent, ModSettingChange type, Mod? mod, int oldValue, int groupIdx) + { + foreach (var directInheritor in directParent.DirectParentOf) + { + switch (type) + { + case ModSettingChange.MultiInheritance: + case ModSettingChange.MultiEnableState: + _communicator.ModSettingChanged.Invoke(directInheritor, type, null, oldValue, groupIdx, true); + break; + default: + if (directInheritor._settings[mod!.Index] == null) + _communicator.ModSettingChanged.Invoke(directInheritor, type, mod, oldValue, groupIdx, true); + break; + } + + RecurseInheritors(directInheritor, type, mod, oldValue, groupIdx); + } + } +} diff --git a/Penumbra/Collections/Manager/CollectionManager.cs b/Penumbra/Collections/Manager/CollectionManager.cs index b124b7db..5e1c5781 100644 --- a/Penumbra/Collections/Manager/CollectionManager.cs +++ b/Penumbra/Collections/Manager/CollectionManager.cs @@ -7,14 +7,16 @@ public class CollectionManager public readonly InheritanceManager Inheritances; public readonly CollectionCacheManager Caches; public readonly TempCollectionManager Temp; + public readonly CollectionEditor Editor; public CollectionManager(CollectionStorage storage, ActiveCollections active, InheritanceManager inheritances, - CollectionCacheManager caches, TempCollectionManager temp) + CollectionCacheManager caches, TempCollectionManager temp, CollectionEditor editor) { Storage = storage; Active = active; Inheritances = inheritances; Caches = caches; Temp = temp; + Editor = editor; } } diff --git a/Penumbra/Collections/Manager/InheritanceManager.cs b/Penumbra/Collections/Manager/InheritanceManager.cs index 06227fdf..e5034ece 100644 --- a/Penumbra/Collections/Manager/InheritanceManager.cs +++ b/Penumbra/Collections/Manager/InheritanceManager.cs @@ -1,12 +1,34 @@ using System; +using System.Collections.Generic; +using System.Linq; using Dalamud.Interface.Internal.Notifications; +using OtterGui; +using OtterGui.Filesystem; using Penumbra.Services; using Penumbra.Util; namespace Penumbra.Collections.Manager; +/// +/// ModCollections can inherit from an arbitrary number of other collections. +/// This is transitive, so a collection A inheriting from B also inherits from everything B inherits. +/// Circular dependencies are resolved by distinctness. +/// public class InheritanceManager : IDisposable { + public enum ValidInheritance + { + Valid, + /// Can not inherit from self + Self, + /// Can not inherit from the empty collection + Empty, + /// Already inherited from + Contained, + /// Inheritance would lead to a circle. + Circle, + } + private readonly CollectionStorage _storage; private readonly CommunicatorService _communicator; private readonly SaveService _saveService; @@ -26,22 +48,88 @@ public class InheritanceManager : IDisposable _communicator.CollectionChange.Unsubscribe(OnCollectionChange); } + /// Check whether a collection can be inherited from. + public static ValidInheritance CheckValidInheritance(ModCollection potentialInheritor, ModCollection? potentialParent) + { + if (potentialParent == null || ReferenceEquals(potentialParent, ModCollection.Empty)) + return ValidInheritance.Empty; + + if (ReferenceEquals(potentialParent, potentialInheritor)) + return ValidInheritance.Self; + + if (potentialInheritor.DirectlyInheritsFrom.Contains(potentialParent)) + return ValidInheritance.Contained; + + if (ModCollection.InheritedCollections(potentialParent).Any(c => ReferenceEquals(c, potentialInheritor))) + return ValidInheritance.Circle; + + return ValidInheritance.Valid; + } + + /// + /// Add a new collection to the inheritance list. + /// We do not check if this collection would be visited before, + /// only that it is unique in the list itself. + /// + public bool AddInheritance(ModCollection inheritor, ModCollection parent) + => AddInheritance(inheritor, parent, true); + + /// Remove an existing inheritance from a collection. + public void RemoveInheritance(ModCollection inheritor, int idx) + { + var parent = inheritor.DirectlyInheritsFrom[idx]; + ((List)inheritor.DirectlyInheritsFrom).RemoveAt(idx); + ((List)parent.DirectParentOf).Remove(inheritor); + _communicator.CollectionInheritanceChanged.Invoke(inheritor, false); + RecurseInheritanceChanges(inheritor); + Penumbra.Log.Debug($"Removed {parent.AnonymizedName} from {inheritor.AnonymizedName} inheritances."); + } + + /// Order in the inheritance list is relevant. + public void MoveInheritance(ModCollection inheritor, int from, int to) + { + if (!((List)inheritor.DirectlyInheritsFrom).Move(from, to)) + return; + + _communicator.CollectionInheritanceChanged.Invoke(inheritor, false); + RecurseInheritanceChanges(inheritor); + Penumbra.Log.Debug($"Moved {inheritor.AnonymizedName}s inheritance {from} to {to}."); + } + + /// + private bool AddInheritance(ModCollection inheritor, ModCollection parent, bool invokeEvent) + { + if (CheckValidInheritance(inheritor, parent) != ValidInheritance.Valid) + return false; + + ((List)inheritor.DirectlyInheritsFrom).Add(parent); + ((List)parent.DirectParentOf).Add(inheritor); + if (invokeEvent) + { + _communicator.CollectionInheritanceChanged.Invoke(inheritor, false); + RecurseInheritanceChanges(inheritor); + } + + Penumbra.Log.Debug($"Added {parent.AnonymizedName} to {inheritor.AnonymizedName} inheritances."); + return true; + } + /// /// Inheritances can not be setup before all collections are read, /// so this happens after reading the collections in the constructor, consuming the stored strings. /// private void ApplyInheritances() { - foreach (var (collection, inheritances, changes) in _storage.ConsumeInheritanceNames()) + foreach (var (collection, directParents, changes) in _storage.ConsumeInheritanceNames()) { var localChanges = changes; - foreach (var subCollection in inheritances) + foreach (var parent in directParents) { - if (collection.AddInheritance(subCollection, false)) + if (AddInheritance(collection, parent, false)) continue; localChanges = true; - Penumbra.ChatService.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.", "Warning", + Penumbra.ChatService.NotificationMessage($"{collection.Name} can not inherit from {parent.Name}, removed.", "Warning", NotificationType.Warning); } @@ -55,14 +143,22 @@ public class InheritanceManager : IDisposable if (collectionType is not CollectionType.Inactive || old == null) return; - foreach (var inheritance in old.Inheritance) - old.ClearSubscriptions(inheritance); - foreach (var c in _storage) { - var inheritedIdx = c._inheritance.IndexOf(old); + var inheritedIdx = c.DirectlyInheritsFrom.IndexOf(old); if (inheritedIdx >= 0) - c.RemoveInheritance(inheritedIdx); + RemoveInheritance(c, inheritedIdx); + + ((List)c.DirectParentOf).Remove(old); + } + } + + private void RecurseInheritanceChanges(ModCollection newInheritor) + { + foreach (var inheritor in newInheritor.DirectParentOf) + { + _communicator.CollectionInheritanceChanged.Invoke(inheritor, true); + RecurseInheritanceChanges(inheritor); } } } diff --git a/Penumbra/Collections/ModCollection.Cache.cs b/Penumbra/Collections/ModCollection.Cache.cs index 4a3c008c..5ed7f801 100644 --- a/Penumbra/Collections/ModCollection.Cache.cs +++ b/Penumbra/Collections/ModCollection.Cache.cs @@ -50,21 +50,12 @@ public class ModCollectionCache : IDisposable public ModCollectionCache( ModCollection collection ) { _collection = collection; - MetaManipulations = new MetaManager( _collection ); - _collection.ModSettingChanged += OnModSettingChange; - _collection.InheritanceChanged += OnInheritanceChange; - if( !Penumbra.CharacterUtility.Ready ) - { - Penumbra.CharacterUtility.LoadingFinished += IncrementCounter; - } + MetaManipulations = new MetaManager( _collection ); } public void Dispose() { MetaManipulations.Dispose(); - _collection.ModSettingChanged -= OnModSettingChange; - _collection.InheritanceChanged -= OnInheritanceChange; - Penumbra.CharacterUtility.LoadingFinished -= IncrementCounter; } // Resolve a given game path according to this collection. @@ -133,58 +124,6 @@ public class ModCollectionCache : IDisposable return ret; } - private void OnModSettingChange( ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool _ ) - { - switch( type ) - { - case ModSettingChange.Inheritance: - ReloadMod( Penumbra.ModManager[ modIdx ], true ); - break; - case ModSettingChange.EnableState: - if( oldValue == 0 ) - { - AddMod( Penumbra.ModManager[ modIdx ], true ); - } - else if( oldValue == 1 ) - { - RemoveMod( Penumbra.ModManager[ modIdx ], true ); - } - else if( _collection[ modIdx ].Settings?.Enabled == true ) - { - ReloadMod( Penumbra.ModManager[ modIdx ], true ); - } - else - { - RemoveMod( Penumbra.ModManager[ modIdx ], true ); - } - - break; - case ModSettingChange.Priority: - if( Conflicts( Penumbra.ModManager[ modIdx ] ).Count > 0 ) - { - ReloadMod( Penumbra.ModManager[ modIdx ], true ); - } - - break; - case ModSettingChange.Setting: - if( _collection[ modIdx ].Settings?.Enabled == true ) - { - ReloadMod( Penumbra.ModManager[ modIdx ], true ); - } - - break; - case ModSettingChange.MultiInheritance: - case ModSettingChange.MultiEnableState: - FullRecalculation(_collection == Penumbra.CollectionManager.Active.Default); - break; - } - } - - // Inheritance changes are too big to check for relevance, - // just recompute everything. - private void OnInheritanceChange( bool _ ) - => FullRecalculation(_collection == Penumbra.CollectionManager.Active.Default); - public void FullRecalculation(bool isDefault) { ResolvedFiles.Clear(); diff --git a/Penumbra/Collections/ModCollection.Changes.cs b/Penumbra/Collections/ModCollection.Changes.cs deleted file mode 100644 index 44c622a1..00000000 --- a/Penumbra/Collections/ModCollection.Changes.cs +++ /dev/null @@ -1,138 +0,0 @@ -using Penumbra.Mods; -using System; -using System.Collections.Generic; -using System.Linq; -using Penumbra.Api.Enums; - -namespace Penumbra.Collections; - -public partial class ModCollection -{ - // If the change type is a bool, oldValue will be 1 for true and 0 for false. - // optionName will only be set for type == Setting. - public delegate void ModSettingChangeDelegate( ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited ); - public event ModSettingChangeDelegate ModSettingChanged; - - // Enable or disable the mod inheritance of mod idx. - public bool SetModInheritance( int idx, bool inherit ) - { - if (!FixInheritance(idx, inherit)) - return false; - - ModSettingChanged.Invoke( ModSettingChange.Inheritance, idx, inherit ? 0 : 1, 0, false ); - return true; - - } - - // Set the enabled state mod idx to newValue if it differs from the current enabled state. - // If mod idx is currently inherited, stop the inheritance. - public bool SetModState( int idx, bool newValue ) - { - var oldValue = _settings[ idx ]?.Enabled ?? this[ idx ].Settings?.Enabled ?? false; - if (newValue == oldValue) - return false; - - var inheritance = FixInheritance( idx, false ); - _settings[ idx ]!.Enabled = newValue; - ModSettingChanged.Invoke( ModSettingChange.EnableState, idx, inheritance ? -1 : newValue ? 0 : 1, 0, false ); - return true; - - } - - // Enable or disable the mod inheritance of every mod in mods. - public void SetMultipleModInheritances( IEnumerable< Mod > mods, bool inherit ) - { - if( mods.Aggregate( false, ( current, mod ) => current | FixInheritance( mod.Index, inherit ) ) ) - ModSettingChanged.Invoke( ModSettingChange.MultiInheritance, -1, -1, 0, false ); - } - - // Set the enabled state of every mod in mods to the new value. - // If the mod is currently inherited, stop the inheritance. - public void SetMultipleModStates( IEnumerable< Mod > mods, bool newValue ) - { - var changes = false; - foreach( var mod in mods ) - { - var oldValue = _settings[ mod.Index ]?.Enabled; - if (newValue == oldValue) - continue; - - FixInheritance( mod.Index, false ); - _settings[ mod.Index ]!.Enabled = newValue; - changes = true; - } - - if( changes ) - { - ModSettingChanged.Invoke( ModSettingChange.MultiEnableState, -1, -1, 0, false ); - } - } - - // Set the priority of mod idx to newValue if it differs from the current priority. - // If mod idx is currently inherited, stop the inheritance. - public bool SetModPriority( int idx, int newValue ) - { - var oldValue = _settings[ idx ]?.Priority ?? this[ idx ].Settings?.Priority ?? 0; - if (newValue == oldValue) - return false; - - var inheritance = FixInheritance( idx, false ); - _settings[ idx ]!.Priority = newValue; - ModSettingChanged.Invoke( ModSettingChange.Priority, idx, inheritance ? -1 : oldValue, 0, false ); - return true; - - } - - // Set a given setting group settingName of mod idx to newValue if it differs from the current value and fix it if necessary. - // If mod idx is currently inherited, stop the inheritance. - public bool SetModSetting( int idx, int groupIdx, uint newValue ) - { - var settings = _settings[ idx ] != null ? _settings[ idx ]!.Settings : this[ idx ].Settings?.Settings; - var oldValue = settings?[ groupIdx ] ?? Penumbra.ModManager[idx].Groups[groupIdx].DefaultSettings; - if (oldValue == newValue) - return false; - - var inheritance = FixInheritance( idx, false ); - _settings[ idx ]!.SetValue( Penumbra.ModManager[ idx ], groupIdx, newValue ); - ModSettingChanged.Invoke( ModSettingChange.Setting, idx, inheritance ? -1 : ( int )oldValue, groupIdx, false ); - return true; - - } - - // Change one of the available mod settings for mod idx discerned by type. - // If type == Setting, settingName should be a valid setting for that mod, otherwise it will be ignored. - // The setting will also be automatically fixed if it is invalid for that setting group. - // For boolean parameters, newValue == 0 will be treated as false and != 0 as true. - public bool ChangeModSetting( ModSettingChange type, int idx, int newValue, int groupIdx ) - { - return type switch - { - ModSettingChange.Inheritance => SetModInheritance( idx, newValue != 0 ), - ModSettingChange.EnableState => SetModState( idx, newValue != 0 ), - ModSettingChange.Priority => SetModPriority( idx, newValue ), - ModSettingChange.Setting => SetModSetting( idx, groupIdx, ( uint )newValue ), - _ => throw new ArgumentOutOfRangeException( nameof( type ), type, null ), - }; - } - - // Set inheritance of a mod without saving, - // to be used as an intermediary. - private bool FixInheritance( int idx, bool inherit ) - { - var settings = _settings[ idx ]; - if( inherit == ( settings == null ) ) - return false; - - _settings[ idx ] = inherit ? null : this[ idx ].Settings?.DeepCopy() ?? ModSettings.DefaultSettings( Penumbra.ModManager[ idx ] ); - return true; - } - - private void SaveOnChange( ModSettingChange _1, int _2, int _3, int _4, bool inherited ) - => SaveOnChange( inherited ); - - private void SaveOnChange( bool inherited ) - { - if( !inherited ) - Penumbra.SaveService.QueueSave(this); - } -} \ No newline at end of file diff --git a/Penumbra/Collections/ModCollection.File.cs b/Penumbra/Collections/ModCollection.File.cs index dd632f71..f688b5ca 100644 --- a/Penumbra/Collections/ModCollection.File.cs +++ b/Penumbra/Collections/ModCollection.File.cs @@ -32,7 +32,7 @@ public partial class ModCollection : ISavable // Custom deserialization that is converted with the constructor. var settings = obj[nameof(Settings)]?.ToObject>() ?? new Dictionary(); - inheritance = obj[nameof(Inheritance)]?.ToObject>() ?? (IReadOnlyList)Array.Empty(); + inheritance = obj["Inheritance"]?.ToObject>() ?? (IReadOnlyList)Array.Empty(); return new ModCollection(name, version, settings); } @@ -86,8 +86,8 @@ public partial class ModCollection : ISavable j.WriteEndObject(); // Inherit by collection name. - j.WritePropertyName(nameof(Inheritance)); - x.Serialize(j, Inheritance.Select(c => c.Name)); + j.WritePropertyName("Inheritance"); + x.Serialize(j, DirectlyInheritsFrom.Select(c => c.Name)); j.WriteEndObject(); } } diff --git a/Penumbra/Collections/ModCollection.Inheritance.cs b/Penumbra/Collections/ModCollection.Inheritance.cs deleted file mode 100644 index 4c694f88..00000000 --- a/Penumbra/Collections/ModCollection.Inheritance.cs +++ /dev/null @@ -1,170 +0,0 @@ -using OtterGui.Filesystem; -using Penumbra.Mods; -using System; -using System.Collections.Generic; -using System.Linq; -using Penumbra.Api.Enums; - -namespace Penumbra.Collections; - -// ModCollections can inherit from an arbitrary number of other collections. -// This is transitive, so a collection A inheriting from B also inherits from everything B inherits. -// Circular dependencies are resolved by distinctness. -public partial class ModCollection -{ - // A change in inheritance usually requires complete recomputation. - // The bool signifies whether the change was in an already inherited collection. - public event Action< bool > InheritanceChanged; - - internal readonly List< ModCollection > _inheritance = new(); - - public IReadOnlyList< ModCollection > Inheritance - => _inheritance; - - // Iterate over all collections inherited from in depth-first order. - // Skip already visited collections to avoid circular dependencies. - public IEnumerable< ModCollection > GetFlattenedInheritance() - => InheritedCollections( this ).Distinct(); - - // All inherited collections in application order without filtering for duplicates. - private static IEnumerable< ModCollection > InheritedCollections( ModCollection collection ) - => collection.Inheritance.SelectMany( InheritedCollections ).Prepend( collection ); - - // Reasons why a collection can not be inherited from. - public enum ValidInheritance - { - Valid, - Self, // Can not inherit from self - Empty, // Can not inherit from the empty collection - Contained, // Already inherited from - Circle, // Inheritance would lead to a circle. - } - - // Check whether a collection can be inherited from. - public ValidInheritance CheckValidInheritance( ModCollection? collection ) - { - if( collection == null || ReferenceEquals( collection, Empty ) ) - { - return ValidInheritance.Empty; - } - - if( ReferenceEquals( collection, this ) ) - { - return ValidInheritance.Self; - } - - if( _inheritance.Contains( collection ) ) - { - return ValidInheritance.Contained; - } - - if( InheritedCollections( collection ).Any( c => c == this ) ) - { - return ValidInheritance.Circle; - } - - return ValidInheritance.Valid; - } - - // Add a new collection to the inheritance list. - // We do not check if this collection would be visited before, - // only that it is unique in the list itself. - public bool AddInheritance( ModCollection collection, bool invokeEvent ) - { - if( CheckValidInheritance( collection ) != ValidInheritance.Valid ) - { - return false; - } - - _inheritance.Add( collection ); - // Changes in inherited collections may need to trigger further changes here. - collection.ModSettingChanged += OnInheritedModSettingChange; - collection.InheritanceChanged += OnInheritedInheritanceChange; - if( invokeEvent ) - { - InheritanceChanged.Invoke( false ); - } - - Penumbra.Log.Debug( $"Added {collection.AnonymizedName} to {AnonymizedName} inheritances." ); - return true; - } - - public void RemoveInheritance( int idx ) - { - var inheritance = _inheritance[ idx ]; - ClearSubscriptions( inheritance ); - _inheritance.RemoveAt( idx ); - InheritanceChanged.Invoke( false ); - Penumbra.Log.Debug( $"Removed {inheritance.AnonymizedName} from {AnonymizedName} inheritances." ); - } - - internal void ClearSubscriptions( ModCollection other ) - { - other.ModSettingChanged -= OnInheritedModSettingChange; - other.InheritanceChanged -= OnInheritedInheritanceChange; - } - - // Order in the inheritance list is relevant. - public void MoveInheritance( int from, int to ) - { - if( _inheritance.Move( from, to ) ) - { - InheritanceChanged.Invoke( false ); - Penumbra.Log.Debug( $"Moved {AnonymizedName}s inheritance {from} to {to}." ); - } - } - - // Carry changes in collections inherited from forward if they are relevant for this collection. - private void OnInheritedModSettingChange( ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool _ ) - { - switch( type ) - { - case ModSettingChange.MultiInheritance: - case ModSettingChange.MultiEnableState: - ModSettingChanged.Invoke( type, modIdx, oldValue, groupIdx, true ); - return; - default: - if( modIdx < 0 || modIdx >= _settings.Count ) - { - Penumbra.Log.Warning( - $"Collection state broken, Mod {modIdx} in inheritance does not exist. ({_settings.Count} mods exist)." ); - return; - } - - if( _settings[ modIdx ] == null ) - { - ModSettingChanged.Invoke( type, modIdx, oldValue, groupIdx, true ); - } - - return; - } - } - - private void OnInheritedInheritanceChange( bool _ ) - => InheritanceChanged.Invoke( true ); - - // Obtain the actual settings for a given mod via index. - // Also returns the collection the settings are taken from. - // If no collection provides settings for this mod, this collection is returned together with null. - public (ModSettings? Settings, ModCollection Collection) this[ Index idx ] - { - get - { - if( Index <= 0 ) - { - return ( ModSettings.Empty, this ); - } - - foreach( var collection in GetFlattenedInheritance() ) - { - var settings = collection._settings[ idx ]; - if( settings != null ) - { - return ( settings, collection ); - } - } - - return ( null, this ); - } - } -} \ No newline at end of file diff --git a/Penumbra/Collections/ModCollection.Migration.cs b/Penumbra/Collections/ModCollection.Migration.cs index ceaac70d..e03268a4 100644 --- a/Penumbra/Collections/ModCollection.Migration.cs +++ b/Penumbra/Collections/ModCollection.Migration.cs @@ -1,7 +1,6 @@ using Penumbra.Mods; using System.Collections.Generic; using System.Linq; -using Penumbra.Services; using Penumbra.Util; namespace Penumbra.Collections; diff --git a/Penumbra/Collections/ModCollection.cs b/Penumbra/Collections/ModCollection.cs index 67913760..363ee5e4 100644 --- a/Penumbra/Collections/ModCollection.cs +++ b/Penumbra/Collections/ModCollection.cs @@ -15,9 +15,9 @@ namespace Penumbra.Collections; // - any change in settings or inheritance of the collection causes a Save. public partial class ModCollection { - public const int CurrentVersion = 1; + public const int CurrentVersion = 1; public const string DefaultCollectionName = "Default"; - public const string EmptyCollection = "None"; + public const string EmptyCollection = "None"; public static readonly ModCollection Empty = CreateEmpty(); @@ -51,18 +51,18 @@ public partial class ModCollection => Enumerable.Range(0, _settings.Count).Select(i => this[i].Settings); // Settings for deleted mods will be kept via directory name. - private readonly Dictionary _unusedSettings; + internal readonly Dictionary _unusedSettings; // Constructor for duplication. private ModCollection(string name, ModCollection duplicate) { - Name = name; - Version = duplicate.Version; - _settings = duplicate._settings.ConvertAll(s => s?.DeepCopy()); - _unusedSettings = duplicate._unusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy()); - _inheritance = duplicate._inheritance.ToList(); - ModSettingChanged += SaveOnChange; - InheritanceChanged += SaveOnChange; + Name = name; + Version = duplicate.Version; + _settings = duplicate._settings.ConvertAll(s => s?.DeepCopy()); + _unusedSettings = duplicate._unusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy()); + DirectlyInheritsFrom = duplicate.DirectlyInheritsFrom.ToList(); + foreach (var c in DirectlyInheritsFrom) + ((List)c.DirectParentOf).Add(this); } // Constructor for reading from files. @@ -76,8 +76,6 @@ public partial class ModCollection ApplyModSettings(); Migration.Migrate(Penumbra.SaveService, this); - ModSettingChanged += SaveOnChange; - InheritanceChanged += SaveOnChange; } // Create a new, unique empty collection of a given name. @@ -87,11 +85,11 @@ public partial class ModCollection // Create a new temporary collection that does not save and has a negative index. public static ModCollection CreateNewTemporary(string name, int changeCounter) { - var collection = new ModCollection(name, Empty); - collection.ModSettingChanged -= collection.SaveOnChange; - collection.InheritanceChanged -= collection.SaveOnChange; - collection.Index = ~Penumbra.TempCollections.Count; - collection.ChangeCounter = changeCounter; + var collection = new ModCollection(name, Empty) + { + Index = ~Penumbra.TempCollections.Count, + ChangeCounter = changeCounter, + }; collection.CreateCache(false); return collection; } @@ -162,55 +160,43 @@ public partial class ModCollection Penumbra.SaveService.ImmediateSave(this); } - public bool CopyModSettings(int modIdx, string modName, int targetIdx, string targetName) - { - if (targetName.Length == 0 && targetIdx < 0 || modName.Length == 0 && modIdx < 0) - return false; - - // If the source mod exists, convert its settings to saved settings or null if its inheriting. - // If it does not exist, check unused settings. - // If it does not exist and has no unused settings, also use null. - ModSettings.SavedSettings? savedSettings = modIdx >= 0 - ? _settings[modIdx] != null - ? new ModSettings.SavedSettings(_settings[modIdx]!, Penumbra.ModManager[modIdx]) - : null - : _unusedSettings.TryGetValue(modName, out var s) - ? s - : null; - - if (targetIdx >= 0) - { - if (savedSettings != null) - { - // The target mod exists and the source settings are not inheriting, convert and fix the settings and copy them. - // This triggers multiple events. - savedSettings.Value.ToSettings(Penumbra.ModManager[targetIdx], out var settings); - SetModState(targetIdx, settings.Enabled); - SetModPriority(targetIdx, settings.Priority); - foreach (var (value, index) in settings.Settings.WithIndex()) - SetModSetting(targetIdx, index, value); - } - else - { - // The target mod exists, but the source is inheriting, set the target to inheriting. - // This triggers events. - SetModInheritance(targetIdx, true); - } - } - else - { - // The target mod does not exist. - // Either copy the unused source settings directly if they are not inheriting, - // or remove any unused settings for the target if they are inheriting. - if (savedSettings != null) - _unusedSettings[targetName] = savedSettings.Value; - else - _unusedSettings.Remove(targetName); - } - - return true; - } - public override string ToString() => Name; + + /// + /// Obtain the actual settings for a given mod via index. + /// Also returns the collection the settings are taken from. + /// If no collection provides settings for this mod, this collection is returned together with null. + /// + public (ModSettings? Settings, ModCollection Collection) this[Index idx] + { + get + { + if (Index <= 0) + return (ModSettings.Empty, this); + + foreach (var collection in GetFlattenedInheritance()) + { + var settings = collection._settings[idx]; + if (settings != null) + return (settings, collection); + } + + return (null, this); + } + } + + public readonly IReadOnlyList DirectlyInheritsFrom = new List(); + public readonly IReadOnlyList DirectParentOf = new List(); + + /// All inherited collections in application order without filtering for duplicates. + public static IEnumerable InheritedCollections(ModCollection collection) + => collection.DirectlyInheritsFrom.SelectMany(InheritedCollections).Prepend(collection); + + /// + /// Iterate over all collections inherited from in depth-first order. + /// Skip already visited collections to avoid circular dependencies. + /// + public IEnumerable GetFlattenedInheritance() + => InheritedCollections(this).Distinct(); } diff --git a/Penumbra/CommandHandler.cs b/Penumbra/CommandHandler.cs index 96acda5f..bdc16d75 100644 --- a/Penumbra/CommandHandler.cs +++ b/Penumbra/CommandHandler.cs @@ -8,7 +8,7 @@ using Dalamud.Game.Text.SeStringHandling; using ImGuiNET; using Penumbra.Api.Enums; using Penumbra.Collections; -using Penumbra.Collections.Manager; +using Penumbra.Collections.Manager; using Penumbra.GameData.Actors; using Penumbra.Interop.Services; using Penumbra.Mods; @@ -23,18 +23,20 @@ public class CommandHandler : IDisposable { private const string CommandName = "/penumbra"; - private readonly CommandManager _commandManager; - private readonly RedrawService _redrawService; - private readonly ChatGui _chat; - private readonly Configuration _config; - private readonly ConfigWindow _configWindow; - private readonly ActorManager _actors; - private readonly ModManager _modManager; + private readonly CommandManager _commandManager; + private readonly RedrawService _redrawService; + private readonly ChatGui _chat; + private readonly Configuration _config; + private readonly ConfigWindow _configWindow; + private readonly ActorManager _actors; + private readonly ModManager _modManager; private readonly CollectionManager _collectionManager; - private readonly Penumbra _penumbra; + private readonly Penumbra _penumbra; + private readonly CollectionEditor _collectionEditor; public CommandHandler(Framework framework, CommandManager commandManager, ChatGui chat, RedrawService redrawService, Configuration config, - ConfigWindow configWindow, ModManager modManager, CollectionManager collectionManager, ActorService actors, Penumbra penumbra) + ConfigWindow configWindow, ModManager modManager, CollectionManager collectionManager, ActorService actors, Penumbra penumbra, + CollectionEditor collectionEditor) { _commandManager = commandManager; _redrawService = redrawService; @@ -45,6 +47,7 @@ public class CommandHandler : IDisposable _actors = actors.AwaitedService; _chat = chat; _penumbra = penumbra; + _collectionEditor = collectionEditor; framework.RunOnFrameworkThread(() => { _commandManager.AddHandler(CommandName, new CommandInfo(OnCommand) @@ -455,14 +458,15 @@ public class CommandHandler : IDisposable collection = string.Equals(lowerName, ModCollection.Empty.Name, StringComparison.OrdinalIgnoreCase) ? ModCollection.Empty - : _collectionManager.Storage.ByName(lowerName, out var c) ? c : null; + : _collectionManager.Storage.ByName(lowerName, out var c) + ? c + : null; if (collection != null) return true; _chat.Print(new SeStringBuilder().AddText("The collection ").AddRed(collectionName, true).AddText(" does not exist.") .BuiltString); return false; - } private static bool? ParseTrueFalseToggle(string value) @@ -498,51 +502,47 @@ public class CommandHandler : IDisposable private bool HandleModState(int settingState, ModCollection collection, Mod mod) { - var settings = collection!.Settings[mod.Index]; + var settings = collection.Settings[mod.Index]; switch (settingState) { case 0: - if (collection.SetModState(mod.Index, true)) - { - Print(() => new SeStringBuilder().AddText("Enabled mod ").AddPurple(mod.Name, true).AddText(" in collection ") - .AddYellow(collection.Name, true) - .AddText(".").BuiltString); - return true; - } + if (!_collectionEditor.SetModState(collection, mod, true)) + return false; + + Print(() => new SeStringBuilder().AddText("Enabled mod ").AddPurple(mod.Name, true).AddText(" in collection ") + .AddYellow(collection.Name, true) + .AddText(".").BuiltString); + return true; - return false; case 1: - if (collection.SetModState(mod.Index, false)) - { - Print(() => new SeStringBuilder().AddText("Disabled mod ").AddPurple(mod.Name, true).AddText(" in collection ") - .AddYellow(collection.Name, true) - .AddText(".").BuiltString); - return true; - } + if (!_collectionEditor.SetModState(collection, mod, false)) + return false; + + Print(() => new SeStringBuilder().AddText("Disabled mod ").AddPurple(mod.Name, true).AddText(" in collection ") + .AddYellow(collection.Name, true) + .AddText(".").BuiltString); + return true; - return false; case 2: var setting = !(settings?.Enabled ?? false); - if (collection.SetModState(mod.Index, setting)) - { - Print(() => new SeStringBuilder().AddText(setting ? "Enabled mod " : "Disabled mod ").AddPurple(mod.Name, true) - .AddText(" in collection ") - .AddYellow(collection.Name, true) - .AddText(".").BuiltString); - return true; - } + if (!_collectionEditor.SetModState(collection, mod, setting)) + return false; + + Print(() => new SeStringBuilder().AddText(setting ? "Enabled mod " : "Disabled mod ").AddPurple(mod.Name, true) + .AddText(" in collection ") + .AddYellow(collection.Name, true) + .AddText(".").BuiltString); + return true; - return false; case 3: - if (collection.SetModInheritance(mod.Index, true)) - { - Print(() => new SeStringBuilder().AddText("Set mod ").AddPurple(mod.Name, true).AddText(" in collection ") - .AddYellow(collection.Name, true) - .AddText(" to inherit.").BuiltString); - return true; - } + if (!_collectionEditor.SetModInheritance(collection, mod, true)) + return false; + + Print(() => new SeStringBuilder().AddText("Set mod ").AddPurple(mod.Name, true).AddText(" in collection ") + .AddYellow(collection.Name, true) + .AddText(" to inherit.").BuiltString); + return true; - return false; } return false; diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 6dbcd3a8..dafac640 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -253,7 +253,7 @@ public class Penumbra : IDalamudPlugin void PrintCollection(ModCollection c) => sb.Append($"**Collection {c.AnonymizedName}**\n" - + $"> **`Inheritances: `** {c.Inheritance.Count}\n" + + $"> **`Inheritances: `** {c.DirectlyInheritsFrom.Count}\n" + $"> **`Enabled Mods: `** {c.ActualSettings.Count(s => s is { Enabled: true })}\n" + $"> **`Conflicts (Solved/Total): `** {c.AllConflicts.SelectMany(x => x).Sum(x => x.HasPriority && x.Solved ? x.Conflicts.Count : 0)}/{c.AllConflicts.SelectMany(x => x).Sum(x => x.HasPriority ? x.Conflicts.Count : 0)}\n"); diff --git a/Penumbra/PenumbraNew.cs b/Penumbra/PenumbraNew.cs index 58dfd7ac..ccc893d3 100644 --- a/Penumbra/PenumbraNew.cs +++ b/Penumbra/PenumbraNew.cs @@ -91,6 +91,7 @@ public class PenumbraNew .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton(); // Add Mod Services diff --git a/Penumbra/Services/CommunicatorService.cs b/Penumbra/Services/CommunicatorService.cs index 8a47ff40..f3a9a389 100644 --- a/Penumbra/Services/CommunicatorService.cs +++ b/Penumbra/Services/CommunicatorService.cs @@ -1,5 +1,7 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; +using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.Mods; @@ -171,6 +173,44 @@ public sealed class ModPathChanged : EventWrapper Invoke(this, changeType, mod, oldModDirectory, newModDirectory); } +/// +/// Triggered whenever a mod setting is changed. +/// +/// Parameter is the collection in which the setting was changed. +/// Parameter is the type of change. +/// Parameter is the mod the setting was changed for, unless it was a multi-change. +/// Parameter is the old value of the setting before the change as int. +/// Parameter is the index of the changed group if the change type is Setting. +/// Parameter is whether the change was inherited from another collection. +/// +/// +public sealed class ModSettingChanged : EventWrapper> +{ + public ModSettingChanged() + : base(nameof(ModSettingChanged)) + { } + + public void Invoke(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool inherited) + => Invoke(this, collection, type, mod, oldValue, groupIdx, inherited); +} + +/// +/// Triggered whenever a collections inheritances change. +/// +/// Parameter is the collection whose ancestors were changed. +/// Parameter is whether the change was itself inherited, i.e. if it happened in a direct parent (false) or a more removed ancestor (true). +/// +/// +public sealed class CollectionInheritanceChanged : EventWrapper> +{ + public CollectionInheritanceChanged() + : base(nameof(CollectionInheritanceChanged)) + { } + + public void Invoke(ModCollection collection, bool inherited) + => Invoke(this, collection, inherited); +} + public class CommunicatorService : IDisposable { /// @@ -179,30 +219,36 @@ public class CommunicatorService : IDisposable /// public readonly TemporaryGlobalModChange TemporaryGlobalModChange = new(); - /// + /// public readonly CreatingCharacterBase CreatingCharacterBase = new(); - /// + /// public readonly CreatedCharacterBase CreatedCharacterBase = new(); - /// + /// public readonly ModDataChanged ModDataChanged = new(); - /// + /// public readonly ModOptionChanged ModOptionChanged = new(); - /// + /// public readonly ModDiscoveryStarted ModDiscoveryStarted = new(); - /// + /// public readonly ModDiscoveryFinished ModDiscoveryFinished = new(); - /// + /// public readonly ModDirectoryChanged ModDirectoryChanged = new(); - /// + /// public readonly ModPathChanged ModPathChanged = new(); + /// + public readonly ModSettingChanged ModSettingChanged = new(); + + /// + public readonly CollectionInheritanceChanged CollectionInheritanceChanged = new(); + public void Dispose() { CollectionChange.Dispose(); @@ -215,5 +261,7 @@ public class CommunicatorService : IDisposable ModDiscoveryFinished.Dispose(); ModDirectoryChanged.Dispose(); ModPathChanged.Dispose(); + ModSettingChanged.Dispose(); + CollectionInheritanceChanged.Dispose(); } } diff --git a/Penumbra/Services/ConfigMigrationService.cs b/Penumbra/Services/ConfigMigrationService.cs index 422917a5..ddb6aba1 100644 --- a/Penumbra/Services/ConfigMigrationService.cs +++ b/Penumbra/Services/ConfigMigrationService.cs @@ -200,7 +200,7 @@ public class ConfigMigrationService if (jObject[nameof(ModCollection.Name)]?.ToObject() == ForcedCollection) continue; - jObject[nameof(ModCollection.Inheritance)] = JToken.FromObject(new List { ForcedCollection }); + jObject[nameof(ModCollection.DirectlyInheritsFrom)] = JToken.FromObject(new List { ForcedCollection }); File.WriteAllText(collection.FullName, jObject.ToString()); } catch (Exception e) diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index 57c52fd3..70c5cb75 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -57,7 +57,8 @@ public class ItemSwapTab : IDisposable, ITab }; _communicator.CollectionChange.Subscribe(OnCollectionChange); - _collectionManager.Active.Current.ModSettingChanged += OnSettingChange; + _communicator.ModSettingChanged.Subscribe(OnSettingChange); + _communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange); _communicator.ModOptionChanged.Subscribe(OnModOptionChange); } @@ -102,7 +103,8 @@ public class ItemSwapTab : IDisposable, ITab public void Dispose() { _communicator.CollectionChange.Unsubscribe(OnCollectionChange); - _collectionManager.Active.Current.ModSettingChanged -= OnSettingChange; + _communicator.ModSettingChanged.Unsubscribe(OnSettingChange); + _communicator.CollectionInheritanceChanged.Unsubscribe(OnInheritanceChange); _communicator.ModOptionChanged.Unsubscribe(OnModOptionChange); } @@ -744,21 +746,29 @@ public class ItemSwapTab : IDisposable, ITab if (collectionType != CollectionType.Current || _mod == null || newCollection == null) return; - UpdateMod(_mod, _mod.Index < newCollection.Settings.Count ? newCollection.Settings[_mod.Index] : null); - newCollection.ModSettingChanged += OnSettingChange; - if (oldCollection != null) - oldCollection.ModSettingChanged -= OnSettingChange; + UpdateMod(_mod, _mod.Index < newCollection.Settings.Count ? newCollection[_mod.Index].Settings : null); } - private void OnSettingChange(ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited) + private void OnSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool inherited) { - if (modIdx != _mod?.Index) + + if (collection != _collectionManager.Active.Current || mod != _mod) return; _swapData.LoadMod(_mod, _modSettings); _dirty = true; } + private void OnInheritanceChange(ModCollection collection, bool _) + { + if (collection != _collectionManager.Active.Current || _mod == null) + return; + + UpdateMod(_mod, collection[_mod.Index].Settings); + _swapData.LoadMod(_mod, _modSettings); + _dirty = true; + } + private void OnModOptionChange(ModOptionChangeType type, Mod mod, int a, int b, int c) { if (type is ModOptionChangeType.PrepareChange || mod != _mod) diff --git a/Penumbra/UI/CollectionTab/Collections.InheritanceUi.cs b/Penumbra/UI/CollectionTab/Collections.InheritanceUi.cs index 31044cae..6a43f5b2 100644 --- a/Penumbra/UI/CollectionTab/Collections.InheritanceUi.cs +++ b/Penumbra/UI/CollectionTab/Collections.InheritanceUi.cs @@ -17,7 +17,7 @@ public class InheritanceUi private const int InheritedCollectionHeight = 9; private const string InheritanceDragDropLabel = "##InheritanceMove"; - private readonly CollectionManager _collectionManager; + private readonly CollectionManager _collectionManager; public InheritanceUi(CollectionManager collectionManager) => _collectionManager = collectionManager; @@ -118,7 +118,7 @@ public class InheritanceUi _seenInheritedCollections.Clear(); _seenInheritedCollections.Add(_collectionManager.Active.Current); - foreach (var collection in _collectionManager.Active.Current.Inheritance.ToList()) + foreach (var collection in _collectionManager.Active.Current.DirectlyInheritsFrom.ToList()) DrawInheritance(collection); } @@ -136,7 +136,7 @@ public class InheritanceUi using var target = ImRaii.DragDropTarget(); if (target.Success && ImGuiUtil.IsDropping(InheritanceDragDropLabel)) - _inheritanceAction = (_collectionManager.Active.Current.Inheritance.IndexOf(_movedInheritance!), -1); + _inheritanceAction = (_collectionManager.Active.Current.DirectlyInheritsFrom.IndexOf(_movedInheritance!), -1); } /// @@ -157,9 +157,9 @@ public class InheritanceUi if (_inheritanceAction.Value.Item1 >= 0) { if (_inheritanceAction.Value.Item2 == -1) - _collectionManager.Active.Current.RemoveInheritance(_inheritanceAction.Value.Item1); + _collectionManager.Inheritances.RemoveInheritance(_collectionManager.Active.Current, _inheritanceAction.Value.Item1); else - _collectionManager.Active.Current.MoveInheritance(_inheritanceAction.Value.Item1, _inheritanceAction.Value.Item2); + _collectionManager.Inheritances.MoveInheritance(_collectionManager.Active.Current, _inheritanceAction.Value.Item1, _inheritanceAction.Value.Item2); } _inheritanceAction = null; @@ -173,22 +173,22 @@ public class InheritanceUi { DrawNewInheritanceCombo(); ImGui.SameLine(); - var inheritance = _collectionManager.Active.Current.CheckValidInheritance(_newInheritance); + var inheritance = InheritanceManager.CheckValidInheritance(_collectionManager.Active.Current, _newInheritance); var tt = inheritance switch { - ModCollection.ValidInheritance.Empty => "No valid collection to inherit from selected.", - ModCollection.ValidInheritance.Valid => $"Let the {TutorialService.SelectedCollection} inherit from this collection.", - ModCollection.ValidInheritance.Self => "The collection can not inherit from itself.", - ModCollection.ValidInheritance.Contained => "Already inheriting from this collection.", - ModCollection.ValidInheritance.Circle => "Inheriting from this collection would lead to cyclic inheritance.", + InheritanceManager.ValidInheritance.Empty => "No valid collection to inherit from selected.", + InheritanceManager.ValidInheritance.Valid => $"Let the {TutorialService.SelectedCollection} inherit from this collection.", + InheritanceManager.ValidInheritance.Self => "The collection can not inherit from itself.", + InheritanceManager.ValidInheritance.Contained => "Already inheriting from this collection.", + InheritanceManager.ValidInheritance.Circle => "Inheriting from this collection would lead to cyclic inheritance.", _ => string.Empty, }; if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), UiHelpers.IconButtonSize, tt, - inheritance != ModCollection.ValidInheritance.Valid, true) - && _collectionManager.Active.Current.AddInheritance(_newInheritance!, true)) + inheritance != InheritanceManager.ValidInheritance.Valid, true) + && _collectionManager.Inheritances.AddInheritance(_collectionManager.Active.Current, _newInheritance!)) _newInheritance = null; - if (inheritance != ModCollection.ValidInheritance.Valid) + if (inheritance != InheritanceManager.ValidInheritance.Valid) _newInheritance = null; ImGui.SameLine(); @@ -234,14 +234,14 @@ public class InheritanceUi { ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton); _newInheritance ??= _collectionManager.Storage.FirstOrDefault(c - => c != _collectionManager.Active.Current && !_collectionManager.Active.Current.Inheritance.Contains(c)) + => c != _collectionManager.Active.Current && !_collectionManager.Active.Current.DirectlyInheritsFrom.Contains(c)) ?? ModCollection.Empty; using var combo = ImRaii.Combo("##newInheritance", _newInheritance.Name); if (!combo) return; foreach (var collection in _collectionManager.Storage - .Where(c => _collectionManager.Active.Current.CheckValidInheritance(c) == ModCollection.ValidInheritance.Valid) + .Where(c => InheritanceManager.CheckValidInheritance(_collectionManager.Active.Current, c) == InheritanceManager.ValidInheritance.Valid) .OrderBy(c => c.Name)) { if (ImGui.Selectable(collection.Name, _newInheritance == collection)) @@ -261,8 +261,8 @@ public class InheritanceUi if (_movedInheritance != null) { - var idx1 = _collectionManager.Active.Current.Inheritance.IndexOf(_movedInheritance); - var idx2 = _collectionManager.Active.Current.Inheritance.IndexOf(collection); + var idx1 = _collectionManager.Active.Current.DirectlyInheritsFrom.IndexOf(_movedInheritance); + var idx2 = _collectionManager.Active.Current.DirectlyInheritsFrom.IndexOf(collection); if (idx1 >= 0 && idx2 >= 0) _inheritanceAction = (idx1, idx2); } @@ -292,7 +292,7 @@ public class InheritanceUi if (ImGui.GetIO().KeyCtrl && ImGui.IsItemClicked(ImGuiMouseButton.Right)) { if (withDelete && ImGui.GetIO().KeyShift) - _inheritanceAction = (_collectionManager.Active.Current.Inheritance.IndexOf(collection), -1); + _inheritanceAction = (_collectionManager.Active.Current.DirectlyInheritsFrom.IndexOf(collection), -1); else _newCurrentCollection = collection; } diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs index b6099c64..c33003e4 100644 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs @@ -73,9 +73,9 @@ public sealed class ModFileSystemSelector : FileSystemSelector @@ -360,11 +360,13 @@ public sealed class ModFileSystemSelector : FileSystemSelector @@ -146,7 +146,7 @@ public class ModPanelSettingsTab : ITab if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue) { if (_currentPriority != _settings.Priority) - _collectionManager.Active.Current.SetModPriority(_selector.Selected!.Index, _currentPriority.Value); + _collectionManager.Editor.SetModPriority(_collectionManager.Active.Current, _selector.Selected!, _currentPriority.Value); _currentPriority = null; } @@ -168,7 +168,7 @@ public class ModPanelSettingsTab : ITab var scroll = ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ScrollbarSize : 0; ImGui.SameLine(ImGui.GetWindowWidth() - ImGui.CalcTextSize(text).X - ImGui.GetStyle().FramePadding.X * 2 - scroll); if (ImGui.Button(text)) - _collectionManager.Active.Current.SetModInheritance(_selector.Selected!.Index, true); + _collectionManager.Editor.SetModInheritance(_collectionManager.Active.Current, _selector.Selected!, true); ImGuiUtil.HoverTooltip("Remove current settings from this collection so that it can inherit them.\n" + "If no inherited collection has settings for this mod, it will be disabled."); @@ -191,7 +191,7 @@ public class ModPanelSettingsTab : ITab id.Push(idx2); var option = group[idx2]; if (ImGui.Selectable(option.Name, idx2 == selectedOption)) - _collectionManager.Active.Current.SetModSetting(_selector.Selected!.Index, groupIdx, (uint)idx2); + _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, (uint)idx2); if (option.Description.Length > 0) { @@ -227,7 +227,7 @@ public class ModPanelSettingsTab : ITab { using var id = ImRaii.PushId(groupIdx); var selectedOption = _empty ? (int)group.DefaultSettings : (int)_settings.Settings[groupIdx]; - var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); + var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); void DrawOptions() { @@ -236,7 +236,7 @@ public class ModPanelSettingsTab : ITab using var i = ImRaii.PushId(idx); var option = group[idx]; if (ImGui.RadioButton(option.Name, selectedOption == idx)) - _collectionManager.Active.Current.SetModSetting(_selector.Selected!.Index, groupIdx, (uint)idx); + _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, (uint)idx); if (option.Description.Length <= 0) continue; @@ -264,7 +264,7 @@ public class ModPanelSettingsTab : ITab var shown = ImGui.GetStateStorage().GetBool(collapseId, true); var buttonTextShow = $"Show {group.Count} Options"; var buttonTextHide = $"Hide {group.Count} Options"; - var buttonWidth = Math.Max(ImGui.CalcTextSize(buttonTextShow).X, ImGui.CalcTextSize(buttonTextHide).X) + var buttonWidth = Math.Max(ImGui.CalcTextSize(buttonTextShow).X, ImGui.CalcTextSize(buttonTextHide).X) + 2 * ImGui.GetStyle().FramePadding.X; minWidth = Math.Max(buttonWidth, minWidth); if (shown) @@ -276,10 +276,9 @@ public class ModPanelSettingsTab : ITab draw(); } - - - var width = Math.Max(ImGui.GetItemRectSize().X, minWidth); - var endPos = ImGui.GetCursorPos(); + + var width = Math.Max(ImGui.GetItemRectSize().X, minWidth); + var endPos = ImGui.GetCursorPos(); ImGui.SetCursorPos(pos); if (ImGui.Button(buttonTextHide, new Vector2(width, 0))) ImGui.GetStateStorage().SetBool(collapseId, !shown); @@ -292,7 +291,7 @@ public class ModPanelSettingsTab : ITab + ImGui.GetStyle().ItemInnerSpacing.X + ImGui.GetFrameHeight() + ImGui.GetStyle().FramePadding.X; - var width = Math.Max(optionWidth, minWidth); + var width = Math.Max(optionWidth, minWidth); if (ImGui.Button(buttonTextShow, new Vector2(width, 0))) ImGui.GetStateStorage().SetBool(collapseId, !shown); } @@ -305,9 +304,9 @@ public class ModPanelSettingsTab : ITab /// private void DrawMultiGroup(IModGroup group, int groupIdx) { - using var id = ImRaii.PushId(groupIdx); - var flags = _empty ? group.DefaultSettings : _settings.Settings[groupIdx]; - var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); + using var id = ImRaii.PushId(groupIdx); + var flags = _empty ? group.DefaultSettings : _settings.Settings[groupIdx]; + var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); void DrawOptions() { @@ -321,7 +320,7 @@ public class ModPanelSettingsTab : ITab if (ImGui.Checkbox(option.Name, ref setting)) { flags = setting ? flags | flag : flags & ~flag; - _collectionManager.Active.Current.SetModSetting(_selector.Selected!.Index, groupIdx, flags); + _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, flags); } if (option.Description.Length > 0) @@ -349,10 +348,10 @@ public class ModPanelSettingsTab : ITab if (ImGui.Selectable("Enable All")) { flags = group.Count == 32 ? uint.MaxValue : (1u << group.Count) - 1u; - _collectionManager.Active.Current.SetModSetting(_selector.Selected!.Index, groupIdx, flags); + _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, flags); } if (ImGui.Selectable("Disable All")) - _collectionManager.Active.Current.SetModSetting(_selector.Selected!.Index, groupIdx, 0); + _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, 0); } } diff --git a/Penumbra/UI/Tabs/ModsTab.cs b/Penumbra/UI/Tabs/ModsTab.cs index b454fa3b..2437f96e 100644 --- a/Penumbra/UI/Tabs/ModsTab.cs +++ b/Penumbra/UI/Tabs/ModsTab.cs @@ -91,7 +91,7 @@ public class ModsTab : ITab + $"{_selector.SortMode.Name} Sort Mode\n" + $"{_selector.SelectedLeaf?.Name ?? "NULL"} Selected Leaf\n" + $"{_selector.Selected?.Name ?? "NULL"} Selected Mod\n" - + $"{string.Join(", ", _collectionManager.Active.Current.Inheritance.Select(c => c.AnonymizedName))} Inheritances\n" + + $"{string.Join(", ", _collectionManager.Active.Current.DirectlyInheritsFrom.Select(c => c.AnonymizedName))} Inheritances\n" + $"{_selector.SelectedSettingCollection.AnonymizedName} Collection\n"); } }