Implement dynamic condition UI

This allows the user to add conditions of whatever combination of
job and state criteria they choose.
This commit is contained in:
Zacharie Day 2020-09-02 19:36:53 -04:00 committed by Anna
parent 3f35b5e0d3
commit a3d65ec43b
3 changed files with 140 additions and 133 deletions

View File

@ -50,6 +50,8 @@ namespace HudSwap {
public Dictionary<Guid, Tuple<string, byte[]>> Layouts { get; } = new Dictionary<Guid, Tuple<string, byte[]>>();
public Dictionary<Guid, Layout> Layouts2 { get; } = new Dictionary<Guid, Layout>();
public List<HudConditionMatch> HudConditionMatches = new List<HudConditionMatch>();
public void Initialize(DalamudPluginInterface pluginInterface) {
this.pi = pluginInterface;
this.Migrate();

View File

@ -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<Guid, Layout> 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<HudConditionMatch>(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<ClassJob>().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<ClassJob>()
.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();

View File

@ -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<Status, bool> condition = new Dictionary<Status, bool>();
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<ClassJob>().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 {
/// <summary>
/// Values stored here should be the abbreviation of the class/job name (all caps).
/// We do this because using <see cref="ClassJob"/> results in circular dependency errors when serializing.
/// </summary>
public string ClassJob { get; set; }
public Status? Status { get; set; }
public Guid LayoutId { get; set; }
}
public enum Status {
InCombat = ConditionFlag.InCombat,
WeaponDrawn = ConditionFlag.None,