mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Merge branch 'combining'
This commit is contained in:
commit
d2a8cec01f
13 changed files with 542 additions and 48 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit fd387218d2d2d237075cb35be6ca89eeb53e14e5
|
||||
Subproject commit 055f169572223fd1b59389549c88b4c861c94608
|
||||
196
Penumbra/Mods/Groups/CombiningModGroup.cs
Normal file
196
Penumbra/Mods/Groups/CombiningModGroup.cs
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Settings;
|
||||
using Penumbra.Mods.SubMods;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.UI.ModsTab.Groups;
|
||||
using Penumbra.Util;
|
||||
|
||||
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;
|
||||
|
||||
public GroupDrawBehaviour Behaviour
|
||||
=> GroupDrawBehaviour.MultiSelection;
|
||||
|
||||
public Mod Mod { get; }
|
||||
public string Name { get; set; } = "Group";
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Image { get; set; } = string.Empty;
|
||||
public ModPriority Priority { get; set; }
|
||||
public int Page { get; set; }
|
||||
public Setting DefaultSettings { get; set; }
|
||||
public readonly List<CombiningSubMod> OptionData = [];
|
||||
public List<CombinedDataContainer> Data { get; private set; }
|
||||
|
||||
/// <summary> Groups that allow all available options to be selected at once. </summary>
|
||||
public CombiningModGroup(Mod mod)
|
||||
{
|
||||
Mod = mod;
|
||||
Data = [new CombinedDataContainer(this)];
|
||||
}
|
||||
|
||||
IReadOnlyList<IModOption> IModGroup.Options
|
||||
=> OptionData;
|
||||
|
||||
public IReadOnlyList<IModDataContainer> DataContainers
|
||||
=> Data;
|
||||
|
||||
public bool IsOption
|
||||
=> OptionData.Count > 0;
|
||||
|
||||
public FullPath? FindBestMatch(Utf8GamePath gamePath)
|
||||
{
|
||||
foreach (var path in Data.SelectWhere(o
|
||||
=> (o.Files.TryGetValue(gamePath, out var file) || o.FileSwaps.TryGetValue(gamePath, out file), file)))
|
||||
return path;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IModOption? AddOption(string name, string description = "")
|
||||
{
|
||||
var groupIdx = Mod.Groups.IndexOf(this);
|
||||
if (groupIdx < 0)
|
||||
return null;
|
||||
|
||||
var subMod = new CombiningSubMod(this)
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
};
|
||||
return OptionData.AddNewWithPowerSet(Data, subMod, () => new CombinedDataContainer(this), IModGroup.MaxCombiningOptions)
|
||||
? subMod
|
||||
: null;
|
||||
}
|
||||
|
||||
public static CombiningModGroup? Load(Mod mod, JObject json)
|
||||
{
|
||||
var ret = new CombiningModGroup(mod, true);
|
||||
if (!ModSaveGroup.ReadJsonBase(json, ret))
|
||||
return null;
|
||||
|
||||
var options = json["Options"];
|
||||
if (options != null)
|
||||
foreach (var child in options.Children())
|
||||
{
|
||||
if (ret.OptionData.Count == IModGroup.MaxCombiningOptions)
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Combining Group {ret.Name} in {mod.Name} has more than {IModGroup.MaxCombiningOptions} options, ignoring excessive options.",
|
||||
NotificationType.Warning);
|
||||
break;
|
||||
}
|
||||
|
||||
var subMod = new CombiningSubMod(ret, child);
|
||||
ret.OptionData.Add(subMod);
|
||||
}
|
||||
|
||||
var requiredContainers = 1 << ret.OptionData.Count;
|
||||
var containers = json["Containers"];
|
||||
if (containers != null)
|
||||
foreach (var child in containers.Children())
|
||||
{
|
||||
if (requiredContainers <= ret.Data.Count)
|
||||
{
|
||||
Penumbra.Messager.NotificationMessage(
|
||||
$"Combining Group {ret.Name} in {mod.Name} has more data containers than it can support with {ret.OptionData.Count} options, ignoring excessive containers.",
|
||||
NotificationType.Warning);
|
||||
break;
|
||||
}
|
||||
|
||||
var container = new CombinedDataContainer(ret, child);
|
||||
ret.Data.Add(container);
|
||||
}
|
||||
|
||||
if (requiredContainers > ret.Data.Count)
|
||||
{
|
||||
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.Data.EnsureCapacity(requiredContainers);
|
||||
ret.Data.AddRange(Enumerable.Repeat(0, requiredContainers - ret.Data.Count).Select(_ => new CombinedDataContainer(ret)));
|
||||
}
|
||||
|
||||
ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public int GetIndex()
|
||||
=> ModGroup.GetIndex(this);
|
||||
|
||||
public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer)
|
||||
=> new CombiningModGroupEditDrawer(editDrawer, this);
|
||||
|
||||
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, MetaDictionary manipulations)
|
||||
=> Data[setting.AsIndex].AddDataTo(redirections, manipulations);
|
||||
|
||||
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, IIdentifiedObjectData?> changedItems)
|
||||
{
|
||||
foreach (var container in DataContainers)
|
||||
identifier.AddChangedItems(container, changedItems);
|
||||
}
|
||||
|
||||
public void WriteJson(JsonTextWriter jWriter, JsonSerializer serializer, DirectoryInfo? basePath = null)
|
||||
{
|
||||
ModSaveGroup.WriteJsonBase(jWriter, this);
|
||||
jWriter.WritePropertyName("Options");
|
||||
jWriter.WriteStartArray();
|
||||
foreach (var option in OptionData)
|
||||
{
|
||||
jWriter.WriteStartObject();
|
||||
SubMod.WriteModOption(jWriter, option);
|
||||
jWriter.WriteEndObject();
|
||||
}
|
||||
|
||||
jWriter.WriteEndArray();
|
||||
|
||||
jWriter.WritePropertyName("Containers");
|
||||
jWriter.WriteStartArray();
|
||||
foreach (var container in Data)
|
||||
{
|
||||
jWriter.WriteStartObject();
|
||||
if (container.Name.Length > 0)
|
||||
{
|
||||
jWriter.WritePropertyName("Name");
|
||||
jWriter.WriteValue(container.Name);
|
||||
}
|
||||
|
||||
SubMod.WriteModContainer(jWriter, serializer, container, basePath ?? Mod.ModPath);
|
||||
jWriter.WriteEndObject();
|
||||
}
|
||||
|
||||
jWriter.WriteEndArray();
|
||||
}
|
||||
|
||||
public (int Redirections, int Swaps, int Manips) GetCounts()
|
||||
=> ModGroup.GetCountsBase(this);
|
||||
|
||||
public Setting FixSetting(Setting setting)
|
||||
=> new(Math.Min(setting.Value, (ulong)(Data.Count - 1)));
|
||||
|
||||
/// <summary> Create a group without a mod only for saving it in the creator. </summary>
|
||||
internal static CombiningModGroup WithoutMod(string name)
|
||||
=> new(null!)
|
||||
{
|
||||
Name = name,
|
||||
};
|
||||
|
||||
/// <summary> For loading when no empty container should be created. </summary>
|
||||
private CombiningModGroup(Mod mod, bool _)
|
||||
{
|
||||
Mod = mod;
|
||||
Data = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,8 @@ public enum GroupDrawBehaviour
|
|||
|
||||
public interface IModGroup
|
||||
{
|
||||
public const int MaxMultiOptions = 32;
|
||||
public const int MaxMultiOptions = 32;
|
||||
public const int MaxCombiningOptions = 8;
|
||||
|
||||
public Mod Mod { get; }
|
||||
public string Name { get; set; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Mods.Groups;
|
||||
using Penumbra.Mods.Settings;
|
||||
using Penumbra.Mods.SubMods;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Mods.Manager.OptionEditor;
|
||||
|
||||
public sealed class CombiningModGroupEditor(CommunicatorService communicator, SaveService saveService, Configuration config)
|
||||
: ModOptionEditor<CombiningModGroup, CombiningSubMod>(communicator, saveService, config), IService
|
||||
{
|
||||
protected override CombiningModGroup CreateGroup(Mod mod, string newName, ModPriority priority, SaveType saveType = SaveType.ImmediateSync)
|
||||
=> new(mod)
|
||||
{
|
||||
Name = newName,
|
||||
Priority = priority,
|
||||
};
|
||||
|
||||
protected override CombiningSubMod? CloneOption(CombiningModGroup group, IModOption option)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
protected override void RemoveOption(CombiningModGroup group, int optionIndex)
|
||||
{
|
||||
if (group.OptionData.RemoveWithPowerSet(group.Data, optionIndex))
|
||||
group.DefaultSettings.RemoveBit(optionIndex);
|
||||
}
|
||||
|
||||
protected override bool MoveOption(CombiningModGroup group, int optionIdxFrom, int optionIdxTo)
|
||||
{
|
||||
if (!group.OptionData.MoveWithPowerSet(group.Data, ref optionIdxFrom, ref optionIdxTo))
|
||||
return false;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ public class ModGroupEditor(
|
|||
SingleModGroupEditor singleEditor,
|
||||
MultiModGroupEditor multiEditor,
|
||||
ImcModGroupEditor imcEditor,
|
||||
CombiningModGroupEditor combiningEditor,
|
||||
CommunicatorService communicator,
|
||||
SaveService saveService,
|
||||
Configuration config) : IService
|
||||
|
|
@ -50,6 +51,9 @@ public class ModGroupEditor(
|
|||
public ImcModGroupEditor ImcEditor
|
||||
=> imcEditor;
|
||||
|
||||
public CombiningModGroupEditor CombiningEditor
|
||||
=> combiningEditor;
|
||||
|
||||
/// <summary> Change the settings stored as default options in a mod.</summary>
|
||||
public void ChangeModGroupDefaultOption(IModGroup group, Setting defaultOption)
|
||||
{
|
||||
|
|
@ -223,52 +227,60 @@ public class ModGroupEditor(
|
|||
case ImcSubMod i:
|
||||
ImcEditor.DeleteOption(i);
|
||||
return;
|
||||
case CombiningSubMod c:
|
||||
CombiningEditor.DeleteOption(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public IModOption? AddOption(IModGroup group, IModOption option)
|
||||
=> group switch
|
||||
{
|
||||
SingleModGroup s => SingleEditor.AddOption(s, option),
|
||||
MultiModGroup m => MultiEditor.AddOption(m, option),
|
||||
ImcModGroup i => ImcEditor.AddOption(i, option),
|
||||
_ => null,
|
||||
SingleModGroup s => SingleEditor.AddOption(s, option),
|
||||
MultiModGroup m => MultiEditor.AddOption(m, option),
|
||||
ImcModGroup i => ImcEditor.AddOption(i, option),
|
||||
CombiningModGroup c => CombiningEditor.AddOption(c, option),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
public IModOption? AddOption(IModGroup group, string newName)
|
||||
=> group switch
|
||||
{
|
||||
SingleModGroup s => SingleEditor.AddOption(s, newName),
|
||||
MultiModGroup m => MultiEditor.AddOption(m, newName),
|
||||
ImcModGroup i => ImcEditor.AddOption(i, newName),
|
||||
_ => null,
|
||||
SingleModGroup s => SingleEditor.AddOption(s, newName),
|
||||
MultiModGroup m => MultiEditor.AddOption(m, newName),
|
||||
ImcModGroup i => ImcEditor.AddOption(i, newName),
|
||||
CombiningModGroup c => CombiningEditor.AddOption(c, newName),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
public IModGroup? AddModGroup(Mod mod, GroupType type, string newName, SaveType saveType = SaveType.ImmediateSync)
|
||||
=> type switch
|
||||
{
|
||||
GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType),
|
||||
GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType),
|
||||
GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, default, saveType),
|
||||
_ => null,
|
||||
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, saveType),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
public (IModGroup?, int, bool) FindOrAddModGroup(Mod mod, GroupType type, string name, SaveType saveType = SaveType.ImmediateSync)
|
||||
=> type switch
|
||||
{
|
||||
GroupType.Single => SingleEditor.FindOrAddModGroup(mod, name, saveType),
|
||||
GroupType.Multi => MultiEditor.FindOrAddModGroup(mod, name, saveType),
|
||||
GroupType.Imc => ImcEditor.FindOrAddModGroup(mod, name, saveType),
|
||||
_ => (null, -1, false),
|
||||
GroupType.Single => SingleEditor.FindOrAddModGroup(mod, name, saveType),
|
||||
GroupType.Multi => MultiEditor.FindOrAddModGroup(mod, name, saveType),
|
||||
GroupType.Imc => ImcEditor.FindOrAddModGroup(mod, name, saveType),
|
||||
GroupType.Combining => CombiningEditor.FindOrAddModGroup(mod, name, saveType),
|
||||
_ => (null, -1, false),
|
||||
};
|
||||
|
||||
public (IModOption?, int, bool) FindOrAddOption(IModGroup group, string name, SaveType saveType = SaveType.ImmediateSync)
|
||||
=> group switch
|
||||
{
|
||||
SingleModGroup s => SingleEditor.FindOrAddOption(s, name, saveType),
|
||||
MultiModGroup m => MultiEditor.FindOrAddOption(m, name, saveType),
|
||||
ImcModGroup i => ImcEditor.FindOrAddOption(i, name, saveType),
|
||||
_ => (null, -1, false),
|
||||
SingleModGroup s => SingleEditor.FindOrAddOption(s, name, saveType),
|
||||
MultiModGroup m => MultiEditor.FindOrAddOption(m, name, saveType),
|
||||
ImcModGroup i => ImcEditor.FindOrAddOption(i, name, saveType),
|
||||
CombiningModGroup c => CombiningEditor.FindOrAddOption(c, name, saveType),
|
||||
_ => (null, -1, false),
|
||||
};
|
||||
|
||||
public void MoveOption(IModOption option, int toIdx)
|
||||
|
|
@ -284,6 +296,9 @@ public class ModGroupEditor(
|
|||
case ImcSubMod i:
|
||||
ImcEditor.MoveOption(i, toIdx);
|
||||
return;
|
||||
case CombiningSubMod c:
|
||||
CombiningEditor.MoveOption(c, toIdx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
72
Penumbra/Mods/SubMods/CombinedDataContainer.cs
Normal file
72
Penumbra/Mods/SubMods/CombinedDataContainer.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.Mods.Groups;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Mods.SubMods;
|
||||
|
||||
public class CombinedDataContainer(IModGroup group) : IModDataContainer
|
||||
{
|
||||
public IMod Mod
|
||||
=> Group.Mod;
|
||||
|
||||
public IModGroup Group { get; } = group;
|
||||
|
||||
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();
|
||||
|
||||
public void AddDataTo(Dictionary<Utf8GamePath, FullPath> redirections, MetaDictionary manipulations)
|
||||
=> SubMod.AddContainerTo(this, redirections, manipulations);
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
if (Name.Length > 0)
|
||||
return Name;
|
||||
|
||||
var index = GetDataIndex();
|
||||
if (index == 0)
|
||||
return "None";
|
||||
|
||||
var sb = new StringBuilder(128);
|
||||
for (var i = 0; i < IModGroup.MaxCombiningOptions; ++i)
|
||||
{
|
||||
if ((index & 1) != 0)
|
||||
{
|
||||
sb.Append(Group.Options[i].Name);
|
||||
sb.Append(' ').Append('+').Append(' ');
|
||||
}
|
||||
|
||||
index >>= 1;
|
||||
if (index == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return sb.ToString(0, sb.Length - 3);
|
||||
}
|
||||
|
||||
public string GetFullName()
|
||||
=> $"{Group.Name}: {GetName()}";
|
||||
|
||||
public (int GroupIndex, int DataIndex) GetDataIndices()
|
||||
=> (Group.GetIndex(), GetDataIndex());
|
||||
|
||||
private int GetDataIndex()
|
||||
{
|
||||
var dataIndex = Group.DataContainers.IndexOf(this);
|
||||
if (dataIndex < 0)
|
||||
throw new Exception($"Group {Group.Name} from SubMod {Name} does not contain this SubMod.");
|
||||
|
||||
return dataIndex;
|
||||
}
|
||||
|
||||
public CombinedDataContainer(CombiningModGroup group, JToken token)
|
||||
: this(group)
|
||||
{
|
||||
SubMod.LoadDataContainer(token, this, group.Mod.ModPath);
|
||||
Name = token["Name"]?.ToObject<string>() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
25
Penumbra/Mods/SubMods/CombiningSubMod.cs
Normal file
25
Penumbra/Mods/SubMods/CombiningSubMod.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.Mods.Groups;
|
||||
|
||||
namespace Penumbra.Mods.SubMods;
|
||||
|
||||
public class CombiningSubMod(IModGroup group) : IModOption
|
||||
{
|
||||
public IModGroup Group { get; } = group;
|
||||
|
||||
public Mod Mod
|
||||
=> Group.Mod;
|
||||
|
||||
public string Name { get; set; } = "Option";
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public string FullName
|
||||
=> $"{Group.Name}: {Name}";
|
||||
|
||||
public int GetIndex()
|
||||
=> SubMod.GetIndex(this);
|
||||
|
||||
public CombiningSubMod(CombiningModGroup group, JToken json)
|
||||
: this(group)
|
||||
=> SubMod.LoadOptionData(json, this);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -452,19 +452,17 @@ public partial class ModEditWindow : Window, IDisposable, IUiService
|
|||
|
||||
private bool DrawOptionSelectHeader()
|
||||
{
|
||||
const string defaultOption = "Default Option";
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero).Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
var width = new Vector2(ImGui.GetContentRegionAvail().X / 3, 0);
|
||||
var ret = false;
|
||||
if (ImGuiUtil.DrawDisabledButton(defaultOption, width, "Switch to the default option for the mod.\nThis resets unsaved changes.",
|
||||
_editor.Option is DefaultSubMod))
|
||||
if (ImUtf8.ButtonEx("Default Option"u8, "Switch to the default option for the mod.\nThis resets unsaved changes."u8, width, _editor.Option is DefaultSubMod))
|
||||
{
|
||||
_editor.LoadOption(-1, 0).Wait();
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.DrawDisabledButton("Refresh Data", width, "Refresh data for the current option.\nThis resets unsaved changes.", false))
|
||||
if (ImUtf8.ButtonEx("Refresh Data"u8, "Refresh data for the current option.\nThis resets unsaved changes."u8, width))
|
||||
{
|
||||
_editor.LoadMod(_editor.Mod!, _editor.GroupIdx, _editor.DataIdx).Wait();
|
||||
ret = true;
|
||||
|
|
@ -474,7 +472,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService
|
|||
ImGui.SetNextItemWidth(width.X);
|
||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value());
|
||||
using var combo = ImRaii.Combo("##optionSelector", _editor.Option!.GetFullName());
|
||||
using var combo = ImUtf8.Combo("##optionSelector"u8, _editor.Option!.GetFullName());
|
||||
if (!combo)
|
||||
return ret;
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ public class AddGroupDrawer : IUiService
|
|||
DrawSingleGroupButton(mod, buttonWidth);
|
||||
ImUtf8.SameLineInner();
|
||||
DrawMultiGroupButton(mod, buttonWidth);
|
||||
DrawCombiningGroupButton(mod, buttonWidth);
|
||||
}
|
||||
|
||||
private void DrawSingleGroupButton(Mod mod, Vector2 width)
|
||||
|
|
@ -76,6 +77,18 @@ public class AddGroupDrawer : IUiService
|
|||
_groupNameValid = false;
|
||||
}
|
||||
|
||||
private void DrawCombiningGroupButton(Mod mod, Vector2 width)
|
||||
{
|
||||
if (!ImUtf8.ButtonEx("Add Combining Group"u8, _groupNameValid
|
||||
? "Add a new combining option group to this mod."u8
|
||||
: "Can not add a new group of this name."u8,
|
||||
width, !_groupNameValid))
|
||||
return;
|
||||
|
||||
_modManager.OptionEditor.AddModGroup(mod, GroupType.Combining, _groupName);
|
||||
_groupName = string.Empty;
|
||||
_groupNameValid = false;
|
||||
}
|
||||
private void DrawImcInput(float width)
|
||||
{
|
||||
var change = ImcMetaDrawer.DrawObjectType(ref _imcIdentifier, width);
|
||||
|
|
|
|||
111
Penumbra/UI/ModsTab/Groups/CombiningModGroupEditDrawer.cs
Normal file
111
Penumbra/UI/ModsTab/Groups/CombiningModGroupEditDrawer.cs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
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;
|
||||
|
||||
public readonly struct CombiningModGroupEditDrawer(ModGroupEditDrawer editor, CombiningModGroup group) : IModGroupEditDrawer
|
||||
{
|
||||
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