Use ModManager2

This commit is contained in:
Ottermandias 2023-03-30 23:51:13 +02:00
parent 70c1a2604f
commit afa11f85e2
16 changed files with 131 additions and 542 deletions

View file

@ -56,6 +56,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
{ {
if (value == null) if (value == null)
return; return;
CheckInitialized(); CheckInitialized();
_communicator.CreatingCharacterBase.Event += new Action<nint, string, nint, nint, nint>(value); _communicator.CreatingCharacterBase.Event += new Action<nint, string, nint, nint, nint>(value);
} }
@ -63,6 +64,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
{ {
if (value == null) if (value == null)
return; return;
CheckInitialized(); CheckInitialized();
_communicator.CreatingCharacterBase.Event -= new Action<nint, string, nint, nint, nint>(value); _communicator.CreatingCharacterBase.Event -= new Action<nint, string, nint, nint, nint>(value);
} }
@ -74,6 +76,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
{ {
if (value == null) if (value == null)
return; return;
CheckInitialized(); CheckInitialized();
_communicator.CreatedCharacterBase.Event += new Action<nint, string, nint>(value); _communicator.CreatedCharacterBase.Event += new Action<nint, string, nint>(value);
} }
@ -81,6 +84,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
{ {
if (value == null) if (value == null)
return; return;
CheckInitialized(); CheckInitialized();
_communicator.CreatedCharacterBase.Event -= new Action<nint, string, nint>(value); _communicator.CreatedCharacterBase.Event -= new Action<nint, string, nint>(value);
} }
@ -93,10 +97,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
private Penumbra _penumbra; private Penumbra _penumbra;
private Lumina.GameData? _lumina; private Lumina.GameData? _lumina;
private ModManager _modManager; private ModManager _modManager;
private ResourceLoader _resourceLoader; private ResourceLoader _resourceLoader;
private Configuration _config; private Configuration _config;
private CollectionManager _collectionManager; private CollectionManager _collectionManager;
private DalamudServices _dalamud; private DalamudServices _dalamud;
private TempCollectionManager _tempCollections; private TempCollectionManager _tempCollections;
private TempModManager _tempMods; private TempModManager _tempMods;
@ -108,18 +112,18 @@ public class PenumbraApi : IDisposable, IPenumbraApi
Configuration config, CollectionManager collectionManager, DalamudServices dalamud, TempCollectionManager tempCollections, Configuration config, CollectionManager collectionManager, DalamudServices dalamud, TempCollectionManager tempCollections,
TempModManager tempMods, ActorService actors, CollectionResolver collectionResolver, CutsceneService cutsceneService) TempModManager tempMods, ActorService actors, CollectionResolver collectionResolver, CutsceneService cutsceneService)
{ {
_communicator = communicator; _communicator = communicator;
_penumbra = penumbra; _penumbra = penumbra;
_modManager = modManager; _modManager = modManager;
_resourceLoader = resourceLoader; _resourceLoader = resourceLoader;
_config = config; _config = config;
_collectionManager = collectionManager; _collectionManager = collectionManager;
_dalamud = dalamud; _dalamud = dalamud;
_tempCollections = tempCollections; _tempCollections = tempCollections;
_tempMods = tempMods; _tempMods = tempMods;
_actors = actors; _actors = actors;
_collectionResolver = collectionResolver; _collectionResolver = collectionResolver;
_cutsceneService = cutsceneService; _cutsceneService = cutsceneService;
_lumina = (Lumina.GameData?)_dalamud.GameData.GetType() _lumina = (Lumina.GameData?)_dalamud.GameData.GetType()
.GetField("gameData", BindingFlags.Instance | BindingFlags.NonPublic) .GetField("gameData", BindingFlags.Instance | BindingFlags.NonPublic)
@ -129,13 +133,13 @@ public class PenumbraApi : IDisposable, IPenumbraApi
_communicator.CollectionChange.Event += SubscribeToNewCollections; _communicator.CollectionChange.Event += SubscribeToNewCollections;
_resourceLoader.ResourceLoaded += OnResourceLoaded; _resourceLoader.ResourceLoaded += OnResourceLoaded;
_modManager.ModPathChanged += ModPathChangeSubscriber; _communicator.ModPathChanged.Event += ModPathChangeSubscriber;
} }
public unsafe void Dispose() public unsafe void Dispose()
{ {
if (!Valid) if (!Valid)
return; return;
foreach (var collection in _collectionManager) foreach (var collection in _collectionManager)
{ {
@ -145,7 +149,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
_resourceLoader.ResourceLoaded -= OnResourceLoaded; _resourceLoader.ResourceLoaded -= OnResourceLoaded;
_communicator.CollectionChange.Event -= SubscribeToNewCollections; _communicator.CollectionChange.Event -= SubscribeToNewCollections;
_modManager.ModPathChanged -= ModPathChangeSubscriber; _communicator.ModPathChanged.Event -= ModPathChangeSubscriber;
_lumina = null; _lumina = null;
_communicator = null!; _communicator = null!;
_penumbra = null!; _penumbra = null!;
@ -182,12 +186,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi
add add
{ {
CheckInitialized(); CheckInitialized();
_modManager.ModDirectoryChanged += value; _communicator.ModDirectoryChanged.Event += value;
} }
remove remove
{ {
CheckInitialized(); CheckInitialized();
_modManager.ModDirectoryChanged -= value; _communicator.ModDirectoryChanged.Event -= value;
} }
} }
@ -542,8 +546,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public unsafe (nint, string) GetDrawObjectInfo(nint drawObject) public unsafe (nint, string) GetDrawObjectInfo(nint drawObject)
{ {
CheckInitialized(); CheckInitialized();
var data = _collectionResolver.IdentifyCollection((DrawObject*) drawObject, true); var data = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
return (data.AssociatedGameObject, data.ModCollection.Name); return (data.AssociatedGameObject, data.ModCollection.Name);
} }
@ -592,7 +596,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return PenumbraApiEc.ModMissing; return PenumbraApiEc.ModMissing;
_modManager.ReloadMod(mod.Index); _modManager.ReloadMod(mod);
return PenumbraApiEc.Success; return PenumbraApiEc.Success;
} }
@ -613,7 +617,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return PenumbraApiEc.NothingChanged; return PenumbraApiEc.NothingChanged;
_modManager.DeleteMod(mod.Index); _modManager.DeleteMod(mod);
return PenumbraApiEc.Success; return PenumbraApiEc.Success;
} }

View file

@ -9,7 +9,6 @@ using System.IO;
using System.Linq; using System.Linq;
using Penumbra.Api; using Penumbra.Api;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.Interop;
using Penumbra.Interop.Services; using Penumbra.Interop.Services;
using Penumbra.Services; using Penumbra.Services;
using Penumbra.Util; using Penumbra.Util;
@ -71,10 +70,10 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
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; _communicator.ModDiscoveryStarted.Event += OnModDiscoveryStarted;
_modManager.ModDiscoveryFinished += OnModDiscoveryFinished; _communicator.ModDiscoveryFinished.Event += OnModDiscoveryFinished;
_communicator.ModOptionChanged.Event += OnModOptionsChanged; _communicator.ModOptionChanged.Event += OnModOptionsChanged;
_modManager.ModPathChanged += OnModPathChange; _communicator.ModPathChanged.Event += OnModPathChange;
_communicator.CollectionChange.Event += SaveOnChange; _communicator.CollectionChange.Event += SaveOnChange;
_communicator.TemporaryGlobalModChange.Event += OnGlobalModChange; _communicator.TemporaryGlobalModChange.Event += OnGlobalModChange;
ReadCollections(files); ReadCollections(files);
@ -87,10 +86,10 @@ public sealed partial class CollectionManager : IDisposable, IEnumerable<ModColl
{ {
_communicator.CollectionChange.Event -= SaveOnChange; _communicator.CollectionChange.Event -= SaveOnChange;
_communicator.TemporaryGlobalModChange.Event -= OnGlobalModChange; _communicator.TemporaryGlobalModChange.Event -= OnGlobalModChange;
_modManager.ModDiscoveryStarted -= OnModDiscoveryStarted; _communicator.ModDiscoveryStarted.Event -= OnModDiscoveryStarted;
_modManager.ModDiscoveryFinished -= OnModDiscoveryFinished; _communicator.ModDiscoveryFinished.Event -= OnModDiscoveryFinished;
_communicator.ModOptionChanged.Event -= OnModOptionsChanged; _communicator.ModOptionChanged.Event -= OnModOptionsChanged;
_modManager.ModPathChanged -= OnModPathChange; _communicator.ModPathChanged.Event -= OnModPathChange;
} }
private void OnGlobalModChange(TemporaryMod mod, bool created, bool removed) private void OnGlobalModChange(TemporaryMod mod, bool created, bool removed)

View file

@ -131,7 +131,7 @@ public class ModBackup
ZipFile.ExtractToDirectory(Name, _mod.ModPath.FullName); ZipFile.ExtractToDirectory(Name, _mod.ModPath.FullName);
Penumbra.Log.Debug($"Extracted exported file {Name} to {_mod.ModPath.FullName}."); Penumbra.Log.Debug($"Extracted exported file {Name} to {_mod.ModPath.FullName}.");
modManager.ReloadMod(_mod.Index); modManager.ReloadMod(_mod);
} }
catch (Exception e) catch (Exception e)
{ {

View file

@ -1,24 +1,27 @@
using System; using System;
using System.IO; using System.IO;
using Penumbra.Services;
namespace Penumbra.Mods; namespace Penumbra.Mods;
public class ExportManager : IDisposable public class ExportManager : IDisposable
{ {
private readonly Configuration _config; private readonly Configuration _config;
private readonly ModManager _modManager; private readonly CommunicatorService _communicator;
private readonly ModManager _modManager;
private DirectoryInfo? _exportDirectory; private DirectoryInfo? _exportDirectory;
public DirectoryInfo ExportDirectory public DirectoryInfo ExportDirectory
=> _exportDirectory ?? _modManager.BasePath; => _exportDirectory ?? _modManager.BasePath;
public ExportManager(Configuration config, ModManager modManager) public ExportManager(Configuration config, CommunicatorService communicator, ModManager modManager)
{ {
_config = config; _config = config;
_modManager = modManager; _communicator = communicator;
_modManager = modManager;
UpdateExportDirectory(_config.ExportDirectory, false); UpdateExportDirectory(_config.ExportDirectory, false);
_modManager.ModPathChanged += OnModPathChange; _communicator.ModPathChanged.Event += OnModPathChange;
} }
/// <inheritdoc cref="UpdateExportDirectory(string, bool)"/> /// <inheritdoc cref="UpdateExportDirectory(string, bool)"/>
@ -73,7 +76,7 @@ public class ExportManager : IDisposable
} }
public void Dispose() public void Dispose()
=> _modManager.ModPathChanged -= OnModPathChange; => _communicator.ModPathChanged.Event -= OnModPathChange;
/// <summary> Automatically migrate the backup file to the new name if any exists. </summary> /// <summary> Automatically migrate the backup file to the new name if any exists. </summary>
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory, private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,

View file

@ -1,203 +0,0 @@
using System;
using System.IO;
using System.Linq;
namespace Penumbra.Mods;
public partial class ModManager
{
public delegate void ModPathChangeDelegate(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,
DirectoryInfo? newDirectory);
public event ModPathChangeDelegate ModPathChanged;
/// <summary>
/// Rename/Move a mod directory.
/// Updates all collection settings and sort order settings.
/// </summary>
public void MoveModDirectory(int idx, string newName)
{
var mod = this[idx];
var oldName = mod.Name;
var oldDirectory = mod.ModPath;
switch (NewDirectoryValid(oldDirectory.Name, newName, out var dir))
{
case NewDirectoryState.NonExisting:
// Nothing to do
break;
case NewDirectoryState.ExistsEmpty:
try
{
Directory.Delete(dir!.FullName);
}
catch (Exception e)
{
Penumbra.Log.Error($"Could not delete empty directory {dir!.FullName} to move {mod.Name} to it:\n{e}");
return;
}
break;
// Should be caught beforehand.
case NewDirectoryState.ExistsNonEmpty:
case NewDirectoryState.ExistsAsFile:
case NewDirectoryState.ContainsInvalidSymbols:
// Nothing to do at all.
case NewDirectoryState.Identical:
default:
return;
}
try
{
Directory.Move(oldDirectory.FullName, dir!.FullName);
}
catch (Exception e)
{
Penumbra.Log.Error($"Could not move {mod.Name} from {oldDirectory.Name} to {dir!.Name}:\n{e}");
return;
}
DataEditor.MoveDataFile(oldDirectory, dir);
dir.Refresh();
mod.ModPath = dir;
if (!mod.Reload(this, false, out var metaChange))
{
Penumbra.Log.Error($"Error reloading moved mod {mod.Name}.");
return;
}
ModPathChanged.Invoke(ModPathChangeType.Moved, mod, oldDirectory, dir);
if (metaChange != ModDataChangeType.None)
_communicator.ModDataChanged.Invoke(metaChange, mod, oldName);
}
/// <summary>
/// Reload a mod without changing its base directory.
/// If the base directory does not exist anymore, the mod will be deleted.
/// </summary>
public void ReloadMod(int idx)
{
var mod = this[idx];
var oldName = mod.Name;
ModPathChanged.Invoke(ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath);
if (!mod.Reload(this, true, out var metaChange))
{
Penumbra.Log.Warning(mod.Name.Length == 0
? $"Reloading mod {oldName} has failed, new name is empty. Deleting instead."
: $"Reloading mod {oldName} failed, {mod.ModPath.FullName} does not exist anymore or it ha. Deleting instead.");
DeleteMod(idx);
return;
}
ModPathChanged.Invoke(ModPathChangeType.Reloaded, mod, mod.ModPath, mod.ModPath);
if (metaChange != ModDataChangeType.None)
_communicator.ModDataChanged.Invoke(metaChange, mod, oldName);
}
/// <summary>
/// Delete a mod by its index. The event is invoked before the mod is removed from the list.
/// Deletes from filesystem as well as from internal data.
/// Updates indices of later mods.
/// </summary>
public void DeleteMod(int idx)
{
var mod = this[idx];
if (Directory.Exists(mod.ModPath.FullName))
try
{
Directory.Delete(mod.ModPath.FullName, true);
Penumbra.Log.Debug($"Deleted directory {mod.ModPath.FullName} for {mod.Name}.");
}
catch (Exception e)
{
Penumbra.Log.Error($"Could not delete the mod {mod.ModPath.Name}:\n{e}");
}
ModPathChanged.Invoke(ModPathChangeType.Deleted, mod, mod.ModPath, null);
_mods.RemoveAt(idx);
foreach (var remainingMod in _mods.Skip(idx))
--remainingMod.Index;
Penumbra.Log.Debug($"Deleted mod {mod.Name}.");
}
/// <summary> Load a new mod and add it to the manager if successful. </summary>
public void AddMod(DirectoryInfo modFolder)
{
if (_mods.Any(m => m.ModPath.Name == modFolder.Name))
return;
Mod.Creator.SplitMultiGroups(modFolder);
var mod = Mod.LoadMod(this, modFolder, true);
if (mod == null)
return;
mod.Index = _mods.Count;
_mods.Add(mod);
ModPathChanged.Invoke(ModPathChangeType.Added, mod, null, mod.ModPath);
Penumbra.Log.Debug($"Added new mod {mod.Name} from {modFolder.FullName}.");
}
public enum NewDirectoryState
{
NonExisting,
ExistsEmpty,
ExistsNonEmpty,
ExistsAsFile,
ContainsInvalidSymbols,
Identical,
Empty,
}
/// <summary> Return the state of the new potential name of a directory. </summary>
public NewDirectoryState NewDirectoryValid(string oldName, string newName, out DirectoryInfo? directory)
{
directory = null;
if (newName.Length == 0)
return NewDirectoryState.Empty;
if (oldName == newName)
return NewDirectoryState.Identical;
var fixedNewName = Mod.Creator.ReplaceBadXivSymbols(newName);
if (fixedNewName != newName)
return NewDirectoryState.ContainsInvalidSymbols;
directory = new DirectoryInfo(Path.Combine(BasePath.FullName, fixedNewName));
if (File.Exists(directory.FullName))
return NewDirectoryState.ExistsAsFile;
if (!Directory.Exists(directory.FullName))
return NewDirectoryState.NonExisting;
if (directory.EnumerateFileSystemInfos().Any())
return NewDirectoryState.ExistsNonEmpty;
return NewDirectoryState.ExistsEmpty;
}
/// <summary> Add new mods to NewMods and remove deleted mods from NewMods. </summary>
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory,
DirectoryInfo? newDirectory)
{
switch (type)
{
case ModPathChangeType.Added:
NewMods.Add(mod);
break;
case ModPathChangeType.Deleted:
NewMods.Remove(mod);
break;
case ModPathChangeType.Moved:
if (oldDirectory != null && newDirectory != null)
DataEditor.MoveDataFile(oldDirectory, newDirectory);
break;
}
}
}

View file

@ -1,102 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;
namespace Penumbra.Mods;
public sealed partial class ModManager
{
public DirectoryInfo BasePath { get; private set; } = null!;
public bool Valid { get; private set; }
public event Action? ModDiscoveryStarted;
public event Action? ModDiscoveryFinished;
public event Action<string, bool> ModDirectoryChanged;
// Change the mod base directory and discover available mods.
public void DiscoverMods(string newDir)
{
SetBaseDirectory(newDir, false);
DiscoverMods();
}
// Set the mod base directory.
// If its not the first time, check if it is the same directory as before.
// Also checks if the directory is available and tries to create it if it is not.
private void SetBaseDirectory(string newPath, bool firstTime)
{
if (!firstTime && string.Equals(newPath, Penumbra.Config.ModDirectory, StringComparison.OrdinalIgnoreCase))
return;
if (newPath.Length == 0)
{
Valid = false;
BasePath = new DirectoryInfo(".");
if (Penumbra.Config.ModDirectory != BasePath.FullName)
ModDirectoryChanged.Invoke(string.Empty, false);
}
else
{
var newDir = new DirectoryInfo(newPath);
if (!newDir.Exists)
try
{
Directory.CreateDirectory(newDir.FullName);
newDir.Refresh();
}
catch (Exception e)
{
Penumbra.Log.Error($"Could not create specified mod directory {newDir.FullName}:\n{e}");
}
BasePath = newDir;
Valid = Directory.Exists(newDir.FullName);
if (Penumbra.Config.ModDirectory != BasePath.FullName)
ModDirectoryChanged.Invoke(BasePath.FullName, Valid);
}
}
private static void OnModDirectoryChange(string newPath, bool _)
{
Penumbra.Log.Information($"Set new mod base directory from {Penumbra.Config.ModDirectory} to {newPath}.");
Penumbra.Config.ModDirectory = newPath;
Penumbra.Config.Save();
}
// Discover new mods.
public void DiscoverMods()
{
NewMods.Clear();
ModDiscoveryStarted?.Invoke();
_mods.Clear();
BasePath.Refresh();
if (Valid && BasePath.Exists)
{
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = Environment.ProcessorCount / 2,
};
var queue = new ConcurrentQueue<Mod>();
Parallel.ForEach(BasePath.EnumerateDirectories(), options, dir =>
{
var mod = Mod.LoadMod(this, dir, false);
if (mod != null)
queue.Enqueue(mod);
});
foreach (var mod in queue)
{
mod.Index = _mods.Count;
_mods.Add(mod);
}
}
ModDiscoveryFinished?.Invoke();
Penumbra.Log.Information("Rediscovered mods.");
if (MigrateModBackups)
ModBackup.MigrateZipToPmp(this);
}
}

View file

@ -1,15 +1,12 @@
using System; using System;
using System.Collections; using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Penumbra.Services; using Penumbra.Services;
using Penumbra.Util;
namespace Penumbra.Mods; namespace Penumbra.Mods;
/// <summary> Describes the state of a potential move-target for a mod. </summary> /// <summary> Describes the state of a potential move-target for a mod. </summary>
public enum NewDirectoryState public enum NewDirectoryState
{ {
@ -20,25 +17,27 @@ public enum NewDirectoryState
ContainsInvalidSymbols, ContainsInvalidSymbols,
Identical, Identical,
Empty, Empty,
} }
public sealed class ModManager2 : ModStorage public sealed class ModManager : ModStorage
{ {
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 readonly ModOptionEditor OptionEditor;
public DirectoryInfo BasePath { get; private set; } = null!; public DirectoryInfo BasePath { get; private set; } = null!;
public bool Valid { get; private set; } public bool Valid { get; private set; }
public ModManager2(Configuration config, CommunicatorService communicator, ModDataEditor dataEditor, ModOptionEditor optionEditor) public ModManager(Configuration config, CommunicatorService communicator, ModDataEditor dataEditor, ModOptionEditor optionEditor)
{ {
_config = config; _config = config;
_communicator = communicator; _communicator = communicator;
DataEditor = dataEditor; DataEditor = dataEditor;
OptionEditor = optionEditor; OptionEditor = optionEditor;
SetBaseDirectory(config.ModDirectory, true);
DiscoverMods();
} }
/// <summary> Change the mod base directory and discover available mods. </summary> /// <summary> Change the mod base directory and discover available mods. </summary>
@ -75,7 +74,7 @@ public sealed class ModManager2 : ModStorage
return; return;
Mod.Creator.SplitMultiGroups(modFolder); Mod.Creator.SplitMultiGroups(modFolder);
var mod = Mod.LoadMod(Penumbra.ModManager, modFolder, true); var mod = Mod.LoadMod(this, modFolder, true);
if (mod == null) if (mod == null)
return; return;
@ -133,16 +132,16 @@ public sealed class ModManager2 : ModStorage
_communicator.ModPathChanged.Invoke(ModPathChangeType.Reloaded, mod, mod.ModPath, mod.ModPath); _communicator.ModPathChanged.Invoke(ModPathChangeType.Reloaded, mod, mod.ModPath, mod.ModPath);
if (metaChange != ModDataChangeType.None) if (metaChange != ModDataChangeType.None)
_communicator.ModDataChanged.Invoke(metaChange, mod, oldName); _communicator.ModDataChanged.Invoke(metaChange, mod, oldName);
} }
/// <summary> /// <summary>
/// Rename/Move a mod directory. /// Rename/Move a mod directory.
/// Updates all collection settings and sort order settings. /// Updates all collection settings and sort order settings.
/// </summary> /// </summary>
public void MoveModDirectory(Mod mod, string newName) public void MoveModDirectory(Mod mod, string newName)
{ {
var oldName = mod.Name; var oldName = mod.Name;
var oldDirectory = mod.ModPath; var oldDirectory = mod.ModPath;
switch (NewDirectoryValid(oldDirectory.Name, newName, out var dir)) switch (NewDirectoryValid(oldDirectory.Name, newName, out var dir))
@ -195,8 +194,8 @@ public sealed class ModManager2 : ModStorage
_communicator.ModPathChanged.Invoke(ModPathChangeType.Moved, mod, oldDirectory, dir); _communicator.ModPathChanged.Invoke(ModPathChangeType.Moved, mod, oldDirectory, dir);
if (metaChange != ModDataChangeType.None) if (metaChange != ModDataChangeType.None)
_communicator.ModDataChanged.Invoke(metaChange, mod, oldName); _communicator.ModDataChanged.Invoke(metaChange, mod, oldName);
} }
/// <summary> Return the state of the new potential name of a directory. </summary> /// <summary> Return the state of the new potential name of a directory. </summary>
public NewDirectoryState NewDirectoryValid(string oldName, string newName, out DirectoryInfo? directory) public NewDirectoryState NewDirectoryValid(string oldName, string newName, out DirectoryInfo? directory)
{ {
@ -243,16 +242,16 @@ public sealed class ModManager2 : ModStorage
break; break;
} }
} }
public void Dispose() public void Dispose()
{ } { }
/// <summary> /// <summary>
/// Set the mod base directory. /// Set the mod base directory.
/// If its not the first time, check if it is the same directory as before. /// If its not the first time, check if it is the same directory as before.
/// Also checks if the directory is available and tries to create it if it is not. /// Also checks if the directory is available and tries to create it if it is not.
/// </summary> /// </summary>
private void SetBaseDirectory(string newPath, bool firstTime) private void SetBaseDirectory(string newPath, bool firstTime)
{ {
if (!firstTime && string.Equals(newPath, _config.ModDirectory, StringComparison.OrdinalIgnoreCase)) if (!firstTime && string.Equals(newPath, _config.ModDirectory, StringComparison.OrdinalIgnoreCase))
@ -260,7 +259,7 @@ public sealed class ModManager2 : ModStorage
if (newPath.Length == 0) if (newPath.Length == 0)
{ {
Valid = false; Valid = false;
BasePath = new DirectoryInfo("."); BasePath = new DirectoryInfo(".");
if (_config.ModDirectory != BasePath.FullName) if (_config.ModDirectory != BasePath.FullName)
TriggerModDirectoryChange(string.Empty, false); TriggerModDirectoryChange(string.Empty, false);
@ -280,8 +279,8 @@ public sealed class ModManager2 : ModStorage
} }
BasePath = newDir; BasePath = newDir;
Valid = Directory.Exists(newDir.FullName); Valid = Directory.Exists(newDir.FullName);
if (_config.ModDirectory != BasePath.FullName) if (!firstTime && _config.ModDirectory != BasePath.FullName)
TriggerModDirectoryChange(BasePath.FullName, Valid); TriggerModDirectoryChange(BasePath.FullName, Valid);
} }
} }
@ -295,10 +294,9 @@ public sealed class ModManager2 : ModStorage
} }
/// <summary>
/// <summary>
/// Iterate through available mods with multiple threads and queue their loads, /// Iterate through available mods with multiple threads and queue their loads,
/// then add the mods from the queue. /// then add the mods from the queue.
/// </summary> /// </summary>
private void ScanMods() private void ScanMods()
{ {
@ -309,7 +307,7 @@ public sealed class ModManager2 : ModStorage
var queue = new ConcurrentQueue<Mod>(); var queue = new ConcurrentQueue<Mod>();
Parallel.ForEach(BasePath.EnumerateDirectories(), options, dir => Parallel.ForEach(BasePath.EnumerateDirectories(), options, dir =>
{ {
var mod = Mod.LoadMod(Penumbra.ModManager, dir, false); var mod = Mod.LoadMod(this, dir, false);
if (mod != null) if (mod != null)
queue.Enqueue(mod); queue.Enqueue(mod);
}); });
@ -321,111 +319,3 @@ public sealed class ModManager2 : ModStorage
} }
} }
} }
public sealed partial class ModManager : IReadOnlyList<Mod>, IDisposable
{
// Set when reading Config and migrating from v4 to v5.
public static bool MigrateModBackups = false;
// 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.
public readonly HashSet<Mod> NewMods = new();
private readonly List<Mod> _mods = 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();
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,
ModOptionEditor optionEditor)
{
using var timer = time.Measure(StartTimeType.Mods);
_config = config;
_communicator = communicator;
DataEditor = dataEditor;
OptionEditor = optionEditor;
ModDirectoryChanged += OnModDirectoryChange;
SetBaseDirectory(config.ModDirectory, true);
_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.
public bool TryGetMod(string modDirectory, string modName, [NotNullWhen(true)] out Mod? mod)
{
mod = null;
foreach (var m in _mods)
{
if (string.Equals(m.ModPath.Name, modDirectory, StringComparison.OrdinalIgnoreCase))
{
mod = m;
return true;
}
if (m.Name == modName)
mod ??= m;
}
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,
};
}
}

View file

@ -20,7 +20,6 @@ public class ModCacheManager : IDisposable, IReadOnlyList<ModCache>
private readonly List<ModCache> _cache = new(); private readonly List<ModCache> _cache = new();
// TODO ModManager2
public ModCacheManager(CommunicatorService communicator, IdentifierService identifier, ModManager modManager) public ModCacheManager(CommunicatorService communicator, IdentifierService identifier, ModManager modManager)
{ {
_communicator = communicator; _communicator = communicator;

View file

@ -12,7 +12,7 @@ namespace Penumbra.Mods;
public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
{ {
private readonly ModManager _modManager; private readonly ModManager _modManager;
private readonly CommunicatorService _communicator; private readonly CommunicatorService _communicator;
private readonly FilenameService _files; private readonly FilenameService _files;
@ -23,17 +23,17 @@ public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
_communicator = communicator; _communicator = communicator;
_files = files; _files = files;
Reload(); Reload();
Changed += OnChange; Changed += OnChange;
_modManager.ModDiscoveryFinished += Reload; _communicator.ModDiscoveryFinished.Event += Reload;
_communicator.ModDataChanged.Event += OnDataChange; _communicator.ModDataChanged.Event += OnDataChange;
_modManager.ModPathChanged += OnModPathChange; _communicator.ModPathChanged.Event += OnModPathChange;
} }
public void Dispose() public void Dispose()
{ {
_modManager.ModPathChanged -= OnModPathChange; _communicator.ModPathChanged.Event -= OnModPathChange;
_modManager.ModDiscoveryFinished -= Reload; _communicator.ModDiscoveryFinished.Event -= Reload;
_communicator.ModDataChanged.Event -= OnDataChange; _communicator.ModDataChanged.Event -= OnDataChange;
} }
public struct ImportDate : ISortMode<Mod> public struct ImportDate : ISortMode<Mod>

View file

@ -94,7 +94,8 @@ public class PenumbraNew
.AddSingleton<ModOptionEditor>() .AddSingleton<ModOptionEditor>()
.AddSingleton<ModManager>() .AddSingleton<ModManager>()
.AddSingleton<ExportManager>() .AddSingleton<ExportManager>()
.AddSingleton<ModFileSystem>(); .AddSingleton<ModFileSystem>()
.AddSingleton<ModCacheManager>();
// Add Resource services // Add Resource services
services.AddSingleton<ResourceLoader>() services.AddSingleton<ResourceLoader>()
@ -150,8 +151,7 @@ public class PenumbraNew
.AddSingleton<ModMetaEditor>() .AddSingleton<ModMetaEditor>()
.AddSingleton<ModSwapEditor>() .AddSingleton<ModSwapEditor>()
.AddSingleton<ModNormalizer>() .AddSingleton<ModNormalizer>()
.AddSingleton<ModEditor>() .AddSingleton<ModEditor>();
.AddSingleton<ModCacheManager>();
// Add API // Add API
services.AddSingleton<PenumbraApi>() services.AddSingleton<PenumbraApi>()

View file

@ -113,7 +113,7 @@ public class ConfigMigrationService
if (_config.Version != 4) if (_config.Version != 4)
return; return;
ModManager.MigrateModBackups = true; ModBackup.MigrateModBackups = true;
_config.Version = 5; _config.Version = 5;
} }

View file

@ -270,9 +270,9 @@ 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, _modManager.Last(), if (!_swapData.WriteMod(_modManager, _modManager[^1],
_useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps)) _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps))
_modManager.DeleteMod(_modManager.Count - 1); _modManager.DeleteMod(_modManager[^1]);
} }
private void CreateOption() private void CreateOption()

View file

@ -9,23 +9,22 @@ using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Utility; using Dalamud.Utility;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using Penumbra.Mods; using Penumbra.Services;
namespace Penumbra.UI; namespace Penumbra.UI;
public class FileDialogService : IDisposable public class FileDialogService : IDisposable
{ {
private readonly ModManager _mods; private readonly CommunicatorService _communicator;
private readonly FileDialogManager _manager; private readonly FileDialogManager _manager;
private readonly ConcurrentDictionary<string, string> _startPaths = new(); private readonly ConcurrentDictionary<string, string> _startPaths = new();
private bool _isOpen; private bool _isOpen;
public FileDialogService(ModManager mods, Configuration config) public FileDialogService(CommunicatorService communicator, Configuration config)
{ {
_mods = mods; _communicator = communicator;
_manager = SetupFileManager(config.ModDirectory); _manager = SetupFileManager(config.ModDirectory);
_communicator.ModDirectoryChanged.Event += OnModDirectoryChange;
_mods.ModDirectoryChanged += OnModDirectoryChange;
} }
public void OpenFilePicker(string title, string filters, Action<bool, List<string>> callback, int selectionCountMax, string? startPath, public void OpenFilePicker(string title, string filters, Action<bool, List<string>> callback, int selectionCountMax, string? startPath,
@ -72,7 +71,7 @@ public class FileDialogService : IDisposable
{ {
_startPaths.Clear(); _startPaths.Clear();
_manager.Reset(); _manager.Reset();
_mods.ModDirectoryChanged -= OnModDirectoryChange; _communicator.ModDirectoryChanged.Event -= OnModDirectoryChange;
} }
private string? GetStartPath(string title, string? startPath, bool forceStartPath) private string? GetStartPath(string title, string? startPath, bool forceStartPath)
@ -87,7 +86,7 @@ public class FileDialogService : IDisposable
{ {
return (valid, list) => return (valid, list) =>
{ {
_isOpen = false; _isOpen = false;
var loc = HandleRoot(GetCurrentLocation()); var loc = HandleRoot(GetCurrentLocation());
_startPaths[title] = loc; _startPaths[title] = loc;
callback(valid, list.Select(HandleRoot).ToList()); callback(valid, list.Select(HandleRoot).ToList());

View file

@ -25,20 +25,20 @@ namespace Penumbra.UI.ModsTab;
public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSystemSelector.ModState> public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSystemSelector.ModState>
{ {
private readonly CommunicatorService _communicator; private readonly CommunicatorService _communicator;
private readonly ChatService _chat; private readonly ChatService _chat;
private readonly Configuration _config; private readonly Configuration _config;
private readonly FileDialogService _fileDialog; private readonly FileDialogService _fileDialog;
private readonly ModManager _modManager; private readonly ModManager _modManager;
private readonly CollectionManager _collectionManager; private readonly CollectionManager _collectionManager;
private readonly TutorialService _tutorial; private readonly TutorialService _tutorial;
private readonly ModEditor _modEditor; private readonly ModEditor _modEditor;
private TexToolsImporter? _import; private TexToolsImporter? _import;
public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty; public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty;
public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty; public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty;
private uint _infoPopupId = 0; private uint _infoPopupId = 0;
public ModFileSystemSelector(CommunicatorService communicator, ModFileSystem fileSystem, ModManager modManager, public ModFileSystemSelector(CommunicatorService communicator, ModFileSystem fileSystem, ModManager modManager,
CollectionManager collectionManager, Configuration config, TutorialService tutorial, FileDialogService fileDialog, ChatService chat, CollectionManager collectionManager, Configuration config, TutorialService tutorial, FileDialogService fileDialog, ChatService chat,
@ -81,16 +81,16 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
_collectionManager.Current.ModSettingChanged += OnSettingChange; _collectionManager.Current.ModSettingChanged += OnSettingChange;
_collectionManager.Current.InheritanceChanged += OnInheritanceChange; _collectionManager.Current.InheritanceChanged += OnInheritanceChange;
_communicator.ModDataChanged.Event += OnModDataChange; _communicator.ModDataChanged.Event += OnModDataChange;
_modManager.ModDiscoveryStarted += StoreCurrentSelection; _communicator.ModDiscoveryStarted.Event += StoreCurrentSelection;
_modManager.ModDiscoveryFinished += RestoreLastSelection; _communicator.ModDiscoveryFinished.Event += RestoreLastSelection;
OnCollectionChange(CollectionType.Current, null, _collectionManager.Current, ""); OnCollectionChange(CollectionType.Current, null, _collectionManager.Current, "");
} }
public override void Dispose() public override void Dispose()
{ {
base.Dispose(); base.Dispose();
_modManager.ModDiscoveryStarted -= StoreCurrentSelection; _communicator.ModDiscoveryStarted.Event -= StoreCurrentSelection;
_modManager.ModDiscoveryFinished -= RestoreLastSelection; _communicator.ModDiscoveryFinished.Event -= RestoreLastSelection;
_communicator.ModDataChanged.Event -= OnModDataChange; _communicator.ModDataChanged.Event -= OnModDataChange;
_collectionManager.Current.ModSettingChanged -= OnSettingChange; _collectionManager.Current.ModSettingChanged -= OnSettingChange;
_collectionManager.Current.InheritanceChanged -= OnInheritanceChange; _collectionManager.Current.InheritanceChanged -= OnInheritanceChange;
@ -258,8 +258,8 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
var size = new Vector2(width * 2, height); var size = new Vector2(width * 2, height);
ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Always, Vector2.One / 2); ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Always, Vector2.One / 2);
ImGui.SetNextWindowSize(size); ImGui.SetNextWindowSize(size);
var infoPopupId = ImGui.GetID("Import Status"); var infoPopupId = ImGui.GetID("Import Status");
using var popup = ImRaii.Popup("Import Status", ImGuiWindowFlags.Modal); using var popup = ImRaii.Popup("Import Status", ImGuiWindowFlags.Modal);
if (_import == null || !popup.Success) if (_import == null || !popup.Success)
return; return;
@ -320,7 +320,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true) if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true)
&& Selected != null) && Selected != null)
_modManager.DeleteMod(Selected.Index); _modManager.DeleteMod(Selected);
} }
private void AddHelpButton(Vector2 size) private void AddHelpButton(Vector2 size)
@ -336,7 +336,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
var mods = folder.GetAllDescendants(ISortMode<Mod>.Lexicographical).OfType<ModFileSystem.Leaf>().Select(l => var mods = folder.GetAllDescendants(ISortMode<Mod>.Lexicographical).OfType<ModFileSystem.Leaf>().Select(l =>
{ {
// Any mod handled here should not stay new. // Any mod handled here should not stay new.
_modManager.NewMods.Remove(l.Value); _modManager.SetKnown(l.Value);
return l.Value; return l.Value;
}); });
@ -428,7 +428,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
} }
private static void HandleException(Exception e) private static void HandleException(Exception e)
=> Penumbra.ChatService.NotificationMessage(e.Message, "Failure", NotificationType.Warning); => Penumbra.ChatService.NotificationMessage(e.Message, "Failure", NotificationType.Warning);
#endregion #endregion
@ -618,7 +618,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
/// <summary> Only get the text color for a mod if no filters are set. </summary> /// <summary> Only get the text color for a mod if no filters are set. </summary>
private ColorId GetTextColor(Mod mod, ModSettings? settings, ModCollection collection) private ColorId GetTextColor(Mod mod, ModSettings? settings, ModCollection collection)
{ {
if (Penumbra.ModManager.NewMods.Contains(mod)) if (_modManager.IsNew(mod))
return ColorId.NewMod; return ColorId.NewMod;
if (settings == null) if (settings == null)
@ -638,7 +638,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModF
private bool CheckStateFilters(Mod mod, ModSettings? settings, ModCollection collection, ref ModState state) private bool CheckStateFilters(Mod mod, ModSettings? settings, ModCollection collection, ref ModState state)
{ {
var isNew = _modManager.NewMods.Contains(mod); var isNew = _modManager.IsNew(mod);
// Handle mod details. // Handle mod details.
if (CheckFlags(mod.TotalFileCount, ModFilter.HasNoFiles, ModFilter.HasFiles) if (CheckFlags(mod.TotalFileCount, ModFilter.HasNoFiles, ModFilter.HasFiles)
|| CheckFlags(mod.TotalSwapCount, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps) || CheckFlags(mod.TotalSwapCount, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps)

View file

@ -118,7 +118,7 @@ public class ModPanelEditTab : ITab
if (ImGuiUtil.DrawDisabledButton("Reload Mod", buttonSize, "Reload the current mod from its files.\n" if (ImGuiUtil.DrawDisabledButton("Reload Mod", buttonSize, "Reload the current mod from its files.\n"
+ "If the mod directory or meta file do not exist anymore or if the new mod name is empty, the mod is deleted instead.", + "If the mod directory or meta file do not exist anymore or if the new mod name is empty, the mod is deleted instead.",
false)) false))
_modManager.ReloadMod(_mod.Index); _modManager.ReloadMod(_mod);
BackupButtons(buttonSize); BackupButtons(buttonSize);
MoveDirectory.Draw(_modManager, _mod, buttonSize); MoveDirectory.Draw(_modManager, _mod, buttonSize);
@ -255,13 +255,13 @@ 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 NewDirectoryState _state = NewDirectoryState.Identical;
public static void Reset() public static void Reset()
{ {
_currentModDirectory = null; _currentModDirectory = null;
_state = ModManager.NewDirectoryState.Identical; _state = NewDirectoryState.Identical;
} }
public static void Draw(ModManager modManager, Mod mod, Vector2 buttonSize) public static void Draw(ModManager modManager, Mod mod, Vector2 buttonSize)
@ -276,20 +276,20 @@ public class ModPanelEditTab : ITab
var (disabled, tt) = _state switch var (disabled, tt) = _state switch
{ {
ModManager.NewDirectoryState.Identical => (true, "Current directory name is identical to new one."), NewDirectoryState.Identical => (true, "Current directory name is identical to new one."),
ModManager.NewDirectoryState.Empty => (true, "Please enter a new directory name first."), NewDirectoryState.Empty => (true, "Please enter a new directory name first."),
ModManager.NewDirectoryState.NonExisting => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."), NewDirectoryState.NonExisting => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."),
ModManager.NewDirectoryState.ExistsEmpty => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."), NewDirectoryState.ExistsEmpty => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."),
ModManager.NewDirectoryState.ExistsNonEmpty => (true, $"{_currentModDirectory} already exists and is not empty."), NewDirectoryState.ExistsNonEmpty => (true, $"{_currentModDirectory} already exists and is not empty."),
ModManager.NewDirectoryState.ExistsAsFile => (true, $"{_currentModDirectory} exists as a file."), NewDirectoryState.ExistsAsFile => (true, $"{_currentModDirectory} exists as a file."),
ModManager.NewDirectoryState.ContainsInvalidSymbols => (true, NewDirectoryState.ContainsInvalidSymbols => (true,
$"{_currentModDirectory} contains invalid symbols for FFXIV."), $"{_currentModDirectory} contains invalid symbols for FFXIV."),
_ => (true, "Unknown error."), _ => (true, "Unknown error."),
}; };
ImGui.SameLine(); ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton("Rename Mod Directory", buttonSize, tt, disabled) && _currentModDirectory != null) if (ImGuiUtil.DrawDisabledButton("Rename Mod Directory", buttonSize, tt, disabled) && _currentModDirectory != null)
{ {
modManager.MoveModDirectory(mod.Index, _currentModDirectory); modManager.MoveModDirectory(mod, _currentModDirectory);
Reset(); Reset();
} }

View file

@ -125,7 +125,7 @@ public class ModPanelSettingsTab : ITab
if (!ImGui.Checkbox("Enabled", ref enabled)) if (!ImGui.Checkbox("Enabled", ref enabled))
return; return;
_modManager.NewMods.Remove(_selector.Selected!); _modManager.SetKnown(_selector.Selected!);
_collectionManager.Current.SetModState(_selector.Selected!.Index, enabled); _collectionManager.Current.SetModState(_selector.Selected!.Index, enabled);
} }