mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-23 08:17:59 +01:00
This commit is contained in:
parent
d79e687162
commit
6b475ee229
66 changed files with 666 additions and 705 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
Penumbra/Mods/Manager/ModCombo.cs
Normal file
25
Penumbra/Mods/Manager/ModCombo.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using OtterGui.Filesystem;
|
||||
using Luna;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.Mods.Groups;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue