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 OtterGui;
using OtterGui.Classes;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
using Penumbra.GameData.Data;
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>
public sealed class CombiningModGroup : IModGroup
{
public GroupType Type
=> GroupType.Combining;
@ -60,33 +58,6 @@ public sealed class CombiningModGroup : IModGroup
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 = "")
{
var groupIdx = Mod.Groups.IndexOf(this);
@ -98,10 +69,9 @@ public sealed class CombiningModGroup : IModGroup
Name = name,
Description = description,
};
// Double available containers.
FillContainers(2 * Data.Count);
OptionData.Add(subMod);
return subMod;
return OptionData.AddNewWithPowerSet(Data, subMod, () => new CombinedDataContainer(this), IModGroup.MaxCombiningOptions)
? subMod
: null;
}
public static CombiningModGroup? Load(Mod mod, JObject json)
@ -148,7 +118,8 @@ public sealed class CombiningModGroup : IModGroup
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.",
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);
@ -222,14 +193,4 @@ public sealed class CombiningModGroup : IModGroup
Mod = mod;
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.Filesystem;
using OtterGui.Services;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Settings;
@ -19,54 +19,30 @@ public sealed class CombiningModGroupEditor(CommunicatorService communicator, Sa
};
protected override CombiningSubMod? CloneOption(CombiningModGroup group, IModOption option)
{
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;
}
=> throw new NotImplementedException();
protected override void RemoveOption(CombiningModGroup group, int optionIndex)
{
var optionFlag = 1 << optionIndex;
for (var i = group.Data.Count - 1; i >= 0; --i)
{
group.Data.RemoveAll()
if ((i & optionFlag) == optionFlag)
group.Data.RemoveAt(i);
}
group.OptionData.RemoveAt(optionIndex);
group.DefaultSettings = group.DefaultSettings.RemoveBit(optionIndex);
if (group.OptionData.RemoveWithPowerSet(group.Data, optionIndex))
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;
group.DefaultSettings = group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
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:
ImcEditor.DeleteOption(i);
return;
case CombiningModGroup c:
case CombiningSubMod c:
CombiningEditor.DeleteOption(c);
return;
}
@ -259,7 +259,7 @@ public class ModGroupEditor(
GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType),
GroupType.Multi => MultiEditor.AddModGroup(mod, newName, 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,
};

View file

@ -82,13 +82,11 @@ public partial class ModCreator(
if (incorporateMetaChanges)
IncorporateAllMetaChanges(mod, true);
if (deleteDefaultMetaChanges && !Config.KeepDefaultMetaChanges)
{
foreach (var container in mod.AllDataContainers)
{
if (ModMetaEditor.DeleteDefaultValues(metaFileManager, container.Manipulations))
saveService.ImmediateSaveSync(new ModSaveGroup(container, Config.ReplaceNonAsciiOnImport));
}
}
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 delete is true, the files are deleted afterwards.
/// </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 oldSize = option.Manipulations.Count;
@ -447,9 +446,10 @@ public partial class ModCreator(
var json = JObject.Parse(File.ReadAllText(file.FullName));
switch (json[nameof(Type)]?.ToObject<GroupType>() ?? GroupType.Single)
{
case GroupType.Multi: return MultiModGroup.Load(mod, json);
case GroupType.Single: return SingleModGroup.Load(mod, json);
case GroupType.Imc: return ImcModGroup.Load(mod, json);
case GroupType.Multi: return MultiModGroup.Load(mod, json);
case GroupType.Single: return SingleModGroup.Load(mod, json);
case GroupType.Imc: return ImcModGroup.Load(mod, json);
case GroupType.Combining: return CombiningModGroup.Load(mod, json);
}
}
catch (Exception e)

View file

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

View file

@ -81,29 +81,40 @@ public static class SubMod
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
public static void WriteModContainer(JsonWriter j, JsonSerializer serializer, IModDataContainer data, DirectoryInfo basePath)
{
j.WritePropertyName(nameof(data.Files));
j.WriteStartObject();
foreach (var (gamePath, file) in data.Files)
if (data.Files.Count > 0)
{
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.WriteValue(relPath.ToString());
j.WriteValue(file.ToString());
}
j.WriteEndObject();
}
j.WriteEndObject();
j.WritePropertyName(nameof(data.FileSwaps));
j.WriteStartObject();
foreach (var (gamePath, file) in data.FileSwaps)
if (data.Manipulations.Count > 0)
{
j.WritePropertyName(gamePath.ToString());
j.WriteValue(file.ToString());
j.WritePropertyName(nameof(data.Manipulations));
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>

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.SubMods;
namespace Penumbra.UI.ModsTab.Groups;
@ -6,6 +12,100 @@ public readonly struct CombiningModGroupEditDrawer(ModGroupEditDrawer editor, Co
{
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 bool _draggingAcross;
internal string? CombiningDisplayName;
internal int CombiningDisplayIndex;
public void Draw(Mod mod)
{
PrepareStyle();
@ -275,6 +278,7 @@ public sealed class ModGroupEditDrawer(
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal string DrawNewOptionBase(IModGroup group, int count)
{
ImGui.AlignTextToFramePadding();
ImUtf8.Selectable($"Option #{count + 1}", false, size: OptionIdxSelectable);
Target(group, count);