mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +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)
|
||||
{
|
||||
var optionIdx = multi.PrioritizedOptions.IndexOf(o => o.Mod.Name == name);
|
||||
var optionIdx = multi.OptionData.IndexOf(o => o.Mod.Name == name);
|
||||
if (optionIdx < 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args);
|
||||
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ public partial class TexToolsImporter
|
|||
}
|
||||
|
||||
// Iterate through all pages
|
||||
var options = new List<SubMod>();
|
||||
var options = new List<MultiSubMod>();
|
||||
var groupPriority = ModPriority.Default;
|
||||
var groupNames = new HashSet<string>();
|
||||
foreach (var page in modList.ModPackPages)
|
||||
|
|
@ -183,7 +183,7 @@ public partial class TexToolsImporter
|
|||
var optionFolder = ModCreator.NewSubFolderName(groupFolder, option.Name, _config.ReplaceNonAsciiOnImport)
|
||||
?? new DirectoryInfo(Path.Combine(groupFolder.FullName, $"Option {i + optionIdx + 1}"));
|
||||
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)
|
||||
defaultSettings = group.SelectionType == GroupType.Multi
|
||||
? defaultSettings!.Value | Setting.Multi(i)
|
||||
|
|
@ -203,7 +203,7 @@ public partial class TexToolsImporter
|
|||
{
|
||||
var option = group.OptionList[idx];
|
||||
_currentOptionName = option.Name;
|
||||
options.Insert(idx, SubMod.CreateForSaving(option.Name));
|
||||
options.Insert(idx, MultiSubMod.CreateForSaving(option.Name, option.Description, ModPriority.Default));
|
||||
if (option.IsChecked)
|
||||
defaultSettings = Setting.Single(idx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,17 +50,15 @@ public unsafe class MetaFileManager
|
|||
TexToolsMeta.WriteTexToolsMeta(this, mod.Default.Manipulations, mod.ModPath);
|
||||
foreach (var group in mod.Groups)
|
||||
{
|
||||
if (group is not ITexToolsGroup texToolsGroup)
|
||||
continue;
|
||||
|
||||
var dir = ModCreator.NewOptionDirectory(mod.ModPath, group.Name, Config.ReplaceNonAsciiOnImport);
|
||||
if (!dir.Exists)
|
||||
dir.Create();
|
||||
|
||||
var optionEnumerator = group switch
|
||||
{
|
||||
SingleModGroup single => single.OptionData,
|
||||
MultiModGroup multi => multi.PrioritizedOptions.Select(o => o.Mod),
|
||||
_ => [],
|
||||
};
|
||||
foreach (var option in optionEnumerator)
|
||||
|
||||
foreach (var option in texToolsGroup.OptionData)
|
||||
{
|
||||
var optionDir = ModCreator.NewOptionDirectory(dir, option.Name, Config.ReplaceNonAsciiOnImport);
|
||||
if (!optionDir.Exists)
|
||||
|
|
@ -99,7 +97,7 @@ public unsafe class MetaFileManager
|
|||
return;
|
||||
|
||||
ResidentResources.Reload();
|
||||
if (collection?._cache == null)
|
||||
if (collection._cache == null)
|
||||
CharacterUtility.ResetAll();
|
||||
else
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
|
|
@ -72,7 +72,7 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co
|
|||
|
||||
return;
|
||||
|
||||
void HandleSubMod(SubMod subMod, int groupIdx, int optionIdx)
|
||||
void HandleSubMod(IModDataContainer subMod, int groupIdx, int optionIdx)
|
||||
{
|
||||
var changes = false;
|
||||
var dict = subMod.Files.ToDictionary(kvp => kvp.Key,
|
||||
|
|
@ -86,7 +86,7 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co
|
|||
}
|
||||
else
|
||||
{
|
||||
subMod.FileData = dict;
|
||||
subMod.Files = dict;
|
||||
saveService.ImmediateSaveSync(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ namespace Penumbra.Mods.Editor;
|
|||
|
||||
public class FileRegistry : IEquatable<FileRegistry>
|
||||
{
|
||||
public readonly List<(SubMod, Utf8GamePath)> SubModUsage = [];
|
||||
public FullPath File { get; private init; }
|
||||
public Utf8RelPath RelPath { get; private init; }
|
||||
public long FileSize { get; private init; }
|
||||
public int CurrentUsage;
|
||||
public bool IsOnPlayer;
|
||||
public readonly List<(IModDataContainer, Utf8GamePath)> SubModUsage = [];
|
||||
public FullPath File { get; private init; }
|
||||
public Utf8RelPath RelPath { get; private init; }
|
||||
public long FileSize { get; private init; }
|
||||
public int CurrentUsage;
|
||||
public bool IsOnPlayer;
|
||||
|
||||
public static bool FromFile(DirectoryInfo modPath, FileInfo file, [NotNullWhen(true)] out FileRegistry? registry)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using OtterGui;
|
||||
using OtterGui.Compression;
|
||||
using Penumbra.Mods.Subclasses;
|
||||
|
|
@ -25,20 +24,20 @@ public class ModEditor(
|
|||
public readonly MdlMaterialEditor MdlMaterialEditor = mdlMaterialEditor;
|
||||
public readonly FileCompactor Compactor = compactor;
|
||||
|
||||
public Mod? Mod { get; private set; }
|
||||
public int GroupIdx { get; private set; }
|
||||
public int OptionIdx { get; private set; }
|
||||
public Mod? Mod { get; private set; }
|
||||
public int GroupIdx { get; private set; }
|
||||
public int DataIdx { get; private set; }
|
||||
|
||||
public IModGroup? Group { get; private set; }
|
||||
public SubMod? Option { get; private set; }
|
||||
public IModGroup? Group { get; private set; }
|
||||
public IModDataContainer? Option { get; private set; }
|
||||
|
||||
public void LoadMod(Mod mod)
|
||||
=> LoadMod(mod, -1, 0);
|
||||
|
||||
public void LoadMod(Mod mod, int groupIdx, int optionIdx)
|
||||
public void LoadMod(Mod mod, int groupIdx, int dataIdx)
|
||||
{
|
||||
Mod = mod;
|
||||
LoadOption(groupIdx, optionIdx, true);
|
||||
LoadOption(groupIdx, dataIdx, true);
|
||||
Files.UpdateAll(mod, Option!);
|
||||
SwapEditor.Revert(Option!);
|
||||
MetaEditor.Load(Mod!, Option!);
|
||||
|
|
@ -46,9 +45,9 @@ public class ModEditor(
|
|||
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!);
|
||||
Files.UpdatePaths(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>
|
||||
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 (groupIdx == -1 && optionIdx == 0)
|
||||
if (groupIdx == -1 && dataIdx == 0)
|
||||
{
|
||||
Group = null;
|
||||
Option = Mod.Default;
|
||||
GroupIdx = groupIdx;
|
||||
OptionIdx = optionIdx;
|
||||
Group = null;
|
||||
Option = Mod.Default;
|
||||
GroupIdx = groupIdx;
|
||||
DataIdx = dataIdx;
|
||||
return;
|
||||
}
|
||||
|
||||
if (groupIdx >= 0)
|
||||
{
|
||||
Group = Mod.Groups[groupIdx];
|
||||
switch(Group)
|
||||
if (dataIdx >= 0 && dataIdx < Group.DataContainers.Count)
|
||||
{
|
||||
case SingleModGroup single when optionIdx >= 0 && optionIdx < single.OptionData.Count:
|
||||
Option = single.OptionData[optionIdx];
|
||||
GroupIdx = groupIdx;
|
||||
OptionIdx = optionIdx;
|
||||
return;
|
||||
case MultiModGroup multi when optionIdx >= 0 && optionIdx < multi.PrioritizedOptions.Count:
|
||||
Option = multi.PrioritizedOptions[optionIdx].Mod;
|
||||
GroupIdx = groupIdx;
|
||||
OptionIdx = optionIdx;
|
||||
return;
|
||||
Option = Group.DataContainers[dataIdx];
|
||||
GroupIdx = groupIdx;
|
||||
DataIdx = dataIdx;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Group = null;
|
||||
Option = Mod?.Default;
|
||||
GroupIdx = -1;
|
||||
OptionIdx = 0;
|
||||
Group = null;
|
||||
Option = Mod?.Default;
|
||||
GroupIdx = -1;
|
||||
DataIdx = 0;
|
||||
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()
|
||||
|
|
@ -111,7 +104,7 @@ public class ModEditor(
|
|||
=> Clear();
|
||||
|
||||
/// <summary> Apply a option action to all available option in a mod, including the default option. </summary>
|
||||
public static void ApplyToAllOptions(Mod mod, Action<SubMod, int, int> action)
|
||||
public static void ApplyToAllOptions(Mod mod, Action<IModDataContainer, int, int> action)
|
||||
{
|
||||
action(mod.Default, -1, 0);
|
||||
foreach (var (group, groupIdx) in mod.Groups.WithIndex())
|
||||
|
|
@ -123,8 +116,8 @@ public class ModEditor(
|
|||
action(single.OptionData[optionIdx], groupIdx, optionIdx);
|
||||
break;
|
||||
case MultiModGroup multi:
|
||||
for (var optionIdx = 0; optionIdx < multi.PrioritizedOptions.Count; ++optionIdx)
|
||||
action(multi.PrioritizedOptions[optionIdx].Mod, groupIdx, optionIdx);
|
||||
for (var optionIdx = 0; optionIdx < multi.OptionData.Count; ++optionIdx)
|
||||
action(multi.OptionData[optionIdx], groupIdx, optionIdx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,13 +38,13 @@ public class ModFileCollection : IDisposable
|
|||
|
||||
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());
|
||||
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());
|
||||
|
||||
public void Clear()
|
||||
|
|
@ -59,7 +59,7 @@ public class ModFileCollection : IDisposable
|
|||
public void ClearMissingFiles()
|
||||
=> _missing.Clear();
|
||||
|
||||
public void RemoveUsedPath(SubMod option, FileRegistry? file, Utf8GamePath gamePath)
|
||||
public void RemoveUsedPath(IModDataContainer option, FileRegistry? file, Utf8GamePath gamePath)
|
||||
{
|
||||
_usedPaths.Remove(gamePath);
|
||||
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);
|
||||
|
||||
public void AddUsedPath(SubMod option, FileRegistry? file, Utf8GamePath gamePath)
|
||||
public void AddUsedPath(IModDataContainer option, FileRegistry? file, Utf8GamePath gamePath)
|
||||
{
|
||||
_usedPaths.Add(gamePath);
|
||||
if (file == null)
|
||||
|
|
@ -82,7 +82,7 @@ public class ModFileCollection : IDisposable
|
|||
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);
|
||||
|
||||
public void ChangeUsedPath(FileRegistry file, int pathIdx, Utf8GamePath gamePath)
|
||||
|
|
@ -154,14 +154,14 @@ public class ModFileCollection : IDisposable
|
|||
_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();
|
||||
ClearPaths(clearRegistries, tok);
|
||||
|
||||
tok.ThrowIfCancellationRequested();
|
||||
|
||||
foreach (var subMod in mod.AllSubMods)
|
||||
foreach (var subMod in mod.AllDataContainers)
|
||||
{
|
||||
foreach (var (gamePath, file) in subMod.Files)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
|
|||
Changes = false;
|
||||
}
|
||||
|
||||
public int Apply(Mod mod, SubMod option)
|
||||
public int Apply(Mod mod, IModDataContainer option)
|
||||
{
|
||||
var dict = new Dictionary<Utf8GamePath, FullPath>();
|
||||
var num = 0;
|
||||
|
|
@ -24,23 +24,23 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
|
|||
num += dict.TryAdd(path.Item2, file.File) ? 0 : 1;
|
||||
}
|
||||
|
||||
var (groupIdx, optionIdx) = option.GetIndices();
|
||||
modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict);
|
||||
var (groupIdx, dataIdx) = option.GetDataIndices();
|
||||
modManager.OptionEditor.OptionSetFiles(mod, groupIdx, dataIdx, dict);
|
||||
files.UpdatePaths(mod, option);
|
||||
Changes = false;
|
||||
return num;
|
||||
}
|
||||
|
||||
public void Revert(Mod mod, SubMod option)
|
||||
public void Revert(Mod mod, IModDataContainer option)
|
||||
{
|
||||
files.UpdateAll(mod, option);
|
||||
Changes = false;
|
||||
}
|
||||
|
||||
/// <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))
|
||||
.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 pathIdx is equal to the total number of paths, path will be added, otherwise replaced.
|
||||
/// </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)
|
||||
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,
|
||||
/// and add them to the given option.
|
||||
/// </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;
|
||||
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>
|
||||
public void RemovePathsFromSelected(SubMod option, IEnumerable<FileRegistry> files1)
|
||||
public void RemovePathsFromSelected(IModDataContainer option, IEnumerable<FileRegistry> 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>
|
||||
public void DeleteFiles(Mod mod, SubMod option, IEnumerable<FileRegistry> files1)
|
||||
public void DeleteFiles(Mod mod, IModDataContainer option, IEnumerable<FileRegistry> files1)
|
||||
{
|
||||
var deletions = 0;
|
||||
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))
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Utility;
|
||||
using ImGuizmoNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Api.Enums;
|
||||
|
|
@ -33,9 +32,9 @@ public class ModMerger : IDisposable
|
|||
private readonly Dictionary<string, string> _fileToFile = [];
|
||||
private readonly HashSet<string> _createdDirectories = [];
|
||||
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 Exception? Error { get; private set; }
|
||||
|
|
@ -94,7 +93,7 @@ public class ModMerger : IDisposable
|
|||
|
||||
private void MergeWithOptions()
|
||||
{
|
||||
MergeIntoOption(Enumerable.Repeat(MergeFromMod!.Default, 1), MergeToMod!.Default, false);
|
||||
MergeIntoOption([MergeFromMod!.Default], MergeToMod!.Default, false);
|
||||
|
||||
foreach (var originalGroup in MergeFromMod!.Groups)
|
||||
{
|
||||
|
|
@ -105,20 +104,13 @@ public class ModMerger : IDisposable
|
|||
((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}.");
|
||||
|
||||
var optionEnumerator = group switch
|
||||
foreach (var originalOption in group.DataContainers)
|
||||
{
|
||||
SingleModGroup single => single.OptionData,
|
||||
MultiModGroup multi => multi.PrioritizedOptions.Select(o => o.Mod),
|
||||
_ => [],
|
||||
};
|
||||
|
||||
foreach (var originalOption in optionEnumerator)
|
||||
{
|
||||
var (option, _, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, originalOption.Name);
|
||||
var (option, _, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, originalOption.GetName());
|
||||
if (optionCreated)
|
||||
{
|
||||
_createdOptions.Add(option);
|
||||
MergeIntoOption(Enumerable.Repeat(originalOption, 1), option, false);
|
||||
_createdOptions.Add((IModDataOption)option);
|
||||
MergeIntoOption([originalOption], (IModDataOption)option, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -136,7 +128,7 @@ public class ModMerger : IDisposable
|
|||
if (groupName.Length == 0 && optionName.Length == 0)
|
||||
{
|
||||
CopyFiles(MergeToMod!.ModPath);
|
||||
MergeIntoOption(MergeFromMod!.AllSubMods.Reverse(), MergeToMod!.Default, true);
|
||||
MergeIntoOption(MergeFromMod!.AllDataContainers.Reverse(), MergeToMod!.Default, true);
|
||||
}
|
||||
else if (groupName.Length * optionName.Length == 0)
|
||||
{
|
||||
|
|
@ -148,7 +140,7 @@ public class ModMerger : IDisposable
|
|||
_createdGroups.Add(groupIdx);
|
||||
var (option, _, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, optionName, SaveType.None);
|
||||
if (optionCreated)
|
||||
_createdOptions.Add(option);
|
||||
_createdOptions.Add((IModDataOption)option);
|
||||
var dir = ModCreator.NewOptionDirectory(MergeToMod!.ModPath, groupName, _config.ReplaceNonAsciiOnImport);
|
||||
if (!dir.Exists)
|
||||
_createdDirectories.Add(dir.FullName);
|
||||
|
|
@ -156,14 +148,14 @@ public class ModMerger : IDisposable
|
|||
if (!dir.Exists)
|
||||
_createdDirectories.Add(dir.FullName);
|
||||
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 swaps = option.FileSwapData.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
var manips = option.ManipulationData.ToHashSet();
|
||||
var redirections = option.Files.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
var swaps = option.FileSwaps.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
var manips = option.Manipulations.ToHashSet();
|
||||
|
||||
foreach (var originalOption in mergeOptions)
|
||||
{
|
||||
|
|
@ -171,31 +163,31 @@ public class ModMerger : IDisposable
|
|||
{
|
||||
if (!manips.Add(manip))
|
||||
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)
|
||||
{
|
||||
if (!swaps.TryAdd(swapA, swapB))
|
||||
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)
|
||||
{
|
||||
if (!GetFullPath(path, out var newFile))
|
||||
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))
|
||||
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();
|
||||
_editor.OptionSetFiles(MergeToMod!, groupIdx, optionIdx, redirections, SaveType.None);
|
||||
_editor.OptionSetFileSwaps(MergeToMod!, groupIdx, optionIdx, swaps, SaveType.None);
|
||||
_editor.OptionSetManipulations(MergeToMod!, groupIdx, optionIdx, manips, SaveType.ImmediateSync);
|
||||
var (groupIdx, dataIdx) = option.GetDataIndices();
|
||||
_editor.OptionSetFiles(MergeToMod!, groupIdx, dataIdx, redirections, SaveType.None);
|
||||
_editor.OptionSetFileSwaps(MergeToMod!, groupIdx, dataIdx, swaps, SaveType.None);
|
||||
_editor.OptionSetManipulations(MergeToMod!, groupIdx, dataIdx, manips, SaveType.ImmediateSync);
|
||||
return;
|
||||
|
||||
bool GetFullPath(FullPath input, out FullPath ret)
|
||||
|
|
@ -270,30 +262,29 @@ public class ModMerger : IDisposable
|
|||
{
|
||||
var files = CopySubModFiles(mods[0], dir);
|
||||
_editor.OptionSetFiles(result, -1, 0, files);
|
||||
_editor.OptionSetFileSwaps(result, -1, 0, mods[0].FileSwapData);
|
||||
_editor.OptionSetManipulations(result, -1, 0, mods[0].ManipulationData);
|
||||
_editor.OptionSetFileSwaps(result, -1, 0, mods[0].FileSwaps);
|
||||
_editor.OptionSetManipulations(result, -1, 0, mods[0].Manipulations);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var originalOption in mods)
|
||||
{
|
||||
if (originalOption.IsDefault)
|
||||
if (originalOption.Group is not {} originalGroup)
|
||||
{
|
||||
var files = CopySubModFiles(mods[0], dir);
|
||||
_editor.OptionSetFiles(result, -1, 0, files);
|
||||
_editor.OptionSetFileSwaps(result, -1, 0, mods[0].FileSwapData);
|
||||
_editor.OptionSetManipulations(result, -1, 0, mods[0].ManipulationData);
|
||||
_editor.OptionSetFileSwaps(result, -1, 0, mods[0].FileSwaps);
|
||||
_editor.OptionSetManipulations(result, -1, 0, mods[0].Manipulations);
|
||||
}
|
||||
else
|
||||
{
|
||||
var originalGroup = originalOption.Group;
|
||||
var (group, groupIdx, _) = _editor.FindOrAddModGroup(result, originalGroup.Type, originalGroup.Name);
|
||||
var (option, optionIdx, _) = _editor.FindOrAddOption(result, groupIdx, originalOption.Name);
|
||||
var (group, groupIdx, _) = _editor.FindOrAddModGroup(result, originalGroup.Type, originalGroup.Name);
|
||||
var (option, optionIdx, _) = _editor.FindOrAddOption(result, groupIdx, originalOption.GetName());
|
||||
var folder = Path.Combine(dir.FullName, group.Name, option.Name);
|
||||
var files = CopySubModFiles(originalOption, new DirectoryInfo(folder));
|
||||
_editor.OptionSetFiles(result, groupIdx, optionIdx, files);
|
||||
_editor.OptionSetFileSwaps(result, groupIdx, optionIdx, originalOption.FileSwapData);
|
||||
_editor.OptionSetManipulations(result, groupIdx, optionIdx, originalOption.ManipulationData);
|
||||
_editor.OptionSetFileSwaps(result, groupIdx, optionIdx, originalOption.FileSwaps);
|
||||
_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;
|
||||
foreach (var (path, file) in option.FileData)
|
||||
foreach (var (path, file) in option.Files)
|
||||
{
|
||||
var target = Path.GetRelativePath(parentPath, file.FullName);
|
||||
target = Path.Combine(newMod.FullName, target);
|
||||
|
|
@ -348,7 +339,7 @@ public class ModMerger : IDisposable
|
|||
{
|
||||
foreach (var option in _createdOptions)
|
||||
{
|
||||
var (groupIdx, optionIdx) = option.GetIndices();
|
||||
var (groupIdx, optionIdx) = option.GetOptionIndices();
|
||||
_editor.DeleteOption(MergeToMod!, groupIdx, optionIdx);
|
||||
Penumbra.Log.Verbose($"[Merger] Removed option {option.FullName}.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ public class ModMetaEditor(ModManager modManager)
|
|||
Changes = true;
|
||||
}
|
||||
|
||||
public void Load(Mod mod, SubMod currentOption)
|
||||
public void Load(Mod mod, IModDataContainer currentOption)
|
||||
{
|
||||
OtherImcCount = 0;
|
||||
OtherEqpCount = 0;
|
||||
|
|
@ -111,7 +111,7 @@ public class ModMetaEditor(ModManager modManager)
|
|||
OtherGmpCount = 0;
|
||||
OtherEstCount = 0;
|
||||
OtherRspCount = 0;
|
||||
foreach (var option in mod.AllSubMods)
|
||||
foreach (var option in mod.AllDataContainers)
|
||||
{
|
||||
if (option == currentOption)
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -168,27 +169,11 @@ public class ModNormalizer(ModManager _modManager, Configuration _config)
|
|||
foreach (var (group, groupIdx) in Mod.Groups.WithIndex())
|
||||
{
|
||||
var groupDir = ModCreator.CreateModFolder(directory, group.Name, _config.ReplaceNonAsciiOnImport, true);
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup single:
|
||||
_redirections[groupIdx + 1].EnsureCapacity(single.OptionData.Count);
|
||||
for (var i = _redirections[groupIdx + 1].Count; i < single.OptionData.Count; ++i)
|
||||
_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;
|
||||
}
|
||||
_redirections[groupIdx + 1].EnsureCapacity(group.DataContainers.Count);
|
||||
for (var i = _redirections[groupIdx + 1].Count; i < group.DataContainers.Count; ++i)
|
||||
_redirections[groupIdx + 1].Add([]);
|
||||
foreach (var (data, dataIdx) in group.DataContainers.WithIndex())
|
||||
HandleSubMod(groupDir, data, _redirections[groupIdx + 1][dataIdx]);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -200,13 +185,14 @@ public class ModNormalizer(ModManager _modManager, Configuration _config)
|
|||
|
||||
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.EnsureCapacity(option.FileData.Count);
|
||||
foreach (var (gamePath, fullPath) in option.FileData)
|
||||
newDict.EnsureCapacity(option.Files.Count);
|
||||
foreach (var (gamePath, fullPath) in option.Files)
|
||||
{
|
||||
var relPath = new Utf8RelPath(gamePath).ToString();
|
||||
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]);
|
||||
break;
|
||||
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]);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public class ModSwapEditor(ModManager modManager)
|
|||
public IReadOnlyDictionary<Utf8GamePath, FullPath> Swaps
|
||||
=> _swaps;
|
||||
|
||||
public void Revert(SubMod option)
|
||||
public void Revert(IModDataContainer option)
|
||||
{
|
||||
_swaps.SetTo(option.FileSwaps);
|
||||
Changes = false;
|
||||
|
|
|
|||
|
|
@ -176,13 +176,13 @@ public class ModCacheManager : IDisposable
|
|||
}
|
||||
|
||||
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)
|
||||
=> mod.TotalSwapCount = mod.AllSubMods.Sum(s => s.FileSwaps.Count);
|
||||
=> mod.TotalSwapCount = mod.AllDataContainers.Sum(s => s.FileSwaps.Count);
|
||||
|
||||
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)
|
||||
=> mod.HasOptions = mod.Groups.Any(o => o.IsOption);
|
||||
|
|
@ -194,10 +194,10 @@ public class ModCacheManager : IDisposable
|
|||
{
|
||||
var changedItems = (SortedList<string, object?>)mod.ChangedItems;
|
||||
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());
|
||||
|
||||
foreach (var manip in mod.AllSubMods.SelectMany(m => m.Manipulations))
|
||||
foreach (var manip in mod.AllDataContainers.SelectMany(m => m.Manipulations))
|
||||
ComputeChangedItems(_identifier, changedItems, manip);
|
||||
|
||||
mod.LowerChangedItemsString = string.Join("\0", mod.ChangedItems.Keys.Select(k => k.ToLowerInvariant()));
|
||||
|
|
@ -211,20 +211,11 @@ public class ModCacheManager : IDisposable
|
|||
mod.HasOptions = false;
|
||||
foreach (var group in mod.Groups)
|
||||
{
|
||||
mod.HasOptions |= group.IsOption;
|
||||
var optionEnumerator = group switch
|
||||
{
|
||||
SingleModGroup single => single.OptionData,
|
||||
MultiModGroup multi => multi.PrioritizedOptions.Select(o => o.Mod),
|
||||
_ => [],
|
||||
};
|
||||
|
||||
foreach (var s in optionEnumerator)
|
||||
{
|
||||
mod.TotalFileCount += s.Files.Count;
|
||||
mod.TotalSwapCount += s.FileSwaps.Count;
|
||||
mod.TotalManipulations += s.Manipulations.Count;
|
||||
}
|
||||
mod.HasOptions |= group.IsOption;
|
||||
var (files, swaps, manips) = group.GetCounts();
|
||||
mod.TotalFileCount += files;
|
||||
mod.TotalSwapCount += swaps;
|
||||
mod.TotalManipulations += manips;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,14 +71,14 @@ public static partial class ModMigration
|
|||
foreach (var unusedFile in mod.FindUnusedFiles().Where(f => !seenMetaFiles.Contains(f)))
|
||||
{
|
||||
if (unusedFile.ToGamePath(mod.ModPath, out var gamePath)
|
||||
&& !mod.Default.FileData.TryAdd(gamePath, unusedFile))
|
||||
Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod.Default.FileData[gamePath]}.");
|
||||
&& !mod.Default.Files.TryAdd(gamePath, unusedFile))
|
||||
Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod.Default.Files[gamePath]}.");
|
||||
}
|
||||
|
||||
mod.Default.FileSwapData.Clear();
|
||||
mod.Default.FileSwapData.EnsureCapacity(swaps.Count);
|
||||
mod.Default.FileSwaps.Clear();
|
||||
mod.Default.FileSwaps.EnsureCapacity(swaps.Count);
|
||||
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);
|
||||
foreach (var (_, index) in mod.Groups.WithIndex())
|
||||
|
|
@ -134,7 +134,7 @@ public static partial class ModMigration
|
|||
};
|
||||
mod.Groups.Add(newMultiGroup);
|
||||
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;
|
||||
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)
|
||||
{
|
||||
var fullPath = new FullPath(basePath, relPath);
|
||||
foreach (var gamePath in gamePaths)
|
||||
mod.FileData.TryAdd(gamePath, fullPath);
|
||||
mod.Files.TryAdd(gamePath, fullPath);
|
||||
|
||||
if (fullPath.Extension is ".meta" or ".rgsp")
|
||||
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);
|
||||
creator.IncorporateMetaChanges(subMod, mod.ModPath, false);
|
||||
return subMod;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Security.AccessControl;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -179,10 +178,10 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
switch (mod.Groups[groupIdx])
|
||||
{
|
||||
case MultiModGroup multi:
|
||||
if (multi.PrioritizedOptions[optionIdx].Priority == newPriority)
|
||||
if (multi.OptionData[optionIdx].Priority == newPriority)
|
||||
return;
|
||||
|
||||
multi.PrioritizedOptions[optionIdx] = (multi.PrioritizedOptions[optionIdx].Mod, newPriority);
|
||||
multi.OptionData[optionIdx].Priority = newPriority;
|
||||
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, optionIdx, -1);
|
||||
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>
|
||||
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];
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup single:
|
||||
{
|
||||
var idx = single.OptionData.IndexOf(o => o.Name == newName);
|
||||
if (idx >= 0)
|
||||
return (single.OptionData[idx], idx, false);
|
||||
var idx = group.Options.IndexOf(o => o.Name == newName);
|
||||
if (idx >= 0)
|
||||
return (group.Options[idx], idx, false);
|
||||
|
||||
idx = single.AddOption(mod, newName);
|
||||
if (idx < 0)
|
||||
throw new Exception($"Could not create new option with name {newName} in {group.Name}.");
|
||||
idx = group.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 (single.OptionData[^1], single.OptionData.Count - 1, 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()}.");
|
||||
saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, idx, -1);
|
||||
return (group.Options[idx], idx, true);
|
||||
}
|
||||
|
||||
/// <summary> Add an existing option to a given group with default priority. </summary>
|
||||
public void AddOption(Mod mod, int groupIdx, SubMod 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)
|
||||
/// <summary> Add an existing option to a given group. </summary>
|
||||
public void AddOption(Mod mod, int groupIdx, IModOption option)
|
||||
{
|
||||
var group = mod.Groups[groupIdx];
|
||||
int idx;
|
||||
switch (group)
|
||||
{
|
||||
case MultiModGroup { PrioritizedOptions.Count: >= IModGroup.MaxMultiOptions }:
|
||||
case MultiModGroup { OptionData.Count: >= IModGroup.MaxMultiOptions }:
|
||||
Penumbra.Log.Error(
|
||||
$"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.");
|
||||
return;
|
||||
case SingleModGroup s:
|
||||
{
|
||||
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;
|
||||
}
|
||||
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;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
default: return;
|
||||
}
|
||||
|
||||
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||
|
|
@ -295,7 +286,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
m.PrioritizedOptions.RemoveAt(optionIdx);
|
||||
m.OptionData.RemoveAt(optionIdx);
|
||||
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>
|
||||
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)
|
||||
{
|
||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||
var subMod = GetSubMod(mod, groupIdx, dataContainerIdx);
|
||||
if (subMod.Manipulations.Count == manipulations.Count
|
||||
&& subMod.Manipulations.All(m => manipulations.TryGetValue(m, out var old) && old.EntryEquals(m)))
|
||||
return;
|
||||
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
subMod.ManipulationData.SetTo(manipulations);
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, dataContainerIdx, -1);
|
||||
subMod.Manipulations.SetTo(manipulations);
|
||||
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>
|
||||
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)
|
||||
{
|
||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||
if (subMod.FileData.SetEquals(replacements))
|
||||
var subMod = GetSubMod(mod, groupIdx, dataContainerIdx);
|
||||
if (subMod.Files.SetEquals(replacements))
|
||||
return;
|
||||
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
subMod.FileData.SetTo(replacements);
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, dataContainerIdx, -1);
|
||||
subMod.Files.SetTo(replacements);
|
||||
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>
|
||||
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 oldCount = subMod.FileData.Count;
|
||||
subMod.FileData.AddFrom(additions);
|
||||
if (oldCount != subMod.FileData.Count)
|
||||
var subMod = GetSubMod(mod, groupIdx, dataContainerIdx);
|
||||
var oldCount = subMod.Files.Count;
|
||||
subMod.Files.AddFrom(additions);
|
||||
if (oldCount != subMod.Files.Count)
|
||||
{
|
||||
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>
|
||||
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)
|
||||
{
|
||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||
if (subMod.FileSwapData.SetEquals(swaps))
|
||||
var subMod = GetSubMod(mod, groupIdx, dataContainerIdx);
|
||||
if (subMod.FileSwaps.SetEquals(swaps))
|
||||
return;
|
||||
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
subMod.FileSwapData.SetTo(swaps);
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, dataContainerIdx, -1);
|
||||
subMod.FileSwaps.SetTo(swaps);
|
||||
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>
|
||||
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.Groups[groupIdx] switch
|
||||
{
|
||||
SingleModGroup s => s.OptionData[optionIdx],
|
||||
MultiModGroup m => m.PrioritizedOptions[optionIdx].Mod,
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
return mod.Groups[groupIdx].DataContainers[dataContainerIdx];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public sealed class Mod : IMod
|
|||
internal Mod(DirectoryInfo modPath)
|
||||
{
|
||||
ModPath = modPath;
|
||||
Default = SubMod.CreateDefault(this);
|
||||
Default = new DefaultSubMod(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
|
@ -61,8 +61,8 @@ public sealed class Mod : IMod
|
|||
|
||||
|
||||
// Options
|
||||
public readonly SubMod Default;
|
||||
public readonly List<IModGroup> Groups = [];
|
||||
public readonly DefaultSubMod Default;
|
||||
public readonly List<IModGroup> Groups = [];
|
||||
|
||||
public AppliedModData GetData(ModSettings? settings = null)
|
||||
{
|
||||
|
|
@ -77,21 +77,16 @@ public sealed class Mod : IMod
|
|||
group.AddData(config, dictRedirections, setManips);
|
||||
}
|
||||
|
||||
Default.AddData(dictRedirections, setManips);
|
||||
Default.AddDataTo(dictRedirections, setManips);
|
||||
return new AppliedModData(dictRedirections, setManips);
|
||||
}
|
||||
|
||||
public IEnumerable<SubMod> AllSubMods
|
||||
=> Groups.SelectMany(o => o switch
|
||||
{
|
||||
SingleModGroup single => single.OptionData,
|
||||
MultiModGroup multi => multi.PrioritizedOptions.Select(s => s.Mod),
|
||||
_ => [],
|
||||
}).Prepend(Default);
|
||||
public IEnumerable<IModDataContainer> AllDataContainers
|
||||
=> Groups.SelectMany(o => o.DataContainers).Prepend(Default);
|
||||
|
||||
public List<FullPath> FindUnusedFiles()
|
||||
{
|
||||
var modFiles = AllSubMods.SelectMany(o => o.Files)
|
||||
var modFiles = AllDataContainers.SelectMany(o => o.Files)
|
||||
.Select(p => p.Value)
|
||||
.ToHashSet();
|
||||
return ModPath.EnumerateDirectories()
|
||||
|
|
|
|||
|
|
@ -112,10 +112,8 @@ public partial class ModCreator(
|
|||
var defaultFile = _saveService.FileNames.OptionGroupFile(mod, -1, Config.ReplaceNonAsciiOnImport);
|
||||
try
|
||||
{
|
||||
if (!File.Exists(defaultFile))
|
||||
mod.Default.Load(mod.ModPath, new JObject(), out _);
|
||||
else
|
||||
mod.Default.Load(mod.ModPath, JObject.Parse(File.ReadAllText(defaultFile)), out _);
|
||||
var jObject = File.Exists(defaultFile) ? JObject.Parse(File.ReadAllText(defaultFile)) : new JObject();
|
||||
IModDataContainer.Load(jObject, mod.Default, mod.ModPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -154,7 +152,7 @@ public partial class ModCreator(
|
|||
{
|
||||
var changes = false;
|
||||
List<string> deleteList = new();
|
||||
foreach (var subMod in mod.AllSubMods)
|
||||
foreach (var subMod in mod.AllDataContainers)
|
||||
{
|
||||
var (localChanges, localDeleteList) = IncorporateMetaChanges(subMod, mod.ModPath, false);
|
||||
changes |= localChanges;
|
||||
|
|
@ -162,7 +160,7 @@ public partial class ModCreator(
|
|||
deleteList.AddRange(localDeleteList);
|
||||
}
|
||||
|
||||
SubMod.DeleteDeleteList(deleteList, delete);
|
||||
IModDataContainer.DeleteDeleteList(deleteList, delete);
|
||||
|
||||
if (!changes)
|
||||
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 delete is true, the files are deleted afterwards.
|
||||
/// </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 oldSize = option.ManipulationData.Count;
|
||||
var oldSize = option.Manipulations.Count;
|
||||
var deleteString = delete ? "with deletion." : "without deletion.";
|
||||
foreach (var (key, file) in option.Files.ToList())
|
||||
{
|
||||
|
|
@ -189,7 +187,7 @@ public partial class ModCreator(
|
|||
{
|
||||
if (ext1 == ".meta" || ext2 == ".meta")
|
||||
{
|
||||
option.FileData.Remove(key);
|
||||
option.Files.Remove(key);
|
||||
if (!file.Exists)
|
||||
continue;
|
||||
|
||||
|
|
@ -198,11 +196,11 @@ public partial class ModCreator(
|
|||
Penumbra.Log.Verbose(
|
||||
$"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}");
|
||||
deleteList.Add(file.FullName);
|
||||
option.ManipulationData.UnionWith(meta.MetaManipulations);
|
||||
option.Manipulations.UnionWith(meta.MetaManipulations);
|
||||
}
|
||||
else if (ext1 == ".rgsp" || ext2 == ".rgsp")
|
||||
{
|
||||
option.FileData.Remove(key);
|
||||
option.Files.Remove(key);
|
||||
if (!file.Exists)
|
||||
continue;
|
||||
|
||||
|
|
@ -212,7 +210,7 @@ public partial class ModCreator(
|
|||
$"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}");
|
||||
deleteList.Add(file.FullName);
|
||||
|
||||
option.ManipulationData.UnionWith(rgsp.MetaManipulations);
|
||||
option.Manipulations.UnionWith(rgsp.MetaManipulations);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -221,8 +219,8 @@ public partial class ModCreator(
|
|||
}
|
||||
}
|
||||
|
||||
SubMod.DeleteDeleteList(deleteList, delete);
|
||||
return (oldSize < option.ManipulationData.Count, deleteList);
|
||||
IModDataContainer.DeleteDeleteList(deleteList, delete);
|
||||
return (oldSize < option.Manipulations.Count, deleteList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -238,7 +236,7 @@ public partial class ModCreator(
|
|||
|
||||
/// <summary> Create a file for an option group from given data. </summary>
|
||||
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)
|
||||
{
|
||||
|
|
@ -248,7 +246,7 @@ public partial class ModCreator(
|
|||
group.Description = desc;
|
||||
group.Priority = priority;
|
||||
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));
|
||||
break;
|
||||
}
|
||||
|
|
@ -258,7 +256,7 @@ public partial class ModCreator(
|
|||
group.Description = desc;
|
||||
group.Priority = priority;
|
||||
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));
|
||||
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>
|
||||
public SubMod CreateSubMod(DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option)
|
||||
public MultiSubMod CreateSubMod(DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option, ModPriority priority)
|
||||
{
|
||||
var list = optionFolder.EnumerateNonHiddenFiles()
|
||||
.Select(f => (Utf8GamePath.FromFile(f, optionFolder, out var gamePath, true), gamePath, new FullPath(f)))
|
||||
.Where(t => t.Item1);
|
||||
|
||||
var mod = SubMod.CreateForSaving(option.Name);
|
||||
mod.Description = option.Description;
|
||||
var mod = MultiSubMod.CreateForSaving(option.Name, option.Description, priority);
|
||||
foreach (var (_, gamePath, file) in list)
|
||||
mod.FileData.TryAdd(gamePath, file);
|
||||
mod.Files.TryAdd(gamePath, file);
|
||||
|
||||
IncorporateMetaChanges(mod, baseFolder, true);
|
||||
return mod;
|
||||
|
|
@ -292,7 +289,7 @@ public partial class ModCreator(
|
|||
foreach (var file in mod.FindUnusedFiles())
|
||||
{
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Mods.Subclasses;
|
||||
|
||||
public interface IModDataContainer
|
||||
{
|
||||
public IMod Mod { get; }
|
||||
public IModGroup? Group { get; }
|
||||
|
||||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; }
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; }
|
||||
public HashSet<MetaManipulation> Manipulations { get; set; }
|
||||
|
|
@ -21,6 +25,32 @@ public interface IModDataContainer
|
|||
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)
|
||||
{
|
||||
data.Files.Clear();
|
||||
|
|
@ -77,4 +107,22 @@ public interface IModDataContainer
|
|||
serializer.Serialize(j, data.Manipulations);
|
||||
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 Penumbra.Api.Enums;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
|
@ -6,6 +7,11 @@ using Penumbra.String.Classes;
|
|||
|
||||
namespace Penumbra.Mods.Subclasses;
|
||||
|
||||
public interface ITexToolsGroup
|
||||
{
|
||||
public IReadOnlyList<IModDataOption> OptionData { get; }
|
||||
}
|
||||
|
||||
public interface IModGroup
|
||||
{
|
||||
public const int MaxMultiOptions = 63;
|
||||
|
|
@ -19,28 +25,89 @@ public interface IModGroup
|
|||
|
||||
public FullPath? FindBestMatch(Utf8GamePath gamePath);
|
||||
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 bool IsOption { get; }
|
||||
public IReadOnlyList<IModOption> Options { get; }
|
||||
public IReadOnlyList<IModDataContainer> DataContainers { get; }
|
||||
public bool IsOption { get; }
|
||||
|
||||
public IModGroup Convert(GroupType type);
|
||||
public bool MoveOption(int optionIdxFrom, int optionIdxTo);
|
||||
|
||||
public int GetIndex();
|
||||
|
||||
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations);
|
||||
|
||||
/// <summary> Ensure that a value is valid for a group. </summary>
|
||||
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
|
||||
{
|
||||
private readonly DirectoryInfo _basePath;
|
||||
private readonly IModGroup? _group;
|
||||
private readonly int _groupIdx;
|
||||
private readonly SubMod? _defaultMod;
|
||||
private readonly bool _onlyAscii;
|
||||
private readonly DirectoryInfo _basePath;
|
||||
private readonly IModGroup? _group;
|
||||
private readonly int _groupIdx;
|
||||
private readonly DefaultSubMod? _defaultMod;
|
||||
private readonly bool _onlyAscii;
|
||||
|
||||
public ModSaveGroup(Mod mod, int groupIdx, bool onlyAscii)
|
||||
{
|
||||
|
|
@ -61,7 +128,7 @@ public readonly struct ModSaveGroup : ISavable
|
|||
_onlyAscii = onlyAscii;
|
||||
}
|
||||
|
||||
public ModSaveGroup(DirectoryInfo basePath, SubMod @default, bool onlyAscii)
|
||||
public ModSaveGroup(DirectoryInfo basePath, DefaultSubMod @default, bool onlyAscii)
|
||||
{
|
||||
_basePath = basePath;
|
||||
_groupIdx = -1;
|
||||
|
|
@ -77,42 +144,11 @@ public readonly struct ModSaveGroup : ISavable
|
|||
using var j = new JsonTextWriter(writer);
|
||||
j.Formatting = Formatting.Indented;
|
||||
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
||||
j.WriteStartObject();
|
||||
if (_groupIdx >= 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
_group!.WriteJson(j, serializer);
|
||||
else
|
||||
{
|
||||
SubMod.WriteSubMod(j, serializer, _defaultMod!, _basePath, null);
|
||||
}
|
||||
IModDataContainer.WriteModData(j, serializer, _defaultMod!, _basePath);
|
||||
j.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ public interface IModOption
|
|||
option.Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty;
|
||||
}
|
||||
|
||||
public (int GroupIndex, int OptionIndex) GetOptionIndices();
|
||||
|
||||
public static void WriteModOption(JsonWriter j, IModOption option)
|
||||
{
|
||||
j.WritePropertyName(nameof(Name));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -10,20 +11,30 @@ using Penumbra.String.Classes;
|
|||
namespace Penumbra.Mods.Subclasses;
|
||||
|
||||
/// <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
|
||||
=> GroupType.Multi;
|
||||
|
||||
public Mod Mod { get; set; } = mod;
|
||||
public string Name { get; set; } = "Group";
|
||||
public string Description { get; set; } = "A non-exclusive group of settings.";
|
||||
public ModPriority Priority { get; set; }
|
||||
public Setting DefaultSettings { get; set; }
|
||||
public Mod Mod { get; set; } = mod;
|
||||
public string Name { get; set; } = "Group";
|
||||
public string Description { get; set; } = "A non-exclusive group of settings.";
|
||||
public ModPriority Priority { 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)
|
||||
=> PrioritizedOptions.OrderByDescending(o => o.Priority)
|
||||
.SelectWhere(o => (o.Mod.FileData.TryGetValue(gamePath, out var file) || o.Mod.FileSwapData.TryGetValue(gamePath, out file), file))
|
||||
=> OptionData.OrderByDescending(o => o.Priority)
|
||||
.SelectWhere(o => (o.Files.TryGetValue(gamePath, out var file) || o.FileSwaps.TryGetValue(gamePath, out file), file))
|
||||
.FirstOrDefault();
|
||||
|
||||
public int AddOption(Mod mod, string name, string description = "")
|
||||
|
|
@ -32,49 +43,15 @@ public sealed class MultiModGroup(Mod mod) : IModGroup
|
|||
if (groupIdx < 0)
|
||||
return -1;
|
||||
|
||||
var subMod = new SubMod(mod, this)
|
||||
var subMod = new MultiSubMod(mod, this)
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
};
|
||||
PrioritizedOptions.Add((subMod, ModPriority.Default));
|
||||
return PrioritizedOptions.Count - 1;
|
||||
OptionData.Add(subMod);
|
||||
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)
|
||||
{
|
||||
var ret = new MultiModGroup(mod)
|
||||
|
|
@ -91,7 +68,7 @@ public sealed class MultiModGroup(Mod mod) : IModGroup
|
|||
if (options != null)
|
||||
foreach (var child in options.Children())
|
||||
{
|
||||
if (ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions)
|
||||
if (ret.OptionData.Count == IModGroup.MaxMultiOptions)
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"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;
|
||||
}
|
||||
|
||||
var subMod = new SubMod(mod, ret);
|
||||
subMod.Load(mod.ModPath, child, out var priority);
|
||||
ret.PrioritizedOptions.Add((subMod, priority));
|
||||
var subMod = new MultiSubMod(mod, ret, child);
|
||||
ret.OptionData.Add(subMod);
|
||||
}
|
||||
|
||||
ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings);
|
||||
|
|
@ -115,39 +91,68 @@ public sealed class MultiModGroup(Mod mod) : IModGroup
|
|||
{
|
||||
case GroupType.Multi: return this;
|
||||
case GroupType.Single:
|
||||
var multi = new SingleModGroup(Mod)
|
||||
var single = new SingleModGroup(Mod)
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
Priority = Priority,
|
||||
DefaultSettings = DefaultSettings.TurnMulti(PrioritizedOptions.Count),
|
||||
DefaultSettings = DefaultSettings.TurnMulti(OptionData.Count),
|
||||
};
|
||||
multi.OptionData.AddRange(PrioritizedOptions.Select(p => p.Mod));
|
||||
return multi;
|
||||
single.OptionData.AddRange(OptionData.Select(o => o.ConvertToSingle(Mod, single)));
|
||||
return single;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveOption(int optionIdxFrom, int optionIdxTo)
|
||||
{
|
||||
if (!PrioritizedOptions.Move(optionIdxFrom, optionIdxTo))
|
||||
if (!OptionData.Move(optionIdxFrom, optionIdxTo))
|
||||
return false;
|
||||
|
||||
DefaultSettings = DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
|
||||
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)
|
||||
{
|
||||
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))
|
||||
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)
|
||||
=> 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>
|
||||
internal static MultiModGroup CreateForSaving(string name)
|
||||
|
|
@ -155,4 +160,7 @@ public sealed class MultiModGroup(Mod mod) : IModGroup
|
|||
{
|
||||
Name = name,
|
||||
};
|
||||
|
||||
IReadOnlyList<IModDataOption> ITexToolsGroup.OptionData
|
||||
=> OptionData;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Filesystem;
|
||||
|
|
@ -8,7 +9,7 @@ using Penumbra.String.Classes;
|
|||
namespace Penumbra.Mods.Subclasses;
|
||||
|
||||
/// <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
|
||||
=> GroupType.Single;
|
||||
|
|
@ -19,16 +20,19 @@ public sealed class SingleModGroup(Mod mod) : IModGroup
|
|||
public ModPriority Priority { 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)
|
||||
=> 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();
|
||||
|
||||
public int AddOption(Mod mod, string name, string description = "")
|
||||
{
|
||||
var subMod = new SubMod(mod, this)
|
||||
var subMod = new SingleSubMod(mod, this)
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
|
|
@ -37,35 +41,12 @@ public sealed class SingleModGroup(Mod mod) : IModGroup
|
|||
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
|
||||
=> OptionData;
|
||||
|
||||
public IReadOnlyList<IModDataContainer> DataContainers
|
||||
=> OptionData;
|
||||
|
||||
public bool IsOption
|
||||
=> OptionData.Count > 1;
|
||||
|
||||
|
|
@ -85,8 +66,7 @@ public sealed class SingleModGroup(Mod mod) : IModGroup
|
|||
if (options != null)
|
||||
foreach (var child in options.Children())
|
||||
{
|
||||
var subMod = new SubMod(mod, ret);
|
||||
subMod.Load(mod.ModPath, child, out _);
|
||||
var subMod = new SingleSubMod(mod, ret, child);
|
||||
ret.OptionData.Add(subMod);
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +87,7 @@ public sealed class SingleModGroup(Mod mod) : IModGroup
|
|||
Priority = Priority,
|
||||
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;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
|
|
@ -137,12 +117,39 @@ public sealed class SingleModGroup(Mod mod) : IModGroup
|
|||
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)
|
||||
=> OptionData[setting.AsIndex].AddData(redirections, manipulations);
|
||||
=> OptionData[setting.AsIndex].AddDataTo(redirections, manipulations);
|
||||
|
||||
public Setting FixSetting(Setting setting)
|
||||
=> 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>
|
||||
internal static SingleModGroup CreateForSaving(string name)
|
||||
=> new(null!)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ using Penumbra.String.Classes;
|
|||
|
||||
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 SingleModGroup Group = group;
|
||||
|
|
@ -19,12 +21,68 @@ public class SingleSubMod(Mod mod, SingleModGroup group) : IModOption, IModDataC
|
|||
|
||||
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> FileSwaps { 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 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> FileSwaps { 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 string FullName
|
||||
=> "Default Option";
|
||||
public const string FullName = "Default Option";
|
||||
|
||||
public string Description
|
||||
=> string.Empty;
|
||||
|
|
@ -55,183 +177,176 @@ public class DefaultSubMod(IMod mod) : IModDataContainer
|
|||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { 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
|
||||
=> Group == null ? "Default Option" : $"{Group.Name}: {Name}";
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
internal readonly IMod Mod = mod;
|
||||
internal readonly IModGroup? Group = group;
|
||||
|
||||
internal (int GroupIdx, int OptionIdx) GetIndices()
|
||||
{
|
||||
if (IsDefault)
|
||||
return (-1, 0);
|
||||
|
||||
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}.");
|
||||
|
||||
return (groupIdx, GetOptionIndex());
|
||||
}
|
||||
|
||||
private int GetOptionIndex()
|
||||
{
|
||||
var optionIndex = Group switch
|
||||
{
|
||||
null => 0,
|
||||
SingleModGroup single => single.OptionData.IndexOf(this),
|
||||
MultiModGroup multi => multi.PrioritizedOptions.IndexOf(p => p.Mod == this),
|
||||
_ => throw new Exception($"Group {Group.Name} from SubMod {Name} has unknown type {typeof(Group)}"),
|
||||
};
|
||||
if (optionIndex < 0)
|
||||
throw new Exception($"Group {Group!.Name} from SubMod {Name} does not contain this SubMod.");
|
||||
|
||||
return optionIndex;
|
||||
}
|
||||
|
||||
public static SubMod CreateDefault(IMod mod)
|
||||
=> new(mod, null!);
|
||||
|
||||
[MemberNotNullWhen(false, nameof(Group))]
|
||||
public bool IsDefault
|
||||
=> Group == null;
|
||||
|
||||
public void AddData(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||
{
|
||||
foreach (var (path, file) in Files)
|
||||
redirections.TryAdd(path, file);
|
||||
|
||||
foreach (var (path, file) in FileSwaps)
|
||||
redirections.TryAdd(path, file);
|
||||
manipulations.UnionWith(Manipulations);
|
||||
}
|
||||
|
||||
public Dictionary<Utf8GamePath, FullPath> FileData = [];
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwapData = [];
|
||||
public HashSet<MetaManipulation> ManipulationData = [];
|
||||
|
||||
public IReadOnlyDictionary<Utf8GamePath, FullPath> Files
|
||||
=> FileData;
|
||||
|
||||
public IReadOnlyDictionary<Utf8GamePath, FullPath> FileSwaps
|
||||
=> FileSwapData;
|
||||
|
||||
public IReadOnlySet<MetaManipulation> Manipulations
|
||||
=> ManipulationData;
|
||||
|
||||
public void Load(DirectoryInfo basePath, JToken json, out ModPriority priority)
|
||||
{
|
||||
FileData.Clear();
|
||||
FileSwapData.Clear();
|
||||
ManipulationData.Clear();
|
||||
|
||||
// Every option has a name, but priorities are only relevant for multi group options.
|
||||
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty;
|
||||
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty;
|
||||
priority = json[nameof(IModGroup.Priority)]?.ToObject<ModPriority>() ?? ModPriority.Default;
|
||||
|
||||
var files = (JObject?)json[nameof(Files)];
|
||||
if (files != null)
|
||||
foreach (var property in files.Properties())
|
||||
{
|
||||
if (Utf8GamePath.FromString(property.Name, out var p, true))
|
||||
FileData.TryAdd(p, new FullPath(basePath, property.Value.ToObject<Utf8RelPath>()));
|
||||
}
|
||||
|
||||
var swaps = (JObject?)json[nameof(FileSwaps)];
|
||||
if (swaps != null)
|
||||
foreach (var property in swaps.Properties())
|
||||
{
|
||||
if (Utf8GamePath.FromString(property.Name, out var p, true))
|
||||
FileSwapData.TryAdd(p, new FullPath(property.Value.ToObject<string>()!));
|
||||
}
|
||||
|
||||
var manips = json[nameof(Manipulations)];
|
||||
if (manips != null)
|
||||
foreach (var s in manips.Children().Select(c => c.ToObject<MetaManipulation>())
|
||||
.Where(m => m.Validate()))
|
||||
ManipulationData.Add(s);
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Create a sub mod without a mod or group only for saving it in the creator. </summary>
|
||||
internal static SubMod CreateForSaving(string name)
|
||||
=> new(null!, null!)
|
||||
{
|
||||
Name = name,
|
||||
};
|
||||
|
||||
|
||||
public static void WriteSubMod(JsonWriter j, JsonSerializer serializer, SubMod mod, DirectoryInfo basePath, ModPriority? priority)
|
||||
{
|
||||
j.WriteStartObject();
|
||||
j.WritePropertyName(nameof(Name));
|
||||
j.WriteValue(mod.Name);
|
||||
j.WritePropertyName(nameof(Description));
|
||||
j.WriteValue(mod.Description);
|
||||
if (priority != null)
|
||||
{
|
||||
j.WritePropertyName(nameof(IModGroup.Priority));
|
||||
j.WriteValue(priority.Value.Value);
|
||||
}
|
||||
|
||||
j.WritePropertyName(nameof(mod.Files));
|
||||
j.WriteStartObject();
|
||||
foreach (var (gamePath, file) in mod.Files)
|
||||
{
|
||||
if (file.ToRelPath(basePath, out var relPath))
|
||||
{
|
||||
j.WritePropertyName(gamePath.ToString());
|
||||
j.WriteValue(relPath.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
j.WriteEndObject();
|
||||
j.WritePropertyName(nameof(mod.FileSwaps));
|
||||
j.WriteStartObject();
|
||||
foreach (var (gamePath, file) in mod.FileSwaps)
|
||||
{
|
||||
j.WritePropertyName(gamePath.ToString());
|
||||
j.WriteValue(file.ToString());
|
||||
}
|
||||
|
||||
j.WriteEndObject();
|
||||
j.WritePropertyName(nameof(mod.Manipulations));
|
||||
serializer.Serialize(j, mod.Manipulations);
|
||||
j.WriteEndObject();
|
||||
}
|
||||
}
|
||||
//public sealed class SubMod(IMod mod, IModGroup group) : IModOption
|
||||
//{
|
||||
// public string Name { get; set; } = "Default";
|
||||
//
|
||||
// public string FullName
|
||||
// => Group == null ? "Default Option" : $"{Group.Name}: {Name}";
|
||||
//
|
||||
// public string Description { get; set; } = string.Empty;
|
||||
//
|
||||
// internal readonly IMod Mod = mod;
|
||||
// internal readonly IModGroup? Group = group;
|
||||
//
|
||||
// internal (int GroupIdx, int OptionIdx) GetIndices()
|
||||
// {
|
||||
// if (IsDefault)
|
||||
// return (-1, 0);
|
||||
//
|
||||
// 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}.");
|
||||
//
|
||||
// return (groupIdx, GetOptionIndex());
|
||||
// }
|
||||
//
|
||||
// private int GetOptionIndex()
|
||||
// {
|
||||
// var optionIndex = Group switch
|
||||
// {
|
||||
// null => 0,
|
||||
// SingleModGroup single => single.OptionData.IndexOf(this),
|
||||
// MultiModGroup multi => multi.OptionData.IndexOf(p => p.Mod == this),
|
||||
// _ => throw new Exception($"Group {Group.Name} from SubMod {Name} has unknown type {typeof(Group)}"),
|
||||
// };
|
||||
// if (optionIndex < 0)
|
||||
// throw new Exception($"Group {Group!.Name} from SubMod {Name} does not contain this SubMod.");
|
||||
//
|
||||
// return optionIndex;
|
||||
// }
|
||||
//
|
||||
// public static SubMod CreateDefault(IMod mod)
|
||||
// => new(mod, null!);
|
||||
//
|
||||
// [MemberNotNullWhen(false, nameof(Group))]
|
||||
// public bool IsDefault
|
||||
// => Group == null;
|
||||
//
|
||||
// public void AddData(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||
// {
|
||||
// foreach (var (path, file) in Files)
|
||||
// redirections.TryAdd(path, file);
|
||||
//
|
||||
// foreach (var (path, file) in FileSwaps)
|
||||
// redirections.TryAdd(path, file);
|
||||
// manipulations.UnionWith(Manipulations);
|
||||
// }
|
||||
//
|
||||
// public Dictionary<Utf8GamePath, FullPath> FileData = [];
|
||||
// public Dictionary<Utf8GamePath, FullPath> FileSwapData = [];
|
||||
// public HashSet<MetaManipulation> ManipulationData = [];
|
||||
//
|
||||
// public IReadOnlyDictionary<Utf8GamePath, FullPath> Files
|
||||
// => FileData;
|
||||
//
|
||||
// public IReadOnlyDictionary<Utf8GamePath, FullPath> FileSwaps
|
||||
// => FileSwapData;
|
||||
//
|
||||
// public IReadOnlySet<MetaManipulation> Manipulations
|
||||
// => ManipulationData;
|
||||
//
|
||||
// public void Load(DirectoryInfo basePath, JToken json, out ModPriority priority)
|
||||
// {
|
||||
// FileData.Clear();
|
||||
// FileSwapData.Clear();
|
||||
// ManipulationData.Clear();
|
||||
//
|
||||
// // Every option has a name, but priorities are only relevant for multi group options.
|
||||
// Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty;
|
||||
// Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty;
|
||||
// priority = json[nameof(IModGroup.Priority)]?.ToObject<ModPriority>() ?? ModPriority.Default;
|
||||
//
|
||||
// var files = (JObject?)json[nameof(Files)];
|
||||
// if (files != null)
|
||||
// foreach (var property in files.Properties())
|
||||
// {
|
||||
// if (Utf8GamePath.FromString(property.Name, out var p, true))
|
||||
// FileData.TryAdd(p, new FullPath(basePath, property.Value.ToObject<Utf8RelPath>()));
|
||||
// }
|
||||
//
|
||||
// var swaps = (JObject?)json[nameof(FileSwaps)];
|
||||
// if (swaps != null)
|
||||
// foreach (var property in swaps.Properties())
|
||||
// {
|
||||
// if (Utf8GamePath.FromString(property.Name, out var p, true))
|
||||
// FileSwapData.TryAdd(p, new FullPath(property.Value.ToObject<string>()!));
|
||||
// }
|
||||
//
|
||||
// var manips = json[nameof(Manipulations)];
|
||||
// if (manips != null)
|
||||
// foreach (var s in manips.Children().Select(c => c.ToObject<MetaManipulation>())
|
||||
// .Where(m => m.Validate()))
|
||||
// ManipulationData.Add(s);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /// <summary> Create a sub mod without a mod or group only for saving it in the creator. </summary>
|
||||
// internal static SubMod CreateForSaving(string name)
|
||||
// => new(null!, null!)
|
||||
// {
|
||||
// Name = name,
|
||||
// };
|
||||
//
|
||||
//
|
||||
// public static void WriteSubMod(JsonWriter j, JsonSerializer serializer, SubMod mod, DirectoryInfo basePath, ModPriority? priority)
|
||||
// {
|
||||
// j.WriteStartObject();
|
||||
// j.WritePropertyName(nameof(Name));
|
||||
// j.WriteValue(mod.Name);
|
||||
// j.WritePropertyName(nameof(Description));
|
||||
// j.WriteValue(mod.Description);
|
||||
// if (priority != null)
|
||||
// {
|
||||
// j.WritePropertyName(nameof(IModGroup.Priority));
|
||||
// j.WriteValue(priority.Value.Value);
|
||||
// }
|
||||
//
|
||||
// j.WritePropertyName(nameof(mod.Files));
|
||||
// j.WriteStartObject();
|
||||
// foreach (var (gamePath, file) in mod.Files)
|
||||
// {
|
||||
// if (file.ToRelPath(basePath, out var relPath))
|
||||
// {
|
||||
// j.WritePropertyName(gamePath.ToString());
|
||||
// j.WriteValue(relPath.ToString());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// j.WriteEndObject();
|
||||
// j.WritePropertyName(nameof(mod.FileSwaps));
|
||||
// j.WriteStartObject();
|
||||
// foreach (var (gamePath, file) in mod.FileSwaps)
|
||||
// {
|
||||
// j.WritePropertyName(gamePath.ToString());
|
||||
// j.WriteValue(file.ToString());
|
||||
// }
|
||||
//
|
||||
// j.WriteEndObject();
|
||||
// j.WritePropertyName(nameof(mod.Manipulations));
|
||||
// serializer.Serialize(j, mod.Manipulations);
|
||||
// j.WriteEndObject();
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -18,49 +18,46 @@ public class TemporaryMod : IMod
|
|||
public int TotalManipulations
|
||||
=> Default.Manipulations.Count;
|
||||
|
||||
public readonly SubMod Default;
|
||||
public readonly DefaultSubMod Default;
|
||||
|
||||
public AppliedModData GetData(ModSettings? settings = null)
|
||||
{
|
||||
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
|
||||
{
|
||||
// Need to ensure uniqueness.
|
||||
dict = new Dictionary<Utf8GamePath, FullPath>(Default.FileData.Count + Default.FileSwaps.Count);
|
||||
foreach (var (gamePath, file) in Default.FileData.Concat(Default.FileSwaps))
|
||||
dict = new Dictionary<Utf8GamePath, FullPath>(Default.Files.Count + Default.FileSwaps.Count);
|
||||
foreach (var (gamePath, file) in Default.Files.Concat(Default.FileSwaps))
|
||||
dict.TryAdd(gamePath, file);
|
||||
}
|
||||
|
||||
return new AppliedModData(dict, Default.ManipulationData);
|
||||
return new AppliedModData(dict, Default.Manipulations);
|
||||
}
|
||||
|
||||
public IReadOnlyList<IModGroup> Groups
|
||||
=> Array.Empty<IModGroup>();
|
||||
|
||||
public IEnumerable<SubMod> AllSubMods
|
||||
=> [Default];
|
||||
|
||||
public TemporaryMod()
|
||||
=> Default = SubMod.CreateDefault(this);
|
||||
=> Default = new(this);
|
||||
|
||||
public void SetFile(Utf8GamePath gamePath, FullPath fullPath)
|
||||
=> Default.FileData[gamePath] = fullPath;
|
||||
=> Default.Files[gamePath] = fullPath;
|
||||
|
||||
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)
|
||||
{
|
||||
Default.FileData = dict;
|
||||
Default.ManipulationData = manips;
|
||||
Default.Files = dict;
|
||||
Default.Manipulations = manips;
|
||||
}
|
||||
|
||||
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));
|
||||
File.Copy(targetPath, target, true);
|
||||
defaultMod.FileData[gamePath] = new FullPath(target);
|
||||
defaultMod.Files[gamePath] = new FullPath(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultMod.FileSwapData[gamePath] = new FullPath(targetPath);
|
||||
defaultMod.FileSwaps[gamePath] = new FullPath(targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
modManager.AddMod(dir);
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ public class FileEditor<T>(
|
|||
UiHelpers.Text(gamePath.Path);
|
||||
ImGui.TableNextColumn();
|
||||
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.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.Mods.Subclasses;
|
||||
using Penumbra.String.Classes;
|
||||
|
|
@ -79,7 +78,7 @@ public partial class ModEditWindow
|
|||
var file = f.RelPath.ToString();
|
||||
return f.SubModUsage.Count == 0
|
||||
? 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));
|
||||
});
|
||||
|
||||
|
|
@ -148,13 +147,13 @@ public partial class ModEditWindow
|
|||
(string, int) GetMulti()
|
||||
{
|
||||
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
|
||||
{
|
||||
ColorId.ConflictingMod => (string.Empty, 0),
|
||||
ColorId.NewMod => (registry.SubModUsage[0].Item1.Name, 1),
|
||||
ColorId.NewMod => (registry.SubModUsage[0].Item1.GetName(), 1),
|
||||
ColorId.InheritedMod => GetMulti(),
|
||||
_ => (string.Empty, 0),
|
||||
};
|
||||
|
|
@ -192,7 +191,7 @@ public partial class ModEditWindow
|
|||
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);
|
||||
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 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.";
|
||||
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)
|
||||
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.";
|
||||
ImGui.NewLine();
|
||||
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();
|
||||
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?
|
||||
// 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))
|
||||
.Where(kv => kv.Value.FullName.Equals(path, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(kv => kv.Key)
|
||||
|
|
@ -103,7 +103,7 @@ public partial class ModEditWindow
|
|||
return [];
|
||||
|
||||
// Filter then prepend the current option to ensure it's chosen first.
|
||||
return mod.AllSubMods
|
||||
return mod.AllDataContainers
|
||||
.Where(subMod => subMod != option)
|
||||
.Prepend(option)
|
||||
.SelectMany(subMod => subMod.Manipulations)
|
||||
|
|
|
|||
|
|
@ -187,8 +187,8 @@ public partial class ModEditWindow
|
|||
if (editor == null)
|
||||
return new QuickImportAction(owner._editor, FallbackOptionName, gamePath);
|
||||
|
||||
var subMod = editor.Option;
|
||||
var optionName = subMod!.FullName;
|
||||
var subMod = editor.Option!;
|
||||
var optionName = subMod is IModOption o ? o.FullName : FallbackOptionName;
|
||||
if (gamePath.IsEmpty || file == null || editor.FileEditor.Changes)
|
||||
return new QuickImportAction(editor, optionName, gamePath);
|
||||
|
||||
|
|
@ -199,7 +199,7 @@ public partial class ModEditWindow
|
|||
if (mod == null)
|
||||
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;
|
||||
if (File.Exists(targetPath))
|
||||
return new QuickImportAction(editor, optionName, gamePath);
|
||||
|
|
@ -222,16 +222,16 @@ public partial class ModEditWindow
|
|||
{
|
||||
fileRegistry,
|
||||
}, _subDirs);
|
||||
_editor.FileEditor.Apply(_editor.Mod!, (SubMod)_editor.Option!);
|
||||
_editor.FileEditor.Apply(_editor.Mod!, _editor.Option!);
|
||||
|
||||
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 subDirs = 0;
|
||||
if (subMod == mod.Default)
|
||||
if (subMod == null)
|
||||
return (path, subDirs);
|
||||
|
||||
var name = subMod.Name;
|
||||
|
|
|
|||
|
|
@ -77,10 +77,10 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
_forceTextureStartPath = true;
|
||||
}
|
||||
|
||||
public void ChangeOption(SubMod? subMod)
|
||||
public void ChangeOption(IModDataContainer? subMod)
|
||||
{
|
||||
var (groupIdx, optionIdx) = subMod?.GetIndices() ?? (-1, 0);
|
||||
_editor.LoadOption(groupIdx, optionIdx);
|
||||
var (groupIdx, dataIdx) = subMod?.GetDataIndices() ?? (-1, 0);
|
||||
_editor.LoadOption(groupIdx, dataIdx);
|
||||
}
|
||||
|
||||
public void UpdateModels()
|
||||
|
|
@ -111,7 +111,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
});
|
||||
var manipulations = 0;
|
||||
var subMods = 0;
|
||||
var swaps = Mod!.AllSubMods.Sum(m =>
|
||||
var swaps = Mod!.AllDataContainers.Sum(m =>
|
||||
{
|
||||
++subMods;
|
||||
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))
|
||||
{
|
||||
_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)
|
||||
|
|
@ -405,7 +405,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
var width = new Vector2(ImGui.GetContentRegionAvail().X / 3, 0);
|
||||
var ret = false;
|
||||
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);
|
||||
ret = true;
|
||||
|
|
@ -414,7 +414,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
ImGui.SameLine();
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -422,17 +422,17 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
ImGui.SetNextItemWidth(width.X);
|
||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
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)
|
||||
return ret;
|
||||
|
||||
foreach (var (option, idx) in Mod!.AllSubMods.WithIndex())
|
||||
foreach (var (option, idx) in Mod!.AllDataContainers.WithIndex())
|
||||
{
|
||||
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();
|
||||
_editor.LoadOption(groupIdx, optionIdx);
|
||||
var (groupIdx, dataIdx) = option.GetDataIndices();
|
||||
_editor.LoadOption(groupIdx, dataIdx);
|
||||
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.";
|
||||
ImGui.NewLine();
|
||||
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();
|
||||
tt = setsEqual ? "No changes staged." : "Revert all currently staged changes.";
|
||||
|
|
@ -569,7 +569,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
}
|
||||
|
||||
if (Mod != null)
|
||||
foreach (var option in Mod.AllSubMods)
|
||||
foreach (var option in Mod.AllDataContainers)
|
||||
{
|
||||
foreach (var path in option.Files.Keys)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -50,8 +50,7 @@ public class ModMergeTab(ModMerger modMerger)
|
|||
ImGui.SameLine();
|
||||
DrawCombo(size - ImGui.GetItemRectSize().X - ImGui.GetStyle().ItemSpacing.X);
|
||||
|
||||
var width = ImGui.GetItemRectSize();
|
||||
using (var g = ImRaii.Group())
|
||||
using (ImRaii.Group())
|
||||
{
|
||||
using var disabled = ImRaii.Disabled(modMerger.MergeFromMod.HasOptions);
|
||||
var buttonWidth = (size - ImGui.GetStyle().ItemSpacing.X) / 2;
|
||||
|
|
@ -124,13 +123,13 @@ public class ModMergeTab(ModMerger modMerger)
|
|||
ImGui.Dummy(Vector2.One);
|
||||
var buttonSize = new Vector2((size - 2 * ImGui.GetStyle().ItemSpacing.X) / 3, 0);
|
||||
if (ImGui.Button("Select All", buttonSize))
|
||||
modMerger.SelectedOptions.UnionWith(modMerger.MergeFromMod!.AllSubMods);
|
||||
modMerger.SelectedOptions.UnionWith(modMerger.MergeFromMod!.AllDataContainers);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Unselect All", buttonSize))
|
||||
modMerger.SelectedOptions.Clear();
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Invert Selection", buttonSize))
|
||||
modMerger.SelectedOptions.SymmetricExceptWith(modMerger.MergeFromMod!.AllSubMods);
|
||||
modMerger.SelectedOptions.SymmetricExceptWith(modMerger.MergeFromMod!.AllDataContainers);
|
||||
DrawOptionTable(size);
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +143,7 @@ public class ModMergeTab(ModMerger modMerger)
|
|||
|
||||
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
|
||||
? ImGui.GetContentRegionAvail().Y - 3 * ImGui.GetFrameHeightWithSpacing()
|
||||
: 8 * ImGui.GetFrameHeightWithSpacing();
|
||||
|
|
@ -176,47 +175,41 @@ public class ModMergeTab(ModMerger modMerger)
|
|||
if (ImGui.Checkbox("##check", ref selected))
|
||||
Handle(option, selected);
|
||||
|
||||
if (option.IsDefault)
|
||||
if (option.Group is not { } group)
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(option.FullName);
|
||||
ImGuiUtil.DrawTableColumn(option.GetFullName());
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(option.Name);
|
||||
var group = option.Group;
|
||||
var optionEnumerator = group switch
|
||||
{
|
||||
SingleModGroup single => single.OptionData,
|
||||
MultiModGroup multi => multi.PrioritizedOptions.Select(o => o.Mod),
|
||||
_ => [],
|
||||
};
|
||||
ImGuiUtil.DrawTableColumn(option.GetName());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Selectable(group.Name, false);
|
||||
if (ImGui.BeginPopupContextItem("##groupContext"))
|
||||
{
|
||||
if (ImGui.MenuItem("Select All"))
|
||||
// ReSharper disable once PossibleMultipleEnumeration
|
||||
foreach (var opt in optionEnumerator)
|
||||
foreach (var opt in group.DataContainers)
|
||||
Handle(opt, true);
|
||||
|
||||
if (ImGui.MenuItem("Unselect All"))
|
||||
// ReSharper disable once PossibleMultipleEnumeration
|
||||
foreach (var opt in optionEnumerator)
|
||||
foreach (var opt in group.DataContainers)
|
||||
Handle(opt, false);
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.RightAlign(option.FileData.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
|
||||
ImGuiUtil.RightAlign(option.Files.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.RightAlign(option.FileSwapData.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
|
||||
ImGuiUtil.RightAlign(option.FileSwaps.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiUtil.RightAlign(option.Manipulations.Count.ToString(), 3 * ImGuiHelpers.GlobalScale);
|
||||
continue;
|
||||
|
||||
void Handle(SubMod option2, bool selected2)
|
||||
void Handle(IModDataContainer option2, bool selected2)
|
||||
{
|
||||
if (selected2)
|
||||
modMerger.SelectedOptions.Add(option2);
|
||||
|
|
|
|||
|
|
@ -486,7 +486,7 @@ public class ModPanelEditTab(
|
|||
EditOption(panel, single, groupIdx, optionIdx);
|
||||
break;
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
|
@ -542,7 +542,7 @@ public class ModPanelEditTab(
|
|||
if (group is not MultiModGroup multi)
|
||||
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))
|
||||
panel._modManager.OptionEditor.ChangeOptionPriority(panel._mod, groupIdx, optionIdx, priority);
|
||||
|
||||
|
|
@ -557,7 +557,7 @@ public class ModPanelEditTab(
|
|||
var count = group switch
|
||||
{
|
||||
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."),
|
||||
};
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -591,6 +591,9 @@ public class ModPanelEditTab(
|
|||
// Handle drag and drop to move options inside a group or into another group.
|
||||
private static void Source(IModGroup group, int groupIdx, int optionIdx)
|
||||
{
|
||||
if (group is not ITexToolsGroup)
|
||||
return;
|
||||
|
||||
using var source = ImRaii.DragDropSource();
|
||||
if (!source)
|
||||
return;
|
||||
|
|
@ -606,6 +609,9 @@ public class ModPanelEditTab(
|
|||
|
||||
private static void Target(ModPanelEditTab panel, IModGroup group, int groupIdx, int optionIdx)
|
||||
{
|
||||
if (group is not ITexToolsGroup)
|
||||
return;
|
||||
|
||||
using var target = ImRaii.DragDropTarget();
|
||||
if (!target.Success || !ImGuiUtil.IsDropping(DragDropLabel))
|
||||
return;
|
||||
|
|
@ -624,22 +630,12 @@ public class ModPanelEditTab(
|
|||
var sourceGroupIdx = _dragDropGroupIdx;
|
||||
var sourceOption = _dragDropOptionIdx;
|
||||
var sourceGroup = panel._mod.Groups[sourceGroupIdx];
|
||||
var currentCount = group switch
|
||||
{
|
||||
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."),
|
||||
};
|
||||
var currentCount = group.DataContainers.Count;
|
||||
var option = ((ITexToolsGroup) sourceGroup).OptionData[_dragDropOptionIdx];
|
||||
panel._delayedActions.Enqueue(() =>
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ public class ModPanelTabBar
|
|||
if (ImGui.TabItemButton("Advanced Editing", ImGuiTabItemFlags.Trailing | ImGuiTabItemFlags.NoTooltip))
|
||||
{
|
||||
_modEditWindow.ChangeMod(mod);
|
||||
_modEditWindow.ChangeOption((SubMod)mod.Default);
|
||||
_modEditWindow.ChangeOption(mod.Default);
|
||||
_modEditWindow.IsOpen = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue