diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs
index e1b32204..ded1dc73 100644
--- a/Penumbra/Collections/Cache/CollectionCache.cs
+++ b/Penumbra/Collections/Cache/CollectionCache.cs
@@ -2,12 +2,10 @@ using OtterGui;
using OtterGui.Classes;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
-using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.Mods.Editor;
using Penumbra.String.Classes;
using Penumbra.Mods.Manager;
-using Penumbra.Mods.Subclasses;
namespace Penumbra.Collections.Cache;
@@ -231,37 +229,12 @@ public sealed class CollectionCache : IDisposable
/// Add all files and possibly manipulations of a given mod according to its settings in this collection.
internal void AddModSync(IMod mod, bool addMetaChanges)
{
- if (mod.Index >= 0)
- {
- var settings = _collection[mod.Index].Settings;
- if (settings is not { Enabled: true })
- return;
+ var files = GetFiles(mod);
+ foreach (var (path, file) in files.FileRedirections)
+ AddFile(path, file, mod);
- foreach (var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending(g => g.Value.Priority))
- {
- if (group.Count == 0)
- continue;
-
- var config = settings.Settings[groupIndex];
- switch (group)
- {
- case SingleModGroup single:
- AddSubMod(single[config.AsIndex], mod);
- break;
- case MultiModGroup multi:
- {
- foreach (var (option, _) in multi.WithIndex()
- .Where(p => config.HasFlag(p.Index))
- .OrderByDescending(p => group.OptionPriority(p.Index)))
- AddSubMod(option, mod);
-
- break;
- }
- }
- }
- }
-
- AddSubMod(mod.Default, mod);
+ foreach (var manip in files.Manipulations)
+ AddManipulation(manip, mod);
if (addMetaChanges)
{
@@ -273,14 +246,15 @@ public sealed class CollectionCache : IDisposable
}
}
- // Add all files and possibly manipulations of a specific submod
- private void AddSubMod(ISubMod subMod, IMod parentMod)
+ private AppliedModData GetFiles(IMod mod)
{
- foreach (var (path, file) in subMod.Files.Concat(subMod.FileSwaps))
- AddFile(path, file, parentMod);
+ if (mod.Index < 0)
+ return mod.GetData();
- foreach (var manip in subMod.Manipulations)
- AddManipulation(manip, parentMod);
+ var settings = _collection[mod.Index].Settings;
+ return settings is not { Enabled: true }
+ ? AppliedModData.Empty
+ : mod.GetData(settings);
}
/// Invoke only if not in a full recalculation.
diff --git a/Penumbra/Collections/Cache/CollectionModData.cs b/Penumbra/Collections/Cache/CollectionModData.cs
index 3a3afad2..d0a3bc76 100644
--- a/Penumbra/Collections/Cache/CollectionModData.cs
+++ b/Penumbra/Collections/Cache/CollectionModData.cs
@@ -1,10 +1,12 @@
using Penumbra.Meta.Manipulations;
-using Penumbra.Mods;
using Penumbra.Mods.Editor;
using Penumbra.String.Classes;
namespace Penumbra.Collections.Cache;
+///
+/// Contains associations between a mod and the paths and meta manipulations affected by that mod.
+///
public class CollectionModData
{
private readonly Dictionary, HashSet)> _data = new();
@@ -17,7 +19,7 @@ public class CollectionModData
if (_data.Remove(mod, out var data))
return data;
- return (Array.Empty(), Array.Empty());
+ return ([], []);
}
public void AddPath(IMod mod, Utf8GamePath path)
@@ -28,7 +30,7 @@ public class CollectionModData
}
else
{
- data = (new HashSet { path }, new HashSet());
+ data = ([path], []);
_data.Add(mod, data);
}
}
@@ -41,7 +43,7 @@ public class CollectionModData
}
else
{
- data = (new HashSet(), new HashSet { manipulation });
+ data = ([], [manipulation]);
_data.Add(mod, data);
}
}
diff --git a/Penumbra/Mods/Editor/IMod.cs b/Penumbra/Mods/Editor/IMod.cs
index d3bc19b0..8b5b65e1 100644
--- a/Penumbra/Mods/Editor/IMod.cs
+++ b/Penumbra/Mods/Editor/IMod.cs
@@ -1,15 +1,27 @@
using OtterGui.Classes;
+using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Subclasses;
+using Penumbra.String.Classes;
namespace Penumbra.Mods.Editor;
+public record struct AppliedModData(
+ IReadOnlyCollection> FileRedirections,
+ IReadOnlyCollection Manipulations)
+{
+ public static readonly AppliedModData Empty = new([], []);
+}
+
public interface IMod
{
LowerString Name { get; }
- public int Index { get; }
+ public int Index { get; }
public ModPriority Priority { get; }
+ public AppliedModData GetData(ModSettings? settings = null);
+
+
public ISubMod Default { get; }
public IReadOnlyList Groups { get; }
diff --git a/Penumbra/Mods/Mod.cs b/Penumbra/Mods/Mod.cs
index b7d1186d..3c996c8f 100644
--- a/Penumbra/Mods/Mod.cs
+++ b/Penumbra/Mods/Mod.cs
@@ -1,5 +1,7 @@
using OtterGui;
using OtterGui.Classes;
+using Penumbra.Collections.Cache;
+using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Subclasses;
using Penumbra.String.Classes;
@@ -59,6 +61,23 @@ public sealed class Mod : IMod
public readonly SubMod Default;
public readonly List Groups = [];
+ public AppliedModData GetData(ModSettings? settings = null)
+ {
+ if (settings is not { Enabled: true })
+ return AppliedModData.Empty;
+
+ var dictRedirections = new Dictionary(TotalFileCount);
+ var setManips = new HashSet(TotalManipulations);
+ foreach (var (group, groupIndex) in Groups.WithIndex().OrderByDescending(g => g.Value.Priority))
+ {
+ var config = settings.Settings[groupIndex];
+ group.AddData(config, dictRedirections, setManips);
+ }
+
+ ((ISubMod)Default).AddData(dictRedirections, setManips);
+ return new AppliedModData(dictRedirections, setManips);
+ }
+
ISubMod IMod.Default
=> Default;
diff --git a/Penumbra/Mods/Subclasses/IModGroup.cs b/Penumbra/Mods/Subclasses/IModGroup.cs
index 2daf31e6..57ef4e98 100644
--- a/Penumbra/Mods/Subclasses/IModGroup.cs
+++ b/Penumbra/Mods/Subclasses/IModGroup.cs
@@ -1,6 +1,8 @@
using Newtonsoft.Json;
using Penumbra.Api.Enums;
+using Penumbra.Meta.Manipulations;
using Penumbra.Services;
+using Penumbra.String.Classes;
namespace Penumbra.Mods.Subclasses;
@@ -24,6 +26,8 @@ public interface IModGroup : IReadOnlyCollection
public bool MoveOption(int optionIdxFrom, int optionIdxTo);
public void UpdatePositions(int from = 0);
+ public void AddData(Setting setting, Dictionary redirections, HashSet manipulations);
+
/// Ensure that a value is valid for a group.
public Setting FixSetting(Setting setting);
}
diff --git a/Penumbra/Mods/Subclasses/ISubMod.cs b/Penumbra/Mods/Subclasses/ISubMod.cs
index 29323c1d..e997e07d 100644
--- a/Penumbra/Mods/Subclasses/ISubMod.cs
+++ b/Penumbra/Mods/Subclasses/ISubMod.cs
@@ -14,6 +14,16 @@ public interface ISubMod
public IReadOnlyDictionary FileSwaps { get; }
public IReadOnlySet Manipulations { get; }
+ public void AddData(Dictionary redirections, HashSet manipulations)
+ {
+ foreach (var (path, file) in Files)
+ redirections.TryAdd(path, file);
+
+ foreach (var (path, file) in FileSwaps)
+ redirections.TryAdd(path, file);
+ manipulations.UnionWith(Manipulations);
+ }
+
public bool IsDefault { get; }
public static void WriteSubMod(JsonWriter j, JsonSerializer serializer, ISubMod mod, DirectoryInfo basePath, ModPriority? priority)
diff --git a/Penumbra/Mods/Subclasses/MultiModGroup.cs b/Penumbra/Mods/Subclasses/MultiModGroup.cs
index 7479cd54..266d3037 100644
--- a/Penumbra/Mods/Subclasses/MultiModGroup.cs
+++ b/Penumbra/Mods/Subclasses/MultiModGroup.cs
@@ -5,6 +5,8 @@ using OtterGui;
using OtterGui.Classes;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
+using Penumbra.Meta.Manipulations;
+using Penumbra.String.Classes;
namespace Penumbra.Mods.Subclasses;
@@ -110,6 +112,15 @@ public sealed class MultiModGroup : IModGroup
o.SetPosition(o.GroupIdx, i);
}
+ public void AddData(Setting setting, Dictionary redirections, HashSet manipulations)
+ {
+ foreach (var (option, index) in PrioritizedOptions.WithIndex().OrderByDescending(o => o.Value.Priority))
+ {
+ if (setting.HasFlag(index))
+ ((ISubMod)option.Mod).AddData(redirections, manipulations);
+ }
+ }
+
public Setting FixSetting(Setting setting)
=> new(setting.Value & ((1ul << Count) - 1));
}
diff --git a/Penumbra/Mods/Subclasses/SingleModGroup.cs b/Penumbra/Mods/Subclasses/SingleModGroup.cs
index 74769c7e..f797a709 100644
--- a/Penumbra/Mods/Subclasses/SingleModGroup.cs
+++ b/Penumbra/Mods/Subclasses/SingleModGroup.cs
@@ -3,6 +3,8 @@ using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
+using Penumbra.Meta.Manipulations;
+using Penumbra.String.Classes;
namespace Penumbra.Mods.Subclasses;
@@ -114,6 +116,9 @@ public sealed class SingleModGroup : IModGroup
o.SetPosition(o.GroupIdx, i);
}
+ public void AddData(Setting setting, Dictionary redirections, HashSet manipulations)
+ => this[setting.AsIndex].AddData(redirections, manipulations);
+
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 6be07881..8f27e201 100644
--- a/Penumbra/Mods/TemporaryMod.cs
+++ b/Penumbra/Mods/TemporaryMod.cs
@@ -20,6 +20,28 @@ public class TemporaryMod : IMod
public readonly SubMod Default;
+ public AppliedModData GetData(ModSettings? settings = null)
+ {
+ Dictionary dict;
+ if (Default.FileSwapData.Count == 0)
+ {
+ dict = Default.FileData;
+ }
+ else if (Default.FileData.Count == 0)
+ {
+ dict = Default.FileSwapData;
+ }
+ else
+ {
+ // Need to ensure uniqueness.
+ dict = new Dictionary(Default.FileData.Count + Default.FileSwaps.Count);
+ foreach (var (gamePath, file) in Default.FileData.Concat(Default.FileSwaps))
+ dict.TryAdd(gamePath, file);
+ }
+
+ return new AppliedModData(dict, Default.Manipulations);
+ }
+
ISubMod IMod.Default
=> Default;
@@ -53,7 +75,8 @@ public class TemporaryMod : IMod
dir = ModCreator.CreateModFolder(modManager.BasePath, collection.Name, config.ReplaceNonAsciiOnImport, true);
var fileDir = Directory.CreateDirectory(Path.Combine(dir.FullName, "files"));
modManager.DataEditor.CreateMeta(dir, collection.Name, character ?? config.DefaultModAuthor,
- $"Mod generated from temporary collection {collection.Id} for {character ?? "Unknown Character"} with name {collection.Name}.", null, null);
+ $"Mod generated from temporary collection {collection.Id} for {character ?? "Unknown Character"} with name {collection.Name}.",
+ null, null);
var mod = new Mod(dir);
var defaultMod = mod.Default;
foreach (var (gamePath, fullPath) in collection.ResolvedFiles)
@@ -86,7 +109,8 @@ public class TemporaryMod : IMod
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport));
modManager.AddMod(dir);
- Penumbra.Log.Information($"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Identifier}.");
+ Penumbra.Log.Information(
+ $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Identifier}.");
}
catch (Exception e)
{