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

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
{