diff --git a/BossMod/BossModule/BossModuleConfigWindow.cs b/BossMod/BossModule/BossModuleConfigWindow.cs new file mode 100644 index 0000000000..04bc75bf77 --- /dev/null +++ b/BossMod/BossModule/BossModuleConfigWindow.cs @@ -0,0 +1,34 @@ +using Dalamud.Interface.Utility.Raii; + +namespace BossMod; + +public class BossModuleConfigWindow : UIWindow +{ + private ModuleRegistry.Info _info; + private WorldState _ws; + private UITree _tree = new(); + + public BossModuleConfigWindow(ModuleRegistry.Info info, WorldState ws) : base($"{info.ModuleType.Name} config", true, new(1200, 800)) + { + _info = info; + _ws = ws; + } + + public override void Draw() + { + if (_info.ConfigType == null) + return; // nothing to do... + + using var tabs = ImRaii.TabBar("Tabs"); + if (tabs) + { + using (var tab = ImRaii.TabItem("Encounter-specific config")) + if (tab) + ConfigUI.DrawNode(Service.Config.Get(_info.ConfigType), Service.Config, _tree, _ws); + if (_ws.Party.Player() != null) + using (var tab = ImRaii.TabItem("Party roles assignment")) + if (tab) + ConfigUI.DrawNode(Service.Config.Get(), Service.Config, _tree, _ws); + } + } +} diff --git a/BossMod/BossModule/BossModuleMainWindow.cs b/BossMod/BossModule/BossModuleMainWindow.cs index 2fe90422d2..f6cd46cc0b 100644 --- a/BossMod/BossModule/BossModuleMainWindow.cs +++ b/BossMod/BossModule/BossModuleMainWindow.cs @@ -1,4 +1,5 @@ -using ImGuiNET; +using Dalamud.Interface; +using ImGuiNET; namespace BossMod; @@ -12,6 +13,7 @@ public class BossModuleMainWindow : UIWindow { _mgr = mgr; RespectCloseHotkey = false; + TitleBarButtons.Add(new() { Icon = FontAwesomeIcon.Cog, IconOffset = new(1), Click = _ => OpenModuleConfig() }); } public override void PreOpenCheck() @@ -94,4 +96,10 @@ private void DrawMovementHints(BossComponent.MovementHints? arrows, float y) Camera.Instance.DrawWorldLine(arrowStart - offset, end3, color); } } + + private void OpenModuleConfig() + { + if (_mgr.ActiveModule?.Info?.ConfigType != null) + new BossModuleConfigWindow(_mgr.ActiveModule.Info, _mgr.WorldState); + } } diff --git a/BossMod/Config/ConfigUI.cs b/BossMod/Config/ConfigUI.cs index e38f7f7793..ac2bb2fdc6 100644 --- a/BossMod/Config/ConfigUI.cs +++ b/BossMod/Config/ConfigUI.cs @@ -73,6 +73,29 @@ public void Draw() } } + public static void DrawNode(ConfigNode node, ConfigRoot root, UITree tree, WorldState ws) + { + // draw standard properties + foreach (var field in node.GetType().GetFields()) + { + var props = field.GetCustomAttribute(); + if (props == null) + continue; + + _ = field.GetValue(node) switch + { + bool v => DrawProperty(props, node, field, v), + Enum v => DrawProperty(props, node, field, v), + float v => DrawProperty(props, node, field, v), + GroupAssignment v => DrawProperty(props, node, field, v, root, tree, ws), + _ => false + }; + } + + // draw custom stuff + node.DrawCustom(tree, ws); + } + private static string GenerateNodeName(Type t) => t.Name.EndsWith("Config") ? t.Name.Remove(t.Name.Length - "Config".Length) : t.Name; private void SortByOrder(List nodes) @@ -86,32 +109,12 @@ private void DrawNodes(List nodes) { foreach (var n in _tree.Nodes(nodes, n => new(n.Name))) { - // draw standard properties - foreach (var field in n.Node.GetType().GetFields()) - { - var props = field.GetCustomAttribute(); - if (props == null) - continue; - - _ = field.GetValue(n.Node) switch - { - bool v => DrawProperty(props, n.Node, field, v), - Enum v => DrawProperty(props, n.Node, field, v), - float v => DrawProperty(props, n.Node, field, v), - GroupAssignment v => DrawProperty(props, n.Node, field, v), - _ => false - }; - } - - // draw custom stuff - n.Node.DrawCustom(_tree, _ws); - - // draw subnodes + DrawNode(n.Node, _root, _tree, _ws); DrawNodes(n.Children); } } - private bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, FieldInfo member, bool v) + private static bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, FieldInfo member, bool v) { var combo = member.GetCustomAttribute(); if (combo != null) @@ -133,7 +136,7 @@ private bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, Field return true; } - private bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, FieldInfo member, Enum v) + private static bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, FieldInfo member, Enum v) { if (UICombo.Enum(props.Label, ref v)) { @@ -143,7 +146,7 @@ private bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, Field return true; } - private bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, FieldInfo member, float v) + private static bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, FieldInfo member, float v) { var slider = member.GetCustomAttribute(); if (slider != null) @@ -168,15 +171,15 @@ private bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, Field return true; } - private bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, FieldInfo member, GroupAssignment v) + private static bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, FieldInfo member, GroupAssignment v, ConfigRoot root, UITree tree, WorldState ws) { var group = member.GetCustomAttribute(); if (group == null) return false; - foreach (var tn in _tree.Node(props.Label, false, v.Validate() ? 0xffffffff : 0xff00ffff, () => DrawPropertyContextMenu(node, member, v))) + foreach (var tn in tree.Node(props.Label, false, v.Validate() ? 0xffffffff : 0xff00ffff, () => DrawPropertyContextMenu(node, member, v))) { - var assignments = _root.Get().SlotsPerAssignment(_ws.Party); + var assignments = root.Get().SlotsPerAssignment(ws.Party); if (ImGui.BeginTable("table", group.Names.Length + 2, ImGuiTableFlags.SizingFixedFit)) { foreach (var n in group.Names) @@ -206,7 +209,7 @@ private bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, Field string name = r.ToString(); if (assignments.Length > 0) - name += $" ({_ws.Party[assignments[i]]?.Name})"; + name += $" ({ws.Party[assignments[i]]?.Name})"; ImGui.TableNextColumn(); ImGui.TextUnformatted(name); } @@ -216,7 +219,7 @@ private bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, Field return true; } - private void DrawPropertyContextMenu(ConfigNode node, FieldInfo member, GroupAssignment v) + private static void DrawPropertyContextMenu(ConfigNode node, FieldInfo member, GroupAssignment v) { foreach (var preset in member.GetCustomAttributes()) { diff --git a/BossMod/Config/ModuleViewer.cs b/BossMod/Config/ModuleViewer.cs index 2ead81c04c..be5a27e4e4 100644 --- a/BossMod/Config/ModuleViewer.cs +++ b/BossMod/Config/ModuleViewer.cs @@ -1,4 +1,5 @@ -using Dalamud.Interface.Internal; +using Dalamud.Interface; +using Dalamud.Interface.Internal; using Dalamud.Interface.Utility.Raii; using ImGuiNET; using Lumina.Excel.GeneratedSheets; @@ -10,7 +11,7 @@ namespace BossMod; public class ModuleViewer : IDisposable { - private record struct ModuleInfo(Type Type, string Name, int SortOrder); + private record struct ModuleInfo(ModuleRegistry.Info Info, string Name, int SortOrder); private record struct ModuleGroupInfo(string Name, uint Id, uint SortOrder, IDalamudTextureWrap? Icon = null); private record struct ModuleGroup(ModuleGroupInfo Info, List Modules); @@ -98,7 +99,7 @@ public ModuleViewer() g.Modules.SortBy(m => m.SortOrder); foreach (var (m1, m2) in g.Modules.Pairwise()) if (m1.SortOrder == m2.SortOrder) - Service.Log($"[ModuleViewer] Same sort order between modules {m1.Type.FullName} and {m2.Type.FullName}"); + Service.Log($"[ModuleViewer] Same sort order between modules {m1.Info.ModuleType.FullName} and {m2.Info.ModuleType.FullName}"); } } } @@ -204,8 +205,16 @@ private void DrawModules(UITree _tree) ImGui.TableNextColumn(); foreach (var _ in _tree.Node($"{group.Info.Name}###{i}/{j}/{group.Info.Id}")) + { foreach (var mod in group.Modules) - ImGui.TextUnformatted($"{mod.Name} [{mod.Type.Name}]"); + { + using (ImRaii.Disabled(mod.Info.ConfigType == null)) + if (UIMisc.IconButton(FontAwesomeIcon.Cog, "cfg", $"###{mod.Info.ModuleType.FullName}")) + new BossModuleConfigWindow(mod.Info, new(TimeSpan.TicksPerSecond, "fake")); + ImGui.SameLine(); + ImGui.TextUnformatted($"{mod.Name} [{mod.Info.ModuleType.Name}]"); + } + } } } } @@ -238,38 +247,38 @@ private void Customize((string name, IDalamudTextureWrap? icon)[] array, int ele var cfcRow = Service.LuminaRow(module.GroupID); var cfcSort = cfcRow?.SortKey ?? 0u; var cfcName = FixCase(cfcRow?.Name); - return (new(cfcName, groupId, cfcSort != 0 ? cfcSort : groupId), new(module.ModuleType, BNpcName(module.NameID), module.SortOrder)); + return (new(cfcName, groupId, cfcSort != 0 ? cfcSort : groupId), new(module, BNpcName(module.NameID), module.SortOrder)); case BossModuleInfo.GroupType.MaskedCarnivale: groupId |= module.GroupID; var mcRow = Service.LuminaRow(module.GroupID); var mcSort = uint.Parse((mcRow?.ShortCode ?? "").Substring(3)); // 'aozNNN' var mcName = $"Stage {mcSort}: {FixCase(mcRow?.Name)}"; - return (new(mcName, groupId, mcSort), new(module.ModuleType, BNpcName(module.NameID), module.SortOrder)); + return (new(mcName, groupId, mcSort), new(module, BNpcName(module.NameID), module.SortOrder)); case BossModuleInfo.GroupType.RemovedUnreal: - return (new("Removed Content", groupId, groupId), new(module.ModuleType, BNpcName(module.NameID), module.SortOrder)); + return (new("Removed Content", groupId, groupId), new(module, BNpcName(module.NameID), module.SortOrder)); case BossModuleInfo.GroupType.Quest: var questRow = Service.LuminaRow(module.GroupID); groupId |= questRow?.JournalGenre.Row ?? 0; var questCategoryName = questRow?.JournalGenre.Value?.Name ?? ""; - return (new(questCategoryName, groupId, groupId), new(module.ModuleType, $"{questRow?.Name}: {BNpcName(module.NameID)}", module.SortOrder)); + return (new(questCategoryName, groupId, groupId), new(module, $"{questRow?.Name}: {BNpcName(module.NameID)}", module.SortOrder)); case BossModuleInfo.GroupType.Fate: var fateRow = Service.LuminaRow(module.GroupID); - return (new($"{module.Expansion.ShortName()} FATE", groupId, groupId, _iconFATE), new(module.ModuleType, $"{fateRow?.Name}: {BNpcName(module.NameID)}", module.SortOrder)); + return (new($"{module.Expansion.ShortName()} FATE", groupId, groupId, _iconFATE), new(module, $"{fateRow?.Name}: {BNpcName(module.NameID)}", module.SortOrder)); case BossModuleInfo.GroupType.Hunt: groupId |= module.GroupID; - return (new($"{module.Expansion.ShortName()} Hunt {(BossModuleInfo.HuntRank)module.GroupID}", groupId, groupId, _iconHunt), new(module.ModuleType, BNpcName(module.NameID), module.SortOrder)); + return (new($"{module.Expansion.ShortName()} Hunt {(BossModuleInfo.HuntRank)module.GroupID}", groupId, groupId, _iconHunt), new(module, BNpcName(module.NameID), module.SortOrder)); case BossModuleInfo.GroupType.BozjaCE: groupId |= module.GroupID; var ceName = $"{FixCase(Service.LuminaRow(module.GroupID)?.Name)} CE"; - return (new(ceName, groupId, groupId), new(module.ModuleType, Service.LuminaRow(module.NameID)?.Name ?? "", module.SortOrder)); + return (new(ceName, groupId, groupId), new(module, Service.LuminaRow(module.NameID)?.Name ?? "", module.SortOrder)); case BossModuleInfo.GroupType.BozjaDuel: groupId |= module.GroupID; var duelName = $"{FixCase(Service.LuminaRow(module.GroupID)?.Name)} Duel"; - return (new(duelName, groupId, groupId), new(module.ModuleType, Service.LuminaRow(module.NameID)?.Name ?? "", module.SortOrder)); + return (new(duelName, groupId, groupId), new(module, Service.LuminaRow(module.NameID)?.Name ?? "", module.SortOrder)); case BossModuleInfo.GroupType.GoldSaucer: - return (new("Gold saucer", groupId, groupId), new(module.ModuleType, $"{Service.LuminaRow(module.GroupID)?.Text}: {BNpcName(module.NameID)}", module.SortOrder)); + return (new("Gold saucer", groupId, groupId), new(module, $"{Service.LuminaRow(module.GroupID)?.Text}: {BNpcName(module.NameID)}", module.SortOrder)); default: - return (new("Ungrouped", groupId, groupId), new(module.ModuleType, BNpcName(module.NameID), module.SortOrder)); + return (new("Ungrouped", groupId, groupId), new(module, BNpcName(module.NameID), module.SortOrder)); } } } diff --git a/BossMod/Util/UIMisc.cs b/BossMod/Util/UIMisc.cs index 16582d7b40..2a45505c5a 100644 --- a/BossMod/Util/UIMisc.cs +++ b/BossMod/Util/UIMisc.cs @@ -1,4 +1,6 @@ -using Dalamud.Interface.Internal; +using Dalamud.Interface; +using Dalamud.Interface.Internal; +using Dalamud.Interface.Utility.Raii; using ImGuiNET; namespace BossMod; @@ -55,4 +57,13 @@ public static bool ImageToggleButton(IDalamudTextureWrap? icon, Vector2 size, bo return ImGui.Button("", size); } } + + // works around issues with fonts in uidev + public static unsafe bool IconButton(FontAwesomeIcon icon, string fallback, string text) + { + if (Service.PluginInterface == null) + return ImGui.Button(fallback + text); + using var scope = ImRaii.PushFont(UiBuilder.IconFont); + return ImGui.Button(icon.ToIconString() + text); + } } diff --git a/TODO b/TODO index 8a52a9a756..3b8f3dc762 100644 --- a/TODO +++ b/TODO @@ -24,7 +24,7 @@ general: -- button to 'keep' last (add as temp to replay manager) or to 'save' last (save into replay file) - better timing tracking for: statuses, gauges, cooldowns, cast times, anim lock, ... - constrain bossmodules to zone id (e.g. for T04) -- module registry improvements +- revise module categories - consider merging fates/hunts/quests/gold saucer?/pvp? into outdoor?/casual? - refactor pendingeffects boss modules: