mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Update with workable prototype.
This commit is contained in:
parent
e77fa18c61
commit
795fa7336e
9 changed files with 168 additions and 116 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit fd387218d2d2d237075cb35be6ca89eeb53e14e5
|
||||
Subproject commit 055f169572223fd1b59389549c88b4c861c94608
|
||||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue