mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-20 23:07:51 +01:00
This commit is contained in:
parent
c5d09d7cd1
commit
924c9b9f7e
48 changed files with 1323 additions and 1271 deletions
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue