mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 20:24:17 +01:00
Rework options, pre-submod types.
This commit is contained in:
parent
792a04337f
commit
07afbfb229
25 changed files with 620 additions and 300 deletions
|
|
@ -56,13 +56,13 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
|
|||
|
||||
var dict = new Dictionary<string, (string[], int)>(mod.Groups.Count);
|
||||
foreach (var g in mod.Groups)
|
||||
dict.Add(g.Name, (g.Select(o => o.Name).ToArray(), (int)g.Type));
|
||||
dict.Add(g.Name, (g.Options.Select(o => o.Name).ToArray(), (int)g.Type));
|
||||
return new AvailableModSettings(dict);
|
||||
}
|
||||
|
||||
public Dictionary<string, (string[], int)>? GetAvailableModSettingsBase(string modDirectory, string modName)
|
||||
=> _modManager.TryGetMod(modDirectory, modName, out var mod)
|
||||
? mod.Groups.ToDictionary(g => g.Name, g => (g.Select(o => o.Name).ToArray(), (int)g.Type))
|
||||
? mod.Groups.ToDictionary(g => g.Name, g => (g.Options.Select(o => o.Name).ToArray(), (int)g.Type))
|
||||
: null;
|
||||
|
||||
public (PenumbraApiEc, (bool, int, Dictionary<string, List<string>>, bool)?) GetCurrentModSettings(Guid collectionId, string modDirectory,
|
||||
|
|
@ -153,7 +153,7 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
|
|||
if (groupIdx < 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.OptionGroupMissing, args);
|
||||
|
||||
var optionIdx = mod.Groups[groupIdx].IndexOf(o => o.Name == optionName);
|
||||
var optionIdx = mod.Groups[groupIdx].Options.IndexOf(o => o.Name == optionName);
|
||||
if (optionIdx < 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args);
|
||||
|
||||
|
|
@ -190,7 +190,7 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
|
|||
{
|
||||
case SingleModGroup single:
|
||||
{
|
||||
var optionIdx = optionNames.Count == 0 ? -1 : single.IndexOf(o => o.Name == optionNames[^1]);
|
||||
var optionIdx = optionNames.Count == 0 ? -1 : single.OptionData.IndexOf(o => o.Name == optionNames[^1]);
|
||||
if (optionIdx < 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args);
|
||||
|
||||
|
|
@ -201,7 +201,7 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
|
|||
{
|
||||
foreach (var name in optionNames)
|
||||
{
|
||||
var optionIdx = multi.IndexOf(o => o.Name == name);
|
||||
var optionIdx = multi.PrioritizedOptions.IndexOf(o => o.Mod.Name == name);
|
||||
if (optionIdx < 0)
|
||||
return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args);
|
||||
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ public partial class TexToolsImporter
|
|||
{
|
||||
var option = group.OptionList[idx];
|
||||
_currentOptionName = option.Name;
|
||||
options.Insert(idx, ModCreator.CreateEmptySubMod(option.Name));
|
||||
options.Insert(idx, SubMod.CreateForSaving(option.Name));
|
||||
if (option.IsChecked)
|
||||
defaultSettings = Setting.Single(idx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,13 @@ public unsafe class MetaFileManager
|
|||
if (!dir.Exists)
|
||||
dir.Create();
|
||||
|
||||
foreach (var option in group.OfType<SubMod>())
|
||||
var optionEnumerator = group switch
|
||||
{
|
||||
SingleModGroup single => single.OptionData,
|
||||
MultiModGroup multi => multi.PrioritizedOptions.Select(o => o.Mod),
|
||||
_ => [],
|
||||
};
|
||||
foreach (var option in optionEnumerator)
|
||||
{
|
||||
var optionDir = ModCreator.NewOptionDirectory(dir, option.Name, Config.ReplaceNonAsciiOnImport);
|
||||
if (!optionDir.Exists)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using OtterGui;
|
||||
using OtterGui.Compression;
|
||||
using Penumbra.Mods.Subclasses;
|
||||
|
|
@ -72,12 +73,18 @@ public class ModEditor(
|
|||
if (groupIdx >= 0)
|
||||
{
|
||||
Group = Mod.Groups[groupIdx];
|
||||
if (optionIdx >= 0 && optionIdx < Group.Count)
|
||||
switch(Group)
|
||||
{
|
||||
Option = Group[optionIdx];
|
||||
GroupIdx = groupIdx;
|
||||
OptionIdx = optionIdx;
|
||||
return;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -109,8 +116,17 @@ public class ModEditor(
|
|||
action(mod.Default, -1, 0);
|
||||
foreach (var (group, groupIdx) in mod.Groups.WithIndex())
|
||||
{
|
||||
for (var optionIdx = 0; optionIdx < group.Count; ++optionIdx)
|
||||
action(group[optionIdx], groupIdx, optionIdx);
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup single:
|
||||
for (var optionIdx = 0; optionIdx < single.OptionData.Count; ++optionIdx)
|
||||
action(single.OptionData[optionIdx], groupIdx, optionIdx);
|
||||
break;
|
||||
case MultiModGroup multi:
|
||||
for (var optionIdx = 0; optionIdx < multi.PrioritizedOptions.Count; ++optionIdx)
|
||||
action(multi.PrioritizedOptions[optionIdx].Mod, groupIdx, optionIdx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu
|
|||
num += dict.TryAdd(path.Item2, file.File) ? 0 : 1;
|
||||
}
|
||||
|
||||
modManager.OptionEditor.OptionSetFiles(mod, option.GroupIdx, option.OptionIdx, dict);
|
||||
var (groupIdx, optionIdx) = option.GetIndices();
|
||||
modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict);
|
||||
files.UpdatePaths(mod, option);
|
||||
Changes = false;
|
||||
return num;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Utility;
|
||||
using ImGuizmoNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Api.Enums;
|
||||
|
|
@ -104,9 +105,16 @@ 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}.");
|
||||
|
||||
foreach (var originalOption in originalGroup)
|
||||
var optionEnumerator = group switch
|
||||
{
|
||||
var (option, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, originalOption.Name);
|
||||
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);
|
||||
if (optionCreated)
|
||||
{
|
||||
_createdOptions.Add(option);
|
||||
|
|
@ -138,7 +146,7 @@ public class ModMerger : IDisposable
|
|||
var (group, groupIdx, groupCreated) = _editor.FindOrAddModGroup(MergeToMod!, GroupType.Multi, groupName, SaveType.None);
|
||||
if (groupCreated)
|
||||
_createdGroups.Add(groupIdx);
|
||||
var (option, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, optionName, SaveType.None);
|
||||
var (option, _, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, optionName, SaveType.None);
|
||||
if (optionCreated)
|
||||
_createdOptions.Add(option);
|
||||
var dir = ModCreator.NewOptionDirectory(MergeToMod!.ModPath, groupName, _config.ReplaceNonAsciiOnImport);
|
||||
|
|
@ -184,9 +192,10 @@ public class ModMerger : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
_editor.OptionSetFiles(MergeToMod!, option.GroupIdx, option.OptionIdx, redirections, SaveType.None);
|
||||
_editor.OptionSetFileSwaps(MergeToMod!, option.GroupIdx, option.OptionIdx, swaps, SaveType.None);
|
||||
_editor.OptionSetManipulations(MergeToMod!, option.GroupIdx, option.OptionIdx, manips, SaveType.ImmediateSync);
|
||||
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);
|
||||
return;
|
||||
|
||||
bool GetFullPath(FullPath input, out FullPath ret)
|
||||
|
|
@ -251,7 +260,7 @@ public class ModMerger : IDisposable
|
|||
Mod? result = null;
|
||||
try
|
||||
{
|
||||
dir = _creator.CreateEmptyMod(_mods.BasePath, modName, $"Split off from {mods[0].ParentMod.Name}.");
|
||||
dir = _creator.CreateEmptyMod(_mods.BasePath, modName, $"Split off from {mods[0].Mod.Name}.");
|
||||
if (dir == null)
|
||||
throw new Exception($"Could not split off mods, unable to create new mod with name {modName}.");
|
||||
|
||||
|
|
@ -268,7 +277,6 @@ public class ModMerger : IDisposable
|
|||
{
|
||||
foreach (var originalOption in mods)
|
||||
{
|
||||
var originalGroup = originalOption.ParentMod.Groups[originalOption.GroupIdx];
|
||||
if (originalOption.IsDefault)
|
||||
{
|
||||
var files = CopySubModFiles(mods[0], dir);
|
||||
|
|
@ -278,13 +286,14 @@ public class ModMerger : IDisposable
|
|||
}
|
||||
else
|
||||
{
|
||||
var originalGroup = originalOption.Group;
|
||||
var (group, groupIdx, _) = _editor.FindOrAddModGroup(result, originalGroup.Type, originalGroup.Name);
|
||||
var (option, _) = _editor.FindOrAddOption(result, groupIdx, originalOption.Name);
|
||||
var (option, optionIdx, _) = _editor.FindOrAddOption(result, groupIdx, originalOption.Name);
|
||||
var folder = Path.Combine(dir.FullName, group.Name, option.Name);
|
||||
var files = CopySubModFiles(originalOption, new DirectoryInfo(folder));
|
||||
_editor.OptionSetFiles(result, groupIdx, option.OptionIdx, files);
|
||||
_editor.OptionSetFileSwaps(result, groupIdx, option.OptionIdx, originalOption.FileSwapData);
|
||||
_editor.OptionSetManipulations(result, groupIdx, option.OptionIdx, originalOption.ManipulationData);
|
||||
_editor.OptionSetFiles(result, groupIdx, optionIdx, files);
|
||||
_editor.OptionSetFileSwaps(result, groupIdx, optionIdx, originalOption.FileSwapData);
|
||||
_editor.OptionSetManipulations(result, groupIdx, optionIdx, originalOption.ManipulationData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -309,7 +318,7 @@ public class ModMerger : IDisposable
|
|||
private static Dictionary<Utf8GamePath, FullPath> CopySubModFiles(SubMod option, DirectoryInfo newMod)
|
||||
{
|
||||
var ret = new Dictionary<Utf8GamePath, FullPath>(option.FileData.Count);
|
||||
var parentPath = ((Mod)option.ParentMod).ModPath.FullName;
|
||||
var parentPath = ((Mod)option.Mod).ModPath.FullName;
|
||||
foreach (var (path, file) in option.FileData)
|
||||
{
|
||||
var target = Path.GetRelativePath(parentPath, file.FullName);
|
||||
|
|
@ -339,7 +348,8 @@ public class ModMerger : IDisposable
|
|||
{
|
||||
foreach (var option in _createdOptions)
|
||||
{
|
||||
_editor.DeleteOption(MergeToMod!, option.GroupIdx, option.OptionIdx);
|
||||
var (groupIdx, optionIdx) = option.GetIndices();
|
||||
_editor.DeleteOption(MergeToMod!, groupIdx, optionIdx);
|
||||
Penumbra.Log.Verbose($"[Merger] Removed option {option.FullName}.");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -167,28 +167,27 @@ public class ModNormalizer(ModManager _modManager, Configuration _config)
|
|||
// Normalize all other options.
|
||||
foreach (var (group, groupIdx) in Mod.Groups.WithIndex())
|
||||
{
|
||||
_redirections[groupIdx + 1].EnsureCapacity(group.Count);
|
||||
for (var i = _redirections[groupIdx + 1].Count; i < group.Count; ++i)
|
||||
_redirections[groupIdx + 1].Add([]);
|
||||
|
||||
var groupDir = ModCreator.CreateModFolder(directory, group.Name, _config.ReplaceNonAsciiOnImport, true);
|
||||
foreach (var option in group.OfType<SubMod>())
|
||||
switch (group)
|
||||
{
|
||||
var optionDir = ModCreator.CreateModFolder(groupDir, option.Name, _config.ReplaceNonAsciiOnImport, true);
|
||||
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([]);
|
||||
|
||||
newDict = _redirections[groupIdx + 1][option.OptionIdx];
|
||||
newDict.Clear();
|
||||
newDict.EnsureCapacity(option.FileData.Count);
|
||||
foreach (var (gamePath, fullPath) in option.FileData)
|
||||
{
|
||||
var relPath = new Utf8RelPath(gamePath).ToString();
|
||||
var newFullPath = Path.Combine(optionDir.FullName, relPath);
|
||||
var redirectPath = new FullPath(Path.Combine(Mod.ModPath.FullName, groupDir.Name, optionDir.Name, relPath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(newFullPath)!);
|
||||
File.Copy(fullPath.FullName, newFullPath, true);
|
||||
newDict.Add(gamePath, redirectPath);
|
||||
++Step;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -200,6 +199,24 @@ public class ModNormalizer(ModManager _modManager, Configuration _config)
|
|||
}
|
||||
|
||||
return false;
|
||||
|
||||
void HandleSubMod(DirectoryInfo groupDir, SubMod option, Dictionary<Utf8GamePath, FullPath> newDict)
|
||||
{
|
||||
var optionDir = ModCreator.CreateModFolder(groupDir, option.Name, _config.ReplaceNonAsciiOnImport, true);
|
||||
|
||||
newDict.Clear();
|
||||
newDict.EnsureCapacity(option.FileData.Count);
|
||||
foreach (var (gamePath, fullPath) in option.FileData)
|
||||
{
|
||||
var relPath = new Utf8RelPath(gamePath).ToString();
|
||||
var newFullPath = Path.Combine(optionDir.FullName, relPath);
|
||||
var redirectPath = new FullPath(Path.Combine(Mod.ModPath.FullName, groupDir.Name, optionDir.Name, relPath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(newFullPath)!);
|
||||
File.Copy(fullPath.FullName, newFullPath, true);
|
||||
newDict.Add(gamePath, redirectPath);
|
||||
++Step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool MoveOldFiles()
|
||||
|
|
@ -274,9 +291,20 @@ public class ModNormalizer(ModManager _modManager, Configuration _config)
|
|||
|
||||
private void ApplyRedirections()
|
||||
{
|
||||
foreach (var option in Mod.AllSubMods)
|
||||
_modManager.OptionEditor.OptionSetFiles(Mod, option.GroupIdx, option.OptionIdx,
|
||||
_redirections[option.GroupIdx + 1][option.OptionIdx]);
|
||||
foreach (var (group, groupIdx) in Mod.Groups.WithIndex())
|
||||
{
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup single:
|
||||
foreach (var (_, optionIdx) in single.OptionData.WithIndex())
|
||||
_modManager.OptionEditor.OptionSetFiles(Mod, groupIdx, optionIdx, _redirections[groupIdx + 1][optionIdx]);
|
||||
break;
|
||||
case MultiModGroup multi:
|
||||
foreach (var (_, optionIdx) in multi.PrioritizedOptions.WithIndex())
|
||||
_modManager.OptionEditor.OptionSetFiles(Mod, groupIdx, optionIdx, _redirections[groupIdx + 1][optionIdx]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++Step;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Penumbra.Communication;
|
|||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Subclasses;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Mods.Manager;
|
||||
|
|
@ -211,7 +212,14 @@ public class ModCacheManager : IDisposable
|
|||
foreach (var group in mod.Groups)
|
||||
{
|
||||
mod.HasOptions |= group.IsOption;
|
||||
foreach (var s in group)
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ public static partial class ModMigration
|
|||
case GroupType.Multi:
|
||||
|
||||
var optionPriority = ModPriority.Default;
|
||||
var newMultiGroup = new MultiModGroup()
|
||||
var newMultiGroup = new MultiModGroup(mod)
|
||||
{
|
||||
Name = group.GroupName,
|
||||
Priority = priority++,
|
||||
|
|
@ -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, option, seenMetaFiles), optionPriority++));
|
||||
newMultiGroup.PrioritizedOptions.Add((SubModFromOption(creator, mod, newMultiGroup, option, seenMetaFiles), optionPriority++));
|
||||
|
||||
break;
|
||||
case GroupType.Single:
|
||||
|
|
@ -144,7 +144,7 @@ public static partial class ModMigration
|
|||
return;
|
||||
}
|
||||
|
||||
var newSingleGroup = new SingleModGroup()
|
||||
var newSingleGroup = new SingleModGroup(mod)
|
||||
{
|
||||
Name = group.GroupName,
|
||||
Priority = priority++,
|
||||
|
|
@ -152,7 +152,7 @@ public static partial class ModMigration
|
|||
};
|
||||
mod.Groups.Add(newSingleGroup);
|
||||
foreach (var option in group.Options)
|
||||
newSingleGroup.OptionData.Add(SubModFromOption(creator, mod, option, seenMetaFiles));
|
||||
newSingleGroup.OptionData.Add(SubModFromOption(creator, mod, newSingleGroup, option, seenMetaFiles));
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -171,9 +171,9 @@ public static partial class ModMigration
|
|||
}
|
||||
}
|
||||
|
||||
private static SubMod SubModFromOption(ModCreator creator, Mod mod, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
||||
private static SubMod SubModFromOption(ModCreator creator, Mod mod, IModGroup group, OptionV0 option, HashSet<FullPath> seenMetaFiles)
|
||||
{
|
||||
var subMod = new SubMod(mod) { Name = option.OptionName };
|
||||
var subMod = new SubMod(mod, group) { Name = option.OptionName };
|
||||
AddFilesToSubMod(subMod, mod.ModPath, option, seenMetaFiles);
|
||||
creator.IncorporateMetaChanges(subMod, mod.ModPath, false);
|
||||
return subMod;
|
||||
|
|
|
|||
|
|
@ -87,12 +87,12 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
var maxPriority = mod.Groups.Count == 0 ? ModPriority.Default : mod.Groups.Max(o => o.Priority) + 1;
|
||||
|
||||
mod.Groups.Add(type == GroupType.Multi
|
||||
? new MultiModGroup
|
||||
? new MultiModGroup(mod)
|
||||
{
|
||||
Name = newName,
|
||||
Priority = maxPriority,
|
||||
}
|
||||
: new SingleModGroup
|
||||
: new SingleModGroup(mod)
|
||||
{
|
||||
Name = newName,
|
||||
Priority = maxPriority,
|
||||
|
|
@ -120,7 +120,6 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
{
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, -1, -1);
|
||||
mod.Groups.RemoveAt(groupIdx);
|
||||
UpdateSubModPositions(mod, groupIdx);
|
||||
saveService.SaveAllOptionGroups(mod, false, config.ReplaceNonAsciiOnImport);
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
|
@ -131,7 +130,6 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
if (!mod.Groups.Move(groupIdxFrom, groupIdxTo))
|
||||
return;
|
||||
|
||||
UpdateSubModPositions(mod, Math.Min(groupIdxFrom, groupIdxTo));
|
||||
saveService.SaveAllOptionGroups(mod, false, config.ReplaceNonAsciiOnImport);
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupMoved, mod, groupIdxFrom, -1, groupIdxTo);
|
||||
}
|
||||
|
|
@ -156,12 +154,9 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
/// <summary> Change the description of the given option. </summary>
|
||||
public void ChangeOptionDescription(Mod mod, int groupIdx, int optionIdx, string newDescription)
|
||||
{
|
||||
var group = mod.Groups[groupIdx];
|
||||
var option = group[optionIdx];
|
||||
if (option.Description == newDescription)
|
||||
if (!mod.Groups[groupIdx].ChangeOptionDescription(optionIdx, newDescription))
|
||||
return;
|
||||
|
||||
option.Description = newDescription;
|
||||
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
|
@ -173,12 +168,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
if (group.Priority == newPriority)
|
||||
return;
|
||||
|
||||
var _ = group switch
|
||||
{
|
||||
SingleModGroup s => s.Priority = newPriority,
|
||||
MultiModGroup m => m.Priority = newPriority,
|
||||
_ => newPriority,
|
||||
};
|
||||
group.Priority = newPriority;
|
||||
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
|
@ -188,14 +178,11 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
{
|
||||
switch (mod.Groups[groupIdx])
|
||||
{
|
||||
case SingleModGroup:
|
||||
ChangeGroupPriority(mod, groupIdx, newPriority);
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
if (m.PrioritizedOptions[optionIdx].Priority == newPriority)
|
||||
case MultiModGroup multi:
|
||||
if (multi.PrioritizedOptions[optionIdx].Priority == newPriority)
|
||||
return;
|
||||
|
||||
m.PrioritizedOptions[optionIdx] = (m.PrioritizedOptions[optionIdx].Mod, newPriority);
|
||||
multi.PrioritizedOptions[optionIdx] = (multi.PrioritizedOptions[optionIdx].Mod, newPriority);
|
||||
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, optionIdx, -1);
|
||||
return;
|
||||
|
|
@ -205,60 +192,63 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
/// <summary> Rename the given option. </summary>
|
||||
public void RenameOption(Mod mod, int groupIdx, int optionIdx, string newName)
|
||||
{
|
||||
switch (mod.Groups[groupIdx])
|
||||
{
|
||||
case SingleModGroup s:
|
||||
if (s.OptionData[optionIdx].Name == newName)
|
||||
return;
|
||||
|
||||
s.OptionData[optionIdx].Name = newName;
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
var option = m.PrioritizedOptions[optionIdx].Mod;
|
||||
if (option.Name == newName)
|
||||
return;
|
||||
|
||||
option.Name = newName;
|
||||
break;
|
||||
}
|
||||
if (!mod.Groups[groupIdx].ChangeOptionName(optionIdx, newName))
|
||||
return;
|
||||
|
||||
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
/// <summary> Add a new empty option of the given name for the given group. </summary>
|
||||
public void AddOption(Mod mod, int groupIdx, string newName, SaveType saveType = SaveType.Queue)
|
||||
public int AddOption(Mod mod, int groupIdx, string newName, SaveType saveType = SaveType.Queue)
|
||||
{
|
||||
var group = mod.Groups[groupIdx];
|
||||
var subMod = new SubMod(mod) { Name = newName };
|
||||
subMod.SetPosition(groupIdx, group.Count);
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup s:
|
||||
s.OptionData.Add(subMod);
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
m.PrioritizedOptions.Add((subMod, ModPriority.Default));
|
||||
break;
|
||||
}
|
||||
var group = mod.Groups[groupIdx];
|
||||
var idx = group.AddOption(mod, newName);
|
||||
if (idx < 0)
|
||||
return -1;
|
||||
|
||||
saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, idx, -1);
|
||||
return idx;
|
||||
}
|
||||
|
||||
/// <summary> Add a new empty option of the given name for the given group if it does not exist already. </summary>
|
||||
public (SubMod, bool) FindOrAddOption(Mod mod, int groupIdx, string newName, SaveType saveType = SaveType.Queue)
|
||||
public (SubMod, int, bool) FindOrAddOption(Mod mod, int groupIdx, string newName, SaveType saveType = SaveType.Queue)
|
||||
{
|
||||
var group = mod.Groups[groupIdx];
|
||||
var idx = group.IndexOf(o => o.Name == newName);
|
||||
if (idx >= 0)
|
||||
return ((SubMod)group[idx], false);
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup single:
|
||||
{
|
||||
var idx = single.OptionData.IndexOf(o => o.Name == newName);
|
||||
if (idx >= 0)
|
||||
return (single.OptionData[idx], idx, false);
|
||||
|
||||
AddOption(mod, groupIdx, newName, saveType);
|
||||
if (group[^1].Name != newName)
|
||||
throw new Exception($"Could not create new option with name {newName} in {group.Name}.");
|
||||
idx = single.AddOption(mod, newName);
|
||||
if (idx < 0)
|
||||
throw new Exception($"Could not create new option with name {newName} in {group.Name}.");
|
||||
|
||||
return ((SubMod)group[^1], true);
|
||||
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()}.");
|
||||
}
|
||||
|
||||
/// <summary> Add an existing option to a given group with default priority. </summary>
|
||||
|
|
@ -269,25 +259,28 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
public void AddOption(Mod mod, int groupIdx, SubMod option, ModPriority priority)
|
||||
{
|
||||
var group = mod.Groups[groupIdx];
|
||||
int idx;
|
||||
switch (group)
|
||||
{
|
||||
case MultiModGroup { Count: >= IModGroup.MaxMultiOptions }:
|
||||
case MultiModGroup { PrioritizedOptions.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:
|
||||
option.SetPosition(groupIdx, s.Count);
|
||||
idx = s.OptionData.Count;
|
||||
s.OptionData.Add(option);
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
option.SetPosition(groupIdx, m.Count);
|
||||
idx = m.PrioritizedOptions.Count;
|
||||
m.PrioritizedOptions.Add((option, priority));
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, idx, -1);
|
||||
}
|
||||
|
||||
/// <summary> Delete the given option from the given group. </summary>
|
||||
|
|
@ -306,7 +299,6 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
break;
|
||||
}
|
||||
|
||||
group.UpdatePositions(optionIdx);
|
||||
saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
|
||||
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionDeleted, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
|
@ -396,16 +388,6 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <summary> Update the indices stored in options from a given group on. </summary>
|
||||
private static void UpdateSubModPositions(Mod mod, int fromGroup)
|
||||
{
|
||||
foreach (var (group, groupIdx) in mod.Groups.WithIndex().Skip(fromGroup))
|
||||
{
|
||||
foreach (var (o, optionIdx) in group.OfType<SubMod>().WithIndex())
|
||||
o.SetPosition(groupIdx, optionIdx);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Get the correct option for the given group and option index. </summary>
|
||||
private static SubMod GetSubMod(Mod mod, int groupIdx, int optionIdx)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public sealed class Mod : IMod
|
|||
internal Mod(DirectoryInfo modPath)
|
||||
{
|
||||
ModPath = modPath;
|
||||
Default = new SubMod(this);
|
||||
Default = SubMod.CreateDefault(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
|
@ -82,7 +82,12 @@ public sealed class Mod : IMod
|
|||
}
|
||||
|
||||
public IEnumerable<SubMod> AllSubMods
|
||||
=> Groups.SelectMany(o => o).Prepend(Default);
|
||||
=> Groups.SelectMany(o => o switch
|
||||
{
|
||||
SingleModGroup single => single.OptionData,
|
||||
MultiModGroup multi => multi.PrioritizedOptions.Select(s => s.Mod),
|
||||
_ => [],
|
||||
}).Prepend(Default);
|
||||
|
||||
public List<FullPath> FindUnusedFiles()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@ using Penumbra.String.Classes;
|
|||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public partial class ModCreator(SaveService _saveService, Configuration config, ModDataEditor _dataEditor, MetaFileManager _metaFileManager,
|
||||
public partial class ModCreator(
|
||||
SaveService _saveService,
|
||||
Configuration config,
|
||||
ModDataEditor _dataEditor,
|
||||
MetaFileManager _metaFileManager,
|
||||
GamePathParser _gamePathParser)
|
||||
{
|
||||
public readonly Configuration Config = config;
|
||||
|
|
@ -106,7 +110,6 @@ public partial class ModCreator(SaveService _saveService, Configuration config,
|
|||
public void LoadDefaultOption(Mod mod)
|
||||
{
|
||||
var defaultFile = _saveService.FileNames.OptionGroupFile(mod, -1, Config.ReplaceNonAsciiOnImport);
|
||||
mod.Default.SetPosition(-1, 0);
|
||||
try
|
||||
{
|
||||
if (!File.Exists(defaultFile))
|
||||
|
|
@ -241,27 +244,21 @@ public partial class ModCreator(SaveService _saveService, Configuration config,
|
|||
{
|
||||
case GroupType.Multi:
|
||||
{
|
||||
var group = new MultiModGroup()
|
||||
{
|
||||
Name = name,
|
||||
Description = desc,
|
||||
Priority = priority,
|
||||
DefaultSettings = defaultSettings,
|
||||
};
|
||||
var group = MultiModGroup.CreateForSaving(name);
|
||||
group.Description = desc;
|
||||
group.Priority = priority;
|
||||
group.DefaultSettings = defaultSettings;
|
||||
group.PrioritizedOptions.AddRange(subMods.Select((s, idx) => (s, new ModPriority(idx))));
|
||||
_saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index, Config.ReplaceNonAsciiOnImport));
|
||||
break;
|
||||
}
|
||||
case GroupType.Single:
|
||||
{
|
||||
var group = new SingleModGroup()
|
||||
{
|
||||
Name = name,
|
||||
Description = desc,
|
||||
Priority = priority,
|
||||
DefaultSettings = defaultSettings,
|
||||
};
|
||||
group.OptionData.AddRange(subMods.OfType<SubMod>());
|
||||
var group = SingleModGroup.CreateForSaving(name);
|
||||
group.Description = desc;
|
||||
group.Priority = priority;
|
||||
group.DefaultSettings = defaultSettings;
|
||||
group.OptionData.AddRange(subMods);
|
||||
_saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index, Config.ReplaceNonAsciiOnImport));
|
||||
break;
|
||||
}
|
||||
|
|
@ -275,11 +272,8 @@ public partial class ModCreator(SaveService _saveService, Configuration config,
|
|||
.Select(f => (Utf8GamePath.FromFile(f, optionFolder, out var gamePath, true), gamePath, new FullPath(f)))
|
||||
.Where(t => t.Item1);
|
||||
|
||||
var mod = new SubMod(null!) // Mod is irrelevant here, only used for saving.
|
||||
{
|
||||
Name = option.Name,
|
||||
Description = option.Description,
|
||||
};
|
||||
var mod = SubMod.CreateForSaving(option.Name);
|
||||
mod.Description = option.Description;
|
||||
foreach (var (_, gamePath, file) in list)
|
||||
mod.FileData.TryAdd(gamePath, file);
|
||||
|
||||
|
|
@ -287,13 +281,6 @@ public partial class ModCreator(SaveService _saveService, Configuration config,
|
|||
return mod;
|
||||
}
|
||||
|
||||
/// <summary> Create an empty sub mod for single groups with None options. </summary>
|
||||
internal static SubMod CreateEmptySubMod(string name)
|
||||
=> new SubMod(null!) // Mod is irrelevant here, only used for saving.
|
||||
{
|
||||
Name = name,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create the default data file from all unused files that were not handled before
|
||||
/// and are used in sub mods.
|
||||
|
|
|
|||
80
Penumbra/Mods/Subclasses/IModDataContainer.cs
Normal file
80
Penumbra/Mods/Subclasses/IModDataContainer.cs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Mods.Subclasses;
|
||||
|
||||
public interface IModDataContainer
|
||||
{
|
||||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; }
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; }
|
||||
public HashSet<MetaManipulation> Manipulations { get; set; }
|
||||
|
||||
public void AddDataTo(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 static void Load(JToken json, IModDataContainer data, DirectoryInfo basePath)
|
||||
{
|
||||
data.Files.Clear();
|
||||
data.FileSwaps.Clear();
|
||||
data.Manipulations.Clear();
|
||||
|
||||
var files = (JObject?)json[nameof(Files)];
|
||||
if (files != null)
|
||||
foreach (var property in files.Properties())
|
||||
{
|
||||
if (Utf8GamePath.FromString(property.Name, out var p, true))
|
||||
data.Files.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))
|
||||
data.FileSwaps.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()))
|
||||
data.Manipulations.Add(s);
|
||||
}
|
||||
|
||||
public static void WriteModData(JsonWriter j, JsonSerializer serializer, IModDataContainer data, DirectoryInfo basePath)
|
||||
{
|
||||
j.WritePropertyName(nameof(data.Files));
|
||||
j.WriteStartObject();
|
||||
foreach (var (gamePath, file) in data.Files)
|
||||
{
|
||||
if (file.ToRelPath(basePath, out var relPath))
|
||||
{
|
||||
j.WritePropertyName(gamePath.ToString());
|
||||
j.WriteValue(relPath.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
j.WriteEndObject();
|
||||
j.WritePropertyName(nameof(data.FileSwaps));
|
||||
j.WriteStartObject();
|
||||
foreach (var (gamePath, file) in data.FileSwaps)
|
||||
{
|
||||
j.WritePropertyName(gamePath.ToString());
|
||||
j.WriteValue(file.ToString());
|
||||
}
|
||||
|
||||
j.WriteEndObject();
|
||||
j.WritePropertyName(nameof(data.Manipulations));
|
||||
serializer.Serialize(j, data.Manipulations);
|
||||
j.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,25 +6,27 @@ using Penumbra.String.Classes;
|
|||
|
||||
namespace Penumbra.Mods.Subclasses;
|
||||
|
||||
public interface IModGroup : IReadOnlyCollection<SubMod>
|
||||
public interface IModGroup
|
||||
{
|
||||
public const int MaxMultiOptions = 63;
|
||||
|
||||
public Mod Mod { get; }
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public GroupType Type { get; }
|
||||
public ModPriority Priority { get; }
|
||||
public ModPriority Priority { get; set; }
|
||||
public Setting DefaultSettings { get; set; }
|
||||
|
||||
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 SubMod this[Index idx] { get; }
|
||||
|
||||
public bool IsOption { get; }
|
||||
public IReadOnlyList<IModOption> Options { get; }
|
||||
public bool IsOption { get; }
|
||||
|
||||
public IModGroup Convert(GroupType type);
|
||||
public bool MoveOption(int optionIdxFrom, int optionIdxTo);
|
||||
public void UpdatePositions(int from = 0);
|
||||
|
||||
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations);
|
||||
|
||||
|
|
|
|||
25
Penumbra/Mods/Subclasses/IModOption.cs
Normal file
25
Penumbra/Mods/Subclasses/IModOption.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Penumbra.Mods.Subclasses;
|
||||
|
||||
public interface IModOption
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FullName { get; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public static void Load(JToken json, IModOption option)
|
||||
{
|
||||
option.Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty;
|
||||
option.Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty;
|
||||
}
|
||||
|
||||
public static void WriteModOption(JsonWriter j, IModOption option)
|
||||
{
|
||||
j.WritePropertyName(nameof(Name));
|
||||
j.WriteValue(option.Name);
|
||||
j.WritePropertyName(nameof(Description));
|
||||
j.WriteValue(option.Description);
|
||||
}
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ public class ModSettings
|
|||
var config = Settings[groupIdx];
|
||||
Settings[groupIdx] = group.Type switch
|
||||
{
|
||||
GroupType.Single => config.TurnMulti(group.Count),
|
||||
GroupType.Single => config.TurnMulti(group.Options.Count),
|
||||
GroupType.Multi => Setting.Multi((int)config.Value),
|
||||
_ => config,
|
||||
};
|
||||
|
|
@ -182,15 +182,15 @@ public class ModSettings
|
|||
if (idx >= mod.Groups.Count)
|
||||
break;
|
||||
|
||||
var group = mod.Groups[idx];
|
||||
if (group.Type == GroupType.Single && setting.Value < (ulong)group.Count)
|
||||
switch (mod.Groups[idx])
|
||||
{
|
||||
dict.Add(group.Name, [group[(int)setting.Value].Name]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var list = group.Where((_, optionIdx) => (setting.Value & (1ul << optionIdx)) != 0).Select(o => o.Name).ToList();
|
||||
dict.Add(group.Name, list);
|
||||
case SingleModGroup single when setting.Value < (ulong)single.Options.Count:
|
||||
dict.Add(single.Name, [single.Options[setting.AsIndex].Name]);
|
||||
break;
|
||||
case MultiModGroup multi:
|
||||
var list = multi.Options.WithIndex().Where(p => setting.HasFlag(p.Index)).Select(p => p.Value.Name).ToList();
|
||||
dict.Add(multi.Name, list);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -11,11 +10,12 @@ 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 : IModGroup
|
||||
public sealed class MultiModGroup(Mod mod) : IModGroup
|
||||
{
|
||||
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; }
|
||||
|
|
@ -26,27 +26,58 @@ public sealed class MultiModGroup : IModGroup
|
|||
.SelectWhere(o => (o.Mod.FileData.TryGetValue(gamePath, out var file) || o.Mod.FileSwapData.TryGetValue(gamePath, out file), file))
|
||||
.FirstOrDefault();
|
||||
|
||||
public SubMod this[Index idx]
|
||||
=> PrioritizedOptions[idx].Mod;
|
||||
public int AddOption(Mod mod, string name, string description = "")
|
||||
{
|
||||
var groupIdx = mod.Groups.IndexOf(this);
|
||||
if (groupIdx < 0)
|
||||
return -1;
|
||||
|
||||
var subMod = new SubMod(mod, this)
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
};
|
||||
PrioritizedOptions.Add((subMod, ModPriority.Default));
|
||||
return PrioritizedOptions.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
|
||||
=> Count > 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public int Count
|
||||
=> PrioritizedOptions.Count;
|
||||
=> PrioritizedOptions.Count > 0;
|
||||
|
||||
public readonly List<(SubMod Mod, ModPriority Priority)> PrioritizedOptions = [];
|
||||
|
||||
public IEnumerator<SubMod> GetEnumerator()
|
||||
=> PrioritizedOptions.Select(o => o.Mod).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public static MultiModGroup? Load(Mod mod, JObject json, int groupIdx)
|
||||
{
|
||||
var ret = new MultiModGroup()
|
||||
var ret = new MultiModGroup(mod)
|
||||
{
|
||||
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty,
|
||||
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
|
||||
|
|
@ -68,8 +99,7 @@ public sealed class MultiModGroup : IModGroup
|
|||
break;
|
||||
}
|
||||
|
||||
var subMod = new SubMod(mod);
|
||||
subMod.SetPosition(groupIdx, ret.PrioritizedOptions.Count);
|
||||
var subMod = new SubMod(mod, ret);
|
||||
subMod.Load(mod.ModPath, child, out var priority);
|
||||
ret.PrioritizedOptions.Add((subMod, priority));
|
||||
}
|
||||
|
|
@ -85,12 +115,12 @@ public sealed class MultiModGroup : IModGroup
|
|||
{
|
||||
case GroupType.Multi: return this;
|
||||
case GroupType.Single:
|
||||
var multi = new SingleModGroup()
|
||||
var multi = new SingleModGroup(Mod)
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
Priority = Priority,
|
||||
DefaultSettings = DefaultSettings.TurnMulti(Count),
|
||||
DefaultSettings = DefaultSettings.TurnMulti(PrioritizedOptions.Count),
|
||||
};
|
||||
multi.OptionData.AddRange(PrioritizedOptions.Select(p => p.Mod));
|
||||
return multi;
|
||||
|
|
@ -104,16 +134,9 @@ public sealed class MultiModGroup : IModGroup
|
|||
return false;
|
||||
|
||||
DefaultSettings = DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
|
||||
UpdatePositions(Math.Min(optionIdxFrom, optionIdxTo));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdatePositions(int from = 0)
|
||||
{
|
||||
foreach (var ((o, _), i) in PrioritizedOptions.WithIndex().Skip(from))
|
||||
o.SetPosition(o.GroupIdx, i);
|
||||
}
|
||||
|
||||
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||
{
|
||||
foreach (var (option, index) in PrioritizedOptions.WithIndex().OrderByDescending(o => o.Value.Priority))
|
||||
|
|
@ -124,5 +147,12 @@ public sealed class MultiModGroup : IModGroup
|
|||
}
|
||||
|
||||
public Setting FixSetting(Setting setting)
|
||||
=> new(setting.Value & ((1ul << Count) - 1));
|
||||
=> new(setting.Value & ((1ul << PrioritizedOptions.Count) - 1));
|
||||
|
||||
/// <summary> Create a group without a mod only for saving it in the creator. </summary>
|
||||
internal static MultiModGroup CreateForSaving(string name)
|
||||
=> new(null!)
|
||||
{
|
||||
Name = name,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Filesystem;
|
||||
|
|
@ -9,11 +8,12 @@ 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 : IModGroup
|
||||
public sealed class SingleModGroup(Mod mod) : IModGroup
|
||||
{
|
||||
public GroupType Type
|
||||
=> GroupType.Single;
|
||||
|
||||
public Mod Mod { get; set; } = mod;
|
||||
public string Name { get; set; } = "Option";
|
||||
public string Description { get; set; } = "A mutually exclusive group of settings.";
|
||||
public ModPriority Priority { get; set; }
|
||||
|
|
@ -26,26 +26,53 @@ public sealed class SingleModGroup : IModGroup
|
|||
.SelectWhere(m => (m.FileData.TryGetValue(gamePath, out var file) || m.FileSwapData.TryGetValue(gamePath, out file), file))
|
||||
.FirstOrDefault();
|
||||
|
||||
public SubMod this[Index idx]
|
||||
=> OptionData[idx];
|
||||
public int AddOption(Mod mod, string name, string description = "")
|
||||
{
|
||||
var subMod = new SubMod(mod, this)
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
};
|
||||
OptionData.Add(subMod);
|
||||
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 bool IsOption
|
||||
=> Count > 1;
|
||||
|
||||
[JsonIgnore]
|
||||
public int Count
|
||||
=> OptionData.Count;
|
||||
|
||||
public IEnumerator<SubMod> GetEnumerator()
|
||||
=> OptionData.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
=> OptionData.Count > 1;
|
||||
|
||||
public static SingleModGroup? Load(Mod mod, JObject json, int groupIdx)
|
||||
{
|
||||
var options = json["Options"];
|
||||
var ret = new SingleModGroup
|
||||
var ret = new SingleModGroup(mod)
|
||||
{
|
||||
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty,
|
||||
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
|
||||
|
|
@ -58,8 +85,7 @@ public sealed class SingleModGroup : IModGroup
|
|||
if (options != null)
|
||||
foreach (var child in options.Children())
|
||||
{
|
||||
var subMod = new SubMod(mod);
|
||||
subMod.SetPosition(groupIdx, ret.OptionData.Count);
|
||||
var subMod = new SubMod(mod, ret);
|
||||
subMod.Load(mod.ModPath, child, out _);
|
||||
ret.OptionData.Add(subMod);
|
||||
}
|
||||
|
|
@ -74,7 +100,7 @@ public sealed class SingleModGroup : IModGroup
|
|||
{
|
||||
case GroupType.Single: return this;
|
||||
case GroupType.Multi:
|
||||
var multi = new MultiModGroup()
|
||||
var multi = new MultiModGroup(Mod)
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
|
|
@ -108,19 +134,19 @@ public sealed class SingleModGroup : IModGroup
|
|||
DefaultSettings = Setting.Single(currentIndex + 1);
|
||||
}
|
||||
|
||||
UpdatePositions(Math.Min(optionIdxFrom, optionIdxTo));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdatePositions(int from = 0)
|
||||
{
|
||||
foreach (var (o, i) in OptionData.WithIndex().Skip(from))
|
||||
o.SetPosition(o.GroupIdx, i);
|
||||
}
|
||||
|
||||
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||
=> this[setting.AsIndex].AddData(redirections, manipulations);
|
||||
=> OptionData[setting.AsIndex].AddData(redirections, manipulations);
|
||||
|
||||
public Setting FixSetting(Setting setting)
|
||||
=> Count == 0 ? Setting.Zero : new Setting(Math.Min(setting.Value, (ulong)(Count - 1)));
|
||||
=> OptionData.Count == 0 ? Setting.Zero : new Setting(Math.Min(setting.Value, (ulong)(OptionData.Count - 1)));
|
||||
|
||||
/// <summary> Create a group without a mod only for saving it in the creator. </summary>
|
||||
internal static SingleModGroup CreateForSaving(string name)
|
||||
=> new(null!)
|
||||
{
|
||||
Name = name,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,62 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Mods.Subclasses;
|
||||
|
||||
public class SingleSubMod(Mod mod, SingleModGroup group) : IModOption, IModDataContainer
|
||||
{
|
||||
internal readonly Mod Mod = mod;
|
||||
internal readonly SingleModGroup Group = group;
|
||||
|
||||
public string Name { get; set; } = "Option";
|
||||
|
||||
public string FullName
|
||||
=> $"{Group.Name}: {Name}";
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
|
||||
public HashSet<MetaManipulation> Manipulations { get; set; } = [];
|
||||
}
|
||||
|
||||
public class MultiSubMod(Mod mod, MultiModGroup group) : IModOption, IModDataContainer
|
||||
{
|
||||
internal readonly Mod Mod = mod;
|
||||
internal readonly MultiModGroup Group = group;
|
||||
|
||||
public string Name { get; set; } = "Option";
|
||||
|
||||
public string FullName
|
||||
=> $"{Group.Name}: {Name}";
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public ModPriority Priority { get; set; } = ModPriority.Default;
|
||||
|
||||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
|
||||
public HashSet<MetaManipulation> Manipulations { get; set; } = [];
|
||||
}
|
||||
|
||||
public class DefaultSubMod(IMod mod) : IModDataContainer
|
||||
{
|
||||
public string FullName
|
||||
=> "Default Option";
|
||||
|
||||
public string Description
|
||||
=> string.Empty;
|
||||
|
||||
internal readonly IMod Mod = mod;
|
||||
|
||||
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
|
||||
public HashSet<MetaManipulation> Manipulations { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A sub mod is a collection of
|
||||
/// - file replacements
|
||||
|
|
@ -16,21 +67,51 @@ namespace Penumbra.Mods.Subclasses;
|
|||
/// 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
|
||||
public sealed class SubMod(IMod mod, IModGroup group) : IModOption
|
||||
{
|
||||
public string Name { get; set; } = "Default";
|
||||
|
||||
public string FullName
|
||||
=> GroupIdx < 0 ? "Default Option" : $"{ParentMod.Groups[GroupIdx].Name}: {Name}";
|
||||
=> Group == null ? "Default Option" : $"{Group.Name}: {Name}";
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
internal IMod ParentMod { get; private init; }
|
||||
internal int GroupIdx { get; private set; }
|
||||
internal int OptionIdx { get; private set; }
|
||||
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
|
||||
=> GroupIdx < 0;
|
||||
=> Group == null;
|
||||
|
||||
public void AddData(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||
{
|
||||
|
|
@ -46,9 +127,6 @@ public sealed class SubMod
|
|||
public Dictionary<Utf8GamePath, FullPath> FileSwapData = [];
|
||||
public HashSet<MetaManipulation> ManipulationData = [];
|
||||
|
||||
public SubMod(IMod parentMod)
|
||||
=> ParentMod = parentMod;
|
||||
|
||||
public IReadOnlyDictionary<Utf8GamePath, FullPath> Files
|
||||
=> FileData;
|
||||
|
||||
|
|
@ -58,12 +136,6 @@ public sealed class SubMod
|
|||
public IReadOnlySet<MetaManipulation> Manipulations
|
||||
=> ManipulationData;
|
||||
|
||||
public void SetPosition(int groupIdx, int optionIdx)
|
||||
{
|
||||
GroupIdx = groupIdx;
|
||||
OptionIdx = optionIdx;
|
||||
}
|
||||
|
||||
public void Load(DirectoryInfo basePath, JToken json, out ModPriority priority)
|
||||
{
|
||||
FileData.Clear();
|
||||
|
|
@ -116,6 +188,14 @@ public sealed class SubMod
|
|||
}
|
||||
}
|
||||
|
||||
/// <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();
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public class TemporaryMod : IMod
|
|||
=> [Default];
|
||||
|
||||
public TemporaryMod()
|
||||
=> Default = new SubMod(this);
|
||||
=> Default = SubMod.CreateDefault(this);
|
||||
|
||||
public void SetFile(Utf8GamePath gamePath, FullPath fullPath)
|
||||
=> Default.FileData[gamePath] = fullPath;
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
_subModValid = _mod != null
|
||||
&& _newGroupName.Length > 0
|
||||
&& _newOptionName.Length > 0
|
||||
&& (_selectedGroup?.All(o => o.Name != _newOptionName) ?? true);
|
||||
&& (_selectedGroup?.Options.All(o => o.Name != _newOptionName) ?? true);
|
||||
}
|
||||
|
||||
private void CreateMod()
|
||||
|
|
@ -275,7 +275,7 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
|
||||
var groupCreated = false;
|
||||
var dirCreated = false;
|
||||
var optionCreated = false;
|
||||
var optionCreated = -1;
|
||||
DirectoryInfo? optionFolderName = null;
|
||||
try
|
||||
{
|
||||
|
|
@ -294,14 +294,17 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
groupCreated = true;
|
||||
}
|
||||
|
||||
_modManager.OptionEditor.AddOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _newOptionName);
|
||||
optionCreated = true;
|
||||
var optionIdx = _modManager.OptionEditor.AddOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _newOptionName);
|
||||
if (optionIdx < 0)
|
||||
throw new Exception($"Failure creating mod option.");
|
||||
|
||||
optionCreated = optionIdx;
|
||||
optionFolderName = Directory.CreateDirectory(optionFolderName.FullName);
|
||||
dirCreated = true;
|
||||
if (!_swapData.WriteMod(_modManager, _mod,
|
||||
_useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps,
|
||||
optionFolderName,
|
||||
_mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1))
|
||||
_mod.Groups.IndexOf(_selectedGroup), optionIdx))
|
||||
throw new Exception("Failure writing files for mod swap.");
|
||||
}
|
||||
}
|
||||
|
|
@ -310,8 +313,8 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
Penumbra.Messager.NotificationMessage(e, "Could not create new Swap Option.", NotificationType.Error, false);
|
||||
try
|
||||
{
|
||||
if (optionCreated && _selectedGroup != null)
|
||||
_modManager.OptionEditor.DeleteOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1);
|
||||
if (optionCreated >= 0 && _selectedGroup != null)
|
||||
_modManager.OptionEditor.DeleteOption(_mod, _mod.Groups.IndexOf(_selectedGroup), optionCreated);
|
||||
|
||||
if (groupCreated)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -78,7 +78,10 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
}
|
||||
|
||||
public void ChangeOption(SubMod? subMod)
|
||||
=> _editor.LoadOption(subMod?.GroupIdx ?? -1, subMod?.OptionIdx ?? 0);
|
||||
{
|
||||
var (groupIdx, optionIdx) = subMod?.GetIndices() ?? (-1, 0);
|
||||
_editor.LoadOption(groupIdx, optionIdx);
|
||||
}
|
||||
|
||||
public void UpdateModels()
|
||||
{
|
||||
|
|
@ -428,7 +431,8 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
using var id = ImRaii.PushId(idx);
|
||||
if (ImGui.Selectable(option.FullName, option == _editor.Option))
|
||||
{
|
||||
_editor.LoadOption(option.GroupIdx, option.OptionIdx);
|
||||
var (groupIdx, optionIdx) = option.GetIndices();
|
||||
_editor.LoadOption(groupIdx, optionIdx);
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -565,7 +569,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
}
|
||||
|
||||
if (Mod != null)
|
||||
foreach (var option in Mod.Groups.SelectMany(g => g).Append(Mod.Default))
|
||||
foreach (var option in Mod.AllSubMods)
|
||||
{
|
||||
foreach (var path in option.Files.Keys)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ public class ModMergeTab(ModMerger modMerger)
|
|||
|
||||
color = color == Colors.DiscordColor
|
||||
? Colors.DiscordColor
|
||||
: group == null || group.Any(o => o.Name == modMerger.OptionName)
|
||||
: group == null || group.Options.Any(o => o.Name == modMerger.OptionName)
|
||||
? Colors.PressEnterWarningBg
|
||||
: Colors.DiscordColor;
|
||||
c.Push(ImGuiCol.Border, color);
|
||||
|
|
@ -184,18 +184,26 @@ public class ModMergeTab(ModMerger modMerger)
|
|||
else
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(option.Name);
|
||||
var group = option.ParentMod.Groups[option.GroupIdx];
|
||||
var group = option.Group;
|
||||
var optionEnumerator = group switch
|
||||
{
|
||||
SingleModGroup single => single.OptionData,
|
||||
MultiModGroup multi => multi.PrioritizedOptions.Select(o => o.Mod),
|
||||
_ => [],
|
||||
};
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Selectable(group.Name, false);
|
||||
if (ImGui.BeginPopupContextItem("##groupContext"))
|
||||
{
|
||||
if (ImGui.MenuItem("Select All"))
|
||||
foreach (var opt in group)
|
||||
Handle((SubMod)opt, true);
|
||||
// ReSharper disable once PossibleMultipleEnumeration
|
||||
foreach (var opt in optionEnumerator)
|
||||
Handle(opt, true);
|
||||
|
||||
if (ImGui.MenuItem("Unselect All"))
|
||||
foreach (var opt in group)
|
||||
Handle((SubMod)opt, false);
|
||||
// ReSharper disable once PossibleMultipleEnumeration
|
||||
foreach (var opt in optionEnumerator)
|
||||
Handle(opt, false);
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ public class ModPanelEditTab(
|
|||
? mod.Description
|
||||
: optionIdx < 0
|
||||
? mod.Groups[groupIdx].Description
|
||||
: mod.Groups[groupIdx][optionIdx].Description;
|
||||
: mod.Groups[groupIdx].Options[optionIdx].Description;
|
||||
_oldDescription = _newDescription;
|
||||
|
||||
_mod = mod;
|
||||
|
|
@ -479,17 +479,24 @@ public class ModPanelEditTab(
|
|||
ImGui.TableSetupColumn("delete", ImGuiTableColumnFlags.WidthFixed, UiHelpers.IconButtonSize.X);
|
||||
ImGui.TableSetupColumn("priority", ImGuiTableColumnFlags.WidthFixed, 50 * UiHelpers.Scale);
|
||||
|
||||
var group = panel._mod.Groups[groupIdx];
|
||||
for (var optionIdx = 0; optionIdx < group.Count; ++optionIdx)
|
||||
EditOption(panel, group, groupIdx, optionIdx);
|
||||
|
||||
switch (panel._mod.Groups[groupIdx])
|
||||
{
|
||||
case SingleModGroup single:
|
||||
for (var optionIdx = 0; optionIdx < single.OptionData.Count; ++optionIdx)
|
||||
EditOption(panel, single, groupIdx, optionIdx);
|
||||
break;
|
||||
case MultiModGroup multi:
|
||||
for (var optionIdx = 0; optionIdx < multi.PrioritizedOptions.Count; ++optionIdx)
|
||||
EditOption(panel, multi, groupIdx, optionIdx);
|
||||
break;
|
||||
}
|
||||
DrawNewOption(panel, groupIdx, UiHelpers.IconButtonSize);
|
||||
}
|
||||
|
||||
/// <summary> Draw a line for a single option. </summary>
|
||||
private static void EditOption(ModPanelEditTab panel, IModGroup group, int groupIdx, int optionIdx)
|
||||
{
|
||||
var option = group[optionIdx];
|
||||
var option = group.Options[optionIdx];
|
||||
using var id = ImRaii.PushId(optionIdx);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
|
|
@ -547,10 +554,16 @@ public class ModPanelEditTab(
|
|||
{
|
||||
var mod = panel._mod;
|
||||
var group = mod.Groups[groupIdx];
|
||||
var count = 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."),
|
||||
};
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Selectable($"Option #{group.Count + 1}");
|
||||
Target(panel, group, groupIdx, group.Count);
|
||||
ImGui.Selectable($"Option #{count + 1}");
|
||||
Target(panel, group, groupIdx, count);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
|
|
@ -562,7 +575,7 @@ public class ModPanelEditTab(
|
|||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var canAddGroup = mod.Groups[groupIdx].Type != GroupType.Multi || mod.Groups[groupIdx].Count < IModGroup.MaxMultiOptions;
|
||||
var canAddGroup = mod.Groups[groupIdx].Type != GroupType.Multi || count < IModGroup.MaxMultiOptions;
|
||||
var validName = _newOptionName.Length > 0 && _newOptionNameIdx == groupIdx;
|
||||
var tt = canAddGroup
|
||||
? validName ? "Add a new option to this group." : "Please enter a name for the new option."
|
||||
|
|
@ -588,7 +601,7 @@ public class ModPanelEditTab(
|
|||
_dragDropOptionIdx = optionIdx;
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted($"Dragging option {group[optionIdx].Name} from group {group.Name}...");
|
||||
ImGui.TextUnformatted($"Dragging option {group.Options[optionIdx].Name} from group {group.Name}...");
|
||||
}
|
||||
|
||||
private static void Target(ModPanelEditTab panel, IModGroup group, int groupIdx, int optionIdx)
|
||||
|
|
@ -611,12 +624,17 @@ public class ModPanelEditTab(
|
|||
var sourceGroupIdx = _dragDropGroupIdx;
|
||||
var sourceOption = _dragDropOptionIdx;
|
||||
var sourceGroup = panel._mod.Groups[sourceGroupIdx];
|
||||
var currentCount = group.Count;
|
||||
var option = sourceGroup[sourceOption];
|
||||
var priority = sourceGroup switch
|
||||
var currentCount = group switch
|
||||
{
|
||||
MultiModGroup multi => multi.PrioritizedOptions[_dragDropOptionIdx].Priority,
|
||||
_ => ModPriority.Default,
|
||||
SingleModGroup single => single.OptionData.Count,
|
||||
MultiModGroup multi => multi.PrioritizedOptions.Count,
|
||||
_ => throw new Exception($"Dragging options to an option group of type {group.GetType()} is not supported."),
|
||||
};
|
||||
var (option, priority) = sourceGroup switch
|
||||
{
|
||||
SingleModGroup single => (single.OptionData[_dragDropOptionIdx], ModPriority.Default),
|
||||
MultiModGroup multi => multi.PrioritizedOptions[_dragDropOptionIdx],
|
||||
_ => throw new Exception($"Dragging options from an option group of type {sourceGroup.GetType()} is not supported."),
|
||||
};
|
||||
panel._delayedActions.Enqueue(() =>
|
||||
{
|
||||
|
|
@ -651,7 +669,7 @@ public class ModPanelEditTab(
|
|||
if (ImGui.Selectable(GroupTypeName(GroupType.Single), group.Type == GroupType.Single))
|
||||
_modManager.OptionEditor.ChangeModGroupType(_mod, groupIdx, GroupType.Single);
|
||||
|
||||
var canSwitchToMulti = group.Count <= IModGroup.MaxMultiOptions;
|
||||
var canSwitchToMulti = group.Options.Count <= IModGroup.MaxMultiOptions;
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, !canSwitchToMulti);
|
||||
if (ImGui.Selectable(GroupTypeName(GroupType.Multi), group.Type == GroupType.Multi) && canSwitchToMulti)
|
||||
_modManager.OptionEditor.ChangeModGroupType(_mod, groupIdx, GroupType.Multi);
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public class ModPanelSettingsTab : ITab
|
|||
{
|
||||
var useDummy = true;
|
||||
foreach (var (group, idx) in _selector.Selected!.Groups.WithIndex()
|
||||
.Where(g => g.Value.Type == GroupType.Single && g.Value.Count > _config.SingleGroupRadioMax))
|
||||
.Where(g => g.Value.Type == GroupType.Single && g.Value.Options.Count > _config.SingleGroupRadioMax))
|
||||
{
|
||||
ImGuiUtil.Dummy(UiHelpers.DefaultSpace, useDummy);
|
||||
useDummy = false;
|
||||
|
|
@ -92,7 +92,7 @@ public class ModPanelSettingsTab : ITab
|
|||
case GroupType.Multi:
|
||||
DrawMultiGroup(group, idx);
|
||||
break;
|
||||
case GroupType.Single when group.Count <= _config.SingleGroupRadioMax:
|
||||
case GroupType.Single when group.Options.Count <= _config.SingleGroupRadioMax:
|
||||
DrawSingleGroupRadio(group, idx);
|
||||
break;
|
||||
}
|
||||
|
|
@ -181,13 +181,14 @@ public class ModPanelSettingsTab : ITab
|
|||
using var id = ImRaii.PushId(groupIdx);
|
||||
var selectedOption = _empty ? group.DefaultSettings.AsIndex : _settings.Settings[groupIdx].AsIndex;
|
||||
ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X * 3 / 4);
|
||||
using (var combo = ImRaii.Combo(string.Empty, group[selectedOption].Name))
|
||||
var options = group.Options;
|
||||
using (var combo = ImRaii.Combo(string.Empty, options[selectedOption].Name))
|
||||
{
|
||||
if (combo)
|
||||
for (var idx2 = 0; idx2 < group.Count; ++idx2)
|
||||
for (var idx2 = 0; idx2 < options.Count; ++idx2)
|
||||
{
|
||||
id.Push(idx2);
|
||||
var option = group[idx2];
|
||||
var option = options[idx2];
|
||||
if (ImGui.Selectable(option.Name, idx2 == selectedOption))
|
||||
_collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx,
|
||||
Setting.Single(idx2));
|
||||
|
|
@ -213,18 +214,18 @@ public class ModPanelSettingsTab : ITab
|
|||
using var id = ImRaii.PushId(groupIdx);
|
||||
var selectedOption = _empty ? group.DefaultSettings.AsIndex : _settings.Settings[groupIdx].AsIndex;
|
||||
var minWidth = Widget.BeginFramedGroup(group.Name, group.Description);
|
||||
|
||||
DrawCollapseHandling(group, minWidth, DrawOptions);
|
||||
var options = group.Options;
|
||||
DrawCollapseHandling(options, minWidth, DrawOptions);
|
||||
|
||||
Widget.EndFramedGroup();
|
||||
return;
|
||||
|
||||
void DrawOptions()
|
||||
{
|
||||
for (var idx = 0; idx < group.Count; ++idx)
|
||||
for (var idx = 0; idx < group.Options.Count; ++idx)
|
||||
{
|
||||
using var i = ImRaii.PushId(idx);
|
||||
var option = group[idx];
|
||||
var option = options[idx];
|
||||
if (ImGui.RadioButton(option.Name, selectedOption == idx))
|
||||
_collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx,
|
||||
Setting.Single(idx));
|
||||
|
|
@ -239,9 +240,9 @@ public class ModPanelSettingsTab : ITab
|
|||
}
|
||||
|
||||
|
||||
private void DrawCollapseHandling(IModGroup group, float minWidth, Action draw)
|
||||
private void DrawCollapseHandling(IReadOnlyList<IModOption> options, float minWidth, Action draw)
|
||||
{
|
||||
if (group.Count <= _config.OptionGroupCollapsibleMin)
|
||||
if (options.Count <= _config.OptionGroupCollapsibleMin)
|
||||
{
|
||||
draw();
|
||||
}
|
||||
|
|
@ -249,8 +250,8 @@ public class ModPanelSettingsTab : ITab
|
|||
{
|
||||
var collapseId = ImGui.GetID("Collapse");
|
||||
var shown = ImGui.GetStateStorage().GetBool(collapseId, true);
|
||||
var buttonTextShow = $"Show {group.Count} Options";
|
||||
var buttonTextHide = $"Hide {group.Count} Options";
|
||||
var buttonTextShow = $"Show {options.Count} Options";
|
||||
var buttonTextHide = $"Hide {options.Count} Options";
|
||||
var buttonWidth = Math.Max(ImGui.CalcTextSize(buttonTextShow).X, ImGui.CalcTextSize(buttonTextHide).X)
|
||||
+ 2 * ImGui.GetStyle().FramePadding.X;
|
||||
minWidth = Math.Max(buttonWidth, minWidth);
|
||||
|
|
@ -274,7 +275,7 @@ public class ModPanelSettingsTab : ITab
|
|||
}
|
||||
else
|
||||
{
|
||||
var optionWidth = group.Max(o => ImGui.CalcTextSize(o.Name).X)
|
||||
var optionWidth = options.Max(o => ImGui.CalcTextSize(o.Name).X)
|
||||
+ ImGui.GetStyle().ItemInnerSpacing.X
|
||||
+ ImGui.GetFrameHeight()
|
||||
+ ImGui.GetStyle().FramePadding.X;
|
||||
|
|
@ -294,8 +295,8 @@ public class ModPanelSettingsTab : ITab
|
|||
using var id = ImRaii.PushId(groupIdx);
|
||||
var flags = _empty ? group.DefaultSettings : _settings.Settings[groupIdx];
|
||||
var minWidth = Widget.BeginFramedGroup(group.Name, group.Description);
|
||||
|
||||
DrawCollapseHandling(group, minWidth, DrawOptions);
|
||||
var options = group.Options;
|
||||
DrawCollapseHandling(options, minWidth, DrawOptions);
|
||||
|
||||
Widget.EndFramedGroup();
|
||||
var label = $"##multi{groupIdx}";
|
||||
|
|
@ -307,10 +308,10 @@ public class ModPanelSettingsTab : ITab
|
|||
|
||||
void DrawOptions()
|
||||
{
|
||||
for (var idx = 0; idx < group.Count; ++idx)
|
||||
for (var idx = 0; idx < options.Count; ++idx)
|
||||
{
|
||||
using var i = ImRaii.PushId(idx);
|
||||
var option = group[idx];
|
||||
var option = options[idx];
|
||||
var setting = flags.HasFlag(idx);
|
||||
|
||||
if (ImGui.Checkbox(option.Name, ref setting))
|
||||
|
|
@ -339,7 +340,7 @@ public class ModPanelSettingsTab : ITab
|
|||
ImGui.Separator();
|
||||
if (ImGui.Selectable("Enable All"))
|
||||
_collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx,
|
||||
Setting.AllBits(group.Count));
|
||||
Setting.AllBits(group.Options.Count));
|
||||
|
||||
if (ImGui.Selectable("Disable All"))
|
||||
_collectionManager.Editor.SetModSetting(_collectionManager.Active.Current, _selector.Selected!, groupIdx, Setting.Zero);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue