Add some early support for IMC groups.

This commit is contained in:
Ottermandias 2024-05-16 18:30:40 +02:00
parent d47d31b665
commit df6eb3fdd2
15 changed files with 360 additions and 146 deletions

@ -1 +1 @@
Subproject commit 866389b3988d9c4926a786f6c78ac9d5265591ac
Subproject commit 5028fba767ca8febd75a1a5ebc312bd354efc81b

View file

@ -36,7 +36,7 @@ public readonly struct ImcCache : IDisposable
public bool ApplyMod(MetaFileManager manager, ModCollection collection, ImcManipulation manip)
{
if (!manip.Validate())
if (!manip.Validate(true))
return false;
var idx = _imcManipulations.FindIndex(p => p.Item1.Equals(manip));
@ -77,7 +77,7 @@ public readonly struct ImcCache : IDisposable
public bool RevertMod(MetaFileManager manager, ModCollection collection, ImcManipulation m)
{
if (!m.Validate())
if (!m.Validate(false))
return false;
var idx = _imcManipulations.FindIndex(p => p.Item1.Equals(m));

View file

@ -120,7 +120,7 @@ public partial class TexToolsMeta
{
var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot,
value);
if (imc.Validate())
if (imc.Validate(true))
MetaManipulations.Add(imc);
}

View file

@ -146,7 +146,7 @@ public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
public bool Apply(ImcFile file)
=> file.SetEntry(ImcFile.PartIndex(EquipSlot), Variant.Id, Entry);
public bool Validate()
public bool Validate(bool withMaterial)
{
switch (ObjectType)
{
@ -178,7 +178,7 @@ public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
break;
}
if (Entry.MaterialId == 0)
if (withMaterial && Entry.MaterialId == 0)
return false;
return true;

View file

@ -98,7 +98,7 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
return;
case ImcManipulation m:
Imc = m;
ManipulationType = m.Validate() ? Type.Imc : Type.Unknown;
ManipulationType = m.Validate(true) ? Type.Imc : Type.Unknown;
return;
}
}
@ -108,7 +108,7 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
{
return ManipulationType switch
{
Type.Imc => Imc.Validate(),
Type.Imc => Imc.Validate(true),
Type.Eqdp => Eqdp.Validate(),
Type.Eqp => Eqp.Validate(),
Type.Est => Est.Validate(),

View file

@ -1,4 +1,7 @@
using Dalamud.Interface.Internal.Notifications;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui.Classes;
using Penumbra.Api.Enums;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -157,4 +160,44 @@ public class ImcModGroup(Mod mod) : IModGroup
public (int Redirections, int Swaps, int Manips) GetCounts()
=> (0, 0, 1);
public static ImcModGroup? Load(Mod mod, JObject json)
{
var options = json["Options"];
var ret = new ImcModGroup(mod)
{
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty,
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
Priority = json[nameof(Priority)]?.ToObject<ModPriority>() ?? ModPriority.Default,
DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<Setting>() ?? Setting.Zero,
ObjectType = json[nameof(ObjectType)]?.ToObject<ObjectType>() ?? ObjectType.Unknown,
BodySlot = json[nameof(BodySlot)]?.ToObject<BodySlot>() ?? BodySlot.Unknown,
EquipSlot = json[nameof(EquipSlot)]?.ToObject<EquipSlot>() ?? EquipSlot.Unknown,
PrimaryId = new PrimaryId(json[nameof(PrimaryId)]?.ToObject<ushort>() ?? 0),
SecondaryId = new SecondaryId(json[nameof(SecondaryId)]?.ToObject<ushort>() ?? 0),
Variant = new Variant(json[nameof(Variant)]?.ToObject<byte>() ?? 0),
CanBeDisabled = json[nameof(CanBeDisabled)]?.ToObject<bool>() ?? false,
DefaultEntry = json[nameof(DefaultEntry)]?.ToObject<ImcEntry>() ?? new ImcEntry(),
};
if (ret.Name.Length == 0)
return null;
if (options != null)
foreach (var child in options.Children())
{
var subMod = new ImcSubMod(ret, child);
ret.OptionData.Add(subMod);
}
if (!new ImcManipulation(ret.ObjectType, ret.BodySlot, ret.PrimaryId, ret.SecondaryId.Id, ret.Variant.Id, ret.EquipSlot,
ret.DefaultEntry).Validate(true))
{
Penumbra.Messager.NotificationMessage($"Could not add IMC group because the associated IMC Entry is invalid.",
NotificationType.Warning);
return null;
}
ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings);
return ret;
}
}

View file

@ -1,6 +1,7 @@
using OtterGui.Classes;
using OtterGui.Filesystem;
using OtterGui.Services;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
@ -11,13 +12,43 @@ namespace Penumbra.Mods.Manager.OptionEditor;
public sealed class ImcModGroupEditor(CommunicatorService communicator, SaveService saveService, Configuration config)
: ModOptionEditor<ImcModGroup, ImcSubMod>(communicator, saveService, config), IService
{
/// <summary> Add a new, empty imc group with the given manipulation data. </summary>
public ImcModGroup? AddModGroup(Mod mod, string newName, ImcManipulation manip, SaveType saveType = SaveType.ImmediateSync)
{
if (!ModGroupEditor.VerifyFileName(mod, null, newName, true))
return null;
var maxPriority = mod.Groups.Count == 0 ? ModPriority.Default : mod.Groups.Max(o => o.Priority) + 1;
var group = CreateGroup(mod, newName, manip, maxPriority);
mod.Groups.Add(group);
SaveService.Save(saveType, new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport));
Communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, group, null, null, -1);
return group;
}
protected override ImcModGroup CreateGroup(Mod mod, string newName, ModPriority priority, SaveType saveType = SaveType.ImmediateSync)
=> new(mod)
{
Name = newName,
Name = newName,
Priority = priority,
};
private static ImcModGroup CreateGroup(Mod mod, string newName, ImcManipulation manip, ModPriority priority,
SaveType saveType = SaveType.ImmediateSync)
=> new(mod)
{
Name = newName,
Priority = priority,
ObjectType = manip.ObjectType,
EquipSlot = manip.EquipSlot,
BodySlot = manip.BodySlot,
PrimaryId = manip.PrimaryId,
SecondaryId = manip.SecondaryId.Id,
Variant = manip.Variant,
DefaultEntry = manip.Entry,
};
protected override ImcSubMod? CloneOption(ImcModGroup group, IModOption option)
=> null;

View file

@ -246,7 +246,7 @@ public class ModGroupEditor(
{
GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType),
GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType),
GroupType.Imc => ImcEditor.AddModGroup(mod, newName, saveType),
GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, saveType),
_ => null,
};

View file

@ -434,6 +434,7 @@ public partial class ModCreator(
{
case GroupType.Multi: return MultiModGroup.Load(mod, json);
case GroupType.Single: return SingleModGroup.Load(mod, json);
case GroupType.Imc: return ImcModGroup.Load(mod, json);
}
}
catch (Exception e)

View file

@ -1,3 +1,4 @@
using Newtonsoft.Json.Linq;
using Penumbra.Mods.Groups;
namespace Penumbra.Mods.SubMods;
@ -6,6 +7,12 @@ public class ImcSubMod(ImcModGroup group) : IModOption
{
public readonly ImcModGroup Group = group;
public ImcSubMod(ImcModGroup group, JToken json)
: this(group)
{
SubMod.LoadOptionData(json, this);
}
public Mod Mod
=> Group.Mod;

View file

@ -639,11 +639,11 @@ public class ItemSwapTab : IDisposable, ITab
ImGui.TextUnformatted(text);
ImGui.TableNextColumn();
_dirty |= Combos.Gender("##Gender", InputWidth, _currentGender, out _currentGender);
_dirty |= Combos.Gender("##Gender", _currentGender, out _currentGender, InputWidth);
if (drawRace == 1)
{
ImGui.SameLine();
_dirty |= Combos.Race("##Race", InputWidth, _currentRace, out _currentRace);
_dirty |= Combos.Race("##Race", _currentRace, out _currentRace, InputWidth);
}
else if (drawRace == 2)
{

View file

@ -10,6 +10,7 @@ using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor;
using Penumbra.UI.Classes;
using Penumbra.UI.ModsTab;
namespace Penumbra.UI.AdvancedWindow;
@ -145,7 +146,7 @@ public partial class ModEditWindow
ImGuiUtil.HoverTooltip(ModelSetIdTooltip);
ImGui.TableNextColumn();
if (Combos.EqpEquipSlot("##eqpSlot", 100, _new.Slot, out var slot))
if (Combos.EqpEquipSlot("##eqpSlot", _new.Slot, out var slot))
_new = new EqpManipulation(ExpandedEqpFile.GetDefault(metaFileManager, setId), slot, _new.SetId);
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
@ -351,90 +352,31 @@ public partial class ModEditWindow
// Identifier
ImGui.TableNextColumn();
if (Combos.ImcType("##imcType", _new.ObjectType, out var type))
{
var equipSlot = type switch
{
ObjectType.Equipment => _new.EquipSlot.IsEquipment() ? _new.EquipSlot : EquipSlot.Head,
ObjectType.DemiHuman => _new.EquipSlot.IsEquipment() ? _new.EquipSlot : EquipSlot.Head,
ObjectType.Accessory => _new.EquipSlot.IsAccessory() ? _new.EquipSlot : EquipSlot.Ears,
_ => EquipSlot.Unknown,
};
_new = new ImcManipulation(type, _new.BodySlot, _new.PrimaryId, _new.SecondaryId == 0 ? (ushort)1 : _new.SecondaryId,
_new.Variant.Id, equipSlot, _new.Entry);
}
ImGuiUtil.HoverTooltip(ObjectTypeTooltip);
var change = MetaManipulationDrawer.DrawObjectType(ref _new);
ImGui.TableNextColumn();
if (IdInput("##imcId", IdWidth, _new.PrimaryId.Id, out var setId, 0, ushort.MaxValue, _new.PrimaryId <= 1))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, setId, _new.SecondaryId, _new.Variant.Id, _new.EquipSlot, _new.Entry)
.Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry());
ImGuiUtil.HoverTooltip(PrimaryIdTooltip);
change |= MetaManipulationDrawer.DrawPrimaryId(ref _new);
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
ImGui.TableNextColumn();
// Equipment and accessories are slightly different imcs than other types.
if (_new.ObjectType is ObjectType.Equipment)
{
if (Combos.EqpEquipSlot("##imcSlot", 100, _new.EquipSlot, out var slot))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot,
_new.Entry)
.Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry());
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
}
else if (_new.ObjectType is ObjectType.Accessory)
{
if (Combos.AccessorySlot("##imcSlot", _new.EquipSlot, out var slot))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot,
_new.Entry)
.Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry());
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
}
if (_new.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
change |= MetaManipulationDrawer.DrawSlot(ref _new);
else
{
if (IdInput("##imcId2", 100 * UiHelpers.Scale, _new.SecondaryId.Id, out var setId2, 0, ushort.MaxValue, false))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, setId2, _new.Variant.Id, _new.EquipSlot,
_new.Entry)
.Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry());
ImGuiUtil.HoverTooltip(SecondaryIdTooltip);
}
change |= MetaManipulationDrawer.DrawSecondaryId(ref _new);
ImGui.TableNextColumn();
if (IdInput("##imcVariant", SmallIdWidth, _new.Variant.Id, out var variant, 0, byte.MaxValue, false))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, variant, _new.EquipSlot,
_new.Entry).Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry());
ImGuiUtil.HoverTooltip(VariantIdTooltip);
change |= MetaManipulationDrawer.DrawVariant(ref _new);
ImGui.TableNextColumn();
if (_new.ObjectType is ObjectType.DemiHuman)
{
if (Combos.EqpEquipSlot("##imcSlot", 70, _new.EquipSlot, out var slot))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot,
_new.Entry)
.Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry());
ImGuiUtil.HoverTooltip(EquipSlotTooltip);
}
change |= MetaManipulationDrawer.DrawSlot(ref _new, 70);
else
{
ImGui.Dummy(new Vector2(70 * UiHelpers.Scale, 0));
}
if (change)
_new = _new.Copy(GetDefault(metaFileManager, _new) ?? new ImcEntry());
// Values
using var disabled = ImRaii.Disabled();
ImGui.TableNextColumn();

View file

@ -8,41 +8,35 @@ namespace Penumbra.UI.Classes;
public static class Combos
{
// Different combos to use with enums.
public static bool Race(string label, ModelRace current, out ModelRace race)
=> Race(label, 100, current, out race);
public static bool Race(string label, float unscaledWidth, ModelRace current, out ModelRace race)
public static bool Race(string label, ModelRace current, out ModelRace race, float unscaledWidth = 100)
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out race, RaceEnumExtensions.ToName, 1);
public static bool Gender(string label, Gender current, out Gender gender)
=> Gender(label, 120, current, out gender);
public static bool Gender(string label, Gender current, out Gender gender, float unscaledWidth = 120)
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth, current, out gender, RaceEnumExtensions.ToName, 1);
public static bool Gender(string label, float unscaledWidth, Gender current, out Gender gender)
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out gender, RaceEnumExtensions.ToName, 1);
public static bool EqdpEquipSlot(string label, EquipSlot current, out EquipSlot slot)
=> ImGuiUtil.GenericEnumCombo(label, 100 * UiHelpers.Scale, current, out slot, EquipSlotExtensions.EqdpSlots,
public static bool EqdpEquipSlot(string label, EquipSlot current, out EquipSlot slot, float unscaledWidth = 100)
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out slot, EquipSlotExtensions.EqdpSlots,
EquipSlotExtensions.ToName);
public static bool EqpEquipSlot(string label, float width, EquipSlot current, out EquipSlot slot)
=> ImGuiUtil.GenericEnumCombo(label, width * UiHelpers.Scale, current, out slot, EquipSlotExtensions.EquipmentSlots,
public static bool EqpEquipSlot(string label, EquipSlot current, out EquipSlot slot, float unscaledWidth = 100)
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out slot, EquipSlotExtensions.EquipmentSlots,
EquipSlotExtensions.ToName);
public static bool AccessorySlot(string label, EquipSlot current, out EquipSlot slot)
=> ImGuiUtil.GenericEnumCombo(label, 100 * UiHelpers.Scale, current, out slot, EquipSlotExtensions.AccessorySlots,
public static bool AccessorySlot(string label, EquipSlot current, out EquipSlot slot, float unscaledWidth = 100)
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out slot, EquipSlotExtensions.AccessorySlots,
EquipSlotExtensions.ToName);
public static bool SubRace(string label, SubRace current, out SubRace subRace)
=> ImGuiUtil.GenericEnumCombo(label, 150 * UiHelpers.Scale, current, out subRace, RaceEnumExtensions.ToName, 1);
public static bool SubRace(string label, SubRace current, out SubRace subRace, float unscaledWidth = 150)
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out subRace, RaceEnumExtensions.ToName, 1);
public static bool RspAttribute(string label, RspAttribute current, out RspAttribute attribute)
=> ImGuiUtil.GenericEnumCombo(label, 200 * UiHelpers.Scale, current, out attribute,
public static bool RspAttribute(string label, RspAttribute current, out RspAttribute attribute, float unscaledWidth = 200)
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out attribute,
RspAttributeExtensions.ToFullString, 0, 1);
public static bool EstSlot(string label, EstManipulation.EstType current, out EstManipulation.EstType attribute)
=> ImGuiUtil.GenericEnumCombo(label, 200 * UiHelpers.Scale, current, out attribute);
public static bool EstSlot(string label, EstManipulation.EstType current, out EstManipulation.EstType attribute, float unscaledWidth = 200)
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out attribute);
public static bool ImcType(string label, ObjectType current, out ObjectType type)
=> ImGuiUtil.GenericEnumCombo(label, 110 * UiHelpers.Scale, current, out type, ObjectTypeExtensions.ValidImcTypes,
public static bool ImcType(string label, ObjectType current, out ObjectType type, float unscaledWidth = 110)
=> ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out type, ObjectTypeExtensions.ValidImcTypes,
ObjectTypeExtensions.ToName);
}

View file

@ -1,12 +1,19 @@
using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Utility;
using ImGuiNET;
using Lumina.Data.Files;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Text;
using OtterGui.Text.EndObjects;
using Penumbra.Api.Enums;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Manager;
@ -15,9 +22,236 @@ using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
using Penumbra.Services;
using Penumbra.UI.Classes;
using ImcFile = Penumbra.Meta.Files.ImcFile;
namespace Penumbra.UI.ModsTab;
public static class MetaManipulationDrawer
{
public static bool DrawObjectType(ref ImcManipulation manip, float width = 110)
{
var ret = Combos.ImcType("##imcType", manip.ObjectType, out var type, width);
ImUtf8.HoverTooltip("Object Type"u8);
if (ret)
{
var equipSlot = type switch
{
ObjectType.Equipment => manip.EquipSlot.IsEquipment() ? manip.EquipSlot : EquipSlot.Head,
ObjectType.DemiHuman => manip.EquipSlot.IsEquipment() ? manip.EquipSlot : EquipSlot.Head,
ObjectType.Accessory => manip.EquipSlot.IsAccessory() ? manip.EquipSlot : EquipSlot.Ears,
_ => EquipSlot.Unknown,
};
manip = new ImcManipulation(type, manip.BodySlot, manip.PrimaryId, manip.SecondaryId == 0 ? 1 : manip.SecondaryId,
manip.Variant.Id, equipSlot, manip.Entry);
}
return ret;
}
public static bool DrawPrimaryId(ref ImcManipulation manip, float unscaledWidth = 80)
{
var ret = IdInput("##imcPrimaryId"u8, unscaledWidth, manip.PrimaryId.Id, out var newId, 0, ushort.MaxValue,
manip.PrimaryId.Id <= 1);
ImUtf8.HoverTooltip("Primary ID - You can usually find this as the 'x####' part of an item path.\n"u8
+ "This should generally not be left <= 1 unless you explicitly want that."u8);
if (ret)
manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, newId, manip.SecondaryId, manip.Variant.Id, manip.EquipSlot,
manip.Entry);
return ret;
}
public static bool DrawSecondaryId(ref ImcManipulation manip, float unscaledWidth = 100)
{
var ret = IdInput("##imcSecondaryId"u8, unscaledWidth, manip.SecondaryId.Id, out var newId, 0, ushort.MaxValue, false);
ImUtf8.HoverTooltip("Secondary ID"u8);
if (ret)
manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, newId, manip.Variant.Id, manip.EquipSlot,
manip.Entry);
return ret;
}
public static bool DrawVariant(ref ImcManipulation manip, float unscaledWidth = 45)
{
var ret = IdInput("##imcVariant"u8, unscaledWidth, manip.Variant.Id, out var newId, 0, byte.MaxValue, false);
ImUtf8.HoverTooltip("Variant ID"u8);
if (ret)
manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, (byte)newId, manip.EquipSlot,
manip.Entry);
return ret;
}
public static bool DrawSlot(ref ImcManipulation manip, float unscaledWidth = 100)
{
bool ret;
EquipSlot slot;
switch (manip.ObjectType)
{
case ObjectType.Equipment:
case ObjectType.DemiHuman:
ret = Combos.EqpEquipSlot("##slot", manip.EquipSlot, out slot, unscaledWidth);
break;
case ObjectType.Accessory:
ret = Combos.AccessorySlot("##slot", manip.EquipSlot, out slot, unscaledWidth);
break;
default: return false;
}
ImUtf8.HoverTooltip("Equip Slot"u8);
if (ret)
manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, manip.Variant.Id, slot,
manip.Entry);
return ret;
}
// A number input for ids with a optional max id of given width.
// Returns true if newId changed against currentId.
private static bool IdInput(ReadOnlySpan<byte> label, float unscaledWidth, ushort currentId, out ushort newId, int minId, int maxId,
bool border)
{
int tmp = currentId;
ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale);
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, UiHelpers.Scale, border);
using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.RegexWarningBorder, border);
if (ImUtf8.InputScalar(label, ref tmp))
tmp = Math.Clamp(tmp, minId, maxId);
newId = (ushort)tmp;
return newId != currentId;
}
}
public class AddGroupDrawer : IUiService
{
private string _groupName = string.Empty;
private bool _groupNameValid = false;
private ImcManipulation _imcManip = new(EquipSlot.Head, 1, 1, new ImcEntry());
private ImcEntry _defaultEntry;
private bool _imcFileExists;
private bool _entryExists;
private bool _entryInvalid;
private readonly MetaFileManager _metaManager;
private readonly ModManager _modManager;
public AddGroupDrawer(MetaFileManager metaManager, ModManager modManager)
{
_metaManager = metaManager;
_modManager = modManager;
UpdateEntry();
}
public void Draw(Mod mod, float width)
{
DrawBasicGroups(mod, width);
DrawImcData(mod, width);
}
private void UpdateEntry()
{
try
{
_defaultEntry = ImcFile.GetDefault(_metaManager, _imcManip.GamePath(), _imcManip.EquipSlot, _imcManip.Variant,
out _entryExists);
_imcFileExists = true;
}
catch (Exception)
{
_defaultEntry = new ImcEntry();
_imcFileExists = false;
_entryExists = false;
}
_imcManip = _imcManip.Copy(_entryExists ? _defaultEntry : new ImcEntry());
_entryInvalid = !_imcManip.Validate(true);
}
private void DrawBasicGroups(Mod mod, float width)
{
ImGui.SetNextItemWidth(width);
if (ImUtf8.InputText("##name"u8, ref _groupName, "Enter New Name..."u8))
_groupNameValid = ModGroupEditor.VerifyFileName(mod, null, _groupName, false);
var buttonWidth = new Vector2((width - ImUtf8.ItemInnerSpacing.X) / 2, 0);
if (ImUtf8.ButtonEx("Add Single Group"u8, _groupNameValid
? "Add a new single selection option group to this mod."u8
: "Can not add a new group of this name."u8,
buttonWidth, !_groupNameValid))
{
_modManager.OptionEditor.AddModGroup(mod, GroupType.Single, _groupName);
_groupName = string.Empty;
_groupNameValid = false;
}
ImUtf8.SameLineInner();
if (ImUtf8.ButtonEx("Add Multi Group"u8, _groupNameValid
? "Add a new multi selection option group to this mod."u8
: "Can not add a new group of this name."u8,
buttonWidth, !_groupNameValid))
{
_modManager.OptionEditor.AddModGroup(mod, GroupType.Multi, _groupName);
_groupName = string.Empty;
_groupNameValid = false;
}
}
private void DrawImcData(Mod mod, float width)
{
var halfWidth = (width - ImUtf8.ItemInnerSpacing.X) / 2 / ImUtf8.GlobalScale;
var change = MetaManipulationDrawer.DrawObjectType(ref _imcManip, halfWidth);
ImUtf8.SameLineInner();
change |= MetaManipulationDrawer.DrawPrimaryId(ref _imcManip, halfWidth);
if (_imcManip.ObjectType is ObjectType.Weapon or ObjectType.Monster)
{
change |= MetaManipulationDrawer.DrawSecondaryId(ref _imcManip, halfWidth);
ImUtf8.SameLineInner();
change |= MetaManipulationDrawer.DrawVariant(ref _imcManip, halfWidth);
}
else if (_imcManip.ObjectType is ObjectType.DemiHuman)
{
var quarterWidth = (halfWidth - ImUtf8.ItemInnerSpacing.X / ImUtf8.GlobalScale) / 2;
change |= MetaManipulationDrawer.DrawSecondaryId(ref _imcManip, halfWidth);
ImUtf8.SameLineInner();
change |= MetaManipulationDrawer.DrawSlot(ref _imcManip, quarterWidth);
ImUtf8.SameLineInner();
change |= MetaManipulationDrawer.DrawVariant(ref _imcManip, quarterWidth);
}
else
{
change |= MetaManipulationDrawer.DrawSlot(ref _imcManip, halfWidth);
ImUtf8.SameLineInner();
change |= MetaManipulationDrawer.DrawVariant(ref _imcManip, halfWidth);
}
if (change)
UpdateEntry();
var buttonWidth = new Vector2(halfWidth * ImUtf8.GlobalScale, 0);
if (ImUtf8.ButtonEx("Add IMC Group"u8, !_groupNameValid
? "Can not add a new group of this name."u8
: _entryInvalid ?
"The associated IMC entry is invalid."u8
: "Add a new multi selection option group to this mod."u8,
buttonWidth, !_groupNameValid || _entryInvalid))
{
_modManager.OptionEditor.ImcEditor.AddModGroup(mod, _groupName, _imcManip);
_groupName = string.Empty;
_groupNameValid = false;
}
if (_entryInvalid)
{
ImUtf8.SameLineInner();
var text = _imcFileExists
? "IMC Entry Does Not Exist"
: "IMC File Does Not Exist";
ImGuiUtil.DrawTextButton(text, buttonWidth, Colors.PressEnterWarningBg);
}
}
}
public sealed class ModGroupEditDrawer(
ModManager modManager,
Configuration config,
@ -267,9 +501,7 @@ public sealed class ModGroupEditDrawer(
}
private void DrawImcGroup(ImcModGroup group)
{
// TODO
}
{ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DrawOptionPosition(IModGroup group, IModOption option, int optionIdx)

View file

@ -28,7 +28,8 @@ public class ModPanelEditTab(
Configuration config,
PredefinedTagManager predefinedTagManager,
ModGroupEditDrawer groupEditDrawer,
DescriptionEditPopup descriptionPopup)
DescriptionEditPopup descriptionPopup,
AddGroupDrawer addGroupDrawer)
: ITab
{
private readonly TagButtons _modTags = new();
@ -75,7 +76,7 @@ public class ModPanelEditTab(
selector.Selected!);
UiHelpers.DefaultLineSpace();
AddOptionGroup.Draw(filenames, modManager, _mod, config.ReplaceNonAsciiOnImport);
addGroupDrawer.Draw(_mod, UiHelpers.InputTextWidth.X);
UiHelpers.DefaultLineSpace();
groupEditDrawer.Draw(_mod);
@ -84,7 +85,6 @@ public class ModPanelEditTab(
public void Reset()
{
AddOptionGroup.Reset();
MoveDirectory.Reset();
Input.Reset();
}
@ -202,42 +202,6 @@ public class ModPanelEditTab(
Process.Start(new ProcessStartInfo(filenames.ModMetaPath(_mod)) { UseShellExecute = true });
}
/// <summary> Text input to add a new option group at the end of the current groups. </summary>
private static class AddOptionGroup
{
private static string _newGroupName = string.Empty;
public static void Reset()
=> _newGroupName = string.Empty;
public static void Draw(FilenameService filenames, ModManager modManager, Mod mod, bool onlyAscii)
{
using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(UiHelpers.ScaleX3));
ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton3);
ImGui.InputTextWithHint("##newGroup", "Add new option group...", ref _newGroupName, 256);
ImGui.SameLine();
var defaultFile = filenames.OptionGroupFile(mod, -1, onlyAscii);
var fileExists = File.Exists(defaultFile);
var tt = fileExists
? "Open the default option json file in the text editor of your choice."
: "The default option json file does not exist.";
if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.FileExport.ToIconString()}##defaultFile", UiHelpers.IconButtonSize, tt,
!fileExists, true))
Process.Start(new ProcessStartInfo(defaultFile) { UseShellExecute = true });
ImGui.SameLine();
var nameValid = ModGroupEditor.VerifyFileName(mod, null, _newGroupName, false);
tt = nameValid ? "Add new option group to the mod." : "Can not add a group of this name.";
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), UiHelpers.IconButtonSize,
tt, !nameValid, true))
return;
modManager.OptionEditor.SingleEditor.AddModGroup(mod, _newGroupName);
Reset();
}
}
/// <summary> A text input for the new directory name and a button to apply the move. </summary>
private static class MoveDirectory
{