Remove ISubMod.

This commit is contained in:
Ottermandias 2024-04-19 18:28:25 +02:00
parent 8fc7de64d9
commit 9f4c6767f8
23 changed files with 123 additions and 184 deletions

View file

@ -152,7 +152,7 @@ public partial class TexToolsImporter
} }
// Iterate through all pages // Iterate through all pages
var options = new List<ISubMod>(); var options = new List<SubMod>();
var groupPriority = ModPriority.Default; var groupPriority = ModPriority.Default;
var groupNames = new HashSet<string>(); var groupNames = new HashSet<string>();
foreach (var page in modList.ModPackPages) foreach (var page in modList.ModPackPages)

View file

@ -29,7 +29,7 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co
Worker = Task.Run(() => CheckDuplicates(filesTmp, _cancellationTokenSource.Token), _cancellationTokenSource.Token); Worker = Task.Run(() => CheckDuplicates(filesTmp, _cancellationTokenSource.Token), _cancellationTokenSource.Token);
} }
public void DeleteDuplicates(ModFileCollection files, Mod mod, ISubMod option, bool useModManager) public void DeleteDuplicates(ModFileCollection files, Mod mod, SubMod option, bool useModManager)
{ {
if (!Worker.IsCompleted || _duplicates.Count == 0) if (!Worker.IsCompleted || _duplicates.Count == 0)
return; return;
@ -72,7 +72,7 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co
return; return;
void HandleSubMod(ISubMod subMod, int groupIdx, int optionIdx) void HandleSubMod(SubMod subMod, int groupIdx, int optionIdx)
{ {
var changes = false; var changes = false;
var dict = subMod.Files.ToDictionary(kvp => kvp.Key, var dict = subMod.Files.ToDictionary(kvp => kvp.Key,
@ -86,8 +86,7 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co
} }
else else
{ {
var sub = (SubMod)subMod; subMod.FileData = dict;
sub.FileData = dict;
saveService.ImmediateSaveSync(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport)); saveService.ImmediateSaveSync(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
} }
} }

View file

@ -5,7 +5,7 @@ namespace Penumbra.Mods.Editor;
public class FileRegistry : IEquatable<FileRegistry> public class FileRegistry : IEquatable<FileRegistry>
{ {
public readonly List<(ISubMod, Utf8GamePath)> SubModUsage = []; public readonly List<(SubMod, Utf8GamePath)> SubModUsage = [];
public FullPath File { get; private init; } public FullPath File { get; private init; }
public Utf8RelPath RelPath { get; private init; } public Utf8RelPath RelPath { get; private init; }
public long FileSize { get; private init; } public long FileSize { get; private init; }

View file

@ -6,8 +6,8 @@ using Penumbra.String.Classes;
namespace Penumbra.Mods.Editor; namespace Penumbra.Mods.Editor;
public record struct AppliedModData( public record struct AppliedModData(
IReadOnlyCollection<KeyValuePair<Utf8GamePath, FullPath>> FileRedirections, Dictionary<Utf8GamePath, FullPath> FileRedirections,
IReadOnlyCollection<MetaManipulation> Manipulations) HashSet<MetaManipulation> Manipulations)
{ {
public static readonly AppliedModData Empty = new([], []); public static readonly AppliedModData Empty = new([], []);
} }
@ -19,13 +19,9 @@ public interface IMod
public int Index { get; } public int Index { get; }
public ModPriority Priority { get; } public ModPriority Priority { get; }
public AppliedModData GetData(ModSettings? settings = null);
public ISubMod Default { get; }
public IReadOnlyList<IModGroup> Groups { get; } public IReadOnlyList<IModGroup> Groups { get; }
public IEnumerable<SubMod> AllSubMods { get; } public AppliedModData GetData(ModSettings? settings = null);
// Cache // Cache
public int TotalManipulations { get; } public int TotalManipulations { get; }

View file

@ -29,7 +29,7 @@ public class ModEditor(
public int OptionIdx { get; private set; } public int OptionIdx { get; private set; }
public IModGroup? Group { get; private set; } public IModGroup? Group { get; private set; }
public ISubMod? Option { get; private set; } public SubMod? Option { get; private set; }
public void LoadMod(Mod mod) public void LoadMod(Mod mod)
=> LoadMod(mod, -1, 0); => LoadMod(mod, -1, 0);
@ -104,7 +104,7 @@ public class ModEditor(
=> Clear(); => Clear();
/// <summary> Apply a option action to all available option in a mod, including the default option. </summary> /// <summary> Apply a option action to all available option in a mod, including the default option. </summary>
public static void ApplyToAllOptions(Mod mod, Action<ISubMod, int, int> action) public static void ApplyToAllOptions(Mod mod, Action<SubMod, int, int> action)
{ {
action(mod.Default, -1, 0); action(mod.Default, -1, 0);
foreach (var (group, groupIdx) in mod.Groups.WithIndex()) foreach (var (group, groupIdx) in mod.Groups.WithIndex())

View file

@ -38,13 +38,13 @@ public class ModFileCollection : IDisposable
public bool Ready { get; private set; } = true; public bool Ready { get; private set; } = true;
public void UpdateAll(Mod mod, ISubMod option) public void UpdateAll(Mod mod, SubMod option)
{ {
UpdateFiles(mod, new CancellationToken()); UpdateFiles(mod, new CancellationToken());
UpdatePaths(mod, option, false, new CancellationToken()); UpdatePaths(mod, option, false, new CancellationToken());
} }
public void UpdatePaths(Mod mod, ISubMod option) public void UpdatePaths(Mod mod, SubMod option)
=> UpdatePaths(mod, option, true, new CancellationToken()); => UpdatePaths(mod, option, true, new CancellationToken());
public void Clear() public void Clear()
@ -59,7 +59,7 @@ public class ModFileCollection : IDisposable
public void ClearMissingFiles() public void ClearMissingFiles()
=> _missing.Clear(); => _missing.Clear();
public void RemoveUsedPath(ISubMod option, FileRegistry? file, Utf8GamePath gamePath) public void RemoveUsedPath(SubMod option, FileRegistry? file, Utf8GamePath gamePath)
{ {
_usedPaths.Remove(gamePath); _usedPaths.Remove(gamePath);
if (file != null) if (file != null)
@ -69,10 +69,10 @@ public class ModFileCollection : IDisposable
} }
} }
public void RemoveUsedPath(ISubMod option, FullPath file, Utf8GamePath gamePath) public void RemoveUsedPath(SubMod option, FullPath file, Utf8GamePath gamePath)
=> RemoveUsedPath(option, _available.FirstOrDefault(f => f.File.Equals(file)), gamePath); => RemoveUsedPath(option, _available.FirstOrDefault(f => f.File.Equals(file)), gamePath);
public void AddUsedPath(ISubMod option, FileRegistry? file, Utf8GamePath gamePath) public void AddUsedPath(SubMod option, FileRegistry? file, Utf8GamePath gamePath)
{ {
_usedPaths.Add(gamePath); _usedPaths.Add(gamePath);
if (file == null) if (file == null)
@ -82,7 +82,7 @@ public class ModFileCollection : IDisposable
file.SubModUsage.Add((option, gamePath)); file.SubModUsage.Add((option, gamePath));
} }
public void AddUsedPath(ISubMod option, FullPath file, Utf8GamePath gamePath) public void AddUsedPath(SubMod option, FullPath file, Utf8GamePath gamePath)
=> AddUsedPath(option, _available.FirstOrDefault(f => f.File.Equals(file)), gamePath); => AddUsedPath(option, _available.FirstOrDefault(f => f.File.Equals(file)), gamePath);
public void ChangeUsedPath(FileRegistry file, int pathIdx, Utf8GamePath gamePath) public void ChangeUsedPath(FileRegistry file, int pathIdx, Utf8GamePath gamePath)
@ -154,7 +154,7 @@ public class ModFileCollection : IDisposable
_usedPaths.Clear(); _usedPaths.Clear();
} }
private void UpdatePaths(Mod mod, ISubMod option, bool clearRegistries, CancellationToken tok) private void UpdatePaths(Mod mod, SubMod option, bool clearRegistries, CancellationToken tok)
{ {
tok.ThrowIfCancellationRequested(); tok.ThrowIfCancellationRequested();
ClearPaths(clearRegistries, tok); ClearPaths(clearRegistries, tok);

View file

@ -30,16 +30,16 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
return num; return num;
} }
public void Revert(Mod mod, ISubMod option) public void Revert(Mod mod, SubMod option)
{ {
files.UpdateAll(mod, option); files.UpdateAll(mod, option);
Changes = false; Changes = false;
} }
/// <summary> Remove all path redirections where the pointed-to file does not exist. </summary> /// <summary> Remove all path redirections where the pointed-to file does not exist. </summary>
public void RemoveMissingPaths(Mod mod, ISubMod option) public void RemoveMissingPaths(Mod mod, SubMod option)
{ {
void HandleSubMod(ISubMod subMod, int groupIdx, int optionIdx) void HandleSubMod(SubMod subMod, int groupIdx, int optionIdx)
{ {
var newDict = subMod.Files.Where(kvp => CheckAgainstMissing(mod, subMod, kvp.Value, kvp.Key, subMod == option)) var newDict = subMod.Files.Where(kvp => CheckAgainstMissing(mod, subMod, kvp.Value, kvp.Key, subMod == option))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
@ -61,7 +61,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
/// If path is empty, it will be deleted instead. /// If path is empty, it will be deleted instead.
/// If pathIdx is equal to the total number of paths, path will be added, otherwise replaced. /// If pathIdx is equal to the total number of paths, path will be added, otherwise replaced.
/// </summary> /// </summary>
public bool SetGamePath(ISubMod option, int fileIdx, int pathIdx, Utf8GamePath path) public bool SetGamePath(SubMod option, int fileIdx, int pathIdx, Utf8GamePath path)
{ {
if (!CanAddGamePath(path) || fileIdx < 0 || fileIdx > files.Available.Count) if (!CanAddGamePath(path) || fileIdx < 0 || fileIdx > files.Available.Count)
return false; return false;
@ -84,7 +84,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
/// Transform a set of files to the appropriate game paths with the given number of folders skipped, /// Transform a set of files to the appropriate game paths with the given number of folders skipped,
/// and add them to the given option. /// and add them to the given option.
/// </summary> /// </summary>
public int AddPathsToSelected(ISubMod option, IEnumerable<FileRegistry> files1, int skipFolders = 0) public int AddPathsToSelected(SubMod option, IEnumerable<FileRegistry> files1, int skipFolders = 0)
{ {
var failed = 0; var failed = 0;
foreach (var file in files1) foreach (var file in files1)
@ -111,7 +111,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
} }
/// <summary> Remove all paths in the current option from the given files. </summary> /// <summary> Remove all paths in the current option from the given files. </summary>
public void RemovePathsFromSelected(ISubMod option, IEnumerable<FileRegistry> files1) public void RemovePathsFromSelected(SubMod option, IEnumerable<FileRegistry> files1)
{ {
foreach (var file in files1) foreach (var file in files1)
{ {
@ -129,7 +129,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
} }
/// <summary> Delete all given files from your filesystem </summary> /// <summary> Delete all given files from your filesystem </summary>
public void DeleteFiles(Mod mod, ISubMod option, IEnumerable<FileRegistry> files1) public void DeleteFiles(Mod mod, SubMod option, IEnumerable<FileRegistry> files1)
{ {
var deletions = 0; var deletions = 0;
foreach (var file in files1) foreach (var file in files1)
@ -155,7 +155,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
} }
private bool CheckAgainstMissing(Mod mod, ISubMod option, FullPath file, Utf8GamePath key, bool removeUsed) private bool CheckAgainstMissing(Mod mod, SubMod option, FullPath file, Utf8GamePath key, bool removeUsed)
{ {
if (!files.Missing.Contains(file)) if (!files.Missing.Contains(file))
return true; return true;

View file

@ -151,7 +151,7 @@ public class ModMerger : IDisposable
MergeIntoOption(MergeFromMod!.AllSubMods.Reverse(), option, true); MergeIntoOption(MergeFromMod!.AllSubMods.Reverse(), option, true);
} }
private void MergeIntoOption(IEnumerable<ISubMod> mergeOptions, SubMod option, bool fromFileToFile) private void MergeIntoOption(IEnumerable<SubMod> mergeOptions, SubMod option, bool fromFileToFile)
{ {
var redirections = option.FileData.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); var redirections = option.FileData.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
var swaps = option.FileSwapData.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); var swaps = option.FileSwapData.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

View file

@ -103,7 +103,7 @@ public class ModMetaEditor(ModManager modManager)
Changes = true; Changes = true;
} }
public void Load(Mod mod, ISubMod currentOption) public void Load(Mod mod, SubMod currentOption)
{ {
OtherImcCount = 0; OtherImcCount = 0;
OtherEqpCount = 0; OtherEqpCount = 0;

View file

@ -11,7 +11,7 @@ public class ModSwapEditor(ModManager modManager)
public IReadOnlyDictionary<Utf8GamePath, FullPath> Swaps public IReadOnlyDictionary<Utf8GamePath, FullPath> Swaps
=> _swaps; => _swaps;
public void Revert(ISubMod option) public void Revert(SubMod option)
{ {
_swaps.SetTo(option.FileSwaps); _swaps.SetTo(option.FileSwaps);
Changes = false; Changes = false;

View file

@ -5,6 +5,7 @@ using Penumbra.GameData.Structs;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses; using Penumbra.Mods.Subclasses;
@ -15,14 +16,13 @@ public class ItemSwapContainer
private readonly MetaFileManager _manager; private readonly MetaFileManager _manager;
private readonly ObjectIdentification _identifier; private readonly ObjectIdentification _identifier;
private Dictionary<Utf8GamePath, FullPath> _modRedirections = []; private AppliedModData _appliedModData = AppliedModData.Empty;
private HashSet<MetaManipulation> _modManipulations = [];
public IReadOnlyDictionary<Utf8GamePath, FullPath> ModRedirections public IReadOnlyDictionary<Utf8GamePath, FullPath> ModRedirections
=> _modRedirections; => _appliedModData.FileRedirections;
public IReadOnlySet<MetaManipulation> ModManipulations public IReadOnlySet<MetaManipulation> ModManipulations
=> _modManipulations; => _appliedModData.Manipulations;
public readonly List<Swap> Swaps = []; public readonly List<Swap> Swaps = [];
@ -97,12 +97,11 @@ public class ItemSwapContainer
Clear(); Clear();
if (mod == null || mod.Index < 0) if (mod == null || mod.Index < 0)
{ {
_modRedirections = []; _appliedModData = AppliedModData.Empty;
_modManipulations = [];
} }
else else
{ {
(_modRedirections, _modManipulations) = ModSettings.GetResolveData(mod, settings); _appliedModData = ModSettings.GetResolveData(mod, settings);
} }
} }
@ -120,7 +119,7 @@ public class ItemSwapContainer
private Func<MetaManipulation, MetaManipulation> MetaResolver(ModCollection? collection) private Func<MetaManipulation, MetaManipulation> MetaResolver(ModCollection? collection)
{ {
var set = collection?.MetaCache?.Manipulations.ToHashSet() ?? _modManipulations; var set = collection?.MetaCache?.Manipulations.ToHashSet() ?? _appliedModData.Manipulations;
return m => set.TryGetValue(m, out var a) ? a : m; return m => set.TryGetValue(m, out var a) ? a : m;
} }

View file

@ -262,15 +262,12 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
} }
/// <summary> Add an existing option to a given group with default priority. </summary> /// <summary> Add an existing option to a given group with default priority. </summary>
public void AddOption(Mod mod, int groupIdx, ISubMod option) public void AddOption(Mod mod, int groupIdx, SubMod option)
=> AddOption(mod, groupIdx, option, ModPriority.Default); => AddOption(mod, groupIdx, option, ModPriority.Default);
/// <summary> Add an existing option to a given group with a given priority. </summary> /// <summary> Add an existing option to a given group with a given priority. </summary>
public void AddOption(Mod mod, int groupIdx, ISubMod option, ModPriority priority) public void AddOption(Mod mod, int groupIdx, SubMod option, ModPriority priority)
{ {
if (option is not SubMod o)
return;
var group = mod.Groups[groupIdx]; var group = mod.Groups[groupIdx];
switch (group) switch (group)
{ {
@ -280,12 +277,12 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
+ $"since only up to {IModGroup.MaxMultiOptions} options are supported in one group."); + $"since only up to {IModGroup.MaxMultiOptions} options are supported in one group.");
return; return;
case SingleModGroup s: case SingleModGroup s:
o.SetPosition(groupIdx, s.Count); option.SetPosition(groupIdx, s.Count);
s.OptionData.Add(o); s.OptionData.Add(option);
break; break;
case MultiModGroup m: case MultiModGroup m:
o.SetPosition(groupIdx, m.Count); option.SetPosition(groupIdx, m.Count);
m.PrioritizedOptions.Add((o, priority)); m.PrioritizedOptions.Add((option, priority));
break; break;
} }

View file

@ -32,6 +32,9 @@ public sealed class Mod : IMod
public ModPriority Priority public ModPriority Priority
=> ModPriority.Default; => ModPriority.Default;
IReadOnlyList<IModGroup> IMod.Groups
=> Groups;
internal Mod(DirectoryInfo modPath) internal Mod(DirectoryInfo modPath)
{ {
ModPath = modPath; ModPath = modPath;
@ -74,18 +77,12 @@ public sealed class Mod : IMod
group.AddData(config, dictRedirections, setManips); group.AddData(config, dictRedirections, setManips);
} }
((ISubMod)Default).AddData(dictRedirections, setManips); Default.AddData(dictRedirections, setManips);
return new AppliedModData(dictRedirections, setManips); return new AppliedModData(dictRedirections, setManips);
} }
ISubMod IMod.Default
=> Default;
IReadOnlyList<IModGroup> IMod.Groups
=> Groups;
public IEnumerable<SubMod> AllSubMods public IEnumerable<SubMod> AllSubMods
=> Groups.SelectMany(o => o).OfType<SubMod>().Prepend(Default); => Groups.SelectMany(o => o).Prepend(Default);
public List<FullPath> FindUnusedFiles() public List<FullPath> FindUnusedFiles()
{ {

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> /// <summary> Create a file for an option group from given data. </summary>
public void CreateOptionGroup(DirectoryInfo baseFolder, GroupType type, string name, public void CreateOptionGroup(DirectoryInfo baseFolder, GroupType type, string name,
ModPriority priority, int index, Setting defaultSettings, string desc, IEnumerable<ISubMod> subMods) ModPriority priority, int index, Setting defaultSettings, string desc, IEnumerable<SubMod> subMods)
{ {
switch (type) switch (type)
{ {
@ -248,7 +248,7 @@ public partial class ModCreator(SaveService _saveService, Configuration config,
Priority = priority, Priority = priority,
DefaultSettings = defaultSettings, DefaultSettings = defaultSettings,
}; };
group.PrioritizedOptions.AddRange(subMods.OfType<SubMod>().Select((s, idx) => (s, new ModPriority(idx)))); group.PrioritizedOptions.AddRange(subMods.Select((s, idx) => (s, new ModPriority(idx))));
_saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index, Config.ReplaceNonAsciiOnImport)); _saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index, Config.ReplaceNonAsciiOnImport));
break; break;
} }
@ -269,7 +269,7 @@ public partial class ModCreator(SaveService _saveService, Configuration config,
} }
/// <summary> Create the data for a given sub mod from its data and the folder it is based on. </summary> /// <summary> Create the data for a given sub mod from its data and the folder it is based on. </summary>
public ISubMod CreateSubMod(DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option) public SubMod CreateSubMod(DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option)
{ {
var list = optionFolder.EnumerateNonHiddenFiles() var list = optionFolder.EnumerateNonHiddenFiles()
.Select(f => (Utf8GamePath.FromFile(f, optionFolder, out var gamePath, true), gamePath, new FullPath(f))) .Select(f => (Utf8GamePath.FromFile(f, optionFolder, out var gamePath, true), gamePath, new FullPath(f)))
@ -288,7 +288,7 @@ public partial class ModCreator(SaveService _saveService, Configuration config,
} }
/// <summary> Create an empty sub mod for single groups with None options. </summary> /// <summary> Create an empty sub mod for single groups with None options. </summary>
internal static ISubMod CreateEmptySubMod(string name) internal static SubMod CreateEmptySubMod(string name)
=> new SubMod(null!) // Mod is irrelevant here, only used for saving. => new SubMod(null!) // Mod is irrelevant here, only used for saving.
{ {
Name = name, Name = name,

View file

@ -6,7 +6,7 @@ using Penumbra.String.Classes;
namespace Penumbra.Mods.Subclasses; namespace Penumbra.Mods.Subclasses;
public interface IModGroup : IReadOnlyCollection<ISubMod> public interface IModGroup : IReadOnlyCollection<SubMod>
{ {
public const int MaxMultiOptions = 63; public const int MaxMultiOptions = 63;
@ -18,7 +18,7 @@ public interface IModGroup : IReadOnlyCollection<ISubMod>
public ModPriority OptionPriority(Index optionIdx); public ModPriority OptionPriority(Index optionIdx);
public ISubMod this[Index idx] { get; } public SubMod this[Index idx] { get; }
public bool IsOption { get; } public bool IsOption { get; }
@ -37,7 +37,7 @@ public readonly struct ModSaveGroup : ISavable
private readonly DirectoryInfo _basePath; private readonly DirectoryInfo _basePath;
private readonly IModGroup? _group; private readonly IModGroup? _group;
private readonly int _groupIdx; private readonly int _groupIdx;
private readonly ISubMod? _defaultMod; private readonly SubMod? _defaultMod;
private readonly bool _onlyAscii; private readonly bool _onlyAscii;
public ModSaveGroup(Mod mod, int groupIdx, bool onlyAscii) public ModSaveGroup(Mod mod, int groupIdx, bool onlyAscii)
@ -59,7 +59,7 @@ public readonly struct ModSaveGroup : ISavable
_onlyAscii = onlyAscii; _onlyAscii = onlyAscii;
} }
public ModSaveGroup(DirectoryInfo basePath, ISubMod @default, bool onlyAscii) public ModSaveGroup(DirectoryInfo basePath, SubMod @default, bool onlyAscii)
{ {
_basePath = basePath; _basePath = basePath;
_groupIdx = -1; _groupIdx = -1;
@ -91,7 +91,7 @@ public readonly struct ModSaveGroup : ISavable
j.WriteStartArray(); j.WriteStartArray();
for (var idx = 0; idx < _group.Count; ++idx) for (var idx = 0; idx < _group.Count; ++idx)
{ {
ISubMod.WriteSubMod(j, serializer, _group[idx], _basePath, _group.Type switch SubMod.WriteSubMod(j, serializer, _group[idx], _basePath, _group.Type switch
{ {
GroupType.Multi => _group.OptionPriority(idx), GroupType.Multi => _group.OptionPriority(idx),
_ => null, _ => null,
@ -103,7 +103,7 @@ public readonly struct ModSaveGroup : ISavable
} }
else else
{ {
ISubMod.WriteSubMod(j, serializer, _defaultMod!, _basePath, null); SubMod.WriteSubMod(j, serializer, _defaultMod!, _basePath, null);
} }
} }
} }

View file

@ -1,67 +0,0 @@
using Newtonsoft.Json;
using Penumbra.Meta.Manipulations;
using Penumbra.String.Classes;
namespace Penumbra.Mods.Subclasses;
public interface ISubMod
{
public string Name { get; }
public string FullName { get; }
public string Description { get; }
public IReadOnlyDictionary<Utf8GamePath, FullPath> Files { get; }
public IReadOnlyDictionary<Utf8GamePath, FullPath> FileSwaps { get; }
public IReadOnlySet<MetaManipulation> Manipulations { get; }
public void AddData(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> 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)
{
j.WriteStartObject();
j.WritePropertyName(nameof(Name));
j.WriteValue(mod.Name);
j.WritePropertyName(nameof(Description));
j.WriteValue(mod.Description);
if (priority != null)
{
j.WritePropertyName(nameof(IModGroup.Priority));
j.WriteValue(priority.Value.Value);
}
j.WritePropertyName(nameof(mod.Files));
j.WriteStartObject();
foreach (var (gamePath, file) in mod.Files)
{
if (file.ToRelPath(basePath, out var relPath))
{
j.WritePropertyName(gamePath.ToString());
j.WriteValue(relPath.ToString());
}
}
j.WriteEndObject();
j.WritePropertyName(nameof(mod.FileSwaps));
j.WriteStartObject();
foreach (var (gamePath, file) in mod.FileSwaps)
{
j.WritePropertyName(gamePath.ToString());
j.WriteValue(file.ToString());
}
j.WriteEndObject();
j.WritePropertyName(nameof(mod.Manipulations));
serializer.Serialize(j, mod.Manipulations);
j.WriteEndObject();
}
}

View file

@ -2,6 +2,7 @@ using OtterGui;
using OtterGui.Filesystem; using OtterGui.Filesystem;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
using Penumbra.String.Classes; using Penumbra.String.Classes;
@ -34,44 +35,14 @@ public class ModSettings
}; };
// Return everything required to resolve things for a single mod with given settings (which can be null, in which case the default is used. // Return everything required to resolve things for a single mod with given settings (which can be null, in which case the default is used.
public static (Dictionary<Utf8GamePath, FullPath>, HashSet<MetaManipulation>) GetResolveData(Mod mod, ModSettings? settings) public static AppliedModData GetResolveData(Mod mod, ModSettings? settings)
{ {
if (settings == null) if (settings == null)
settings = DefaultSettings(mod); settings = DefaultSettings(mod);
else else
settings.Settings.FixSize(mod); settings.Settings.FixSize(mod);
var dict = new Dictionary<Utf8GamePath, FullPath>(); return mod.GetData(settings);
var set = new HashSet<MetaManipulation>();
foreach (var (group, index) in mod.Groups.WithIndex().OrderByDescending(g => g.Value.Priority))
{
if (group.Type is GroupType.Single)
{
if (group.Count > 0)
AddOption(group[settings.Settings[index].AsIndex]);
}
else
{
foreach (var (option, optionIdx) in group.WithIndex().OrderByDescending(o => group.OptionPriority(o.Index)))
{
if (settings.Settings[index].HasFlag(optionIdx))
AddOption(option);
}
}
}
AddOption(mod.Default);
return (dict, set);
void AddOption(ISubMod option)
{
foreach (var (path, file) in option.Files.Concat(option.FileSwaps))
dict.TryAdd(path, file);
foreach (var manip in option.Manipulations)
set.Add(manip);
}
} }
// Automatically react to changes in a mods available options. // Automatically react to changes in a mods available options.

View file

@ -24,7 +24,7 @@ public sealed class MultiModGroup : IModGroup
public ModPriority OptionPriority(Index idx) public ModPriority OptionPriority(Index idx)
=> PrioritizedOptions[idx].Priority; => PrioritizedOptions[idx].Priority;
public ISubMod this[Index idx] public SubMod this[Index idx]
=> PrioritizedOptions[idx].Mod; => PrioritizedOptions[idx].Mod;
public bool IsOption public bool IsOption
@ -36,7 +36,7 @@ public sealed class MultiModGroup : IModGroup
public readonly List<(SubMod Mod, ModPriority Priority)> PrioritizedOptions = []; public readonly List<(SubMod Mod, ModPriority Priority)> PrioritizedOptions = [];
public IEnumerator<ISubMod> GetEnumerator() public IEnumerator<SubMod> GetEnumerator()
=> PrioritizedOptions.Select(o => o.Mod).GetEnumerator(); => PrioritizedOptions.Select(o => o.Mod).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
@ -117,7 +117,7 @@ public sealed class MultiModGroup : IModGroup
foreach (var (option, index) in PrioritizedOptions.WithIndex().OrderByDescending(o => o.Value.Priority)) foreach (var (option, index) in PrioritizedOptions.WithIndex().OrderByDescending(o => o.Value.Priority))
{ {
if (setting.HasFlag(index)) if (setting.HasFlag(index))
((ISubMod)option.Mod).AddData(redirections, manipulations); option.Mod.AddData(redirections, manipulations);
} }
} }

View file

@ -24,7 +24,7 @@ public sealed class SingleModGroup : IModGroup
public ModPriority OptionPriority(Index _) public ModPriority OptionPriority(Index _)
=> Priority; => Priority;
public ISubMod this[Index idx] public SubMod this[Index idx]
=> OptionData[idx]; => OptionData[idx];
public bool IsOption public bool IsOption
@ -34,7 +34,7 @@ public sealed class SingleModGroup : IModGroup
public int Count public int Count
=> OptionData.Count; => OptionData.Count;
public IEnumerator<ISubMod> GetEnumerator() public IEnumerator<SubMod> GetEnumerator()
=> OptionData.GetEnumerator(); => OptionData.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()

View file

@ -1,3 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor; using Penumbra.Mods.Editor;
@ -15,7 +16,7 @@ namespace Penumbra.Mods.Subclasses;
/// Nothing is checked for existence or validity when loading. /// Nothing is checked for existence or validity when loading.
/// Objects are also not checked for uniqueness, the first appearance of a game path or meta path decides. /// Objects are also not checked for uniqueness, the first appearance of a game path or meta path decides.
/// </summary> /// </summary>
public sealed class SubMod : ISubMod public sealed class SubMod
{ {
public string Name { get; set; } = "Default"; public string Name { get; set; } = "Default";
@ -31,6 +32,16 @@ public sealed class SubMod : ISubMod
public bool IsDefault public bool IsDefault
=> GroupIdx < 0; => GroupIdx < 0;
public void AddData(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> 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 Dictionary<Utf8GamePath, FullPath> FileData = []; public Dictionary<Utf8GamePath, FullPath> FileData = [];
public Dictionary<Utf8GamePath, FullPath> FileSwapData = []; public Dictionary<Utf8GamePath, FullPath> FileSwapData = [];
public HashSet<MetaManipulation> ManipulationData = []; public HashSet<MetaManipulation> ManipulationData = [];
@ -60,8 +71,8 @@ public sealed class SubMod : ISubMod
ManipulationData.Clear(); ManipulationData.Clear();
// Every option has a name, but priorities are only relevant for multi group options. // Every option has a name, but priorities are only relevant for multi group options.
Name = json[nameof(ISubMod.Name)]?.ToObject<string>() ?? string.Empty; Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty;
Description = json[nameof(ISubMod.Description)]?.ToObject<string>() ?? string.Empty; Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty;
priority = json[nameof(IModGroup.Priority)]?.ToObject<ModPriority>() ?? ModPriority.Default; priority = json[nameof(IModGroup.Priority)]?.ToObject<ModPriority>() ?? ModPriority.Default;
var files = (JObject?)json[nameof(Files)]; var files = (JObject?)json[nameof(Files)];
@ -104,4 +115,43 @@ public sealed class SubMod : ISubMod
} }
} }
} }
public static void WriteSubMod(JsonWriter j, JsonSerializer serializer, SubMod mod, DirectoryInfo basePath, ModPriority? priority)
{
j.WriteStartObject();
j.WritePropertyName(nameof(Name));
j.WriteValue(mod.Name);
j.WritePropertyName(nameof(Description));
j.WriteValue(mod.Description);
if (priority != null)
{
j.WritePropertyName(nameof(IModGroup.Priority));
j.WriteValue(priority.Value.Value);
}
j.WritePropertyName(nameof(mod.Files));
j.WriteStartObject();
foreach (var (gamePath, file) in mod.Files)
{
if (file.ToRelPath(basePath, out var relPath))
{
j.WritePropertyName(gamePath.ToString());
j.WriteValue(relPath.ToString());
}
}
j.WriteEndObject();
j.WritePropertyName(nameof(mod.FileSwaps));
j.WriteStartObject();
foreach (var (gamePath, file) in mod.FileSwaps)
{
j.WritePropertyName(gamePath.ToString());
j.WriteValue(file.ToString());
}
j.WriteEndObject();
j.WritePropertyName(nameof(mod.Manipulations));
serializer.Serialize(j, mod.Manipulations);
j.WriteEndObject();
}
} }

View file

@ -39,12 +39,9 @@ public class TemporaryMod : IMod
dict.TryAdd(gamePath, file); dict.TryAdd(gamePath, file);
} }
return new AppliedModData(dict, Default.Manipulations); return new AppliedModData(dict, Default.ManipulationData);
} }
ISubMod IMod.Default
=> Default;
public IReadOnlyList<IModGroup> Groups public IReadOnlyList<IModGroup> Groups
=> Array.Empty<IModGroup>(); => Array.Empty<IModGroup>();

View file

@ -192,7 +192,7 @@ public partial class ModEditWindow
ImGuiUtil.RightAlign(rightText); ImGuiUtil.RightAlign(rightText);
} }
private void PrintGamePath(int i, int j, FileRegistry registry, ISubMod subMod, Utf8GamePath gamePath) private void PrintGamePath(int i, int j, FileRegistry registry, SubMod subMod, Utf8GamePath gamePath)
{ {
using var id = ImRaii.PushId(j); using var id = ImRaii.PushId(j);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
@ -228,7 +228,7 @@ public partial class ModEditWindow
} }
} }
private void PrintNewGamePath(int i, FileRegistry registry, ISubMod subMod) private void PrintNewGamePath(int i, FileRegistry registry, SubMod subMod)
{ {
var tmp = _fileIdx == i && _pathIdx == -1 ? _gamePathEdit : string.Empty; var tmp = _fileIdx == i && _pathIdx == -1 ? _gamePathEdit : string.Empty;
var pos = ImGui.GetCursorPosX() - ImGui.GetFrameHeight(); var pos = ImGui.GetCursorPosX() - ImGui.GetFrameHeight();

View file

@ -227,7 +227,7 @@ public partial class ModEditWindow
return fileRegistry; return fileRegistry;
} }
private static (DirectoryInfo, int) GetPreferredPath(Mod mod, ISubMod subMod, bool replaceNonAscii) private static (DirectoryInfo, int) GetPreferredPath(Mod mod, SubMod subMod, bool replaceNonAscii)
{ {
var path = mod.ModPath; var path = mod.ModPath;
var subDirs = 0; var subDirs = 0;