mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Add checking for supported features with the currently new supported features 'Atch', 'Shp' and 'Atr'.
This commit is contained in:
parent
98203e4e8a
commit
318a41fe52
14 changed files with 216 additions and 37 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit 1465203967d08519c6716292bc5e5094c7fbcacc
|
Subproject commit ff7b3b4014a97455f823380c78b8a7c5107f8e2f
|
||||||
|
|
@ -17,7 +17,7 @@ public class PenumbraApi(
|
||||||
UiApi ui) : IDisposable, IApiService, IPenumbraApi
|
UiApi ui) : IDisposable, IApiService, IPenumbraApi
|
||||||
{
|
{
|
||||||
public const int BreakingVersion = 5;
|
public const int BreakingVersion = 5;
|
||||||
public const int FeatureVersion = 9;
|
public const int FeatureVersion = 10;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,38 @@
|
||||||
|
using System.Collections.Frozen;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
|
using Penumbra.Mods;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.Api.Api;
|
namespace Penumbra.Api.Api;
|
||||||
|
|
||||||
public class PluginStateApi : IPenumbraApiPluginState, IApiService
|
public class PluginStateApi(Configuration config, CommunicatorService communicator) : IPenumbraApiPluginState, IApiService
|
||||||
{
|
{
|
||||||
private readonly Configuration _config;
|
|
||||||
private readonly CommunicatorService _communicator;
|
|
||||||
|
|
||||||
public PluginStateApi(Configuration config, CommunicatorService communicator)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
_communicator = communicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetModDirectory()
|
public string GetModDirectory()
|
||||||
=> _config.ModDirectory;
|
=> config.ModDirectory;
|
||||||
|
|
||||||
public string GetConfiguration()
|
public string GetConfiguration()
|
||||||
=> JsonConvert.SerializeObject(_config, Formatting.Indented);
|
=> JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||||
|
|
||||||
public event Action<string, bool>? ModDirectoryChanged
|
public event Action<string, bool>? ModDirectoryChanged
|
||||||
{
|
{
|
||||||
add => _communicator.ModDirectoryChanged.Subscribe(value!, Communication.ModDirectoryChanged.Priority.Api);
|
add => communicator.ModDirectoryChanged.Subscribe(value!, Communication.ModDirectoryChanged.Priority.Api);
|
||||||
remove => _communicator.ModDirectoryChanged.Unsubscribe(value!);
|
remove => communicator.ModDirectoryChanged.Unsubscribe(value!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetEnabledState()
|
public bool GetEnabledState()
|
||||||
=> _config.EnableMods;
|
=> config.EnableMods;
|
||||||
|
|
||||||
public event Action<bool>? EnabledChange
|
public event Action<bool>? EnabledChange
|
||||||
{
|
{
|
||||||
add => _communicator.EnabledChanged.Subscribe(value!, EnabledChanged.Priority.Api);
|
add => communicator.EnabledChanged.Subscribe(value!, EnabledChanged.Priority.Api);
|
||||||
remove => _communicator.EnabledChanged.Unsubscribe(value!);
|
remove => communicator.EnabledChanged.Unsubscribe(value!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FrozenSet<string> SupportedFeatures
|
||||||
|
=> FeatureChecker.SupportedFeatures.ToFrozenSet();
|
||||||
|
|
||||||
|
public string[] CheckSupportedFeatures(IEnumerable<string> requiredFeatures)
|
||||||
|
=> requiredFeatures.Where(f => !FeatureChecker.Supported(f)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,8 @@ public sealed class IpcProviders : IDisposable, IApiService
|
||||||
IpcSubscribers.ModDirectoryChanged.Provider(pi, api.PluginState),
|
IpcSubscribers.ModDirectoryChanged.Provider(pi, api.PluginState),
|
||||||
IpcSubscribers.GetEnabledState.Provider(pi, api.PluginState),
|
IpcSubscribers.GetEnabledState.Provider(pi, api.PluginState),
|
||||||
IpcSubscribers.EnabledChange.Provider(pi, api.PluginState),
|
IpcSubscribers.EnabledChange.Provider(pi, api.PluginState),
|
||||||
|
IpcSubscribers.SupportedFeatures.Provider(pi, api.PluginState),
|
||||||
|
IpcSubscribers.CheckSupportedFeatures.Provider(pi, api.PluginState),
|
||||||
|
|
||||||
IpcSubscribers.RedrawObject.Provider(pi, api.Redraw),
|
IpcSubscribers.RedrawObject.Provider(pi, api.Redraw),
|
||||||
IpcSubscribers.RedrawAll.Provider(pi, api.Redraw),
|
IpcSubscribers.RedrawAll.Provider(pi, api.Redraw),
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
using OtterGui.Text;
|
||||||
using Penumbra.Api.Helpers;
|
using Penumbra.Api.Helpers;
|
||||||
using Penumbra.Api.IpcSubscribers;
|
using Penumbra.Api.IpcSubscribers;
|
||||||
|
|
||||||
|
|
@ -26,6 +27,9 @@ public class PluginStateIpcTester : IUiService, IDisposable
|
||||||
private readonly List<DateTimeOffset> _initializedList = [];
|
private readonly List<DateTimeOffset> _initializedList = [];
|
||||||
private readonly List<DateTimeOffset> _disposedList = [];
|
private readonly List<DateTimeOffset> _disposedList = [];
|
||||||
|
|
||||||
|
private string _requiredFeatureString = string.Empty;
|
||||||
|
private string[] _requiredFeatures = [];
|
||||||
|
|
||||||
private DateTimeOffset _lastEnabledChange = DateTimeOffset.UnixEpoch;
|
private DateTimeOffset _lastEnabledChange = DateTimeOffset.UnixEpoch;
|
||||||
private bool? _lastEnabledValue;
|
private bool? _lastEnabledValue;
|
||||||
|
|
||||||
|
|
@ -48,12 +52,15 @@ public class PluginStateIpcTester : IUiService, IDisposable
|
||||||
EnabledChange.Dispose();
|
EnabledChange.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
using var _ = ImRaii.TreeNode("Plugin State");
|
using var _ = ImRaii.TreeNode("Plugin State");
|
||||||
if (!_)
|
if (!_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (ImUtf8.InputText("Required Features"u8, ref _requiredFeatureString))
|
||||||
|
_requiredFeatures = _requiredFeatureString.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||||
if (!table)
|
if (!table)
|
||||||
return;
|
return;
|
||||||
|
|
@ -71,6 +78,12 @@ public class PluginStateIpcTester : IUiService, IDisposable
|
||||||
IpcTester.DrawIntro(IpcSubscribers.EnabledChange.Label, "Last Change");
|
IpcTester.DrawIntro(IpcSubscribers.EnabledChange.Label, "Last Change");
|
||||||
ImGui.TextUnformatted(_lastEnabledValue is { } v ? $"{_lastEnabledChange} (to {v})" : "Never");
|
ImGui.TextUnformatted(_lastEnabledValue is { } v ? $"{_lastEnabledChange} (to {v})" : "Never");
|
||||||
|
|
||||||
|
IpcTester.DrawIntro(SupportedFeatures.Label, "Supported Features");
|
||||||
|
ImUtf8.Text(string.Join(", ", new SupportedFeatures(_pi).Invoke()));
|
||||||
|
|
||||||
|
IpcTester.DrawIntro(CheckSupportedFeatures.Label, "Missing Features");
|
||||||
|
ImUtf8.Text(string.Join(", ", new CheckSupportedFeatures(_pi).Invoke(_requiredFeatures)));
|
||||||
|
|
||||||
DrawConfigPopup();
|
DrawConfigPopup();
|
||||||
IpcTester.DrawIntro(GetConfiguration.Label, "Configuration");
|
IpcTester.DrawIntro(GetConfiguration.Label, "Configuration");
|
||||||
if (ImGui.Button("Get"))
|
if (ImGui.Button("Get"))
|
||||||
|
|
|
||||||
95
Penumbra/Mods/FeatureChecker.cs
Normal file
95
Penumbra/Mods/FeatureChecker.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui.Text;
|
||||||
|
using Penumbra.Mods.Manager;
|
||||||
|
using Penumbra.UI.Classes;
|
||||||
|
using Notification = OtterGui.Classes.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 = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||||
|
var textColor = ImGui.GetColorU32(ImGuiCol.TextDisabled);
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, innerSpacing)
|
||||||
|
.Push(ImGuiStyleVar.FrameBorderSize, 0);
|
||||||
|
using (var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value())
|
||||||
|
.Push(ImGuiCol.Button, buttonColor)
|
||||||
|
.Push(ImGuiCol.Text, textColor))
|
||||||
|
{
|
||||||
|
foreach (var flag in SupportedFlags.Values)
|
||||||
|
{
|
||||||
|
if (mod.RequiredFeatures.HasFlag(flag))
|
||||||
|
{
|
||||||
|
style.Push(ImGuiStyleVar.FrameBorderSize, ImUtf8.GlobalScale);
|
||||||
|
color.Pop(2);
|
||||||
|
if (ImUtf8.Button($"{flag}", size))
|
||||||
|
editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures & ~flag);
|
||||||
|
color.Push(ImGuiCol.Button, buttonColor)
|
||||||
|
.Push(ImGuiCol.Text, textColor);
|
||||||
|
style.Pop();
|
||||||
|
}
|
||||||
|
else if (ImUtf8.Button($"{flag}", size))
|
||||||
|
{
|
||||||
|
editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures | flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImUtf8.ButtonEx("Compute"u8, "Compute the required features automatically from the used features."u8, size))
|
||||||
|
editor.ChangeRequiredFeatures(mod, mod.ComputeRequiredFeatures());
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImUtf8.ButtonEx("Clear"u8, "Clear all required features."u8, size))
|
||||||
|
editor.ChangeRequiredFeatures(mod, FeatureFlags.None);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImUtf8.Text("Required Features"u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ public enum ModDataChangeType : ushort
|
||||||
Image = 0x1000,
|
Image = 0x1000,
|
||||||
DefaultChangedItems = 0x2000,
|
DefaultChangedItems = 0x2000,
|
||||||
PreferredChangedItems = 0x4000,
|
PreferredChangedItems = 0x4000,
|
||||||
|
RequiredFeatures = 0x8000,
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ModDataEditor(SaveService saveService, CommunicatorService communicatorService, ItemData itemData) : IService
|
public class ModDataEditor(SaveService saveService, CommunicatorService communicatorService, ItemData itemData) : IService
|
||||||
|
|
@ -97,6 +98,16 @@ public class ModDataEditor(SaveService saveService, CommunicatorService communic
|
||||||
communicatorService.ModDataChanged.Invoke(ModDataChangeType.Website, mod, null);
|
communicatorService.ModDataChanged.Invoke(ModDataChangeType.Website, mod, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ChangeRequiredFeatures(Mod mod, FeatureFlags flags)
|
||||||
|
{
|
||||||
|
if (mod.RequiredFeatures == flags)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mod.RequiredFeatures = flags;
|
||||||
|
saveService.QueueSave(new ModMeta(mod));
|
||||||
|
communicatorService.ModDataChanged.Invoke(ModDataChangeType.RequiredFeatures, mod, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void ChangeModTag(Mod mod, int tagIdx, string newTag)
|
public void ChangeModTag(Mod mod, int tagIdx, string newTag)
|
||||||
=> ChangeTag(mod, tagIdx, newTag, false);
|
=> ChangeTag(mod, tagIdx, newTag, false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,7 @@ public sealed class ModManager : ModStorage, IDisposable, IService
|
||||||
_communicator.ModPathChanged.Invoke(ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath);
|
_communicator.ModPathChanged.Invoke(ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath);
|
||||||
if (!Creator.ReloadMod(mod, true, false, out var metaChange))
|
if (!Creator.ReloadMod(mod, true, false, out var metaChange))
|
||||||
{
|
{
|
||||||
|
if (mod.RequiredFeatures is not FeatureFlags.Invalid)
|
||||||
Penumbra.Log.Warning(mod.Name.Length == 0
|
Penumbra.Log.Warning(mod.Name.Length == 0
|
||||||
? $"Reloading mod {oldName} has failed, new name is empty. Removing from loaded mods instead."
|
? $"Reloading mod {oldName} has failed, new name is empty. Removing from loaded mods instead."
|
||||||
: $"Reloading mod {oldName} failed, {mod.ModPath.FullName} does not exist anymore or it has invalid data. Removing from loaded mods instead.");
|
: $"Reloading mod {oldName} failed, {mod.ModPath.FullName} does not exist anymore or it has invalid data. Removing from loaded mods instead.");
|
||||||
|
|
@ -251,12 +252,8 @@ public sealed class ModManager : ModStorage, IDisposable, IService
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case ModPathChangeType.Added:
|
case ModPathChangeType.Added: SetNew(mod); break;
|
||||||
SetNew(mod);
|
case ModPathChangeType.Deleted: SetKnown(mod); break;
|
||||||
break;
|
|
||||||
case ModPathChangeType.Deleted:
|
|
||||||
SetKnown(mod);
|
|
||||||
break;
|
|
||||||
case ModPathChangeType.Moved:
|
case ModPathChangeType.Moved:
|
||||||
if (oldDirectory != null && newDirectory != null)
|
if (oldDirectory != null && newDirectory != null)
|
||||||
DataEditor.MoveDataFile(oldDirectory, newDirectory);
|
DataEditor.MoveDataFile(oldDirectory, newDirectory);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using OtterGui;
|
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Extensions;
|
using OtterGui.Extensions;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
|
|
@ -12,6 +11,16 @@ using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
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 sealed class Mod : IMod
|
||||||
{
|
{
|
||||||
public static readonly TemporaryMod ForcedFiles = new()
|
public static readonly TemporaryMod ForcedFiles = new()
|
||||||
|
|
@ -57,6 +66,7 @@ public sealed class Mod : IMod
|
||||||
public string Image { get; internal set; } = string.Empty;
|
public string Image { get; internal set; } = string.Empty;
|
||||||
public IReadOnlyList<string> ModTags { get; internal set; } = [];
|
public IReadOnlyList<string> ModTags { get; internal set; } = [];
|
||||||
public HashSet<CustomItemId> DefaultPreferredItems { get; internal set; } = [];
|
public HashSet<CustomItemId> DefaultPreferredItems { get; internal set; } = [];
|
||||||
|
public FeatureFlags RequiredFeatures { get; internal set; } = 0;
|
||||||
|
|
||||||
|
|
||||||
// Local Data
|
// Local Data
|
||||||
|
|
@ -70,6 +80,23 @@ public sealed class Mod : IMod
|
||||||
public readonly DefaultSubMod Default;
|
public readonly DefaultSubMod Default;
|
||||||
public readonly List<IModGroup> Groups = [];
|
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)
|
public AppliedModData GetData(ModSettings? settings = null)
|
||||||
{
|
{
|
||||||
if (settings is not { Enabled: true })
|
if (settings is not { Enabled: true })
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ public partial class ModCreator(
|
||||||
MetaFileManager metaFileManager,
|
MetaFileManager metaFileManager,
|
||||||
GamePathParser gamePathParser) : IService
|
GamePathParser gamePathParser) : IService
|
||||||
{
|
{
|
||||||
|
public const FeatureFlags SupportedFeatures = FeatureFlags.Atch | FeatureFlags.Shp | FeatureFlags.Atr;
|
||||||
public readonly Configuration Config = config;
|
public readonly Configuration Config = config;
|
||||||
|
|
||||||
/// <summary> Creates directory and files necessary for a new mod without adding it to the manager. </summary>
|
/// <summary> Creates directory and files necessary for a new mod without adding it to the manager. </summary>
|
||||||
|
|
@ -74,7 +75,7 @@ public partial class ModCreator(
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
modDataChange = ModMeta.Load(dataEditor, this, mod);
|
modDataChange = ModMeta.Load(dataEditor, this, mod);
|
||||||
if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0)
|
if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0 || mod.RequiredFeatures is FeatureFlags.Invalid)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
modDataChange |= ModLocalData.Load(dataEditor, mod);
|
modDataChange |= ModLocalData.Load(dataEditor, mod);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Mods.Editor;
|
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
|
|
@ -28,6 +27,19 @@ public readonly struct ModMeta(Mod mod) : ISavable
|
||||||
{ nameof(Mod.ModTags), JToken.FromObject(mod.ModTags) },
|
{ nameof(Mod.ModTags), JToken.FromObject(mod.ModTags) },
|
||||||
{ nameof(Mod.DefaultPreferredItems), JToken.FromObject(mod.DefaultPreferredItems) },
|
{ nameof(Mod.DefaultPreferredItems), JToken.FromObject(mod.DefaultPreferredItems) },
|
||||||
};
|
};
|
||||||
|
if (mod.RequiredFeatures is not FeatureFlags.None)
|
||||||
|
{
|
||||||
|
var features = mod.RequiredFeatures;
|
||||||
|
var array = new JArray();
|
||||||
|
foreach (var flag in Enum.GetValues<FeatureFlags>())
|
||||||
|
{
|
||||||
|
if ((features & flag) is not FeatureFlags.None)
|
||||||
|
array.Add(flag.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
jObject[nameof(Mod.RequiredFeatures)] = array;
|
||||||
|
}
|
||||||
|
|
||||||
using var jWriter = new JsonTextWriter(writer);
|
using var jWriter = new JsonTextWriter(writer);
|
||||||
jWriter.Formatting = Formatting.Indented;
|
jWriter.Formatting = Formatting.Indented;
|
||||||
jObject.WriteTo(jWriter);
|
jObject.WriteTo(jWriter);
|
||||||
|
|
@ -60,6 +72,8 @@ public readonly struct ModMeta(Mod mod) : ISavable
|
||||||
var modTags = (json[nameof(Mod.ModTags)] as JArray)?.Values<string>().OfType<string>();
|
var modTags = (json[nameof(Mod.ModTags)] as JArray)?.Values<string>().OfType<string>();
|
||||||
var defaultItems = (json[nameof(Mod.DefaultPreferredItems)] as JArray)?.Values<ulong>().Select(i => (CustomItemId)i).ToHashSet()
|
var defaultItems = (json[nameof(Mod.DefaultPreferredItems)] as JArray)?.Values<ulong>().Select(i => (CustomItemId)i).ToHashSet()
|
||||||
?? [];
|
?? [];
|
||||||
|
var requiredFeatureArray = (json[nameof(Mod.RequiredFeatures)] as JArray)?.Values<string>() ?? [];
|
||||||
|
var requiredFeatures = FeatureChecker.ParseFlags(mod.ModPath.Name, newName.Length > 0 ? newName : mod.Name.Length > 0 ? mod.Name : "Unknown", requiredFeatureArray!);
|
||||||
|
|
||||||
ModDataChangeType changes = 0;
|
ModDataChangeType changes = 0;
|
||||||
if (mod.Name != newName)
|
if (mod.Name != newName)
|
||||||
|
|
@ -111,6 +125,13 @@ public readonly struct ModMeta(Mod mod) : ISavable
|
||||||
editor.SaveService.ImmediateSave(new ModMeta(mod));
|
editor.SaveService.ImmediateSave(new ModMeta(mod));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Required features get checked during parsing, in which case the new required features signal invalid.
|
||||||
|
if (requiredFeatures != mod.RequiredFeatures)
|
||||||
|
{
|
||||||
|
changes |= ModDataChangeType.RequiredFeatures;
|
||||||
|
mod.RequiredFeatures = requiredFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
changes |= ModLocalData.UpdateTags(mod, modTags, null);
|
changes |= ModLocalData.UpdateTags(mod, modTags, null);
|
||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,10 @@ public class ModPanelEditTab(
|
||||||
messager.NotificationMessage(e.Message, NotificationType.Warning, false);
|
messager.NotificationMessage(e.Message, NotificationType.Warning, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UiHelpers.DefaultLineSpace();
|
||||||
|
|
||||||
|
FeatureChecker.DrawFeatureFlagInput(modManager.DataEditor, _mod, UiHelpers.InputTextWidth.X);
|
||||||
|
|
||||||
UiHelpers.DefaultLineSpace();
|
UiHelpers.DefaultLineSpace();
|
||||||
var sharedTagsEnabled = predefinedTagManager.Count > 0;
|
var sharedTagsEnabled = predefinedTagManager.Count > 0;
|
||||||
var sharedTagButtonOffset = sharedTagsEnabled ? ImGui.GetFrameHeight() + ImGui.GetStyle().FramePadding.X : 0;
|
var sharedTagButtonOffset = sharedTagsEnabled ? ImGui.GetFrameHeight() + ImGui.GetStyle().FramePadding.X : 0;
|
||||||
|
|
@ -76,6 +80,7 @@ public class ModPanelEditTab(
|
||||||
predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, false,
|
predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, false,
|
||||||
selector.Selected!);
|
selector.Selected!);
|
||||||
|
|
||||||
|
|
||||||
UiHelpers.DefaultLineSpace();
|
UiHelpers.DefaultLineSpace();
|
||||||
addGroupDrawer.Draw(_mod, UiHelpers.InputTextWidth.X);
|
addGroupDrawer.Draw(_mod, UiHelpers.InputTextWidth.X);
|
||||||
UiHelpers.DefaultLineSpace();
|
UiHelpers.DefaultLineSpace();
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ public class ShapeInspector(ObjectManager objects, CollectionResolver resolver)
|
||||||
if (set.All is { } value)
|
if (set.All is { } value)
|
||||||
{
|
{
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value);
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value);
|
||||||
ImUtf8.Text("All"u8);
|
ImUtf8.Text("All, "u8);
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,14 @@
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"uniqueItems": true
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"RequiredFeatures": {
|
||||||
|
"description": "A list of required features by name.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uniqueItems": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue