diff --git a/HudSwap/PluginConfig.cs b/HudSwap/PluginConfig.cs index a404960..0558a11 100644 --- a/HudSwap/PluginConfig.cs +++ b/HudSwap/PluginConfig.cs @@ -50,6 +50,8 @@ namespace HudSwap { public Dictionary> Layouts { get; } = new Dictionary>(); public Dictionary Layouts2 { get; } = new Dictionary(); + public List HudConditionMatches = new List(); + public void Initialize(DalamudPluginInterface pluginInterface) { this.pi = pluginInterface; this.Migrate(); diff --git a/HudSwap/PluginUI.cs b/HudSwap/PluginUI.cs index 0987c46..e419d54 100644 --- a/HudSwap/PluginUI.cs +++ b/HudSwap/PluginUI.cs @@ -42,6 +42,9 @@ namespace HudSwap { private string jobFilter = ""; + private int editingConditionIndex = -1; + private HudConditionMatch editingCondition; + private static bool configErrorOpen = true; public static void ConfigError() { if (ImGui.Begin("HudSwap error", ref configErrorOpen)) { @@ -120,6 +123,7 @@ namespace HudSwap { if (ImGui.Button("Delete") && this.selectedLayout != null) { this.plugin.Config.Layouts2.Remove(this.selectedLayout); + this.plugin.Config.HudConditionMatches.RemoveAll(m => m.LayoutId == this.selectedLayout); this.selectedLayout = Guid.Empty; this.renameName = ""; this.plugin.Config.Save(); @@ -211,104 +215,129 @@ namespace HudSwap { ImGui.Separator(); - ImGui.Text("This is the default layout. If none of the below conditions are\nsatisfied, this layout will be enabled."); + ImGui.Text("Add conditions below for when to swap.\nThe topmost condition in the list has priority."); - if (ImGui.BeginCombo("##default-layout", this.LayoutNameOrDefault(this.plugin.Config.DefaultLayout))) { - foreach (KeyValuePair entry in this.plugin.Config.Layouts2) { - if (ImGui.Selectable(entry.Value.Name)) { - this.plugin.Config.DefaultLayout = entry.Key; - this.plugin.Config.Save(); + ImGui.Columns(4); + + var conditions = new List(plugin.Config.HudConditionMatches); + if (this.editingConditionIndex == conditions.Count) + conditions.Add(new HudConditionMatch()); + + ImGui.Text("Job"); + ImGui.NextColumn(); + + ImGui.Text("State"); + ImGui.NextColumn(); + + ImGui.Text("Layout"); + ImGui.NextColumn(); + + ImGui.Text("Options"); + ImGui.NextColumn(); + + ImGui.Separator(); + + bool addCondition = false; + int actionedItemIndex = -1; + int action = 0; // 0 for delete, otherwise move. + foreach (var item in conditions.Select((cond, i) => new { cond, i })) { + if (this.editingConditionIndex == item.i) { + if (ImGui.BeginCombo("##condition-edit-job", this.editingCondition.ClassJob ?? "Any")) { + if (ImGui.Selectable("Any##condition-edit-job")) + this.editingCondition.ClassJob = null; + foreach (ClassJob job in this.pi.Data.GetExcelSheet().ToList()) + if (ImGui.Selectable($"{job.Abbreviation}##condition-edit-job")) + this.editingCondition.ClassJob = job.Abbreviation; + ImGui.EndCombo(); + } + ImGui.NextColumn(); + + if (ImGui.BeginCombo("##condition-edit-status", this.editingCondition.Status?.Name() ?? "Any")) { + if (ImGui.Selectable("Any##condition-edit-status")) + this.editingCondition.Status = null; + foreach (Status status in Enum.GetValues(typeof(Status))) + if (ImGui.Selectable($"{status.Name()}##condition-edit-status")) + this.editingCondition.Status = status; + ImGui.EndCombo(); + } + ImGui.NextColumn(); + + if (ImGui.BeginCombo("##condition-edit-layout", this.editingCondition.LayoutId == Guid.Empty ? string.Empty : this.plugin.Config.Layouts2[this.editingCondition.LayoutId].Name)) { + if (ImGui.Selectable("##condition-edit-layout-empty")) + this.editingCondition.LayoutId = Guid.Empty; + foreach (var layout in this.plugin.Config.Layouts2) + if (ImGui.Selectable($"{layout.Value.Name}##condition-edit-layout")) + this.editingCondition.LayoutId = layout.Key; + ImGui.EndCombo(); + } + ImGui.NextColumn(); + + if (this.editingCondition.LayoutId != Guid.Empty) + if (ImGui.Button("Confirm##condition-edit")) + addCondition = true; + } else { + ImGui.Text(item.cond.ClassJob ?? string.Empty); + ImGui.NextColumn(); + + ImGui.Text(item.cond.Status?.Name() ?? string.Empty); + ImGui.NextColumn(); + + ImGui.Text(this.plugin.Config.Layouts2[item.cond.LayoutId].Name); + ImGui.NextColumn(); + + if (ImGui.Button($"E##{item.i}")) { + this.editingConditionIndex = item.i; + this.editingCondition = item.cond; + } + + ImGui.SameLine(); + if (ImGui.Button($"D##{item.i}")) + actionedItemIndex = item.i; + ImGui.SameLine(); + if (ImGui.Button($"↑##{item.i}")) { + actionedItemIndex = item.i; + action = -1; + } + + ImGui.SameLine(); + if (ImGui.Button($"↓##{item.i}")) { + actionedItemIndex = item.i; + action = 1; } } - ImGui.EndCombo(); + + ImGui.NextColumn(); } - ImGui.Spacing(); - ImGui.Text("These settings are ordered from highest priority to lowest priority.\nHigher priorities overwrite lower priorities when enabled."); - ImGui.Spacing(); + ImGui.Columns(); - if (ImGui.CollapsingHeader("Status layouts", ImGuiTreeNodeFlags.DefaultOpen)) { - if (ImGui.BeginChild("##layout-selections", new Vector2(0, 125))) { - ImGui.Columns(2); - - float maxSize = 0f; - - foreach (Status status in Statuses.ORDER.Reverse()) { - maxSize = Math.Max(maxSize, ImGui.CalcTextSize(status.Name()).X); - - this.plugin.Config.StatusLayouts.TryGetValue(status, out Guid layout); - - if (this.LayoutBox(status.Name(), layout, out Guid newLayout)) { - this.plugin.Config.StatusLayouts[status] = newLayout; - this.plugin.Config.Save(); - if (this.plugin.Config.SwapsEnabled) { - this.plugin.Statuses.SetHudLayout(player, true); - } - } - } - - ImGui.SetColumnWidth(0, maxSize + ImGui.GetStyle().ItemSpacing.X * 2); - - ImGui.Columns(1); - ImGui.EndChild(); - } + if (ImGui.Button("Add##condition")) { + this.editingConditionIndex = this.plugin.Config.HudConditionMatches.Count; + this.editingCondition = new HudConditionMatch(); } - if (ImGui.CollapsingHeader("Job layouts")) { - if (ImGui.InputText("Filter", ref this.jobFilter, 50, ImGuiInputTextFlags.AutoSelectAll)) { - this.jobFilter = this.jobFilter.ToLower(); + if (addCondition) { + if (this.editingConditionIndex == this.plugin.Config.HudConditionMatches.Count) + this.plugin.Config.HudConditionMatches.Add(this.editingCondition); + else { + this.plugin.Config.HudConditionMatches.RemoveAt(this.editingConditionIndex); + this.plugin.Config.HudConditionMatches.Insert(this.editingConditionIndex, this.editingCondition); } + this.plugin.Config.Save(); + this.editingConditionIndex = -1; + } - if (ImGui.BeginChild("##job-layout-selections", new Vector2(0, 125))) { - ImGui.Columns(2); - - float maxSize = 0f; - - var acceptableJobs = this.pi.Data.GetExcelSheet() - .Where(job => job.NameEnglish != "Adventurer") - .Where(job => this.jobFilter.Length == 0 || (job.NameEnglish.ToLower().Contains(this.jobFilter) || job.Abbreviation.ToLower().Contains(this.jobFilter))); - - foreach (ClassJob job in acceptableJobs) { - maxSize = Math.Max(maxSize, ImGui.CalcTextSize(job.NameEnglish).X); - - this.plugin.Config.JobLayouts.TryGetValue(job.Abbreviation, out Guid layout); - - if (this.LayoutBox(job.NameEnglish, layout, out Guid newLayout)) { - this.plugin.Config.JobLayouts[job.Abbreviation] = newLayout; - this.plugin.Config.Save(); - if (this.plugin.Config.SwapsEnabled) { - this.plugin.Statuses.SetHudLayout(player, true); - } - } - } - - ImGui.SetColumnWidth(0, maxSize + ImGui.GetStyle().ItemSpacing.X * 2); - - ImGui.Columns(1); - ImGui.EndChild(); - } - - bool combatOnlyJobs = this.plugin.Config.JobsCombatOnly; - if (ImGui.Checkbox("Jobs only in combat/weapon drawn", ref combatOnlyJobs)) { - this.plugin.Config.JobsCombatOnly = combatOnlyJobs; - this.plugin.Config.Save(); - if (this.plugin.Config.SwapsEnabled) { - this.plugin.Statuses.SetHudLayout(player, true); + if (actionedItemIndex >= 0) { + if (action == 0) + this.plugin.Config.HudConditionMatches.RemoveAt(actionedItemIndex); + else { + if (actionedItemIndex + action >= 0 && actionedItemIndex + action < this.plugin.Config.HudConditionMatches.Count) { + // Move the condition. + var c = this.plugin.Config.HudConditionMatches[actionedItemIndex]; + this.plugin.Config.HudConditionMatches.RemoveAt(actionedItemIndex); + this.plugin.Config.HudConditionMatches.Insert(actionedItemIndex + action, c); } } - ImGui.SameLine(); - HelpMarker("Selecting this will make the HUD layout change for a job only when in combat or when your weapon is drawn."); - - bool highPriorityJobs = this.plugin.Config.HighPriorityJobs; - if (ImGui.Checkbox("Jobs take priority over status", ref highPriorityJobs)) { - this.plugin.Config.HighPriorityJobs = highPriorityJobs; - this.plugin.Config.Save(); - if (this.plugin.Config.SwapsEnabled) { - this.plugin.Statuses.SetHudLayout(player, true); - } - } - ImGui.SameLine(); - HelpMarker("Selecting this will make job layouts always apply when on that job. If this is unselected, job layouts will only apply if the default layout was going to be used (or only in combat if the above checkbox is selected)."); } ImGui.EndTabItem(); diff --git a/HudSwap/Statuses.cs b/HudSwap/Statuses.cs index 2ce6949..32c5f47 100644 --- a/HudSwap/Statuses.cs +++ b/HudSwap/Statuses.cs @@ -5,25 +5,16 @@ using Lumina.Excel.GeneratedSheets; using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using Newtonsoft.Json; // TODO: Zone swaps? namespace HudSwap { public class Statuses { - public static readonly Status[] ORDER = { - Status.Roleplaying, - Status.Fishing, - Status.Gathering, - Status.Crafting, - Status.InInstance, - Status.WeaponDrawn, - Status.InCombat, - }; - private readonly HudSwapPlugin plugin; private readonly DalamudPluginInterface pi; - private readonly bool[] condition = new bool[ORDER.Length]; + private readonly Dictionary condition = new Dictionary(); private ClassJob job; internal static byte GetStatus(DalamudPluginInterface pi, Actor actor) { @@ -41,8 +32,6 @@ namespace HudSwap { return false; } - bool[] old = (bool[])this.condition.Clone(); - bool anyChanged = false; ClassJob currentJob = this.pi.Data.GetExcelSheet().GetRow(player.ClassJob.Id); @@ -51,10 +40,10 @@ namespace HudSwap { } this.job = currentJob; - for (int i = 0; i < ORDER.Length; i++) { - Status status = ORDER[i]; - this.condition[i] = status.Active(player, this.pi); - anyChanged |= old[i] != this.condition[i]; + foreach (Status status in Enum.GetValues(typeof(Status))) { + var old = this.condition.ContainsKey(status) && this.condition[status]; + this.condition[status] = status.Active(player, this.pi); + anyChanged |= old != this.condition[status]; } return anyChanged; @@ -66,38 +55,13 @@ namespace HudSwap { return Guid.Empty; } - // get the job layout if there is one and check if jobs are high priority - if (this.plugin.Config.JobLayouts.TryGetValue(this.job.Abbreviation, out Guid jobLayout) && this.plugin.Config.HighPriorityJobs) { - return jobLayout; + foreach (var match in this.plugin.Config.HudConditionMatches) { + if ((!match.Status.HasValue || this.condition[match.Status.Value]) && + (match.ClassJob == null || this.job.Abbreviation == match.ClassJob)) + return match.LayoutId; } - Guid layout = Guid.Empty; - - // check all status conditions and set layout as appropriate - for (int i = 0; i < ORDER.Length; i++) { - if (!this.condition[i]) { - continue; - } - Status status = ORDER[i]; - if (this.plugin.Config.StatusLayouts.TryGetValue(status, out Guid statusLayout) && statusLayout != Guid.Empty) { - layout = statusLayout; - } - } - - // if a job layout is set for the current job - if (jobLayout != Guid.Empty) { - // if jobs are combat only and the player is either in combat or has their weapon drawn, use the job layout - if (this.plugin.Config.JobsCombatOnly && (this.condition[5] || this.condition[6])) { - layout = jobLayout; - } - - // if the layout was going to be default, use job layout unless jobs are not combat only - if (!this.plugin.Config.JobsCombatOnly && layout == Guid.Empty) { - layout = jobLayout; - } - } - - return layout == Guid.Empty ? this.plugin.Config.DefaultLayout : layout; + return this.plugin.Config.DefaultLayout; } public void SetHudLayout(PlayerCharacter player, bool update = false) { @@ -121,6 +85,18 @@ namespace HudSwap { } } + public struct HudConditionMatch { + /// + /// Values stored here should be the abbreviation of the class/job name (all caps). + /// We do this because using results in circular dependency errors when serializing. + /// + public string ClassJob { get; set; } + + public Status? Status { get; set; } + + public Guid LayoutId { get; set; } + } + public enum Status { InCombat = ConditionFlag.InCombat, WeaponDrawn = ConditionFlag.None,