refactor: pull layouts into a dictionary

This commit is contained in:
Anna 2020-07-31 12:46:40 -04:00
parent c793cfc613
commit 7226458785
4 changed files with 248 additions and 178 deletions

View File

@ -20,14 +20,23 @@ namespace HudSwap {
public Guid defaultLayout = Guid.Empty;
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
public Guid combatLayout = Guid.Empty;
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
public Guid weaponDrawnLayout = Guid.Empty;
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
public Guid instanceLayout = Guid.Empty;
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
public Guid craftingLayout = Guid.Empty;
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
public Guid gatheringLayout = Guid.Empty;
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
public Guid fishingLayout = Guid.Empty;
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
public Guid roleplayingLayout = Guid.Empty;
public Dictionary<Status, Guid> StatusLayouts { get; set; } = new Dictionary<Status, Guid>();
public Dictionary<string, Guid> JobLayouts { get; set; } = new Dictionary<string, Guid>();
public bool HighPriorityJobs { get; set; } = false;
public bool JobsCombatOnly { get; set; } = false;
@ -36,10 +45,51 @@ namespace HudSwap {
public void Initialize(DalamudPluginInterface pluginInterface) {
this.pi = pluginInterface;
this.Migrate();
this.Save();
}
public void Save() {
this.pi.SavePluginConfig(this);
}
private void Migrate() {
#pragma warning disable 618
if (this.combatLayout != Guid.Empty) {
this.StatusLayouts[Status.InCombat] = this.combatLayout;
this.combatLayout = Guid.Empty;
}
if (this.weaponDrawnLayout != Guid.Empty) {
this.StatusLayouts[Status.WeaponDrawn] = this.weaponDrawnLayout;
this.weaponDrawnLayout = Guid.Empty;
}
if (this.instanceLayout != Guid.Empty) {
this.StatusLayouts[Status.InInstance] = this.instanceLayout;
this.instanceLayout = Guid.Empty;
}
if (this.craftingLayout != Guid.Empty) {
this.StatusLayouts[Status.Crafting] = this.craftingLayout;
this.craftingLayout = Guid.Empty;
}
if (this.gatheringLayout != Guid.Empty) {
this.StatusLayouts[Status.Gathering] = this.gatheringLayout;
this.gatheringLayout = Guid.Empty;
}
if (this.fishingLayout != Guid.Empty) {
this.StatusLayouts[Status.Fishing] = this.fishingLayout;
this.fishingLayout = Guid.Empty;
}
if (this.roleplayingLayout != Guid.Empty) {
this.StatusLayouts[Status.Roleplaying] = this.roleplayingLayout;
this.roleplayingLayout = Guid.Empty;
}
#pragma warning restore 618
}
}
}

View File

@ -64,9 +64,10 @@
<Compile Include="Plugin.cs" />
<Compile Include="PluginUI.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Statuses.cs" />
</ItemGroup>
<ItemGroup>
<None Include="HudSwap.json" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>

View File

@ -1,5 +1,4 @@
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Plugin;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
@ -7,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
// TODO: Zone swaps?
@ -192,17 +190,23 @@ namespace HudSwap {
if (ImGui.BeginChild("##layout-selections", new Vector2(0, 125))) {
ImGui.Columns(2);
List<float> textSizes = new List<float>();
float maxSize = 0f;
this.LayoutBox("In combat", ref this.plugin.config.combatLayout, player, textSizes);
this.LayoutBox("Weapon drawn", ref this.plugin.config.weaponDrawnLayout, player, textSizes);
this.LayoutBox("In instance", ref this.plugin.config.instanceLayout, player, textSizes);
this.LayoutBox("Crafting", ref this.plugin.config.craftingLayout, player, textSizes);
this.LayoutBox("Gathering", ref this.plugin.config.gatheringLayout, player, textSizes);
this.LayoutBox("Fishing", ref this.plugin.config.fishingLayout, player, textSizes);
this.LayoutBox("Roleplaying", ref this.plugin.config.roleplayingLayout, player, textSizes);
foreach (Status status in Statuses.ORDER.Reverse()) {
maxSize = Math.Max(maxSize, ImGui.CalcTextSize(status.Name()).X);
ImGui.SetColumnWidth(0, textSizes.Max() + ImGui.GetStyle().ItemSpacing.X * 2);
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.statuses.SetHudLayout(player, true);
}
}
}
ImGui.SetColumnWidth(0, maxSize + ImGui.GetStyle().ItemSpacing.X * 2);
ImGui.Columns(1);
ImGui.EndChild();
@ -217,21 +221,19 @@ namespace HudSwap {
if (ImGui.BeginChild("##job-layout-selections", new Vector2(0, 125))) {
ImGui.Columns(2);
List<float> textSizes = new List<float>();
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);
Guid oldLayout = layout;
this.LayoutBox(job.NameEnglish, ref layout, player, textSizes);
if (oldLayout != layout) {
this.plugin.config.JobLayouts[job.Abbreviation] = 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.statuses.SetHudLayout(player, true);
@ -239,7 +241,7 @@ namespace HudSwap {
}
}
ImGui.SetColumnWidth(0, textSizes.DefaultIfEmpty(0).Max() + ImGui.GetStyle().ItemSpacing.X * 2);
ImGui.SetColumnWidth(0, maxSize + ImGui.GetStyle().ItemSpacing.X * 2);
ImGui.Columns(1);
ImGui.EndChild();
@ -314,31 +316,27 @@ namespace HudSwap {
}
}
private void LayoutBox(string name, ref Guid layout, PlayerCharacter player, List<float> textSizes) {
textSizes.Add(ImGui.CalcTextSize(name).X);
private bool LayoutBox(string name, Guid currentLayout, out Guid newLayout) {
newLayout = Guid.Empty;
bool updated = false;
ImGui.Text(name);
ImGui.NextColumn();
if (ImGui.BeginCombo($"##{name}-layout", this.LayoutNameOrDefault(layout))) {
if (ImGui.BeginCombo($"##{name}-layout", this.LayoutNameOrDefault(currentLayout))) {
if (ImGui.Selectable("Not set")) {
layout = Guid.Empty;
this.plugin.config.Save();
if (this.plugin.config.SwapsEnabled) {
this.statuses.SetHudLayout(player, true);
}
updated = true;
}
ImGui.Separator();
foreach (KeyValuePair<Guid, Tuple<string, byte[]>> entry in this.plugin.config.Layouts) {
if (ImGui.Selectable(entry.Value.Item1)) {
layout = entry.Key;
this.plugin.config.Save();
if (this.plugin.config.SwapsEnabled) {
this.statuses.SetHudLayout(player, true);
}
updated = true;
newLayout = entry.Key;
}
}
ImGui.EndCombo();
}
ImGui.NextColumn();
return updated;
}
public void ImportSlot(HudSlot slot, string name, bool save = true) {
@ -348,148 +346,4 @@ namespace HudSwap {
}
}
}
public class Statuses {
private readonly HudSwapPlugin plugin;
private readonly DalamudPluginInterface pi;
private readonly bool[] condition = new bool[ORDER.Length];
private ClassJob job;
// Order: lowest to highest priority
// For conditions that require custom logic, use ConditionFlag.None
private static readonly ConditionFlag[] ORDER = {
ConditionFlag.RolePlaying,
ConditionFlag.Fishing,
ConditionFlag.Gathering,
ConditionFlag.Crafting,
ConditionFlag.BoundByDuty,
ConditionFlag.None, // weapon drawn
ConditionFlag.InCombat,
};
private delegate bool CustomCondition(HudSwapPlugin plugin, DalamudPluginInterface pi, PlayerCharacter player);
// Add handlers in the order that ConditionFlag.None flags appear in ORDER.
private static readonly CustomCondition[] CUSTOM = {
// weapon drawn
(plugin, pi, player) => (GetStatus(pi, player) & 4) > 0,
};
protected static byte GetStatus(DalamudPluginInterface pi, Actor actor) {
IntPtr statusPtr = pi.TargetModuleScanner.ResolveRelativeAddress(actor.Address, 0x1901);
return Marshal.ReadByte(statusPtr);
}
public Statuses(HudSwapPlugin plugin, DalamudPluginInterface pi) {
this.plugin = plugin;
this.pi = pi;
if (ORDER.Length != this.GetLayouts().Length) {
throw new ApplicationException("Statuses.ORDER is not the same length as the array returned by Statuses.GetLayouts()");
}
if (ORDER.Where(flag => flag == ConditionFlag.None).Count() != CUSTOM.Length) {
throw new ApplicationException("Statuses.CUSTOM does not have an amount of handlers equalling the amount of ConditionFlag.None in Statuses.ORDER");
}
}
private Guid[] GetLayouts() {
// These layouts must be in the same order as the flags in ORDER are defined
Guid[] layouts = {
this.plugin.config.roleplayingLayout,
this.plugin.config.fishingLayout,
this.plugin.config.gatheringLayout,
this.plugin.config.craftingLayout,
this.plugin.config.instanceLayout,
this.plugin.config.weaponDrawnLayout,
this.plugin.config.combatLayout,
};
return layouts;
}
public bool Update(PlayerCharacter player) {
if (player == null) {
return false;
}
int customs = 0;
bool[] old = (bool[])this.condition.Clone();
Condition condition = this.pi.ClientState.Condition;
bool anyChanged = false;
ClassJob currentJob = this.pi.Data.GetExcelSheet<ClassJob>().GetRow(player.ClassJob.Id);
if (this.job != null && this.job != currentJob) {
anyChanged = true;
}
this.job = currentJob;
for (int i = 0; i < ORDER.Length; i++) {
ConditionFlag flag = ORDER[i];
if (flag == ConditionFlag.None) {
this.condition[i] = CUSTOM[customs].Invoke(this.plugin, this.pi, player);
customs += 1;
} else {
this.condition[i] = condition[flag];
}
anyChanged |= old[i] != this.condition[i];
}
return anyChanged;
}
public Guid CalculateCurrentHud() {
PlayerCharacter player = this.pi.ClientState.LocalPlayer;
if (player == null) {
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;
}
Guid layout = Guid.Empty;
Guid[] layouts = this.GetLayouts();
// check all status conditions and set layout as appropriate
for (int i = 0; i < ORDER.Length; i++) {
Guid flagLayout = layouts[i];
if (this.condition[i] && flagLayout != Guid.Empty) {
layout = flagLayout;
}
}
// 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;
}
public void SetHudLayout(PlayerCharacter player, bool update = false) {
if (update && player != null) {
this.Update(player);
}
Guid layout = this.CalculateCurrentHud();
if (layout == Guid.Empty) {
return; // FIXME: do something better
}
if (!this.plugin.config.Layouts.TryGetValue(layout, out Tuple<string, byte[]> entry)) {
return; // FIXME: do something better
}
this.plugin.hud.WriteLayout(this.plugin.config.StagingSlot, entry.Item2);
this.plugin.hud.SelectSlot(this.plugin.config.StagingSlot, true);
}
}
}

165
HudSwap/Statuses.cs Normal file
View File

@ -0,0 +1,165 @@
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Plugin;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Linq;
using System.Runtime.InteropServices;
// TODO: Zone swaps?
namespace HudSwap {
public class Statuses {
public static 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 ClassJob job;
internal static byte GetStatus(DalamudPluginInterface pi, Actor actor) {
IntPtr statusPtr = pi.TargetModuleScanner.ResolveRelativeAddress(actor.Address, 0x1901);
return Marshal.ReadByte(statusPtr);
}
public Statuses(HudSwapPlugin plugin, DalamudPluginInterface pi) {
this.plugin = plugin;
this.pi = pi;
}
public bool Update(PlayerCharacter player) {
if (player == null) {
return false;
}
bool[] old = (bool[])this.condition.Clone();
bool anyChanged = false;
ClassJob currentJob = this.pi.Data.GetExcelSheet<ClassJob>().GetRow(player.ClassJob.Id);
if (this.job != null && this.job != currentJob) {
anyChanged = true;
}
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];
}
return anyChanged;
}
public Guid CalculateCurrentHud() {
PlayerCharacter player = this.pi.ClientState.LocalPlayer;
if (player == null) {
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;
}
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)) {
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;
}
public void SetHudLayout(PlayerCharacter player, bool update = false) {
if (update && player != null) {
this.Update(player);
}
Guid layout = this.CalculateCurrentHud();
if (layout == Guid.Empty) {
return; // FIXME: do something better
}
if (!this.plugin.config.Layouts.TryGetValue(layout, out Tuple<string, byte[]> entry)) {
return; // FIXME: do something better
}
this.plugin.hud.WriteLayout(this.plugin.config.StagingSlot, entry.Item2);
this.plugin.hud.SelectSlot(this.plugin.config.StagingSlot, true);
}
}
public enum Status {
InCombat = ConditionFlag.InCombat,
WeaponDrawn = ConditionFlag.None,
InInstance = ConditionFlag.BoundByDuty,
Crafting = ConditionFlag.Crafting,
Gathering = ConditionFlag.Gathering,
Fishing = ConditionFlag.Fishing,
Roleplaying = ConditionFlag.RolePlaying,
}
public static class StatusExtensions {
public static string Name(this Status status) {
switch (status) {
case Status.InCombat:
return "In combat";
case Status.WeaponDrawn:
return "Weapon drawn";
case Status.InInstance:
return "In instance";
case Status.Crafting:
return "Crafting";
case Status.Gathering:
return "Gathering";
case Status.Fishing:
return "Fishing";
case Status.Roleplaying:
return "Roleplaying";
}
throw new ApplicationException($"No name was set up for {status}");
}
public static bool Active(this Status status, PlayerCharacter player, DalamudPluginInterface pi) {
ConditionFlag flag = (ConditionFlag)status;
if (flag != ConditionFlag.None) {
return pi.ClientState.Condition[flag];
}
switch (status) {
case Status.WeaponDrawn:
return (Statuses.GetStatus(pi, player) & 4) > 0;
}
return false;
}
}
}