Move a lot of things over to Luna.
Some checks failed
.NET Build / build (push) Has been cancelled

This commit is contained in:
Ottermandias 2025-10-24 22:47:14 +02:00
parent d79e687162
commit 6b475ee229
66 changed files with 666 additions and 705 deletions

View file

@ -1,4 +1,3 @@
using OtterGui.Tasks;
using Penumbra.Mods.Manager;
namespace Penumbra.Mods.Editor;
@ -77,7 +76,7 @@ public class ModBackup
return;
CreatingBackup = true;
await AsyncTask.Run(Create);
await Task.Run(Create);
CreatingBackup = false;
}

View file

@ -59,9 +59,6 @@ public class ModMerger : IDisposable, IService
_communicator.ModPathChanged.Unsubscribe(OnModPathChange);
}
public IEnumerable<Mod> ModsWithoutCurrent
=> _mods.Where(m => m != MergeFromMod);
public bool CanMerge
=> MergeToMod != null && MergeToMod != MergeFromMod;

View file

@ -1,6 +1,5 @@
using Dalamud.Interface.ImGuiNotification;
using Luna;
using OtterGui.Tasks;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Manager;
using Penumbra.Mods.SubMods;
@ -9,7 +8,7 @@ using Penumbra.String.Classes;
namespace Penumbra.Mods.Editor;
public class ModNormalizer(ModManager modManager, Configuration config, SaveService saveService) : Luna.IService
public class ModNormalizer(ModManager modManager, Configuration config, SaveService saveService) : IService
{
private readonly List<List<Dictionary<Utf8GamePath, FullPath>>> _redirections = [];
@ -36,7 +35,7 @@ public class ModNormalizer(ModManager modManager, Configuration config, SaveServ
Step = 0;
TotalSteps = mod.TotalFileCount + 5;
Worker = TrackedTask.Run(NormalizeSync);
Worker = Task.Run(NormalizeSync);
}
public void NormalizeUi(DirectoryInfo modDirectory)

View file

@ -1,95 +1,90 @@
using System.Collections.Frozen;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility.Raii;
using ImSharp;
using OtterGui.Text;
using Penumbra.Mods.Manager;
using Penumbra.UI.Classes;
using Notification = Luna.Notification;
namespace Penumbra.Mods;
public static class FeatureChecker
{
/// <summary> Manually setup supported features to exclude None and Invalid and not make something supported too early. </summary>
private static readonly FrozenDictionary<string, FeatureFlags> SupportedFlags = new[]
{
FeatureFlags.Atch,
FeatureFlags.Shp,
FeatureFlags.Atr,
}.ToFrozenDictionary(f => f.ToString(), f => f);
public static IReadOnlyCollection<string> SupportedFeatures
=> SupportedFlags.Keys;
public static FeatureFlags ParseFlags(string modDirectory, string modName, IEnumerable<string> features)
{
var featureFlags = FeatureFlags.None;
HashSet<string> missingFeatures = [];
foreach (var feature in features)
{
if (SupportedFlags.TryGetValue(feature, out var featureFlag))
featureFlags |= featureFlag;
else
missingFeatures.Add(feature);
}
if (missingFeatures.Count > 0)
{
Penumbra.Messager.AddMessage(new Notification(
$"Please update Penumbra to use the mod {modName}{(modDirectory != modName ? $" at {modDirectory}" : string.Empty)}!\n\nLoading failed because it requires the unsupported feature{(missingFeatures.Count > 1 ? $"s\n\n\t[{string.Join("], [", missingFeatures)}]." : $" [{missingFeatures.First()}].")}",
NotificationType.Warning));
return FeatureFlags.Invalid;
}
return featureFlags;
}
public static bool Supported(string features)
=> SupportedFlags.ContainsKey(features);
public static void DrawFeatureFlagInput(ModDataEditor editor, Mod mod, float width)
{
const int numButtons = 5;
var innerSpacing = ImGui.GetStyle().ItemInnerSpacing;
var size = new Vector2((width - (numButtons - 1) * innerSpacing.X) / numButtons, 0);
var buttonColor = Im.Style[ImGuiColor.FrameBackground];
var textColor = Im.Style[ImGuiColor.TextDisabled];
using System.Collections.Frozen;
using ImSharp;
using Penumbra.Mods.Manager;
using Penumbra.UI.Classes;
using Notification = Luna.Notification;
namespace Penumbra.Mods;
public static class FeatureChecker
{
/// <summary> Manually setup supported features to exclude None and Invalid and not make something supported too early. </summary>
private static readonly FrozenDictionary<string, FeatureFlags> SupportedFlags = new[]
{
FeatureFlags.Atch,
FeatureFlags.Shp,
FeatureFlags.Atr,
}.ToFrozenDictionary(f => f.ToString(), f => f);
public static IReadOnlyCollection<string> SupportedFeatures
=> SupportedFlags.Keys;
public static FeatureFlags ParseFlags(string modDirectory, string modName, IEnumerable<string> features)
{
var featureFlags = FeatureFlags.None;
HashSet<string> missingFeatures = [];
foreach (var feature in features)
{
if (SupportedFlags.TryGetValue(feature, out var featureFlag))
featureFlags |= featureFlag;
else
missingFeatures.Add(feature);
}
if (missingFeatures.Count > 0)
{
Penumbra.Messager.AddMessage(new Notification($"Please update Penumbra to use the mod {modName}{(modDirectory != modName ? $" at {modDirectory}" : string.Empty)}!\n\n"
+ $"Loading failed because it requires the unsupported feature{(missingFeatures.Count > 1 ? $"s\n\n\t[{string.Join("], [", missingFeatures)}]." : $" [{missingFeatures.First()}].")}"));
return FeatureFlags.Invalid;
}
return featureFlags;
}
public static bool Supported(string features)
=> SupportedFlags.ContainsKey(features);
public static void DrawFeatureFlagInput(ModDataEditor editor, Mod mod, float width)
{
const int numButtons = 5;
var innerSpacing = Im.Style.ItemInnerSpacing;
var size = new Vector2((width - (numButtons - 1) * innerSpacing.X) / numButtons, 0);
var buttonColor = Im.Style[ImGuiColor.FrameBackground];
var textColor = Im.Style[ImGuiColor.TextDisabled];
using (var style = ImStyleBorder.Frame.Push(ColorId.FolderLine.Value(), 0)
.Push(ImStyleDouble.ItemSpacing, innerSpacing)
.Push(ImGuiColor.Button, buttonColor)
.Push(ImGuiColor.Text, textColor))
{
foreach (var flag in SupportedFlags.Values)
{
if (mod.RequiredFeatures.HasFlag(flag))
.Push(ImStyleDouble.ItemSpacing, innerSpacing)
.Push(ImGuiColor.Button, buttonColor)
.Push(ImGuiColor.Text, textColor))
{
foreach (var flag in SupportedFlags.Values)
{
if (mod.RequiredFeatures.HasFlag(flag))
{
style.Push(ImStyleSingle.FrameBorderThickness, ImUtf8.GlobalScale);
style.PopColor(2);
if (Im.Button($"{flag}", size))
editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures & ~flag);
style.Push(ImGuiColor.Button, buttonColor)
.Push(ImGuiColor.Text, textColor);
style.PopStyle();
}
else if (Im.Button($"{flag}", size))
{
editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures | flag);
}
Im.Line.Same();
}
}
if (ImUtf8.ButtonEx("Compute"u8, "Compute the required features automatically from the used features."u8, size))
editor.ChangeRequiredFeatures(mod, mod.ComputeRequiredFeatures());
Im.Line.Same();
if (ImUtf8.ButtonEx("Clear"u8, "Clear all required features."u8, size))
editor.ChangeRequiredFeatures(mod, FeatureFlags.None);
Im.Line.Same();
ImUtf8.Text("Required Features"u8);
}
}
style.Push(ImStyleSingle.FrameBorderThickness, Im.Style.GlobalScale);
style.PopColor(2);
if (Im.Button($"{flag}", size))
editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures & ~flag);
style.Push(ImGuiColor.Button, buttonColor)
.Push(ImGuiColor.Text, textColor);
style.PopStyle();
}
else if (Im.Button($"{flag}", size))
{
editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures | flag);
}
Im.Line.Same();
}
}
if (ImEx.Button("Compute"u8, size, "Compute the required features automatically from the used features."u8))
editor.ChangeRequiredFeatures(mod, mod.ComputeRequiredFeatures());
Im.Line.Same();
if (ImEx.Button("Clear"u8, size, "Clear all required features."u8))
editor.ChangeRequiredFeatures(mod, FeatureFlags.None);
Im.Line.Same();
Im.Text("Required Features"u8);
}
}

View file

@ -0,0 +1,25 @@
using ImSharp;
using Luna;
using Penumbra.Mods.Editor;
namespace Penumbra.Mods.Manager;
public class ModCombo(ModStorage modStorage) : SimpleFilterCombo<Mod>(SimpleFilterType.Regex), IUiService
{
protected readonly ModStorage ModStorage = modStorage;
public override StringU8 DisplayString(in Mod value)
=> new(value.Name);
public override string FilterString(in Mod value)
=> value.Name;
public override IEnumerable<Mod> GetBaseItems()
=> ModStorage;
}
public sealed class ModComboWithoutCurrent(ModStorage modStorage, ModMerger modMerger) : ModCombo(modStorage)
{
public override IEnumerable<Mod> GetBaseItems()
=> ModStorage.Where(m => m != modMerger.MergeFromMod);
}

View file

@ -1,3 +1,4 @@
using Luna;
using Penumbra.Communication;
using Penumbra.Interop;
using Penumbra.Mods.Editor;
@ -228,7 +229,7 @@ public sealed class ModManager : ModStorage, IDisposable, Luna.IService
if (oldName == newName)
return NewDirectoryState.Identical;
var fixedNewName = ModCreator.ReplaceBadXivSymbols(newName, _config.ReplaceNonAsciiOnImport);
var fixedNewName = newName.ReplaceBadXivSymbols(_config.ReplaceNonAsciiOnImport);
if (fixedNewName != newName)
return NewDirectoryState.ContainsInvalidSymbols;

View file

@ -1,17 +1,5 @@
using OtterGui.Classes;
using OtterGui.Widgets;
namespace Penumbra.Mods.Manager;
public class ModCombo(Func<IReadOnlyList<Mod>> generator) : FilterComboCache<Mod>(generator, MouseWheelType.None, Penumbra.Log)
{
protected override bool IsVisible(int globalIndex, LowerString filter)
=> Items[globalIndex].Name.Contains(filter);
protected override string ToString(Mod obj)
=> obj.Name;
}
public class ModStorage : IReadOnlyList<Mod>
{
/// <summary> The actual list of mods. </summary>

View file

@ -1,5 +1,4 @@
using Luna;
using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.GameData.Structs;
using Penumbra.Meta.Manipulations;
@ -137,7 +136,7 @@ public sealed class ImcModGroupEditor(CommunicatorService communicator, SaveServ
protected override bool MoveOption(ImcModGroup group, int optionIdxFrom, int optionIdxTo)
{
if (!Extensions.Move(group.OptionData, ref optionIdxFrom, ref optionIdxTo))
if (!group.OptionData.Move(ref optionIdxFrom, ref optionIdxTo))
return false;
group.DefaultSettings = group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);

View file

@ -1,6 +1,5 @@
using Dalamud.Interface.ImGuiNotification;
using Luna;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.Meta.Manipulations;
@ -40,7 +39,7 @@ public class ModGroupEditor(
CombiningModGroupEditor combiningEditor,
CommunicatorService communicator,
SaveService saveService,
Configuration config) : Luna.IService
Configuration config) : IService
{
public SingleModGroupEditor SingleEditor
=> singleEditor;

View file

@ -1,5 +1,4 @@
using Luna;
using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Settings;
@ -9,7 +8,7 @@ using Penumbra.Services;
namespace Penumbra.Mods.Manager.OptionEditor;
public sealed class MultiModGroupEditor(CommunicatorService communicator, SaveService saveService, Configuration config)
: ModOptionEditor<MultiModGroup, MultiSubMod>(communicator, saveService, config), Luna.IService
: ModOptionEditor<MultiModGroup, MultiSubMod>(communicator, saveService, config), IService
{
public void ChangeToSingle(MultiModGroup group)
{
@ -75,7 +74,7 @@ public sealed class MultiModGroupEditor(CommunicatorService communicator, SaveSe
protected override bool MoveOption(MultiModGroup group, int optionIdxFrom, int optionIdxTo)
{
if (!Extensions.Move(group.OptionData, ref optionIdxFrom, ref optionIdxTo))
if (!group.OptionData.Move(ref optionIdxFrom, ref optionIdxTo))
return false;
group.DefaultSettings = group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);

View file

@ -1,5 +1,4 @@
using Luna;
using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Settings;
@ -48,7 +47,7 @@ public sealed class SingleModGroupEditor(CommunicatorService communicator, SaveS
protected override bool MoveOption(SingleModGroup group, int optionIdxFrom, int optionIdxTo)
{
if (!Extensions.Move(group.OptionData, ref optionIdxFrom, ref optionIdxTo))
if (!group.OptionData.Move(ref optionIdxFrom, ref optionIdxTo))
return false;
group.DefaultSettings = group.DefaultSettings.MoveSingle(optionIdxFrom, optionIdxTo);

View file

@ -2,7 +2,6 @@ using Dalamud.Interface.ImGuiNotification;
using Luna;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
using Penumbra.GameData.Data;
using Penumbra.Import;
@ -29,7 +28,8 @@ public partial class ModCreator(
public readonly Configuration Config = config;
/// <summary> Creates directory and files necessary for a new mod without adding it to the manager. </summary>
public DirectoryInfo? CreateEmptyMod(DirectoryInfo basePath, string newName, string description = "", string? author = null, params string[] tags)
public DirectoryInfo? CreateEmptyMod(DirectoryInfo basePath, string newName, string description = "", string? author = null,
params string[] tags)
{
try
{
@ -81,7 +81,7 @@ public partial class ModCreator(
if (incorporateMetaChanges)
IncorporateAllMetaChanges(mod, true, deleteDefaultMetaChanges);
else if (deleteDefaultMetaChanges)
ModMetaEditor.DeleteDefaultValues(mod, metaFileManager, saveService, false);
ModMetaEditor.DeleteDefaultValues(mod, metaFileManager, saveService);
return true;
}
@ -98,7 +98,7 @@ public partial class ModCreator(
{
changes = changes
|| saveService.FileNames.OptionGroupFile(mod.ModPath.FullName, mod.Groups.Count, group.Name, true)
!= Path.Combine(file.DirectoryName!, ReplaceBadXivSymbols(file.Name, true));
!= Path.Combine(file.DirectoryName!, file.Name.ReplaceBadXivSymbols(true));
mod.Groups.Add(group);
}
else
@ -167,7 +167,7 @@ public partial class ModCreator(
DeleteDeleteList(deleteList, delete);
if (removeDefaultValues && !Config.KeepDefaultMetaChanges)
changes |= ModMetaEditor.DeleteDefaultValues(mod, metaFileManager, null, false);
changes |= ModMetaEditor.DeleteDefaultValues(mod, metaFileManager, null);
if (!changes)
return;
@ -306,33 +306,10 @@ public partial class ModCreator(
/// <summary> Return the name of a new valid directory based on the base directory and the given name. </summary>
public static DirectoryInfo NewOptionDirectory(DirectoryInfo baseDir, string optionName, bool onlyAscii)
{
var option = ReplaceBadXivSymbols(optionName, onlyAscii);
var option = optionName.ReplaceBadXivSymbols(onlyAscii);
return new DirectoryInfo(Path.Combine(baseDir.FullName, option.Length > 0 ? option : "_"));
}
/// <summary> Normalize for nicer names, and remove invalid symbols or invalid paths. </summary>
public static string ReplaceBadXivSymbols(string s, bool onlyAscii, string replacement = "_")
{
switch (s)
{
case ".": return replacement;
case "..": return replacement + replacement;
}
StringBuilder sb = new(s.Length);
foreach (var c in s.Normalize(NormalizationForm.FormKC))
{
if (c.IsInvalidInPath())
sb.Append(replacement);
else if (onlyAscii && c.IsInvalidAscii())
sb.Append(replacement);
else
sb.Append(c);
}
return sb.ToString().Trim();
}
public void SplitMultiGroups(DirectoryInfo baseDir)
{
var mod = new Mod(baseDir);

View file

@ -1,4 +1,4 @@
using OtterGui.Filesystem;
using Luna;
using Penumbra.Api.Enums;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Groups;

View file

@ -1,5 +1,5 @@
using Luna;
using Newtonsoft.Json;
using OtterGui;
namespace Penumbra.Mods.Settings;
@ -27,10 +27,10 @@ public readonly record struct Setting(ulong Value)
=> idx >= 0 && (Value & (1ul << idx)) != 0;
public Setting MoveBit(int idx1, int idx2)
=> new(Functions.MoveBit(Value, idx1, idx2));
=> new(BitFunctions.MoveBit(Value, idx1, idx2));
public Setting RemoveBit(int idx)
=> new(Functions.RemoveBit(Value, idx));
=> new(BitFunctions.RemoveBit(Value, idx));
public Setting SetBit(int idx, bool value)
=> new(value ? Value | (1ul << idx) : Value & ~(1ul << idx));