diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index da1eafd0..dc1e8472 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -27,6 +27,7 @@ using Penumbra.UI; using TextureType = Penumbra.Api.Enums.TextureType; using Penumbra.Interop.ResourceTree; using Penumbra.Mods.Editor; +using Penumbra.Mods.Subclasses; namespace Penumbra.Api; @@ -39,13 +40,13 @@ public class PenumbraApi : IDisposable, IPenumbraApi { add => _communicator.PreSettingsTabBarDraw.Subscribe(value!, Communication.PreSettingsTabBarDraw.Priority.Default); remove => _communicator.PreSettingsTabBarDraw.Unsubscribe(value!); - } + } public event Action? PreSettingsPanelDraw { add => _communicator.PreSettingsPanelDraw.Subscribe(value!, Communication.PreSettingsPanelDraw.Priority.Default); remove => _communicator.PreSettingsPanelDraw.Unsubscribe(value!); - } + } public event Action? PostEnabledDraw { @@ -649,7 +650,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi var shareSettings = settings.ConvertToShareable(mod); return (PenumbraApiEc.Success, - (shareSettings.Enabled, shareSettings.Priority, shareSettings.Settings, collection.Settings[mod.Index] != null)); + (shareSettings.Enabled, shareSettings.Priority.Value, shareSettings.Settings, collection.Settings[mod.Index] != null)); } public PenumbraApiEc ReloadMod(string modDirectory, string modName) @@ -791,7 +792,9 @@ public class PenumbraApi : IDisposable, IPenumbraApi if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) return PenumbraApiEc.ModMissing; - return _collectionEditor.SetModPriority(collection, mod, priority) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; + return _collectionEditor.SetModPriority(collection, mod, new ModPriority(priority)) + ? PenumbraApiEc.Success + : PenumbraApiEc.NothingChanged; } public PenumbraApiEc TrySetModSetting(string collectionName, string modDirectory, string modName, string optionGroupName, @@ -820,7 +823,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, "OptionName", optionName)); - var setting = mod.Groups[groupIdx].Type == GroupType.Multi ? 1u << optionIdx : (uint)optionIdx; + var setting = mod.Groups[groupIdx].Type switch + { + GroupType.Multi => Setting.Multi(optionIdx), + GroupType.Single => Setting.Single(optionIdx), + _ => Setting.Zero, + }; return Return( _collectionEditor.SetModSetting(collection, mod, groupIdx, setting) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged, @@ -850,7 +858,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi var group = mod.Groups[groupIdx]; - uint setting = 0; + var setting = Setting.Zero; if (group.Type == GroupType.Single) { var optionIdx = optionNames.Count == 0 ? -1 : group.IndexOf(o => o.Name == optionNames[^1]); @@ -859,7 +867,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, "#optionNames", optionNames.Count.ToString())); - setting = (uint)optionIdx; + setting = Setting.Single(optionIdx); } else { @@ -871,7 +879,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, "#optionNames", optionNames.Count.ToString())); - setting |= 1u << optionIdx; + setting |= Setting.Multi(optionIdx); } } @@ -993,7 +1001,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi if (!ConvertManips(manipString, out var m)) return PenumbraApiEc.InvalidManipulation; - return _tempMods.Register(tag, null, p, m, priority) switch + return _tempMods.Register(tag, null, p, m, new ModPriority(priority)) switch { RedirectResult.Success => PenumbraApiEc.Success, _ => PenumbraApiEc.UnknownError, @@ -1014,7 +1022,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi if (!ConvertManips(manipString, out var m)) return PenumbraApiEc.InvalidManipulation; - return _tempMods.Register(tag, collection, p, m, priority) switch + return _tempMods.Register(tag, collection, p, m, new ModPriority(priority)) switch { RedirectResult.Success => PenumbraApiEc.Success, _ => PenumbraApiEc.UnknownError, @@ -1024,7 +1032,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi public PenumbraApiEc RemoveTemporaryModAll(string tag, int priority) { CheckInitialized(); - return _tempMods.Unregister(tag, null, priority) switch + return _tempMods.Unregister(tag, null, new ModPriority(priority)) switch { RedirectResult.Success => PenumbraApiEc.Success, RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged, @@ -1039,7 +1047,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi && !_collectionManager.Storage.ByName(collectionName, out collection)) return PenumbraApiEc.CollectionMissing; - return _tempMods.Unregister(tag, collection, priority) switch + return _tempMods.Unregister(tag, collection, new ModPriority(priority)) switch { RedirectResult.Success => PenumbraApiEc.Success, RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged, @@ -1089,7 +1097,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi public IReadOnlyDictionary?[] GetGameObjectResourcePaths(ushort[] gameObjects) { - var characters = gameObjects.Select(index => _objects.GetDalamudObject((int) index)).OfType(); + var characters = gameObjects.Select(index => _objects.GetDalamudObject((int)index)).OfType(); var resourceTrees = _resourceTreeFactory.FromCharacters(characters, 0); var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees); @@ -1153,7 +1161,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi var collection = _tempCollections.Collections.TryGetCollection(identifier, out var c) ? c : _collectionManager.Active.Individual(identifier); - var set = collection.MetaCache?.Manipulations.ToArray() ?? Array.Empty(); + var set = collection.MetaCache?.Manipulations.ToArray() ?? []; return Functions.ToCompressedBase64(set, MetaManipulation.CurrentVersion); } @@ -1161,7 +1169,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi { CheckInitialized(); AssociatedCollection(gameObjectIdx, out var collection); - var set = collection.MetaCache?.Manipulations.ToArray() ?? Array.Empty(); + var set = collection.MetaCache?.Manipulations.ToArray() ?? []; return Functions.ToCompressedBase64(set, MetaManipulation.CurrentVersion); } @@ -1190,7 +1198,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private unsafe ActorIdentifier AssociatedIdentifier(int gameObjectIdx) + private ActorIdentifier AssociatedIdentifier(int gameObjectIdx) { if (gameObjectIdx < 0 || gameObjectIdx >= _objects.TotalCount) return ActorIdentifier.Invalid; @@ -1217,10 +1225,9 @@ public class PenumbraApi : IDisposable, IPenumbraApi CheckInitialized(); try { - if (Path.IsPathRooted(resolvedPath)) - return _lumina?.GetFileFromDisk(resolvedPath); - - return _gameData.GetFile(resolvedPath); + return Path.IsPathRooted(resolvedPath) + ? _lumina?.GetFileFromDisk(resolvedPath) + : _gameData.GetFile(resolvedPath); } catch (Exception e) { @@ -1295,7 +1302,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi return _actors.CreatePlayer(b, worldId); } - private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int _1, int _2, bool inherited) + private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting _1, int _2, bool inherited) => ModSettingChanged?.Invoke(type, collection.Name, mod?.ModPath.Name ?? string.Empty, inherited); private void OnCreatedCharacterBase(nint gameObject, ModCollection collection, nint drawObject) diff --git a/Penumbra/Api/TempModManager.cs b/Penumbra/Api/TempModManager.cs index c7840b75..7d682338 100644 --- a/Penumbra/Api/TempModManager.cs +++ b/Penumbra/Api/TempModManager.cs @@ -6,6 +6,7 @@ using Penumbra.Services; using Penumbra.String.Classes; using Penumbra.Collections.Manager; using Penumbra.Communication; +using Penumbra.Mods.Subclasses; namespace Penumbra.Api; @@ -21,8 +22,8 @@ public class TempModManager : IDisposable { private readonly CommunicatorService _communicator; - private readonly Dictionary> _mods = new(); - private readonly List _modsForAllCollections = new(); + private readonly Dictionary> _mods = []; + private readonly List _modsForAllCollections = []; public TempModManager(CommunicatorService communicator) { @@ -42,7 +43,7 @@ public class TempModManager : IDisposable => _modsForAllCollections; public RedirectResult Register(string tag, ModCollection? collection, Dictionary dict, - HashSet manips, int priority) + HashSet manips, ModPriority priority) { var mod = GetOrCreateMod(tag, collection, priority, out var created); Penumbra.Log.Verbose($"{(created ? "Created" : "Changed")} temporary Mod {mod.Name}."); @@ -51,10 +52,10 @@ public class TempModManager : IDisposable return RedirectResult.Success; } - public RedirectResult Unregister(string tag, ModCollection? collection, int? priority) + public RedirectResult Unregister(string tag, ModCollection? collection, ModPriority? priority) { Penumbra.Log.Verbose($"Removing temporary mod with tag {tag}..."); - var list = collection == null ? _modsForAllCollections : _mods.TryGetValue(collection, out var l) ? l : null; + var list = collection == null ? _modsForAllCollections : _mods.GetValueOrDefault(collection); if (list == null) return RedirectResult.NotRegistered; @@ -85,13 +86,13 @@ public class TempModManager : IDisposable { Penumbra.Log.Verbose($"Removing temporary Mod {mod.Name} from {collection.AnonymizedName}."); collection.Remove(mod); - _communicator.ModSettingChanged.Invoke(collection, ModSettingChange.TemporaryMod, null, 0, 0, false); + _communicator.ModSettingChanged.Invoke(collection, ModSettingChange.TemporaryMod, null, Setting.False, 0, false); } else { Penumbra.Log.Verbose($"Adding {(created ? "new " : string.Empty)}temporary Mod {mod.Name} to {collection.AnonymizedName}."); collection.Apply(mod, created); - _communicator.ModSettingChanged.Invoke(collection, ModSettingChange.TemporaryMod, null, 1, 0, false); + _communicator.ModSettingChanged.Invoke(collection, ModSettingChange.TemporaryMod, null, Setting.True, 0, false); } } else @@ -116,7 +117,7 @@ public class TempModManager : IDisposable // Find or create a mod with the given tag as name and the given priority, for the given collection (or all collections). // Returns the found or created mod and whether it was newly created. - private TemporaryMod GetOrCreateMod(string tag, ModCollection? collection, int priority, out bool created) + private TemporaryMod GetOrCreateMod(string tag, ModCollection? collection, ModPriority priority, out bool created) { List list; if (collection == null) @@ -129,14 +130,14 @@ public class TempModManager : IDisposable } else { - list = new List(); + list = []; _mods.Add(collection, list); } var mod = list.Find(m => m.Priority == priority && m.Name == tag); if (mod == null) { - mod = new TemporaryMod() + mod = new TemporaryMod { Name = tag, Priority = priority, diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs index 6b2b688b..72f0fb59 100644 --- a/Penumbra/Collections/Cache/CollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -237,7 +237,7 @@ public sealed class CollectionCache : IDisposable if (settings is not { Enabled: true }) return; - foreach (var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending(g => g.Item1.Priority)) + foreach (var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending(g => g.Value.Priority)) { if (group.Count == 0) continue; @@ -246,13 +246,13 @@ public sealed class CollectionCache : IDisposable switch (group.Type) { case GroupType.Single: - AddSubMod(group[(int)config], mod); + AddSubMod(group[config.AsIndex], mod); break; case GroupType.Multi: { foreach (var (option, _) in group.WithIndex() - .Where(p => ((1 << p.Item2) & config) != 0) - .OrderByDescending(p => group.OptionPriority(p.Item2))) + .Where(p => config.HasFlag(p.Index)) + .OrderByDescending(p => group.OptionPriority(p.Index))) AddSubMod(option, mod); break; diff --git a/Penumbra/Collections/Cache/CollectionCacheManager.cs b/Penumbra/Collections/Cache/CollectionCacheManager.cs index 5a6b5593..f6c6e14a 100644 --- a/Penumbra/Collections/Cache/CollectionCacheManager.cs +++ b/Penumbra/Collections/Cache/CollectionCacheManager.cs @@ -8,6 +8,7 @@ using Penumbra.Interop.ResourceLoading; using Penumbra.Meta; using Penumbra.Mods; using Penumbra.Mods.Manager; +using Penumbra.Mods.Subclasses; using Penumbra.Services; using Penumbra.String.Classes; @@ -288,7 +289,7 @@ public class CollectionCacheManager : IDisposable MetaFileManager.CharacterUtility.LoadingFinished -= IncrementCounters; } - private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool _) + private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx, bool _) { if (!collection.HasCache) return; @@ -300,9 +301,9 @@ public class CollectionCacheManager : IDisposable cache.ReloadMod(mod!, true); break; case ModSettingChange.EnableState: - if (oldValue == 0) + if (oldValue == Setting.False) cache.AddMod(mod!, true); - else if (oldValue == 1) + else if (oldValue == Setting.True) cache.RemoveMod(mod!, true); else if (collection[mod!.Index].Settings?.Enabled == true) cache.ReloadMod(mod!, true); diff --git a/Penumbra/Collections/Manager/CollectionEditor.cs b/Penumbra/Collections/Manager/CollectionEditor.cs index 73950942..4af19e6b 100644 --- a/Penumbra/Collections/Manager/CollectionEditor.cs +++ b/Penumbra/Collections/Manager/CollectionEditor.cs @@ -7,26 +7,15 @@ using Penumbra.Services; namespace Penumbra.Collections.Manager; -public class CollectionEditor +public class CollectionEditor(SaveService saveService, CommunicatorService communicator, ModStorage modStorage) { - private readonly CommunicatorService _communicator; - private readonly SaveService _saveService; - private readonly ModStorage _modStorage; - - public CollectionEditor(SaveService saveService, CommunicatorService communicator, ModStorage modStorage) - { - _saveService = saveService; - _communicator = communicator; - _modStorage = modStorage; - } - /// 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); + InvokeChange(collection, ModSettingChange.Inheritance, mod, inherit ? Setting.False : Setting.True, 0); return true; } @@ -42,7 +31,8 @@ public class CollectionEditor var inheritance = FixInheritance(collection, mod, false); ((List)collection.Settings)[mod.Index]!.Enabled = newValue; - InvokeChange(collection, ModSettingChange.EnableState, mod, inheritance ? -1 : newValue ? 0 : 1, 0); + InvokeChange(collection, ModSettingChange.EnableState, mod, inheritance ? Setting.Indefinite : newValue ? Setting.False : Setting.True, + 0); return true; } @@ -52,7 +42,7 @@ public class CollectionEditor if (!mods.Aggregate(false, (current, mod) => current | FixInheritance(collection, mod, inherit))) return; - InvokeChange(collection, ModSettingChange.MultiInheritance, null, -1, 0); + InvokeChange(collection, ModSettingChange.MultiInheritance, null, Setting.Indefinite, 0); } /// @@ -76,22 +66,22 @@ public class CollectionEditor if (!changes) return; - InvokeChange(collection, ModSettingChange.MultiEnableState, null, -1, 0); + InvokeChange(collection, ModSettingChange.MultiEnableState, null, Setting.Indefinite, 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) + public bool SetModPriority(ModCollection collection, Mod mod, ModPriority newValue) { - var oldValue = collection.Settings[mod.Index]?.Priority ?? collection[mod.Index].Settings?.Priority ?? 0; + var oldValue = collection.Settings[mod.Index]?.Priority ?? collection[mod.Index].Settings?.Priority ?? ModPriority.Default; if (newValue == oldValue) return false; var inheritance = FixInheritance(collection, mod, false); ((List)collection.Settings)[mod.Index]!.Priority = newValue; - InvokeChange(collection, ModSettingChange.Priority, mod, inheritance ? -1 : oldValue, 0); + InvokeChange(collection, ModSettingChange.Priority, mod, inheritance ? Setting.Indefinite : oldValue.AsSetting, 0); return true; } @@ -99,7 +89,7 @@ public class CollectionEditor /// 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) + public bool SetModSetting(ModCollection collection, Mod mod, int groupIdx, Setting newValue) { var settings = collection.Settings[mod.Index] != null ? collection.Settings[mod.Index]!.Settings @@ -110,7 +100,7 @@ public class CollectionEditor var inheritance = FixInheritance(collection, mod, false); ((List)collection.Settings)[mod.Index]!.SetValue(mod, groupIdx, newValue); - InvokeChange(collection, ModSettingChange.Setting, mod, inheritance ? -1 : (int)oldValue, groupIdx); + InvokeChange(collection, ModSettingChange.Setting, mod, inheritance ? Setting.Indefinite : oldValue, groupIdx); return true; } @@ -158,35 +148,17 @@ public class CollectionEditor if (savedSettings != null) { ((Dictionary)collection.UnusedSettings)[targetName] = savedSettings.Value; - _saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); + saveService.QueueSave(new ModCollectionSave(modStorage, collection)); } else if (((Dictionary)collection.UnusedSettings).Remove(targetName)) { - _saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); + saveService.QueueSave(new ModCollectionSave(modStorage, collection)); } } 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. @@ -204,16 +176,16 @@ public class CollectionEditor /// 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) + private void InvokeChange(ModCollection changedCollection, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx) { - _saveService.QueueSave(new ModCollectionSave(_modStorage, changedCollection)); - _communicator.ModSettingChanged.Invoke(changedCollection, type, mod, oldValue, groupIdx, false); + saveService.QueueSave(new ModCollectionSave(modStorage, 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) + private void RecurseInheritors(ModCollection directParent, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx) { foreach (var directInheritor in directParent.DirectParentOf) { @@ -221,11 +193,11 @@ public class CollectionEditor { case ModSettingChange.MultiInheritance: case ModSettingChange.MultiEnableState: - _communicator.ModSettingChanged.Invoke(directInheritor, type, null, oldValue, groupIdx, true); + 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); + communicator.ModSettingChanged.Invoke(directInheritor, type, mod, oldValue, groupIdx, true); break; } diff --git a/Penumbra/Collections/Manager/CollectionStorage.cs b/Penumbra/Collections/Manager/CollectionStorage.cs index 0ee55376..d0b61e57 100644 --- a/Penumbra/Collections/Manager/CollectionStorage.cs +++ b/Penumbra/Collections/Manager/CollectionStorage.cs @@ -268,7 +268,7 @@ public class CollectionStorage : IReadOnlyList, IDisposable case ModPathChangeType.Reloaded: foreach (var collection in this) { - if (collection.Settings[mod.Index]?.FixAllSettings(mod) ?? false) + if (collection.Settings[mod.Index]?.Settings.FixAll(mod) ?? false) _saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); } diff --git a/Penumbra/Collections/Manager/ModCollectionMigration.cs b/Penumbra/Collections/Manager/ModCollectionMigration.cs index b2b8df0d..053f0a2b 100644 --- a/Penumbra/Collections/Manager/ModCollectionMigration.cs +++ b/Penumbra/Collections/Manager/ModCollectionMigration.cs @@ -40,9 +40,9 @@ internal static class ModCollectionMigration /// We treat every completely defaulted setting as inheritance-ready. private static bool SettingIsDefaultV0(ModSettings.SavedSettings setting) - => setting is { Enabled: false, Priority: 0 } && setting.Settings.Values.All(s => s == 0); + => setting is { Enabled: true, Priority.IsDefault: true } && setting.Settings.Values.All(s => s == Setting.Zero); /// private static bool SettingIsDefaultV0(ModSettings? setting) - => setting is { Enabled: false, Priority: 0 } && setting.Settings.All(s => s == 0); + => setting is { Enabled: true, Priority.IsDefault: true } && setting.Settings.All(s => s == Setting.Zero); } diff --git a/Penumbra/Communication/ModSettingChanged.cs b/Penumbra/Communication/ModSettingChanged.cs index 5e0bc0c0..412b3003 100644 --- a/Penumbra/Communication/ModSettingChanged.cs +++ b/Penumbra/Communication/ModSettingChanged.cs @@ -3,6 +3,7 @@ using Penumbra.Api; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Mods; +using Penumbra.Mods.Subclasses; namespace Penumbra.Communication; @@ -12,13 +13,13 @@ namespace Penumbra.Communication; /// 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 old value of the setting before the change as Setting. /// 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(nameof(ModSettingChanged)) + : EventWrapper(nameof(ModSettingChanged)) { public enum Priority { diff --git a/Penumbra/Import/TexToolsImporter.ModPack.cs b/Penumbra/Import/TexToolsImporter.ModPack.cs index 7c4b94d8..7a247a53 100644 --- a/Penumbra/Import/TexToolsImporter.ModPack.cs +++ b/Penumbra/Import/TexToolsImporter.ModPack.cs @@ -174,7 +174,7 @@ public partial class TexToolsImporter ?? new DirectoryInfo(Path.Combine(_currentModDirectory.FullName, numGroups == 1 ? $"Group {groupPriority + 1}" : $"Group {groupPriority + 1}, Part {groupId + 1}")); - uint? defaultSettings = group.SelectionType == GroupType.Multi ? 0u : null; + Setting? defaultSettings = group.SelectionType == GroupType.Multi ? Setting.Zero : null; for (var i = 0; i + optionIdx < allOptions.Count && i < maxOptions; ++i) { var option = allOptions[i + optionIdx]; @@ -186,8 +186,8 @@ public partial class TexToolsImporter options.Add(_modManager.Creator.CreateSubMod(_currentModDirectory, optionFolder, option)); if (option.IsChecked) defaultSettings = group.SelectionType == GroupType.Multi - ? defaultSettings!.Value | (1u << i) - : (uint)i; + ? defaultSettings!.Value | Setting.Multi(i) + : Setting.Single(i); ++_currentOptionIdx; } @@ -205,12 +205,12 @@ public partial class TexToolsImporter _currentOptionName = option.Name; options.Insert(idx, ModCreator.CreateEmptySubMod(option.Name)); if (option.IsChecked) - defaultSettings = (uint) idx; + defaultSettings = Setting.Single(idx); } } _modManager.Creator.CreateOptionGroup(_currentModDirectory, group.SelectionType, name, groupPriority, groupPriority, - defaultSettings ?? 0, group.Description, options); + defaultSettings ?? Setting.Zero, group.Description, options); ++groupPriority; } } diff --git a/Penumbra/Interop/PathResolving/CollectionResolver.cs b/Penumbra/Interop/PathResolving/CollectionResolver.cs index fa122e39..aea58304 100644 --- a/Penumbra/Interop/PathResolving/CollectionResolver.cs +++ b/Penumbra/Interop/PathResolving/CollectionResolver.cs @@ -18,6 +18,7 @@ public sealed unsafe class CollectionResolver( PerformanceTracker performance, IdentifiedCollectionCache cache, IClientState clientState, + ObjectManager objects, IGameGui gameGui, ActorManager actors, CutsceneService cutscenes, @@ -35,8 +36,8 @@ public sealed unsafe class CollectionResolver( public ModCollection PlayerCollection() { using var performance1 = performance.Measure(PerformanceType.IdentifyCollection); - var gameObject = (GameObject*)(clientState.LocalPlayer?.Address ?? nint.Zero); - if (gameObject == null) + var gameObject = objects[0]; + if (!gameObject.Valid) return collectionManager.Active.ByType(CollectionType.Yourself) ?? collectionManager.Active.Default; diff --git a/Penumbra/Mods/Editor/IMod.cs b/Penumbra/Mods/Editor/IMod.cs index 78250341..d3bc19b0 100644 --- a/Penumbra/Mods/Editor/IMod.cs +++ b/Penumbra/Mods/Editor/IMod.cs @@ -7,8 +7,8 @@ public interface IMod { LowerString Name { get; } - public int Index { get; } - public int Priority { get; } + public int Index { get; } + public ModPriority Priority { get; } public ISubMod Default { get; } public IReadOnlyList Groups { get; } diff --git a/Penumbra/Mods/Manager/ModOptionEditor.cs b/Penumbra/Mods/Manager/ModOptionEditor.cs index 60508d33..ea6a62df 100644 --- a/Penumbra/Mods/Manager/ModOptionEditor.cs +++ b/Penumbra/Mods/Manager/ModOptionEditor.cs @@ -1,3 +1,4 @@ +using System.Security.AccessControl; using Dalamud.Interface.Internal.Notifications; using OtterGui; using OtterGui.Classes; @@ -33,6 +34,8 @@ public enum ModOptionChangeType public class ModOptionEditor(CommunicatorService communicator, SaveService saveService, Configuration config) { + + /// Change the type of a group given by mod and index to type, if possible. public void ChangeModGroupType(Mod mod, int groupIdx, GroupType type) { @@ -46,7 +49,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS } /// Change the settings stored as default options in a mod. - public void ChangeModGroupDefaultOption(Mod mod, int groupIdx, uint defaultOption) + public void ChangeModGroupDefaultOption(Mod mod, int groupIdx, Setting defaultOption) { var group = mod.Groups[groupIdx]; if (group.DefaultSettings == defaultOption) diff --git a/Penumbra/Mods/Mod.cs b/Penumbra/Mods/Mod.cs index c5e671af..b7d1186d 100644 --- a/Penumbra/Mods/Mod.cs +++ b/Penumbra/Mods/Mod.cs @@ -12,7 +12,7 @@ public sealed class Mod : IMod { Name = "Forced Files", Index = -1, - Priority = int.MaxValue, + Priority = ModPriority.MaxValue, }; // Main Data @@ -26,9 +26,9 @@ public sealed class Mod : IMod public bool IsTemporary => Index < 0; - /// Unused if Index < 0 but used for special temporary mods. - public int Priority - => 0; + /// Unused if Index is less than 0 but used for special temporary mods. + public ModPriority Priority + => ModPriority.Default; internal Mod(DirectoryInfo modPath) { diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index 042c98b4..c324af48 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -235,7 +235,7 @@ public partial class ModCreator(SaveService _saveService, Configuration config, /// Create a file for an option group from given data. public void CreateOptionGroup(DirectoryInfo baseFolder, GroupType type, string name, - int priority, int index, uint defaultSettings, string desc, IEnumerable subMods) + int priority, int index, Setting defaultSettings, string desc, IEnumerable subMods) { switch (type) { diff --git a/Penumbra/Mods/Subclasses/IModGroup.cs b/Penumbra/Mods/Subclasses/IModGroup.cs index ea5f176c..2f6b2403 100644 --- a/Penumbra/Mods/Subclasses/IModGroup.cs +++ b/Penumbra/Mods/Subclasses/IModGroup.cs @@ -12,7 +12,7 @@ public interface IModGroup : IEnumerable public string Description { get; } public GroupType Type { get; } public int Priority { get; } - public uint DefaultSettings { get; set; } + public Setting DefaultSettings { get; set; } public int OptionPriority(Index optionIdx); @@ -31,6 +31,9 @@ public interface IModGroup : IEnumerable public IModGroup Convert(GroupType type); public bool MoveOption(int optionIdxFrom, int optionIdxTo); public void UpdatePositions(int from = 0); + + /// Ensure that a value is valid for a group. + public Setting FixSetting(Setting setting); } public readonly struct ModSaveGroup : ISavable @@ -87,7 +90,7 @@ public readonly struct ModSaveGroup : ISavable j.WritePropertyName(nameof(Type)); j.WriteValue(_group.Type.ToString()); j.WritePropertyName(nameof(_group.DefaultSettings)); - j.WriteValue(_group.DefaultSettings); + j.WriteValue(_group.DefaultSettings.Value); j.WritePropertyName("Options"); j.WriteStartArray(); for (var idx = 0; idx < _group.Count; ++idx) diff --git a/Penumbra/Mods/Subclasses/ModPriority.cs b/Penumbra/Mods/Subclasses/ModPriority.cs new file mode 100644 index 00000000..3302c627 --- /dev/null +++ b/Penumbra/Mods/Subclasses/ModPriority.cs @@ -0,0 +1,61 @@ +using Newtonsoft.Json; + +namespace Penumbra.Mods.Subclasses; + +[JsonConverter(typeof(Converter))] +public readonly record struct ModPriority(int Value) : + IComparisonOperators, + IAdditionOperators, + IAdditionOperators, + ISubtractionOperators, + ISubtractionOperators +{ + public static readonly ModPriority Default = new(0); + public static readonly ModPriority MaxValue = new(int.MaxValue); + + public bool IsDefault + => Value == Default.Value; + + public Setting AsSetting + => new((uint)Value); + + public ModPriority Max(ModPriority other) + => this < other ? other : this; + + public override string ToString() + => Value.ToString(); + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, ModPriority value, JsonSerializer serializer) + => serializer.Serialize(writer, value.Value); + + public override ModPriority ReadJson(JsonReader reader, Type objectType, ModPriority existingValue, bool hasExistingValue, + JsonSerializer serializer) + => new(serializer.Deserialize(reader)); + } + + public static bool operator >(ModPriority left, ModPriority right) + => left.Value > right.Value; + + public static bool operator >=(ModPriority left, ModPriority right) + => left.Value >= right.Value; + + public static bool operator <(ModPriority left, ModPriority right) + => left.Value < right.Value; + + public static bool operator <=(ModPriority left, ModPriority right) + => left.Value <= right.Value; + + public static ModPriority operator +(ModPriority left, ModPriority right) + => new(left.Value + right.Value); + + public static ModPriority operator +(ModPriority left, int right) + => new(left.Value + right); + + public static ModPriority operator -(ModPriority left, ModPriority right) + => new(left.Value - right.Value); + + public static ModPriority operator -(ModPriority left, int right) + => new(left.Value - right); +} diff --git a/Penumbra/Mods/Subclasses/ModSettings.cs b/Penumbra/Mods/Subclasses/ModSettings.cs index ed8ad84e..b79b3242 100644 --- a/Penumbra/Mods/Subclasses/ModSettings.cs +++ b/Penumbra/Mods/Subclasses/ModSettings.cs @@ -11,8 +11,8 @@ namespace Penumbra.Mods.Subclasses; public class ModSettings { public static readonly ModSettings Empty = new(); - public List Settings { get; private init; } = []; - public int Priority { get; set; } + public SettingList Settings { get; private init; } = []; + public ModPriority Priority { get; set; } public bool Enabled { get; set; } // Create an independent copy of the current settings. @@ -21,7 +21,7 @@ public class ModSettings { Enabled = Enabled, Priority = Priority, - Settings = [.. Settings], + Settings = Settings.Clone(), }; // Create default settings for a given mod. @@ -29,8 +29,8 @@ public class ModSettings => new() { Enabled = false, - Priority = 0, - Settings = mod.Groups.Select(g => g.DefaultSettings).ToList(), + Priority = ModPriority.Default, + Settings = SettingList.Default(mod), }; // Return everything required to resolve things for a single mod with given settings (which can be null, in which case the default is used. @@ -39,7 +39,7 @@ public class ModSettings if (settings == null) settings = DefaultSettings(mod); else - settings.AddMissingSettings(mod); + settings.Settings.FixSize(mod); var dict = new Dictionary(); var set = new HashSet(); @@ -49,13 +49,13 @@ public class ModSettings if (group.Type is GroupType.Single) { if (group.Count > 0) - AddOption(group[(int)settings.Settings[index]]); + AddOption(group[settings.Settings[index].AsIndex]); } else { foreach (var (option, optionIdx) in group.WithIndex().OrderByDescending(o => group.OptionPriority(o.Index))) { - if (((settings.Settings[index] >> optionIdx) & 1) == 1) + if (settings.Settings[index].HasFlag(optionIdx)) AddOption(option); } } @@ -97,8 +97,8 @@ public class ModSettings var config = Settings[groupIdx]; Settings[groupIdx] = group.Type switch { - GroupType.Single => (uint)Math.Max(Math.Min(group.Count - 1, BitOperations.TrailingZeroCount(config)), 0), - GroupType.Multi => 1u << (int)config, + GroupType.Single => config.TurnMulti(group.Count), + GroupType.Multi => Setting.Multi((int)config.Value), _ => config, }; return config != Settings[groupIdx]; @@ -111,9 +111,11 @@ public class ModSettings var config = Settings[groupIdx]; Settings[groupIdx] = group.Type switch { - GroupType.Single => config >= optionIdx ? config > 1 ? config - 1 : 0 : config, - GroupType.Multi => Functions.RemoveBit(config, optionIdx), - _ => config, + GroupType.Single => config.AsIndex >= optionIdx + ? config.AsIndex > 1 ? Setting.Single(config.AsIndex - 1) : Setting.Zero + : config, + GroupType.Multi => config.RemoveBit(optionIdx), + _ => config, }; return config != Settings[groupIdx]; } @@ -128,8 +130,8 @@ public class ModSettings var config = Settings[groupIdx]; Settings[groupIdx] = group.Type switch { - GroupType.Single => config == optionIdx ? (uint)movedToIdx : config, - GroupType.Multi => Functions.MoveBit(config, optionIdx, movedToIdx), + GroupType.Single => config.AsIndex == optionIdx ? Setting.Single(movedToIdx) : config, + GroupType.Multi => config.MoveBit(optionIdx, movedToIdx), _ => config, }; return config != Settings[groupIdx]; @@ -138,96 +140,52 @@ public class ModSettings } } - public bool FixAllSettings(Mod mod) + /// Set a setting. Ensures that there are enough settings and fixes the setting beforehand. + public void SetValue(Mod mod, int groupIdx, Setting newValue) { - var ret = false; - for (var i = 0; i < Settings.Count; ++i) - { - var newValue = FixSetting(mod.Groups[i], Settings[i]); - if (newValue != Settings[i]) - { - ret = true; - Settings[i] = newValue; - } - } - - return AddMissingSettings(mod) || ret; - } - - // Ensure that a value is valid for a group. - private static uint FixSetting(IModGroup group, uint value) - => group.Type switch - { - GroupType.Single => (uint)Math.Min(value, group.Count - 1), - GroupType.Multi => (uint)(value & ((1ul << group.Count) - 1)), - _ => value, - }; - - // Set a setting. Ensures that there are enough settings and fixes the setting beforehand. - public void SetValue(Mod mod, int groupIdx, uint newValue) - { - AddMissingSettings(mod); + Settings.FixSize(mod); var group = mod.Groups[groupIdx]; - Settings[groupIdx] = FixSetting(group, newValue); - } - - // Add defaulted settings up to the required count. - private bool AddMissingSettings(Mod mod) - { - var changes = false; - for (var i = Settings.Count; i < mod.Groups.Count; ++i) - { - Settings.Add(mod.Groups[i].DefaultSettings); - changes = true; - } - - return changes; + Settings[groupIdx] = group.FixSetting(newValue); } // A simple struct conversion to easily save settings by name instead of value. public struct SavedSettings { - public Dictionary Settings; - public int Priority; - public bool Enabled; + public Dictionary Settings; + public ModPriority Priority; + public bool Enabled; public SavedSettings DeepCopy() - => new() - { - Enabled = Enabled, - Priority = Priority, - Settings = Settings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), - }; + => this with { Settings = Settings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) }; public SavedSettings(ModSettings settings, Mod mod) { Priority = settings.Priority; Enabled = settings.Enabled; - Settings = new Dictionary(mod.Groups.Count); - settings.AddMissingSettings(mod); + Settings = new Dictionary(mod.Groups.Count); + settings.Settings.FixSize(mod); foreach (var (group, setting) in mod.Groups.Zip(settings.Settings)) Settings.Add(group.Name, setting); } // Convert and fix. - public bool ToSettings(Mod mod, out ModSettings settings) + public readonly bool ToSettings(Mod mod, out ModSettings settings) { - var list = new List(mod.Groups.Count); + var list = new SettingList(mod.Groups.Count); var changes = Settings.Count != mod.Groups.Count; foreach (var group in mod.Groups) { if (Settings.TryGetValue(group.Name, out var config)) { - var castConfig = (uint)Math.Clamp(config, 0, uint.MaxValue); - var actualConfig = FixSetting(group, castConfig); + var actualConfig = group.FixSetting(config); list.Add(actualConfig); if (actualConfig != config) changes = true; } else { - list.Add(0); + list.Add(group.DefaultSettings); changes = true; } } @@ -245,7 +203,7 @@ public class ModSettings // Return the settings for a given mod in a shareable format, using the names of groups and options instead of indices. // Does not repair settings but ignores settings not fitting to the given mod. - public (bool Enabled, int Priority, Dictionary> Settings) ConvertToShareable(Mod mod) + public (bool Enabled, ModPriority Priority, Dictionary> Settings) ConvertToShareable(Mod mod) { var dict = new Dictionary>(Settings.Count); foreach (var (setting, idx) in Settings.WithIndex()) @@ -254,16 +212,13 @@ public class ModSettings break; var group = mod.Groups[idx]; - if (group.Type == GroupType.Single && setting < group.Count) + if (group.Type == GroupType.Single && setting.Value < (ulong)group.Count) { - dict.Add(group.Name, new[] - { - group[(int)setting].Name, - }); + dict.Add(group.Name, [group[(int)setting.Value].Name]); } else { - var list = group.Where((_, optionIdx) => (setting & (1 << optionIdx)) != 0).Select(o => o.Name).ToList(); + var list = group.Where((_, optionIdx) => (setting.Value & (1ul << optionIdx)) != 0).Select(o => o.Name).ToList(); dict.Add(group.Name, list); } } diff --git a/Penumbra/Mods/Subclasses/MultiModGroup.cs b/Penumbra/Mods/Subclasses/MultiModGroup.cs index 07f84722..8a8e10bd 100644 --- a/Penumbra/Mods/Subclasses/MultiModGroup.cs +++ b/Penumbra/Mods/Subclasses/MultiModGroup.cs @@ -14,10 +14,10 @@ public sealed class MultiModGroup : IModGroup public GroupType Type => GroupType.Multi; - public string Name { get; set; } = "Group"; - public string Description { get; set; } = "A non-exclusive group of settings."; - public int Priority { get; set; } - public uint DefaultSettings { get; set; } + public string Name { get; set; } = "Group"; + public string Description { get; set; } = "A non-exclusive group of settings."; + public int Priority { get; set; } + public Setting DefaultSettings { get; set; } public int OptionPriority(Index idx) => PrioritizedOptions[idx].Priority; @@ -44,7 +44,7 @@ public sealed class MultiModGroup : IModGroup Name = json[nameof(Name)]?.ToObject() ?? string.Empty, Description = json[nameof(Description)]?.ToObject() ?? string.Empty, Priority = json[nameof(Priority)]?.ToObject() ?? 0, - DefaultSettings = json[nameof(DefaultSettings)]?.ToObject() ?? 0, + DefaultSettings = json[nameof(DefaultSettings)]?.ToObject() ?? Setting.Zero, }; if (ret.Name.Length == 0) return null; @@ -56,7 +56,8 @@ public sealed class MultiModGroup : IModGroup if (ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions) { Penumbra.Messager.NotificationMessage( - $"Multi Group {ret.Name} in {mod.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options.", NotificationType.Warning); + $"Multi Group {ret.Name} in {mod.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options.", + NotificationType.Warning); break; } @@ -66,7 +67,7 @@ public sealed class MultiModGroup : IModGroup ret.PrioritizedOptions.Add((subMod, priority)); } - ret.DefaultSettings = (uint)(ret.DefaultSettings & ((1ul << ret.Count) - 1)); + ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings); return ret; } @@ -82,7 +83,7 @@ public sealed class MultiModGroup : IModGroup Name = Name, Description = Description, Priority = Priority, - DefaultSettings = (uint)Math.Max(Math.Min(Count - 1, BitOperations.TrailingZeroCount(DefaultSettings)), 0), + DefaultSettings = DefaultSettings.TurnMulti(Count), }; multi.OptionData.AddRange(PrioritizedOptions.Select(p => p.Mod)); return multi; @@ -95,7 +96,7 @@ public sealed class MultiModGroup : IModGroup if (!PrioritizedOptions.Move(optionIdxFrom, optionIdxTo)) return false; - DefaultSettings = Functions.MoveBit(DefaultSettings, optionIdxFrom, optionIdxTo); + DefaultSettings = DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo); UpdatePositions(Math.Min(optionIdxFrom, optionIdxTo)); return true; } @@ -105,4 +106,7 @@ public sealed class MultiModGroup : IModGroup foreach (var ((o, _), i) in PrioritizedOptions.WithIndex().Skip(from)) o.SetPosition(o.GroupIdx, i); } + + public Setting FixSetting(Setting setting) + => new(setting.Value & ((1ul << Count) - 1)); } diff --git a/Penumbra/Mods/Subclasses/Setting.cs b/Penumbra/Mods/Subclasses/Setting.cs new file mode 100644 index 00000000..18b1e4ca --- /dev/null +++ b/Penumbra/Mods/Subclasses/Setting.cs @@ -0,0 +1,62 @@ +using Newtonsoft.Json; +using OtterGui; + +namespace Penumbra.Mods.Subclasses; + +[JsonConverter(typeof(Converter))] +public readonly record struct Setting(ulong Value) +{ + public static readonly Setting Zero = new(0); + public static readonly Setting True = new(1); + public static readonly Setting False = new(0); + public static readonly Setting Indefinite = new(ulong.MaxValue); + + public static Setting Multi(int idx) + => new(1ul << idx); + + public static Setting Single(int idx) + => new(Math.Max(0ul, (ulong)idx)); + + public static Setting operator |(Setting lhs, Setting rhs) + => new(lhs.Value | rhs.Value); + + public int AsIndex + => (int)Math.Clamp(Value, 0ul, int.MaxValue); + + public bool HasFlag(int idx) + => idx >= 0 && (Value & (1ul << idx)) != 0; + + public Setting MoveBit(int idx1, int idx2) + => new(Functions.MoveBit(Value, idx1, idx2)); + + public Setting RemoveBit(int idx) + => new(Functions.RemoveBit(Value, idx)); + + public Setting SetBit(int idx, bool value) + => new(value ? Value | (1ul << idx) : Value & ~(1ul << idx)); + + public static Setting AllBits(int count) + => new((1ul << Math.Clamp(count, 0, 63)) - 1); + + public Setting TurnMulti(int count) + => new(Math.Max((ulong)Math.Min(count - 1, BitOperations.TrailingZeroCount(Value)), 0)); + + public ModPriority AsPriority + => new((int)(Value & 0xFFFFFFFF)); + + public static Setting FromBool(bool value) + => value ? True : False; + + public bool AsBool + => Value != 0; + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, Setting value, JsonSerializer serializer) + => serializer.Serialize(writer, value.Value); + + public override Setting ReadJson(JsonReader reader, Type objectType, Setting existingValue, bool hasExistingValue, + JsonSerializer serializer) + => new(serializer.Deserialize(reader)); + } +} diff --git a/Penumbra/Mods/Subclasses/SettingList.cs b/Penumbra/Mods/Subclasses/SettingList.cs new file mode 100644 index 00000000..ea1e447f --- /dev/null +++ b/Penumbra/Mods/Subclasses/SettingList.cs @@ -0,0 +1,57 @@ +namespace Penumbra.Mods.Subclasses; + +public class SettingList : List +{ + public SettingList() + { } + + public SettingList(int capacity) + : base(capacity) + { } + + public SettingList(IEnumerable settings) + => AddRange(settings); + + public SettingList Clone() + => new(this); + + public static SettingList Default(Mod mod) + => new(mod.Groups.Select(g => g.DefaultSettings)); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool FixSize(Mod mod) + { + var diff = Count - mod.Groups.Count; + + switch (diff) + { + case 0: return false; + case > 0: + RemoveRange(mod.Groups.Count, diff); + return true; + default: + EnsureCapacity(mod.Groups.Count); + for (var i = Count; i < mod.Groups.Count; ++i) + Add(mod.Groups[i].DefaultSettings); + return true; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool FixAll(Mod mod) + { + var ret = false; + for (var i = 0; i < Count; ++i) + { + var oldValue = this[i]; + var newValue = mod.Groups[i].FixSetting(oldValue); + if (newValue == oldValue) + continue; + + ret = true; + this[i] = newValue; + } + + return FixSize(mod) | ret; + } +} diff --git a/Penumbra/Mods/Subclasses/SingleModGroup.cs b/Penumbra/Mods/Subclasses/SingleModGroup.cs index 2b7ebd09..be1dbde5 100644 --- a/Penumbra/Mods/Subclasses/SingleModGroup.cs +++ b/Penumbra/Mods/Subclasses/SingleModGroup.cs @@ -12,10 +12,10 @@ public sealed class SingleModGroup : IModGroup public GroupType Type => GroupType.Single; - public string Name { get; set; } = "Option"; - public string Description { get; set; } = "A mutually exclusive group of settings."; - public int Priority { get; set; } - public uint DefaultSettings { get; set; } + public string Name { get; set; } = "Option"; + public string Description { get; set; } = "A mutually exclusive group of settings."; + public int Priority { get; set; } + public Setting DefaultSettings { get; set; } public readonly List OptionData = []; @@ -43,7 +43,7 @@ public sealed class SingleModGroup : IModGroup Name = json[nameof(Name)]?.ToObject() ?? string.Empty, Description = json[nameof(Description)]?.ToObject() ?? string.Empty, Priority = json[nameof(Priority)]?.ToObject() ?? 0, - DefaultSettings = json[nameof(DefaultSettings)]?.ToObject() ?? 0u, + DefaultSettings = json[nameof(DefaultSettings)]?.ToObject() ?? Setting.Zero, }; if (ret.Name.Length == 0) return null; @@ -57,9 +57,7 @@ public sealed class SingleModGroup : IModGroup ret.OptionData.Add(subMod); } - if ((int)ret.DefaultSettings >= ret.Count) - ret.DefaultSettings = 0; - + ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings); return ret; } @@ -74,7 +72,7 @@ public sealed class SingleModGroup : IModGroup Name = Name, Description = Description, Priority = Priority, - DefaultSettings = 1u << (int)DefaultSettings, + DefaultSettings = Setting.Multi((int) DefaultSettings.Value), }; multi.PrioritizedOptions.AddRange(OptionData.Select((o, i) => (o, i))); return multi; @@ -87,19 +85,20 @@ public sealed class SingleModGroup : IModGroup if (!OptionData.Move(optionIdxFrom, optionIdxTo)) return false; + var currentIndex = DefaultSettings.AsIndex; // Update default settings with the move. - if (DefaultSettings == optionIdxFrom) + if (currentIndex == optionIdxFrom) { - DefaultSettings = (uint)optionIdxTo; + DefaultSettings = Setting.Single(optionIdxTo); } else if (optionIdxFrom < optionIdxTo) { - if (DefaultSettings > optionIdxFrom && DefaultSettings <= optionIdxTo) - --DefaultSettings; + if (currentIndex > optionIdxFrom && currentIndex <= optionIdxTo) + DefaultSettings = Setting.Single(currentIndex - 1); } - else if (DefaultSettings < optionIdxFrom && DefaultSettings >= optionIdxTo) + else if (currentIndex < optionIdxFrom && currentIndex >= optionIdxTo) { - ++DefaultSettings; + DefaultSettings = Setting.Single(currentIndex + 1); } UpdatePositions(Math.Min(optionIdxFrom, optionIdxTo)); @@ -111,4 +110,7 @@ public sealed class SingleModGroup : IModGroup foreach (var (o, i) in OptionData.WithIndex().Skip(from)) o.SetPosition(o.GroupIdx, i); } + + public Setting FixSetting(Setting setting) + => Count == 0 ? Setting.Zero : new Setting(Math.Min(setting.Value, (ulong)(Count - 1))); } diff --git a/Penumbra/Mods/TemporaryMod.cs b/Penumbra/Mods/TemporaryMod.cs index c80334aa..4de2ac13 100644 --- a/Penumbra/Mods/TemporaryMod.cs +++ b/Penumbra/Mods/TemporaryMod.cs @@ -13,7 +13,7 @@ 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 ModPriority Priority { get; init; } = ModPriority.MaxValue; public int TotalManipulations => Default.Manipulations.Count; @@ -27,10 +27,7 @@ public class TemporaryMod : IMod => Array.Empty(); public IEnumerable AllSubMods - => new[] - { - Default, - }; + => [Default]; public TemporaryMod() => Default = new SubMod(this); diff --git a/Penumbra/Services/ConfigMigrationService.cs b/Penumbra/Services/ConfigMigrationService.cs index b84c0996..d1e952f1 100644 --- a/Penumbra/Services/ConfigMigrationService.cs +++ b/Penumbra/Services/ConfigMigrationService.cs @@ -341,15 +341,15 @@ public class ConfigMigrationService(SaveService saveService) : IService var text = File.ReadAllText(collectionJson.FullName); var data = JArray.Parse(text); - var maxPriority = 0; + var maxPriority = ModPriority.Default; var dict = new Dictionary(); foreach (var setting in data.Cast()) { - var modName = (string)setting["FolderName"]!; - var enabled = (bool)setting["Enabled"]!; - var priority = (int)setting["Priority"]!; - var settings = setting["Settings"]!.ToObject>() - ?? setting["Conf"]!.ToObject>(); + var modName = setting["FolderName"]?.ToObject()!; + var enabled = setting["Enabled"]?.ToObject() ?? false; + var priority = setting["Priority"]?.ToObject() ?? ModPriority.Default; + var settings = setting["Settings"]!.ToObject>() + ?? setting["Conf"]!.ToObject>(); dict[modName] = new ModSettings.SavedSettings() { @@ -357,7 +357,7 @@ public class ConfigMigrationService(SaveService saveService) : IService Priority = priority, Settings = settings!, }; - maxPriority = Math.Max(maxPriority, priority); + maxPriority = maxPriority.Max(priority); } InvertModListOrder = _data[nameof(InvertModListOrder)]?.ToObject() ?? InvertModListOrder; @@ -365,8 +365,7 @@ public class ConfigMigrationService(SaveService saveService) : IService dict = dict.ToDictionary(kvp => kvp.Key, kvp => kvp.Value with { Priority = maxPriority - kvp.Value.Priority }); var emptyStorage = new ModStorage(); - var collection = ModCollection.CreateFromData(saveService, emptyStorage, ModCollection.DefaultCollectionName, 0, 1, dict, - Array.Empty()); + var collection = ModCollection.CreateFromData(saveService, emptyStorage, ModCollection.DefaultCollectionName, 0, 1, dict, []); saveService.ImmediateSaveSync(new ModCollectionSave(emptyStorage, collection)); } catch (Exception e) diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index 0205f3c6..7b5ce2dc 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -694,7 +694,7 @@ public class ItemSwapTab : IDisposable, ITab UpdateMod(_mod, _mod.Index < newCollection.Settings.Count ? newCollection[_mod.Index].Settings : null); } - private void OnSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool inherited) + private void OnSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx, bool inherited) { if (collection != _collectionManager.Active.Current || mod != _mod) return; diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs index 15b18692..cd0eb982 100644 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs @@ -74,7 +74,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector 0) { var mod = _modManager.FirstOrDefault(m @@ -92,7 +92,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector Label => "Conflicts"u8; public bool IsVisible - => _collectionManager.Active.Current.Conflicts(_selector.Selected!).Count > 0; + => collectionManager.Active.Current.Conflicts(selector.Selected!).Count > 0; - private readonly ConditionalWeakTable _expandedMods = new(); + private readonly ConditionalWeakTable _expandedMods = []; - private int GetPriority(ModConflicts conflicts) + private ModPriority GetPriority(ModConflicts conflicts) { if (conflicts.Mod2.Index < 0) return conflicts.Mod2.Priority; - return _collectionManager.Active.Current[conflicts.Mod2.Index].Settings?.Priority ?? 0; + return collectionManager.Active.Current[conflicts.Mod2.Index].Settings?.Priority ?? ModPriority.Default; } public void DrawContent() @@ -63,8 +55,8 @@ public class ModPanelConflictsTab : ITab DrawCurrentRow(priorityWidth); // Can not be null because otherwise the tab bar is never drawn. - var mod = _selector.Selected!; - foreach (var (conflict, index) in _collectionManager.Active.Current.Conflicts(mod).OrderByDescending(GetPriority) + var mod = selector.Selected!; + foreach (var (conflict, index) in collectionManager.Active.Current.Conflicts(mod).OrderByDescending(GetPriority) .ThenBy(c => c.Mod2.Name.Lower).WithIndex()) { using var id = ImRaii.PushId(index); @@ -77,18 +69,18 @@ public class ModPanelConflictsTab : ITab ImGui.TableNextColumn(); using var c = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderLine.Value()); ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(_selector.Selected!.Name); + ImGui.TextUnformatted(selector.Selected!.Name); ImGui.TableNextColumn(); - var priority = _collectionManager.Active.Current[_selector.Selected!.Index].Settings!.Priority; + var priority = collectionManager.Active.Current[selector.Selected!.Index].Settings!.Priority.Value; ImGui.SetNextItemWidth(priorityWidth); if (ImGui.InputInt("##priority", ref priority, 0, 0, ImGuiInputTextFlags.EnterReturnsTrue)) _currentPriority = priority; if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue) { - if (_currentPriority != _collectionManager.Active.Current[_selector.Selected!.Index].Settings!.Priority) - _collectionManager.Editor.SetModPriority(_collectionManager.Active.Current, (Mod)_selector.Selected!, - _currentPriority.Value); + if (_currentPriority != collectionManager.Active.Current[selector.Selected!.Index].Settings!.Priority.Value) + collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selector.Selected!, + new ModPriority(_currentPriority.Value)); _currentPriority = null; } @@ -104,7 +96,7 @@ public class ModPanelConflictsTab : ITab { ImGui.AlignTextToFramePadding(); if (ImGui.Selectable(conflict.Mod2.Name) && conflict.Mod2 is Mod otherMod) - _selector.SelectByValue(otherMod); + selector.SelectByValue(otherMod); var hovered = ImGui.IsItemHovered(); var rightClicked = ImGui.IsItemClicked(ImGuiMouseButton.Right); if (conflict.Mod2 is Mod otherMod2) @@ -112,7 +104,7 @@ public class ModPanelConflictsTab : ITab if (hovered) ImGui.SetTooltip("Click to jump to mod, Control + Right-Click to disable mod."); if (rightClicked && ImGui.GetIO().KeyCtrl) - _collectionManager.Editor.SetModState(_collectionManager.Active.Current, otherMod2, false); + collectionManager.Editor.SetModState(collectionManager.Active.Current, otherMod2, false); } } @@ -146,7 +138,7 @@ public class ModPanelConflictsTab : ITab ImGui.TableNextColumn(); var conflictPriority = DrawPriorityInput(conflict, priorityWidth); ImGui.SameLine(); - var selectedPriority = _collectionManager.Active.Current[_selector.Selected!.Index].Settings!.Priority; + var selectedPriority = collectionManager.Active.Current[selector.Selected!.Index].Settings!.Priority.Value; DrawPriorityButtons(conflict.Mod2 as Mod, conflictPriority, selectedPriority, buttonSize); ImGui.TableNextColumn(); DrawExpandButton(conflict.Mod2, expanded, buttonSize); @@ -171,7 +163,7 @@ public class ModPanelConflictsTab : ITab using var color = ImRaii.PushColor(ImGuiCol.Text, conflict.HasPriority ? ColorId.HandledConflictMod.Value() : ColorId.ConflictingMod.Value()); using var disabled = ImRaii.Disabled(conflict.Mod2.Index < 0); - var priority = _currentPriority ?? GetPriority(conflict); + var priority = _currentPriority ?? GetPriority(conflict).Value; ImGui.SetNextItemWidth(priorityWidth); if (ImGui.InputInt("##priority", ref priority, 0, 0, ImGuiInputTextFlags.EnterReturnsTrue)) @@ -179,8 +171,9 @@ public class ModPanelConflictsTab : ITab if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue) { - if (_currentPriority != GetPriority(conflict)) - _collectionManager.Editor.SetModPriority(_collectionManager.Active.Current, (Mod)conflict.Mod2, _currentPriority.Value); + if (_currentPriority != GetPriority(conflict).Value) + collectionManager.Editor.SetModPriority(collectionManager.Active.Current, (Mod)conflict.Mod2, + new ModPriority(_currentPriority.Value)); _currentPriority = null; } @@ -195,12 +188,14 @@ public class ModPanelConflictsTab : ITab private void DrawPriorityButtons(Mod? conflict, int conflictPriority, int selectedPriority, Vector2 buttonSize) { if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.SortNumericUpAlt.ToIconString(), buttonSize, - $"Set the priority of the currently selected mod to this mods priority plus one. ({selectedPriority} -> {conflictPriority + 1})", selectedPriority > conflictPriority, true)) - _collectionManager.Editor.SetModPriority(_collectionManager.Active.Current, _selector.Selected!, conflictPriority + 1); + $"Set the priority of the currently selected mod to this mods priority plus one. ({selectedPriority} -> {conflictPriority + 1})", + selectedPriority > conflictPriority, true)) + collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selector.Selected!, + new ModPriority(conflictPriority + 1)); ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.SortNumericDownAlt.ToIconString(), buttonSize, $"Set the priority of this mod to the currently selected mods priority minus one. ({conflictPriority} -> {selectedPriority - 1})", selectedPriority > conflictPriority || conflict == null, true)) - _collectionManager.Editor.SetModPriority(_collectionManager.Active.Current, conflict!, selectedPriority - 1); + collectionManager.Editor.SetModPriority(collectionManager.Active.Current, conflict!, new ModPriority(selectedPriority - 1)); } } diff --git a/Penumbra/UI/ModsTab/ModPanelEditTab.cs b/Penumbra/UI/ModsTab/ModPanelEditTab.cs index eb79869e..1292367a 100644 --- a/Penumbra/UI/ModsTab/ModPanelEditTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelEditTab.cs @@ -502,18 +502,16 @@ public class ModPanelEditTab( if (group.Type == GroupType.Single) { - if (ImGui.RadioButton("##default", group.DefaultSettings == optionIdx)) - panel._modManager.OptionEditor.ChangeModGroupDefaultOption(panel._mod, groupIdx, (uint)optionIdx); + if (ImGui.RadioButton("##default", group.DefaultSettings.AsIndex == optionIdx)) + panel._modManager.OptionEditor.ChangeModGroupDefaultOption(panel._mod, groupIdx, Setting.Single(optionIdx)); ImGuiUtil.HoverTooltip($"Set {option.Name} as the default choice for this group."); } else { - var isDefaultOption = ((group.DefaultSettings >> optionIdx) & 1) != 0; + var isDefaultOption = group.DefaultSettings.HasFlag(optionIdx); if (ImGui.Checkbox("##default", ref isDefaultOption)) - panel._modManager.OptionEditor.ChangeModGroupDefaultOption(panel._mod, groupIdx, isDefaultOption - ? group.DefaultSettings | (1u << optionIdx) - : group.DefaultSettings & ~(1u << optionIdx)); + panel._modManager.OptionEditor.ChangeModGroupDefaultOption(panel._mod, groupIdx, group.DefaultSettings.SetBit(optionIdx, isDefaultOption)); ImGuiUtil.HoverTooltip($"{(isDefaultOption ? "Disable" : "Enable")} {option.Name} per default in this group."); } diff --git a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs index b14cad01..1107aa20 100644 --- a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs @@ -26,7 +26,7 @@ public class ModPanelSettingsTab : ITab private ModSettings _settings = null!; private ModCollection _collection = null!; private bool _empty; - private int? _currentPriority = null; + private int? _currentPriority; public ModPanelSettingsTab(CollectionManager collectionManager, ModManager modManager, ModFileSystemSelector selector, TutorialService tutorial, CommunicatorService communicator, Configuration config) @@ -136,15 +136,15 @@ public class ModPanelSettingsTab : ITab private void DrawPriorityInput() { using var group = ImRaii.Group(); - var priority = _currentPriority ?? _settings.Priority; + var priority = _currentPriority ?? _settings.Priority.Value; ImGui.SetNextItemWidth(50 * UiHelpers.Scale); if (ImGui.InputInt("##Priority", ref priority, 0, 0)) _currentPriority = priority; if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue) { - if (_currentPriority != _settings.Priority) - _collectionManager.Editor.SetModPriority(_collectionManager.Active.Current, _selector.Selected!, _currentPriority.Value); + if (_currentPriority != _settings.Priority.Value) + _collectionManager.Editor.SetModPriority(_collectionManager.Active.Current, _selector.Selected!, new ModPriority(_currentPriority.Value)); _currentPriority = null; } @@ -179,7 +179,7 @@ public class ModPanelSettingsTab : ITab private void DrawSingleGroupCombo(IModGroup group, int groupIdx) { using var id = ImRaii.PushId(groupIdx); - var selectedOption = _empty ? (int)group.DefaultSettings : (int)_settings.Settings[groupIdx]; + var selectedOption = _empty ? group.DefaultSettings.AsIndex : _settings.Settings[groupIdx].AsIndex; ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X * 3 / 4); using (var combo = ImRaii.Combo(string.Empty, group[selectedOption].Name)) { @@ -189,7 +189,8 @@ public class ModPanelSettingsTab : ITab id.Push(idx2); var option = group[idx2]; if (ImGui.Selectable(option.Name, idx2 == selectedOption)) - _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, (uint)idx2); + _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, + Setting.Single(idx2)); if (option.Description.Length > 0) ImGuiUtil.SelectableHelpMarker(option.Description); @@ -210,8 +211,8 @@ public class ModPanelSettingsTab : ITab private void DrawSingleGroupRadio(IModGroup group, int groupIdx) { using var id = ImRaii.PushId(groupIdx); - var selectedOption = _empty ? (int)group.DefaultSettings : (int)_settings.Settings[groupIdx]; - var minWidth = Widget.BeginFramedGroup(group.Name, description:group.Description); + var selectedOption = _empty ? group.DefaultSettings.AsIndex : _settings.Settings[groupIdx].AsIndex; + var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); DrawCollapseHandling(group, minWidth, DrawOptions); @@ -225,7 +226,8 @@ public class ModPanelSettingsTab : ITab using var i = ImRaii.PushId(idx); var option = group[idx]; if (ImGui.RadioButton(option.Name, selectedOption == idx)) - _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, (uint)idx); + _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, + Setting.Single(idx)); if (option.Description.Length <= 0) continue; @@ -291,7 +293,17 @@ public class ModPanelSettingsTab : ITab { using var id = ImRaii.PushId(groupIdx); var flags = _empty ? group.DefaultSettings : _settings.Settings[groupIdx]; - var minWidth = Widget.BeginFramedGroup(group.Name, description: group.Description); + var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); + + DrawCollapseHandling(group, minWidth, DrawOptions); + + Widget.EndFramedGroup(); + var label = $"##multi{groupIdx}"; + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + ImGui.OpenPopup($"##multi{groupIdx}"); + + DrawMultiPopup(group, groupIdx, label); + return; void DrawOptions() { @@ -299,12 +311,11 @@ public class ModPanelSettingsTab : ITab { using var i = ImRaii.PushId(idx); var option = group[idx]; - var flag = 1u << idx; - var setting = (flags & flag) != 0; + var setting = flags.HasFlag(idx); if (ImGui.Checkbox(option.Name, ref setting)) { - flags = setting ? flags | flag : flags & ~flag; + flags = flags.SetBit(idx, setting); _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, flags); } @@ -315,14 +326,10 @@ public class ModPanelSettingsTab : ITab } } } + } - DrawCollapseHandling(group, minWidth, DrawOptions); - - Widget.EndFramedGroup(); - var label = $"##multi{groupIdx}"; - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - ImGui.OpenPopup($"##multi{groupIdx}"); - + private void DrawMultiPopup(IModGroup group, int groupIdx, string label) + { using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 1); using var popup = ImRaii.Popup(label); if (!popup) @@ -331,12 +338,10 @@ public class ModPanelSettingsTab : ITab ImGui.TextUnformatted(group.Name); ImGui.Separator(); if (ImGui.Selectable("Enable All")) - { - flags = group.Count == 32 ? uint.MaxValue : (1u << group.Count) - 1u; - _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, flags); - } + _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, + Setting.AllBits(group.Count)); if (ImGui.Selectable("Disable All")) - _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, 0); + _collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, Setting.Zero); } } diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index 06f1d126..9a956d2d 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -211,7 +211,7 @@ public class DebugTab : Window, ITab color.Pop(); foreach (var (mod, paths, manips) in collection._cache!.ModData.Data.OrderBy(t => t.Item1.Name)) { - using var id = mod is TemporaryMod t ? PushId(t.Priority) : PushId(((Mod)mod).ModPath.Name); + using var id = mod is TemporaryMod t ? PushId(t.Priority.Value) : PushId(((Mod)mod).ModPath.Name); using var node2 = TreeNode(mod.Name.Text); if (!node2) continue;