Update with workable prototype.

This commit is contained in:
Ottermandias 2025-01-15 17:44:22 +01:00
parent e77fa18c61
commit 795fa7336e
9 changed files with 168 additions and 116 deletions

@ -1 +1 @@
Subproject commit fd387218d2d2d237075cb35be6ca89eeb53e14e5 Subproject commit 055f169572223fd1b59389549c88b4c861c94608

View file

@ -3,7 +3,6 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Filesystem;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
@ -18,7 +17,6 @@ namespace Penumbra.Mods.Groups;
/// <summary> Groups that allow all available options to be selected at once. </summary> /// <summary> Groups that allow all available options to be selected at once. </summary>
public sealed class CombiningModGroup : IModGroup public sealed class CombiningModGroup : IModGroup
{ {
public GroupType Type public GroupType Type
=> GroupType.Combining; => GroupType.Combining;
@ -60,33 +58,6 @@ public sealed class CombiningModGroup : IModGroup
return null; return null;
} }
public void RemoveOption(int index)
{
if(index < 0 || index >= OptionData.Count)
return;
OptionData.RemoveAt(index);
var list = new List<CombinedDataContainer>(Data.Count / 2);
var optionFlag = 1 << index;
list.AddRange(Data.Where((c, i) => (i & optionFlag) == 0));
Data = list;
}
public void MoveOption(int from, int to)
{
if (!OptionData.Move(ref from, ref to))
return;
var list = new List<CombinedDataContainer>(Data.Count);
for (var i = 0ul; i < (ulong)Data.Count; ++i)
{
var actualIndex = (int) Functions.MoveBit(i, from, to);
list.Add(Data[actualIndex]);
}
Data = list;
}
public IModOption? AddOption(string name, string description = "") public IModOption? AddOption(string name, string description = "")
{ {
var groupIdx = Mod.Groups.IndexOf(this); var groupIdx = Mod.Groups.IndexOf(this);
@ -98,10 +69,9 @@ public sealed class CombiningModGroup : IModGroup
Name = name, Name = name,
Description = description, Description = description,
}; };
// Double available containers. return OptionData.AddNewWithPowerSet(Data, subMod, () => new CombinedDataContainer(this), IModGroup.MaxCombiningOptions)
FillContainers(2 * Data.Count); ? subMod
OptionData.Add(subMod); : null;
return subMod;
} }
public static CombiningModGroup? Load(Mod mod, JObject json) public static CombiningModGroup? Load(Mod mod, JObject json)
@ -148,7 +118,8 @@ public sealed class CombiningModGroup : IModGroup
Penumbra.Messager.NotificationMessage( Penumbra.Messager.NotificationMessage(
$"Combining Group {ret.Name} in {mod.Name} has not enough data containers for its {ret.OptionData.Count} options, filling with empty containers.", $"Combining Group {ret.Name} in {mod.Name} has not enough data containers for its {ret.OptionData.Count} options, filling with empty containers.",
NotificationType.Warning); NotificationType.Warning);
ret.FillContainers(requiredContainers); ret.Data.EnsureCapacity(requiredContainers);
ret.Data.AddRange(Enumerable.Repeat(0, requiredContainers - ret.Data.Count).Select(_ => new CombinedDataContainer(ret)));
} }
ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings); ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings);
@ -222,14 +193,4 @@ public sealed class CombiningModGroup : IModGroup
Mod = mod; Mod = mod;
Data = []; Data = [];
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private void FillContainers(int requiredCount)
{
if (requiredCount <= Data.Count)
return;
Data.EnsureCapacity(requiredCount);
Data.AddRange(Enumerable.Repeat(0, requiredCount - Data.Count).Select(_ => new CombinedDataContainer(this)));
}
} }

View file

@ -1,5 +1,5 @@
using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Filesystem;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.Mods.Groups; using Penumbra.Mods.Groups;
using Penumbra.Mods.Settings; using Penumbra.Mods.Settings;
@ -19,54 +19,30 @@ public sealed class CombiningModGroupEditor(CommunicatorService communicator, Sa
}; };
protected override CombiningSubMod? CloneOption(CombiningModGroup group, IModOption option) protected override CombiningSubMod? CloneOption(CombiningModGroup group, IModOption option)
{ => throw new NotImplementedException();
if (group.OptionData.Count >= IModGroup.MaxCombiningOptions)
{
Penumbra.Log.Error(
$"Could not add option {option.Name} to {group.Name} for mod {group.Mod.Name}, "
+ $"since only up to {IModGroup.MaxCombiningOptions} options are supported in one group.");
return null;
}
var newOption = new CombiningSubMod(group)
{
Name = option.Name,
Description = option.Description,
};
if (option is IModDataContainer data)
{
SubMod.Clone(data, newOption);
if (option is MultiSubMod m)
newOption.Priority = m.Priority;
else
newOption.Priority = new ModPriority(group.OptionData.Max(o => o.Priority.Value) + 1);
}
group.OptionData.Add(newOption);
return newOption;
}
protected override void RemoveOption(CombiningModGroup group, int optionIndex) protected override void RemoveOption(CombiningModGroup group, int optionIndex)
{ {
var optionFlag = 1 << optionIndex; if (group.OptionData.RemoveWithPowerSet(group.Data, optionIndex))
for (var i = group.Data.Count - 1; i >= 0; --i) group.DefaultSettings.RemoveBit(optionIndex);
{
group.Data.RemoveAll()
if ((i & optionFlag) == optionFlag)
group.Data.RemoveAt(i);
}
group.OptionData.RemoveAt(optionIndex);
group.DefaultSettings = group.DefaultSettings.RemoveBit(optionIndex);
} }
protected override bool MoveOption(MultiModGroup group, int optionIdxFrom, int optionIdxTo) protected override bool MoveOption(CombiningModGroup group, int optionIdxFrom, int optionIdxTo)
{ {
if (!group.OptionData.Move(ref optionIdxFrom, ref optionIdxTo)) if (!group.OptionData.MoveWithPowerSet(group.Data, ref optionIdxFrom, ref optionIdxTo))
return false; return false;
group.DefaultSettings = group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo); group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
return true; return true;
} }
public void SetDisplayName(CombinedDataContainer container, string name, SaveType saveType = SaveType.Queue)
{
if (container.Name == name)
return;
container.Name = name;
SaveService.Save(saveType, new ModSaveGroup(container.Group, Config.ReplaceNonAsciiOnImport));
Communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, container.Group.Mod, container.Group, null, null, -1);
}
} }

View file

@ -227,7 +227,7 @@ public class ModGroupEditor(
case ImcSubMod i: case ImcSubMod i:
ImcEditor.DeleteOption(i); ImcEditor.DeleteOption(i);
return; return;
case CombiningModGroup c: case CombiningSubMod c:
CombiningEditor.DeleteOption(c); CombiningEditor.DeleteOption(c);
return; return;
} }
@ -259,7 +259,7 @@ public class ModGroupEditor(
GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType), GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType),
GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType), GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType),
GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, default, saveType), GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, default, saveType),
GroupType.Combining => CombiningEditor.AddModGroup(mod, newName, default, default, saveType), GroupType.Combining => CombiningEditor.AddModGroup(mod, newName, saveType),
_ => null, _ => null,
}; };

View file

@ -82,13 +82,11 @@ public partial class ModCreator(
if (incorporateMetaChanges) if (incorporateMetaChanges)
IncorporateAllMetaChanges(mod, true); IncorporateAllMetaChanges(mod, true);
if (deleteDefaultMetaChanges && !Config.KeepDefaultMetaChanges) if (deleteDefaultMetaChanges && !Config.KeepDefaultMetaChanges)
{
foreach (var container in mod.AllDataContainers) foreach (var container in mod.AllDataContainers)
{ {
if (ModMetaEditor.DeleteDefaultValues(metaFileManager, container.Manipulations)) if (ModMetaEditor.DeleteDefaultValues(metaFileManager, container.Manipulations))
saveService.ImmediateSaveSync(new ModSaveGroup(container, Config.ReplaceNonAsciiOnImport)); saveService.ImmediateSaveSync(new ModSaveGroup(container, Config.ReplaceNonAsciiOnImport));
} }
}
return true; return true;
} }
@ -186,7 +184,8 @@ public partial class ModCreator(
/// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod. /// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod.
/// If delete is true, the files are deleted afterwards. /// If delete is true, the files are deleted afterwards.
/// </summary> /// </summary>
public (bool Changes, List<string> DeleteList) IncorporateMetaChanges(IModDataContainer option, DirectoryInfo basePath, bool delete, bool deleteDefault) public (bool Changes, List<string> DeleteList) IncorporateMetaChanges(IModDataContainer option, DirectoryInfo basePath, bool delete,
bool deleteDefault)
{ {
var deleteList = new List<string>(); var deleteList = new List<string>();
var oldSize = option.Manipulations.Count; var oldSize = option.Manipulations.Count;
@ -447,9 +446,10 @@ public partial class ModCreator(
var json = JObject.Parse(File.ReadAllText(file.FullName)); var json = JObject.Parse(File.ReadAllText(file.FullName));
switch (json[nameof(Type)]?.ToObject<GroupType>() ?? GroupType.Single) switch (json[nameof(Type)]?.ToObject<GroupType>() ?? GroupType.Single)
{ {
case GroupType.Multi: return MultiModGroup.Load(mod, json); case GroupType.Multi: return MultiModGroup.Load(mod, json);
case GroupType.Single: return SingleModGroup.Load(mod, json); case GroupType.Single: return SingleModGroup.Load(mod, json);
case GroupType.Imc: return ImcModGroup.Load(mod, json); case GroupType.Imc: return ImcModGroup.Load(mod, json);
case GroupType.Combining: return CombiningModGroup.Load(mod, json);
} }
} }
catch (Exception e) catch (Exception e)

View file

@ -4,7 +4,6 @@ using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor; using Penumbra.Mods.Editor;
using Penumbra.Mods.Groups; using Penumbra.Mods.Groups;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Swan.Formatters;
namespace Penumbra.Mods.SubMods; namespace Penumbra.Mods.SubMods;
@ -15,7 +14,7 @@ public class CombinedDataContainer(IModGroup group) : IModDataContainer
public IModGroup Group { get; } = group; public IModGroup Group { get; } = group;
public string Name { get; } = string.Empty; public string Name { get; set; } = string.Empty;
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = []; public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = []; public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
public MetaDictionary Manipulations { get; set; } = new(); public MetaDictionary Manipulations { get; set; } = new();
@ -35,11 +34,12 @@ public class CombinedDataContainer(IModGroup group) : IModDataContainer
var sb = new StringBuilder(128); var sb = new StringBuilder(128);
for (var i = 0; i < IModGroup.MaxCombiningOptions; ++i) for (var i = 0; i < IModGroup.MaxCombiningOptions; ++i)
{ {
if ((index & 1) == 0) if ((index & 1) != 0)
continue; {
sb.Append(Group.Options[i].Name);
sb.Append(' ').Append('+').Append(' ');
}
sb.Append(Group.Options[i].Name);
sb.Append(' ').Append('+').Append(' ');
index >>= 1; index >>= 1;
if (index == 0) if (index == 0)
break; break;

View file

@ -81,29 +81,40 @@ public static class SubMod
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
public static void WriteModContainer(JsonWriter j, JsonSerializer serializer, IModDataContainer data, DirectoryInfo basePath) public static void WriteModContainer(JsonWriter j, JsonSerializer serializer, IModDataContainer data, DirectoryInfo basePath)
{ {
j.WritePropertyName(nameof(data.Files)); if (data.Files.Count > 0)
j.WriteStartObject();
foreach (var (gamePath, file) in data.Files)
{ {
if (file.ToRelPath(basePath, out var relPath)) j.WritePropertyName(nameof(data.Files));
j.WriteStartObject();
foreach (var (gamePath, file) in data.Files)
{
if (file.ToRelPath(basePath, out var relPath))
{
j.WritePropertyName(gamePath.ToString());
j.WriteValue(relPath.ToString());
}
}
j.WriteEndObject();
}
if (data.FileSwaps.Count > 0)
{
j.WritePropertyName(nameof(data.FileSwaps));
j.WriteStartObject();
foreach (var (gamePath, file) in data.FileSwaps)
{ {
j.WritePropertyName(gamePath.ToString()); j.WritePropertyName(gamePath.ToString());
j.WriteValue(relPath.ToString()); j.WriteValue(file.ToString());
} }
j.WriteEndObject();
} }
j.WriteEndObject(); if (data.Manipulations.Count > 0)
j.WritePropertyName(nameof(data.FileSwaps));
j.WriteStartObject();
foreach (var (gamePath, file) in data.FileSwaps)
{ {
j.WritePropertyName(gamePath.ToString()); j.WritePropertyName(nameof(data.Manipulations));
j.WriteValue(file.ToString()); serializer.Serialize(j, data.Manipulations);
} }
j.WriteEndObject();
j.WritePropertyName(nameof(data.Manipulations));
serializer.Serialize(j, data.Manipulations);
} }
/// <summary> Write the data for a selectable mod option on a JsonWriter. </summary> /// <summary> Write the data for a selectable mod option on a JsonWriter. </summary>

View file

@ -1,4 +1,10 @@
using Dalamud.Interface;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.Mods.Groups; using Penumbra.Mods.Groups;
using Penumbra.Mods.SubMods;
namespace Penumbra.UI.ModsTab.Groups; namespace Penumbra.UI.ModsTab.Groups;
@ -6,6 +12,100 @@ public readonly struct CombiningModGroupEditDrawer(ModGroupEditDrawer editor, Co
{ {
public void Draw() public void Draw()
{ {
foreach (var (option, optionIdx) in group.OptionData.WithIndex())
{
using var id = ImUtf8.PushId(optionIdx);
editor.DrawOptionPosition(group, option, optionIdx);
ImUtf8.SameLineInner();
editor.DrawOptionDefaultMultiBehaviour(group, option, optionIdx);
ImUtf8.SameLineInner();
editor.DrawOptionName(option);
ImUtf8.SameLineInner();
editor.DrawOptionDescription(option);
ImUtf8.SameLineInner();
editor.DrawOptionDelete(option);
}
DrawNewOption();
DrawContainerNames();
}
private void DrawNewOption()
{
var count = group.OptionData.Count;
if (count >= IModGroup.MaxCombiningOptions)
return;
var name = editor.DrawNewOptionBase(group, count);
var validName = name.Length > 0;
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName
? "Add a new option to this group."u8
: "Please enter a name for the new option."u8, default, !validName))
{
editor.ModManager.OptionEditor.CombiningEditor.AddOption(group, name);
editor.NewOptionName = null;
}
}
private unsafe void DrawContainerNames()
{
if (ImUtf8.ButtonEx("Edit Container Names"u8,
"Add optional names to separate data containers of the combining group.\nThose are just for easier identification while editing the mod, and are not generally displayed to the user."u8,
new Vector2(400 * ImUtf8.GlobalScale, 0)))
ImUtf8.OpenPopup("DataContainerNames"u8);
var sizeX = group.OptionData.Count * (ImGui.GetStyle().ItemInnerSpacing.X + ImGui.GetFrameHeight()) + 300 * ImUtf8.GlobalScale;
ImGui.SetNextWindowSize(new Vector2(sizeX, ImGui.GetFrameHeightWithSpacing() * Math.Min(16, group.Data.Count) + 200 * ImUtf8.GlobalScale));
using var popup = ImUtf8.Popup("DataContainerNames"u8);
if (!popup)
return;
foreach (var option in group.OptionData)
{
ImUtf8.RotatedText(option.Name, true);
ImUtf8.SameLineInner();
}
ImGui.NewLine();
ImGui.Separator();
using var child = ImUtf8.Child("##Child"u8, ImGui.GetContentRegionAvail());
ImGuiClip.ClippedDraw(group.Data, DrawRow, ImGui.GetFrameHeightWithSpacing());
}
private void DrawRow(CombinedDataContainer container, int index)
{
using var id = ImUtf8.PushId(index);
using (ImRaii.Disabled())
{
for (var i = 0; i < group.OptionData.Count; ++i)
{
id.Push(i);
var check = (index & (1 << i)) != 0;
ImUtf8.Checkbox(""u8, ref check);
ImUtf8.SameLineInner();
id.Pop();
}
}
var name = editor.CombiningDisplayIndex == index ? editor.CombiningDisplayName ?? container.Name : container.Name;
if (ImUtf8.InputText("##Nothing"u8, ref name, "Optional Display Name..."u8))
{
editor.CombiningDisplayIndex = index;
editor.CombiningDisplayName = name;
}
if (ImGui.IsItemDeactivatedAfterEdit())
editor.ModManager.OptionEditor.CombiningEditor.SetDisplayName(container, name);
if (ImGui.IsItemDeactivated())
{
editor.CombiningDisplayIndex = -1;
editor.CombiningDisplayName = null;
}
} }
} }

View file

@ -58,6 +58,9 @@ public sealed class ModGroupEditDrawer(
private IModOption? _dragDropOption; private IModOption? _dragDropOption;
private bool _draggingAcross; private bool _draggingAcross;
internal string? CombiningDisplayName;
internal int CombiningDisplayIndex;
public void Draw(Mod mod) public void Draw(Mod mod)
{ {
PrepareStyle(); PrepareStyle();
@ -275,6 +278,7 @@ public sealed class ModGroupEditDrawer(
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal string DrawNewOptionBase(IModGroup group, int count) internal string DrawNewOptionBase(IModGroup group, int count)
{ {
ImGui.AlignTextToFramePadding();
ImUtf8.Selectable($"Option #{count + 1}", false, size: OptionIdxSelectable); ImUtf8.Selectable($"Option #{count + 1}", false, size: OptionIdxSelectable);
Target(group, count); Target(group, count);