Now that was a lot of work.

This commit is contained in:
Ottermandias 2024-04-26 18:43:45 +02:00
parent 297be487b5
commit 1e5ed1c414
44 changed files with 1182 additions and 766 deletions

View file

@ -60,7 +60,7 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co
private void HandleDuplicate(Mod mod, FullPath duplicate, FullPath remaining, bool useModManager)
{
ModEditor.ApplyToAllOptions(mod, HandleSubMod);
ModEditor.ApplyToAllContainers(mod, HandleSubMod);
try
{
@ -73,7 +73,7 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co
return;
void HandleSubMod(IModDataContainer subMod, int groupIdx, int optionIdx)
void HandleSubMod(IModDataContainer subMod)
{
var changes = false;
var dict = subMod.Files.ToDictionary(kvp => kvp.Key,
@ -82,14 +82,9 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co
return;
if (useModManager)
{
modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict, SaveType.ImmediateSync);
}
modManager.OptionEditor.SetFiles(subMod, dict, SaveType.ImmediateSync);
else
{
subMod.Files = dict;
saveService.ImmediateSaveSync(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
}
saveService.ImmediateSaveSync(new ModSaveGroup(mod.ModPath, subMod, config.ReplaceNonAsciiOnImport));
}
}

View file

@ -29,8 +29,8 @@ public class ModEditor(
public int GroupIdx { get; private set; }
public int DataIdx { get; private set; }
public IModGroup? Group { get; private set; }
public IModDataContainer? Option { get; private set; }
public IModGroup? Group { get; private set; }
public IModDataContainer? Option { get; private set; }
public void LoadMod(Mod mod)
=> LoadMod(mod, -1, 0);
@ -63,10 +63,10 @@ public class ModEditor(
{
if (groupIdx == -1 && dataIdx == 0)
{
Group = null;
Option = Mod.Default;
GroupIdx = groupIdx;
DataIdx = dataIdx;
Group = null;
Option = Mod.Default;
GroupIdx = groupIdx;
DataIdx = dataIdx;
return;
}
@ -75,18 +75,18 @@ public class ModEditor(
Group = Mod.Groups[groupIdx];
if (dataIdx >= 0 && dataIdx < Group.DataContainers.Count)
{
Option = Group.DataContainers[dataIdx];
GroupIdx = groupIdx;
DataIdx = dataIdx;
Option = Group.DataContainers[dataIdx];
GroupIdx = groupIdx;
DataIdx = dataIdx;
return;
}
}
}
Group = null;
Option = Mod?.Default;
GroupIdx = -1;
DataIdx = 0;
Group = null;
Option = Mod?.Default;
GroupIdx = -1;
DataIdx = 0;
if (message)
Penumbra.Log.Error($"Loading invalid option {groupIdx} {dataIdx} for Mod {Mod?.Name ?? "Unknown"}.");
}
@ -105,23 +105,11 @@ public class ModEditor(
=> Clear();
/// <summary> Apply a option action to all available option in a mod, including the default option. </summary>
public static void ApplyToAllOptions(Mod mod, Action<IModDataContainer, int, int> action)
public static void ApplyToAllContainers(Mod mod, Action<IModDataContainer> action)
{
action(mod.Default, -1, 0);
foreach (var (group, groupIdx) in mod.Groups.WithIndex())
{
switch (group)
{
case SingleModGroup single:
for (var optionIdx = 0; optionIdx < single.OptionData.Count; ++optionIdx)
action(single.OptionData[optionIdx], groupIdx, optionIdx);
break;
case MultiModGroup multi:
for (var optionIdx = 0; optionIdx < multi.OptionData.Count; ++optionIdx)
action(multi.OptionData[optionIdx], groupIdx, optionIdx);
break;
}
}
action(mod.Default);
foreach (var container in mod.Groups.SelectMany(g => g.DataContainers))
action(container);
}
// Does not delete the base directory itself even if it is completely empty at the end.

View file

@ -24,8 +24,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
num += dict.TryAdd(path.Item2, file.File) ? 0 : 1;
}
var (groupIdx, dataIdx) = option.GetDataIndices();
modManager.OptionEditor.OptionSetFiles(mod, groupIdx, dataIdx, dict);
modManager.OptionEditor.SetFiles(option, dict);
files.UpdatePaths(mod, option);
Changes = false;
return num;
@ -40,15 +39,15 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
/// <summary> Remove all path redirections where the pointed-to file does not exist. </summary>
public void RemoveMissingPaths(Mod mod, IModDataContainer option)
{
void HandleSubMod(IModDataContainer subMod, int groupIdx, int optionIdx)
void HandleSubMod(IModDataContainer subMod)
{
var newDict = subMod.Files.Where(kvp => CheckAgainstMissing(mod, subMod, kvp.Value, kvp.Key, subMod == option))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
if (newDict.Count != subMod.Files.Count)
modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, newDict);
modManager.OptionEditor.SetFiles(subMod, newDict);
}
ModEditor.ApplyToAllOptions(mod, HandleSubMod);
ModEditor.ApplyToAllContainers(mod, HandleSubMod);
files.ClearMissingFiles();
}

View file

@ -16,7 +16,7 @@ public class ModMerger : IDisposable
{
private readonly Configuration _config;
private readonly CommunicatorService _communicator;
private readonly ModOptionEditor _editor;
private readonly ModGroupEditor _editor;
private readonly ModFileSystemSelector _selector;
private readonly DuplicateManager _duplicates;
private readonly ModManager _mods;
@ -32,14 +32,14 @@ public class ModMerger : IDisposable
private readonly Dictionary<string, string> _fileToFile = [];
private readonly HashSet<string> _createdDirectories = [];
private readonly HashSet<int> _createdGroups = [];
private readonly HashSet<IModDataOption> _createdOptions = [];
private readonly HashSet<IModOption> _createdOptions = [];
public readonly HashSet<IModDataContainer> SelectedOptions = [];
public readonly IReadOnlyList<string> Warnings = [];
public Exception? Error { get; private set; }
public ModMerger(ModManager mods, ModOptionEditor editor, ModFileSystemSelector selector, DuplicateManager duplicates,
public ModMerger(ModManager mods, ModGroupEditor editor, ModFileSystemSelector selector, DuplicateManager duplicates,
CommunicatorService communicator, ModCreator creator, Configuration config)
{
_editor = editor;
@ -100,22 +100,23 @@ public class ModMerger : IDisposable
var (group, groupIdx, groupCreated) = _editor.FindOrAddModGroup(MergeToMod!, originalGroup.Type, originalGroup.Name);
if (groupCreated)
_createdGroups.Add(groupIdx);
if (group.Type != originalGroup.Type)
((List<string>)Warnings).Add(
$"The merged group {group.Name} already existed, but has a different type {group.Type} than the original group of type {originalGroup.Type}.");
if (group == null)
throw new Exception(
$"The merged group {originalGroup.Name} already existed, but had a different type than the original group of type {originalGroup.Type}.");
foreach (var originalOption in group.DataContainers)
{
var (option, _, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, originalOption.GetName());
var (option, _, optionCreated) = _editor.FindOrAddOption(group, originalOption.GetName());
if (optionCreated)
{
_createdOptions.Add((IModDataOption)option);
MergeIntoOption([originalOption], (IModDataOption)option, false);
_createdOptions.Add(option!);
// #TODO DataContainer <> Option.
MergeIntoOption([originalOption], (IModDataContainer)option!, false);
}
else
{
throw new Exception(
$"Could not merge {MergeFromMod!.Name} into {MergeToMod!.Name}: The option {option.FullName} already existed.");
$"Could not merge {MergeFromMod!.Name} into {MergeToMod!.Name}: The option {option!.FullName} already existed.");
}
}
}
@ -138,9 +139,9 @@ public class ModMerger : IDisposable
var (group, groupIdx, groupCreated) = _editor.FindOrAddModGroup(MergeToMod!, GroupType.Multi, groupName, SaveType.None);
if (groupCreated)
_createdGroups.Add(groupIdx);
var (option, _, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, optionName, SaveType.None);
var (option, _, optionCreated) = _editor.FindOrAddOption(group!, optionName, SaveType.None);
if (optionCreated)
_createdOptions.Add((IModDataOption)option);
_createdOptions.Add(option!);
var dir = ModCreator.NewOptionDirectory(MergeToMod!.ModPath, groupName, _config.ReplaceNonAsciiOnImport);
if (!dir.Exists)
_createdDirectories.Add(dir.FullName);
@ -148,7 +149,8 @@ public class ModMerger : IDisposable
if (!dir.Exists)
_createdDirectories.Add(dir.FullName);
CopyFiles(dir);
MergeIntoOption(MergeFromMod!.AllDataContainers.Reverse(), (IModDataOption)option, true);
// #TODO DataContainer <> Option.
MergeIntoOption(MergeFromMod!.AllDataContainers.Reverse(), (IModDataContainer)option!, true);
}
private void MergeIntoOption(IEnumerable<IModDataContainer> mergeOptions, IModDataContainer option, bool fromFileToFile)
@ -184,10 +186,9 @@ public class ModMerger : IDisposable
}
}
var (groupIdx, dataIdx) = option.GetDataIndices();
_editor.OptionSetFiles(MergeToMod!, groupIdx, dataIdx, redirections, SaveType.None);
_editor.OptionSetFileSwaps(MergeToMod!, groupIdx, dataIdx, swaps, SaveType.None);
_editor.OptionSetManipulations(MergeToMod!, groupIdx, dataIdx, manips, SaveType.ImmediateSync);
_editor.SetFiles(option, redirections, SaveType.None);
_editor.SetFileSwaps(option, swaps, SaveType.None);
_editor.SetManipulations(option, manips, SaveType.ImmediateSync);
return;
bool GetFullPath(FullPath input, out FullPath ret)
@ -261,30 +262,31 @@ public class ModMerger : IDisposable
if (mods.Count == 1)
{
var files = CopySubModFiles(mods[0], dir);
_editor.OptionSetFiles(result, -1, 0, files);
_editor.OptionSetFileSwaps(result, -1, 0, mods[0].FileSwaps);
_editor.OptionSetManipulations(result, -1, 0, mods[0].Manipulations);
_editor.SetFiles(result.Default, files);
_editor.SetFileSwaps(result.Default, mods[0].FileSwaps);
_editor.SetManipulations(result.Default, mods[0].Manipulations);
}
else
{
foreach (var originalOption in mods)
{
if (originalOption.Group is not {} originalGroup)
if (originalOption.Group is not { } originalGroup)
{
var files = CopySubModFiles(mods[0], dir);
_editor.OptionSetFiles(result, -1, 0, files);
_editor.OptionSetFileSwaps(result, -1, 0, mods[0].FileSwaps);
_editor.OptionSetManipulations(result, -1, 0, mods[0].Manipulations);
_editor.SetFiles(result.Default, files);
_editor.SetFileSwaps(result.Default, mods[0].FileSwaps);
_editor.SetManipulations(result.Default, mods[0].Manipulations);
}
else
{
var (group, groupIdx, _) = _editor.FindOrAddModGroup(result, originalGroup.Type, originalGroup.Name);
var (option, optionIdx, _) = _editor.FindOrAddOption(result, groupIdx, originalOption.GetName());
var folder = Path.Combine(dir.FullName, group.Name, option.Name);
// TODO DataContainer <> Option.
var (group, _, _) = _editor.FindOrAddModGroup(result, originalGroup.Type, originalGroup.Name);
var (option, _, _) = _editor.FindOrAddOption(group!, originalOption.GetName());
var folder = Path.Combine(dir.FullName, group!.Name, option!.Name);
var files = CopySubModFiles(originalOption, new DirectoryInfo(folder));
_editor.OptionSetFiles(result, groupIdx, optionIdx, files);
_editor.OptionSetFileSwaps(result, groupIdx, optionIdx, originalOption.FileSwaps);
_editor.OptionSetManipulations(result, groupIdx, optionIdx, originalOption.Manipulations);
_editor.SetFiles((IModDataContainer)option, files);
_editor.SetFileSwaps((IModDataContainer)option, originalOption.FileSwaps);
_editor.SetManipulations((IModDataContainer)option, originalOption.Manipulations);
}
}
}
@ -339,16 +341,15 @@ public class ModMerger : IDisposable
{
foreach (var option in _createdOptions)
{
var (groupIdx, optionIdx) = option.GetOptionIndices();
_editor.DeleteOption(MergeToMod!, groupIdx, optionIdx);
_editor.DeleteOption(option);
Penumbra.Log.Verbose($"[Merger] Removed option {option.FullName}.");
}
foreach (var group in _createdGroups)
{
var groupName = MergeToMod!.Groups[group];
_editor.DeleteModGroup(MergeToMod!, group);
Penumbra.Log.Verbose($"[Merger] Removed option group {groupName}.");
_editor.DeleteModGroup(groupName);
Penumbra.Log.Verbose($"[Merger] Removed option group {groupName.Name}.");
}
foreach (var dir in _createdDirectories)

View file

@ -145,12 +145,12 @@ public class ModMetaEditor(ModManager modManager)
Split(currentOption.Manipulations);
}
public void Apply(Mod mod, int groupIdx, int optionIdx)
public void Apply(IModDataContainer container)
{
if (!Changes)
return;
modManager.OptionEditor.OptionSetManipulations(mod, groupIdx, optionIdx, Recombine().ToHashSet());
modManager.OptionEditor.SetManipulations(container, Recombine().ToHashSet());
Changes = false;
}

View file

@ -283,12 +283,12 @@ public class ModNormalizer(ModManager _modManager, Configuration _config)
switch (group)
{
case SingleModGroup single:
foreach (var (_, optionIdx) in single.OptionData.WithIndex())
_modManager.OptionEditor.OptionSetFiles(Mod, groupIdx, optionIdx, _redirections[groupIdx + 1][optionIdx]);
foreach (var (option, optionIdx) in single.OptionData.WithIndex())
_modManager.OptionEditor.SetFiles(option, _redirections[groupIdx + 1][optionIdx]);
break;
case MultiModGroup multi:
foreach (var (_, optionIdx) in multi.OptionData.WithIndex())
_modManager.OptionEditor.OptionSetFiles(Mod, groupIdx, optionIdx, _redirections[groupIdx + 1][optionIdx]);
foreach (var (option, optionIdx) in multi.OptionData.WithIndex())
_modManager.OptionEditor.SetFiles(option, _redirections[groupIdx + 1][optionIdx]);
break;
}
}

View file

@ -17,12 +17,12 @@ public class ModSwapEditor(ModManager modManager)
Changes = false;
}
public void Apply(Mod mod, int groupIdx, int optionIdx)
public void Apply(IModDataContainer container)
{
if (!Changes)
return;
modManager.OptionEditor.OptionSetFileSwaps(mod, groupIdx, optionIdx, _swaps);
modManager.OptionEditor.SetFileSwaps(container, _swaps);
Changes = false;
}