Update to Luna state.
Some checks failed
.NET Build / build (push) Has been cancelled

This commit is contained in:
Ottermandias 2025-09-17 18:05:07 +02:00
parent c5d09d7cd1
commit 924c9b9f7e
48 changed files with 1323 additions and 1271 deletions

View file

@ -1,4 +1,3 @@
using OtterGui.Classes;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Settings;
@ -15,7 +14,7 @@ public record struct AppliedModData(
public interface IMod
{
LowerString Name { get; }
public string Name { get; }
public int Index { get; }
public ModPriority Priority { get; }

View file

@ -479,8 +479,8 @@ public class ModMerger : IDisposable, IService
private void OnSelectionChange(in ModSelection.Arguments arguments)
{
if (OptionGroupName == "Merges" && OptionName.Length == 0 || OptionName == arguments.OldSelection?.Name.Text)
OptionName = arguments.NewSelection?.Name.Text ?? string.Empty;
if (OptionGroupName == "Merges" && OptionName.Length == 0 || OptionName == arguments.OldSelection?.Name)
OptionName = arguments.NewSelection?.Name ?? string.Empty;
if (MergeToMod == arguments.NewSelection)
MergeToMod = null;

View file

@ -1,5 +1,4 @@
using Dalamud.Utility;
using OtterGui.Classes;
using Penumbra.Communication;
using Penumbra.GameData.Data;
using Penumbra.GameData.Structs;
@ -39,8 +38,8 @@ public class ModDataEditor(SaveService saveService, CommunicatorService communic
string? website, params string[] tags)
{
var mod = new Mod(directory);
mod.Name = name.IsNullOrEmpty() ? mod.Name : new LowerString(name);
mod.Author = author != null ? new LowerString(author) : mod.Author;
mod.Name = name.IsNullOrEmpty() ? mod.Name : name;
mod.Author = author ?? mod.Author;
mod.Description = description ?? mod.Description;
mod.Version = version ?? mod.Version;
mod.Website = website ?? mod.Website;
@ -50,13 +49,13 @@ public class ModDataEditor(SaveService saveService, CommunicatorService communic
public void ChangeModName(Mod mod, string newName)
{
if (mod.Name.Text == newName)
if (mod.Name == newName)
return;
var oldName = mod.Name;
mod.Name = newName;
saveService.QueueSave(new ModMeta(mod));
communicatorService.ModDataChanged.Invoke(new ModDataChanged.Arguments(ModDataChangeType.Name, mod, oldName.Text));
communicatorService.ModDataChanged.Invoke(new ModDataChanged.Arguments(ModDataChangeType.Name, mod, oldName));
}
public void ChangeModAuthor(Mod mod, string newAuthor)
@ -97,7 +96,7 @@ public class ModDataEditor(SaveService saveService, CommunicatorService communic
mod.Website = newWebsite;
saveService.QueueSave(new ModMeta(mod));
communicatorService.ModDataChanged.Invoke(new ModDataChanged.Arguments(ModDataChangeType.Website, mod, null));
}
}
public void ChangeRequiredFeatures(Mod mod, FeatureFlags flags)
{

View file

@ -3,6 +3,7 @@ using Luna;
using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.Services;
using FileSystemChangeType = OtterGui.Filesystem.FileSystemChangeType;
namespace Penumbra.Mods.Manager;
@ -82,9 +83,9 @@ public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable, ISer
if (!arguments.Type.HasFlag(ModDataChangeType.Name) || arguments.OldName == null || !TryGetValue(arguments.Mod, out var leaf))
return;
var old = arguments.OldName.FixName();
if (old == leaf.Name || leaf.Name.IsDuplicateName(out var baseName, out _) && baseName == old)
RenameWithDuplicates(leaf, arguments.Mod.Name.Text);
var old = Extensions.FixName(arguments.OldName);
if (old == leaf.Name || Extensions.IsDuplicateName(leaf.Name, out var baseName, out _) && baseName == old)
RenameWithDuplicates(leaf, arguments.Mod.Name);
}
// Update the filesystem if a mod has been added or removed.
@ -107,7 +108,7 @@ public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable, ISer
NotificationType.Warning);
}
CreateDuplicateLeaf(parent, arguments.Mod.Name.Text, arguments.Mod);
CreateDuplicateLeaf(parent, arguments.Mod.Name, arguments.Mod);
break;
case ModPathChangeType.Deleted:
if (TryGetValue(arguments.Mod, out var leaf))
@ -128,7 +129,7 @@ public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable, ISer
=> mod.ModPath.Name;
private static string ModToName(Mod mod)
=> mod.Name.Text.FixName();
=> Extensions.FixName(mod.Name);
// Return whether a mod has a custom path or is just a numbered default path.
public static bool ModHasDefaultPath(Mod mod, string fullPath)

View file

@ -9,7 +9,7 @@ public class ModCombo(Func<IReadOnlyList<Mod>> generator) : FilterComboCache<Mod
=> Items[globalIndex].Name.Contains(filter);
protected override string ToString(Mod obj)
=> obj.Name.Text;
=> obj.Name;
}
public class ModStorage : IReadOnlyList<Mod>
@ -60,7 +60,7 @@ public class ModStorage : IReadOnlyList<Mod>
/// Mods are removed when they are deleted or when they are toggled in any collection.
/// Also gets cleared on mod rediscovery.
/// </summary>
private readonly HashSet<Mod> _newMods = new();
private readonly HashSet<Mod> _newMods = [];
public bool IsNew(Mod mod)
=> _newMods.Contains(mod);

View file

@ -1,144 +1,143 @@
using Luna;
using OtterGui.Classes;
using Penumbra.GameData.Data;
using Penumbra.GameData.Structs;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
using Penumbra.String.Classes;
namespace Penumbra.Mods;
[Flags]
public enum FeatureFlags : ulong
{
None = 0,
Atch = 1ul << 0,
Shp = 1ul << 1,
Atr = 1ul << 2,
Invalid = 1ul << 62,
}
public sealed class Mod : IMod
{
public static readonly TemporaryMod ForcedFiles = new()
{
Name = "Forced Files",
Index = -1,
Priority = ModPriority.MaxValue,
};
// Main Data
public DirectoryInfo ModPath { get; internal set; }
public string Identifier
=> Index >= 0 ? ModPath.Name : Name;
public int Index { get; internal set; } = -1;
public bool IsTemporary
=> Index < 0;
/// <summary>Unused if Index is less than 0 but used for special temporary mods.</summary>
public ModPriority Priority
=> ModPriority.Default;
IReadOnlyList<IModGroup> IMod.Groups
=> Groups;
internal Mod(DirectoryInfo modPath)
{
ModPath = modPath;
Default = new DefaultSubMod(this);
}
public override string ToString()
=> Name.Text;
// Meta Data
public LowerString Name { get; internal set; } = "New Mod";
public LowerString Author { get; internal set; } = LowerString.Empty;
public string Description { get; internal set; } = string.Empty;
public string Version { get; internal set; } = string.Empty;
public string Website { get; internal set; } = string.Empty;
public string Image { get; internal set; } = string.Empty;
public IReadOnlyList<string> ModTags { get; internal set; } = [];
public HashSet<CustomItemId> DefaultPreferredItems { get; internal set; } = [];
public FeatureFlags RequiredFeatures { get; internal set; } = 0;
// Local Data
public long ImportDate { get; internal set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
public IReadOnlyList<string> LocalTags { get; internal set; } = [];
public string Note { get; internal set; } = string.Empty;
public HashSet<CustomItemId> PreferredChangedItems { get; internal set; } = [];
public bool Favorite { get; internal set; }
// Options
public readonly DefaultSubMod Default;
public readonly List<IModGroup> Groups = [];
/// <summary> Compute the required feature flags for this mod. </summary>
public FeatureFlags ComputeRequiredFeatures()
{
var flags = FeatureFlags.None;
foreach (var option in AllDataContainers)
{
if (option.Manipulations.Atch.Count > 0)
flags |= FeatureFlags.Atch;
if (option.Manipulations.Atr.Count > 0)
flags |= FeatureFlags.Atr;
if (option.Manipulations.Shp.Count > 0)
flags |= FeatureFlags.Shp;
}
return flags;
}
public AppliedModData GetData(ModSettings? settings = null)
{
if (settings is not { Enabled: true })
return AppliedModData.Empty;
var dictRedirections = new Dictionary<Utf8GamePath, FullPath>(TotalFileCount);
var setManips = new MetaDictionary();
foreach (var (groupIndex, group) in Groups.Index().Reverse().OrderByDescending(g => g.Item.Priority))
{
var config = settings.Settings[groupIndex];
group.AddData(config, dictRedirections, setManips);
}
Default.AddTo(dictRedirections, setManips);
return new AppliedModData(dictRedirections, setManips);
}
public IEnumerable<IModDataContainer> AllDataContainers
=> Groups.SelectMany(o => o.DataContainers).Prepend(Default);
public List<FullPath> FindUnusedFiles()
{
var modFiles = AllDataContainers.SelectMany(o => o.Files)
.Select(p => p.Value)
.ToHashSet();
return ModPath.EnumerateDirectories()
.Where(d => !d.IsHidden())
.SelectMany(FileExtensions.EnumerateNonHiddenFiles)
.Select(f => new FullPath(f))
.Where(f => !modFiles.Contains(f))
.ToList();
}
// Cache
public readonly SortedList<string, IIdentifiedObjectData> ChangedItems = new();
public string LowerChangedItemsString { get; internal set; } = string.Empty;
public string AllTagsLower { get; internal set; } = string.Empty;
public int TotalFileCount { get; internal set; }
public int TotalSwapCount { get; internal set; }
public int TotalManipulations { get; internal set; }
public ushort LastChangedItemsUpdate { get; internal set; }
public bool HasOptions { get; internal set; }
}
using Luna;
using Penumbra.GameData.Data;
using Penumbra.GameData.Structs;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
using Penumbra.String.Classes;
namespace Penumbra.Mods;
[Flags]
public enum FeatureFlags : ulong
{
None = 0,
Atch = 1ul << 0,
Shp = 1ul << 1,
Atr = 1ul << 2,
Invalid = 1ul << 62,
}
public sealed class Mod : IMod
{
public static readonly TemporaryMod ForcedFiles = new()
{
Name = "Forced Files",
Index = -1,
Priority = ModPriority.MaxValue,
};
// Main Data
public DirectoryInfo ModPath { get; internal set; }
public string Identifier
=> Index >= 0 ? ModPath.Name : Name;
public int Index { get; internal set; } = -1;
public bool IsTemporary
=> Index < 0;
/// <summary>Unused if Index is less than 0 but used for special temporary mods.</summary>
public ModPriority Priority
=> ModPriority.Default;
IReadOnlyList<IModGroup> IMod.Groups
=> Groups;
internal Mod(DirectoryInfo modPath)
{
ModPath = modPath;
Default = new DefaultSubMod(this);
}
public override string ToString()
=> Name;
// Meta Data
public string Name { get; internal set; } = "New Mod";
public string Author { get; internal set; } = string.Empty;
public string Description { get; internal set; } = string.Empty;
public string Version { get; internal set; } = string.Empty;
public string Website { get; internal set; } = string.Empty;
public string Image { get; internal set; } = string.Empty;
public IReadOnlyList<string> ModTags { get; internal set; } = [];
public HashSet<CustomItemId> DefaultPreferredItems { get; internal set; } = [];
public FeatureFlags RequiredFeatures { get; internal set; } = 0;
// Local Data
public long ImportDate { get; internal set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
public IReadOnlyList<string> LocalTags { get; internal set; } = [];
public string Note { get; internal set; } = string.Empty;
public HashSet<CustomItemId> PreferredChangedItems { get; internal set; } = [];
public bool Favorite { get; internal set; }
// Options
public readonly DefaultSubMod Default;
public readonly List<IModGroup> Groups = [];
/// <summary> Compute the required feature flags for this mod. </summary>
public FeatureFlags ComputeRequiredFeatures()
{
var flags = FeatureFlags.None;
foreach (var option in AllDataContainers)
{
if (option.Manipulations.Atch.Count > 0)
flags |= FeatureFlags.Atch;
if (option.Manipulations.Atr.Count > 0)
flags |= FeatureFlags.Atr;
if (option.Manipulations.Shp.Count > 0)
flags |= FeatureFlags.Shp;
}
return flags;
}
public AppliedModData GetData(ModSettings? settings = null)
{
if (settings is not { Enabled: true })
return AppliedModData.Empty;
var dictRedirections = new Dictionary<Utf8GamePath, FullPath>(TotalFileCount);
var setManips = new MetaDictionary();
foreach (var (groupIndex, group) in Groups.Index().Reverse().OrderByDescending(g => g.Item.Priority))
{
var config = settings.Settings[groupIndex];
group.AddData(config, dictRedirections, setManips);
}
Default.AddTo(dictRedirections, setManips);
return new AppliedModData(dictRedirections, setManips);
}
public IEnumerable<IModDataContainer> AllDataContainers
=> Groups.SelectMany(o => o.DataContainers).Prepend(Default);
public List<FullPath> FindUnusedFiles()
{
var modFiles = AllDataContainers.SelectMany(o => o.Files)
.Select(p => p.Value)
.ToHashSet();
return ModPath.EnumerateDirectories()
.Where(d => !d.IsHidden())
.SelectMany(FileExtensions.EnumerateNonHiddenFiles)
.Select(f => new FullPath(f))
.Where(f => !modFiles.Contains(f))
.ToList();
}
// Cache
public readonly SortedList<string, IIdentifiedObjectData> ChangedItems = new();
public string LowerChangedItemsString { get; internal set; } = string.Empty;
public string AllTagsLower { get; internal set; } = string.Empty;
public int TotalFileCount { get; internal set; }
public int TotalSwapCount { get; internal set; }
public int TotalManipulations { get; internal set; }
public ushort LastChangedItemsUpdate { get; internal set; }
public bool HasOptions { get; internal set; }
}

View file

@ -23,7 +23,7 @@ public partial class ModCreator(
Configuration config,
ModDataEditor dataEditor,
MetaFileManager metaFileManager,
GamePathParser gamePathParser) : Luna.IService
GamePathParser gamePathParser) : IService
{
public const FeatureFlags SupportedFeatures = FeatureFlags.Atch | FeatureFlags.Shp | FeatureFlags.Atr;
public readonly Configuration Config = config;
@ -139,7 +139,7 @@ public partial class ModCreator(
name = "_";
var newModFolderBase = NewOptionDirectory(outDirectory, name, onlyAscii);
var newModFolder = newModFolderBase.FullName.ObtainUniqueFile();
var newModFolder = FileSystemUtility.ObtainUniqueFile(newModFolderBase.FullName);
if (newModFolder.Length == 0)
throw new IOException("Could not create mod folder: too many folders of the same name exist.");
@ -236,7 +236,7 @@ public partial class ModCreator(
public static DirectoryInfo? NewSubFolderName(DirectoryInfo parentFolder, string subFolderName, bool onlyAscii)
{
var newModFolderBase = NewOptionDirectory(parentFolder, subFolderName, onlyAscii);
var newModFolder = newModFolderBase.FullName.ObtainUniqueFile();
var newModFolder = FileSystemUtility.ObtainUniqueFile(newModFolderBase.FullName);
return newModFolder.Length == 0 ? null : new DirectoryInfo(newModFolder);
}

View file

@ -1,4 +1,3 @@
using OtterGui.Classes;
using Penumbra.Collections;
using Penumbra.Interop.PathResolving;
using Penumbra.Meta.Manipulations;
@ -14,7 +13,7 @@ namespace Penumbra.Mods;
public class TemporaryMod : IMod
{
public LowerString Name { get; init; } = LowerString.Empty;
public string Name { get; init; } = string.Empty;
public int Index { get; init; } = -2;
public ModPriority Priority { get; init; } = ModPriority.MaxValue;