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>
|
||||
{
|
||||
private readonly Mods.ModManager _modManager;
|
||||
private readonly ModManager _modManager;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly CharacterUtility _characterUtility;
|
||||
private readonly ResidentResourceManager _residentResources;
|
||||
private readonly Configuration _config;
|
||||
|
|
@ -57,7 +58,8 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
|||
=> _collections;
|
||||
|
||||
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);
|
||||
_communicator = communicator;
|
||||
|
|
@ -65,12 +67,13 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
|||
_residentResources = residentResources;
|
||||
_config = config;
|
||||
_modManager = modManager;
|
||||
_saveService = saveService;
|
||||
Individuals = individuals;
|
||||
|
||||
// The collection manager reacts to changes in mods by itself.
|
||||
_modManager.ModDiscoveryStarted += OnModDiscoveryStarted;
|
||||
_modManager.ModDiscoveryFinished += OnModDiscoveryFinished;
|
||||
_modManager.ModOptionChanged += OnModOptionsChanged;
|
||||
_communicator.ModOptionChanged.Event += OnModOptionsChanged;
|
||||
_modManager.ModPathChanged += OnModPathChange;
|
||||
_communicator.CollectionChange.Event += SaveOnChange;
|
||||
_communicator.TemporaryGlobalModChange.Event += OnGlobalModChange;
|
||||
|
|
@ -86,7 +89,7 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
|||
_communicator.TemporaryGlobalModChange.Event -= OnGlobalModChange;
|
||||
_modManager.ModDiscoveryStarted -= OnModDiscoveryStarted;
|
||||
_modManager.ModDiscoveryFinished -= OnModDiscoveryFinished;
|
||||
_modManager.ModOptionChanged -= OnModOptionsChanged;
|
||||
_communicator.ModOptionChanged.Event -= OnModOptionsChanged;
|
||||
_modManager.ModPathChanged -= OnModPathChange;
|
||||
}
|
||||
|
||||
|
|
@ -279,7 +282,7 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
|||
foreach (var collection in this)
|
||||
{
|
||||
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,
|
||||
|
|
@ -310,7 +313,7 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
|||
}
|
||||
|
||||
var defaultCollection = ModCollection.CreateNewEmpty((string)ModCollection.DefaultCollection);
|
||||
Penumbra.SaveService.ImmediateSave(defaultCollection);
|
||||
_saveService.ImmediateSave(defaultCollection);
|
||||
defaultCollection.Index = _collections.Count;
|
||||
_collections.Add(defaultCollection);
|
||||
}
|
||||
|
|
@ -337,7 +340,7 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
|||
}
|
||||
|
||||
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."
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
break;
|
||||
// The group of all Characters is redundant if they are all equal to Default or unassigned.
|
||||
case CollectionType.MalePlayerCharacter:
|
||||
|
|
@ -464,7 +468,8 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
|
|||
continue;
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -6,20 +6,23 @@ using System.Linq;
|
|||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public class DuplicateManager
|
||||
{
|
||||
private readonly ModManager _modManager;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly ModManager _modManager;
|
||||
private readonly SHA256 _hasher = SHA256.Create();
|
||||
private readonly ModFileCollection _files;
|
||||
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;
|
||||
_modManager = modManager;
|
||||
_files = files;
|
||||
_modManager = modManager;
|
||||
_saveService = saveService;
|
||||
}
|
||||
|
||||
public IReadOnlyList<(FullPath[] Paths, long Size, byte[] Hash)> Duplicates
|
||||
|
|
@ -76,16 +79,13 @@ public class DuplicateManager
|
|||
|
||||
if (useModManager)
|
||||
{
|
||||
_modManager.OptionSetFiles(mod, groupIdx, optionIdx, dict);
|
||||
_modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sub = (SubMod)subMod;
|
||||
sub.FileData = dict;
|
||||
if (groupIdx == -1)
|
||||
mod.SaveDefaultMod();
|
||||
else
|
||||
IModGroup.Save(mod.Groups[groupIdx], mod.ModPath, groupIdx);
|
||||
_saveService.ImmediateSave(new ModSaveGroup(mod, groupIdx));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class ModFileEditor
|
|||
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);
|
||||
|
||||
return num;
|
||||
|
|
@ -54,7 +54,7 @@ public class ModFileEditor
|
|||
var newDict = subMod.Files.Where(kvp => CheckAgainstMissing(mod, subMod, kvp.Value, kvp.Key, subMod == option))
|
||||
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
if (newDict.Count != subMod.Files.Count)
|
||||
_modManager.OptionSetFiles(mod, groupIdx, optionIdx, newDict);
|
||||
_modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, newDict);
|
||||
}
|
||||
|
||||
ModEditor.ApplyToAllOptions(mod, HandleSubMod);
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ public class ModMetaEditor
|
|||
if (!Changes)
|
||||
return;
|
||||
|
||||
_modManager.OptionSetManipulations(mod, groupIdx, optionIdx, Recombine().ToHashSet());
|
||||
_modManager.OptionEditor.OptionSetManipulations(mod, groupIdx, optionIdx, Recombine().ToHashSet());
|
||||
Changes = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Penumbra.Mods;
|
|||
|
||||
public class ModNormalizer
|
||||
{
|
||||
private readonly ModManager _modManager;
|
||||
private readonly ModManager _modManager;
|
||||
private readonly List<List<Dictionary<Utf8GamePath, FullPath>>> _redirections = new();
|
||||
|
||||
public Mod Mod { get; private set; } = null!;
|
||||
|
|
@ -280,9 +280,8 @@ public class ModNormalizer
|
|||
private void ApplyRedirections()
|
||||
{
|
||||
foreach (var option in Mod.AllSubMods.OfType<SubMod>())
|
||||
{
|
||||
_modManager.OptionSetFiles(Mod, option.GroupIdx, option.OptionIdx, _redirections[option.GroupIdx + 1][option.OptionIdx]);
|
||||
}
|
||||
_modManager.OptionEditor.OptionSetFiles(Mod, option.GroupIdx, option.OptionIdx,
|
||||
_redirections[option.GroupIdx + 1][option.OptionIdx]);
|
||||
|
||||
++Step;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ public class ModSwapEditor
|
|||
|
||||
public void Apply(Mod mod, int groupIdx, int optionIdx)
|
||||
{
|
||||
if (Changes)
|
||||
{
|
||||
_modManager.OptionSetFileSwaps(mod, groupIdx, optionIdx, _swaps);
|
||||
Changes = false;
|
||||
}
|
||||
if (!Changes)
|
||||
return;
|
||||
|
||||
_modManager.OptionEditor.OptionSetFileSwaps(mod, groupIdx, optionIdx, _swaps);
|
||||
Changes = false;
|
||||
}
|
||||
|
||||
public bool Changes { get; private set; }
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public class ItemSwapContainer
|
|||
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 convertedFiles = new Dictionary< Utf8GamePath, FullPath >( Swaps.Count );
|
||||
|
|
@ -82,9 +82,9 @@ public class ItemSwapContainer
|
|||
}
|
||||
}
|
||||
|
||||
Penumbra.ModManager.OptionSetFiles( mod, groupIndex, optionIndex, convertedFiles );
|
||||
Penumbra.ModManager.OptionSetFileSwaps( mod, groupIndex, optionIndex, convertedSwaps );
|
||||
Penumbra.ModManager.OptionSetManipulations( mod, groupIndex, optionIndex, convertedManips );
|
||||
manager.OptionEditor.OptionSetFiles( mod, groupIndex, optionIndex, convertedFiles );
|
||||
manager.OptionEditor.OptionSetFileSwaps( mod, groupIndex, optionIndex, convertedSwaps );
|
||||
manager.OptionEditor.OptionSetManipulations( mod, groupIndex, optionIndex, convertedManips );
|
||||
return true;
|
||||
}
|
||||
catch( Exception e )
|
||||
|
|
|
|||
|
|
@ -2,12 +2,76 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
|
||||
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.
|
||||
public static bool MigrateModBackups = false;
|
||||
|
|
@ -38,21 +102,29 @@ public sealed partial class ModManager : IReadOnlyList<Mod>
|
|||
private readonly Configuration _config;
|
||||
private readonly CommunicatorService _communicator;
|
||||
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);
|
||||
_config = config;
|
||||
_communicator = communicator;
|
||||
DataEditor = dataEditor;
|
||||
OptionEditor = optionEditor;
|
||||
ModDirectoryChanged += OnModDirectoryChange;
|
||||
SetBaseDirectory(config.ModDirectory, true);
|
||||
UpdateExportDirectory(_config.ExportDirectory, false);
|
||||
ModOptionChanged += OnModOptionChange;
|
||||
ModPathChanged += OnModPathChange;
|
||||
_communicator.ModOptionChanged.Event += OnModOptionChange;
|
||||
ModPathChanged += OnModPathChange;
|
||||
DiscoverMods();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_communicator.ModOptionChanged.Event -= OnModOptionChange;
|
||||
}
|
||||
|
||||
|
||||
// Try to obtain a mod by its directory name (unique identifier, preferred),
|
||||
// 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;
|
||||
}
|
||||
|
||||
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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using OtterGui;
|
||||
using OtterGui.Filesystem;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
|
||||
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 void ChangeModGroupType(Mod mod, int groupIdx, GroupType type)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
if (group.Type == type)
|
||||
return;
|
||||
|
||||
mod._groups[groupIdx] = group.Convert(type);
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.GroupTypeChanged, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
public void ChangeModGroupDefaultOption(Mod mod, int groupIdx, uint defaultOption)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
if (group.DefaultSettings == defaultOption)
|
||||
return;
|
||||
|
||||
group.DefaultSettings = defaultOption;
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.DefaultOptionChanged, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
public void RenameModGroup(Mod mod, int groupIdx, string newName)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var oldName = group.Name;
|
||||
if (oldName == newName || !VerifyFileName(mod, group, newName, true))
|
||||
return;
|
||||
|
||||
group.DeleteFile(mod.ModPath, groupIdx);
|
||||
|
||||
var _ = group switch
|
||||
{
|
||||
SingleModGroup s => s.Name = newName,
|
||||
MultiModGroup m => m.Name = newName,
|
||||
_ => newName,
|
||||
};
|
||||
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.GroupRenamed, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
public void AddModGroup(Mod mod, GroupType type, string newName)
|
||||
{
|
||||
if (!VerifyFileName(mod, null, newName, true))
|
||||
return;
|
||||
|
||||
var maxPriority = mod._groups.Count == 0 ? 0 : mod._groups.Max(o => o.Priority) + 1;
|
||||
|
||||
mod._groups.Add(type == GroupType.Multi
|
||||
? new MultiModGroup
|
||||
{
|
||||
Name = newName,
|
||||
Priority = maxPriority,
|
||||
}
|
||||
: new SingleModGroup
|
||||
{
|
||||
Name = newName,
|
||||
Priority = maxPriority,
|
||||
});
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, mod._groups.Count - 1, -1, -1);
|
||||
}
|
||||
|
||||
public void DeleteModGroup(Mod mod, int groupIdx)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, -1, -1);
|
||||
mod._groups.RemoveAt(groupIdx);
|
||||
UpdateSubModPositions(mod, groupIdx);
|
||||
group.DeleteFile(mod.ModPath, groupIdx);
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
public void MoveModGroup(Mod mod, int groupIdxFrom, int groupIdxTo)
|
||||
{
|
||||
if (mod._groups.Move(groupIdxFrom, groupIdxTo))
|
||||
{
|
||||
UpdateSubModPositions(mod, Math.Min(groupIdxFrom, groupIdxTo));
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.GroupMoved, mod, groupIdxFrom, -1, groupIdxTo);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateSubModPositions(Mod mod, int fromGroup)
|
||||
{
|
||||
foreach (var (group, groupIdx) in mod._groups.WithIndex().Skip(fromGroup))
|
||||
{
|
||||
foreach (var (o, optionIdx) in group.OfType<SubMod>().WithIndex())
|
||||
o.SetPosition(groupIdx, optionIdx);
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeGroupDescription(Mod mod, int groupIdx, string newDescription)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
if (group.Description == newDescription)
|
||||
return;
|
||||
|
||||
var _ = group switch
|
||||
{
|
||||
SingleModGroup s => s.Description = newDescription,
|
||||
MultiModGroup m => m.Description = newDescription,
|
||||
_ => newDescription,
|
||||
};
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
public void ChangeOptionDescription(Mod mod, int groupIdx, int optionIdx, string newDescription)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var option = group[optionIdx];
|
||||
if (option.Description == newDescription || option is not SubMod s)
|
||||
return;
|
||||
|
||||
s.Description = newDescription;
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
public void ChangeGroupPriority(Mod mod, int groupIdx, int newPriority)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
if (group.Priority == newPriority)
|
||||
return;
|
||||
|
||||
var _ = group switch
|
||||
{
|
||||
SingleModGroup s => s.Priority = newPriority,
|
||||
MultiModGroup m => m.Priority = newPriority,
|
||||
_ => newPriority,
|
||||
};
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
public void ChangeOptionPriority(Mod mod, int groupIdx, int optionIdx, int newPriority)
|
||||
{
|
||||
switch (mod._groups[groupIdx])
|
||||
{
|
||||
case SingleModGroup:
|
||||
ChangeGroupPriority(mod, groupIdx, newPriority);
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
if (m.PrioritizedOptions[optionIdx].Priority == newPriority)
|
||||
return;
|
||||
|
||||
m.PrioritizedOptions[optionIdx] = (m.PrioritizedOptions[optionIdx].Mod, newPriority);
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, optionIdx, -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void RenameOption(Mod mod, int groupIdx, int optionIdx, string newName)
|
||||
{
|
||||
switch (mod._groups[groupIdx])
|
||||
{
|
||||
case SingleModGroup s:
|
||||
if (s.OptionData[optionIdx].Name == newName)
|
||||
return;
|
||||
|
||||
s.OptionData[optionIdx].Name = newName;
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
var option = m.PrioritizedOptions[optionIdx].Mod;
|
||||
if (option.Name == newName)
|
||||
return;
|
||||
|
||||
option.Name = newName;
|
||||
break;
|
||||
}
|
||||
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
public void AddOption(Mod mod, int groupIdx, string newName)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var subMod = new SubMod(mod) { Name = newName };
|
||||
subMod.SetPosition(groupIdx, group.Count);
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup s:
|
||||
s.OptionData.Add(subMod);
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
m.PrioritizedOptions.Add((subMod, 0));
|
||||
break;
|
||||
}
|
||||
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
||||
}
|
||||
|
||||
public void AddOption(Mod mod, int groupIdx, ISubMod option, int priority = 0)
|
||||
{
|
||||
if (option is not SubMod o)
|
||||
return;
|
||||
|
||||
var group = mod._groups[groupIdx];
|
||||
if (group.Count > 63)
|
||||
{
|
||||
Penumbra.Log.Error(
|
||||
$"Could not add option {option.Name} to {group.Name} for mod {mod.Name}, "
|
||||
+ "since only up to 64 options are supported in one group.");
|
||||
return;
|
||||
}
|
||||
|
||||
o.SetPosition(groupIdx, group.Count);
|
||||
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup s:
|
||||
s.OptionData.Add(o);
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
m.PrioritizedOptions.Add((o, priority));
|
||||
break;
|
||||
}
|
||||
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
||||
}
|
||||
|
||||
public void DeleteOption(Mod mod, int groupIdx, int optionIdx)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup s:
|
||||
s.OptionData.RemoveAt(optionIdx);
|
||||
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
m.PrioritizedOptions.RemoveAt(optionIdx);
|
||||
break;
|
||||
}
|
||||
|
||||
group.UpdatePositions(optionIdx);
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionDeleted, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
public void MoveOption(Mod mod, int groupIdx, int optionIdxFrom, int optionIdxTo)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
if (group.MoveOption(optionIdxFrom, optionIdxTo))
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionMoved, mod, groupIdx, optionIdxFrom, optionIdxTo);
|
||||
}
|
||||
|
||||
public void OptionSetManipulations(Mod mod, int groupIdx, int optionIdx, HashSet<MetaManipulation> manipulations)
|
||||
{
|
||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||
if (subMod.Manipulations.Count == manipulations.Count
|
||||
&& subMod.Manipulations.All(m => manipulations.TryGetValue(m, out var old) && old.EntryEquals(m)))
|
||||
return;
|
||||
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
subMod.ManipulationData = manipulations;
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
public void OptionSetFiles(Mod mod, int groupIdx, int optionIdx, Dictionary<Utf8GamePath, FullPath> replacements)
|
||||
{
|
||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||
if (subMod.FileData.SetEquals(replacements))
|
||||
return;
|
||||
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
subMod.FileData = replacements;
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
public void OptionAddFiles(Mod mod, int groupIdx, int optionIdx, Dictionary<Utf8GamePath, FullPath> additions)
|
||||
{
|
||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||
var oldCount = subMod.FileData.Count;
|
||||
subMod.FileData.AddFrom(additions);
|
||||
if (oldCount != subMod.FileData.Count)
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesAdded, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
public void OptionSetFileSwaps(Mod mod, int groupIdx, int optionIdx, Dictionary<Utf8GamePath, FullPath> swaps)
|
||||
{
|
||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||
if (subMod.FileSwapData.SetEquals(swaps))
|
||||
return;
|
||||
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
subMod.FileSwapData = swaps;
|
||||
ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
public bool VerifyFileName(Mod mod, IModGroup? group, string newName, bool message)
|
||||
{
|
||||
var path = newName.RemoveInvalidPathSymbols();
|
||||
if (path.Length != 0
|
||||
&& !mod.Groups.Any(o => !ReferenceEquals(o, group)
|
||||
&& string.Equals(o.Name.RemoveInvalidPathSymbols(), path, StringComparison.OrdinalIgnoreCase)))
|
||||
return true;
|
||||
|
||||
if (message)
|
||||
Penumbra.ChatService.NotificationMessage(
|
||||
$"Could not name option {newName} because option with same filename {path} already exists.",
|
||||
"Warning", NotificationType.Warning);
|
||||
|
||||
return 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(),
|
||||
};
|
||||
}
|
||||
|
||||
private static void OnModOptionChange(ModOptionChangeType type, Mod mod, int groupIdx, int _, int _2)
|
||||
{
|
||||
if (type == ModOptionChangeType.PrepareChange)
|
||||
return;
|
||||
|
||||
// File deletion is handled in the actual function.
|
||||
if (type is ModOptionChangeType.GroupDeleted or ModOptionChangeType.GroupMoved)
|
||||
{
|
||||
mod.SaveAllGroups();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (groupIdx == -1)
|
||||
mod.SaveDefaultModDelayed();
|
||||
else
|
||||
IModGroup.SaveDelayed(mod._groups[groupIdx], mod.ModPath, groupIdx);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using OtterGui;
|
||||
using OtterGui.Filesystem;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
|
||||
|
||||
public class ModOptionEditor
|
||||
{
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly FilenameService _filenames;
|
||||
private readonly SaveService _saveService;
|
||||
|
||||
public ModOptionEditor(CommunicatorService communicator, SaveService saveService, FilenameService filenames)
|
||||
{
|
||||
_communicator = communicator;
|
||||
_saveService = saveService;
|
||||
_filenames = filenames;
|
||||
}
|
||||
|
||||
/// <summary> Change the type of a group given by mod and index to type, if possible. </summary>
|
||||
public void ChangeModGroupType(Mod mod, int groupIdx, GroupType type)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
if (group.Type == type)
|
||||
return;
|
||||
|
||||
mod._groups[groupIdx] = group.Convert(type);
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupTypeChanged, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
/// <summary> Change the settings stored as default options in a mod.</summary>
|
||||
public void ChangeModGroupDefaultOption(Mod mod, int groupIdx, uint defaultOption)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
if (group.DefaultSettings == defaultOption)
|
||||
return;
|
||||
|
||||
group.DefaultSettings = defaultOption;
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DefaultOptionChanged, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
/// <summary> Rename an option group if possible. </summary>
|
||||
public void RenameModGroup(Mod mod, int groupIdx, string newName)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var oldName = group.Name;
|
||||
if (oldName == newName || !VerifyFileName(mod, group, newName, true))
|
||||
return;
|
||||
|
||||
_saveService.ImmediateDelete(new ModSaveGroup(mod, groupIdx));
|
||||
var _ = group switch
|
||||
{
|
||||
SingleModGroup s => s.Name = newName,
|
||||
MultiModGroup m => m.Name = newName,
|
||||
_ => newName,
|
||||
};
|
||||
|
||||
_saveService.ImmediateSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupRenamed, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
/// <summary> Add a new mod, empty option group of the given type and name. </summary>
|
||||
public void AddModGroup(Mod mod, GroupType type, string newName)
|
||||
{
|
||||
if (!VerifyFileName(mod, null, newName, true))
|
||||
return;
|
||||
|
||||
var maxPriority = mod._groups.Count == 0 ? 0 : mod._groups.Max(o => o.Priority) + 1;
|
||||
|
||||
mod._groups.Add(type == GroupType.Multi
|
||||
? new MultiModGroup
|
||||
{
|
||||
Name = newName,
|
||||
Priority = maxPriority,
|
||||
}
|
||||
: new SingleModGroup
|
||||
{
|
||||
Name = newName,
|
||||
Priority = maxPriority,
|
||||
});
|
||||
_saveService.ImmediateSave(new ModSaveGroup(mod, mod._groups.Count - 1));
|
||||
_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>
|
||||
public void DeleteModGroup(Mod mod, int groupIdx)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, -1, -1);
|
||||
mod._groups.RemoveAt(groupIdx);
|
||||
UpdateSubModPositions(mod, groupIdx);
|
||||
_saveService.SaveAllOptionGroups(mod);
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
/// <summary> Move the index of a given option group. </summary>
|
||||
public void MoveModGroup(Mod mod, int groupIdxFrom, int groupIdxTo)
|
||||
{
|
||||
if (!mod._groups.Move(groupIdxFrom, groupIdxTo))
|
||||
return;
|
||||
|
||||
UpdateSubModPositions(mod, Math.Min(groupIdxFrom, groupIdxTo));
|
||||
_saveService.SaveAllOptionGroups(mod);
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupMoved, mod, groupIdxFrom, -1, groupIdxTo);
|
||||
}
|
||||
|
||||
/// <summary> Change the description of the given option group. </summary>
|
||||
public void ChangeGroupDescription(Mod mod, int groupIdx, string newDescription)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
if (group.Description == newDescription)
|
||||
return;
|
||||
|
||||
var _ = group switch
|
||||
{
|
||||
SingleModGroup s => s.Description = newDescription,
|
||||
MultiModGroup m => m.Description = newDescription,
|
||||
_ => newDescription,
|
||||
};
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
/// <summary> Change the description of the given option. </summary>
|
||||
public void ChangeOptionDescription(Mod mod, int groupIdx, int optionIdx, string newDescription)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var option = group[optionIdx];
|
||||
if (option.Description == newDescription || option is not SubMod s)
|
||||
return;
|
||||
|
||||
s.Description = newDescription;
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
/// <summary> Change the internal priority of the given option group. </summary>
|
||||
public void ChangeGroupPriority(Mod mod, int groupIdx, int newPriority)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
if (group.Priority == newPriority)
|
||||
return;
|
||||
|
||||
var _ = group switch
|
||||
{
|
||||
SingleModGroup s => s.Priority = newPriority,
|
||||
MultiModGroup m => m.Priority = newPriority,
|
||||
_ => newPriority,
|
||||
};
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, -1, -1);
|
||||
}
|
||||
|
||||
/// <summary> Change the internal priority of the given option. </summary>
|
||||
public void ChangeOptionPriority(Mod mod, int groupIdx, int optionIdx, int newPriority)
|
||||
{
|
||||
switch (mod._groups[groupIdx])
|
||||
{
|
||||
case SingleModGroup:
|
||||
ChangeGroupPriority(mod, groupIdx, newPriority);
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
if (m.PrioritizedOptions[optionIdx].Priority == newPriority)
|
||||
return;
|
||||
|
||||
m.PrioritizedOptions[optionIdx] = (m.PrioritizedOptions[optionIdx].Mod, newPriority);
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, optionIdx, -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Rename the given option. </summary>
|
||||
public void RenameOption(Mod mod, int groupIdx, int optionIdx, string newName)
|
||||
{
|
||||
switch (mod._groups[groupIdx])
|
||||
{
|
||||
case SingleModGroup s:
|
||||
if (s.OptionData[optionIdx].Name == newName)
|
||||
return;
|
||||
|
||||
s.OptionData[optionIdx].Name = newName;
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
var option = m.PrioritizedOptions[optionIdx].Mod;
|
||||
if (option.Name == newName)
|
||||
return;
|
||||
|
||||
option.Name = newName;
|
||||
break;
|
||||
}
|
||||
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
/// <summary> Add a new empty option of the given name for the given group. </summary>
|
||||
public void AddOption(Mod mod, int groupIdx, string newName)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
var subMod = new SubMod(mod) { Name = newName };
|
||||
subMod.SetPosition(groupIdx, group.Count);
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup s:
|
||||
s.OptionData.Add(subMod);
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
m.PrioritizedOptions.Add((subMod, 0));
|
||||
break;
|
||||
}
|
||||
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (option is not SubMod o)
|
||||
return;
|
||||
|
||||
var group = mod._groups[groupIdx];
|
||||
if (group.Type is GroupType.Multi && group.Count >= IModGroup.MaxMultiOptions)
|
||||
{
|
||||
Penumbra.Log.Error(
|
||||
$"Could not add option {option.Name} to {group.Name} for mod {mod.Name}, "
|
||||
+ $"since only up to {IModGroup.MaxMultiOptions} options are supported in one group.");
|
||||
return;
|
||||
}
|
||||
|
||||
o.SetPosition(groupIdx, group.Count);
|
||||
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup s:
|
||||
s.OptionData.Add(o);
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
m.PrioritizedOptions.Add((o, priority));
|
||||
break;
|
||||
}
|
||||
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
||||
}
|
||||
|
||||
/// <summary> Delete the given option from the given group. </summary>
|
||||
public void DeleteOption(Mod mod, int groupIdx, int optionIdx)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
switch (group)
|
||||
{
|
||||
case SingleModGroup s:
|
||||
s.OptionData.RemoveAt(optionIdx);
|
||||
|
||||
break;
|
||||
case MultiModGroup m:
|
||||
m.PrioritizedOptions.RemoveAt(optionIdx);
|
||||
break;
|
||||
}
|
||||
|
||||
group.UpdatePositions(optionIdx);
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionDeleted, 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)
|
||||
{
|
||||
var group = mod._groups[groupIdx];
|
||||
if (!group.MoveOption(optionIdxFrom, optionIdxTo))
|
||||
return;
|
||||
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMoved, mod, groupIdx, optionIdxFrom, optionIdxTo);
|
||||
}
|
||||
|
||||
/// <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.Manipulations.Count == manipulations.Count
|
||||
&& subMod.Manipulations.All(m => manipulations.TryGetValue(m, out var old) && old.EntryEquals(m)))
|
||||
return;
|
||||
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
subMod.ManipulationData = manipulations;
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
/// <summary> Set the file redirections for a given option. Replaces existing redirections. </summary>
|
||||
public void OptionSetFiles(Mod mod, int groupIdx, int optionIdx, Dictionary<Utf8GamePath, FullPath> replacements)
|
||||
{
|
||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||
if (subMod.FileData.SetEquals(replacements))
|
||||
return;
|
||||
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
subMod.FileData = replacements;
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
/// <summary> Add additional file redirections to a given option, keeping already existing ones. Only fires an event if anything is actually added.</summary>
|
||||
public void OptionAddFiles(Mod mod, int groupIdx, int optionIdx, Dictionary<Utf8GamePath, FullPath> additions)
|
||||
{
|
||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||
var oldCount = subMod.FileData.Count;
|
||||
subMod.FileData.AddFrom(additions);
|
||||
if (oldCount != subMod.FileData.Count)
|
||||
{
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesAdded, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var subMod = GetSubMod(mod, groupIdx, optionIdx);
|
||||
if (subMod.FileSwapData.SetEquals(swaps))
|
||||
return;
|
||||
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||
subMod.FileSwapData = swaps;
|
||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, mod, groupIdx, optionIdx, -1);
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Verify that a new option group name is unique in this mod. </summary>
|
||||
public static bool VerifyFileName(Mod mod, IModGroup? group, string newName, bool message)
|
||||
{
|
||||
var path = newName.RemoveInvalidPathSymbols();
|
||||
if (path.Length != 0
|
||||
&& !mod.Groups.Any(o => !ReferenceEquals(o, group)
|
||||
&& string.Equals(o.Name.RemoveInvalidPathSymbols(), path, StringComparison.OrdinalIgnoreCase)))
|
||||
return true;
|
||||
|
||||
if (message)
|
||||
Penumbra.ChatService.NotificationMessage(
|
||||
$"Could not name option {newName} because option with same filename {path} already exists.",
|
||||
"Warning", NotificationType.Warning);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary> Update the indices stored in options from a given group on. </summary>
|
||||
private static void UpdateSubModPositions(Mod mod, int fromGroup)
|
||||
{
|
||||
foreach (var (group, groupIdx) in mod._groups.WithIndex().Skip(fromGroup))
|
||||
{
|
||||
foreach (var (o, optionIdx) in group.OfType<SubMod>().WithIndex())
|
||||
o.SetPosition(groupIdx, optionIdx);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Get the correct option for the given group and option index. </summary>
|
||||
private static SubMod GetSubMod(Mod mod, int groupIdx, int optionIdx)
|
||||
{
|
||||
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 )
|
||||
{
|
||||
SaveAllGroups();
|
||||
Penumbra.SaveService.SaveAllOptionGroups(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,8 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Utility;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Filesystem;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Import.Structs;
|
||||
|
|
@ -79,8 +77,8 @@ public partial class Mod
|
|||
Priority = priority,
|
||||
DefaultSettings = defaultSettings,
|
||||
};
|
||||
group.PrioritizedOptions.AddRange( subMods.OfType< SubMod >().Select( ( s, idx ) => ( s, idx ) ) );
|
||||
IModGroup.Save( group, baseFolder, index );
|
||||
group.PrioritizedOptions.AddRange( subMods.OfType< SubMod >().Select( ( s, idx ) => ( s, idx ) ) );
|
||||
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index));
|
||||
break;
|
||||
}
|
||||
case GroupType.Single:
|
||||
|
|
@ -93,7 +91,7 @@ public partial class Mod
|
|||
DefaultSettings = defaultSettings,
|
||||
};
|
||||
group.OptionData.AddRange( subMods.OfType< SubMod >() );
|
||||
IModGroup.Save( group, baseFolder, index );
|
||||
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ public partial class Mod
|
|||
var group = LoadModGroup( this, file, _groups.Count );
|
||||
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 );
|
||||
}
|
||||
else
|
||||
|
|
@ -114,32 +114,7 @@ public partial class Mod
|
|||
|
||||
if( changes )
|
||||
{
|
||||
SaveAllGroups();
|
||||
}
|
||||
}
|
||||
|
||||
// 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 );
|
||||
Penumbra.SaveService.SaveAllOptionGroups(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,8 +85,8 @@ public sealed partial class Mod
|
|||
mod._default.FileSwapData.Add(gamePath, swapPath);
|
||||
|
||||
mod._default.IncorporateMetaChanges(mod.ModPath, true);
|
||||
foreach (var (group, index) in mod.Groups.WithIndex())
|
||||
IModGroup.Save(group, mod.ModPath, index);
|
||||
foreach (var (_, index) in mod.Groups.WithIndex())
|
||||
Penumbra.SaveService.ImmediateSave(new ModSaveGroup(mod, index));
|
||||
|
||||
// Delete meta files.
|
||||
foreach (var file in seenMetaFiles.Where(f => f.Exists))
|
||||
|
|
|
|||
|
|
@ -2,24 +2,25 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using OtterGui.Filesystem;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public interface IModGroup : IEnumerable< ISubMod >
|
||||
public interface IModGroup : IEnumerable<ISubMod>
|
||||
{
|
||||
public const int MaxMultiOptions = 32;
|
||||
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public GroupType Type { get; }
|
||||
public int Priority { get; }
|
||||
public uint DefaultSettings { get; set; }
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public GroupType Type { get; }
|
||||
public int Priority { get; }
|
||||
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; }
|
||||
|
||||
|
|
@ -28,72 +29,76 @@ public interface IModGroup : IEnumerable< ISubMod >
|
|||
{
|
||||
GroupType.Single => Count > 1,
|
||||
GroupType.Multi => Count > 0,
|
||||
_ => false,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public string FileName( DirectoryInfo basePath, int groupIdx )
|
||||
=> Path.Combine( basePath.FullName, $"group_{groupIdx + 1:D3}_{Name.RemoveInvalidPathSymbols().ToLowerInvariant()}.json" );
|
||||
public IModGroup Convert(GroupType type);
|
||||
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 );
|
||||
if( !File.Exists( file ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
_basePath = mod.ModPath;
|
||||
if (_groupIdx < 0)
|
||||
_defaultMod = mod.Default;
|
||||
else
|
||||
_group = mod.Groups[groupIdx];
|
||||
_groupIdx = groupIdx;
|
||||
}
|
||||
|
||||
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}",
|
||||
() => SaveModGroup( group, basePath, groupIdx ) );
|
||||
_basePath = basePath;
|
||||
_group = group;
|
||||
_groupIdx = groupIdx;
|
||||
}
|
||||
|
||||
public static void Save( IModGroup group, DirectoryInfo basePath, int groupIdx )
|
||||
=> SaveModGroup( group, basePath, groupIdx );
|
||||
|
||||
private static void SaveModGroup( IModGroup group, DirectoryInfo basePath, int groupIdx )
|
||||
public ModSaveGroup(DirectoryInfo basePath, ISubMod @default)
|
||||
{
|
||||
var file = group.FileName( basePath, groupIdx );
|
||||
using var s = File.Exists( file ) ? File.Open( file, FileMode.Truncate ) : File.Open( file, FileMode.CreateNew );
|
||||
using var writer = new StreamWriter( s );
|
||||
using var j = new JsonTextWriter( writer ) { Formatting = Formatting.Indented };
|
||||
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
||||
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 )
|
||||
_basePath = basePath;
|
||||
_groupIdx = -1;
|
||||
_defaultMod = @default;
|
||||
}
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
=> fileNames.OptionGroupFile(_basePath.FullName, _groupIdx, _group?.Name ?? string.Empty);
|
||||
|
||||
public void Save(StreamWriter writer)
|
||||
{
|
||||
using var j = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
||||
if (_groupIdx >= 0)
|
||||
{
|
||||
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
|
||||
=> "Penumbra";
|
||||
|
||||
public static Logger Log { get; private set; } = null!;
|
||||
public static ChatService ChatService { get; private set; } = null!;
|
||||
public static SaveService SaveService { get; private set; } = null!;
|
||||
public static Configuration Config { get; private set; } = null!;
|
||||
public static Logger Log { get; private set; } = null!;
|
||||
public static ChatService ChatService { get; private set; } = null!;
|
||||
public static FilenameService Filenames { 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 CharacterUtility CharacterUtility { get; private set; } = null!;
|
||||
public static GameEventManager GameEvents { get; private set; } = null!;
|
||||
public static MetaFileManager MetaFileManager { get; private set; } = null!;
|
||||
public static ModManager ModManager { get; private set; } = null!;
|
||||
public static CollectionManager CollectionManager { get; private set; } = null!;
|
||||
public static ModManager ModManager { get; private set; } = null!;
|
||||
public static CollectionManager CollectionManager { get; private set; } = null!;
|
||||
public static TempCollectionManager TempCollections { get; private set; } = null!;
|
||||
public static TempModManager TempMods { 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 readonly PathResolver PathResolver;
|
||||
public readonly RedrawService RedrawService;
|
||||
public readonly ModFileSystem ModFileSystem;
|
||||
public HttpApi HttpApi = null!;
|
||||
internal ConfigWindow? ConfigWindow { get; private set; }
|
||||
private PenumbraWindowSystem? _windowSystem;
|
||||
private bool _disposed;
|
||||
public readonly PathResolver PathResolver;
|
||||
public readonly RedrawService RedrawService;
|
||||
public readonly ModFileSystem ModFileSystem;
|
||||
public HttpApi HttpApi = null!;
|
||||
internal ConfigWindow? ConfigWindow { get; private set; }
|
||||
private PenumbraWindowSystem? _windowSystem;
|
||||
private bool _disposed;
|
||||
|
||||
private readonly PenumbraNew _tmp;
|
||||
|
||||
|
|
@ -80,29 +81,30 @@ public class Penumbra : IDalamudPlugin
|
|||
{
|
||||
_tmp = new PenumbraNew(this, pluginInterface);
|
||||
ChatService = _tmp.Services.GetRequiredService<ChatService>();
|
||||
Filenames = _tmp.Services.GetRequiredService<FilenameService>();
|
||||
SaveService = _tmp.Services.GetRequiredService<SaveService>();
|
||||
Performance = _tmp.Services.GetRequiredService<PerformanceTracker>();
|
||||
ValidityChecker = _tmp.Services.GetRequiredService<ValidityChecker>();
|
||||
_tmp.Services.GetRequiredService<BackupService>();
|
||||
Config = _tmp.Services.GetRequiredService<Configuration>();
|
||||
CharacterUtility = _tmp.Services.GetRequiredService<CharacterUtility>();
|
||||
GameEvents = _tmp.Services.GetRequiredService<GameEventManager>();
|
||||
MetaFileManager = _tmp.Services.GetRequiredService<MetaFileManager>();
|
||||
Framework = _tmp.Services.GetRequiredService<FrameworkManager>();
|
||||
Actors = _tmp.Services.GetRequiredService<ActorService>().AwaitedService;
|
||||
Identifier = _tmp.Services.GetRequiredService<IdentifierService>().AwaitedService;
|
||||
GamePathParser = _tmp.Services.GetRequiredService<IGamePathParser>();
|
||||
StainService = _tmp.Services.GetRequiredService<StainService>();
|
||||
TempMods = _tmp.Services.GetRequiredService<TempModManager>();
|
||||
ResidentResources = _tmp.Services.GetRequiredService<ResidentResourceManager>();
|
||||
Config = _tmp.Services.GetRequiredService<Configuration>();
|
||||
CharacterUtility = _tmp.Services.GetRequiredService<CharacterUtility>();
|
||||
GameEvents = _tmp.Services.GetRequiredService<GameEventManager>();
|
||||
MetaFileManager = _tmp.Services.GetRequiredService<MetaFileManager>();
|
||||
Framework = _tmp.Services.GetRequiredService<FrameworkManager>();
|
||||
Actors = _tmp.Services.GetRequiredService<ActorService>().AwaitedService;
|
||||
Identifier = _tmp.Services.GetRequiredService<IdentifierService>().AwaitedService;
|
||||
GamePathParser = _tmp.Services.GetRequiredService<IGamePathParser>();
|
||||
StainService = _tmp.Services.GetRequiredService<StainService>();
|
||||
TempMods = _tmp.Services.GetRequiredService<TempModManager>();
|
||||
ResidentResources = _tmp.Services.GetRequiredService<ResidentResourceManager>();
|
||||
_tmp.Services.GetRequiredService<ResourceManagerService>();
|
||||
ModManager = _tmp.Services.GetRequiredService<ModManager>();
|
||||
CollectionManager = _tmp.Services.GetRequiredService<CollectionManager>();
|
||||
TempCollections = _tmp.Services.GetRequiredService<TempCollectionManager>();
|
||||
ModFileSystem = _tmp.Services.GetRequiredService<ModFileSystem>();
|
||||
RedrawService = _tmp.Services.GetRequiredService<RedrawService>();
|
||||
ModManager = _tmp.Services.GetRequiredService<ModManager>();
|
||||
CollectionManager = _tmp.Services.GetRequiredService<CollectionManager>();
|
||||
TempCollections = _tmp.Services.GetRequiredService<TempCollectionManager>();
|
||||
ModFileSystem = _tmp.Services.GetRequiredService<ModFileSystem>();
|
||||
RedrawService = _tmp.Services.GetRequiredService<RedrawService>();
|
||||
_tmp.Services.GetRequiredService<ResourceService>();
|
||||
ResourceLoader = _tmp.Services.GetRequiredService<ResourceLoader>();
|
||||
ResourceLoader = _tmp.Services.GetRequiredService<ResourceLoader>();
|
||||
using (var t = _tmp.Services.GetRequiredService<StartTracker>().Measure(StartTimeType.PathResolver))
|
||||
{
|
||||
PathResolver = _tmp.Services.GetRequiredService<PathResolver>();
|
||||
|
|
@ -112,7 +114,8 @@ public class Penumbra : IDalamudPlugin
|
|||
SetupApi();
|
||||
|
||||
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);
|
||||
Log.Information($"Loading native OtterTex assembly from {OtterTex.NativeDll.Directory}.");
|
||||
|
||||
|
|
@ -129,8 +132,8 @@ public class Penumbra : IDalamudPlugin
|
|||
private void SetupApi()
|
||||
{
|
||||
using var timer = _tmp.Services.GetRequiredService<StartTracker>().Measure(StartTimeType.Api);
|
||||
var api = _tmp.Services.GetRequiredService<IPenumbraApi>();
|
||||
HttpApi = _tmp.Services.GetRequiredService<HttpApi>();
|
||||
var api = _tmp.Services.GetRequiredService<IPenumbraApi>();
|
||||
HttpApi = _tmp.Services.GetRequiredService<HttpApi>();
|
||||
_tmp.Services.GetRequiredService<PenumbraIpcProviders>();
|
||||
if (Config.EnableHttpApi)
|
||||
HttpApi.CreateWebServer();
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ public class PenumbraNew
|
|||
// Add Mod Services
|
||||
services.AddSingleton<TempModManager>()
|
||||
.AddSingleton<ModDataEditor>()
|
||||
.AddSingleton<ModOptionEditor>()
|
||||
.AddSingleton<ModManager>()
|
||||
.AddSingleton<ModFileSystem>();
|
||||
|
||||
|
|
|
|||
|
|
@ -45,13 +45,22 @@ public class CommunicatorService : IDisposable
|
|||
/// </list> </summary>
|
||||
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 changed mod. </item>
|
||||
/// <item>Parameter is the old name of the mod in case of a name change, and null otherwise. </item>
|
||||
/// </list> </summary>
|
||||
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()
|
||||
{
|
||||
CollectionChange.Dispose();
|
||||
|
|
@ -60,5 +69,6 @@ public class CommunicatorService : IDisposable
|
|||
CreatingCharacterBase.Dispose();
|
||||
CreatedCharacterBase.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>
|
||||
public string ModMetaPath(string modDirectory)
|
||||
=> 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);
|
||||
Mod.Creator.CreateDefaultFiles(newDir);
|
||||
_modManager.AddMod(newDir);
|
||||
if (!_swapData.WriteMod(_modManager.Last(),
|
||||
if (!_swapData.WriteMod(_modManager, _modManager.Last(),
|
||||
_useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps))
|
||||
_modManager.DeleteMod(_modManager.Count - 1);
|
||||
}
|
||||
|
|
@ -296,16 +296,16 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
{
|
||||
if (_selectedGroup == null)
|
||||
{
|
||||
_modManager.AddModGroup(_mod, GroupType.Multi, _newGroupName);
|
||||
_modManager.OptionEditor.AddModGroup(_mod, GroupType.Multi, _newGroupName);
|
||||
_selectedGroup = _mod.Groups.Last();
|
||||
groupCreated = true;
|
||||
}
|
||||
|
||||
_modManager.AddOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _newOptionName);
|
||||
_modManager.OptionEditor.AddOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _newOptionName);
|
||||
optionCreated = true;
|
||||
optionFolderName = Directory.CreateDirectory(optionFolderName.FullName);
|
||||
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,
|
||||
_mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1))
|
||||
throw new Exception("Failure writing files for mod swap.");
|
||||
|
|
@ -317,11 +317,11 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
try
|
||||
{
|
||||
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)
|
||||
{
|
||||
_modManager.DeleteModGroup(_mod, _mod.Groups.IndexOf(_selectedGroup!));
|
||||
_modManager.OptionEditor.DeleteModGroup(_mod, _mod.Groups.IndexOf(_selectedGroup!));
|
||||
_selectedGroup = null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using OtterGui.Raii;
|
|||
using OtterGui.Widgets;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.UI.AdvancedWindow;
|
||||
using Penumbra.Util;
|
||||
|
||||
|
|
@ -20,7 +21,8 @@ namespace Penumbra.UI.ModsTab;
|
|||
public class ModPanelEditTab : ITab
|
||||
{
|
||||
private readonly ChatService _chat;
|
||||
private readonly ModManager _modManager;
|
||||
private readonly FilenameService _filenames;
|
||||
private readonly ModManager _modManager;
|
||||
private readonly ModFileSystem _fileSystem;
|
||||
private readonly ModFileSystemSelector _selector;
|
||||
private readonly ModEditWindow _editWindow;
|
||||
|
|
@ -34,14 +36,15 @@ public class ModPanelEditTab : ITab
|
|||
private Mod _mod = null!;
|
||||
|
||||
public ModPanelEditTab(ModManager modManager, ModFileSystemSelector selector, ModFileSystem fileSystem, ChatService chat,
|
||||
ModEditWindow editWindow, ModEditor editor)
|
||||
ModEditWindow editWindow, ModEditor editor, FilenameService filenames)
|
||||
{
|
||||
_modManager = modManager;
|
||||
_selector = selector;
|
||||
_fileSystem = fileSystem;
|
||||
_chat = chat;
|
||||
_editWindow = editWindow;
|
||||
_editor = editor;
|
||||
_modManager = modManager;
|
||||
_selector = selector;
|
||||
_fileSystem = fileSystem;
|
||||
_chat = chat;
|
||||
_editWindow = editWindow;
|
||||
_editor = editor;
|
||||
_filenames = filenames;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
@ -129,7 +132,7 @@ public class ModPanelEditTab : ITab
|
|||
if (ImGui.Button("Update Bibo Material", buttonSize))
|
||||
{
|
||||
_editor.LoadMod(_mod);
|
||||
_editor.MdlMaterialEditor.ReplaceAllMaterials("bibo", "b");
|
||||
_editor.MdlMaterialEditor.ReplaceAllMaterials("bibo", "b");
|
||||
_editor.MdlMaterialEditor.ReplaceAllMaterials("bibopube", "c");
|
||||
_editor.MdlMaterialEditor.SaveAllModels();
|
||||
_editWindow.UpdateModels();
|
||||
|
|
@ -189,7 +192,7 @@ public class ModPanelEditTab : ITab
|
|||
|
||||
var reducedSize = new Vector2(UiHelpers.InputTextMinusButton3, 0);
|
||||
if (ImGui.Button("Edit Description", reducedSize))
|
||||
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_mod, Input.Description));
|
||||
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_filenames, _mod, Input.Description));
|
||||
|
||||
ImGui.SameLine();
|
||||
var fileExists = File.Exists(_modManager.DataEditor.MetaFile(_mod));
|
||||
|
|
@ -235,13 +238,13 @@ public class ModPanelEditTab : ITab
|
|||
|
||||
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.";
|
||||
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), UiHelpers.IconButtonSize,
|
||||
tt, !nameValid, true))
|
||||
return;
|
||||
|
||||
modManager.AddModGroup(mod, GroupType.Single, _newGroupName);
|
||||
modManager.OptionEditor.AddModGroup(mod, GroupType.Single, _newGroupName);
|
||||
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>
|
||||
private static class MoveDirectory
|
||||
{
|
||||
private static string? _currentModDirectory;
|
||||
private static string? _currentModDirectory;
|
||||
private static ModManager.NewDirectoryState _state = ModManager.NewDirectoryState.Identical;
|
||||
|
||||
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>
|
||||
private static class DescriptionEdit
|
||||
{
|
||||
private const string PopupName = "Edit Description";
|
||||
private static string _newDescription = string.Empty;
|
||||
private static int _newDescriptionIdx = -1;
|
||||
private static int _newDescriptionOptionIdx = -1;
|
||||
private static Mod? _mod;
|
||||
private const string PopupName = "Edit Description";
|
||||
private static string _newDescription = string.Empty;
|
||||
private static int _newDescriptionIdx = -1;
|
||||
private static int _newDescriptionOptionIdx = -1;
|
||||
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;
|
||||
_newDescriptionOptionIdx = optionIdx;
|
||||
_newDescription = groupIdx < 0
|
||||
|
|
@ -353,9 +358,10 @@ public class ModPanelEditTab : ITab
|
|||
break;
|
||||
case >= 0:
|
||||
if (_newDescriptionOptionIdx < 0)
|
||||
modManager.ChangeGroupDescription(_mod, _newDescriptionIdx, _newDescription);
|
||||
modManager.OptionEditor.ChangeGroupDescription(_mod, _newDescriptionIdx, _newDescription);
|
||||
else
|
||||
modManager.ChangeOptionDescription(_mod, _newDescriptionIdx, _newDescriptionOptionIdx, _newDescription);
|
||||
modManager.OptionEditor.ChangeOptionDescription(_mod, _newDescriptionIdx, _newDescriptionOptionIdx,
|
||||
_newDescription);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -384,18 +390,18 @@ public class ModPanelEditTab : ITab
|
|||
.Push(ImGuiStyleVar.ItemSpacing, _itemSpacing);
|
||||
|
||||
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");
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), UiHelpers.IconButtonSize,
|
||||
"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();
|
||||
|
||||
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");
|
||||
|
||||
|
|
@ -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}.";
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowUp.ToIconString(), UiHelpers.IconButtonSize,
|
||||
tt, groupIdx == 0, true))
|
||||
_delayedActions.Enqueue(() => _modManager.MoveModGroup(_mod, groupIdx, groupIdx - 1));
|
||||
_delayedActions.Enqueue(() => _modManager.OptionEditor.MoveModGroup(_mod, groupIdx, groupIdx - 1));
|
||||
|
||||
ImGui.SameLine();
|
||||
tt = groupIdx == _mod.Groups.Count - 1
|
||||
|
|
@ -413,16 +419,16 @@ public class ModPanelEditTab : ITab
|
|||
: $"Move this group down to group {groupIdx + 2}.";
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowDown.ToIconString(), UiHelpers.IconButtonSize,
|
||||
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();
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Edit.ToIconString(), UiHelpers.IconButtonSize,
|
||||
"Edit group description.", false, true))
|
||||
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_mod, groupIdx));
|
||||
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_filenames, _mod, groupIdx));
|
||||
|
||||
ImGui.SameLine();
|
||||
var fileName = group.FileName(_mod.ModPath, groupIdx);
|
||||
var fileName = _filenames.OptionGroupFile(_mod, groupIdx);
|
||||
var fileExists = File.Exists(fileName);
|
||||
tt = fileExists
|
||||
? $"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 (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.");
|
||||
}
|
||||
|
|
@ -499,7 +505,7 @@ public class ModPanelEditTab : ITab
|
|||
{
|
||||
var isDefaultOption = ((group.DefaultSettings >> optionIdx) & 1) != 0;
|
||||
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));
|
||||
|
||||
|
|
@ -508,17 +514,17 @@ public class ModPanelEditTab : ITab
|
|||
|
||||
ImGui.TableNextColumn();
|
||||
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();
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Edit.ToIconString(), UiHelpers.IconButtonSize, "Edit option description.",
|
||||
false, true))
|
||||
panel._delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(panel._mod, groupIdx, optionIdx));
|
||||
panel._delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(panel._filenames, panel._mod, groupIdx, optionIdx));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), UiHelpers.IconButtonSize,
|
||||
"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();
|
||||
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,
|
||||
50 * UiHelpers.Scale))
|
||||
panel._modManager.ChangeOptionPriority(panel._mod, groupIdx, optionIdx, priority);
|
||||
panel._modManager.OptionEditor.ChangeOptionPriority(panel._mod, groupIdx, optionIdx, priority);
|
||||
|
||||
ImGuiUtil.HoverTooltip("Option priority.");
|
||||
}
|
||||
|
|
@ -560,7 +566,7 @@ public class ModPanelEditTab : ITab
|
|||
tt, !(canAddGroup && validName), true))
|
||||
return;
|
||||
|
||||
panel._modManager.AddOption(mod, groupIdx, _newOptionName);
|
||||
panel._modManager.OptionEditor.AddOption(mod, groupIdx, _newOptionName);
|
||||
_newOptionName = string.Empty;
|
||||
}
|
||||
|
||||
|
|
@ -591,7 +597,7 @@ public class ModPanelEditTab : ITab
|
|||
if (_dragDropGroupIdx == groupIdx)
|
||||
{
|
||||
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
|
||||
{
|
||||
|
|
@ -604,9 +610,9 @@ public class ModPanelEditTab : ITab
|
|||
var priority = sourceGroup.OptionPriority(_dragDropOptionIdx);
|
||||
panel._delayedActions.Enqueue(() =>
|
||||
{
|
||||
panel._modManager.DeleteOption(panel._mod, sourceGroupIdx, sourceOption);
|
||||
panel._modManager.AddOption(panel._mod, groupIdx, option, priority);
|
||||
panel._modManager.MoveOption(panel._mod, groupIdx, currentCount, optionIdx);
|
||||
panel._modManager.OptionEditor.DeleteOption(panel._mod, sourceGroupIdx, sourceOption);
|
||||
panel._modManager.OptionEditor.AddOption(panel._mod, groupIdx, option, priority);
|
||||
panel._modManager.OptionEditor.MoveOption(panel._mod, groupIdx, currentCount, optionIdx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -633,12 +639,12 @@ public class ModPanelEditTab : ITab
|
|||
return;
|
||||
|
||||
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;
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, !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();
|
||||
if (!canSwitchToMulti)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.IO;
|
|||
using System.Text;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Log;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Util;
|
||||
|
|
@ -94,4 +95,24 @@ public class SaveService
|
|||
_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