mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Bunch of work on Option Editor.
This commit is contained in:
parent
1253079968
commit
fbe2ed1a71
21 changed files with 749 additions and 595 deletions
|
|
@ -19,8 +19,9 @@ namespace Penumbra.Collections;
|
||||||
|
|
||||||
public sealed partial class CollectionManager : IDisposable, IEnumerable<ModCollection>
|
public sealed partial class CollectionManager : IDisposable, IEnumerable<ModCollection>
|
||||||
{
|
{
|
||||||
private readonly Mods.ModManager _modManager;
|
private readonly ModManager _modManager;
|
||||||
private readonly CommunicatorService _communicator;
|
private readonly CommunicatorService _communicator;
|
||||||
|
private readonly SaveService _saveService;
|
||||||
private readonly CharacterUtility _characterUtility;
|
private readonly CharacterUtility _characterUtility;
|
||||||
private readonly ResidentResourceManager _residentResources;
|
private readonly ResidentResourceManager _residentResources;
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
|
|
@ -57,7 +58,8 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
||||||
=> _collections;
|
=> _collections;
|
||||||
|
|
||||||
public CollectionManager(StartTracker timer, CommunicatorService communicator, FilenameService files, CharacterUtility characterUtility,
|
public CollectionManager(StartTracker timer, CommunicatorService communicator, FilenameService files, CharacterUtility characterUtility,
|
||||||
ResidentResourceManager residentResources, Configuration config, Mods.ModManager modManager, IndividualCollections individuals)
|
ResidentResourceManager residentResources, Configuration config, ModManager modManager, IndividualCollections individuals,
|
||||||
|
SaveService saveService)
|
||||||
{
|
{
|
||||||
using var time = timer.Measure(StartTimeType.Collections);
|
using var time = timer.Measure(StartTimeType.Collections);
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
|
|
@ -65,12 +67,13 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
||||||
_residentResources = residentResources;
|
_residentResources = residentResources;
|
||||||
_config = config;
|
_config = config;
|
||||||
_modManager = modManager;
|
_modManager = modManager;
|
||||||
|
_saveService = saveService;
|
||||||
Individuals = individuals;
|
Individuals = individuals;
|
||||||
|
|
||||||
// The collection manager reacts to changes in mods by itself.
|
// The collection manager reacts to changes in mods by itself.
|
||||||
_modManager.ModDiscoveryStarted += OnModDiscoveryStarted;
|
_modManager.ModDiscoveryStarted += OnModDiscoveryStarted;
|
||||||
_modManager.ModDiscoveryFinished += OnModDiscoveryFinished;
|
_modManager.ModDiscoveryFinished += OnModDiscoveryFinished;
|
||||||
_modManager.ModOptionChanged += OnModOptionsChanged;
|
_communicator.ModOptionChanged.Event += OnModOptionsChanged;
|
||||||
_modManager.ModPathChanged += OnModPathChange;
|
_modManager.ModPathChanged += OnModPathChange;
|
||||||
_communicator.CollectionChange.Event += SaveOnChange;
|
_communicator.CollectionChange.Event += SaveOnChange;
|
||||||
_communicator.TemporaryGlobalModChange.Event += OnGlobalModChange;
|
_communicator.TemporaryGlobalModChange.Event += OnGlobalModChange;
|
||||||
|
|
@ -86,7 +89,7 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
||||||
_communicator.TemporaryGlobalModChange.Event -= OnGlobalModChange;
|
_communicator.TemporaryGlobalModChange.Event -= OnGlobalModChange;
|
||||||
_modManager.ModDiscoveryStarted -= OnModDiscoveryStarted;
|
_modManager.ModDiscoveryStarted -= OnModDiscoveryStarted;
|
||||||
_modManager.ModDiscoveryFinished -= OnModDiscoveryFinished;
|
_modManager.ModDiscoveryFinished -= OnModDiscoveryFinished;
|
||||||
_modManager.ModOptionChanged -= OnModOptionsChanged;
|
_communicator.ModOptionChanged.Event -= OnModOptionsChanged;
|
||||||
_modManager.ModPathChanged -= OnModPathChange;
|
_modManager.ModPathChanged -= OnModPathChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,7 +282,7 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
||||||
foreach (var collection in this)
|
foreach (var collection in this)
|
||||||
{
|
{
|
||||||
if (collection._settings[mod.Index]?.HandleChanges(type, mod, groupIdx, optionIdx, movedToIdx) ?? false)
|
if (collection._settings[mod.Index]?.HandleChanges(type, mod, groupIdx, optionIdx, movedToIdx) ?? false)
|
||||||
Penumbra.SaveService.QueueSave(collection);
|
_saveService.QueueSave(collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle changes that reload the mod if the changes did not need to be prepared,
|
// Handle changes that reload the mod if the changes did not need to be prepared,
|
||||||
|
|
@ -310,7 +313,7 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultCollection = ModCollection.CreateNewEmpty((string)ModCollection.DefaultCollection);
|
var defaultCollection = ModCollection.CreateNewEmpty((string)ModCollection.DefaultCollection);
|
||||||
Penumbra.SaveService.ImmediateSave(defaultCollection);
|
_saveService.ImmediateSave(defaultCollection);
|
||||||
defaultCollection.Index = _collections.Count;
|
defaultCollection.Index = _collections.Count;
|
||||||
_collections.Add(defaultCollection);
|
_collections.Add(defaultCollection);
|
||||||
}
|
}
|
||||||
|
|
@ -337,7 +340,7 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes)
|
if (changes)
|
||||||
Penumbra.SaveService.ImmediateSave(collection);
|
_saveService.ImmediateSave(collection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,6 +408,7 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
||||||
? "Assignment is redundant due to an identical unowned NPC assignment existing.\nYou can remove it."
|
? "Assignment is redundant due to an identical unowned NPC assignment existing.\nYou can remove it."
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
// The group of all Characters is redundant if they are all equal to Default or unassigned.
|
// The group of all Characters is redundant if they are all equal to Default or unassigned.
|
||||||
case CollectionType.MalePlayerCharacter:
|
case CollectionType.MalePlayerCharacter:
|
||||||
|
|
@ -464,7 +468,8 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (assignment.Index == checkAssignment.Index)
|
if (assignment.Index == checkAssignment.Index)
|
||||||
return $"Assignment is currently redundant due to overwriting {parentType.ToName()} with an identical collection.\nYou can remove it.";
|
return
|
||||||
|
$"Assignment is currently redundant due to overwriting {parentType.ToName()} with an identical collection.\nYou can remove it.";
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,23 @@ using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
public class DuplicateManager
|
public class DuplicateManager
|
||||||
{
|
{
|
||||||
private readonly ModManager _modManager;
|
private readonly SaveService _saveService;
|
||||||
|
private readonly ModManager _modManager;
|
||||||
private readonly SHA256 _hasher = SHA256.Create();
|
private readonly SHA256 _hasher = SHA256.Create();
|
||||||
private readonly ModFileCollection _files;
|
private readonly ModFileCollection _files;
|
||||||
private readonly List<(FullPath[] Paths, long Size, byte[] Hash)> _duplicates = new();
|
private readonly List<(FullPath[] Paths, long Size, byte[] Hash)> _duplicates = new();
|
||||||
|
|
||||||
public DuplicateManager(ModFileCollection files, ModManager modManager)
|
public DuplicateManager(ModFileCollection files, ModManager modManager, SaveService saveService)
|
||||||
{
|
{
|
||||||
_files = files;
|
_files = files;
|
||||||
_modManager = modManager;
|
_modManager = modManager;
|
||||||
|
_saveService = saveService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<(FullPath[] Paths, long Size, byte[] Hash)> Duplicates
|
public IReadOnlyList<(FullPath[] Paths, long Size, byte[] Hash)> Duplicates
|
||||||
|
|
@ -76,16 +79,13 @@ public class DuplicateManager
|
||||||
|
|
||||||
if (useModManager)
|
if (useModManager)
|
||||||
{
|
{
|
||||||
_modManager.OptionSetFiles(mod, groupIdx, optionIdx, dict);
|
_modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var sub = (SubMod)subMod;
|
var sub = (SubMod)subMod;
|
||||||
sub.FileData = dict;
|
sub.FileData = dict;
|
||||||
if (groupIdx == -1)
|
_saveService.ImmediateSave(new ModSaveGroup(mod, groupIdx));
|
||||||
mod.SaveDefaultMod();
|
|
||||||
else
|
|
||||||
IModGroup.Save(mod.Groups[groupIdx], mod.ModPath, groupIdx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ public class ModFileEditor
|
||||||
num += dict.TryAdd(path.Item2, file.File) ? 0 : 1;
|
num += dict.TryAdd(path.Item2, file.File) ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Penumbra.ModManager.OptionSetFiles(mod, option.GroupIdx, option.OptionIdx, dict);
|
_modManager.OptionEditor.OptionSetFiles(mod, option.GroupIdx, option.OptionIdx, dict);
|
||||||
_files.UpdatePaths(mod, option);
|
_files.UpdatePaths(mod, option);
|
||||||
|
|
||||||
return num;
|
return num;
|
||||||
|
|
@ -54,7 +54,7 @@ public class ModFileEditor
|
||||||
var newDict = subMod.Files.Where(kvp => CheckAgainstMissing(mod, subMod, kvp.Value, kvp.Key, subMod == option))
|
var newDict = subMod.Files.Where(kvp => CheckAgainstMissing(mod, subMod, kvp.Value, kvp.Key, subMod == option))
|
||||||
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||||
if (newDict.Count != subMod.Files.Count)
|
if (newDict.Count != subMod.Files.Count)
|
||||||
_modManager.OptionSetFiles(mod, groupIdx, optionIdx, newDict);
|
_modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, newDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
ModEditor.ApplyToAllOptions(mod, HandleSubMod);
|
ModEditor.ApplyToAllOptions(mod, HandleSubMod);
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ public class ModMetaEditor
|
||||||
if (!Changes)
|
if (!Changes)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_modManager.OptionSetManipulations(mod, groupIdx, optionIdx, Recombine().ToHashSet());
|
_modManager.OptionEditor.OptionSetManipulations(mod, groupIdx, optionIdx, Recombine().ToHashSet());
|
||||||
Changes = false;
|
Changes = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Penumbra.Mods;
|
||||||
|
|
||||||
public class ModNormalizer
|
public class ModNormalizer
|
||||||
{
|
{
|
||||||
private readonly ModManager _modManager;
|
private readonly ModManager _modManager;
|
||||||
private readonly List<List<Dictionary<Utf8GamePath, FullPath>>> _redirections = new();
|
private readonly List<List<Dictionary<Utf8GamePath, FullPath>>> _redirections = new();
|
||||||
|
|
||||||
public Mod Mod { get; private set; } = null!;
|
public Mod Mod { get; private set; } = null!;
|
||||||
|
|
@ -280,9 +280,8 @@ public class ModNormalizer
|
||||||
private void ApplyRedirections()
|
private void ApplyRedirections()
|
||||||
{
|
{
|
||||||
foreach (var option in Mod.AllSubMods.OfType<SubMod>())
|
foreach (var option in Mod.AllSubMods.OfType<SubMod>())
|
||||||
{
|
_modManager.OptionEditor.OptionSetFiles(Mod, option.GroupIdx, option.OptionIdx,
|
||||||
_modManager.OptionSetFiles(Mod, option.GroupIdx, option.OptionIdx, _redirections[option.GroupIdx + 1][option.OptionIdx]);
|
_redirections[option.GroupIdx + 1][option.OptionIdx]);
|
||||||
}
|
|
||||||
|
|
||||||
++Step;
|
++Step;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,11 @@ public class ModSwapEditor
|
||||||
|
|
||||||
public void Apply(Mod mod, int groupIdx, int optionIdx)
|
public void Apply(Mod mod, int groupIdx, int optionIdx)
|
||||||
{
|
{
|
||||||
if (Changes)
|
if (!Changes)
|
||||||
{
|
return;
|
||||||
_modManager.OptionSetFileSwaps(mod, groupIdx, optionIdx, _swaps);
|
|
||||||
Changes = false;
|
_modManager.OptionEditor.OptionSetFileSwaps(mod, groupIdx, optionIdx, _swaps);
|
||||||
}
|
Changes = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Changes { get; private set; }
|
public bool Changes { get; private set; }
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public class ItemSwapContainer
|
||||||
NoSwaps,
|
NoSwaps,
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool WriteMod( Mod mod, WriteType writeType = WriteType.NoSwaps, DirectoryInfo? directory = null, int groupIndex = -1, int optionIndex = 0 )
|
public bool WriteMod( ModManager manager, Mod mod, WriteType writeType = WriteType.NoSwaps, DirectoryInfo? directory = null, int groupIndex = -1, int optionIndex = 0 )
|
||||||
{
|
{
|
||||||
var convertedManips = new HashSet< MetaManipulation >( Swaps.Count );
|
var convertedManips = new HashSet< MetaManipulation >( Swaps.Count );
|
||||||
var convertedFiles = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
|
var convertedFiles = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
|
||||||
|
|
@ -82,9 +82,9 @@ public class ItemSwapContainer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Penumbra.ModManager.OptionSetFiles( mod, groupIndex, optionIndex, convertedFiles );
|
manager.OptionEditor.OptionSetFiles( mod, groupIndex, optionIndex, convertedFiles );
|
||||||
Penumbra.ModManager.OptionSetFileSwaps( mod, groupIndex, optionIndex, convertedSwaps );
|
manager.OptionEditor.OptionSetFileSwaps( mod, groupIndex, optionIndex, convertedSwaps );
|
||||||
Penumbra.ModManager.OptionSetManipulations( mod, groupIndex, optionIndex, convertedManips );
|
manager.OptionEditor.OptionSetManipulations( mod, groupIndex, optionIndex, convertedManips );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,76 @@ using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
public sealed partial class ModManager : IReadOnlyList<Mod>
|
public sealed class ModManager2 : IReadOnlyList<Mod>, IDisposable
|
||||||
|
{
|
||||||
|
public readonly ModDataEditor DataEditor;
|
||||||
|
public readonly ModOptionEditor OptionEditor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An easily accessible set of new mods.
|
||||||
|
/// Mods are added when they are created or imported.
|
||||||
|
/// Mods are removed when they are deleted or when they are toggled in any collection.
|
||||||
|
/// Also gets cleared on mod rediscovery.
|
||||||
|
/// </summary>
|
||||||
|
public readonly HashSet<Mod> NewMods = new();
|
||||||
|
|
||||||
|
public Mod this[int idx]
|
||||||
|
=> _mods[idx];
|
||||||
|
|
||||||
|
public Mod this[Index idx]
|
||||||
|
=> _mods[idx];
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
=> _mods.Count;
|
||||||
|
|
||||||
|
public IEnumerator<Mod> GetEnumerator()
|
||||||
|
=> _mods.GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
=> GetEnumerator();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to obtain a mod by its directory name (unique identifier, preferred),
|
||||||
|
/// or the first mod of the given name if no directory fits.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetMod(string identifier, string modName, [NotNullWhen(true)] out Mod? mod)
|
||||||
|
{
|
||||||
|
mod = null;
|
||||||
|
foreach (var m in _mods)
|
||||||
|
{
|
||||||
|
if (string.Equals(m.Identifier, identifier, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mod = m;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.Name == modName)
|
||||||
|
mod ??= m;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mod != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> The actual list of mods. </summary>
|
||||||
|
private readonly List<Mod> _mods = new();
|
||||||
|
|
||||||
|
public ModManager2(ModDataEditor dataEditor, ModOptionEditor optionEditor)
|
||||||
|
{
|
||||||
|
DataEditor = dataEditor;
|
||||||
|
OptionEditor = optionEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed partial class ModManager : IReadOnlyList<Mod>, IDisposable
|
||||||
{
|
{
|
||||||
// Set when reading Config and migrating from v4 to v5.
|
// Set when reading Config and migrating from v4 to v5.
|
||||||
public static bool MigrateModBackups = false;
|
public static bool MigrateModBackups = false;
|
||||||
|
|
@ -38,21 +102,29 @@ public sealed partial class ModManager : IReadOnlyList<Mod>
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly CommunicatorService _communicator;
|
private readonly CommunicatorService _communicator;
|
||||||
public readonly ModDataEditor DataEditor;
|
public readonly ModDataEditor DataEditor;
|
||||||
|
public readonly ModOptionEditor OptionEditor;
|
||||||
|
|
||||||
public ModManager(StartTracker time, Configuration config, CommunicatorService communicator, ModDataEditor dataEditor)
|
public ModManager(StartTracker time, Configuration config, CommunicatorService communicator, ModDataEditor dataEditor,
|
||||||
|
ModOptionEditor optionEditor)
|
||||||
{
|
{
|
||||||
using var timer = time.Measure(StartTimeType.Mods);
|
using var timer = time.Measure(StartTimeType.Mods);
|
||||||
_config = config;
|
_config = config;
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
DataEditor = dataEditor;
|
DataEditor = dataEditor;
|
||||||
|
OptionEditor = optionEditor;
|
||||||
ModDirectoryChanged += OnModDirectoryChange;
|
ModDirectoryChanged += OnModDirectoryChange;
|
||||||
SetBaseDirectory(config.ModDirectory, true);
|
SetBaseDirectory(config.ModDirectory, true);
|
||||||
UpdateExportDirectory(_config.ExportDirectory, false);
|
UpdateExportDirectory(_config.ExportDirectory, false);
|
||||||
ModOptionChanged += OnModOptionChange;
|
_communicator.ModOptionChanged.Event += OnModOptionChange;
|
||||||
ModPathChanged += OnModPathChange;
|
ModPathChanged += OnModPathChange;
|
||||||
DiscoverMods();
|
DiscoverMods();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_communicator.ModOptionChanged.Event -= OnModOptionChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Try to obtain a mod by its directory name (unique identifier, preferred),
|
// Try to obtain a mod by its directory name (unique identifier, preferred),
|
||||||
// or the first mod of the given name if no directory fits.
|
// or the first mod of the given name if no directory fits.
|
||||||
|
|
@ -73,4 +145,37 @@ public sealed partial class ModManager : IReadOnlyList<Mod>
|
||||||
|
|
||||||
return mod != null;
|
return mod != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OnModOptionChange(ModOptionChangeType type, Mod mod, int groupIdx, int _, int _2)
|
||||||
|
{
|
||||||
|
if (type == ModOptionChangeType.PrepareChange)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool ComputeChangedItems()
|
||||||
|
{
|
||||||
|
mod.ComputeChangedItems();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// State can not change on adding groups, as they have no immediate options.
|
||||||
|
var unused = type switch
|
||||||
|
{
|
||||||
|
ModOptionChangeType.GroupAdded => ComputeChangedItems() & mod.SetCounts(),
|
||||||
|
ModOptionChangeType.GroupDeleted => ComputeChangedItems() & mod.SetCounts(),
|
||||||
|
ModOptionChangeType.GroupMoved => false,
|
||||||
|
ModOptionChangeType.GroupTypeChanged => mod.HasOptions = mod.Groups.Any(o => o.IsOption),
|
||||||
|
ModOptionChangeType.PriorityChanged => false,
|
||||||
|
ModOptionChangeType.OptionAdded => ComputeChangedItems() & mod.SetCounts(),
|
||||||
|
ModOptionChangeType.OptionDeleted => ComputeChangedItems() & mod.SetCounts(),
|
||||||
|
ModOptionChangeType.OptionMoved => false,
|
||||||
|
ModOptionChangeType.OptionFilesChanged => ComputeChangedItems()
|
||||||
|
& (0 < (mod.TotalFileCount = mod.AllSubMods.Sum(s => s.Files.Count))),
|
||||||
|
ModOptionChangeType.OptionSwapsChanged => ComputeChangedItems()
|
||||||
|
& (0 < (mod.TotalSwapCount = mod.AllSubMods.Sum(s => s.FileSwaps.Count))),
|
||||||
|
ModOptionChangeType.OptionMetaChanged => ComputeChangedItems()
|
||||||
|
& (0 < (mod.TotalManipulations = mod.AllSubMods.Sum(s => s.Manipulations.Count))),
|
||||||
|
ModOptionChangeType.DisplayChange => false,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,377 +1,386 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.Services;
|
||||||
using Penumbra.Util;
|
using Penumbra.String.Classes;
|
||||||
|
using Penumbra.Util;
|
||||||
namespace Penumbra.Mods;
|
|
||||||
|
namespace Penumbra.Mods;
|
||||||
public sealed partial class ModManager
|
|
||||||
{
|
|
||||||
public delegate void ModOptionChangeDelegate(ModOptionChangeType type, Mod mod, int groupIdx, int optionIdx, int movedToIdx);
|
|
||||||
public event ModOptionChangeDelegate ModOptionChanged;
|
public class ModOptionEditor
|
||||||
|
{
|
||||||
public void ChangeModGroupType(Mod mod, int groupIdx, GroupType type)
|
private readonly CommunicatorService _communicator;
|
||||||
{
|
private readonly FilenameService _filenames;
|
||||||
var group = mod._groups[groupIdx];
|
private readonly SaveService _saveService;
|
||||||
if (group.Type == type)
|
|
||||||
return;
|
public ModOptionEditor(CommunicatorService communicator, SaveService saveService, FilenameService filenames)
|
||||||
|
{
|
||||||
mod._groups[groupIdx] = group.Convert(type);
|
_communicator = communicator;
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.GroupTypeChanged, mod, groupIdx, -1, -1);
|
_saveService = saveService;
|
||||||
}
|
_filenames = filenames;
|
||||||
|
}
|
||||||
public void ChangeModGroupDefaultOption(Mod mod, int groupIdx, uint defaultOption)
|
|
||||||
{
|
/// <summary> Change the type of a group given by mod and index to type, if possible. </summary>
|
||||||
var group = mod._groups[groupIdx];
|
public void ChangeModGroupType(Mod mod, int groupIdx, GroupType type)
|
||||||
if (group.DefaultSettings == defaultOption)
|
{
|
||||||
return;
|
var group = mod._groups[groupIdx];
|
||||||
|
if (group.Type == type)
|
||||||
group.DefaultSettings = defaultOption;
|
return;
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.DefaultOptionChanged, mod, groupIdx, -1, -1);
|
|
||||||
}
|
mod._groups[groupIdx] = group.Convert(type);
|
||||||
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
public void RenameModGroup(Mod mod, int groupIdx, string newName)
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupTypeChanged, mod, groupIdx, -1, -1);
|
||||||
{
|
}
|
||||||
var group = mod._groups[groupIdx];
|
|
||||||
var oldName = group.Name;
|
/// <summary> Change the settings stored as default options in a mod.</summary>
|
||||||
if (oldName == newName || !VerifyFileName(mod, group, newName, true))
|
public void ChangeModGroupDefaultOption(Mod mod, int groupIdx, uint defaultOption)
|
||||||
return;
|
{
|
||||||
|
var group = mod._groups[groupIdx];
|
||||||
group.DeleteFile(mod.ModPath, groupIdx);
|
if (group.DefaultSettings == defaultOption)
|
||||||
|
return;
|
||||||
var _ = group switch
|
|
||||||
{
|
group.DefaultSettings = defaultOption;
|
||||||
SingleModGroup s => s.Name = newName,
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
MultiModGroup m => m.Name = newName,
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DefaultOptionChanged, mod, groupIdx, -1, -1);
|
||||||
_ => newName,
|
}
|
||||||
};
|
|
||||||
|
/// <summary> Rename an option group if possible. </summary>
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.GroupRenamed, mod, groupIdx, -1, -1);
|
public void RenameModGroup(Mod mod, int groupIdx, string newName)
|
||||||
}
|
{
|
||||||
|
var group = mod._groups[groupIdx];
|
||||||
public void AddModGroup(Mod mod, GroupType type, string newName)
|
var oldName = group.Name;
|
||||||
{
|
if (oldName == newName || !VerifyFileName(mod, group, newName, true))
|
||||||
if (!VerifyFileName(mod, null, newName, true))
|
return;
|
||||||
return;
|
|
||||||
|
_saveService.ImmediateDelete(new ModSaveGroup(mod, groupIdx));
|
||||||
var maxPriority = mod._groups.Count == 0 ? 0 : mod._groups.Max(o => o.Priority) + 1;
|
var _ = group switch
|
||||||
|
{
|
||||||
mod._groups.Add(type == GroupType.Multi
|
SingleModGroup s => s.Name = newName,
|
||||||
? new MultiModGroup
|
MultiModGroup m => m.Name = newName,
|
||||||
{
|
_ => newName,
|
||||||
Name = newName,
|
};
|
||||||
Priority = maxPriority,
|
|
||||||
}
|
_saveService.ImmediateSave(new ModSaveGroup(mod, groupIdx));
|
||||||
: new SingleModGroup
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupRenamed, mod, groupIdx, -1, -1);
|
||||||
{
|
}
|
||||||
Name = newName,
|
|
||||||
Priority = maxPriority,
|
/// <summary> Add a new mod, empty option group of the given type and name. </summary>
|
||||||
});
|
public void AddModGroup(Mod mod, GroupType type, string newName)
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, mod._groups.Count - 1, -1, -1);
|
{
|
||||||
}
|
if (!VerifyFileName(mod, null, newName, true))
|
||||||
|
return;
|
||||||
public void DeleteModGroup(Mod mod, int groupIdx)
|
|
||||||
{
|
var maxPriority = mod._groups.Count == 0 ? 0 : mod._groups.Max(o => o.Priority) + 1;
|
||||||
var group = mod._groups[groupIdx];
|
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, -1, -1);
|
mod._groups.Add(type == GroupType.Multi
|
||||||
mod._groups.RemoveAt(groupIdx);
|
? new MultiModGroup
|
||||||
UpdateSubModPositions(mod, groupIdx);
|
{
|
||||||
group.DeleteFile(mod.ModPath, groupIdx);
|
Name = newName,
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, groupIdx, -1, -1);
|
Priority = maxPriority,
|
||||||
}
|
}
|
||||||
|
: new SingleModGroup
|
||||||
public void MoveModGroup(Mod mod, int groupIdxFrom, int groupIdxTo)
|
{
|
||||||
{
|
Name = newName,
|
||||||
if (mod._groups.Move(groupIdxFrom, groupIdxTo))
|
Priority = maxPriority,
|
||||||
{
|
});
|
||||||
UpdateSubModPositions(mod, Math.Min(groupIdxFrom, groupIdxTo));
|
_saveService.ImmediateSave(new ModSaveGroup(mod, mod._groups.Count - 1));
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.GroupMoved, mod, groupIdxFrom, -1, groupIdxTo);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, mod._groups.Count - 1, -1, -1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary> Delete a given option group. Fires an event to prepare before actually deleting. </summary>
|
||||||
private static void UpdateSubModPositions(Mod mod, int fromGroup)
|
public void DeleteModGroup(Mod mod, int groupIdx)
|
||||||
{
|
{
|
||||||
foreach (var (group, groupIdx) in mod._groups.WithIndex().Skip(fromGroup))
|
var group = mod._groups[groupIdx];
|
||||||
{
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, -1, -1);
|
||||||
foreach (var (o, optionIdx) in group.OfType<SubMod>().WithIndex())
|
mod._groups.RemoveAt(groupIdx);
|
||||||
o.SetPosition(groupIdx, optionIdx);
|
UpdateSubModPositions(mod, groupIdx);
|
||||||
}
|
_saveService.SaveAllOptionGroups(mod);
|
||||||
}
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, groupIdx, -1, -1);
|
||||||
|
}
|
||||||
public void ChangeGroupDescription(Mod mod, int groupIdx, string newDescription)
|
|
||||||
{
|
/// <summary> Move the index of a given option group. </summary>
|
||||||
var group = mod._groups[groupIdx];
|
public void MoveModGroup(Mod mod, int groupIdxFrom, int groupIdxTo)
|
||||||
if (group.Description == newDescription)
|
{
|
||||||
return;
|
if (!mod._groups.Move(groupIdxFrom, groupIdxTo))
|
||||||
|
return;
|
||||||
var _ = group switch
|
|
||||||
{
|
UpdateSubModPositions(mod, Math.Min(groupIdxFrom, groupIdxTo));
|
||||||
SingleModGroup s => s.Description = newDescription,
|
_saveService.SaveAllOptionGroups(mod);
|
||||||
MultiModGroup m => m.Description = newDescription,
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupMoved, mod, groupIdxFrom, -1, groupIdxTo);
|
||||||
_ => newDescription,
|
}
|
||||||
};
|
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, -1, -1);
|
/// <summary> Change the description of the given option group. </summary>
|
||||||
}
|
public void ChangeGroupDescription(Mod mod, int groupIdx, string newDescription)
|
||||||
|
{
|
||||||
public void ChangeOptionDescription(Mod mod, int groupIdx, int optionIdx, string newDescription)
|
var group = mod._groups[groupIdx];
|
||||||
{
|
if (group.Description == newDescription)
|
||||||
var group = mod._groups[groupIdx];
|
return;
|
||||||
var option = group[optionIdx];
|
|
||||||
if (option.Description == newDescription || option is not SubMod s)
|
var _ = group switch
|
||||||
return;
|
{
|
||||||
|
SingleModGroup s => s.Description = newDescription,
|
||||||
s.Description = newDescription;
|
MultiModGroup m => m.Description = newDescription,
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
_ => newDescription,
|
||||||
}
|
};
|
||||||
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
public void ChangeGroupPriority(Mod mod, int groupIdx, int newPriority)
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, -1, -1);
|
||||||
{
|
}
|
||||||
var group = mod._groups[groupIdx];
|
|
||||||
if (group.Priority == newPriority)
|
/// <summary> Change the description of the given option. </summary>
|
||||||
return;
|
public void ChangeOptionDescription(Mod mod, int groupIdx, int optionIdx, string newDescription)
|
||||||
|
{
|
||||||
var _ = group switch
|
var group = mod._groups[groupIdx];
|
||||||
{
|
var option = group[optionIdx];
|
||||||
SingleModGroup s => s.Priority = newPriority,
|
if (option.Description == newDescription || option is not SubMod s)
|
||||||
MultiModGroup m => m.Priority = newPriority,
|
return;
|
||||||
_ => newPriority,
|
|
||||||
};
|
s.Description = newDescription;
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, -1, -1);
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
}
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
||||||
|
}
|
||||||
public void ChangeOptionPriority(Mod mod, int groupIdx, int optionIdx, int newPriority)
|
|
||||||
{
|
/// <summary> Change the internal priority of the given option group. </summary>
|
||||||
switch (mod._groups[groupIdx])
|
public void ChangeGroupPriority(Mod mod, int groupIdx, int newPriority)
|
||||||
{
|
{
|
||||||
case SingleModGroup:
|
var group = mod._groups[groupIdx];
|
||||||
ChangeGroupPriority(mod, groupIdx, newPriority);
|
if (group.Priority == newPriority)
|
||||||
break;
|
return;
|
||||||
case MultiModGroup m:
|
|
||||||
if (m.PrioritizedOptions[optionIdx].Priority == newPriority)
|
var _ = group switch
|
||||||
return;
|
{
|
||||||
|
SingleModGroup s => s.Priority = newPriority,
|
||||||
m.PrioritizedOptions[optionIdx] = (m.PrioritizedOptions[optionIdx].Mod, newPriority);
|
MultiModGroup m => m.Priority = newPriority,
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, optionIdx, -1);
|
_ => newPriority,
|
||||||
return;
|
};
|
||||||
}
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
}
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, -1, -1);
|
||||||
|
}
|
||||||
public void RenameOption(Mod mod, int groupIdx, int optionIdx, string newName)
|
|
||||||
{
|
/// <summary> Change the internal priority of the given option. </summary>
|
||||||
switch (mod._groups[groupIdx])
|
public void ChangeOptionPriority(Mod mod, int groupIdx, int optionIdx, int newPriority)
|
||||||
{
|
{
|
||||||
case SingleModGroup s:
|
switch (mod._groups[groupIdx])
|
||||||
if (s.OptionData[optionIdx].Name == newName)
|
{
|
||||||
return;
|
case SingleModGroup:
|
||||||
|
ChangeGroupPriority(mod, groupIdx, newPriority);
|
||||||
s.OptionData[optionIdx].Name = newName;
|
break;
|
||||||
break;
|
case MultiModGroup m:
|
||||||
case MultiModGroup m:
|
if (m.PrioritizedOptions[optionIdx].Priority == newPriority)
|
||||||
var option = m.PrioritizedOptions[optionIdx].Mod;
|
return;
|
||||||
if (option.Name == newName)
|
|
||||||
return;
|
m.PrioritizedOptions[optionIdx] = (m.PrioritizedOptions[optionIdx].Mod, newPriority);
|
||||||
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
option.Name = newName;
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, optionIdx, -1);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
|
||||||
}
|
/// <summary> Rename the given option. </summary>
|
||||||
|
public void RenameOption(Mod mod, int groupIdx, int optionIdx, string newName)
|
||||||
public void AddOption(Mod mod, int groupIdx, string newName)
|
{
|
||||||
{
|
switch (mod._groups[groupIdx])
|
||||||
var group = mod._groups[groupIdx];
|
{
|
||||||
var subMod = new SubMod(mod) { Name = newName };
|
case SingleModGroup s:
|
||||||
subMod.SetPosition(groupIdx, group.Count);
|
if (s.OptionData[optionIdx].Name == newName)
|
||||||
switch (group)
|
return;
|
||||||
{
|
|
||||||
case SingleModGroup s:
|
s.OptionData[optionIdx].Name = newName;
|
||||||
s.OptionData.Add(subMod);
|
break;
|
||||||
break;
|
case MultiModGroup m:
|
||||||
case MultiModGroup m:
|
var option = m.PrioritizedOptions[optionIdx].Mod;
|
||||||
m.PrioritizedOptions.Add((subMod, 0));
|
if (option.Name == newName)
|
||||||
break;
|
return;
|
||||||
}
|
|
||||||
|
option.Name = newName;
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddOption(Mod mod, int groupIdx, ISubMod option, int priority = 0)
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
{
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
||||||
if (option is not SubMod o)
|
}
|
||||||
return;
|
|
||||||
|
/// <summary> Add a new empty option of the given name for the given group. </summary>
|
||||||
var group = mod._groups[groupIdx];
|
public void AddOption(Mod mod, int groupIdx, string newName)
|
||||||
if (group.Count > 63)
|
{
|
||||||
{
|
var group = mod._groups[groupIdx];
|
||||||
Penumbra.Log.Error(
|
var subMod = new SubMod(mod) { Name = newName };
|
||||||
$"Could not add option {option.Name} to {group.Name} for mod {mod.Name}, "
|
subMod.SetPosition(groupIdx, group.Count);
|
||||||
+ "since only up to 64 options are supported in one group.");
|
switch (group)
|
||||||
return;
|
{
|
||||||
}
|
case SingleModGroup s:
|
||||||
|
s.OptionData.Add(subMod);
|
||||||
o.SetPosition(groupIdx, group.Count);
|
break;
|
||||||
|
case MultiModGroup m:
|
||||||
switch (group)
|
m.PrioritizedOptions.Add((subMod, 0));
|
||||||
{
|
break;
|
||||||
case SingleModGroup s:
|
}
|
||||||
s.OptionData.Add(o);
|
|
||||||
break;
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
case MultiModGroup m:
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
||||||
m.PrioritizedOptions.Add((o, priority));
|
}
|
||||||
break;
|
|
||||||
}
|
/// <summary> Add an existing option to a given group with a given priority. </summary>
|
||||||
|
public void AddOption(Mod mod, int groupIdx, ISubMod option, int priority = 0)
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
{
|
||||||
}
|
if (option is not SubMod o)
|
||||||
|
return;
|
||||||
public void DeleteOption(Mod mod, int groupIdx, int optionIdx)
|
|
||||||
{
|
var group = mod._groups[groupIdx];
|
||||||
var group = mod._groups[groupIdx];
|
if (group.Type is GroupType.Multi && group.Count >= IModGroup.MaxMultiOptions)
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
{
|
||||||
switch (group)
|
Penumbra.Log.Error(
|
||||||
{
|
$"Could not add option {option.Name} to {group.Name} for mod {mod.Name}, "
|
||||||
case SingleModGroup s:
|
+ $"since only up to {IModGroup.MaxMultiOptions} options are supported in one group.");
|
||||||
s.OptionData.RemoveAt(optionIdx);
|
return;
|
||||||
|
}
|
||||||
break;
|
|
||||||
case MultiModGroup m:
|
o.SetPosition(groupIdx, group.Count);
|
||||||
m.PrioritizedOptions.RemoveAt(optionIdx);
|
|
||||||
break;
|
switch (group)
|
||||||
}
|
{
|
||||||
|
case SingleModGroup s:
|
||||||
group.UpdatePositions(optionIdx);
|
s.OptionData.Add(o);
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionDeleted, mod, groupIdx, optionIdx, -1);
|
break;
|
||||||
}
|
case MultiModGroup m:
|
||||||
|
m.PrioritizedOptions.Add((o, priority));
|
||||||
public void MoveOption(Mod mod, int groupIdx, int optionIdxFrom, int optionIdxTo)
|
break;
|
||||||
{
|
}
|
||||||
var group = mod._groups[groupIdx];
|
|
||||||
if (group.MoveOption(optionIdxFrom, optionIdxTo))
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionMoved, mod, groupIdx, optionIdxFrom, optionIdxTo);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OptionSetManipulations(Mod mod, int groupIdx, int optionIdx, HashSet<MetaManipulation> manipulations)
|
/// <summary> Delete the given option from the given group. </summary>
|
||||||
{
|
public void DeleteOption(Mod mod, int groupIdx, int optionIdx)
|
||||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
{
|
||||||
if (subMod.Manipulations.Count == manipulations.Count
|
var group = mod._groups[groupIdx];
|
||||||
&& subMod.Manipulations.All(m => manipulations.TryGetValue(m, out var old) && old.EntryEquals(m)))
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||||
return;
|
switch (group)
|
||||||
|
{
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
case SingleModGroup s:
|
||||||
subMod.ManipulationData = manipulations;
|
s.OptionData.RemoveAt(optionIdx);
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, mod, groupIdx, optionIdx, -1);
|
|
||||||
}
|
break;
|
||||||
|
case MultiModGroup m:
|
||||||
public void OptionSetFiles(Mod mod, int groupIdx, int optionIdx, Dictionary<Utf8GamePath, FullPath> replacements)
|
m.PrioritizedOptions.RemoveAt(optionIdx);
|
||||||
{
|
break;
|
||||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
}
|
||||||
if (subMod.FileData.SetEquals(replacements))
|
|
||||||
return;
|
group.UpdatePositions(optionIdx);
|
||||||
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionDeleted, mod, groupIdx, optionIdx, -1);
|
||||||
subMod.FileData = replacements;
|
}
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, mod, groupIdx, optionIdx, -1);
|
|
||||||
}
|
/// <summary> Move an option inside the given option group. </summary>
|
||||||
|
public void MoveOption(Mod mod, int groupIdx, int optionIdxFrom, int optionIdxTo)
|
||||||
public void OptionAddFiles(Mod mod, int groupIdx, int optionIdx, Dictionary<Utf8GamePath, FullPath> additions)
|
{
|
||||||
{
|
var group = mod._groups[groupIdx];
|
||||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
if (!group.MoveOption(optionIdxFrom, optionIdxTo))
|
||||||
var oldCount = subMod.FileData.Count;
|
return;
|
||||||
subMod.FileData.AddFrom(additions);
|
|
||||||
if (oldCount != subMod.FileData.Count)
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesAdded, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMoved, mod, groupIdx, optionIdxFrom, optionIdxTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OptionSetFileSwaps(Mod mod, int groupIdx, int optionIdx, Dictionary<Utf8GamePath, FullPath> swaps)
|
/// <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)
|
||||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
{
|
||||||
if (subMod.FileSwapData.SetEquals(swaps))
|
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||||
return;
|
if (subMod.Manipulations.Count == manipulations.Count
|
||||||
|
&& subMod.Manipulations.All(m => manipulations.TryGetValue(m, out var old) && old.EntryEquals(m)))
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
return;
|
||||||
subMod.FileSwapData = swaps;
|
|
||||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||||
}
|
subMod.ManipulationData = manipulations;
|
||||||
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
public bool VerifyFileName(Mod mod, IModGroup? group, string newName, bool message)
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, mod, groupIdx, optionIdx, -1);
|
||||||
{
|
}
|
||||||
var path = newName.RemoveInvalidPathSymbols();
|
|
||||||
if (path.Length != 0
|
/// <summary> Set the file redirections for a given option. Replaces existing redirections. </summary>
|
||||||
&& !mod.Groups.Any(o => !ReferenceEquals(o, group)
|
public void OptionSetFiles(Mod mod, int groupIdx, int optionIdx, Dictionary<Utf8GamePath, FullPath> replacements)
|
||||||
&& string.Equals(o.Name.RemoveInvalidPathSymbols(), path, StringComparison.OrdinalIgnoreCase)))
|
{
|
||||||
return true;
|
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||||
|
if (subMod.FileData.SetEquals(replacements))
|
||||||
if (message)
|
return;
|
||||||
Penumbra.ChatService.NotificationMessage(
|
|
||||||
$"Could not name option {newName} because option with same filename {path} already exists.",
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||||
"Warning", NotificationType.Warning);
|
subMod.FileData = replacements;
|
||||||
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
return false;
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, mod, groupIdx, optionIdx, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SubMod GetSubMod(Mod mod, int groupIdx, int optionIdx)
|
/// <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, Dictionary<Utf8GamePath, FullPath> additions)
|
||||||
if (groupIdx == -1 && optionIdx == 0)
|
{
|
||||||
return mod._default;
|
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||||
|
var oldCount = subMod.FileData.Count;
|
||||||
return mod._groups[groupIdx] switch
|
subMod.FileData.AddFrom(additions);
|
||||||
{
|
if (oldCount != subMod.FileData.Count)
|
||||||
SingleModGroup s => s.OptionData[optionIdx],
|
{
|
||||||
MultiModGroup m => m.PrioritizedOptions[optionIdx].Mod,
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
_ => throw new InvalidOperationException(),
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesAdded, mod, groupIdx, optionIdx, -1);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnModOptionChange(ModOptionChangeType type, Mod mod, int groupIdx, int _, int _2)
|
/// <summary> Set the file swaps for a given option. Replaces existing swaps. </summary>
|
||||||
{
|
public void OptionSetFileSwaps(Mod mod, int groupIdx, int optionIdx, Dictionary<Utf8GamePath, FullPath> swaps)
|
||||||
if (type == ModOptionChangeType.PrepareChange)
|
{
|
||||||
return;
|
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||||
|
if (subMod.FileSwapData.SetEquals(swaps))
|
||||||
// File deletion is handled in the actual function.
|
return;
|
||||||
if (type is ModOptionChangeType.GroupDeleted or ModOptionChangeType.GroupMoved)
|
|
||||||
{
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||||
mod.SaveAllGroups();
|
subMod.FileSwapData = swaps;
|
||||||
}
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||||
else
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, mod, groupIdx, optionIdx, -1);
|
||||||
{
|
}
|
||||||
if (groupIdx == -1)
|
|
||||||
mod.SaveDefaultModDelayed();
|
|
||||||
else
|
/// <summary> Verify that a new option group name is unique in this mod. </summary>
|
||||||
IModGroup.SaveDelayed(mod._groups[groupIdx], mod.ModPath, groupIdx);
|
public static bool VerifyFileName(Mod mod, IModGroup? group, string newName, bool message)
|
||||||
}
|
{
|
||||||
|
var path = newName.RemoveInvalidPathSymbols();
|
||||||
bool ComputeChangedItems()
|
if (path.Length != 0
|
||||||
{
|
&& !mod.Groups.Any(o => !ReferenceEquals(o, group)
|
||||||
mod.ComputeChangedItems();
|
&& string.Equals(o.Name.RemoveInvalidPathSymbols(), path, StringComparison.OrdinalIgnoreCase)))
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
if (message)
|
||||||
// State can not change on adding groups, as they have no immediate options.
|
Penumbra.ChatService.NotificationMessage(
|
||||||
var unused = type switch
|
$"Could not name option {newName} because option with same filename {path} already exists.",
|
||||||
{
|
"Warning", NotificationType.Warning);
|
||||||
ModOptionChangeType.GroupAdded => ComputeChangedItems() & mod.SetCounts(),
|
|
||||||
ModOptionChangeType.GroupDeleted => ComputeChangedItems() & mod.SetCounts(),
|
return false;
|
||||||
ModOptionChangeType.GroupMoved => false,
|
}
|
||||||
ModOptionChangeType.GroupTypeChanged => mod.HasOptions = mod.Groups.Any(o => o.IsOption),
|
|
||||||
ModOptionChangeType.PriorityChanged => false,
|
/// <summary> Update the indices stored in options from a given group on. </summary>
|
||||||
ModOptionChangeType.OptionAdded => ComputeChangedItems() & mod.SetCounts(),
|
private static void UpdateSubModPositions(Mod mod, int fromGroup)
|
||||||
ModOptionChangeType.OptionDeleted => ComputeChangedItems() & mod.SetCounts(),
|
{
|
||||||
ModOptionChangeType.OptionMoved => false,
|
foreach (var (group, groupIdx) in mod._groups.WithIndex().Skip(fromGroup))
|
||||||
ModOptionChangeType.OptionFilesChanged => ComputeChangedItems()
|
{
|
||||||
& (0 < (mod.TotalFileCount = mod.AllSubMods.Sum(s => s.Files.Count))),
|
foreach (var (o, optionIdx) in group.OfType<SubMod>().WithIndex())
|
||||||
ModOptionChangeType.OptionSwapsChanged => ComputeChangedItems()
|
o.SetPosition(groupIdx, optionIdx);
|
||||||
& (0 < (mod.TotalSwapCount = mod.AllSubMods.Sum(s => s.FileSwaps.Count))),
|
}
|
||||||
ModOptionChangeType.OptionMetaChanged => ComputeChangedItems()
|
}
|
||||||
& (0 < (mod.TotalManipulations = mod.AllSubMods.Sum(s => s.Manipulations.Count))),
|
|
||||||
ModOptionChangeType.DisplayChange => false,
|
/// <summary> Get the correct option for the given group and option index. </summary>
|
||||||
_ => false,
|
private static SubMod GetSubMod(Mod mod, int groupIdx, int optionIdx)
|
||||||
};
|
{
|
||||||
}
|
if (groupIdx == -1 && optionIdx == 0)
|
||||||
}
|
return mod._default;
|
||||||
|
|
||||||
|
return mod._groups[groupIdx] switch
|
||||||
|
{
|
||||||
|
SingleModGroup s => s.OptionData[optionIdx],
|
||||||
|
MultiModGroup m => m.PrioritizedOptions[optionIdx].Mod,
|
||||||
|
_ => throw new InvalidOperationException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -101,7 +101,7 @@ public partial class Mod
|
||||||
|
|
||||||
if( changes )
|
if( changes )
|
||||||
{
|
{
|
||||||
SaveAllGroups();
|
Penumbra.SaveService.SaveAllOptionGroups(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,8 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Dalamud.Utility;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui.Classes;
|
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Import.Structs;
|
using Penumbra.Import.Structs;
|
||||||
|
|
@ -79,8 +77,8 @@ public partial class Mod
|
||||||
Priority = priority,
|
Priority = priority,
|
||||||
DefaultSettings = defaultSettings,
|
DefaultSettings = defaultSettings,
|
||||||
};
|
};
|
||||||
group.PrioritizedOptions.AddRange( subMods.OfType< SubMod >().Select( ( s, idx ) => ( s, idx ) ) );
|
group.PrioritizedOptions.AddRange( subMods.OfType< SubMod >().Select( ( s, idx ) => ( s, idx ) ) );
|
||||||
IModGroup.Save( group, baseFolder, index );
|
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupType.Single:
|
case GroupType.Single:
|
||||||
|
|
@ -93,7 +91,7 @@ public partial class Mod
|
||||||
DefaultSettings = defaultSettings,
|
DefaultSettings = defaultSettings,
|
||||||
};
|
};
|
||||||
group.OptionData.AddRange( subMods.OfType< SubMod >() );
|
group.OptionData.AddRange( subMods.OfType< SubMod >() );
|
||||||
IModGroup.Save( group, baseFolder, index );
|
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ public partial class Mod
|
||||||
var group = LoadModGroup( this, file, _groups.Count );
|
var group = LoadModGroup( this, file, _groups.Count );
|
||||||
if( group != null && _groups.All( g => g.Name != group.Name ) )
|
if( group != null && _groups.All( g => g.Name != group.Name ) )
|
||||||
{
|
{
|
||||||
changes = changes || group.FileName( ModPath, _groups.Count ) != file.FullName;
|
changes = changes || Penumbra.Filenames.OptionGroupFile(ModPath.FullName, Groups.Count, group.Name) != file.FullName;
|
||||||
_groups.Add( group );
|
_groups.Add( group );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -114,32 +114,7 @@ public partial class Mod
|
||||||
|
|
||||||
if( changes )
|
if( changes )
|
||||||
{
|
{
|
||||||
SaveAllGroups();
|
Penumbra.SaveService.SaveAllOptionGroups(this);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all existing group files and save them anew.
|
|
||||||
// Used when indices change in complex ways.
|
|
||||||
internal void SaveAllGroups()
|
|
||||||
{
|
|
||||||
foreach( var file in GroupFiles )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if( file.Exists )
|
|
||||||
{
|
|
||||||
file.Delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
Penumbra.Log.Error( $"Could not delete outdated group file {file}:\n{e}" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach( var (group, index) in _groups.WithIndex() )
|
|
||||||
{
|
|
||||||
IModGroup.Save( group, ModPath, index );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,8 +85,8 @@ public sealed partial class Mod
|
||||||
mod._default.FileSwapData.Add(gamePath, swapPath);
|
mod._default.FileSwapData.Add(gamePath, swapPath);
|
||||||
|
|
||||||
mod._default.IncorporateMetaChanges(mod.ModPath, true);
|
mod._default.IncorporateMetaChanges(mod.ModPath, true);
|
||||||
foreach (var (group, index) in mod.Groups.WithIndex())
|
foreach (var (_, index) in mod.Groups.WithIndex())
|
||||||
IModGroup.Save(group, mod.ModPath, index);
|
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(mod, index));
|
||||||
|
|
||||||
// Delete meta files.
|
// Delete meta files.
|
||||||
foreach (var file in seenMetaFiles.Where(f => f.Exists))
|
foreach (var file in seenMetaFiles.Where(f => f.Exists))
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,25 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OtterGui.Filesystem;
|
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.Services;
|
||||||
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
public interface IModGroup : IEnumerable< ISubMod >
|
public interface IModGroup : IEnumerable<ISubMod>
|
||||||
{
|
{
|
||||||
public const int MaxMultiOptions = 32;
|
public const int MaxMultiOptions = 32;
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public string Description { get; }
|
public string Description { get; }
|
||||||
public GroupType Type { get; }
|
public GroupType Type { get; }
|
||||||
public int Priority { get; }
|
public int Priority { get; }
|
||||||
public uint DefaultSettings { get; set; }
|
public uint DefaultSettings { get; set; }
|
||||||
|
|
||||||
public int OptionPriority( Index optionIdx );
|
public int OptionPriority(Index optionIdx);
|
||||||
|
|
||||||
public ISubMod this[ Index idx ] { get; }
|
public ISubMod this[Index idx] { get; }
|
||||||
|
|
||||||
public int Count { get; }
|
public int Count { get; }
|
||||||
|
|
||||||
|
|
@ -28,72 +29,76 @@ public interface IModGroup : IEnumerable< ISubMod >
|
||||||
{
|
{
|
||||||
GroupType.Single => Count > 1,
|
GroupType.Single => Count > 1,
|
||||||
GroupType.Multi => Count > 0,
|
GroupType.Multi => Count > 0,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
public string FileName( DirectoryInfo basePath, int groupIdx )
|
public IModGroup Convert(GroupType type);
|
||||||
=> Path.Combine( basePath.FullName, $"group_{groupIdx + 1:D3}_{Name.RemoveInvalidPathSymbols().ToLowerInvariant()}.json" );
|
public bool MoveOption(int optionIdxFrom, int optionIdxTo);
|
||||||
|
public void UpdatePositions(int from = 0);
|
||||||
|
}
|
||||||
|
|
||||||
public void DeleteFile( DirectoryInfo basePath, int groupIdx )
|
public readonly struct ModSaveGroup : ISavable
|
||||||
|
{
|
||||||
|
private readonly DirectoryInfo _basePath;
|
||||||
|
private readonly IModGroup? _group;
|
||||||
|
private readonly int _groupIdx;
|
||||||
|
private readonly ISubMod? _defaultMod;
|
||||||
|
|
||||||
|
public ModSaveGroup(Mod mod, int groupIdx)
|
||||||
{
|
{
|
||||||
var file = FileName( basePath, groupIdx );
|
_basePath = mod.ModPath;
|
||||||
if( !File.Exists( file ) )
|
if (_groupIdx < 0)
|
||||||
{
|
_defaultMod = mod.Default;
|
||||||
return;
|
else
|
||||||
}
|
_group = mod.Groups[groupIdx];
|
||||||
|
_groupIdx = groupIdx;
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Delete( file );
|
|
||||||
Penumbra.Log.Debug( $"Deleted group file {file} for group {groupIdx + 1}: {Name}." );
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
Penumbra.Log.Error( $"Could not delete file {file}:\n{e}" );
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SaveDelayed( IModGroup group, DirectoryInfo basePath, int groupIdx )
|
public ModSaveGroup(DirectoryInfo basePath, IModGroup group, int groupIdx)
|
||||||
{
|
{
|
||||||
Penumbra.Framework.RegisterDelayed( $"{nameof( SaveModGroup )}_{basePath.Name}_{group.Name}",
|
_basePath = basePath;
|
||||||
() => SaveModGroup( group, basePath, groupIdx ) );
|
_group = group;
|
||||||
|
_groupIdx = groupIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Save( IModGroup group, DirectoryInfo basePath, int groupIdx )
|
public ModSaveGroup(DirectoryInfo basePath, ISubMod @default)
|
||||||
=> SaveModGroup( group, basePath, groupIdx );
|
|
||||||
|
|
||||||
private static void SaveModGroup( IModGroup group, DirectoryInfo basePath, int groupIdx )
|
|
||||||
{
|
{
|
||||||
var file = group.FileName( basePath, groupIdx );
|
_basePath = basePath;
|
||||||
using var s = File.Exists( file ) ? File.Open( file, FileMode.Truncate ) : File.Open( file, FileMode.CreateNew );
|
_groupIdx = -1;
|
||||||
using var writer = new StreamWriter( s );
|
_defaultMod = @default;
|
||||||
using var j = new JsonTextWriter( writer ) { Formatting = Formatting.Indented };
|
}
|
||||||
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
|
||||||
j.WriteStartObject();
|
public string ToFilename(FilenameService fileNames)
|
||||||
j.WritePropertyName( nameof( group.Name ) );
|
=> fileNames.OptionGroupFile(_basePath.FullName, _groupIdx, _group?.Name ?? string.Empty);
|
||||||
j.WriteValue( group.Name );
|
|
||||||
j.WritePropertyName( nameof( group.Description ) );
|
public void Save(StreamWriter writer)
|
||||||
j.WriteValue( group.Description );
|
{
|
||||||
j.WritePropertyName( nameof( group.Priority ) );
|
using var j = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||||
j.WriteValue( group.Priority );
|
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
||||||
j.WritePropertyName( nameof( Type ) );
|
if (_groupIdx >= 0)
|
||||||
j.WriteValue( group.Type.ToString() );
|
|
||||||
j.WritePropertyName( nameof( group.DefaultSettings ) );
|
|
||||||
j.WriteValue( group.DefaultSettings );
|
|
||||||
j.WritePropertyName( "Options" );
|
|
||||||
j.WriteStartArray();
|
|
||||||
for( var idx = 0; idx < group.Count; ++idx )
|
|
||||||
{
|
{
|
||||||
ISubMod.WriteSubMod( j, serializer, group[ idx ], basePath, group.Type == GroupType.Multi ? group.OptionPriority( idx ) : null );
|
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);
|
||||||
|
j.WritePropertyName(nameof(Type));
|
||||||
|
j.WriteValue(_group.Type.ToString());
|
||||||
|
j.WritePropertyName(nameof(_group.DefaultSettings));
|
||||||
|
j.WriteValue(_group.DefaultSettings);
|
||||||
|
j.WritePropertyName("Options");
|
||||||
|
j.WriteStartArray();
|
||||||
|
for (var idx = 0; idx < _group.Count; ++idx)
|
||||||
|
ISubMod.WriteSubMod(j, serializer, _group[idx], _basePath, _group.Type == GroupType.Multi ? _group.OptionPriority(idx) : null);
|
||||||
|
|
||||||
|
j.WriteEndArray();
|
||||||
|
j.WriteEndObject();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ISubMod.WriteSubMod(j, serializer, _defaultMod!, _basePath, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
j.WriteEndArray();
|
|
||||||
j.WriteEndObject();
|
|
||||||
Penumbra.Log.Debug( $"Saved group file {file} for group {groupIdx + 1}: {group.Name}." );
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public IModGroup Convert( GroupType type );
|
|
||||||
public bool MoveOption( int optionIdxFrom, int optionIdxTo );
|
|
||||||
public void UpdatePositions( int from = 0 );
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -38,17 +38,18 @@ public class Penumbra : IDalamudPlugin
|
||||||
public string Name
|
public string Name
|
||||||
=> "Penumbra";
|
=> "Penumbra";
|
||||||
|
|
||||||
public static Logger Log { get; private set; } = null!;
|
public static Logger Log { get; private set; } = null!;
|
||||||
public static ChatService ChatService { get; private set; } = null!;
|
public static ChatService ChatService { get; private set; } = null!;
|
||||||
public static SaveService SaveService { get; private set; } = null!;
|
public static FilenameService Filenames { get; private set; } = null!;
|
||||||
public static Configuration Config { get; private set; } = null!;
|
public static SaveService SaveService { get; private set; } = null!;
|
||||||
|
public static Configuration Config { get; private set; } = null!;
|
||||||
|
|
||||||
public static ResidentResourceManager ResidentResources { get; private set; } = null!;
|
public static ResidentResourceManager ResidentResources { get; private set; } = null!;
|
||||||
public static CharacterUtility CharacterUtility { get; private set; } = null!;
|
public static CharacterUtility CharacterUtility { get; private set; } = null!;
|
||||||
public static GameEventManager GameEvents { get; private set; } = null!;
|
public static GameEventManager GameEvents { get; private set; } = null!;
|
||||||
public static MetaFileManager MetaFileManager { get; private set; } = null!;
|
public static MetaFileManager MetaFileManager { get; private set; } = null!;
|
||||||
public static ModManager ModManager { get; private set; } = null!;
|
public static ModManager ModManager { get; private set; } = null!;
|
||||||
public static CollectionManager CollectionManager { get; private set; } = null!;
|
public static CollectionManager CollectionManager { get; private set; } = null!;
|
||||||
public static TempCollectionManager TempCollections { get; private set; } = null!;
|
public static TempCollectionManager TempCollections { get; private set; } = null!;
|
||||||
public static TempModManager TempMods { get; private set; } = null!;
|
public static TempModManager TempMods { get; private set; } = null!;
|
||||||
public static ResourceLoader ResourceLoader { get; private set; } = null!;
|
public static ResourceLoader ResourceLoader { get; private set; } = null!;
|
||||||
|
|
@ -63,13 +64,13 @@ public class Penumbra : IDalamudPlugin
|
||||||
|
|
||||||
public static PerformanceTracker Performance { get; private set; } = null!;
|
public static PerformanceTracker Performance { get; private set; } = null!;
|
||||||
|
|
||||||
public readonly PathResolver PathResolver;
|
public readonly PathResolver PathResolver;
|
||||||
public readonly RedrawService RedrawService;
|
public readonly RedrawService RedrawService;
|
||||||
public readonly ModFileSystem ModFileSystem;
|
public readonly ModFileSystem ModFileSystem;
|
||||||
public HttpApi HttpApi = null!;
|
public HttpApi HttpApi = null!;
|
||||||
internal ConfigWindow? ConfigWindow { get; private set; }
|
internal ConfigWindow? ConfigWindow { get; private set; }
|
||||||
private PenumbraWindowSystem? _windowSystem;
|
private PenumbraWindowSystem? _windowSystem;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
private readonly PenumbraNew _tmp;
|
private readonly PenumbraNew _tmp;
|
||||||
|
|
||||||
|
|
@ -80,29 +81,30 @@ public class Penumbra : IDalamudPlugin
|
||||||
{
|
{
|
||||||
_tmp = new PenumbraNew(this, pluginInterface);
|
_tmp = new PenumbraNew(this, pluginInterface);
|
||||||
ChatService = _tmp.Services.GetRequiredService<ChatService>();
|
ChatService = _tmp.Services.GetRequiredService<ChatService>();
|
||||||
|
Filenames = _tmp.Services.GetRequiredService<FilenameService>();
|
||||||
SaveService = _tmp.Services.GetRequiredService<SaveService>();
|
SaveService = _tmp.Services.GetRequiredService<SaveService>();
|
||||||
Performance = _tmp.Services.GetRequiredService<PerformanceTracker>();
|
Performance = _tmp.Services.GetRequiredService<PerformanceTracker>();
|
||||||
ValidityChecker = _tmp.Services.GetRequiredService<ValidityChecker>();
|
ValidityChecker = _tmp.Services.GetRequiredService<ValidityChecker>();
|
||||||
_tmp.Services.GetRequiredService<BackupService>();
|
_tmp.Services.GetRequiredService<BackupService>();
|
||||||
Config = _tmp.Services.GetRequiredService<Configuration>();
|
Config = _tmp.Services.GetRequiredService<Configuration>();
|
||||||
CharacterUtility = _tmp.Services.GetRequiredService<CharacterUtility>();
|
CharacterUtility = _tmp.Services.GetRequiredService<CharacterUtility>();
|
||||||
GameEvents = _tmp.Services.GetRequiredService<GameEventManager>();
|
GameEvents = _tmp.Services.GetRequiredService<GameEventManager>();
|
||||||
MetaFileManager = _tmp.Services.GetRequiredService<MetaFileManager>();
|
MetaFileManager = _tmp.Services.GetRequiredService<MetaFileManager>();
|
||||||
Framework = _tmp.Services.GetRequiredService<FrameworkManager>();
|
Framework = _tmp.Services.GetRequiredService<FrameworkManager>();
|
||||||
Actors = _tmp.Services.GetRequiredService<ActorService>().AwaitedService;
|
Actors = _tmp.Services.GetRequiredService<ActorService>().AwaitedService;
|
||||||
Identifier = _tmp.Services.GetRequiredService<IdentifierService>().AwaitedService;
|
Identifier = _tmp.Services.GetRequiredService<IdentifierService>().AwaitedService;
|
||||||
GamePathParser = _tmp.Services.GetRequiredService<IGamePathParser>();
|
GamePathParser = _tmp.Services.GetRequiredService<IGamePathParser>();
|
||||||
StainService = _tmp.Services.GetRequiredService<StainService>();
|
StainService = _tmp.Services.GetRequiredService<StainService>();
|
||||||
TempMods = _tmp.Services.GetRequiredService<TempModManager>();
|
TempMods = _tmp.Services.GetRequiredService<TempModManager>();
|
||||||
ResidentResources = _tmp.Services.GetRequiredService<ResidentResourceManager>();
|
ResidentResources = _tmp.Services.GetRequiredService<ResidentResourceManager>();
|
||||||
_tmp.Services.GetRequiredService<ResourceManagerService>();
|
_tmp.Services.GetRequiredService<ResourceManagerService>();
|
||||||
ModManager = _tmp.Services.GetRequiredService<ModManager>();
|
ModManager = _tmp.Services.GetRequiredService<ModManager>();
|
||||||
CollectionManager = _tmp.Services.GetRequiredService<CollectionManager>();
|
CollectionManager = _tmp.Services.GetRequiredService<CollectionManager>();
|
||||||
TempCollections = _tmp.Services.GetRequiredService<TempCollectionManager>();
|
TempCollections = _tmp.Services.GetRequiredService<TempCollectionManager>();
|
||||||
ModFileSystem = _tmp.Services.GetRequiredService<ModFileSystem>();
|
ModFileSystem = _tmp.Services.GetRequiredService<ModFileSystem>();
|
||||||
RedrawService = _tmp.Services.GetRequiredService<RedrawService>();
|
RedrawService = _tmp.Services.GetRequiredService<RedrawService>();
|
||||||
_tmp.Services.GetRequiredService<ResourceService>();
|
_tmp.Services.GetRequiredService<ResourceService>();
|
||||||
ResourceLoader = _tmp.Services.GetRequiredService<ResourceLoader>();
|
ResourceLoader = _tmp.Services.GetRequiredService<ResourceLoader>();
|
||||||
using (var t = _tmp.Services.GetRequiredService<StartTracker>().Measure(StartTimeType.PathResolver))
|
using (var t = _tmp.Services.GetRequiredService<StartTracker>().Measure(StartTimeType.PathResolver))
|
||||||
{
|
{
|
||||||
PathResolver = _tmp.Services.GetRequiredService<PathResolver>();
|
PathResolver = _tmp.Services.GetRequiredService<PathResolver>();
|
||||||
|
|
@ -112,7 +114,8 @@ public class Penumbra : IDalamudPlugin
|
||||||
SetupApi();
|
SetupApi();
|
||||||
|
|
||||||
ValidityChecker.LogExceptions();
|
ValidityChecker.LogExceptions();
|
||||||
Log.Information($"Penumbra Version {ValidityChecker.Version}, Commit #{ValidityChecker.CommitHash} successfully Loaded from {pluginInterface.SourceRepository}.");
|
Log.Information(
|
||||||
|
$"Penumbra Version {ValidityChecker.Version}, Commit #{ValidityChecker.CommitHash} successfully Loaded from {pluginInterface.SourceRepository}.");
|
||||||
OtterTex.NativeDll.Initialize(pluginInterface.AssemblyLocation.DirectoryName);
|
OtterTex.NativeDll.Initialize(pluginInterface.AssemblyLocation.DirectoryName);
|
||||||
Log.Information($"Loading native OtterTex assembly from {OtterTex.NativeDll.Directory}.");
|
Log.Information($"Loading native OtterTex assembly from {OtterTex.NativeDll.Directory}.");
|
||||||
|
|
||||||
|
|
@ -129,8 +132,8 @@ public class Penumbra : IDalamudPlugin
|
||||||
private void SetupApi()
|
private void SetupApi()
|
||||||
{
|
{
|
||||||
using var timer = _tmp.Services.GetRequiredService<StartTracker>().Measure(StartTimeType.Api);
|
using var timer = _tmp.Services.GetRequiredService<StartTracker>().Measure(StartTimeType.Api);
|
||||||
var api = _tmp.Services.GetRequiredService<IPenumbraApi>();
|
var api = _tmp.Services.GetRequiredService<IPenumbraApi>();
|
||||||
HttpApi = _tmp.Services.GetRequiredService<HttpApi>();
|
HttpApi = _tmp.Services.GetRequiredService<HttpApi>();
|
||||||
_tmp.Services.GetRequiredService<PenumbraIpcProviders>();
|
_tmp.Services.GetRequiredService<PenumbraIpcProviders>();
|
||||||
if (Config.EnableHttpApi)
|
if (Config.EnableHttpApi)
|
||||||
HttpApi.CreateWebServer();
|
HttpApi.CreateWebServer();
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ public class PenumbraNew
|
||||||
// Add Mod Services
|
// Add Mod Services
|
||||||
services.AddSingleton<TempModManager>()
|
services.AddSingleton<TempModManager>()
|
||||||
.AddSingleton<ModDataEditor>()
|
.AddSingleton<ModDataEditor>()
|
||||||
|
.AddSingleton<ModOptionEditor>()
|
||||||
.AddSingleton<ModManager>()
|
.AddSingleton<ModManager>()
|
||||||
.AddSingleton<ModFileSystem>();
|
.AddSingleton<ModFileSystem>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,13 +45,22 @@ public class CommunicatorService : IDisposable
|
||||||
/// </list> </summary>
|
/// </list> </summary>
|
||||||
public readonly EventWrapper<nint, string, nint> CreatedCharacterBase = new(nameof(CreatedCharacterBase));
|
public readonly EventWrapper<nint, string, nint> CreatedCharacterBase = new(nameof(CreatedCharacterBase));
|
||||||
|
|
||||||
/// <summary><list type="number">
|
/// <summary> <list type="number">
|
||||||
/// <item>Parameter is the type of data change for the mod, which can be multiple flags. </item>
|
/// <item>Parameter is the type of data change for the mod, which can be multiple flags. </item>
|
||||||
/// <item>Parameter is the changed mod. </item>
|
/// <item>Parameter is the changed mod. </item>
|
||||||
/// <item>Parameter is the old name of the mod in case of a name change, and null otherwise. </item>
|
/// <item>Parameter is the old name of the mod in case of a name change, and null otherwise. </item>
|
||||||
/// </list> </summary>
|
/// </list> </summary>
|
||||||
public readonly EventWrapper<ModDataChangeType, Mod, string?> ModDataChanged = new(nameof(ModDataChanged));
|
public readonly EventWrapper<ModDataChangeType, Mod, string?> ModDataChanged = new(nameof(ModDataChanged));
|
||||||
|
|
||||||
|
/// <summary><list type="number">
|
||||||
|
/// <item>Parameter is the type option change. </item>
|
||||||
|
/// <item>Parameter is the changed mod. </item>
|
||||||
|
/// <item>Parameter is the index of the changed group inside the mod. </item>
|
||||||
|
/// <item>Parameter is the index of the changed option inside the group or -1 if it does not concern a specific option. </item>
|
||||||
|
/// <item>Parameter is the index of the group an option was moved to. </item>
|
||||||
|
/// </list> </summary>
|
||||||
|
public readonly EventWrapper<ModOptionChangeType, Mod, int, int, int> ModOptionChanged = new(nameof(ModOptionChanged));
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
CollectionChange.Dispose();
|
CollectionChange.Dispose();
|
||||||
|
|
@ -60,5 +69,6 @@ public class CommunicatorService : IDisposable
|
||||||
CreatingCharacterBase.Dispose();
|
CreatingCharacterBase.Dispose();
|
||||||
CreatedCharacterBase.Dispose();
|
CreatedCharacterBase.Dispose();
|
||||||
ModDataChanged.Dispose();
|
ModDataChanged.Dispose();
|
||||||
|
ModOptionChanged.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,4 +71,21 @@ public class FilenameService
|
||||||
/// <summary> Obtain the path of the meta file given a mod directory. </summary>
|
/// <summary> Obtain the path of the meta file given a mod directory. </summary>
|
||||||
public string ModMetaPath(string modDirectory)
|
public string ModMetaPath(string modDirectory)
|
||||||
=> Path.Combine(modDirectory, "meta.json");
|
=> Path.Combine(modDirectory, "meta.json");
|
||||||
|
|
||||||
|
/// <summary> Obtain the path of the file describing a given option group by its index and the mod. If the index is < 0, return the path for the default mod file. </summary>
|
||||||
|
public string OptionGroupFile(Mod mod, int index)
|
||||||
|
=> OptionGroupFile(mod.ModPath.FullName, index, index >= 0 ? mod.Groups[index].Name : string.Empty);
|
||||||
|
|
||||||
|
/// <summary> Obtain the path of the file describing a given option group by its index, name and basepath. If the index is < 0, return the path for the default mod file. </summary>
|
||||||
|
public string OptionGroupFile(string basePath, int index, string name)
|
||||||
|
{
|
||||||
|
var fileName = index >= 0
|
||||||
|
? $"group_{index + 1:D3}_{name.RemoveInvalidPathSymbols().ToLowerInvariant()}.json"
|
||||||
|
: "default_mod.json";
|
||||||
|
return Path.Combine(basePath, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Enumerate all group files for a given mod. </summary>
|
||||||
|
public IEnumerable<FileInfo> GetOptionGroupFiles(Mod mod)
|
||||||
|
=> mod.ModPath.EnumerateFiles("group_*.json");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ public class ItemSwapTab : IDisposable, ITab
|
||||||
_modManager.DataEditor.CreateMeta(newDir, _newModName, _config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
|
_modManager.DataEditor.CreateMeta(newDir, _newModName, _config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
|
||||||
Mod.Creator.CreateDefaultFiles(newDir);
|
Mod.Creator.CreateDefaultFiles(newDir);
|
||||||
_modManager.AddMod(newDir);
|
_modManager.AddMod(newDir);
|
||||||
if (!_swapData.WriteMod(_modManager.Last(),
|
if (!_swapData.WriteMod(_modManager, _modManager.Last(),
|
||||||
_useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps))
|
_useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps))
|
||||||
_modManager.DeleteMod(_modManager.Count - 1);
|
_modManager.DeleteMod(_modManager.Count - 1);
|
||||||
}
|
}
|
||||||
|
|
@ -296,16 +296,16 @@ public class ItemSwapTab : IDisposable, ITab
|
||||||
{
|
{
|
||||||
if (_selectedGroup == null)
|
if (_selectedGroup == null)
|
||||||
{
|
{
|
||||||
_modManager.AddModGroup(_mod, GroupType.Multi, _newGroupName);
|
_modManager.OptionEditor.AddModGroup(_mod, GroupType.Multi, _newGroupName);
|
||||||
_selectedGroup = _mod.Groups.Last();
|
_selectedGroup = _mod.Groups.Last();
|
||||||
groupCreated = true;
|
groupCreated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_modManager.AddOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _newOptionName);
|
_modManager.OptionEditor.AddOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _newOptionName);
|
||||||
optionCreated = true;
|
optionCreated = true;
|
||||||
optionFolderName = Directory.CreateDirectory(optionFolderName.FullName);
|
optionFolderName = Directory.CreateDirectory(optionFolderName.FullName);
|
||||||
dirCreated = true;
|
dirCreated = true;
|
||||||
if (!_swapData.WriteMod(_mod, _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps,
|
if (!_swapData.WriteMod(_modManager, _mod, _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps,
|
||||||
optionFolderName,
|
optionFolderName,
|
||||||
_mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1))
|
_mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1))
|
||||||
throw new Exception("Failure writing files for mod swap.");
|
throw new Exception("Failure writing files for mod swap.");
|
||||||
|
|
@ -317,11 +317,11 @@ public class ItemSwapTab : IDisposable, ITab
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (optionCreated && _selectedGroup != null)
|
if (optionCreated && _selectedGroup != null)
|
||||||
_modManager.DeleteOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1);
|
_modManager.OptionEditor.DeleteOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1);
|
||||||
|
|
||||||
if (groupCreated)
|
if (groupCreated)
|
||||||
{
|
{
|
||||||
_modManager.DeleteModGroup(_mod, _mod.Groups.IndexOf(_selectedGroup!));
|
_modManager.OptionEditor.DeleteModGroup(_mod, _mod.Groups.IndexOf(_selectedGroup!));
|
||||||
_selectedGroup = null;
|
_selectedGroup = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ using OtterGui.Raii;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Services;
|
||||||
using Penumbra.UI.AdvancedWindow;
|
using Penumbra.UI.AdvancedWindow;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
|
@ -20,7 +21,8 @@ namespace Penumbra.UI.ModsTab;
|
||||||
public class ModPanelEditTab : ITab
|
public class ModPanelEditTab : ITab
|
||||||
{
|
{
|
||||||
private readonly ChatService _chat;
|
private readonly ChatService _chat;
|
||||||
private readonly ModManager _modManager;
|
private readonly FilenameService _filenames;
|
||||||
|
private readonly ModManager _modManager;
|
||||||
private readonly ModFileSystem _fileSystem;
|
private readonly ModFileSystem _fileSystem;
|
||||||
private readonly ModFileSystemSelector _selector;
|
private readonly ModFileSystemSelector _selector;
|
||||||
private readonly ModEditWindow _editWindow;
|
private readonly ModEditWindow _editWindow;
|
||||||
|
|
@ -34,14 +36,15 @@ public class ModPanelEditTab : ITab
|
||||||
private Mod _mod = null!;
|
private Mod _mod = null!;
|
||||||
|
|
||||||
public ModPanelEditTab(ModManager modManager, ModFileSystemSelector selector, ModFileSystem fileSystem, ChatService chat,
|
public ModPanelEditTab(ModManager modManager, ModFileSystemSelector selector, ModFileSystem fileSystem, ChatService chat,
|
||||||
ModEditWindow editWindow, ModEditor editor)
|
ModEditWindow editWindow, ModEditor editor, FilenameService filenames)
|
||||||
{
|
{
|
||||||
_modManager = modManager;
|
_modManager = modManager;
|
||||||
_selector = selector;
|
_selector = selector;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_chat = chat;
|
_chat = chat;
|
||||||
_editWindow = editWindow;
|
_editWindow = editWindow;
|
||||||
_editor = editor;
|
_editor = editor;
|
||||||
|
_filenames = filenames;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
|
|
@ -129,7 +132,7 @@ public class ModPanelEditTab : ITab
|
||||||
if (ImGui.Button("Update Bibo Material", buttonSize))
|
if (ImGui.Button("Update Bibo Material", buttonSize))
|
||||||
{
|
{
|
||||||
_editor.LoadMod(_mod);
|
_editor.LoadMod(_mod);
|
||||||
_editor.MdlMaterialEditor.ReplaceAllMaterials("bibo", "b");
|
_editor.MdlMaterialEditor.ReplaceAllMaterials("bibo", "b");
|
||||||
_editor.MdlMaterialEditor.ReplaceAllMaterials("bibopube", "c");
|
_editor.MdlMaterialEditor.ReplaceAllMaterials("bibopube", "c");
|
||||||
_editor.MdlMaterialEditor.SaveAllModels();
|
_editor.MdlMaterialEditor.SaveAllModels();
|
||||||
_editWindow.UpdateModels();
|
_editWindow.UpdateModels();
|
||||||
|
|
@ -189,7 +192,7 @@ public class ModPanelEditTab : ITab
|
||||||
|
|
||||||
var reducedSize = new Vector2(UiHelpers.InputTextMinusButton3, 0);
|
var reducedSize = new Vector2(UiHelpers.InputTextMinusButton3, 0);
|
||||||
if (ImGui.Button("Edit Description", reducedSize))
|
if (ImGui.Button("Edit Description", reducedSize))
|
||||||
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_mod, Input.Description));
|
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_filenames, _mod, Input.Description));
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var fileExists = File.Exists(_modManager.DataEditor.MetaFile(_mod));
|
var fileExists = File.Exists(_modManager.DataEditor.MetaFile(_mod));
|
||||||
|
|
@ -235,13 +238,13 @@ public class ModPanelEditTab : ITab
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
var nameValid = modManager.VerifyFileName(mod, null, _newGroupName, false);
|
var nameValid = ModOptionEditor.VerifyFileName(mod, null, _newGroupName, false);
|
||||||
tt = nameValid ? "Add new option group to the mod." : "Can not add a group of this name.";
|
tt = nameValid ? "Add new option group to the mod." : "Can not add a group of this name.";
|
||||||
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), UiHelpers.IconButtonSize,
|
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), UiHelpers.IconButtonSize,
|
||||||
tt, !nameValid, true))
|
tt, !nameValid, true))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
modManager.AddModGroup(mod, GroupType.Single, _newGroupName);
|
modManager.OptionEditor.AddModGroup(mod, GroupType.Single, _newGroupName);
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -249,7 +252,7 @@ public class ModPanelEditTab : ITab
|
||||||
/// <summary> A text input for the new directory name and a button to apply the move. </summary>
|
/// <summary> A text input for the new directory name and a button to apply the move. </summary>
|
||||||
private static class MoveDirectory
|
private static class MoveDirectory
|
||||||
{
|
{
|
||||||
private static string? _currentModDirectory;
|
private static string? _currentModDirectory;
|
||||||
private static ModManager.NewDirectoryState _state = ModManager.NewDirectoryState.Identical;
|
private static ModManager.NewDirectoryState _state = ModManager.NewDirectoryState.Identical;
|
||||||
|
|
||||||
public static void Reset()
|
public static void Reset()
|
||||||
|
|
@ -297,14 +300,16 @@ public class ModPanelEditTab : ITab
|
||||||
/// <summary> Open a popup to edit a multi-line mod or option description. </summary>
|
/// <summary> Open a popup to edit a multi-line mod or option description. </summary>
|
||||||
private static class DescriptionEdit
|
private static class DescriptionEdit
|
||||||
{
|
{
|
||||||
private const string PopupName = "Edit Description";
|
private const string PopupName = "Edit Description";
|
||||||
private static string _newDescription = string.Empty;
|
private static string _newDescription = string.Empty;
|
||||||
private static int _newDescriptionIdx = -1;
|
private static int _newDescriptionIdx = -1;
|
||||||
private static int _newDescriptionOptionIdx = -1;
|
private static int _newDescriptionOptionIdx = -1;
|
||||||
private static Mod? _mod;
|
private static Mod? _mod;
|
||||||
|
private static FilenameService? _fileNames;
|
||||||
|
|
||||||
public static void OpenPopup(Mod mod, int groupIdx, int optionIdx = -1)
|
public static void OpenPopup(FilenameService filenames, Mod mod, int groupIdx, int optionIdx = -1)
|
||||||
{
|
{
|
||||||
|
_fileNames = filenames;
|
||||||
_newDescriptionIdx = groupIdx;
|
_newDescriptionIdx = groupIdx;
|
||||||
_newDescriptionOptionIdx = optionIdx;
|
_newDescriptionOptionIdx = optionIdx;
|
||||||
_newDescription = groupIdx < 0
|
_newDescription = groupIdx < 0
|
||||||
|
|
@ -353,9 +358,10 @@ public class ModPanelEditTab : ITab
|
||||||
break;
|
break;
|
||||||
case >= 0:
|
case >= 0:
|
||||||
if (_newDescriptionOptionIdx < 0)
|
if (_newDescriptionOptionIdx < 0)
|
||||||
modManager.ChangeGroupDescription(_mod, _newDescriptionIdx, _newDescription);
|
modManager.OptionEditor.ChangeGroupDescription(_mod, _newDescriptionIdx, _newDescription);
|
||||||
else
|
else
|
||||||
modManager.ChangeOptionDescription(_mod, _newDescriptionIdx, _newDescriptionOptionIdx, _newDescription);
|
modManager.OptionEditor.ChangeOptionDescription(_mod, _newDescriptionIdx, _newDescriptionOptionIdx,
|
||||||
|
_newDescription);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -384,18 +390,18 @@ public class ModPanelEditTab : ITab
|
||||||
.Push(ImGuiStyleVar.ItemSpacing, _itemSpacing);
|
.Push(ImGuiStyleVar.ItemSpacing, _itemSpacing);
|
||||||
|
|
||||||
if (Input.Text("##Name", groupIdx, Input.None, group.Name, out var newGroupName, 256, UiHelpers.InputTextWidth.X))
|
if (Input.Text("##Name", groupIdx, Input.None, group.Name, out var newGroupName, 256, UiHelpers.InputTextWidth.X))
|
||||||
_modManager.RenameModGroup(_mod, groupIdx, newGroupName);
|
_modManager.OptionEditor.RenameModGroup(_mod, groupIdx, newGroupName);
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip("Group Name");
|
ImGuiUtil.HoverTooltip("Group Name");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), UiHelpers.IconButtonSize,
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), UiHelpers.IconButtonSize,
|
||||||
"Delete this option group.\nHold Control while clicking to delete.", !ImGui.GetIO().KeyCtrl, true))
|
"Delete this option group.\nHold Control while clicking to delete.", !ImGui.GetIO().KeyCtrl, true))
|
||||||
_delayedActions.Enqueue(() => _modManager.DeleteModGroup(_mod, groupIdx));
|
_delayedActions.Enqueue(() => _modManager.OptionEditor.DeleteModGroup(_mod, groupIdx));
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
if (Input.Priority("##Priority", groupIdx, Input.None, group.Priority, out var priority, 50 * UiHelpers.Scale))
|
if (Input.Priority("##Priority", groupIdx, Input.None, group.Priority, out var priority, 50 * UiHelpers.Scale))
|
||||||
_modManager.ChangeGroupPriority(_mod, groupIdx, priority);
|
_modManager.OptionEditor.ChangeGroupPriority(_mod, groupIdx, priority);
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip("Group Priority");
|
ImGuiUtil.HoverTooltip("Group Priority");
|
||||||
|
|
||||||
|
|
@ -405,7 +411,7 @@ public class ModPanelEditTab : ITab
|
||||||
var tt = groupIdx == 0 ? "Can not move this group further upwards." : $"Move this group up to group {groupIdx}.";
|
var tt = groupIdx == 0 ? "Can not move this group further upwards." : $"Move this group up to group {groupIdx}.";
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowUp.ToIconString(), UiHelpers.IconButtonSize,
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowUp.ToIconString(), UiHelpers.IconButtonSize,
|
||||||
tt, groupIdx == 0, true))
|
tt, groupIdx == 0, true))
|
||||||
_delayedActions.Enqueue(() => _modManager.MoveModGroup(_mod, groupIdx, groupIdx - 1));
|
_delayedActions.Enqueue(() => _modManager.OptionEditor.MoveModGroup(_mod, groupIdx, groupIdx - 1));
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
tt = groupIdx == _mod.Groups.Count - 1
|
tt = groupIdx == _mod.Groups.Count - 1
|
||||||
|
|
@ -413,16 +419,16 @@ public class ModPanelEditTab : ITab
|
||||||
: $"Move this group down to group {groupIdx + 2}.";
|
: $"Move this group down to group {groupIdx + 2}.";
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowDown.ToIconString(), UiHelpers.IconButtonSize,
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowDown.ToIconString(), UiHelpers.IconButtonSize,
|
||||||
tt, groupIdx == _mod.Groups.Count - 1, true))
|
tt, groupIdx == _mod.Groups.Count - 1, true))
|
||||||
_delayedActions.Enqueue(() => _modManager.MoveModGroup(_mod, groupIdx, groupIdx + 1));
|
_delayedActions.Enqueue(() => _modManager.OptionEditor.MoveModGroup(_mod, groupIdx, groupIdx + 1));
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Edit.ToIconString(), UiHelpers.IconButtonSize,
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Edit.ToIconString(), UiHelpers.IconButtonSize,
|
||||||
"Edit group description.", false, true))
|
"Edit group description.", false, true))
|
||||||
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_mod, groupIdx));
|
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_filenames, _mod, groupIdx));
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var fileName = group.FileName(_mod.ModPath, groupIdx);
|
var fileName = _filenames.OptionGroupFile(_mod, groupIdx);
|
||||||
var fileExists = File.Exists(fileName);
|
var fileExists = File.Exists(fileName);
|
||||||
tt = fileExists
|
tt = fileExists
|
||||||
? $"Open the {group.Name} json file in the text editor of your choice."
|
? $"Open the {group.Name} json file in the text editor of your choice."
|
||||||
|
|
@ -491,7 +497,7 @@ public class ModPanelEditTab : ITab
|
||||||
if (group.Type == GroupType.Single)
|
if (group.Type == GroupType.Single)
|
||||||
{
|
{
|
||||||
if (ImGui.RadioButton("##default", group.DefaultSettings == optionIdx))
|
if (ImGui.RadioButton("##default", group.DefaultSettings == optionIdx))
|
||||||
panel._modManager.ChangeModGroupDefaultOption(panel._mod, groupIdx, (uint)optionIdx);
|
panel._modManager.OptionEditor.ChangeModGroupDefaultOption(panel._mod, groupIdx, (uint)optionIdx);
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip($"Set {option.Name} as the default choice for this group.");
|
ImGuiUtil.HoverTooltip($"Set {option.Name} as the default choice for this group.");
|
||||||
}
|
}
|
||||||
|
|
@ -499,7 +505,7 @@ public class ModPanelEditTab : ITab
|
||||||
{
|
{
|
||||||
var isDefaultOption = ((group.DefaultSettings >> optionIdx) & 1) != 0;
|
var isDefaultOption = ((group.DefaultSettings >> optionIdx) & 1) != 0;
|
||||||
if (ImGui.Checkbox("##default", ref isDefaultOption))
|
if (ImGui.Checkbox("##default", ref isDefaultOption))
|
||||||
panel._modManager.ChangeModGroupDefaultOption(panel._mod, groupIdx, isDefaultOption
|
panel._modManager.OptionEditor.ChangeModGroupDefaultOption(panel._mod, groupIdx, isDefaultOption
|
||||||
? group.DefaultSettings | (1u << optionIdx)
|
? group.DefaultSettings | (1u << optionIdx)
|
||||||
: group.DefaultSettings & ~(1u << optionIdx));
|
: group.DefaultSettings & ~(1u << optionIdx));
|
||||||
|
|
||||||
|
|
@ -508,17 +514,17 @@ public class ModPanelEditTab : ITab
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (Input.Text("##Name", groupIdx, optionIdx, option.Name, out var newOptionName, 256, -1))
|
if (Input.Text("##Name", groupIdx, optionIdx, option.Name, out var newOptionName, 256, -1))
|
||||||
panel._modManager.RenameOption(panel._mod, groupIdx, optionIdx, newOptionName);
|
panel._modManager.OptionEditor.RenameOption(panel._mod, groupIdx, optionIdx, newOptionName);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Edit.ToIconString(), UiHelpers.IconButtonSize, "Edit option description.",
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Edit.ToIconString(), UiHelpers.IconButtonSize, "Edit option description.",
|
||||||
false, true))
|
false, true))
|
||||||
panel._delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(panel._mod, groupIdx, optionIdx));
|
panel._delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(panel._filenames, panel._mod, groupIdx, optionIdx));
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), UiHelpers.IconButtonSize,
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), UiHelpers.IconButtonSize,
|
||||||
"Delete this option.\nHold Control while clicking to delete.", !ImGui.GetIO().KeyCtrl, true))
|
"Delete this option.\nHold Control while clicking to delete.", !ImGui.GetIO().KeyCtrl, true))
|
||||||
panel._delayedActions.Enqueue(() => panel._modManager.DeleteOption(panel._mod, groupIdx, optionIdx));
|
panel._delayedActions.Enqueue(() => panel._modManager.OptionEditor.DeleteOption(panel._mod, groupIdx, optionIdx));
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (group.Type != GroupType.Multi)
|
if (group.Type != GroupType.Multi)
|
||||||
|
|
@ -526,7 +532,7 @@ public class ModPanelEditTab : ITab
|
||||||
|
|
||||||
if (Input.Priority("##Priority", groupIdx, optionIdx, group.OptionPriority(optionIdx), out var priority,
|
if (Input.Priority("##Priority", groupIdx, optionIdx, group.OptionPriority(optionIdx), out var priority,
|
||||||
50 * UiHelpers.Scale))
|
50 * UiHelpers.Scale))
|
||||||
panel._modManager.ChangeOptionPriority(panel._mod, groupIdx, optionIdx, priority);
|
panel._modManager.OptionEditor.ChangeOptionPriority(panel._mod, groupIdx, optionIdx, priority);
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip("Option priority.");
|
ImGuiUtil.HoverTooltip("Option priority.");
|
||||||
}
|
}
|
||||||
|
|
@ -560,7 +566,7 @@ public class ModPanelEditTab : ITab
|
||||||
tt, !(canAddGroup && validName), true))
|
tt, !(canAddGroup && validName), true))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
panel._modManager.AddOption(mod, groupIdx, _newOptionName);
|
panel._modManager.OptionEditor.AddOption(mod, groupIdx, _newOptionName);
|
||||||
_newOptionName = string.Empty;
|
_newOptionName = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -591,7 +597,7 @@ public class ModPanelEditTab : ITab
|
||||||
if (_dragDropGroupIdx == groupIdx)
|
if (_dragDropGroupIdx == groupIdx)
|
||||||
{
|
{
|
||||||
var sourceOption = _dragDropOptionIdx;
|
var sourceOption = _dragDropOptionIdx;
|
||||||
panel._delayedActions.Enqueue(() => panel._modManager.MoveOption(panel._mod, groupIdx, sourceOption, optionIdx));
|
panel._delayedActions.Enqueue(() => panel._modManager.OptionEditor.MoveOption(panel._mod, groupIdx, sourceOption, optionIdx));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -604,9 +610,9 @@ public class ModPanelEditTab : ITab
|
||||||
var priority = sourceGroup.OptionPriority(_dragDropOptionIdx);
|
var priority = sourceGroup.OptionPriority(_dragDropOptionIdx);
|
||||||
panel._delayedActions.Enqueue(() =>
|
panel._delayedActions.Enqueue(() =>
|
||||||
{
|
{
|
||||||
panel._modManager.DeleteOption(panel._mod, sourceGroupIdx, sourceOption);
|
panel._modManager.OptionEditor.DeleteOption(panel._mod, sourceGroupIdx, sourceOption);
|
||||||
panel._modManager.AddOption(panel._mod, groupIdx, option, priority);
|
panel._modManager.OptionEditor.AddOption(panel._mod, groupIdx, option, priority);
|
||||||
panel._modManager.MoveOption(panel._mod, groupIdx, currentCount, optionIdx);
|
panel._modManager.OptionEditor.MoveOption(panel._mod, groupIdx, currentCount, optionIdx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -633,12 +639,12 @@ public class ModPanelEditTab : ITab
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ImGui.Selectable(GroupTypeName(GroupType.Single), group.Type == GroupType.Single))
|
if (ImGui.Selectable(GroupTypeName(GroupType.Single), group.Type == GroupType.Single))
|
||||||
_modManager.ChangeModGroupType(_mod, groupIdx, GroupType.Single);
|
_modManager.OptionEditor.ChangeModGroupType(_mod, groupIdx, GroupType.Single);
|
||||||
|
|
||||||
var canSwitchToMulti = group.Count <= IModGroup.MaxMultiOptions;
|
var canSwitchToMulti = group.Count <= IModGroup.MaxMultiOptions;
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, !canSwitchToMulti);
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, !canSwitchToMulti);
|
||||||
if (ImGui.Selectable(GroupTypeName(GroupType.Multi), group.Type == GroupType.Multi) && canSwitchToMulti)
|
if (ImGui.Selectable(GroupTypeName(GroupType.Multi), group.Type == GroupType.Multi) && canSwitchToMulti)
|
||||||
_modManager.ChangeModGroupType(_mod, groupIdx, GroupType.Multi);
|
_modManager.OptionEditor.ChangeModGroupType(_mod, groupIdx, GroupType.Multi);
|
||||||
|
|
||||||
style.Pop();
|
style.Pop();
|
||||||
if (!canSwitchToMulti)
|
if (!canSwitchToMulti)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
|
using Penumbra.Mods;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Util;
|
namespace Penumbra.Util;
|
||||||
|
|
@ -94,4 +95,24 @@ public class SaveService
|
||||||
_log.Error($"Could not delete {value.GetType().Name} {value.LogName(name)}:\n{ex}");
|
_log.Error($"Could not delete {value.GetType().Name} {value.LogName(name)}:\n{ex}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Immediately delete all existing option group files for a mod and save them anew. </summary>
|
||||||
|
public void SaveAllOptionGroups(Mod mod)
|
||||||
|
{
|
||||||
|
foreach (var file in _fileNames.GetOptionGroupFiles(mod))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (file.Exists)
|
||||||
|
file.Delete();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Could not delete outdated group file {file}:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < mod.Groups.Count; ++i)
|
||||||
|
ImmediateSave(new ModSaveGroup(mod, i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue