mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-14 20:54:16 +01:00
This sucks so hard...
This commit is contained in:
parent
07afbfb229
commit
6b1743b776
33 changed files with 852 additions and 695 deletions
|
|
@ -201,7 +201,7 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
|
||||||
{
|
{
|
||||||
foreach (var name in optionNames)
|
foreach (var name in optionNames)
|
||||||
{
|
{
|
||||||
var optionIdx = multi.PrioritizedOptions.IndexOf(o => o.Mod.Name == name);
|
var optionIdx = multi.OptionData.IndexOf(o => o.Mod.Name == name);
|
||||||
if (optionIdx < 0)
|
if (optionIdx < 0)
|
||||||
return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args);
|
return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ public partial class TexToolsImporter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate through all pages
|
// Iterate through all pages
|
||||||
var options = new List<SubMod>();
|
var options = new List<MultiSubMod>();
|
||||||
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)
|
||||||
|
|
@ -183,7 +183,7 @@ public partial class TexToolsImporter
|
||||||
var optionFolder = ModCreator.NewSubFolderName(groupFolder, option.Name, _config.ReplaceNonAsciiOnImport)
|
var optionFolder = ModCreator.NewSubFolderName(groupFolder, option.Name, _config.ReplaceNonAsciiOnImport)
|
||||||
?? new DirectoryInfo(Path.Combine(groupFolder.FullName, $"Option {i + optionIdx + 1}"));
|
?? new DirectoryInfo(Path.Combine(groupFolder.FullName, $"Option {i + optionIdx + 1}"));
|
||||||
ExtractSimpleModList(optionFolder, option.ModsJsons);
|
ExtractSimpleModList(optionFolder, option.ModsJsons);
|
||||||
options.Add(_modManager.Creator.CreateSubMod(_currentModDirectory, optionFolder, option));
|
options.Add(_modManager.Creator.CreateSubMod(_currentModDirectory, optionFolder, option, new ModPriority(i)));
|
||||||
if (option.IsChecked)
|
if (option.IsChecked)
|
||||||
defaultSettings = group.SelectionType == GroupType.Multi
|
defaultSettings = group.SelectionType == GroupType.Multi
|
||||||
? defaultSettings!.Value | Setting.Multi(i)
|
? defaultSettings!.Value | Setting.Multi(i)
|
||||||
|
|
@ -203,7 +203,7 @@ public partial class TexToolsImporter
|
||||||
{
|
{
|
||||||
var option = group.OptionList[idx];
|
var option = group.OptionList[idx];
|
||||||
_currentOptionName = option.Name;
|
_currentOptionName = option.Name;
|
||||||
options.Insert(idx, SubMod.CreateForSaving(option.Name));
|
options.Insert(idx, MultiSubMod.CreateForSaving(option.Name, option.Description, ModPriority.Default));
|
||||||
if (option.IsChecked)
|
if (option.IsChecked)
|
||||||
defaultSettings = Setting.Single(idx);
|
defaultSettings = Setting.Single(idx);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,17 +50,15 @@ public unsafe class MetaFileManager
|
||||||
TexToolsMeta.WriteTexToolsMeta(this, mod.Default.Manipulations, mod.ModPath);
|
TexToolsMeta.WriteTexToolsMeta(this, mod.Default.Manipulations, mod.ModPath);
|
||||||
foreach (var group in mod.Groups)
|
foreach (var group in mod.Groups)
|
||||||
{
|
{
|
||||||
|
if (group is not ITexToolsGroup texToolsGroup)
|
||||||
|
continue;
|
||||||
|
|
||||||
var dir = ModCreator.NewOptionDirectory(mod.ModPath, group.Name, Config.ReplaceNonAsciiOnImport);
|
var dir = ModCreator.NewOptionDirectory(mod.ModPath, group.Name, Config.ReplaceNonAsciiOnImport);
|
||||||
if (!dir.Exists)
|
if (!dir.Exists)
|
||||||
dir.Create();
|
dir.Create();
|
||||||
|
|
||||||
var optionEnumerator = group switch
|
|
||||||
{
|
foreach (var option in texToolsGroup.OptionData)
|
||||||
SingleModGroup single => single.OptionData,
|
|
||||||
MultiModGroup multi => multi.PrioritizedOptions.Select(o => o.Mod),
|
|
||||||
_ => [],
|
|
||||||
};
|
|
||||||
foreach (var option in optionEnumerator)
|
|
||||||
{
|
{
|
||||||
var optionDir = ModCreator.NewOptionDirectory(dir, option.Name, Config.ReplaceNonAsciiOnImport);
|
var optionDir = ModCreator.NewOptionDirectory(dir, option.Name, Config.ReplaceNonAsciiOnImport);
|
||||||
if (!optionDir.Exists)
|
if (!optionDir.Exists)
|
||||||
|
|
@ -99,7 +97,7 @@ public unsafe class MetaFileManager
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ResidentResources.Reload();
|
ResidentResources.Reload();
|
||||||
if (collection?._cache == null)
|
if (collection._cache == null)
|
||||||
CharacterUtility.ResetAll();
|
CharacterUtility.ResetAll();
|
||||||
else
|
else
|
||||||
collection._cache.Meta.SetFiles();
|
collection._cache.Meta.SetFiles();
|
||||||
|
|
|
||||||
|
|
@ -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, SubMod option, bool useModManager)
|
public void DeleteDuplicates(ModFileCollection files, Mod mod, IModDataContainer 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(SubMod subMod, int groupIdx, int optionIdx)
|
void HandleSubMod(IModDataContainer 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,7 +86,7 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
subMod.FileData = dict;
|
subMod.Files = dict;
|
||||||
saveService.ImmediateSaveSync(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
saveService.ImmediateSaveSync(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ namespace Penumbra.Mods.Editor;
|
||||||
|
|
||||||
public class FileRegistry : IEquatable<FileRegistry>
|
public class FileRegistry : IEquatable<FileRegistry>
|
||||||
{
|
{
|
||||||
public readonly List<(SubMod, Utf8GamePath)> SubModUsage = [];
|
public readonly List<(IModDataContainer, 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; }
|
||||||
public int CurrentUsage;
|
public int CurrentUsage;
|
||||||
public bool IsOnPlayer;
|
public bool IsOnPlayer;
|
||||||
|
|
||||||
public static bool FromFile(DirectoryInfo modPath, FileInfo file, [NotNullWhen(true)] out FileRegistry? registry)
|
public static bool FromFile(DirectoryInfo modPath, FileInfo file, [NotNullWhen(true)] out FileRegistry? registry)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Compression;
|
using OtterGui.Compression;
|
||||||
using Penumbra.Mods.Subclasses;
|
using Penumbra.Mods.Subclasses;
|
||||||
|
|
@ -25,20 +24,20 @@ public class ModEditor(
|
||||||
public readonly MdlMaterialEditor MdlMaterialEditor = mdlMaterialEditor;
|
public readonly MdlMaterialEditor MdlMaterialEditor = mdlMaterialEditor;
|
||||||
public readonly FileCompactor Compactor = compactor;
|
public readonly FileCompactor Compactor = compactor;
|
||||||
|
|
||||||
public Mod? Mod { get; private set; }
|
public Mod? Mod { get; private set; }
|
||||||
public int GroupIdx { get; private set; }
|
public int GroupIdx { get; private set; }
|
||||||
public int OptionIdx { get; private set; }
|
public int DataIdx { get; private set; }
|
||||||
|
|
||||||
public IModGroup? Group { get; private set; }
|
public IModGroup? Group { get; private set; }
|
||||||
public SubMod? Option { get; private set; }
|
public IModDataContainer? Option { get; private set; }
|
||||||
|
|
||||||
public void LoadMod(Mod mod)
|
public void LoadMod(Mod mod)
|
||||||
=> LoadMod(mod, -1, 0);
|
=> LoadMod(mod, -1, 0);
|
||||||
|
|
||||||
public void LoadMod(Mod mod, int groupIdx, int optionIdx)
|
public void LoadMod(Mod mod, int groupIdx, int dataIdx)
|
||||||
{
|
{
|
||||||
Mod = mod;
|
Mod = mod;
|
||||||
LoadOption(groupIdx, optionIdx, true);
|
LoadOption(groupIdx, dataIdx, true);
|
||||||
Files.UpdateAll(mod, Option!);
|
Files.UpdateAll(mod, Option!);
|
||||||
SwapEditor.Revert(Option!);
|
SwapEditor.Revert(Option!);
|
||||||
MetaEditor.Load(Mod!, Option!);
|
MetaEditor.Load(Mod!, Option!);
|
||||||
|
|
@ -46,9 +45,9 @@ public class ModEditor(
|
||||||
MdlMaterialEditor.ScanModels(Mod!);
|
MdlMaterialEditor.ScanModels(Mod!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadOption(int groupIdx, int optionIdx)
|
public void LoadOption(int groupIdx, int dataIdx)
|
||||||
{
|
{
|
||||||
LoadOption(groupIdx, optionIdx, true);
|
LoadOption(groupIdx, dataIdx, true);
|
||||||
SwapEditor.Revert(Option!);
|
SwapEditor.Revert(Option!);
|
||||||
Files.UpdatePaths(Mod!, Option!);
|
Files.UpdatePaths(Mod!, Option!);
|
||||||
MetaEditor.Load(Mod!, Option!);
|
MetaEditor.Load(Mod!, Option!);
|
||||||
|
|
@ -57,44 +56,38 @@ public class ModEditor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Load the correct option by indices for the currently loaded mod if possible, unload if not. </summary>
|
/// <summary> Load the correct option by indices for the currently loaded mod if possible, unload if not. </summary>
|
||||||
private void LoadOption(int groupIdx, int optionIdx, bool message)
|
private void LoadOption(int groupIdx, int dataIdx, bool message)
|
||||||
{
|
{
|
||||||
if (Mod != null && Mod.Groups.Count > groupIdx)
|
if (Mod != null && Mod.Groups.Count > groupIdx)
|
||||||
{
|
{
|
||||||
if (groupIdx == -1 && optionIdx == 0)
|
if (groupIdx == -1 && dataIdx == 0)
|
||||||
{
|
{
|
||||||
Group = null;
|
Group = null;
|
||||||
Option = Mod.Default;
|
Option = Mod.Default;
|
||||||
GroupIdx = groupIdx;
|
GroupIdx = groupIdx;
|
||||||
OptionIdx = optionIdx;
|
DataIdx = dataIdx;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupIdx >= 0)
|
if (groupIdx >= 0)
|
||||||
{
|
{
|
||||||
Group = Mod.Groups[groupIdx];
|
Group = Mod.Groups[groupIdx];
|
||||||
switch(Group)
|
if (dataIdx >= 0 && dataIdx < Group.DataContainers.Count)
|
||||||
{
|
{
|
||||||
case SingleModGroup single when optionIdx >= 0 && optionIdx < single.OptionData.Count:
|
Option = Group.DataContainers[dataIdx];
|
||||||
Option = single.OptionData[optionIdx];
|
GroupIdx = groupIdx;
|
||||||
GroupIdx = groupIdx;
|
DataIdx = dataIdx;
|
||||||
OptionIdx = optionIdx;
|
return;
|
||||||
return;
|
|
||||||
case MultiModGroup multi when optionIdx >= 0 && optionIdx < multi.PrioritizedOptions.Count:
|
|
||||||
Option = multi.PrioritizedOptions[optionIdx].Mod;
|
|
||||||
GroupIdx = groupIdx;
|
|
||||||
OptionIdx = optionIdx;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Group = null;
|
Group = null;
|
||||||
Option = Mod?.Default;
|
Option = Mod?.Default;
|
||||||
GroupIdx = -1;
|
GroupIdx = -1;
|
||||||
OptionIdx = 0;
|
DataIdx = 0;
|
||||||
if (message)
|
if (message)
|
||||||
Penumbra.Log.Error($"Loading invalid option {groupIdx} {optionIdx} for Mod {Mod?.Name ?? "Unknown"}.");
|
Penumbra.Log.Error($"Loading invalid option {groupIdx} {dataIdx} for Mod {Mod?.Name ?? "Unknown"}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
|
|
@ -111,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<SubMod, int, int> action)
|
public static void ApplyToAllOptions(Mod mod, Action<IModDataContainer, 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())
|
||||||
|
|
@ -123,8 +116,8 @@ public class ModEditor(
|
||||||
action(single.OptionData[optionIdx], groupIdx, optionIdx);
|
action(single.OptionData[optionIdx], groupIdx, optionIdx);
|
||||||
break;
|
break;
|
||||||
case MultiModGroup multi:
|
case MultiModGroup multi:
|
||||||
for (var optionIdx = 0; optionIdx < multi.PrioritizedOptions.Count; ++optionIdx)
|
for (var optionIdx = 0; optionIdx < multi.OptionData.Count; ++optionIdx)
|
||||||
action(multi.PrioritizedOptions[optionIdx].Mod, groupIdx, optionIdx);
|
action(multi.OptionData[optionIdx], groupIdx, optionIdx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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, SubMod option)
|
public void UpdateAll(Mod mod, IModDataContainer 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, SubMod option)
|
public void UpdatePaths(Mod mod, IModDataContainer 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(SubMod option, FileRegistry? file, Utf8GamePath gamePath)
|
public void RemoveUsedPath(IModDataContainer 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(SubMod option, FullPath file, Utf8GamePath gamePath)
|
public void RemoveUsedPath(IModDataContainer 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(SubMod option, FileRegistry? file, Utf8GamePath gamePath)
|
public void AddUsedPath(IModDataContainer 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(SubMod option, FullPath file, Utf8GamePath gamePath)
|
public void AddUsedPath(IModDataContainer 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,14 +154,14 @@ public class ModFileCollection : IDisposable
|
||||||
_usedPaths.Clear();
|
_usedPaths.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdatePaths(Mod mod, SubMod option, bool clearRegistries, CancellationToken tok)
|
private void UpdatePaths(Mod mod, IModDataContainer option, bool clearRegistries, CancellationToken tok)
|
||||||
{
|
{
|
||||||
tok.ThrowIfCancellationRequested();
|
tok.ThrowIfCancellationRequested();
|
||||||
ClearPaths(clearRegistries, tok);
|
ClearPaths(clearRegistries, tok);
|
||||||
|
|
||||||
tok.ThrowIfCancellationRequested();
|
tok.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
foreach (var subMod in mod.AllSubMods)
|
foreach (var subMod in mod.AllDataContainers)
|
||||||
{
|
{
|
||||||
foreach (var (gamePath, file) in subMod.Files)
|
foreach (var (gamePath, file) in subMod.Files)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
|
||||||
Changes = false;
|
Changes = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Apply(Mod mod, SubMod option)
|
public int Apply(Mod mod, IModDataContainer option)
|
||||||
{
|
{
|
||||||
var dict = new Dictionary<Utf8GamePath, FullPath>();
|
var dict = new Dictionary<Utf8GamePath, FullPath>();
|
||||||
var num = 0;
|
var num = 0;
|
||||||
|
|
@ -24,23 +24,23 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
|
||||||
num += dict.TryAdd(path.Item2, file.File) ? 0 : 1;
|
num += dict.TryAdd(path.Item2, file.File) ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var (groupIdx, optionIdx) = option.GetIndices();
|
var (groupIdx, dataIdx) = option.GetDataIndices();
|
||||||
modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict);
|
modManager.OptionEditor.OptionSetFiles(mod, groupIdx, dataIdx, dict);
|
||||||
files.UpdatePaths(mod, option);
|
files.UpdatePaths(mod, option);
|
||||||
Changes = false;
|
Changes = false;
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Revert(Mod mod, SubMod option)
|
public void Revert(Mod mod, IModDataContainer 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, SubMod option)
|
public void RemoveMissingPaths(Mod mod, IModDataContainer option)
|
||||||
{
|
{
|
||||||
void HandleSubMod(SubMod subMod, int groupIdx, int optionIdx)
|
void HandleSubMod(IModDataContainer 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);
|
||||||
|
|
@ -62,7 +62,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(SubMod option, int fileIdx, int pathIdx, Utf8GamePath path)
|
public bool SetGamePath(IModDataContainer 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;
|
||||||
|
|
@ -85,7 +85,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(SubMod option, IEnumerable<FileRegistry> files1, int skipFolders = 0)
|
public int AddPathsToSelected(IModDataContainer option, IEnumerable<FileRegistry> files1, int skipFolders = 0)
|
||||||
{
|
{
|
||||||
var failed = 0;
|
var failed = 0;
|
||||||
foreach (var file in files1)
|
foreach (var file in files1)
|
||||||
|
|
@ -112,7 +112,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(SubMod option, IEnumerable<FileRegistry> files1)
|
public void RemovePathsFromSelected(IModDataContainer option, IEnumerable<FileRegistry> files1)
|
||||||
{
|
{
|
||||||
foreach (var file in files1)
|
foreach (var file in files1)
|
||||||
{
|
{
|
||||||
|
|
@ -130,7 +130,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, SubMod option, IEnumerable<FileRegistry> files1)
|
public void DeleteFiles(Mod mod, IModDataContainer option, IEnumerable<FileRegistry> files1)
|
||||||
{
|
{
|
||||||
var deletions = 0;
|
var deletions = 0;
|
||||||
foreach (var file in files1)
|
foreach (var file in files1)
|
||||||
|
|
@ -156,7 +156,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private bool CheckAgainstMissing(Mod mod, SubMod option, FullPath file, Utf8GamePath key, bool removeUsed)
|
private bool CheckAgainstMissing(Mod mod, IModDataContainer option, FullPath file, Utf8GamePath key, bool removeUsed)
|
||||||
{
|
{
|
||||||
if (!files.Missing.Contains(file))
|
if (!files.Missing.Contains(file))
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using ImGuizmoNET;
|
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
|
|
@ -33,9 +32,9 @@ public class ModMerger : IDisposable
|
||||||
private readonly Dictionary<string, string> _fileToFile = [];
|
private readonly Dictionary<string, string> _fileToFile = [];
|
||||||
private readonly HashSet<string> _createdDirectories = [];
|
private readonly HashSet<string> _createdDirectories = [];
|
||||||
private readonly HashSet<int> _createdGroups = [];
|
private readonly HashSet<int> _createdGroups = [];
|
||||||
private readonly HashSet<SubMod> _createdOptions = [];
|
private readonly HashSet<IModDataOption> _createdOptions = [];
|
||||||
|
|
||||||
public readonly HashSet<SubMod> SelectedOptions = [];
|
public readonly HashSet<IModDataContainer> SelectedOptions = [];
|
||||||
|
|
||||||
public readonly IReadOnlyList<string> Warnings = [];
|
public readonly IReadOnlyList<string> Warnings = [];
|
||||||
public Exception? Error { get; private set; }
|
public Exception? Error { get; private set; }
|
||||||
|
|
@ -94,7 +93,7 @@ public class ModMerger : IDisposable
|
||||||
|
|
||||||
private void MergeWithOptions()
|
private void MergeWithOptions()
|
||||||
{
|
{
|
||||||
MergeIntoOption(Enumerable.Repeat(MergeFromMod!.Default, 1), MergeToMod!.Default, false);
|
MergeIntoOption([MergeFromMod!.Default], MergeToMod!.Default, false);
|
||||||
|
|
||||||
foreach (var originalGroup in MergeFromMod!.Groups)
|
foreach (var originalGroup in MergeFromMod!.Groups)
|
||||||
{
|
{
|
||||||
|
|
@ -105,20 +104,13 @@ public class ModMerger : IDisposable
|
||||||
((List<string>)Warnings).Add(
|
((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}.");
|
$"The merged group {group.Name} already existed, but has a different type {group.Type} than the original group of type {originalGroup.Type}.");
|
||||||
|
|
||||||
var optionEnumerator = group switch
|
foreach (var originalOption in group.DataContainers)
|
||||||
{
|
{
|
||||||
SingleModGroup single => single.OptionData,
|
var (option, _, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, originalOption.GetName());
|
||||||
MultiModGroup multi => multi.PrioritizedOptions.Select(o => o.Mod),
|
|
||||||
_ => [],
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var originalOption in optionEnumerator)
|
|
||||||
{
|
|
||||||
var (option, _, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, originalOption.Name);
|
|
||||||
if (optionCreated)
|
if (optionCreated)
|
||||||
{
|
{
|
||||||
_createdOptions.Add(option);
|
_createdOptions.Add((IModDataOption)option);
|
||||||
MergeIntoOption(Enumerable.Repeat(originalOption, 1), option, false);
|
MergeIntoOption([originalOption], (IModDataOption)option, false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -136,7 +128,7 @@ public class ModMerger : IDisposable
|
||||||
if (groupName.Length == 0 && optionName.Length == 0)
|
if (groupName.Length == 0 && optionName.Length == 0)
|
||||||
{
|
{
|
||||||
CopyFiles(MergeToMod!.ModPath);
|
CopyFiles(MergeToMod!.ModPath);
|
||||||
MergeIntoOption(MergeFromMod!.AllSubMods.Reverse(), MergeToMod!.Default, true);
|
MergeIntoOption(MergeFromMod!.AllDataContainers.Reverse(), MergeToMod!.Default, true);
|
||||||
}
|
}
|
||||||
else if (groupName.Length * optionName.Length == 0)
|
else if (groupName.Length * optionName.Length == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -148,7 +140,7 @@ public class ModMerger : IDisposable
|
||||||
_createdGroups.Add(groupIdx);
|
_createdGroups.Add(groupIdx);
|
||||||
var (option, _, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, optionName, SaveType.None);
|
var (option, _, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, optionName, SaveType.None);
|
||||||
if (optionCreated)
|
if (optionCreated)
|
||||||
_createdOptions.Add(option);
|
_createdOptions.Add((IModDataOption)option);
|
||||||
var dir = ModCreator.NewOptionDirectory(MergeToMod!.ModPath, groupName, _config.ReplaceNonAsciiOnImport);
|
var dir = ModCreator.NewOptionDirectory(MergeToMod!.ModPath, groupName, _config.ReplaceNonAsciiOnImport);
|
||||||
if (!dir.Exists)
|
if (!dir.Exists)
|
||||||
_createdDirectories.Add(dir.FullName);
|
_createdDirectories.Add(dir.FullName);
|
||||||
|
|
@ -156,14 +148,14 @@ public class ModMerger : IDisposable
|
||||||
if (!dir.Exists)
|
if (!dir.Exists)
|
||||||
_createdDirectories.Add(dir.FullName);
|
_createdDirectories.Add(dir.FullName);
|
||||||
CopyFiles(dir);
|
CopyFiles(dir);
|
||||||
MergeIntoOption(MergeFromMod!.AllSubMods.Reverse(), option, true);
|
MergeIntoOption(MergeFromMod!.AllDataContainers.Reverse(), (IModDataOption)option, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MergeIntoOption(IEnumerable<SubMod> mergeOptions, SubMod option, bool fromFileToFile)
|
private void MergeIntoOption(IEnumerable<IModDataContainer> mergeOptions, IModDataContainer option, bool fromFileToFile)
|
||||||
{
|
{
|
||||||
var redirections = option.FileData.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
var redirections = option.Files.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||||
var swaps = option.FileSwapData.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
var swaps = option.FileSwaps.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||||
var manips = option.ManipulationData.ToHashSet();
|
var manips = option.Manipulations.ToHashSet();
|
||||||
|
|
||||||
foreach (var originalOption in mergeOptions)
|
foreach (var originalOption in mergeOptions)
|
||||||
{
|
{
|
||||||
|
|
@ -171,31 +163,31 @@ public class ModMerger : IDisposable
|
||||||
{
|
{
|
||||||
if (!manips.Add(manip))
|
if (!manips.Add(manip))
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
$"Could not add meta manipulation {manip} from {originalOption.FullName} to {option.FullName} because another manipulation of the same data already exists in this option.");
|
$"Could not add meta manipulation {manip} from {originalOption.GetFullName()} to {option.GetFullName()} because another manipulation of the same data already exists in this option.");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (swapA, swapB) in originalOption.FileSwaps)
|
foreach (var (swapA, swapB) in originalOption.FileSwaps)
|
||||||
{
|
{
|
||||||
if (!swaps.TryAdd(swapA, swapB))
|
if (!swaps.TryAdd(swapA, swapB))
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
$"Could not add file swap {swapB} -> {swapA} from {originalOption.FullName} to {option.FullName} because another swap of the key already exists.");
|
$"Could not add file swap {swapB} -> {swapA} from {originalOption.GetFullName()} to {option.GetFullName()} because another swap of the key already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (gamePath, path) in originalOption.Files)
|
foreach (var (gamePath, path) in originalOption.Files)
|
||||||
{
|
{
|
||||||
if (!GetFullPath(path, out var newFile))
|
if (!GetFullPath(path, out var newFile))
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
$"Could not add file redirection {path} -> {gamePath} from {originalOption.FullName} to {option.FullName} because the file does not exist in the new mod.");
|
$"Could not add file redirection {path} -> {gamePath} from {originalOption.GetFullName()} to {option.GetFullName()} because the file does not exist in the new mod.");
|
||||||
if (!redirections.TryAdd(gamePath, newFile))
|
if (!redirections.TryAdd(gamePath, newFile))
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
$"Could not add file redirection {path} -> {gamePath} from {originalOption.FullName} to {option.FullName} because a redirection for the game path already exists.");
|
$"Could not add file redirection {path} -> {gamePath} from {originalOption.GetFullName()} to {option.GetFullName()} because a redirection for the game path already exists.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (groupIdx, optionIdx) = option.GetIndices();
|
var (groupIdx, dataIdx) = option.GetDataIndices();
|
||||||
_editor.OptionSetFiles(MergeToMod!, groupIdx, optionIdx, redirections, SaveType.None);
|
_editor.OptionSetFiles(MergeToMod!, groupIdx, dataIdx, redirections, SaveType.None);
|
||||||
_editor.OptionSetFileSwaps(MergeToMod!, groupIdx, optionIdx, swaps, SaveType.None);
|
_editor.OptionSetFileSwaps(MergeToMod!, groupIdx, dataIdx, swaps, SaveType.None);
|
||||||
_editor.OptionSetManipulations(MergeToMod!, groupIdx, optionIdx, manips, SaveType.ImmediateSync);
|
_editor.OptionSetManipulations(MergeToMod!, groupIdx, dataIdx, manips, SaveType.ImmediateSync);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool GetFullPath(FullPath input, out FullPath ret)
|
bool GetFullPath(FullPath input, out FullPath ret)
|
||||||
|
|
@ -270,30 +262,29 @@ public class ModMerger : IDisposable
|
||||||
{
|
{
|
||||||
var files = CopySubModFiles(mods[0], dir);
|
var files = CopySubModFiles(mods[0], dir);
|
||||||
_editor.OptionSetFiles(result, -1, 0, files);
|
_editor.OptionSetFiles(result, -1, 0, files);
|
||||||
_editor.OptionSetFileSwaps(result, -1, 0, mods[0].FileSwapData);
|
_editor.OptionSetFileSwaps(result, -1, 0, mods[0].FileSwaps);
|
||||||
_editor.OptionSetManipulations(result, -1, 0, mods[0].ManipulationData);
|
_editor.OptionSetManipulations(result, -1, 0, mods[0].Manipulations);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var originalOption in mods)
|
foreach (var originalOption in mods)
|
||||||
{
|
{
|
||||||
if (originalOption.IsDefault)
|
if (originalOption.Group is not {} originalGroup)
|
||||||
{
|
{
|
||||||
var files = CopySubModFiles(mods[0], dir);
|
var files = CopySubModFiles(mods[0], dir);
|
||||||
_editor.OptionSetFiles(result, -1, 0, files);
|
_editor.OptionSetFiles(result, -1, 0, files);
|
||||||
_editor.OptionSetFileSwaps(result, -1, 0, mods[0].FileSwapData);
|
_editor.OptionSetFileSwaps(result, -1, 0, mods[0].FileSwaps);
|
||||||
_editor.OptionSetManipulations(result, -1, 0, mods[0].ManipulationData);
|
_editor.OptionSetManipulations(result, -1, 0, mods[0].Manipulations);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var originalGroup = originalOption.Group;
|
var (group, groupIdx, _) = _editor.FindOrAddModGroup(result, originalGroup.Type, originalGroup.Name);
|
||||||
var (group, groupIdx, _) = _editor.FindOrAddModGroup(result, originalGroup.Type, originalGroup.Name);
|
var (option, optionIdx, _) = _editor.FindOrAddOption(result, groupIdx, originalOption.GetName());
|
||||||
var (option, optionIdx, _) = _editor.FindOrAddOption(result, groupIdx, originalOption.Name);
|
|
||||||
var folder = Path.Combine(dir.FullName, group.Name, option.Name);
|
var folder = Path.Combine(dir.FullName, group.Name, option.Name);
|
||||||
var files = CopySubModFiles(originalOption, new DirectoryInfo(folder));
|
var files = CopySubModFiles(originalOption, new DirectoryInfo(folder));
|
||||||
_editor.OptionSetFiles(result, groupIdx, optionIdx, files);
|
_editor.OptionSetFiles(result, groupIdx, optionIdx, files);
|
||||||
_editor.OptionSetFileSwaps(result, groupIdx, optionIdx, originalOption.FileSwapData);
|
_editor.OptionSetFileSwaps(result, groupIdx, optionIdx, originalOption.FileSwaps);
|
||||||
_editor.OptionSetManipulations(result, groupIdx, optionIdx, originalOption.ManipulationData);
|
_editor.OptionSetManipulations(result, groupIdx, optionIdx, originalOption.Manipulations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -315,11 +306,11 @@ public class ModMerger : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<Utf8GamePath, FullPath> CopySubModFiles(SubMod option, DirectoryInfo newMod)
|
private static Dictionary<Utf8GamePath, FullPath> CopySubModFiles(IModDataContainer option, DirectoryInfo newMod)
|
||||||
{
|
{
|
||||||
var ret = new Dictionary<Utf8GamePath, FullPath>(option.FileData.Count);
|
var ret = new Dictionary<Utf8GamePath, FullPath>(option.Files.Count);
|
||||||
var parentPath = ((Mod)option.Mod).ModPath.FullName;
|
var parentPath = ((Mod)option.Mod).ModPath.FullName;
|
||||||
foreach (var (path, file) in option.FileData)
|
foreach (var (path, file) in option.Files)
|
||||||
{
|
{
|
||||||
var target = Path.GetRelativePath(parentPath, file.FullName);
|
var target = Path.GetRelativePath(parentPath, file.FullName);
|
||||||
target = Path.Combine(newMod.FullName, target);
|
target = Path.Combine(newMod.FullName, target);
|
||||||
|
|
@ -348,7 +339,7 @@ public class ModMerger : IDisposable
|
||||||
{
|
{
|
||||||
foreach (var option in _createdOptions)
|
foreach (var option in _createdOptions)
|
||||||
{
|
{
|
||||||
var (groupIdx, optionIdx) = option.GetIndices();
|
var (groupIdx, optionIdx) = option.GetOptionIndices();
|
||||||
_editor.DeleteOption(MergeToMod!, groupIdx, optionIdx);
|
_editor.DeleteOption(MergeToMod!, groupIdx, optionIdx);
|
||||||
Penumbra.Log.Verbose($"[Merger] Removed option {option.FullName}.");
|
Penumbra.Log.Verbose($"[Merger] Removed option {option.FullName}.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ public class ModMetaEditor(ModManager modManager)
|
||||||
Changes = true;
|
Changes = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Load(Mod mod, SubMod currentOption)
|
public void Load(Mod mod, IModDataContainer currentOption)
|
||||||
{
|
{
|
||||||
OtherImcCount = 0;
|
OtherImcCount = 0;
|
||||||
OtherEqpCount = 0;
|
OtherEqpCount = 0;
|
||||||
|
|
@ -111,7 +111,7 @@ public class ModMetaEditor(ModManager modManager)
|
||||||
OtherGmpCount = 0;
|
OtherGmpCount = 0;
|
||||||
OtherEstCount = 0;
|
OtherEstCount = 0;
|
||||||
OtherRspCount = 0;
|
OtherRspCount = 0;
|
||||||
foreach (var option in mod.AllSubMods)
|
foreach (var option in mod.AllDataContainers)
|
||||||
{
|
{
|
||||||
if (option == currentOption)
|
if (option == currentOption)
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
|
@ -168,27 +169,11 @@ public class ModNormalizer(ModManager _modManager, Configuration _config)
|
||||||
foreach (var (group, groupIdx) in Mod.Groups.WithIndex())
|
foreach (var (group, groupIdx) in Mod.Groups.WithIndex())
|
||||||
{
|
{
|
||||||
var groupDir = ModCreator.CreateModFolder(directory, group.Name, _config.ReplaceNonAsciiOnImport, true);
|
var groupDir = ModCreator.CreateModFolder(directory, group.Name, _config.ReplaceNonAsciiOnImport, true);
|
||||||
switch (group)
|
_redirections[groupIdx + 1].EnsureCapacity(group.DataContainers.Count);
|
||||||
{
|
for (var i = _redirections[groupIdx + 1].Count; i < group.DataContainers.Count; ++i)
|
||||||
case SingleModGroup single:
|
_redirections[groupIdx + 1].Add([]);
|
||||||
_redirections[groupIdx + 1].EnsureCapacity(single.OptionData.Count);
|
foreach (var (data, dataIdx) in group.DataContainers.WithIndex())
|
||||||
for (var i = _redirections[groupIdx + 1].Count; i < single.OptionData.Count; ++i)
|
HandleSubMod(groupDir, data, _redirections[groupIdx + 1][dataIdx]);
|
||||||
_redirections[groupIdx + 1].Add([]);
|
|
||||||
|
|
||||||
foreach (var (option, optionIdx) in single.OptionData.WithIndex())
|
|
||||||
HandleSubMod(groupDir, option, _redirections[groupIdx + 1][optionIdx]);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case MultiModGroup multi:
|
|
||||||
_redirections[groupIdx + 1].EnsureCapacity(multi.PrioritizedOptions.Count);
|
|
||||||
for (var i = _redirections[groupIdx + 1].Count; i < multi.PrioritizedOptions.Count; ++i)
|
|
||||||
_redirections[groupIdx + 1].Add([]);
|
|
||||||
|
|
||||||
foreach (var ((option, _), optionIdx) in multi.PrioritizedOptions.WithIndex())
|
|
||||||
HandleSubMod(groupDir, option, _redirections[groupIdx + 1][optionIdx]);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -200,13 +185,14 @@ public class ModNormalizer(ModManager _modManager, Configuration _config)
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
void HandleSubMod(DirectoryInfo groupDir, SubMod option, Dictionary<Utf8GamePath, FullPath> newDict)
|
void HandleSubMod(DirectoryInfo groupDir, IModDataContainer option, Dictionary<Utf8GamePath, FullPath> newDict)
|
||||||
{
|
{
|
||||||
var optionDir = ModCreator.CreateModFolder(groupDir, option.Name, _config.ReplaceNonAsciiOnImport, true);
|
var name = option.GetName();
|
||||||
|
var optionDir = ModCreator.CreateModFolder(groupDir, name, _config.ReplaceNonAsciiOnImport, true);
|
||||||
|
|
||||||
newDict.Clear();
|
newDict.Clear();
|
||||||
newDict.EnsureCapacity(option.FileData.Count);
|
newDict.EnsureCapacity(option.Files.Count);
|
||||||
foreach (var (gamePath, fullPath) in option.FileData)
|
foreach (var (gamePath, fullPath) in option.Files)
|
||||||
{
|
{
|
||||||
var relPath = new Utf8RelPath(gamePath).ToString();
|
var relPath = new Utf8RelPath(gamePath).ToString();
|
||||||
var newFullPath = Path.Combine(optionDir.FullName, relPath);
|
var newFullPath = Path.Combine(optionDir.FullName, relPath);
|
||||||
|
|
@ -300,7 +286,7 @@ public class ModNormalizer(ModManager _modManager, Configuration _config)
|
||||||
_modManager.OptionEditor.OptionSetFiles(Mod, groupIdx, optionIdx, _redirections[groupIdx + 1][optionIdx]);
|
_modManager.OptionEditor.OptionSetFiles(Mod, groupIdx, optionIdx, _redirections[groupIdx + 1][optionIdx]);
|
||||||
break;
|
break;
|
||||||
case MultiModGroup multi:
|
case MultiModGroup multi:
|
||||||
foreach (var (_, optionIdx) in multi.PrioritizedOptions.WithIndex())
|
foreach (var (_, optionIdx) in multi.OptionData.WithIndex())
|
||||||
_modManager.OptionEditor.OptionSetFiles(Mod, groupIdx, optionIdx, _redirections[groupIdx + 1][optionIdx]);
|
_modManager.OptionEditor.OptionSetFiles(Mod, groupIdx, optionIdx, _redirections[groupIdx + 1][optionIdx]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ public class ModSwapEditor(ModManager modManager)
|
||||||
public IReadOnlyDictionary<Utf8GamePath, FullPath> Swaps
|
public IReadOnlyDictionary<Utf8GamePath, FullPath> Swaps
|
||||||
=> _swaps;
|
=> _swaps;
|
||||||
|
|
||||||
public void Revert(SubMod option)
|
public void Revert(IModDataContainer option)
|
||||||
{
|
{
|
||||||
_swaps.SetTo(option.FileSwaps);
|
_swaps.SetTo(option.FileSwaps);
|
||||||
Changes = false;
|
Changes = false;
|
||||||
|
|
|
||||||
|
|
@ -176,13 +176,13 @@ public class ModCacheManager : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateFileCount(Mod mod)
|
private static void UpdateFileCount(Mod mod)
|
||||||
=> mod.TotalFileCount = mod.AllSubMods.Sum(s => s.Files.Count);
|
=> mod.TotalFileCount = mod.AllDataContainers.Sum(s => s.Files.Count);
|
||||||
|
|
||||||
private static void UpdateSwapCount(Mod mod)
|
private static void UpdateSwapCount(Mod mod)
|
||||||
=> mod.TotalSwapCount = mod.AllSubMods.Sum(s => s.FileSwaps.Count);
|
=> mod.TotalSwapCount = mod.AllDataContainers.Sum(s => s.FileSwaps.Count);
|
||||||
|
|
||||||
private static void UpdateMetaCount(Mod mod)
|
private static void UpdateMetaCount(Mod mod)
|
||||||
=> mod.TotalManipulations = mod.AllSubMods.Sum(s => s.Manipulations.Count);
|
=> mod.TotalManipulations = mod.AllDataContainers.Sum(s => s.Manipulations.Count);
|
||||||
|
|
||||||
private static void UpdateHasOptions(Mod mod)
|
private static void UpdateHasOptions(Mod mod)
|
||||||
=> mod.HasOptions = mod.Groups.Any(o => o.IsOption);
|
=> mod.HasOptions = mod.Groups.Any(o => o.IsOption);
|
||||||
|
|
@ -194,10 +194,10 @@ public class ModCacheManager : IDisposable
|
||||||
{
|
{
|
||||||
var changedItems = (SortedList<string, object?>)mod.ChangedItems;
|
var changedItems = (SortedList<string, object?>)mod.ChangedItems;
|
||||||
changedItems.Clear();
|
changedItems.Clear();
|
||||||
foreach (var gamePath in mod.AllSubMods.SelectMany(m => m.Files.Keys.Concat(m.FileSwaps.Keys)))
|
foreach (var gamePath in mod.AllDataContainers.SelectMany(m => m.Files.Keys.Concat(m.FileSwaps.Keys)))
|
||||||
_identifier.Identify(changedItems, gamePath.ToString());
|
_identifier.Identify(changedItems, gamePath.ToString());
|
||||||
|
|
||||||
foreach (var manip in mod.AllSubMods.SelectMany(m => m.Manipulations))
|
foreach (var manip in mod.AllDataContainers.SelectMany(m => m.Manipulations))
|
||||||
ComputeChangedItems(_identifier, changedItems, manip);
|
ComputeChangedItems(_identifier, changedItems, manip);
|
||||||
|
|
||||||
mod.LowerChangedItemsString = string.Join("\0", mod.ChangedItems.Keys.Select(k => k.ToLowerInvariant()));
|
mod.LowerChangedItemsString = string.Join("\0", mod.ChangedItems.Keys.Select(k => k.ToLowerInvariant()));
|
||||||
|
|
@ -211,20 +211,11 @@ public class ModCacheManager : IDisposable
|
||||||
mod.HasOptions = false;
|
mod.HasOptions = false;
|
||||||
foreach (var group in mod.Groups)
|
foreach (var group in mod.Groups)
|
||||||
{
|
{
|
||||||
mod.HasOptions |= group.IsOption;
|
mod.HasOptions |= group.IsOption;
|
||||||
var optionEnumerator = group switch
|
var (files, swaps, manips) = group.GetCounts();
|
||||||
{
|
mod.TotalFileCount += files;
|
||||||
SingleModGroup single => single.OptionData,
|
mod.TotalSwapCount += swaps;
|
||||||
MultiModGroup multi => multi.PrioritizedOptions.Select(o => o.Mod),
|
mod.TotalManipulations += manips;
|
||||||
_ => [],
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var s in optionEnumerator)
|
|
||||||
{
|
|
||||||
mod.TotalFileCount += s.Files.Count;
|
|
||||||
mod.TotalSwapCount += s.FileSwaps.Count;
|
|
||||||
mod.TotalManipulations += s.Manipulations.Count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,14 +71,14 @@ public static partial class ModMigration
|
||||||
foreach (var unusedFile in mod.FindUnusedFiles().Where(f => !seenMetaFiles.Contains(f)))
|
foreach (var unusedFile in mod.FindUnusedFiles().Where(f => !seenMetaFiles.Contains(f)))
|
||||||
{
|
{
|
||||||
if (unusedFile.ToGamePath(mod.ModPath, out var gamePath)
|
if (unusedFile.ToGamePath(mod.ModPath, out var gamePath)
|
||||||
&& !mod.Default.FileData.TryAdd(gamePath, unusedFile))
|
&& !mod.Default.Files.TryAdd(gamePath, unusedFile))
|
||||||
Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod.Default.FileData[gamePath]}.");
|
Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod.Default.Files[gamePath]}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
mod.Default.FileSwapData.Clear();
|
mod.Default.FileSwaps.Clear();
|
||||||
mod.Default.FileSwapData.EnsureCapacity(swaps.Count);
|
mod.Default.FileSwaps.EnsureCapacity(swaps.Count);
|
||||||
foreach (var (gamePath, swapPath) in swaps)
|
foreach (var (gamePath, swapPath) in swaps)
|
||||||
mod.Default.FileSwapData.Add(gamePath, swapPath);
|
mod.Default.FileSwaps.Add(gamePath, swapPath);
|
||||||
|
|
||||||
creator.IncorporateMetaChanges(mod.Default, mod.ModPath, true);
|
creator.IncorporateMetaChanges(mod.Default, mod.ModPath, true);
|
||||||
foreach (var (_, index) in mod.Groups.WithIndex())
|
foreach (var (_, index) in mod.Groups.WithIndex())
|
||||||
|
|
@ -134,7 +134,7 @@ public static partial class ModMigration
|
||||||
};
|
};
|
||||||
mod.Groups.Add(newMultiGroup);
|
mod.Groups.Add(newMultiGroup);
|
||||||
foreach (var option in group.Options)
|
foreach (var option in group.Options)
|
||||||
newMultiGroup.PrioritizedOptions.Add((SubModFromOption(creator, mod, newMultiGroup, option, seenMetaFiles), optionPriority++));
|
newMultiGroup.OptionData.Add(SubModFromOption(creator, mod, newMultiGroup, option, optionPriority++, seenMetaFiles));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case GroupType.Single:
|
case GroupType.Single:
|
||||||
|
|
@ -158,22 +158,41 @@ public static partial class ModMigration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddFilesToSubMod(SubMod mod, DirectoryInfo basePath, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
private static void AddFilesToSubMod(IModDataContainer mod, DirectoryInfo basePath, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
||||||
{
|
{
|
||||||
foreach (var (relPath, gamePaths) in option.OptionFiles)
|
foreach (var (relPath, gamePaths) in option.OptionFiles)
|
||||||
{
|
{
|
||||||
var fullPath = new FullPath(basePath, relPath);
|
var fullPath = new FullPath(basePath, relPath);
|
||||||
foreach (var gamePath in gamePaths)
|
foreach (var gamePath in gamePaths)
|
||||||
mod.FileData.TryAdd(gamePath, fullPath);
|
mod.Files.TryAdd(gamePath, fullPath);
|
||||||
|
|
||||||
if (fullPath.Extension is ".meta" or ".rgsp")
|
if (fullPath.Extension is ".meta" or ".rgsp")
|
||||||
seenMetaFiles.Add(fullPath);
|
seenMetaFiles.Add(fullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SubMod SubModFromOption(ModCreator creator, Mod mod, IModGroup group, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
private static SingleSubMod SubModFromOption(ModCreator creator, Mod mod, SingleModGroup group, OptionV0 option,
|
||||||
|
HashSet<FullPath> seenMetaFiles)
|
||||||
{
|
{
|
||||||
var subMod = new SubMod(mod, group) { Name = option.OptionName };
|
var subMod = new SingleSubMod(mod, group)
|
||||||
|
{
|
||||||
|
Name = option.OptionName,
|
||||||
|
Description = option.OptionDesc,
|
||||||
|
};
|
||||||
|
AddFilesToSubMod(subMod, mod.ModPath, option, seenMetaFiles);
|
||||||
|
creator.IncorporateMetaChanges(subMod, mod.ModPath, false);
|
||||||
|
return subMod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MultiSubMod SubModFromOption(ModCreator creator, Mod mod, MultiModGroup group, OptionV0 option,
|
||||||
|
ModPriority priority, HashSet<FullPath> seenMetaFiles)
|
||||||
|
{
|
||||||
|
var subMod = new MultiSubMod(mod, group)
|
||||||
|
{
|
||||||
|
Name = option.OptionName,
|
||||||
|
Description = option.OptionDesc,
|
||||||
|
Priority = priority,
|
||||||
|
};
|
||||||
AddFilesToSubMod(subMod, mod.ModPath, option, seenMetaFiles);
|
AddFilesToSubMod(subMod, mod.ModPath, option, seenMetaFiles);
|
||||||
creator.IncorporateMetaChanges(subMod, mod.ModPath, false);
|
creator.IncorporateMetaChanges(subMod, mod.ModPath, false);
|
||||||
return subMod;
|
return subMod;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Security.AccessControl;
|
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
|
@ -179,10 +178,10 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
||||||
switch (mod.Groups[groupIdx])
|
switch (mod.Groups[groupIdx])
|
||||||
{
|
{
|
||||||
case MultiModGroup multi:
|
case MultiModGroup multi:
|
||||||
if (multi.PrioritizedOptions[optionIdx].Priority == newPriority)
|
if (multi.OptionData[optionIdx].Priority == newPriority)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
multi.PrioritizedOptions[optionIdx] = (multi.PrioritizedOptions[optionIdx].Mod, newPriority);
|
multi.OptionData[optionIdx].Priority = newPriority;
|
||||||
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, optionIdx, -1);
|
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, optionIdx, -1);
|
||||||
return;
|
return;
|
||||||
|
|
@ -213,70 +212,62 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Add a new empty option of the given name for the given group if it does not exist already. </summary>
|
/// <summary> Add a new empty option of the given name for the given group if it does not exist already. </summary>
|
||||||
public (SubMod, int, bool) FindOrAddOption(Mod mod, int groupIdx, string newName, SaveType saveType = SaveType.Queue)
|
public (IModOption, int, bool) FindOrAddOption(Mod mod, int groupIdx, string newName, SaveType saveType = SaveType.Queue)
|
||||||
{
|
{
|
||||||
var group = mod.Groups[groupIdx];
|
var group = mod.Groups[groupIdx];
|
||||||
switch (group)
|
var idx = group.Options.IndexOf(o => o.Name == newName);
|
||||||
{
|
if (idx >= 0)
|
||||||
case SingleModGroup single:
|
return (group.Options[idx], idx, false);
|
||||||
{
|
|
||||||
var idx = single.OptionData.IndexOf(o => o.Name == newName);
|
|
||||||
if (idx >= 0)
|
|
||||||
return (single.OptionData[idx], idx, false);
|
|
||||||
|
|
||||||
idx = single.AddOption(mod, newName);
|
idx = group.AddOption(mod, newName);
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
throw new Exception($"Could not create new option with name {newName} in {group.Name}.");
|
throw new Exception($"Could not create new option with name {newName} in {group.Name}.");
|
||||||
|
|
||||||
saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, idx, -1);
|
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, idx, -1);
|
||||||
return (single.OptionData[^1], single.OptionData.Count - 1, true);
|
return (group.Options[idx], idx, true);
|
||||||
}
|
|
||||||
case MultiModGroup multi:
|
|
||||||
{
|
|
||||||
var idx = multi.PrioritizedOptions.IndexOf(o => o.Mod.Name == newName);
|
|
||||||
if (idx >= 0)
|
|
||||||
return (multi.PrioritizedOptions[idx].Mod, idx, false);
|
|
||||||
|
|
||||||
idx = multi.AddOption(mod, newName);
|
|
||||||
if (idx < 0)
|
|
||||||
throw new Exception($"Could not create new option with name {newName} in {group.Name}.");
|
|
||||||
|
|
||||||
saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
|
||||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, idx, -1);
|
|
||||||
return (multi.PrioritizedOptions[^1].Mod, multi.PrioritizedOptions.Count - 1, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception($"{nameof(FindOrAddOption)} is not supported for mod groups of type {group.GetType()}.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Add an existing option to a given group with default priority. </summary>
|
/// <summary> Add an existing option to a given group. </summary>
|
||||||
public void AddOption(Mod mod, int groupIdx, SubMod option)
|
public void AddOption(Mod mod, int groupIdx, IModOption option)
|
||||||
=> AddOption(mod, groupIdx, option, ModPriority.Default);
|
|
||||||
|
|
||||||
/// <summary> Add an existing option to a given group with a given priority. </summary>
|
|
||||||
public void AddOption(Mod mod, int groupIdx, SubMod option, ModPriority priority)
|
|
||||||
{
|
{
|
||||||
var group = mod.Groups[groupIdx];
|
var group = mod.Groups[groupIdx];
|
||||||
int idx;
|
int idx;
|
||||||
switch (group)
|
switch (group)
|
||||||
{
|
{
|
||||||
case MultiModGroup { PrioritizedOptions.Count: >= IModGroup.MaxMultiOptions }:
|
case MultiModGroup { OptionData.Count: >= IModGroup.MaxMultiOptions }:
|
||||||
Penumbra.Log.Error(
|
Penumbra.Log.Error(
|
||||||
$"Could not add option {option.Name} to {group.Name} for mod {mod.Name}, "
|
$"Could not add option {option.Name} to {group.Name} for mod {mod.Name}, "
|
||||||
+ $"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:
|
||||||
|
{
|
||||||
idx = s.OptionData.Count;
|
idx = s.OptionData.Count;
|
||||||
s.OptionData.Add(option);
|
var newOption = new SingleSubMod(s.Mod, s)
|
||||||
|
{
|
||||||
|
Name = option.Name,
|
||||||
|
Description = option.Description,
|
||||||
|
};
|
||||||
|
if (option is IModDataContainer data)
|
||||||
|
IModDataContainer.Clone(data, newOption);
|
||||||
|
s.OptionData.Add(newOption);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case MultiModGroup m:
|
case MultiModGroup m:
|
||||||
idx = m.PrioritizedOptions.Count;
|
{
|
||||||
m.PrioritizedOptions.Add((option, priority));
|
idx = m.OptionData.Count;
|
||||||
|
var newOption = new MultiSubMod(m.Mod, m)
|
||||||
|
{
|
||||||
|
Name = option.Name,
|
||||||
|
Description = option.Description,
|
||||||
|
Priority = option is MultiSubMod s ? s.Priority : ModPriority.Default,
|
||||||
|
};
|
||||||
|
if (option is IModDataContainer data)
|
||||||
|
IModDataContainer.Clone(data, newOption);
|
||||||
|
m.OptionData.Add(newOption);
|
||||||
break;
|
break;
|
||||||
default:
|
}
|
||||||
return;
|
default: return;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||||
|
|
@ -295,7 +286,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case MultiModGroup m:
|
case MultiModGroup m:
|
||||||
m.PrioritizedOptions.RemoveAt(optionIdx);
|
m.OptionData.RemoveAt(optionIdx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,59 +306,59 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Set the meta manipulations for a given option. Replaces existing manipulations. </summary>
|
/// <summary> Set the meta manipulations for a given option. Replaces existing manipulations. </summary>
|
||||||
public void OptionSetManipulations(Mod mod, int groupIdx, int optionIdx, HashSet<MetaManipulation> manipulations,
|
public void OptionSetManipulations(Mod mod, int groupIdx, int dataContainerIdx, HashSet<MetaManipulation> manipulations,
|
||||||
SaveType saveType = SaveType.Queue)
|
SaveType saveType = SaveType.Queue)
|
||||||
{
|
{
|
||||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
var subMod = GetSubMod(mod, groupIdx, dataContainerIdx);
|
||||||
if (subMod.Manipulations.Count == manipulations.Count
|
if (subMod.Manipulations.Count == manipulations.Count
|
||||||
&& subMod.Manipulations.All(m => manipulations.TryGetValue(m, out var old) && old.EntryEquals(m)))
|
&& subMod.Manipulations.All(m => manipulations.TryGetValue(m, out var old) && old.EntryEquals(m)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, dataContainerIdx, -1);
|
||||||
subMod.ManipulationData.SetTo(manipulations);
|
subMod.Manipulations.SetTo(manipulations);
|
||||||
saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, mod, groupIdx, optionIdx, -1);
|
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, mod, groupIdx, dataContainerIdx, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Set the file redirections for a given option. Replaces existing redirections. </summary>
|
/// <summary> Set the file redirections for a given option. Replaces existing redirections. </summary>
|
||||||
public void OptionSetFiles(Mod mod, int groupIdx, int optionIdx, IReadOnlyDictionary<Utf8GamePath, FullPath> replacements,
|
public void OptionSetFiles(Mod mod, int groupIdx, int dataContainerIdx, IReadOnlyDictionary<Utf8GamePath, FullPath> replacements,
|
||||||
SaveType saveType = SaveType.Queue)
|
SaveType saveType = SaveType.Queue)
|
||||||
{
|
{
|
||||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
var subMod = GetSubMod(mod, groupIdx, dataContainerIdx);
|
||||||
if (subMod.FileData.SetEquals(replacements))
|
if (subMod.Files.SetEquals(replacements))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, dataContainerIdx, -1);
|
||||||
subMod.FileData.SetTo(replacements);
|
subMod.Files.SetTo(replacements);
|
||||||
saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, mod, groupIdx, optionIdx, -1);
|
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, mod, groupIdx, dataContainerIdx, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Add additional file redirections to a given option, keeping already existing ones. Only fires an event if anything is actually added.</summary>
|
/// <summary> Add additional file redirections to a given option, keeping already existing ones. Only fires an event if anything is actually added.</summary>
|
||||||
public void OptionAddFiles(Mod mod, int groupIdx, int optionIdx, IReadOnlyDictionary<Utf8GamePath, FullPath> additions)
|
public void OptionAddFiles(Mod mod, int groupIdx, int dataContainerIdx, IReadOnlyDictionary<Utf8GamePath, FullPath> additions)
|
||||||
{
|
{
|
||||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
var subMod = GetSubMod(mod, groupIdx, dataContainerIdx);
|
||||||
var oldCount = subMod.FileData.Count;
|
var oldCount = subMod.Files.Count;
|
||||||
subMod.FileData.AddFrom(additions);
|
subMod.Files.AddFrom(additions);
|
||||||
if (oldCount != subMod.FileData.Count)
|
if (oldCount != subMod.Files.Count)
|
||||||
{
|
{
|
||||||
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesAdded, mod, groupIdx, optionIdx, -1);
|
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesAdded, mod, groupIdx, dataContainerIdx, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Set the file swaps for a given option. Replaces existing swaps. </summary>
|
/// <summary> Set the file swaps for a given option. Replaces existing swaps. </summary>
|
||||||
public void OptionSetFileSwaps(Mod mod, int groupIdx, int optionIdx, IReadOnlyDictionary<Utf8GamePath, FullPath> swaps,
|
public void OptionSetFileSwaps(Mod mod, int groupIdx, int dataContainerIdx, IReadOnlyDictionary<Utf8GamePath, FullPath> swaps,
|
||||||
SaveType saveType = SaveType.Queue)
|
SaveType saveType = SaveType.Queue)
|
||||||
{
|
{
|
||||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
var subMod = GetSubMod(mod, groupIdx, dataContainerIdx);
|
||||||
if (subMod.FileSwapData.SetEquals(swaps))
|
if (subMod.FileSwaps.SetEquals(swaps))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, dataContainerIdx, -1);
|
||||||
subMod.FileSwapData.SetTo(swaps);
|
subMod.FileSwaps.SetTo(swaps);
|
||||||
saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, mod, groupIdx, optionIdx, -1);
|
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, mod, groupIdx, dataContainerIdx, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -389,17 +380,12 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Get the correct option for the given group and option index. </summary>
|
/// <summary> Get the correct option for the given group and option index. </summary>
|
||||||
private static SubMod GetSubMod(Mod mod, int groupIdx, int optionIdx)
|
private static IModDataContainer GetSubMod(Mod mod, int groupIdx, int dataContainerIdx)
|
||||||
{
|
{
|
||||||
if (groupIdx == -1 && optionIdx == 0)
|
if (groupIdx == -1 && dataContainerIdx == 0)
|
||||||
return mod.Default;
|
return mod.Default;
|
||||||
|
|
||||||
return mod.Groups[groupIdx] switch
|
return mod.Groups[groupIdx].DataContainers[dataContainerIdx];
|
||||||
{
|
|
||||||
SingleModGroup s => s.OptionData[optionIdx],
|
|
||||||
MultiModGroup m => m.PrioritizedOptions[optionIdx].Mod,
|
|
||||||
_ => throw new InvalidOperationException(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public sealed class Mod : IMod
|
||||||
internal Mod(DirectoryInfo modPath)
|
internal Mod(DirectoryInfo modPath)
|
||||||
{
|
{
|
||||||
ModPath = modPath;
|
ModPath = modPath;
|
||||||
Default = SubMod.CreateDefault(this);
|
Default = new DefaultSubMod(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
@ -61,8 +61,8 @@ public sealed class Mod : IMod
|
||||||
|
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
public readonly SubMod Default;
|
public readonly DefaultSubMod Default;
|
||||||
public readonly List<IModGroup> Groups = [];
|
public readonly List<IModGroup> Groups = [];
|
||||||
|
|
||||||
public AppliedModData GetData(ModSettings? settings = null)
|
public AppliedModData GetData(ModSettings? settings = null)
|
||||||
{
|
{
|
||||||
|
|
@ -77,21 +77,16 @@ public sealed class Mod : IMod
|
||||||
group.AddData(config, dictRedirections, setManips);
|
group.AddData(config, dictRedirections, setManips);
|
||||||
}
|
}
|
||||||
|
|
||||||
Default.AddData(dictRedirections, setManips);
|
Default.AddDataTo(dictRedirections, setManips);
|
||||||
return new AppliedModData(dictRedirections, setManips);
|
return new AppliedModData(dictRedirections, setManips);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<SubMod> AllSubMods
|
public IEnumerable<IModDataContainer> AllDataContainers
|
||||||
=> Groups.SelectMany(o => o switch
|
=> Groups.SelectMany(o => o.DataContainers).Prepend(Default);
|
||||||
{
|
|
||||||
SingleModGroup single => single.OptionData,
|
|
||||||
MultiModGroup multi => multi.PrioritizedOptions.Select(s => s.Mod),
|
|
||||||
_ => [],
|
|
||||||
}).Prepend(Default);
|
|
||||||
|
|
||||||
public List<FullPath> FindUnusedFiles()
|
public List<FullPath> FindUnusedFiles()
|
||||||
{
|
{
|
||||||
var modFiles = AllSubMods.SelectMany(o => o.Files)
|
var modFiles = AllDataContainers.SelectMany(o => o.Files)
|
||||||
.Select(p => p.Value)
|
.Select(p => p.Value)
|
||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
return ModPath.EnumerateDirectories()
|
return ModPath.EnumerateDirectories()
|
||||||
|
|
|
||||||
|
|
@ -112,10 +112,8 @@ public partial class ModCreator(
|
||||||
var defaultFile = _saveService.FileNames.OptionGroupFile(mod, -1, Config.ReplaceNonAsciiOnImport);
|
var defaultFile = _saveService.FileNames.OptionGroupFile(mod, -1, Config.ReplaceNonAsciiOnImport);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!File.Exists(defaultFile))
|
var jObject = File.Exists(defaultFile) ? JObject.Parse(File.ReadAllText(defaultFile)) : new JObject();
|
||||||
mod.Default.Load(mod.ModPath, new JObject(), out _);
|
IModDataContainer.Load(jObject, mod.Default, mod.ModPath);
|
||||||
else
|
|
||||||
mod.Default.Load(mod.ModPath, JObject.Parse(File.ReadAllText(defaultFile)), out _);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -154,7 +152,7 @@ public partial class ModCreator(
|
||||||
{
|
{
|
||||||
var changes = false;
|
var changes = false;
|
||||||
List<string> deleteList = new();
|
List<string> deleteList = new();
|
||||||
foreach (var subMod in mod.AllSubMods)
|
foreach (var subMod in mod.AllDataContainers)
|
||||||
{
|
{
|
||||||
var (localChanges, localDeleteList) = IncorporateMetaChanges(subMod, mod.ModPath, false);
|
var (localChanges, localDeleteList) = IncorporateMetaChanges(subMod, mod.ModPath, false);
|
||||||
changes |= localChanges;
|
changes |= localChanges;
|
||||||
|
|
@ -162,7 +160,7 @@ public partial class ModCreator(
|
||||||
deleteList.AddRange(localDeleteList);
|
deleteList.AddRange(localDeleteList);
|
||||||
}
|
}
|
||||||
|
|
||||||
SubMod.DeleteDeleteList(deleteList, delete);
|
IModDataContainer.DeleteDeleteList(deleteList, delete);
|
||||||
|
|
||||||
if (!changes)
|
if (!changes)
|
||||||
return;
|
return;
|
||||||
|
|
@ -176,10 +174,10 @@ public partial class ModCreator(
|
||||||
/// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod.
|
/// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod.
|
||||||
/// If delete is true, the files are deleted afterwards.
|
/// If delete is true, the files are deleted afterwards.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public (bool Changes, List<string> DeleteList) IncorporateMetaChanges(SubMod option, DirectoryInfo basePath, bool delete)
|
public (bool Changes, List<string> DeleteList) IncorporateMetaChanges(IModDataContainer option, DirectoryInfo basePath, bool delete)
|
||||||
{
|
{
|
||||||
var deleteList = new List<string>();
|
var deleteList = new List<string>();
|
||||||
var oldSize = option.ManipulationData.Count;
|
var oldSize = option.Manipulations.Count;
|
||||||
var deleteString = delete ? "with deletion." : "without deletion.";
|
var deleteString = delete ? "with deletion." : "without deletion.";
|
||||||
foreach (var (key, file) in option.Files.ToList())
|
foreach (var (key, file) in option.Files.ToList())
|
||||||
{
|
{
|
||||||
|
|
@ -189,7 +187,7 @@ public partial class ModCreator(
|
||||||
{
|
{
|
||||||
if (ext1 == ".meta" || ext2 == ".meta")
|
if (ext1 == ".meta" || ext2 == ".meta")
|
||||||
{
|
{
|
||||||
option.FileData.Remove(key);
|
option.Files.Remove(key);
|
||||||
if (!file.Exists)
|
if (!file.Exists)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -198,11 +196,11 @@ public partial class ModCreator(
|
||||||
Penumbra.Log.Verbose(
|
Penumbra.Log.Verbose(
|
||||||
$"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}");
|
$"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}");
|
||||||
deleteList.Add(file.FullName);
|
deleteList.Add(file.FullName);
|
||||||
option.ManipulationData.UnionWith(meta.MetaManipulations);
|
option.Manipulations.UnionWith(meta.MetaManipulations);
|
||||||
}
|
}
|
||||||
else if (ext1 == ".rgsp" || ext2 == ".rgsp")
|
else if (ext1 == ".rgsp" || ext2 == ".rgsp")
|
||||||
{
|
{
|
||||||
option.FileData.Remove(key);
|
option.Files.Remove(key);
|
||||||
if (!file.Exists)
|
if (!file.Exists)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -212,7 +210,7 @@ public partial class ModCreator(
|
||||||
$"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}");
|
$"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}");
|
||||||
deleteList.Add(file.FullName);
|
deleteList.Add(file.FullName);
|
||||||
|
|
||||||
option.ManipulationData.UnionWith(rgsp.MetaManipulations);
|
option.Manipulations.UnionWith(rgsp.MetaManipulations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -221,8 +219,8 @@ public partial class ModCreator(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SubMod.DeleteDeleteList(deleteList, delete);
|
IModDataContainer.DeleteDeleteList(deleteList, delete);
|
||||||
return (oldSize < option.ManipulationData.Count, deleteList);
|
return (oldSize < option.Manipulations.Count, deleteList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -238,7 +236,7 @@ public partial class ModCreator(
|
||||||
|
|
||||||
/// <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<SubMod> subMods)
|
ModPriority priority, int index, Setting defaultSettings, string desc, IEnumerable<MultiSubMod> subMods)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
|
|
@ -248,7 +246,7 @@ public partial class ModCreator(
|
||||||
group.Description = desc;
|
group.Description = desc;
|
||||||
group.Priority = priority;
|
group.Priority = priority;
|
||||||
group.DefaultSettings = defaultSettings;
|
group.DefaultSettings = defaultSettings;
|
||||||
group.PrioritizedOptions.AddRange(subMods.Select((s, idx) => (s, new ModPriority(idx))));
|
group.OptionData.AddRange(subMods.Select(s => s.Clone(null!, group)));
|
||||||
_saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index, Config.ReplaceNonAsciiOnImport));
|
_saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index, Config.ReplaceNonAsciiOnImport));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -258,7 +256,7 @@ public partial class ModCreator(
|
||||||
group.Description = desc;
|
group.Description = desc;
|
||||||
group.Priority = priority;
|
group.Priority = priority;
|
||||||
group.DefaultSettings = defaultSettings;
|
group.DefaultSettings = defaultSettings;
|
||||||
group.OptionData.AddRange(subMods);
|
group.OptionData.AddRange(subMods.Select(s => s.ConvertToSingle(null!, group)));
|
||||||
_saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index, Config.ReplaceNonAsciiOnImport));
|
_saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index, Config.ReplaceNonAsciiOnImport));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -266,16 +264,15 @@ public partial class ModCreator(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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 SubMod CreateSubMod(DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option)
|
public MultiSubMod CreateSubMod(DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option, ModPriority priority)
|
||||||
{
|
{
|
||||||
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)))
|
||||||
.Where(t => t.Item1);
|
.Where(t => t.Item1);
|
||||||
|
|
||||||
var mod = SubMod.CreateForSaving(option.Name);
|
var mod = MultiSubMod.CreateForSaving(option.Name, option.Description, priority);
|
||||||
mod.Description = option.Description;
|
|
||||||
foreach (var (_, gamePath, file) in list)
|
foreach (var (_, gamePath, file) in list)
|
||||||
mod.FileData.TryAdd(gamePath, file);
|
mod.Files.TryAdd(gamePath, file);
|
||||||
|
|
||||||
IncorporateMetaChanges(mod, baseFolder, true);
|
IncorporateMetaChanges(mod, baseFolder, true);
|
||||||
return mod;
|
return mod;
|
||||||
|
|
@ -292,7 +289,7 @@ public partial class ModCreator(
|
||||||
foreach (var file in mod.FindUnusedFiles())
|
foreach (var file in mod.FindUnusedFiles())
|
||||||
{
|
{
|
||||||
if (Utf8GamePath.FromFile(new FileInfo(file.FullName), directory, out var gamePath, true))
|
if (Utf8GamePath.FromFile(new FileInfo(file.FullName), directory, out var gamePath, true))
|
||||||
mod.Default.FileData.TryAdd(gamePath, file);
|
mod.Default.Files.TryAdd(gamePath, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
IncorporateMetaChanges(mod.Default, directory, true);
|
IncorporateMetaChanges(mod.Default, directory, true);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
using Newtonsoft.Json;
|
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.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods.Subclasses;
|
namespace Penumbra.Mods.Subclasses;
|
||||||
|
|
||||||
public interface IModDataContainer
|
public interface IModDataContainer
|
||||||
{
|
{
|
||||||
|
public IMod Mod { get; }
|
||||||
|
public IModGroup? Group { get; }
|
||||||
|
|
||||||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; }
|
public Dictionary<Utf8GamePath, FullPath> Files { get; set; }
|
||||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; }
|
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; }
|
||||||
public HashSet<MetaManipulation> Manipulations { get; set; }
|
public HashSet<MetaManipulation> Manipulations { get; set; }
|
||||||
|
|
@ -21,6 +25,32 @@ public interface IModDataContainer
|
||||||
manipulations.UnionWith(Manipulations);
|
manipulations.UnionWith(Manipulations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetName()
|
||||||
|
=> this switch
|
||||||
|
{
|
||||||
|
IModOption o => o.FullName,
|
||||||
|
DefaultSubMod => DefaultSubMod.FullName,
|
||||||
|
_ => $"Container {GetDataIndices().DataIndex + 1}",
|
||||||
|
};
|
||||||
|
|
||||||
|
public string GetFullName()
|
||||||
|
=> this switch
|
||||||
|
{
|
||||||
|
IModOption o => o.FullName,
|
||||||
|
DefaultSubMod => DefaultSubMod.FullName,
|
||||||
|
_ when Group != null => $"{Group.Name}: Container {GetDataIndices().DataIndex + 1}",
|
||||||
|
_ => $"Container {GetDataIndices().DataIndex + 1}",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void Clone(IModDataContainer from, IModDataContainer to)
|
||||||
|
{
|
||||||
|
to.Files = new Dictionary<Utf8GamePath, FullPath>(from.Files);
|
||||||
|
to.FileSwaps = new Dictionary<Utf8GamePath, FullPath>(from.FileSwaps);
|
||||||
|
to.Manipulations = [.. from.Manipulations];
|
||||||
|
}
|
||||||
|
|
||||||
|
public (int GroupIndex, int DataIndex) GetDataIndices();
|
||||||
|
|
||||||
public static void Load(JToken json, IModDataContainer data, DirectoryInfo basePath)
|
public static void Load(JToken json, IModDataContainer data, DirectoryInfo basePath)
|
||||||
{
|
{
|
||||||
data.Files.Clear();
|
data.Files.Clear();
|
||||||
|
|
@ -77,4 +107,22 @@ public interface IModDataContainer
|
||||||
serializer.Serialize(j, data.Manipulations);
|
serializer.Serialize(j, data.Manipulations);
|
||||||
j.WriteEndObject();
|
j.WriteEndObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void DeleteDeleteList(IEnumerable<string> deleteList, bool delete)
|
||||||
|
{
|
||||||
|
if (!delete)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var file in deleteList)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Could not delete incorporated meta file {file}:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
@ -6,6 +7,11 @@ using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods.Subclasses;
|
namespace Penumbra.Mods.Subclasses;
|
||||||
|
|
||||||
|
public interface ITexToolsGroup
|
||||||
|
{
|
||||||
|
public IReadOnlyList<IModDataOption> OptionData { get; }
|
||||||
|
}
|
||||||
|
|
||||||
public interface IModGroup
|
public interface IModGroup
|
||||||
{
|
{
|
||||||
public const int MaxMultiOptions = 63;
|
public const int MaxMultiOptions = 63;
|
||||||
|
|
@ -19,28 +25,89 @@ public interface IModGroup
|
||||||
|
|
||||||
public FullPath? FindBestMatch(Utf8GamePath gamePath);
|
public FullPath? FindBestMatch(Utf8GamePath gamePath);
|
||||||
public int AddOption(Mod mod, string name, string description = "");
|
public int AddOption(Mod mod, string name, string description = "");
|
||||||
public bool ChangeOptionDescription(int optionIndex, string newDescription);
|
|
||||||
public bool ChangeOptionName(int optionIndex, string newName);
|
|
||||||
|
|
||||||
public IReadOnlyList<IModOption> Options { get; }
|
public IReadOnlyList<IModOption> Options { get; }
|
||||||
public bool IsOption { get; }
|
public IReadOnlyList<IModDataContainer> DataContainers { get; }
|
||||||
|
public bool IsOption { get; }
|
||||||
|
|
||||||
public IModGroup Convert(GroupType type);
|
public IModGroup Convert(GroupType type);
|
||||||
public bool MoveOption(int optionIdxFrom, int optionIdxTo);
|
public bool MoveOption(int optionIdxFrom, int optionIdxTo);
|
||||||
|
|
||||||
|
public int GetIndex();
|
||||||
|
|
||||||
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations);
|
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations);
|
||||||
|
|
||||||
/// <summary> Ensure that a value is valid for a group. </summary>
|
/// <summary> Ensure that a value is valid for a group. </summary>
|
||||||
public Setting FixSetting(Setting setting);
|
public Setting FixSetting(Setting setting);
|
||||||
|
|
||||||
|
public void WriteJson(JsonTextWriter jWriter, JsonSerializer serializer, DirectoryInfo? basePath = null);
|
||||||
|
|
||||||
|
public bool ChangeOptionDescription(int optionIndex, string newDescription)
|
||||||
|
{
|
||||||
|
if (optionIndex < 0 || optionIndex >= Options.Count)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var option = Options[optionIndex];
|
||||||
|
if (option.Description == newDescription)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
option.Description = newDescription;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChangeOptionName(int optionIndex, string newName)
|
||||||
|
{
|
||||||
|
if (optionIndex < 0 || optionIndex >= Options.Count)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var option = Options[optionIndex];
|
||||||
|
if (option.Name == newName)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
option.Name = newName;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteJsonBase(JsonTextWriter jWriter, IModGroup group)
|
||||||
|
{
|
||||||
|
jWriter.WriteStartObject();
|
||||||
|
jWriter.WritePropertyName(nameof(group.Name));
|
||||||
|
jWriter.WriteValue(group!.Name);
|
||||||
|
jWriter.WritePropertyName(nameof(group.Description));
|
||||||
|
jWriter.WriteValue(group.Description);
|
||||||
|
jWriter.WritePropertyName(nameof(group.Priority));
|
||||||
|
jWriter.WriteValue(group.Priority.Value);
|
||||||
|
jWriter.WritePropertyName(nameof(group.Type));
|
||||||
|
jWriter.WriteValue(group.Type.ToString());
|
||||||
|
jWriter.WritePropertyName(nameof(group.DefaultSettings));
|
||||||
|
jWriter.WriteValue(group.DefaultSettings.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (int Redirections, int Swaps, int Manips) GetCounts();
|
||||||
|
|
||||||
|
public static (int Redirections, int Swaps, int Manips) GetCountsBase(IModGroup group)
|
||||||
|
{
|
||||||
|
var redirectionCount = 0;
|
||||||
|
var swapCount = 0;
|
||||||
|
var manipCount = 0;
|
||||||
|
foreach (var option in group.DataContainers)
|
||||||
|
{
|
||||||
|
redirectionCount += option.Files.Count;
|
||||||
|
swapCount += option.FileSwaps.Count;
|
||||||
|
manipCount += option.Manipulations.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (redirectionCount, swapCount, manipCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct ModSaveGroup : ISavable
|
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 SubMod? _defaultMod;
|
private readonly DefaultSubMod? _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)
|
||||||
{
|
{
|
||||||
|
|
@ -61,7 +128,7 @@ public readonly struct ModSaveGroup : ISavable
|
||||||
_onlyAscii = onlyAscii;
|
_onlyAscii = onlyAscii;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModSaveGroup(DirectoryInfo basePath, SubMod @default, bool onlyAscii)
|
public ModSaveGroup(DirectoryInfo basePath, DefaultSubMod @default, bool onlyAscii)
|
||||||
{
|
{
|
||||||
_basePath = basePath;
|
_basePath = basePath;
|
||||||
_groupIdx = -1;
|
_groupIdx = -1;
|
||||||
|
|
@ -77,42 +144,11 @@ public readonly struct ModSaveGroup : ISavable
|
||||||
using var j = new JsonTextWriter(writer);
|
using var j = new JsonTextWriter(writer);
|
||||||
j.Formatting = Formatting.Indented;
|
j.Formatting = Formatting.Indented;
|
||||||
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
||||||
|
j.WriteStartObject();
|
||||||
if (_groupIdx >= 0)
|
if (_groupIdx >= 0)
|
||||||
{
|
_group!.WriteJson(j, serializer);
|
||||||
j.WriteStartObject();
|
|
||||||
j.WritePropertyName(nameof(_group.Name));
|
|
||||||
j.WriteValue(_group!.Name);
|
|
||||||
j.WritePropertyName(nameof(_group.Description));
|
|
||||||
j.WriteValue(_group.Description);
|
|
||||||
j.WritePropertyName(nameof(_group.Priority));
|
|
||||||
j.WriteValue(_group.Priority.Value);
|
|
||||||
j.WritePropertyName(nameof(Type));
|
|
||||||
j.WriteValue(_group.Type.ToString());
|
|
||||||
j.WritePropertyName(nameof(_group.DefaultSettings));
|
|
||||||
j.WriteValue(_group.DefaultSettings.Value);
|
|
||||||
switch (_group)
|
|
||||||
{
|
|
||||||
case SingleModGroup single:
|
|
||||||
j.WritePropertyName("Options");
|
|
||||||
j.WriteStartArray();
|
|
||||||
foreach (var option in single.OptionData)
|
|
||||||
SubMod.WriteSubMod(j, serializer, option, _basePath, null);
|
|
||||||
j.WriteEndArray();
|
|
||||||
j.WriteEndObject();
|
|
||||||
break;
|
|
||||||
case MultiModGroup multi:
|
|
||||||
j.WritePropertyName("Options");
|
|
||||||
j.WriteStartArray();
|
|
||||||
foreach (var (option, priority) in multi.PrioritizedOptions)
|
|
||||||
SubMod.WriteSubMod(j, serializer, option, _basePath, priority);
|
|
||||||
j.WriteEndArray();
|
|
||||||
j.WriteEndObject();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
IModDataContainer.WriteModData(j, serializer, _defaultMod!, _basePath);
|
||||||
SubMod.WriteSubMod(j, serializer, _defaultMod!, _basePath, null);
|
j.WriteEndObject();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ public interface IModOption
|
||||||
option.Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty;
|
option.Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public (int GroupIndex, int OptionIndex) GetOptionIndices();
|
||||||
|
|
||||||
public static void WriteModOption(JsonWriter j, IModOption option)
|
public static void WriteModOption(JsonWriter j, IModOption option)
|
||||||
{
|
{
|
||||||
j.WritePropertyName(nameof(Name));
|
j.WritePropertyName(nameof(Name));
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
|
@ -10,20 +11,30 @@ using Penumbra.String.Classes;
|
||||||
namespace Penumbra.Mods.Subclasses;
|
namespace Penumbra.Mods.Subclasses;
|
||||||
|
|
||||||
/// <summary> Groups that allow all available options to be selected at once. </summary>
|
/// <summary> Groups that allow all available options to be selected at once. </summary>
|
||||||
public sealed class MultiModGroup(Mod mod) : IModGroup
|
public sealed class MultiModGroup(Mod mod) : IModGroup, ITexToolsGroup
|
||||||
{
|
{
|
||||||
public GroupType Type
|
public GroupType Type
|
||||||
=> GroupType.Multi;
|
=> GroupType.Multi;
|
||||||
|
|
||||||
public Mod Mod { get; set; } = mod;
|
public Mod Mod { get; set; } = mod;
|
||||||
public string Name { get; set; } = "Group";
|
public string Name { get; set; } = "Group";
|
||||||
public string Description { get; set; } = "A non-exclusive group of settings.";
|
public string Description { get; set; } = "A non-exclusive group of settings.";
|
||||||
public ModPriority Priority { get; set; }
|
public ModPriority Priority { get; set; }
|
||||||
public Setting DefaultSettings { get; set; }
|
public Setting DefaultSettings { get; set; }
|
||||||
|
public readonly List<MultiSubMod> OptionData = [];
|
||||||
|
|
||||||
|
public IReadOnlyList<IModOption> Options
|
||||||
|
=> OptionData;
|
||||||
|
|
||||||
|
public IReadOnlyList<IModDataContainer> DataContainers
|
||||||
|
=> OptionData;
|
||||||
|
|
||||||
|
public bool IsOption
|
||||||
|
=> OptionData.Count > 0;
|
||||||
|
|
||||||
public FullPath? FindBestMatch(Utf8GamePath gamePath)
|
public FullPath? FindBestMatch(Utf8GamePath gamePath)
|
||||||
=> PrioritizedOptions.OrderByDescending(o => o.Priority)
|
=> OptionData.OrderByDescending(o => o.Priority)
|
||||||
.SelectWhere(o => (o.Mod.FileData.TryGetValue(gamePath, out var file) || o.Mod.FileSwapData.TryGetValue(gamePath, out file), file))
|
.SelectWhere(o => (o.Files.TryGetValue(gamePath, out var file) || o.FileSwaps.TryGetValue(gamePath, out file), file))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
public int AddOption(Mod mod, string name, string description = "")
|
public int AddOption(Mod mod, string name, string description = "")
|
||||||
|
|
@ -32,49 +43,15 @@ public sealed class MultiModGroup(Mod mod) : IModGroup
|
||||||
if (groupIdx < 0)
|
if (groupIdx < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
var subMod = new SubMod(mod, this)
|
var subMod = new MultiSubMod(mod, this)
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Description = description,
|
Description = description,
|
||||||
};
|
};
|
||||||
PrioritizedOptions.Add((subMod, ModPriority.Default));
|
OptionData.Add(subMod);
|
||||||
return PrioritizedOptions.Count - 1;
|
return OptionData.Count - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ChangeOptionDescription(int optionIndex, string newDescription)
|
|
||||||
{
|
|
||||||
if (optionIndex < 0 || optionIndex >= PrioritizedOptions.Count)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var option = PrioritizedOptions[optionIndex].Mod;
|
|
||||||
if (option.Description == newDescription)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
option.Description = newDescription;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ChangeOptionName(int optionIndex, string newName)
|
|
||||||
{
|
|
||||||
if (optionIndex < 0 || optionIndex >= PrioritizedOptions.Count)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var option = PrioritizedOptions[optionIndex].Mod;
|
|
||||||
if (option.Name == newName)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
option.Name = newName;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyList<IModOption> Options
|
|
||||||
=> PrioritizedOptions.Select(p => p.Mod).ToArray();
|
|
||||||
|
|
||||||
public bool IsOption
|
|
||||||
=> PrioritizedOptions.Count > 0;
|
|
||||||
|
|
||||||
public readonly List<(SubMod Mod, ModPriority Priority)> PrioritizedOptions = [];
|
|
||||||
|
|
||||||
public static MultiModGroup? Load(Mod mod, JObject json, int groupIdx)
|
public static MultiModGroup? Load(Mod mod, JObject json, int groupIdx)
|
||||||
{
|
{
|
||||||
var ret = new MultiModGroup(mod)
|
var ret = new MultiModGroup(mod)
|
||||||
|
|
@ -91,7 +68,7 @@ public sealed class MultiModGroup(Mod mod) : IModGroup
|
||||||
if (options != null)
|
if (options != null)
|
||||||
foreach (var child in options.Children())
|
foreach (var child in options.Children())
|
||||||
{
|
{
|
||||||
if (ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions)
|
if (ret.OptionData.Count == IModGroup.MaxMultiOptions)
|
||||||
{
|
{
|
||||||
Penumbra.Messager.NotificationMessage(
|
Penumbra.Messager.NotificationMessage(
|
||||||
$"Multi Group {ret.Name} in {mod.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options.",
|
$"Multi Group {ret.Name} in {mod.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options.",
|
||||||
|
|
@ -99,9 +76,8 @@ public sealed class MultiModGroup(Mod mod) : IModGroup
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var subMod = new SubMod(mod, ret);
|
var subMod = new MultiSubMod(mod, ret, child);
|
||||||
subMod.Load(mod.ModPath, child, out var priority);
|
ret.OptionData.Add(subMod);
|
||||||
ret.PrioritizedOptions.Add((subMod, priority));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings);
|
ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings);
|
||||||
|
|
@ -115,39 +91,68 @@ public sealed class MultiModGroup(Mod mod) : IModGroup
|
||||||
{
|
{
|
||||||
case GroupType.Multi: return this;
|
case GroupType.Multi: return this;
|
||||||
case GroupType.Single:
|
case GroupType.Single:
|
||||||
var multi = new SingleModGroup(Mod)
|
var single = new SingleModGroup(Mod)
|
||||||
{
|
{
|
||||||
Name = Name,
|
Name = Name,
|
||||||
Description = Description,
|
Description = Description,
|
||||||
Priority = Priority,
|
Priority = Priority,
|
||||||
DefaultSettings = DefaultSettings.TurnMulti(PrioritizedOptions.Count),
|
DefaultSettings = DefaultSettings.TurnMulti(OptionData.Count),
|
||||||
};
|
};
|
||||||
multi.OptionData.AddRange(PrioritizedOptions.Select(p => p.Mod));
|
single.OptionData.AddRange(OptionData.Select(o => o.ConvertToSingle(Mod, single)));
|
||||||
return multi;
|
return single;
|
||||||
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool MoveOption(int optionIdxFrom, int optionIdxTo)
|
public bool MoveOption(int optionIdxFrom, int optionIdxTo)
|
||||||
{
|
{
|
||||||
if (!PrioritizedOptions.Move(optionIdxFrom, optionIdxTo))
|
if (!OptionData.Move(optionIdxFrom, optionIdxTo))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
DefaultSettings = DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
|
DefaultSettings = DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetIndex()
|
||||||
|
{
|
||||||
|
var groupIndex = Mod.Groups.IndexOf(this);
|
||||||
|
if (groupIndex < 0)
|
||||||
|
throw new Exception($"Mod {Mod.Name} from Group {Name} does not contain this group.");
|
||||||
|
|
||||||
|
return groupIndex;
|
||||||
|
}
|
||||||
|
|
||||||
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||||
{
|
{
|
||||||
foreach (var (option, index) in PrioritizedOptions.WithIndex().OrderByDescending(o => o.Value.Priority))
|
foreach (var (option, index) in OptionData.WithIndex().OrderByDescending(o => o.Value.Priority))
|
||||||
{
|
{
|
||||||
if (setting.HasFlag(index))
|
if (setting.HasFlag(index))
|
||||||
option.Mod.AddData(redirections, manipulations);
|
option.AddDataTo(redirections, manipulations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WriteJson(JsonTextWriter jWriter, JsonSerializer serializer, DirectoryInfo? basePath = null)
|
||||||
|
{
|
||||||
|
IModGroup.WriteJsonBase(jWriter, this);
|
||||||
|
jWriter.WritePropertyName("Options");
|
||||||
|
jWriter.WriteStartArray();
|
||||||
|
foreach (var option in OptionData)
|
||||||
|
{
|
||||||
|
IModOption.WriteModOption(jWriter, option);
|
||||||
|
jWriter.WritePropertyName(nameof(option.Priority));
|
||||||
|
jWriter.WriteValue(option.Priority.Value);
|
||||||
|
IModDataContainer.WriteModData(jWriter, serializer, option, basePath ?? Mod.ModPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
jWriter.WriteEndArray();
|
||||||
|
jWriter.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public (int Redirections, int Swaps, int Manips) GetCounts()
|
||||||
|
=> IModGroup.GetCountsBase(this);
|
||||||
|
|
||||||
public Setting FixSetting(Setting setting)
|
public Setting FixSetting(Setting setting)
|
||||||
=> new(setting.Value & ((1ul << PrioritizedOptions.Count) - 1));
|
=> new(setting.Value & ((1ul << OptionData.Count) - 1));
|
||||||
|
|
||||||
/// <summary> Create a group without a mod only for saving it in the creator. </summary>
|
/// <summary> Create a group without a mod only for saving it in the creator. </summary>
|
||||||
internal static MultiModGroup CreateForSaving(string name)
|
internal static MultiModGroup CreateForSaving(string name)
|
||||||
|
|
@ -155,4 +160,7 @@ public sealed class MultiModGroup(Mod mod) : IModGroup
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
IReadOnlyList<IModDataOption> ITexToolsGroup.OptionData
|
||||||
|
=> OptionData;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
|
|
@ -8,7 +9,7 @@ using Penumbra.String.Classes;
|
||||||
namespace Penumbra.Mods.Subclasses;
|
namespace Penumbra.Mods.Subclasses;
|
||||||
|
|
||||||
/// <summary> Groups that allow only one of their available options to be selected. </summary>
|
/// <summary> Groups that allow only one of their available options to be selected. </summary>
|
||||||
public sealed class SingleModGroup(Mod mod) : IModGroup
|
public sealed class SingleModGroup(Mod mod) : IModGroup, ITexToolsGroup
|
||||||
{
|
{
|
||||||
public GroupType Type
|
public GroupType Type
|
||||||
=> GroupType.Single;
|
=> GroupType.Single;
|
||||||
|
|
@ -19,16 +20,19 @@ public sealed class SingleModGroup(Mod mod) : IModGroup
|
||||||
public ModPriority Priority { get; set; }
|
public ModPriority Priority { get; set; }
|
||||||
public Setting DefaultSettings { get; set; }
|
public Setting DefaultSettings { get; set; }
|
||||||
|
|
||||||
public readonly List<SubMod> OptionData = [];
|
public readonly List<SingleSubMod> OptionData = [];
|
||||||
|
|
||||||
|
IReadOnlyList<IModDataOption> ITexToolsGroup.OptionData
|
||||||
|
=> OptionData;
|
||||||
|
|
||||||
public FullPath? FindBestMatch(Utf8GamePath gamePath)
|
public FullPath? FindBestMatch(Utf8GamePath gamePath)
|
||||||
=> OptionData
|
=> OptionData
|
||||||
.SelectWhere(m => (m.FileData.TryGetValue(gamePath, out var file) || m.FileSwapData.TryGetValue(gamePath, out file), file))
|
.SelectWhere(m => (m.Files.TryGetValue(gamePath, out var file) || m.FileSwaps.TryGetValue(gamePath, out file), file))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
public int AddOption(Mod mod, string name, string description = "")
|
public int AddOption(Mod mod, string name, string description = "")
|
||||||
{
|
{
|
||||||
var subMod = new SubMod(mod, this)
|
var subMod = new SingleSubMod(mod, this)
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Description = description,
|
Description = description,
|
||||||
|
|
@ -37,35 +41,12 @@ public sealed class SingleModGroup(Mod mod) : IModGroup
|
||||||
return OptionData.Count - 1;
|
return OptionData.Count - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ChangeOptionDescription(int optionIndex, string newDescription)
|
|
||||||
{
|
|
||||||
if (optionIndex < 0 || optionIndex >= OptionData.Count)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var option = OptionData[optionIndex];
|
|
||||||
if (option.Description == newDescription)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
option.Description = newDescription;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ChangeOptionName(int optionIndex, string newName)
|
|
||||||
{
|
|
||||||
if (optionIndex < 0 || optionIndex >= OptionData.Count)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var option = OptionData[optionIndex];
|
|
||||||
if (option.Name == newName)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
option.Name = newName;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyList<IModOption> Options
|
public IReadOnlyList<IModOption> Options
|
||||||
=> OptionData;
|
=> OptionData;
|
||||||
|
|
||||||
|
public IReadOnlyList<IModDataContainer> DataContainers
|
||||||
|
=> OptionData;
|
||||||
|
|
||||||
public bool IsOption
|
public bool IsOption
|
||||||
=> OptionData.Count > 1;
|
=> OptionData.Count > 1;
|
||||||
|
|
||||||
|
|
@ -85,8 +66,7 @@ public sealed class SingleModGroup(Mod mod) : IModGroup
|
||||||
if (options != null)
|
if (options != null)
|
||||||
foreach (var child in options.Children())
|
foreach (var child in options.Children())
|
||||||
{
|
{
|
||||||
var subMod = new SubMod(mod, ret);
|
var subMod = new SingleSubMod(mod, ret, child);
|
||||||
subMod.Load(mod.ModPath, child, out _);
|
|
||||||
ret.OptionData.Add(subMod);
|
ret.OptionData.Add(subMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +87,7 @@ public sealed class SingleModGroup(Mod mod) : IModGroup
|
||||||
Priority = Priority,
|
Priority = Priority,
|
||||||
DefaultSettings = Setting.Multi((int)DefaultSettings.Value),
|
DefaultSettings = Setting.Multi((int)DefaultSettings.Value),
|
||||||
};
|
};
|
||||||
multi.PrioritizedOptions.AddRange(OptionData.Select((o, i) => (o, new ModPriority(i))));
|
multi.OptionData.AddRange(OptionData.Select((o, i) => o.ConvertToMulti(Mod, multi, new ModPriority(i))));
|
||||||
return multi;
|
return multi;
|
||||||
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||||
}
|
}
|
||||||
|
|
@ -137,12 +117,39 @@ public sealed class SingleModGroup(Mod mod) : IModGroup
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetIndex()
|
||||||
|
{
|
||||||
|
var groupIndex = Mod.Groups.IndexOf(this);
|
||||||
|
if (groupIndex < 0)
|
||||||
|
throw new Exception($"Mod {Mod.Name} from Group {Name} does not contain this group.");
|
||||||
|
|
||||||
|
return groupIndex;
|
||||||
|
}
|
||||||
|
|
||||||
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||||
=> OptionData[setting.AsIndex].AddData(redirections, manipulations);
|
=> OptionData[setting.AsIndex].AddDataTo(redirections, manipulations);
|
||||||
|
|
||||||
public Setting FixSetting(Setting setting)
|
public Setting FixSetting(Setting setting)
|
||||||
=> OptionData.Count == 0 ? Setting.Zero : new Setting(Math.Min(setting.Value, (ulong)(OptionData.Count - 1)));
|
=> OptionData.Count == 0 ? Setting.Zero : new Setting(Math.Min(setting.Value, (ulong)(OptionData.Count - 1)));
|
||||||
|
|
||||||
|
public (int Redirections, int Swaps, int Manips) GetCounts()
|
||||||
|
=> IModGroup.GetCountsBase(this);
|
||||||
|
|
||||||
|
public void WriteJson(JsonTextWriter jWriter, JsonSerializer serializer, DirectoryInfo? basePath = null)
|
||||||
|
{
|
||||||
|
IModGroup.WriteJsonBase(jWriter, this);
|
||||||
|
jWriter.WritePropertyName("Options");
|
||||||
|
jWriter.WriteStartArray();
|
||||||
|
foreach (var option in OptionData)
|
||||||
|
{
|
||||||
|
IModOption.WriteModOption(jWriter, option);
|
||||||
|
IModDataContainer.WriteModData(jWriter, serializer, option, basePath ?? Mod.ModPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
jWriter.WriteEndArray();
|
||||||
|
jWriter.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Create a group without a mod only for saving it in the creator. </summary>
|
/// <summary> Create a group without a mod only for saving it in the creator. </summary>
|
||||||
internal static SingleModGroup CreateForSaving(string name)
|
internal static SingleModGroup CreateForSaving(string name)
|
||||||
=> new(null!)
|
=> new(null!)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods.Subclasses;
|
namespace Penumbra.Mods.Subclasses;
|
||||||
|
|
||||||
public class SingleSubMod(Mod mod, SingleModGroup group) : IModOption, IModDataContainer
|
public interface IModDataOption : IModOption, IModDataContainer;
|
||||||
|
|
||||||
|
public class SingleSubMod(Mod mod, SingleModGroup group) : IModDataOption
|
||||||
{
|
{
|
||||||
internal readonly Mod Mod = mod;
|
internal readonly Mod Mod = mod;
|
||||||
internal readonly SingleModGroup Group = group;
|
internal readonly SingleModGroup Group = group;
|
||||||
|
|
@ -19,12 +21,68 @@ public class SingleSubMod(Mod mod, SingleModGroup group) : IModOption, IModDataC
|
||||||
|
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
IMod IModDataContainer.Mod
|
||||||
|
=> Mod;
|
||||||
|
|
||||||
|
IModGroup IModDataContainer.Group
|
||||||
|
=> Group;
|
||||||
|
|
||||||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
||||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
|
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
|
||||||
public HashSet<MetaManipulation> Manipulations { get; set; } = [];
|
public HashSet<MetaManipulation> Manipulations { get; set; } = [];
|
||||||
|
|
||||||
|
public SingleSubMod(Mod mod, SingleModGroup group, JToken json)
|
||||||
|
: this(mod, group)
|
||||||
|
{
|
||||||
|
IModOption.Load(json, this);
|
||||||
|
IModDataContainer.Load(json, this, mod.ModPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleSubMod Clone(Mod mod, SingleModGroup group)
|
||||||
|
{
|
||||||
|
var ret = new SingleSubMod(mod, group)
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Description = Description,
|
||||||
|
};
|
||||||
|
IModDataContainer.Clone(this, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiSubMod ConvertToMulti(Mod mod, MultiModGroup group, ModPriority priority)
|
||||||
|
{
|
||||||
|
var ret = new MultiSubMod(mod, group)
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Description = Description,
|
||||||
|
Priority = priority,
|
||||||
|
};
|
||||||
|
IModDataContainer.Clone(this, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddDataTo(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||||
|
=> ((IModDataContainer)this).AddDataTo(redirections, manipulations);
|
||||||
|
|
||||||
|
public (int GroupIndex, int DataIndex) GetDataIndices()
|
||||||
|
=> (Group.GetIndex(), GetDataIndex());
|
||||||
|
|
||||||
|
public (int GroupIndex, int OptionIndex) GetOptionIndices()
|
||||||
|
=> (Group.GetIndex(), GetDataIndex());
|
||||||
|
|
||||||
|
private int GetDataIndex()
|
||||||
|
{
|
||||||
|
var dataIndex = Group.DataContainers.IndexOf(this);
|
||||||
|
if (dataIndex < 0)
|
||||||
|
throw new Exception($"Group {Group.Name} from SubMod {Name} does not contain this SubMod.");
|
||||||
|
|
||||||
|
return dataIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MultiSubMod(Mod mod, MultiModGroup group) : IModOption, IModDataContainer
|
public class MultiSubMod(Mod mod, MultiModGroup group) : IModDataOption
|
||||||
{
|
{
|
||||||
internal readonly Mod Mod = mod;
|
internal readonly Mod Mod = mod;
|
||||||
internal readonly MultiModGroup Group = group;
|
internal readonly MultiModGroup Group = group;
|
||||||
|
|
@ -40,12 +98,76 @@ public class MultiSubMod(Mod mod, MultiModGroup group) : IModOption, IModDataCon
|
||||||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
||||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
|
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
|
||||||
public HashSet<MetaManipulation> Manipulations { get; set; } = [];
|
public HashSet<MetaManipulation> Manipulations { get; set; } = [];
|
||||||
|
|
||||||
|
IMod IModDataContainer.Mod
|
||||||
|
=> Mod;
|
||||||
|
|
||||||
|
IModGroup IModDataContainer.Group
|
||||||
|
=> Group;
|
||||||
|
|
||||||
|
|
||||||
|
public MultiSubMod(Mod mod, MultiModGroup group, JToken json)
|
||||||
|
: this(mod, group)
|
||||||
|
{
|
||||||
|
IModOption.Load(json, this);
|
||||||
|
IModDataContainer.Load(json, this, mod.ModPath);
|
||||||
|
Priority = json[nameof(IModGroup.Priority)]?.ToObject<ModPriority>() ?? ModPriority.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiSubMod Clone(Mod mod, MultiModGroup group)
|
||||||
|
{
|
||||||
|
var ret = new MultiSubMod(mod, group)
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Description = Description,
|
||||||
|
Priority = Priority,
|
||||||
|
};
|
||||||
|
IModDataContainer.Clone(this, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleSubMod ConvertToSingle(Mod mod, SingleModGroup group)
|
||||||
|
{
|
||||||
|
var ret = new SingleSubMod(mod, group)
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Description = Description,
|
||||||
|
};
|
||||||
|
IModDataContainer.Clone(this, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddDataTo(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||||
|
=> ((IModDataContainer)this).AddDataTo(redirections, manipulations);
|
||||||
|
|
||||||
|
public static MultiSubMod CreateForSaving(string name, string description, ModPriority priority)
|
||||||
|
=> new(null!, null!)
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Description = description,
|
||||||
|
Priority = priority,
|
||||||
|
};
|
||||||
|
|
||||||
|
public (int GroupIndex, int DataIndex) GetDataIndices()
|
||||||
|
=> (Group.GetIndex(), GetDataIndex());
|
||||||
|
|
||||||
|
public (int GroupIndex, int OptionIndex) GetOptionIndices()
|
||||||
|
=> (Group.GetIndex(), GetDataIndex());
|
||||||
|
|
||||||
|
private int GetDataIndex()
|
||||||
|
{
|
||||||
|
var dataIndex = Group.DataContainers.IndexOf(this);
|
||||||
|
if (dataIndex < 0)
|
||||||
|
throw new Exception($"Group {Group.Name} from SubMod {Name} does not contain this SubMod.");
|
||||||
|
|
||||||
|
return dataIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DefaultSubMod(IMod mod) : IModDataContainer
|
public class DefaultSubMod(IMod mod) : IModDataContainer
|
||||||
{
|
{
|
||||||
public string FullName
|
public const string FullName = "Default Option";
|
||||||
=> "Default Option";
|
|
||||||
|
|
||||||
public string Description
|
public string Description
|
||||||
=> string.Empty;
|
=> string.Empty;
|
||||||
|
|
@ -55,183 +177,176 @@ public class DefaultSubMod(IMod mod) : IModDataContainer
|
||||||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
||||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
|
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
|
||||||
public HashSet<MetaManipulation> Manipulations { get; set; } = [];
|
public HashSet<MetaManipulation> Manipulations { get; set; } = [];
|
||||||
|
|
||||||
|
IMod IModDataContainer.Mod
|
||||||
|
=> Mod;
|
||||||
|
|
||||||
|
IModGroup? IModDataContainer.Group
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
|
||||||
|
public DefaultSubMod(Mod mod, JToken json)
|
||||||
|
: this(mod)
|
||||||
|
{
|
||||||
|
IModDataContainer.Load(json, this, mod.ModPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddDataTo(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||||
|
=> ((IModDataContainer)this).AddDataTo(redirections, manipulations);
|
||||||
|
|
||||||
|
public (int GroupIndex, int DataIndex) GetDataIndices()
|
||||||
|
=> (-1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A sub mod is a collection of
|
|
||||||
/// - file replacements
|
|
||||||
/// - file swaps
|
|
||||||
/// - meta manipulations
|
|
||||||
/// that can be used either as an option or as the default data for a mod.
|
|
||||||
/// It can be loaded and reloaded from Json.
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class SubMod(IMod mod, IModGroup group) : IModOption
|
|
||||||
{
|
|
||||||
public string Name { get; set; } = "Default";
|
|
||||||
|
|
||||||
public string FullName
|
//public sealed class SubMod(IMod mod, IModGroup group) : IModOption
|
||||||
=> Group == null ? "Default Option" : $"{Group.Name}: {Name}";
|
//{
|
||||||
|
// public string Name { get; set; } = "Default";
|
||||||
public string Description { get; set; } = string.Empty;
|
//
|
||||||
|
// public string FullName
|
||||||
internal readonly IMod Mod = mod;
|
// => Group == null ? "Default Option" : $"{Group.Name}: {Name}";
|
||||||
internal readonly IModGroup? Group = group;
|
//
|
||||||
|
// public string Description { get; set; } = string.Empty;
|
||||||
internal (int GroupIdx, int OptionIdx) GetIndices()
|
//
|
||||||
{
|
// internal readonly IMod Mod = mod;
|
||||||
if (IsDefault)
|
// internal readonly IModGroup? Group = group;
|
||||||
return (-1, 0);
|
//
|
||||||
|
// internal (int GroupIdx, int OptionIdx) GetIndices()
|
||||||
var groupIdx = Mod.Groups.IndexOf(Group);
|
// {
|
||||||
if (groupIdx < 0)
|
// if (IsDefault)
|
||||||
throw new Exception($"Group {Group.Name} from SubMod {Name} is not contained in Mod {Mod.Name}.");
|
// return (-1, 0);
|
||||||
|
//
|
||||||
return (groupIdx, GetOptionIndex());
|
// var groupIdx = Mod.Groups.IndexOf(Group);
|
||||||
}
|
// if (groupIdx < 0)
|
||||||
|
// throw new Exception($"Group {Group.Name} from SubMod {Name} is not contained in Mod {Mod.Name}.");
|
||||||
private int GetOptionIndex()
|
//
|
||||||
{
|
// return (groupIdx, GetOptionIndex());
|
||||||
var optionIndex = Group switch
|
// }
|
||||||
{
|
//
|
||||||
null => 0,
|
// private int GetOptionIndex()
|
||||||
SingleModGroup single => single.OptionData.IndexOf(this),
|
// {
|
||||||
MultiModGroup multi => multi.PrioritizedOptions.IndexOf(p => p.Mod == this),
|
// var optionIndex = Group switch
|
||||||
_ => throw new Exception($"Group {Group.Name} from SubMod {Name} has unknown type {typeof(Group)}"),
|
// {
|
||||||
};
|
// null => 0,
|
||||||
if (optionIndex < 0)
|
// SingleModGroup single => single.OptionData.IndexOf(this),
|
||||||
throw new Exception($"Group {Group!.Name} from SubMod {Name} does not contain this SubMod.");
|
// MultiModGroup multi => multi.OptionData.IndexOf(p => p.Mod == this),
|
||||||
|
// _ => throw new Exception($"Group {Group.Name} from SubMod {Name} has unknown type {typeof(Group)}"),
|
||||||
return optionIndex;
|
// };
|
||||||
}
|
// if (optionIndex < 0)
|
||||||
|
// throw new Exception($"Group {Group!.Name} from SubMod {Name} does not contain this SubMod.");
|
||||||
public static SubMod CreateDefault(IMod mod)
|
//
|
||||||
=> new(mod, null!);
|
// return optionIndex;
|
||||||
|
// }
|
||||||
[MemberNotNullWhen(false, nameof(Group))]
|
//
|
||||||
public bool IsDefault
|
// public static SubMod CreateDefault(IMod mod)
|
||||||
=> Group == null;
|
// => new(mod, null!);
|
||||||
|
//
|
||||||
public void AddData(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
// [MemberNotNullWhen(false, nameof(Group))]
|
||||||
{
|
// public bool IsDefault
|
||||||
foreach (var (path, file) in Files)
|
// => Group == null;
|
||||||
redirections.TryAdd(path, file);
|
//
|
||||||
|
// public void AddData(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||||
foreach (var (path, file) in FileSwaps)
|
// {
|
||||||
redirections.TryAdd(path, file);
|
// foreach (var (path, file) in Files)
|
||||||
manipulations.UnionWith(Manipulations);
|
// redirections.TryAdd(path, file);
|
||||||
}
|
//
|
||||||
|
// foreach (var (path, file) in FileSwaps)
|
||||||
public Dictionary<Utf8GamePath, FullPath> FileData = [];
|
// redirections.TryAdd(path, file);
|
||||||
public Dictionary<Utf8GamePath, FullPath> FileSwapData = [];
|
// manipulations.UnionWith(Manipulations);
|
||||||
public HashSet<MetaManipulation> ManipulationData = [];
|
// }
|
||||||
|
//
|
||||||
public IReadOnlyDictionary<Utf8GamePath, FullPath> Files
|
// public Dictionary<Utf8GamePath, FullPath> FileData = [];
|
||||||
=> FileData;
|
// public Dictionary<Utf8GamePath, FullPath> FileSwapData = [];
|
||||||
|
// public HashSet<MetaManipulation> ManipulationData = [];
|
||||||
public IReadOnlyDictionary<Utf8GamePath, FullPath> FileSwaps
|
//
|
||||||
=> FileSwapData;
|
// public IReadOnlyDictionary<Utf8GamePath, FullPath> Files
|
||||||
|
// => FileData;
|
||||||
public IReadOnlySet<MetaManipulation> Manipulations
|
//
|
||||||
=> ManipulationData;
|
// public IReadOnlyDictionary<Utf8GamePath, FullPath> FileSwaps
|
||||||
|
// => FileSwapData;
|
||||||
public void Load(DirectoryInfo basePath, JToken json, out ModPriority priority)
|
//
|
||||||
{
|
// public IReadOnlySet<MetaManipulation> Manipulations
|
||||||
FileData.Clear();
|
// => ManipulationData;
|
||||||
FileSwapData.Clear();
|
//
|
||||||
ManipulationData.Clear();
|
// public void Load(DirectoryInfo basePath, JToken json, out ModPriority priority)
|
||||||
|
// {
|
||||||
// Every option has a name, but priorities are only relevant for multi group options.
|
// FileData.Clear();
|
||||||
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty;
|
// FileSwapData.Clear();
|
||||||
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty;
|
// ManipulationData.Clear();
|
||||||
priority = json[nameof(IModGroup.Priority)]?.ToObject<ModPriority>() ?? ModPriority.Default;
|
//
|
||||||
|
// // Every option has a name, but priorities are only relevant for multi group options.
|
||||||
var files = (JObject?)json[nameof(Files)];
|
// Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty;
|
||||||
if (files != null)
|
// Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty;
|
||||||
foreach (var property in files.Properties())
|
// priority = json[nameof(IModGroup.Priority)]?.ToObject<ModPriority>() ?? ModPriority.Default;
|
||||||
{
|
//
|
||||||
if (Utf8GamePath.FromString(property.Name, out var p, true))
|
// var files = (JObject?)json[nameof(Files)];
|
||||||
FileData.TryAdd(p, new FullPath(basePath, property.Value.ToObject<Utf8RelPath>()));
|
// if (files != null)
|
||||||
}
|
// foreach (var property in files.Properties())
|
||||||
|
// {
|
||||||
var swaps = (JObject?)json[nameof(FileSwaps)];
|
// if (Utf8GamePath.FromString(property.Name, out var p, true))
|
||||||
if (swaps != null)
|
// FileData.TryAdd(p, new FullPath(basePath, property.Value.ToObject<Utf8RelPath>()));
|
||||||
foreach (var property in swaps.Properties())
|
// }
|
||||||
{
|
//
|
||||||
if (Utf8GamePath.FromString(property.Name, out var p, true))
|
// var swaps = (JObject?)json[nameof(FileSwaps)];
|
||||||
FileSwapData.TryAdd(p, new FullPath(property.Value.ToObject<string>()!));
|
// if (swaps != null)
|
||||||
}
|
// foreach (var property in swaps.Properties())
|
||||||
|
// {
|
||||||
var manips = json[nameof(Manipulations)];
|
// if (Utf8GamePath.FromString(property.Name, out var p, true))
|
||||||
if (manips != null)
|
// FileSwapData.TryAdd(p, new FullPath(property.Value.ToObject<string>()!));
|
||||||
foreach (var s in manips.Children().Select(c => c.ToObject<MetaManipulation>())
|
// }
|
||||||
.Where(m => m.Validate()))
|
//
|
||||||
ManipulationData.Add(s);
|
// var manips = json[nameof(Manipulations)];
|
||||||
}
|
// if (manips != null)
|
||||||
|
// foreach (var s in manips.Children().Select(c => c.ToObject<MetaManipulation>())
|
||||||
internal static void DeleteDeleteList(IEnumerable<string> deleteList, bool delete)
|
// .Where(m => m.Validate()))
|
||||||
{
|
// ManipulationData.Add(s);
|
||||||
if (!delete)
|
// }
|
||||||
return;
|
//
|
||||||
|
//
|
||||||
foreach (var file in deleteList)
|
// /// <summary> Create a sub mod without a mod or group only for saving it in the creator. </summary>
|
||||||
{
|
// internal static SubMod CreateForSaving(string name)
|
||||||
try
|
// => new(null!, null!)
|
||||||
{
|
// {
|
||||||
File.Delete(file);
|
// Name = name,
|
||||||
}
|
// };
|
||||||
catch (Exception e)
|
//
|
||||||
{
|
//
|
||||||
Penumbra.Log.Error($"Could not delete incorporated meta file {file}:\n{e}");
|
// public static void WriteSubMod(JsonWriter j, JsonSerializer serializer, SubMod mod, DirectoryInfo basePath, ModPriority? priority)
|
||||||
}
|
// {
|
||||||
}
|
// j.WriteStartObject();
|
||||||
}
|
// j.WritePropertyName(nameof(Name));
|
||||||
|
// j.WriteValue(mod.Name);
|
||||||
/// <summary> Create a sub mod without a mod or group only for saving it in the creator. </summary>
|
// j.WritePropertyName(nameof(Description));
|
||||||
internal static SubMod CreateForSaving(string name)
|
// j.WriteValue(mod.Description);
|
||||||
=> new(null!, null!)
|
// if (priority != null)
|
||||||
{
|
// {
|
||||||
Name = name,
|
// j.WritePropertyName(nameof(IModGroup.Priority));
|
||||||
};
|
// j.WriteValue(priority.Value.Value);
|
||||||
|
// }
|
||||||
|
//
|
||||||
public static void WriteSubMod(JsonWriter j, JsonSerializer serializer, SubMod mod, DirectoryInfo basePath, ModPriority? priority)
|
// j.WritePropertyName(nameof(mod.Files));
|
||||||
{
|
// j.WriteStartObject();
|
||||||
j.WriteStartObject();
|
// foreach (var (gamePath, file) in mod.Files)
|
||||||
j.WritePropertyName(nameof(Name));
|
// {
|
||||||
j.WriteValue(mod.Name);
|
// if (file.ToRelPath(basePath, out var relPath))
|
||||||
j.WritePropertyName(nameof(Description));
|
// {
|
||||||
j.WriteValue(mod.Description);
|
// j.WritePropertyName(gamePath.ToString());
|
||||||
if (priority != null)
|
// j.WriteValue(relPath.ToString());
|
||||||
{
|
// }
|
||||||
j.WritePropertyName(nameof(IModGroup.Priority));
|
// }
|
||||||
j.WriteValue(priority.Value.Value);
|
//
|
||||||
}
|
// j.WriteEndObject();
|
||||||
|
// j.WritePropertyName(nameof(mod.FileSwaps));
|
||||||
j.WritePropertyName(nameof(mod.Files));
|
// j.WriteStartObject();
|
||||||
j.WriteStartObject();
|
// foreach (var (gamePath, file) in mod.FileSwaps)
|
||||||
foreach (var (gamePath, file) in mod.Files)
|
// {
|
||||||
{
|
// j.WritePropertyName(gamePath.ToString());
|
||||||
if (file.ToRelPath(basePath, out var relPath))
|
// j.WriteValue(file.ToString());
|
||||||
{
|
// }
|
||||||
j.WritePropertyName(gamePath.ToString());
|
//
|
||||||
j.WriteValue(relPath.ToString());
|
// j.WriteEndObject();
|
||||||
}
|
// j.WritePropertyName(nameof(mod.Manipulations));
|
||||||
}
|
// serializer.Serialize(j, mod.Manipulations);
|
||||||
|
// j.WriteEndObject();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -18,49 +18,46 @@ public class TemporaryMod : IMod
|
||||||
public int TotalManipulations
|
public int TotalManipulations
|
||||||
=> Default.Manipulations.Count;
|
=> Default.Manipulations.Count;
|
||||||
|
|
||||||
public readonly SubMod Default;
|
public readonly DefaultSubMod Default;
|
||||||
|
|
||||||
public AppliedModData GetData(ModSettings? settings = null)
|
public AppliedModData GetData(ModSettings? settings = null)
|
||||||
{
|
{
|
||||||
Dictionary<Utf8GamePath, FullPath> dict;
|
Dictionary<Utf8GamePath, FullPath> dict;
|
||||||
if (Default.FileSwapData.Count == 0)
|
if (Default.FileSwaps.Count == 0)
|
||||||
{
|
{
|
||||||
dict = Default.FileData;
|
dict = Default.Files;
|
||||||
}
|
}
|
||||||
else if (Default.FileData.Count == 0)
|
else if (Default.Files.Count == 0)
|
||||||
{
|
{
|
||||||
dict = Default.FileSwapData;
|
dict = Default.FileSwaps;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Need to ensure uniqueness.
|
// Need to ensure uniqueness.
|
||||||
dict = new Dictionary<Utf8GamePath, FullPath>(Default.FileData.Count + Default.FileSwaps.Count);
|
dict = new Dictionary<Utf8GamePath, FullPath>(Default.Files.Count + Default.FileSwaps.Count);
|
||||||
foreach (var (gamePath, file) in Default.FileData.Concat(Default.FileSwaps))
|
foreach (var (gamePath, file) in Default.Files.Concat(Default.FileSwaps))
|
||||||
dict.TryAdd(gamePath, file);
|
dict.TryAdd(gamePath, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AppliedModData(dict, Default.ManipulationData);
|
return new AppliedModData(dict, Default.Manipulations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<IModGroup> Groups
|
public IReadOnlyList<IModGroup> Groups
|
||||||
=> Array.Empty<IModGroup>();
|
=> Array.Empty<IModGroup>();
|
||||||
|
|
||||||
public IEnumerable<SubMod> AllSubMods
|
|
||||||
=> [Default];
|
|
||||||
|
|
||||||
public TemporaryMod()
|
public TemporaryMod()
|
||||||
=> Default = SubMod.CreateDefault(this);
|
=> Default = new(this);
|
||||||
|
|
||||||
public void SetFile(Utf8GamePath gamePath, FullPath fullPath)
|
public void SetFile(Utf8GamePath gamePath, FullPath fullPath)
|
||||||
=> Default.FileData[gamePath] = fullPath;
|
=> Default.Files[gamePath] = fullPath;
|
||||||
|
|
||||||
public bool SetManipulation(MetaManipulation manip)
|
public bool SetManipulation(MetaManipulation manip)
|
||||||
=> Default.ManipulationData.Remove(manip) | Default.ManipulationData.Add(manip);
|
=> Default.Manipulations.Remove(manip) | Default.Manipulations.Add(manip);
|
||||||
|
|
||||||
public void SetAll(Dictionary<Utf8GamePath, FullPath> dict, HashSet<MetaManipulation> manips)
|
public void SetAll(Dictionary<Utf8GamePath, FullPath> dict, HashSet<MetaManipulation> manips)
|
||||||
{
|
{
|
||||||
Default.FileData = dict;
|
Default.Files = dict;
|
||||||
Default.ManipulationData = manips;
|
Default.Manipulations = manips;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SaveTempCollection(Configuration config, SaveService saveService, ModManager modManager, ModCollection collection,
|
public static void SaveTempCollection(Configuration config, SaveService saveService, ModManager modManager, ModCollection collection,
|
||||||
|
|
@ -93,16 +90,16 @@ public class TemporaryMod : IMod
|
||||||
{
|
{
|
||||||
var target = Path.Combine(fileDir.FullName, Path.GetFileName(targetPath));
|
var target = Path.Combine(fileDir.FullName, Path.GetFileName(targetPath));
|
||||||
File.Copy(targetPath, target, true);
|
File.Copy(targetPath, target, true);
|
||||||
defaultMod.FileData[gamePath] = new FullPath(target);
|
defaultMod.Files[gamePath] = new FullPath(target);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
defaultMod.FileSwapData[gamePath] = new FullPath(targetPath);
|
defaultMod.FileSwaps[gamePath] = new FullPath(targetPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var manip in collection.MetaCache?.Manipulations ?? Array.Empty<MetaManipulation>())
|
foreach (var manip in collection.MetaCache?.Manipulations ?? Array.Empty<MetaManipulation>())
|
||||||
defaultMod.ManipulationData.Add(manip);
|
defaultMod.Manipulations.Add(manip);
|
||||||
|
|
||||||
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport));
|
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport));
|
||||||
modManager.AddMod(dir);
|
modManager.AddMod(dir);
|
||||||
|
|
|
||||||
|
|
@ -305,7 +305,7 @@ public class FileEditor<T>(
|
||||||
UiHelpers.Text(gamePath.Path);
|
UiHelpers.Text(gamePath.Path);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value());
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value());
|
||||||
ImGui.TextUnformatted(option.FullName);
|
ImGui.TextUnformatted(option.GetFullName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.Mods;
|
|
||||||
using Penumbra.Mods.Editor;
|
using Penumbra.Mods.Editor;
|
||||||
using Penumbra.Mods.Subclasses;
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
@ -79,7 +78,7 @@ public partial class ModEditWindow
|
||||||
var file = f.RelPath.ToString();
|
var file = f.RelPath.ToString();
|
||||||
return f.SubModUsage.Count == 0
|
return f.SubModUsage.Count == 0
|
||||||
? Enumerable.Repeat((file, "Unused", string.Empty, 0x40000080u), 1)
|
? Enumerable.Repeat((file, "Unused", string.Empty, 0x40000080u), 1)
|
||||||
: f.SubModUsage.Select(s => (file, s.Item2.ToString(), s.Item1.FullName,
|
: f.SubModUsage.Select(s => (file, s.Item2.ToString(), s.Item1.GetFullName(),
|
||||||
_editor.Option! == s.Item1 && Mod!.HasOptions ? 0x40008000u : 0u));
|
_editor.Option! == s.Item1 && Mod!.HasOptions ? 0x40008000u : 0u));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -148,13 +147,13 @@ public partial class ModEditWindow
|
||||||
(string, int) GetMulti()
|
(string, int) GetMulti()
|
||||||
{
|
{
|
||||||
var groups = registry.SubModUsage.GroupBy(s => s.Item1).ToArray();
|
var groups = registry.SubModUsage.GroupBy(s => s.Item1).ToArray();
|
||||||
return (string.Join("\n", groups.Select(g => g.Key.Name)), groups.Length);
|
return (string.Join("\n", groups.Select(g => g.Key.GetName())), groups.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
var (text, groupCount) = color switch
|
var (text, groupCount) = color switch
|
||||||
{
|
{
|
||||||
ColorId.ConflictingMod => (string.Empty, 0),
|
ColorId.ConflictingMod => (string.Empty, 0),
|
||||||
ColorId.NewMod => (registry.SubModUsage[0].Item1.Name, 1),
|
ColorId.NewMod => (registry.SubModUsage[0].Item1.GetName(), 1),
|
||||||
ColorId.InheritedMod => GetMulti(),
|
ColorId.InheritedMod => GetMulti(),
|
||||||
_ => (string.Empty, 0),
|
_ => (string.Empty, 0),
|
||||||
};
|
};
|
||||||
|
|
@ -192,7 +191,7 @@ public partial class ModEditWindow
|
||||||
ImGuiUtil.RightAlign(rightText);
|
ImGuiUtil.RightAlign(rightText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintGamePath(int i, int j, FileRegistry registry, SubMod subMod, Utf8GamePath gamePath)
|
private void PrintGamePath(int i, int j, FileRegistry registry, IModDataContainer subMod, Utf8GamePath gamePath)
|
||||||
{
|
{
|
||||||
using var id = ImRaii.PushId(j);
|
using var id = ImRaii.PushId(j);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
@ -228,7 +227,7 @@ public partial class ModEditWindow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintNewGamePath(int i, FileRegistry registry, SubMod subMod)
|
private void PrintNewGamePath(int i, FileRegistry registry, IModDataContainer 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();
|
||||||
|
|
@ -301,9 +300,9 @@ public partial class ModEditWindow
|
||||||
tt = changes ? "Apply the current file setup to the currently selected option." : "No changes made.";
|
tt = changes ? "Apply the current file setup to the currently selected option." : "No changes made.";
|
||||||
if (ImGuiUtil.DrawDisabledButton("Apply Changes", Vector2.Zero, tt, !changes))
|
if (ImGuiUtil.DrawDisabledButton("Apply Changes", Vector2.Zero, tt, !changes))
|
||||||
{
|
{
|
||||||
var failedFiles = _editor.FileEditor.Apply(_editor.Mod!, (SubMod)_editor.Option!);
|
var failedFiles = _editor.FileEditor.Apply(_editor.Mod!, _editor.Option!);
|
||||||
if (failedFiles > 0)
|
if (failedFiles > 0)
|
||||||
Penumbra.Log.Information($"Failed to apply {failedFiles} file redirections to {_editor.Option!.FullName}.");
|
Penumbra.Log.Information($"Failed to apply {failedFiles} file redirections to {_editor.Option!.GetFullName()}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ public partial class ModEditWindow
|
||||||
var tt = setsEqual ? "No changes staged." : "Apply the currently staged changes to the option.";
|
var tt = setsEqual ? "No changes staged." : "Apply the currently staged changes to the option.";
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
if (ImGuiUtil.DrawDisabledButton("Apply Changes", Vector2.Zero, tt, setsEqual))
|
if (ImGuiUtil.DrawDisabledButton("Apply Changes", Vector2.Zero, tt, setsEqual))
|
||||||
_editor.MetaEditor.Apply(_editor.Mod!, _editor.GroupIdx, _editor.OptionIdx);
|
_editor.MetaEditor.Apply(_editor.Mod!, _editor.GroupIdx, _editor.DataIdx);
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
tt = setsEqual ? "No changes staged." : "Revert all currently staged changes.";
|
tt = setsEqual ? "No changes staged." : "Revert all currently staged changes.";
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ public partial class ModEditWindow
|
||||||
{
|
{
|
||||||
// TODO: Is it worth trying to order results based on option priorities for cases where more than one match is found?
|
// TODO: Is it worth trying to order results based on option priorities for cases where more than one match is found?
|
||||||
// NOTE: We're using case-insensitive comparisons, as option group paths in mods are stored in lower case, but the mod editor uses paths directly from the file system, which may be mixed case.
|
// NOTE: We're using case-insensitive comparisons, as option group paths in mods are stored in lower case, but the mod editor uses paths directly from the file system, which may be mixed case.
|
||||||
return mod.AllSubMods
|
return mod.AllDataContainers
|
||||||
.SelectMany(m => m.Files.Concat(m.FileSwaps))
|
.SelectMany(m => m.Files.Concat(m.FileSwaps))
|
||||||
.Where(kv => kv.Value.FullName.Equals(path, StringComparison.OrdinalIgnoreCase))
|
.Where(kv => kv.Value.FullName.Equals(path, StringComparison.OrdinalIgnoreCase))
|
||||||
.Select(kv => kv.Key)
|
.Select(kv => kv.Key)
|
||||||
|
|
@ -103,7 +103,7 @@ public partial class ModEditWindow
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
// Filter then prepend the current option to ensure it's chosen first.
|
// Filter then prepend the current option to ensure it's chosen first.
|
||||||
return mod.AllSubMods
|
return mod.AllDataContainers
|
||||||
.Where(subMod => subMod != option)
|
.Where(subMod => subMod != option)
|
||||||
.Prepend(option)
|
.Prepend(option)
|
||||||
.SelectMany(subMod => subMod.Manipulations)
|
.SelectMany(subMod => subMod.Manipulations)
|
||||||
|
|
|
||||||
|
|
@ -187,8 +187,8 @@ public partial class ModEditWindow
|
||||||
if (editor == null)
|
if (editor == null)
|
||||||
return new QuickImportAction(owner._editor, FallbackOptionName, gamePath);
|
return new QuickImportAction(owner._editor, FallbackOptionName, gamePath);
|
||||||
|
|
||||||
var subMod = editor.Option;
|
var subMod = editor.Option!;
|
||||||
var optionName = subMod!.FullName;
|
var optionName = subMod is IModOption o ? o.FullName : FallbackOptionName;
|
||||||
if (gamePath.IsEmpty || file == null || editor.FileEditor.Changes)
|
if (gamePath.IsEmpty || file == null || editor.FileEditor.Changes)
|
||||||
return new QuickImportAction(editor, optionName, gamePath);
|
return new QuickImportAction(editor, optionName, gamePath);
|
||||||
|
|
||||||
|
|
@ -199,7 +199,7 @@ public partial class ModEditWindow
|
||||||
if (mod == null)
|
if (mod == null)
|
||||||
return new QuickImportAction(editor, optionName, gamePath);
|
return new QuickImportAction(editor, optionName, gamePath);
|
||||||
|
|
||||||
var (preferredPath, subDirs) = GetPreferredPath(mod, subMod, owner._config.ReplaceNonAsciiOnImport);
|
var (preferredPath, subDirs) = GetPreferredPath(mod, subMod as IModOption, owner._config.ReplaceNonAsciiOnImport);
|
||||||
var targetPath = new FullPath(Path.Combine(preferredPath.FullName, gamePath.ToString())).FullName;
|
var targetPath = new FullPath(Path.Combine(preferredPath.FullName, gamePath.ToString())).FullName;
|
||||||
if (File.Exists(targetPath))
|
if (File.Exists(targetPath))
|
||||||
return new QuickImportAction(editor, optionName, gamePath);
|
return new QuickImportAction(editor, optionName, gamePath);
|
||||||
|
|
@ -222,16 +222,16 @@ public partial class ModEditWindow
|
||||||
{
|
{
|
||||||
fileRegistry,
|
fileRegistry,
|
||||||
}, _subDirs);
|
}, _subDirs);
|
||||||
_editor.FileEditor.Apply(_editor.Mod!, (SubMod)_editor.Option!);
|
_editor.FileEditor.Apply(_editor.Mod!, _editor.Option!);
|
||||||
|
|
||||||
return fileRegistry;
|
return fileRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (DirectoryInfo, int) GetPreferredPath(Mod mod, SubMod subMod, bool replaceNonAscii)
|
private static (DirectoryInfo, int) GetPreferredPath(Mod mod, IModOption? subMod, bool replaceNonAscii)
|
||||||
{
|
{
|
||||||
var path = mod.ModPath;
|
var path = mod.ModPath;
|
||||||
var subDirs = 0;
|
var subDirs = 0;
|
||||||
if (subMod == mod.Default)
|
if (subMod == null)
|
||||||
return (path, subDirs);
|
return (path, subDirs);
|
||||||
|
|
||||||
var name = subMod.Name;
|
var name = subMod.Name;
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,10 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
_forceTextureStartPath = true;
|
_forceTextureStartPath = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeOption(SubMod? subMod)
|
public void ChangeOption(IModDataContainer? subMod)
|
||||||
{
|
{
|
||||||
var (groupIdx, optionIdx) = subMod?.GetIndices() ?? (-1, 0);
|
var (groupIdx, dataIdx) = subMod?.GetDataIndices() ?? (-1, 0);
|
||||||
_editor.LoadOption(groupIdx, optionIdx);
|
_editor.LoadOption(groupIdx, dataIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateModels()
|
public void UpdateModels()
|
||||||
|
|
@ -111,7 +111,7 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
});
|
});
|
||||||
var manipulations = 0;
|
var manipulations = 0;
|
||||||
var subMods = 0;
|
var subMods = 0;
|
||||||
var swaps = Mod!.AllSubMods.Sum(m =>
|
var swaps = Mod!.AllDataContainers.Sum(m =>
|
||||||
{
|
{
|
||||||
++subMods;
|
++subMods;
|
||||||
manipulations += m.Manipulations.Count;
|
manipulations += m.Manipulations.Count;
|
||||||
|
|
@ -330,7 +330,7 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
else if (ImGuiUtil.DrawDisabledButton("Re-Duplicate and Normalize Mod", Vector2.Zero, tt, !_allowReduplicate && !modifier))
|
else if (ImGuiUtil.DrawDisabledButton("Re-Duplicate and Normalize Mod", Vector2.Zero, tt, !_allowReduplicate && !modifier))
|
||||||
{
|
{
|
||||||
_editor.ModNormalizer.Normalize(Mod!);
|
_editor.ModNormalizer.Normalize(Mod!);
|
||||||
_editor.ModNormalizer.Worker.ContinueWith(_ => _editor.LoadMod(Mod!, _editor.GroupIdx, _editor.OptionIdx), TaskScheduler.Default);
|
_editor.ModNormalizer.Worker.ContinueWith(_ => _editor.LoadMod(Mod!, _editor.GroupIdx, _editor.DataIdx), TaskScheduler.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_editor.Duplicates.Worker.IsCompleted)
|
if (!_editor.Duplicates.Worker.IsCompleted)
|
||||||
|
|
@ -405,7 +405,7 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
var width = new Vector2(ImGui.GetContentRegionAvail().X / 3, 0);
|
var width = new Vector2(ImGui.GetContentRegionAvail().X / 3, 0);
|
||||||
var ret = false;
|
var ret = false;
|
||||||
if (ImGuiUtil.DrawDisabledButton(defaultOption, width, "Switch to the default option for the mod.\nThis resets unsaved changes.",
|
if (ImGuiUtil.DrawDisabledButton(defaultOption, width, "Switch to the default option for the mod.\nThis resets unsaved changes.",
|
||||||
_editor.Option!.IsDefault))
|
_editor.Option is DefaultSubMod))
|
||||||
{
|
{
|
||||||
_editor.LoadOption(-1, 0);
|
_editor.LoadOption(-1, 0);
|
||||||
ret = true;
|
ret = true;
|
||||||
|
|
@ -414,7 +414,7 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGuiUtil.DrawDisabledButton("Refresh Data", width, "Refresh data for the current option.\nThis resets unsaved changes.", false))
|
if (ImGuiUtil.DrawDisabledButton("Refresh Data", width, "Refresh data for the current option.\nThis resets unsaved changes.", false))
|
||||||
{
|
{
|
||||||
_editor.LoadMod(_editor.Mod!, _editor.GroupIdx, _editor.OptionIdx);
|
_editor.LoadMod(_editor.Mod!, _editor.GroupIdx, _editor.DataIdx);
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -422,17 +422,17 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
ImGui.SetNextItemWidth(width.X);
|
ImGui.SetNextItemWidth(width.X);
|
||||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value());
|
using var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value());
|
||||||
using var combo = ImRaii.Combo("##optionSelector", _editor.Option.FullName);
|
using var combo = ImRaii.Combo("##optionSelector", _editor.Option!.GetFullName());
|
||||||
if (!combo)
|
if (!combo)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
foreach (var (option, idx) in Mod!.AllSubMods.WithIndex())
|
foreach (var (option, idx) in Mod!.AllDataContainers.WithIndex())
|
||||||
{
|
{
|
||||||
using var id = ImRaii.PushId(idx);
|
using var id = ImRaii.PushId(idx);
|
||||||
if (ImGui.Selectable(option.FullName, option == _editor.Option))
|
if (ImGui.Selectable(option.GetFullName(), option == _editor.Option))
|
||||||
{
|
{
|
||||||
var (groupIdx, optionIdx) = option.GetIndices();
|
var (groupIdx, dataIdx) = option.GetDataIndices();
|
||||||
_editor.LoadOption(groupIdx, optionIdx);
|
_editor.LoadOption(groupIdx, dataIdx);
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -455,7 +455,7 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
var tt = setsEqual ? "No changes staged." : "Apply the currently staged changes to the option.";
|
var tt = setsEqual ? "No changes staged." : "Apply the currently staged changes to the option.";
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
if (ImGuiUtil.DrawDisabledButton("Apply Changes", Vector2.Zero, tt, setsEqual))
|
if (ImGuiUtil.DrawDisabledButton("Apply Changes", Vector2.Zero, tt, setsEqual))
|
||||||
_editor.SwapEditor.Apply(_editor.Mod!, _editor.GroupIdx, _editor.OptionIdx);
|
_editor.SwapEditor.Apply(_editor.Mod!, _editor.GroupIdx, _editor.DataIdx);
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
tt = setsEqual ? "No changes staged." : "Revert all currently staged changes.";
|
tt = setsEqual ? "No changes staged." : "Revert all currently staged changes.";
|
||||||
|
|
@ -569,7 +569,7 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Mod != null)
|
if (Mod != null)
|
||||||
foreach (var option in Mod.AllSubMods)
|
foreach (var option in Mod.AllDataContainers)
|
||||||
{
|
{
|
||||||
foreach (var path in option.Files.Keys)
|
foreach (var path in option.Files.Keys)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,7 @@ public class ModMergeTab(ModMerger modMerger)
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
DrawCombo(size - ImGui.GetItemRectSize().X - ImGui.GetStyle().ItemSpacing.X);
|
DrawCombo(size - ImGui.GetItemRectSize().X - ImGui.GetStyle().ItemSpacing.X);
|
||||||
|
|
||||||
var width = ImGui.GetItemRectSize();
|
using (ImRaii.Group())
|
||||||
using (var g = ImRaii.Group())
|
|
||||||
{
|
{
|
||||||
using var disabled = ImRaii.Disabled(modMerger.MergeFromMod.HasOptions);
|
using var disabled = ImRaii.Disabled(modMerger.MergeFromMod.HasOptions);
|
||||||
var buttonWidth = (size - ImGui.GetStyle().ItemSpacing.X) / 2;
|
var buttonWidth = (size - ImGui.GetStyle().ItemSpacing.X) / 2;
|
||||||
|
|
@ -124,13 +123,13 @@ public class ModMergeTab(ModMerger modMerger)
|
||||||
ImGui.Dummy(Vector2.One);
|
ImGui.Dummy(Vector2.One);
|
||||||
var buttonSize = new Vector2((size - 2 * ImGui.GetStyle().ItemSpacing.X) / 3, 0);
|
var buttonSize = new Vector2((size - 2 * ImGui.GetStyle().ItemSpacing.X) / 3, 0);
|
||||||
if (ImGui.Button("Select All", buttonSize))
|
if (ImGui.Button("Select All", buttonSize))
|
||||||
modMerger.SelectedOptions.UnionWith(modMerger.MergeFromMod!.AllSubMods);
|
modMerger.SelectedOptions.UnionWith(modMerger.MergeFromMod!.AllDataContainers);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Unselect All", buttonSize))
|
if (ImGui.Button("Unselect All", buttonSize))
|
||||||
modMerger.SelectedOptions.Clear();
|
modMerger.SelectedOptions.Clear();
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Invert Selection", buttonSize))
|
if (ImGui.Button("Invert Selection", buttonSize))
|
||||||
modMerger.SelectedOptions.SymmetricExceptWith(modMerger.MergeFromMod!.AllSubMods);
|
modMerger.SelectedOptions.SymmetricExceptWith(modMerger.MergeFromMod!.AllDataContainers);
|
||||||
DrawOptionTable(size);
|
DrawOptionTable(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,7 +143,7 @@ public class ModMergeTab(ModMerger modMerger)
|
||||||
|
|
||||||
private void DrawOptionTable(float size)
|
private void DrawOptionTable(float size)
|
||||||
{
|
{
|
||||||
var options = modMerger.MergeFromMod!.AllSubMods.ToList();
|
var options = modMerger.MergeFromMod!.AllDataContainers.ToList();
|
||||||
var height = modMerger.Warnings.Count == 0 && modMerger.Error == null
|
var height = modMerger.Warnings.Count == 0 && modMerger.Error == null
|
||||||
? ImGui.GetContentRegionAvail().Y - 3 * ImGui.GetFrameHeightWithSpacing()
|
? ImGui.GetContentRegionAvail().Y - 3 * ImGui.GetFrameHeightWithSpacing()
|
||||||
: 8 * ImGui.GetFrameHeightWithSpacing();
|
: 8 * ImGui.GetFrameHeightWithSpacing();
|
||||||
|
|
@ -176,47 +175,41 @@ public class ModMergeTab(ModMerger modMerger)
|
||||||
if (ImGui.Checkbox("##check", ref selected))
|
if (ImGui.Checkbox("##check", ref selected))
|
||||||
Handle(option, selected);
|
Handle(option, selected);
|
||||||
|
|
||||||
if (option.IsDefault)
|
if (option.Group is not { } group)
|
||||||
{
|
{
|
||||||
ImGuiUtil.DrawTableColumn(option.FullName);
|
ImGuiUtil.DrawTableColumn(option.GetFullName());
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGuiUtil.DrawTableColumn(option.Name);
|
ImGuiUtil.DrawTableColumn(option.GetName());
|
||||||
var group = option.Group;
|
|
||||||
var optionEnumerator = group switch
|
|
||||||
{
|
|
||||||
SingleModGroup single => single.OptionData,
|
|
||||||
MultiModGroup multi => multi.PrioritizedOptions.Select(o => o.Mod),
|
|
||||||
_ => [],
|
|
||||||
};
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Selectable(group.Name, false);
|
ImGui.Selectable(group.Name, false);
|
||||||
if (ImGui.BeginPopupContextItem("##groupContext"))
|
if (ImGui.BeginPopupContextItem("##groupContext"))
|
||||||
{
|
{
|
||||||
if (ImGui.MenuItem("Select All"))
|
if (ImGui.MenuItem("Select All"))
|
||||||
// ReSharper disable once PossibleMultipleEnumeration
|
// ReSharper disable once PossibleMultipleEnumeration
|
||||||
foreach (var opt in optionEnumerator)
|
foreach (var opt in group.DataContainers)
|
||||||
Handle(opt, true);
|
Handle(opt, true);
|
||||||
|
|
||||||
if (ImGui.MenuItem("Unselect All"))
|
if (ImGui.MenuItem("Unselect All"))
|
||||||
// ReSharper disable once PossibleMultipleEnumeration
|
// ReSharper disable once PossibleMultipleEnumeration
|
||||||
foreach (var opt in optionEnumerator)
|
foreach (var opt in group.DataContainers)
|
||||||
Handle(opt, false);
|
Handle(opt, false);
|
||||||
ImGui.EndPopup();
|
ImGui.EndPopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGuiUtil.RightAlign(option.FileData.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
|
ImGuiUtil.RightAlign(option.Files.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGuiUtil.RightAlign(option.FileSwapData.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
|
ImGuiUtil.RightAlign(option.FileSwaps.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGuiUtil.RightAlign(option.Manipulations.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
|
ImGuiUtil.RightAlign(option.Manipulations.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
void Handle(SubMod option2, bool selected2)
|
void Handle(IModDataContainer option2, bool selected2)
|
||||||
{
|
{
|
||||||
if (selected2)
|
if (selected2)
|
||||||
modMerger.SelectedOptions.Add(option2);
|
modMerger.SelectedOptions.Add(option2);
|
||||||
|
|
|
||||||
|
|
@ -486,7 +486,7 @@ public class ModPanelEditTab(
|
||||||
EditOption(panel, single, groupIdx, optionIdx);
|
EditOption(panel, single, groupIdx, optionIdx);
|
||||||
break;
|
break;
|
||||||
case MultiModGroup multi:
|
case MultiModGroup multi:
|
||||||
for (var optionIdx = 0; optionIdx < multi.PrioritizedOptions.Count; ++optionIdx)
|
for (var optionIdx = 0; optionIdx < multi.OptionData.Count; ++optionIdx)
|
||||||
EditOption(panel, multi, groupIdx, optionIdx);
|
EditOption(panel, multi, groupIdx, optionIdx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -542,7 +542,7 @@ public class ModPanelEditTab(
|
||||||
if (group is not MultiModGroup multi)
|
if (group is not MultiModGroup multi)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Input.Priority("##Priority", groupIdx, optionIdx, multi.PrioritizedOptions[optionIdx].Priority, out var priority,
|
if (Input.Priority("##Priority", groupIdx, optionIdx, multi.OptionData[optionIdx].Priority, out var priority,
|
||||||
50 * UiHelpers.Scale))
|
50 * UiHelpers.Scale))
|
||||||
panel._modManager.OptionEditor.ChangeOptionPriority(panel._mod, groupIdx, optionIdx, priority);
|
panel._modManager.OptionEditor.ChangeOptionPriority(panel._mod, groupIdx, optionIdx, priority);
|
||||||
|
|
||||||
|
|
@ -557,7 +557,7 @@ public class ModPanelEditTab(
|
||||||
var count = group switch
|
var count = group switch
|
||||||
{
|
{
|
||||||
SingleModGroup single => single.OptionData.Count,
|
SingleModGroup single => single.OptionData.Count,
|
||||||
MultiModGroup multi => multi.PrioritizedOptions.Count,
|
MultiModGroup multi => multi.OptionData.Count,
|
||||||
_ => throw new Exception($"Dragging options to an option group of type {group.GetType()} is not supported."),
|
_ => throw new Exception($"Dragging options to an option group of type {group.GetType()} is not supported."),
|
||||||
};
|
};
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
@ -591,6 +591,9 @@ public class ModPanelEditTab(
|
||||||
// Handle drag and drop to move options inside a group or into another group.
|
// Handle drag and drop to move options inside a group or into another group.
|
||||||
private static void Source(IModGroup group, int groupIdx, int optionIdx)
|
private static void Source(IModGroup group, int groupIdx, int optionIdx)
|
||||||
{
|
{
|
||||||
|
if (group is not ITexToolsGroup)
|
||||||
|
return;
|
||||||
|
|
||||||
using var source = ImRaii.DragDropSource();
|
using var source = ImRaii.DragDropSource();
|
||||||
if (!source)
|
if (!source)
|
||||||
return;
|
return;
|
||||||
|
|
@ -606,6 +609,9 @@ public class ModPanelEditTab(
|
||||||
|
|
||||||
private static void Target(ModPanelEditTab panel, IModGroup group, int groupIdx, int optionIdx)
|
private static void Target(ModPanelEditTab panel, IModGroup group, int groupIdx, int optionIdx)
|
||||||
{
|
{
|
||||||
|
if (group is not ITexToolsGroup)
|
||||||
|
return;
|
||||||
|
|
||||||
using var target = ImRaii.DragDropTarget();
|
using var target = ImRaii.DragDropTarget();
|
||||||
if (!target.Success || !ImGuiUtil.IsDropping(DragDropLabel))
|
if (!target.Success || !ImGuiUtil.IsDropping(DragDropLabel))
|
||||||
return;
|
return;
|
||||||
|
|
@ -624,22 +630,12 @@ public class ModPanelEditTab(
|
||||||
var sourceGroupIdx = _dragDropGroupIdx;
|
var sourceGroupIdx = _dragDropGroupIdx;
|
||||||
var sourceOption = _dragDropOptionIdx;
|
var sourceOption = _dragDropOptionIdx;
|
||||||
var sourceGroup = panel._mod.Groups[sourceGroupIdx];
|
var sourceGroup = panel._mod.Groups[sourceGroupIdx];
|
||||||
var currentCount = group switch
|
var currentCount = group.DataContainers.Count;
|
||||||
{
|
var option = ((ITexToolsGroup) sourceGroup).OptionData[_dragDropOptionIdx];
|
||||||
SingleModGroup single => single.OptionData.Count,
|
|
||||||
MultiModGroup multi => multi.PrioritizedOptions.Count,
|
|
||||||
_ => throw new Exception($"Dragging options to an option group of type {group.GetType()} is not supported."),
|
|
||||||
};
|
|
||||||
var (option, priority) = sourceGroup switch
|
|
||||||
{
|
|
||||||
SingleModGroup single => (single.OptionData[_dragDropOptionIdx], ModPriority.Default),
|
|
||||||
MultiModGroup multi => multi.PrioritizedOptions[_dragDropOptionIdx],
|
|
||||||
_ => throw new Exception($"Dragging options from an option group of type {sourceGroup.GetType()} is not supported."),
|
|
||||||
};
|
|
||||||
panel._delayedActions.Enqueue(() =>
|
panel._delayedActions.Enqueue(() =>
|
||||||
{
|
{
|
||||||
panel._modManager.OptionEditor.DeleteOption(panel._mod, sourceGroupIdx, sourceOption);
|
panel._modManager.OptionEditor.DeleteOption(panel._mod, sourceGroupIdx, sourceOption);
|
||||||
panel._modManager.OptionEditor.AddOption(panel._mod, groupIdx, option, priority);
|
panel._modManager.OptionEditor.AddOption(panel._mod, groupIdx, option);
|
||||||
panel._modManager.OptionEditor.MoveOption(panel._mod, groupIdx, currentCount, optionIdx);
|
panel._modManager.OptionEditor.MoveOption(panel._mod, groupIdx, currentCount, optionIdx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ public class ModPanelTabBar
|
||||||
if (ImGui.TabItemButton("Advanced Editing", ImGuiTabItemFlags.Trailing | ImGuiTabItemFlags.NoTooltip))
|
if (ImGui.TabItemButton("Advanced Editing", ImGuiTabItemFlags.Trailing | ImGuiTabItemFlags.NoTooltip))
|
||||||
{
|
{
|
||||||
_modEditWindow.ChangeMod(mod);
|
_modEditWindow.ChangeMod(mod);
|
||||||
_modEditWindow.ChangeOption((SubMod)mod.Default);
|
_modEditWindow.ChangeOption(mod.Default);
|
||||||
_modEditWindow.IsOpen = true;
|
_modEditWindow.IsOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue