Turn Settings and Priority into their own types.

This commit is contained in:
Ottermandias 2024-04-05 16:35:55 +02:00
parent 77bf441e62
commit b1ca073276
29 changed files with 422 additions and 298 deletions

View file

@ -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<string>? PreSettingsPanelDraw
{
add => _communicator.PreSettingsPanelDraw.Subscribe(value!, Communication.PreSettingsPanelDraw.Priority.Default);
remove => _communicator.PreSettingsPanelDraw.Unsubscribe(value!);
}
}
public event Action<string>? 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<string, string[]>?[] GetGameObjectResourcePaths(ushort[] gameObjects)
{
var characters = gameObjects.Select(index => _objects.GetDalamudObject((int) index)).OfType<Character>();
var characters = gameObjects.Select(index => _objects.GetDalamudObject((int)index)).OfType<Character>();
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<MetaManipulation>();
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<MetaManipulation>();
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<T>(resolvedPath);
return _gameData.GetFile<T>(resolvedPath);
return Path.IsPathRooted(resolvedPath)
? _lumina?.GetFileFromDisk<T>(resolvedPath)
: _gameData.GetFile<T>(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)

View file

@ -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<ModCollection, List<TemporaryMod>> _mods = new();
private readonly List<TemporaryMod> _modsForAllCollections = new();
private readonly Dictionary<ModCollection, List<TemporaryMod>> _mods = [];
private readonly List<TemporaryMod> _modsForAllCollections = [];
public TempModManager(CommunicatorService communicator)
{
@ -42,7 +43,7 @@ public class TempModManager : IDisposable
=> _modsForAllCollections;
public RedirectResult Register(string tag, ModCollection? collection, Dictionary<Utf8GamePath, FullPath> dict,
HashSet<MetaManipulation> manips, int priority)
HashSet<MetaManipulation> 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<TemporaryMod> list;
if (collection == null)
@ -129,14 +130,14 @@ public class TempModManager : IDisposable
}
else
{
list = new List<TemporaryMod>();
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,

View file

@ -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;

View file

@ -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);

View file

@ -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;
}
/// <summary> Enable or disable the mod inheritance of mod idx. </summary>
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<ModSettings?>)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);
}
/// <summary>
@ -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);
}
/// <summary>
/// Set the priority of mod idx to newValue if it differs from the current priority.
/// If the mod is currently inherited, stop the inheritance.
/// </summary>
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<ModSettings?>)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.
/// </summary>
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<ModSettings?>)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<string, ModSettings.SavedSettings>)collection.UnusedSettings)[targetName] = savedSettings.Value;
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
saveService.QueueSave(new ModCollectionSave(modStorage, collection));
}
else if (((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Remove(targetName))
{
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
saveService.QueueSave(new ModCollectionSave(modStorage, collection));
}
}
return true;
}
/// <summary>
/// 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.
/// </summary>
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),
};
}
/// <summary>
/// Set inheritance of a mod without saving,
/// to be used as an intermediary.
@ -204,16 +176,16 @@ public class CollectionEditor
/// <summary> Queue saves and trigger changes for any non-inherited change in a collection, then trigger changes for all inheritors. </summary>
[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);
}
/// <summary> Trigger changes in all inherited collections. </summary>
[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;
}

View file

@ -268,7 +268,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, 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));
}

View file

@ -40,9 +40,9 @@ internal static class ModCollectionMigration
/// <summary> We treat every completely defaulted setting as inheritance-ready. </summary>
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);
/// <inheritdoc cref="SettingIsDefaultV0(ModSettings.SavedSettings)"/>
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);
}

View file

@ -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;
/// <item>Parameter is the collection in which the setting was changed. </item>
/// <item>Parameter is the type of change. </item>
/// <item>Parameter is the mod the setting was changed for, unless it was a multi-change. </item>
/// <item>Parameter is the old value of the setting before the change as int. </item>
/// <item>Parameter is the old value of the setting before the change as Setting. </item>
/// <item>Parameter is the index of the changed group if the change type is Setting. </item>
/// <item>Parameter is whether the change was inherited from another collection. </item>
/// </list>
/// </summary>
public sealed class ModSettingChanged()
: EventWrapper<ModCollection, ModSettingChange, Mod?, int, int, bool, ModSettingChanged.Priority>(nameof(ModSettingChanged))
: EventWrapper<ModCollection, ModSettingChange, Mod?, Setting, int, bool, ModSettingChanged.Priority>(nameof(ModSettingChanged))
{
public enum Priority
{

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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<IModGroup> Groups { get; }

View file

@ -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)
{
/// <summary> Change the type of a group given by mod and index to type, if possible. </summary>
public void ChangeModGroupType(Mod mod, int groupIdx, GroupType type)
{
@ -46,7 +49,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
}
/// <summary> Change the settings stored as default options in a mod.</summary>
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)

View file

@ -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;
/// <summary>Unused if Index < 0 but used for special temporary mods.</summary>
public int Priority
=> 0;
/// <summary>Unused if Index is less than 0 but used for special temporary mods.</summary>
public ModPriority Priority
=> ModPriority.Default;
internal Mod(DirectoryInfo modPath)
{

View file

@ -235,7 +235,7 @@ public partial class ModCreator(SaveService _saveService, Configuration config,
/// <summary> Create a file for an option group from given data. </summary>
public void CreateOptionGroup(DirectoryInfo baseFolder, GroupType type, string name,
int priority, int index, uint defaultSettings, string desc, IEnumerable<ISubMod> subMods)
int priority, int index, Setting defaultSettings, string desc, IEnumerable<ISubMod> subMods)
{
switch (type)
{

View file

@ -12,7 +12,7 @@ public interface IModGroup : IEnumerable<ISubMod>
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<ISubMod>
public IModGroup Convert(GroupType type);
public bool MoveOption(int optionIdxFrom, int optionIdxTo);
public void UpdatePositions(int from = 0);
/// <summary> Ensure that a value is valid for a group. </summary>
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)

View file

@ -0,0 +1,61 @@
using Newtonsoft.Json;
namespace Penumbra.Mods.Subclasses;
[JsonConverter(typeof(Converter))]
public readonly record struct ModPriority(int Value) :
IComparisonOperators<ModPriority, ModPriority, bool>,
IAdditionOperators<ModPriority, ModPriority, ModPriority>,
IAdditionOperators<ModPriority, int, ModPriority>,
ISubtractionOperators<ModPriority, ModPriority, ModPriority>,
ISubtractionOperators<ModPriority, int, ModPriority>
{
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<ModPriority>
{
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<int>(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);
}

View file

@ -11,8 +11,8 @@ namespace Penumbra.Mods.Subclasses;
public class ModSettings
{
public static readonly ModSettings Empty = new();
public List<uint> 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<Utf8GamePath, FullPath>();
var set = new HashSet<MetaManipulation>();
@ -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)
/// <summary> Set a setting. Ensures that there are enough settings and fixes the setting beforehand. </summary>
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<string, long> Settings;
public int Priority;
public bool Enabled;
public Dictionary<string, Setting> 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<string, long>(mod.Groups.Count);
settings.AddMissingSettings(mod);
Settings = new Dictionary<string, Setting>(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<uint>(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<string, IList<string>> Settings) ConvertToShareable(Mod mod)
public (bool Enabled, ModPriority Priority, Dictionary<string, IList<string>> Settings) ConvertToShareable(Mod mod)
{
var dict = new Dictionary<string, IList<string>>(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);
}
}

View file

@ -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>() ?? string.Empty,
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
Priority = json[nameof(Priority)]?.ToObject<int>() ?? 0,
DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<uint>() ?? 0,
DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<Setting>() ?? 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));
}

View file

@ -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<Setting>
{
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<ulong>(reader));
}
}

View file

@ -0,0 +1,57 @@
namespace Penumbra.Mods.Subclasses;
public class SettingList : List<Setting>
{
public SettingList()
{ }
public SettingList(int capacity)
: base(capacity)
{ }
public SettingList(IEnumerable<Setting> 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;
}
}

View file

@ -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<SubMod> OptionData = [];
@ -43,7 +43,7 @@ public sealed class SingleModGroup : IModGroup
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty,
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
Priority = json[nameof(Priority)]?.ToObject<int>() ?? 0,
DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<uint>() ?? 0u,
DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<Setting>() ?? 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)));
}

View file

@ -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<IModGroup>();
public IEnumerable<SubMod> AllSubMods
=> new[]
{
Default,
};
=> [Default];
public TemporaryMod()
=> Default = new SubMod(this);

View file

@ -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<string, ModSettings.SavedSettings>();
foreach (var setting in data.Cast<JObject>())
{
var modName = (string)setting["FolderName"]!;
var enabled = (bool)setting["Enabled"]!;
var priority = (int)setting["Priority"]!;
var settings = setting["Settings"]!.ToObject<Dictionary<string, long>>()
?? setting["Conf"]!.ToObject<Dictionary<string, long>>();
var modName = setting["FolderName"]?.ToObject<string>()!;
var enabled = setting["Enabled"]?.ToObject<bool>() ?? false;
var priority = setting["Priority"]?.ToObject<ModPriority>() ?? ModPriority.Default;
var settings = setting["Settings"]!.ToObject<Dictionary<string, Setting>>()
?? setting["Conf"]!.ToObject<Dictionary<string, Setting>>();
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<bool>() ?? 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<string>());
var collection = ModCollection.CreateFromData(saveService, emptyStorage, ModCollection.DefaultCollectionName, 0, 1, dict, []);
saveService.ImmediateSaveSync(new ModCollectionSave(emptyStorage, collection));
}
catch (Exception e)

View file

@ -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;

View file

@ -74,7 +74,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
// @formatter:on
SetFilterTooltip();
SelectionChanged += OnSelectionChange;
SelectionChanged += OnSelectionChange;
if (_config.Ephemeral.LastModPath.Length > 0)
{
var mod = _modManager.FirstOrDefault(m
@ -92,7 +92,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
OnCollectionChange(CollectionType.Current, null, _collectionManager.Active.Current, "");
}
private static readonly string[] ValidModExtensions =
private static readonly string[] ValidModExtensions =
[
".ttmp",
".ttmp2",
@ -191,15 +191,15 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
}
}
if (state.Priority != 0 && !_config.HidePrioritiesInSelector)
if (!state.Priority.IsDefault && !_config.HidePrioritiesInSelector)
{
var line = ImGui.GetItemRectMin().Y;
var itemPos = ImGui.GetItemRectMax().X;
var maxWidth = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X;
var priorityString = $"[{state.Priority}]";
var Size = ImGui.CalcTextSize(priorityString).X;
var size = ImGui.CalcTextSize(priorityString).X;
var remainingSpace = maxWidth - itemPos;
var offset = remainingSpace - Size;
var offset = remainingSpace - size;
if (ImGui.GetScrollMaxY() == 0)
offset -= ImGui.GetStyle().ItemInnerSpacing.X;
@ -427,7 +427,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
#region Automatic cache update functions.
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)
return;
@ -517,8 +517,8 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ModState
{
public ColorId Color;
public int Priority;
public ColorId Color;
public ModPriority Priority;
}
private const StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase;
@ -744,7 +744,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
state = new ModState
{
Color = ColorId.EnabledMod,
Priority = settings?.Priority ?? 0,
Priority = settings?.Priority ?? ModPriority.Default,
};
if (ApplyStringFilters(leaf, mod))
return true;

View file

@ -9,38 +9,30 @@ using Penumbra.Collections.Manager;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
using Penumbra.UI.Classes;
namespace Penumbra.UI.ModsTab;
public class ModPanelConflictsTab : ITab
public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSystemSelector selector) : ITab
{
private readonly ModFileSystemSelector _selector;
private readonly CollectionManager _collectionManager;
public ModPanelConflictsTab(CollectionManager collectionManager, ModFileSystemSelector selector)
{
_collectionManager = collectionManager;
_selector = selector;
}
private int? _currentPriority = null;
private int? _currentPriority;
public ReadOnlySpan<byte> 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<IMod, object> _expandedMods = new();
private readonly ConditionalWeakTable<IMod, object> _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));
}
}

View file

@ -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.");
}

View file

@ -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);
}
}

View file

@ -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;