Initial release: HSUI v1.0.0.0 - HUD replacement with configurable hotbars
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
|||||||
|
# Build results
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
[Bb]uild/
|
||||||
|
[Dd]ebug/
|
||||||
|
[Rr]elease/
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
.vs/
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.cache
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# User-specific
|
||||||
|
*.rsuser
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# NuGet
|
||||||
|
packages/
|
||||||
|
*.nupkg
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
@@ -0,0 +1,682 @@
|
|||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Attributes
|
||||||
|
{
|
||||||
|
#region class attributes
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class ExportableAttribute : Attribute
|
||||||
|
{
|
||||||
|
public bool exportable;
|
||||||
|
|
||||||
|
public ExportableAttribute(bool exportable)
|
||||||
|
{
|
||||||
|
this.exportable = exportable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ShareableAttribute : Attribute
|
||||||
|
{
|
||||||
|
public bool shareable;
|
||||||
|
|
||||||
|
public ShareableAttribute(bool shareable)
|
||||||
|
{
|
||||||
|
this.shareable = shareable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResettableAttribute : Attribute
|
||||||
|
{
|
||||||
|
public bool resettable;
|
||||||
|
|
||||||
|
public ResettableAttribute(bool resettable)
|
||||||
|
{
|
||||||
|
this.resettable = resettable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class DisableableAttribute : Attribute
|
||||||
|
{
|
||||||
|
public bool disableable;
|
||||||
|
|
||||||
|
public DisableableAttribute(bool disableable)
|
||||||
|
{
|
||||||
|
this.disableable = disableable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class DisableParentSettingsAttribute : Attribute
|
||||||
|
{
|
||||||
|
public readonly string[] DisabledFields;
|
||||||
|
|
||||||
|
public DisableParentSettingsAttribute(params string[] fields)
|
||||||
|
{
|
||||||
|
this.DisabledFields = fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method attributes
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class ManualDrawAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region field attributes
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public abstract class ConfigAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string friendlyName;
|
||||||
|
public bool isMonitored = false;
|
||||||
|
public bool separator = false;
|
||||||
|
public bool spacing = false;
|
||||||
|
public string? help = null;
|
||||||
|
|
||||||
|
public ConfigAttribute(string friendlyName)
|
||||||
|
{
|
||||||
|
this.friendlyName = friendlyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Draw(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader = false)
|
||||||
|
{
|
||||||
|
bool result = DrawField(field, config, ID, collapsingHeader);
|
||||||
|
|
||||||
|
if (help != null && ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip(help);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader = false);
|
||||||
|
|
||||||
|
protected string IDText(string? ID) => ID != null ? " ##" + ID : "";
|
||||||
|
|
||||||
|
protected void TriggerChangeEvent<T>(PluginConfigObject config, string fieldName, object value, ChangeType type = ChangeType.None)
|
||||||
|
{
|
||||||
|
if (!isMonitored || config is not IOnChangeEventArgs eventObject)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventObject.OnValueChanged(new OnChangeEventArgs<T>(fieldName, (T)value, type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class CheckboxAttribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
public CheckboxAttribute(string friendlyName) : base(friendlyName) { }
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
var disableable = config.Disableable;
|
||||||
|
|
||||||
|
if (!disableable && friendlyName == "Enabled")
|
||||||
|
{
|
||||||
|
if (ID != null)
|
||||||
|
{
|
||||||
|
ImGui.Text(ID);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool? fieldVal = (bool?)field.GetValue(config);
|
||||||
|
bool boolVal = fieldVal.HasValue ? fieldVal.Value : false;
|
||||||
|
|
||||||
|
if (ImGui.Checkbox(ID != null && friendlyName == "Enabled" && !collapsingHeader ? ID : friendlyName + IDText(ID), ref boolVal))
|
||||||
|
{
|
||||||
|
field.SetValue(config, boolVal);
|
||||||
|
|
||||||
|
TriggerChangeEvent<bool>(config, field.Name, boolVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
|
||||||
|
public class RadioSelector : ConfigAttribute
|
||||||
|
{
|
||||||
|
private string[] _options;
|
||||||
|
|
||||||
|
public string? Label { get; set; }
|
||||||
|
|
||||||
|
public RadioSelector(params string[] options) : base(string.Join("_", options))
|
||||||
|
{
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadioSelector(Type enumType) : this(enumType.IsEnum ? Enum.GetNames(enumType) : Array.Empty<string>()) { }
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
object? fieldVal = field.GetValue(config);
|
||||||
|
|
||||||
|
int intVal = 0;
|
||||||
|
if (fieldVal != null)
|
||||||
|
{
|
||||||
|
intVal = (int)fieldVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? label = Label ?? friendlyName;
|
||||||
|
if (!string.IsNullOrEmpty(label) && label != string.Join("_", _options))
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(label);
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _options.Length; i++)
|
||||||
|
{
|
||||||
|
changed |= ImGui.RadioButton(_options[i], ref intVal, i);
|
||||||
|
if (i < _options.Length - 1)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
field.SetValue(config, intVal);
|
||||||
|
TriggerChangeEvent<int>(config, field.Name, intVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class DragFloatAttribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
public float min;
|
||||||
|
public float max;
|
||||||
|
public float velocity;
|
||||||
|
|
||||||
|
public DragFloatAttribute(string friendlyName) : base(friendlyName)
|
||||||
|
{
|
||||||
|
min = 1f;
|
||||||
|
max = 1000f;
|
||||||
|
velocity = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
float? fieldVal = (float?)field.GetValue(config);
|
||||||
|
float floatVal = fieldVal.HasValue ? fieldVal.Value : 0;
|
||||||
|
|
||||||
|
if (ImGui.DragFloat(friendlyName + IDText(ID), ref floatVal, velocity, min, max))
|
||||||
|
{
|
||||||
|
field.SetValue(config, floatVal);
|
||||||
|
|
||||||
|
TriggerChangeEvent<float>(config, field.Name, floatVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class DragIntAttribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
public int min;
|
||||||
|
public int max;
|
||||||
|
public float velocity;
|
||||||
|
|
||||||
|
public DragIntAttribute(string friendlyName) : base(friendlyName)
|
||||||
|
{
|
||||||
|
min = 1;
|
||||||
|
max = 1000;
|
||||||
|
velocity = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
int? fieldVal = (int?)field.GetValue(config);
|
||||||
|
int intVal = fieldVal.HasValue ? fieldVal.Value : 0;
|
||||||
|
|
||||||
|
if (ImGui.DragInt(friendlyName + IDText(ID), ref intVal, velocity, min, max))
|
||||||
|
{
|
||||||
|
field.SetValue(config, intVal);
|
||||||
|
|
||||||
|
TriggerChangeEvent<int>(config, field.Name, intVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class DragFloat2Attribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
public float min;
|
||||||
|
public float max;
|
||||||
|
public float velocity;
|
||||||
|
|
||||||
|
public DragFloat2Attribute(string friendlyName) : base(friendlyName)
|
||||||
|
{
|
||||||
|
min = 1f;
|
||||||
|
max = 1000f;
|
||||||
|
velocity = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
Vector2? fieldVal = (Vector2?)field.GetValue(config);
|
||||||
|
Vector2 vectorVal = fieldVal.HasValue ? fieldVal.Value : Vector2.Zero;
|
||||||
|
|
||||||
|
if (ImGui.DragFloat2(friendlyName + IDText(ID), ref vectorVal, velocity, min, max))
|
||||||
|
{
|
||||||
|
field.SetValue(config, vectorVal);
|
||||||
|
|
||||||
|
TriggerChangeEvent<Vector2>(config, field.Name, vectorVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class DragInt2Attribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
public int min;
|
||||||
|
public int max;
|
||||||
|
public int velocity;
|
||||||
|
|
||||||
|
public DragInt2Attribute(string friendlyName) : base(friendlyName)
|
||||||
|
{
|
||||||
|
min = 1;
|
||||||
|
max = 1000;
|
||||||
|
velocity = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
Vector2? fieldVal = (Vector2?)field.GetValue(config);
|
||||||
|
Vector2 vectorVal = fieldVal.HasValue ? fieldVal.Value : Vector2.Zero;
|
||||||
|
|
||||||
|
if (ImGui.DragFloat2(friendlyName + IDText(ID), ref vectorVal, velocity, min, max))
|
||||||
|
{
|
||||||
|
field.SetValue(config, vectorVal);
|
||||||
|
|
||||||
|
TriggerChangeEvent<Vector2>(config, field.Name, vectorVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class InputTextAttribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
public int maxLength;
|
||||||
|
public bool formattable = true;
|
||||||
|
|
||||||
|
private string _searchText = "";
|
||||||
|
|
||||||
|
public InputTextAttribute(string friendlyName) : base(friendlyName)
|
||||||
|
{
|
||||||
|
this.friendlyName = friendlyName;
|
||||||
|
maxLength = 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
string? fieldVal = (string?)field.GetValue(config);
|
||||||
|
string stringVal = fieldVal ?? "";
|
||||||
|
string? finalValue = null;
|
||||||
|
|
||||||
|
string popupId = ID != null ? "DelvUI_TextTagsList " + ID : "DelvUI_TextTagsList ##" + friendlyName;
|
||||||
|
|
||||||
|
if (!formattable)
|
||||||
|
{
|
||||||
|
if (ImGui.InputText(friendlyName + IDText(ID), ref stringVal))
|
||||||
|
{
|
||||||
|
finalValue = stringVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float scale = ImGuiHelpers.GlobalScale;
|
||||||
|
float width = ImGui.CalcItemWidth();
|
||||||
|
float height = Math.Max(24 * scale, ImGui.CalcTextSize(stringVal, false, width).Y + 6 * scale);
|
||||||
|
Vector2 size = new Vector2(width, height);
|
||||||
|
|
||||||
|
if (ImGui.InputTextMultiline(friendlyName + IDText(ID), ref stringVal, maxLength, size, ImGuiInputTextFlags.AllowTabInput))
|
||||||
|
{
|
||||||
|
finalValue = stringVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// text tags
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Pen.ToIconString() + "##" + ID))
|
||||||
|
{
|
||||||
|
ImGui.OpenPopup(popupId);
|
||||||
|
}
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
ImGuiHelper.SetTooltip("Text Tags");
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedTag = ImGuiHelper.DrawTextTagsList(popupId, ref _searchText);
|
||||||
|
if (selectedTag != null)
|
||||||
|
{
|
||||||
|
finalValue = stringVal + selectedTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalValue != null)
|
||||||
|
{
|
||||||
|
field.SetValue(config, finalValue);
|
||||||
|
TriggerChangeEvent<string>(config, field.Name, finalValue);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ColorEdit4Attribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
public ColorEdit4Attribute(string friendlyName) : base(friendlyName) { }
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
PluginConfigColor? colorVal = (PluginConfigColor?)field.GetValue(config);
|
||||||
|
Vector4 vector = (colorVal != null ? colorVal.Vector : Vector4.Zero);
|
||||||
|
|
||||||
|
if (ImGui.ColorEdit4(friendlyName + IDText(ID), ref vector, ImGuiColorEditFlags.AlphaPreview | ImGuiColorEditFlags.AlphaBar))
|
||||||
|
{
|
||||||
|
if (colorVal is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
colorVal.Vector = vector;
|
||||||
|
field.SetValue(config, colorVal);
|
||||||
|
|
||||||
|
TriggerChangeEvent<PluginConfigColor>(config, field.Name, colorVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ComboAttribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
public string[] options;
|
||||||
|
|
||||||
|
public ComboAttribute(string friendlyName, params string[] options) : base(friendlyName)
|
||||||
|
{
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
object? fieldVal = field.GetValue(config);
|
||||||
|
|
||||||
|
int intVal = 0;
|
||||||
|
if (fieldVal != null)
|
||||||
|
{
|
||||||
|
intVal = (int)fieldVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Combo(friendlyName + IDText(ID), ref intVal, options, 4))
|
||||||
|
{
|
||||||
|
field.SetValue(config, intVal);
|
||||||
|
|
||||||
|
TriggerChangeEvent<int>(config, field.Name, intVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class DragDropHorizontalAttribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
public string[] names;
|
||||||
|
|
||||||
|
public DragDropHorizontalAttribute(string friendlyName, params string[] names) : base(friendlyName)
|
||||||
|
{
|
||||||
|
this.names = names;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
ImGui.Text(friendlyName);
|
||||||
|
int[]? fieldVal = (int[]?)field.GetValue(config);
|
||||||
|
int[] order = fieldVal ?? Array.Empty<int>();
|
||||||
|
|
||||||
|
for (int i = 0; i < order.Length; i++)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Button(names[order[i]], new Vector2(100, 25));
|
||||||
|
|
||||||
|
if (ImGui.IsItemActive())
|
||||||
|
{
|
||||||
|
float drag_dx = ImGui.GetMouseDragDelta(ImGuiMouseButton.Left).X;
|
||||||
|
|
||||||
|
if ((drag_dx > 80.0f && i < order.Length - 1))
|
||||||
|
{
|
||||||
|
var _curri = order[i];
|
||||||
|
order[i] = order[i + 1];
|
||||||
|
order[i + 1] = _curri;
|
||||||
|
field.SetValue(config, order);
|
||||||
|
ImGui.ResetMouseDragDelta();
|
||||||
|
}
|
||||||
|
else if ((drag_dx < -80.0f && i > 0))
|
||||||
|
{
|
||||||
|
var _curri = order[i];
|
||||||
|
order[i] = order[i - 1];
|
||||||
|
order[i - 1] = _curri;
|
||||||
|
field.SetValue(config, order);
|
||||||
|
ImGui.ResetMouseDragDelta();
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggerChangeEvent<int[]>(config, field.Name, order);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class FontAttribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
public FontAttribute(string friendlyName = "Font and Size") : base(friendlyName) { }
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
var fontsConfig = ConfigurationManager.Instance.GetConfigObject<FontsConfig>();
|
||||||
|
if (fontsConfig == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? stringVal = (string?)field.GetValue(config);
|
||||||
|
|
||||||
|
int index = stringVal == null || stringVal.Length == 0 || !fontsConfig.Fonts.ContainsKey(stringVal) ? -1 :
|
||||||
|
fontsConfig.Fonts.IndexOfKey(stringVal);
|
||||||
|
|
||||||
|
if (index == -1)
|
||||||
|
{
|
||||||
|
if (fontsConfig.Fonts.ContainsKey(FontsConfig.DefaultBigFontKey))
|
||||||
|
{
|
||||||
|
index = fontsConfig.Fonts.IndexOfKey(FontsConfig.DefaultBigFontKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = fontsConfig.Fonts.Values.Select(fontData => fontData.Name + " " + fontData.Size.ToString()).ToArray();
|
||||||
|
|
||||||
|
if (ImGui.Combo(friendlyName + IDText(ID), ref index, options, 4))
|
||||||
|
{
|
||||||
|
stringVal = fontsConfig.Fonts.Keys[index];
|
||||||
|
field.SetValue(config, stringVal);
|
||||||
|
|
||||||
|
TriggerChangeEvent<string>(config, field.Name, stringVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class BarTextureAttribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
public BarTextureAttribute(string friendlyName = "Bar Texture") : base(friendlyName) { }
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
if (BarTexturesManager.Instance == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> textures = BarTexturesManager.Instance.BarTextureNames.ToList();
|
||||||
|
string? stringVal = (string?)field.GetValue(config);
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
if (stringVal != null && stringVal.Length > 0 && textures.Contains(stringVal))
|
||||||
|
{
|
||||||
|
index = textures.IndexOf(stringVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] options = textures.ToArray();
|
||||||
|
|
||||||
|
if (ImGui.Combo(friendlyName + IDText(ID), ref index, options, 10))
|
||||||
|
{
|
||||||
|
stringVal = options[index];
|
||||||
|
field.SetValue(config, stringVal);
|
||||||
|
|
||||||
|
TriggerChangeEvent<string>(config, field.Name, stringVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class AnchorAttribute : ComboAttribute
|
||||||
|
{
|
||||||
|
public AnchorAttribute(string friendlyName)
|
||||||
|
: base(friendlyName, new string[] { "Center", "Left", "Right", "Top", "TopLeft", "TopRight", "Bottom", "BottomLeft", "BottomRight" })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class BarTextureDrawModeAttribute : ComboAttribute
|
||||||
|
{
|
||||||
|
public BarTextureDrawModeAttribute(string friendlyName)
|
||||||
|
: base(friendlyName, new string[] { "Stretch", "Repeat Horizontal", "Repeat Vertical", "Repeat" })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class StrataLevelAttribute : ConfigAttribute
|
||||||
|
{
|
||||||
|
private string[] options = { "Lowest", "Low", "Mid-Low", "Mid", "Mid-High", "High", "Highest" };
|
||||||
|
|
||||||
|
public StrataLevelAttribute(string friendlyName) : base(friendlyName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DrawField(FieldInfo field, PluginConfigObject config, string? ID, bool collapsingHeader)
|
||||||
|
{
|
||||||
|
object? fieldVal = field.GetValue(config);
|
||||||
|
|
||||||
|
int intVal = 0;
|
||||||
|
if (fieldVal != null)
|
||||||
|
{
|
||||||
|
intVal = (int)fieldVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Combo(friendlyName + IDText(ID), ref intVal, options, 4))
|
||||||
|
{
|
||||||
|
field.SetValue(config, (StrataLevel?)intVal);
|
||||||
|
|
||||||
|
TriggerChangeEvent<int>(config, field.Name, intVal);
|
||||||
|
ConfigurationManager.Instance?.OnStrataLevelChanged(config);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region field ordering attributes
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class OrderAttribute : Attribute
|
||||||
|
{
|
||||||
|
public int pos;
|
||||||
|
public string? collapseWith = "Enabled";
|
||||||
|
|
||||||
|
public OrderAttribute(int pos)
|
||||||
|
{
|
||||||
|
this.pos = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class NestedConfigAttribute : OrderAttribute
|
||||||
|
{
|
||||||
|
public string friendlyName;
|
||||||
|
public bool separator = false;
|
||||||
|
public bool spacing = true;
|
||||||
|
public bool nest = true;
|
||||||
|
public bool collapsingHeader = true;
|
||||||
|
|
||||||
|
public NestedConfigAttribute(string friendlyName, int pos) : base(pos)
|
||||||
|
{
|
||||||
|
this.friendlyName = friendlyName;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Attributes
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class SectionAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string SectionName;
|
||||||
|
public bool ForceAllowExport;
|
||||||
|
|
||||||
|
public SectionAttribute(string name, bool forceAllowExport = false)
|
||||||
|
{
|
||||||
|
SectionName = name;
|
||||||
|
ForceAllowExport = forceAllowExport;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||||
|
public class SubSectionAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string SubSectionName;
|
||||||
|
public int Depth;
|
||||||
|
|
||||||
|
public SubSectionAttribute(string subSectionName, int depth)
|
||||||
|
{
|
||||||
|
SubSectionName = subSectionName;
|
||||||
|
Depth = depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,703 @@
|
|||||||
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using HSUI.Config.Profiles;
|
||||||
|
using HSUI.Config.Tree;
|
||||||
|
using HSUI.Config.Windows;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface;
|
||||||
|
using HSUI.Interface.EnemyList;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using HSUI.Interface.Jobs;
|
||||||
|
using HSUI.Interface.Party;
|
||||||
|
using HSUI.Interface.PartyCooldowns;
|
||||||
|
using HSUI.Interface.StatusEffects;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace HSUI.Config
|
||||||
|
{
|
||||||
|
public delegate void ConfigurationManagerEventHandler(ConfigurationManager configurationManager);
|
||||||
|
public delegate void StrataLevelsEventHandler(ConfigurationManager configurationManager, PluginConfigObject config);
|
||||||
|
public delegate void GlobalVisibilityEventHandler(ConfigurationManager configurationManager, VisibilityConfig config);
|
||||||
|
|
||||||
|
public class ConfigurationManager : IDisposable
|
||||||
|
{
|
||||||
|
public static ConfigurationManager Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
private BaseNode _configBaseNode;
|
||||||
|
private Dictionary<string, BaseNode> _configBaseNodeByProfile;
|
||||||
|
public BaseNode ConfigBaseNode
|
||||||
|
{
|
||||||
|
get => _configBaseNode;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_configBaseNode = value;
|
||||||
|
_mainConfigWindow.node = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WindowSystem _windowSystem;
|
||||||
|
private MainConfigWindow _mainConfigWindow;
|
||||||
|
private ChangelogWindow _changelogWindow;
|
||||||
|
private GridWindow _gridWindow;
|
||||||
|
|
||||||
|
public bool IsConfigWindowOpened => _mainConfigWindow.IsOpen;
|
||||||
|
public bool IsChangelogWindowOpened => _changelogWindow.IsOpen;
|
||||||
|
public bool ShowingModalWindow = false;
|
||||||
|
|
||||||
|
public GradientDirection GradientDirection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var config = Instance.GetConfigObject<MiscColorConfig>();
|
||||||
|
return config != null ? config.GradientDirection : GradientDirection.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OverrideDalamudStyle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
HUDOptionsConfig config = Instance.GetConfigObject<HUDOptionsConfig>();
|
||||||
|
return config != null ? config.OverrideDalamudStyle : true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CultureInfo ActiveCultreInfo
|
||||||
|
{
|
||||||
|
get {
|
||||||
|
HUDOptionsConfig config = Instance.GetConfigObject<HUDOptionsConfig>();
|
||||||
|
return config == null || config.UseRegionalNumberFormats ? CultureInfo.CurrentCulture : CultureInfo.InvariantCulture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ConfigDirectory;
|
||||||
|
|
||||||
|
public string CurrentVersion => Plugin.Version;
|
||||||
|
public string? PreviousVersion { get; private set; } = null;
|
||||||
|
|
||||||
|
private bool _needsProfileUpdate = false;
|
||||||
|
private bool _lockHUD = true;
|
||||||
|
|
||||||
|
public bool LockHUD
|
||||||
|
{
|
||||||
|
get => _lockHUD;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_lockHUD == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lockHUD = value;
|
||||||
|
_mainConfigWindow.IsOpen = value;
|
||||||
|
_gridWindow.IsOpen = !value;
|
||||||
|
|
||||||
|
LockEvent?.Invoke(this);
|
||||||
|
|
||||||
|
if (_lockHUD)
|
||||||
|
{
|
||||||
|
ConfigBaseNode.NeedsSave = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowHUD = true;
|
||||||
|
|
||||||
|
public event ConfigurationManagerEventHandler? ResetEvent;
|
||||||
|
public event ConfigurationManagerEventHandler? LockEvent;
|
||||||
|
public event ConfigurationManagerEventHandler? ConfigClosedEvent;
|
||||||
|
public event StrataLevelsEventHandler? StrataLevelsChangedEvent;
|
||||||
|
public event GlobalVisibilityEventHandler? GlobalVisibilityEvent;
|
||||||
|
|
||||||
|
public ConfigurationManager()
|
||||||
|
{
|
||||||
|
ConfigDirectory = Plugin.PluginInterface.GetPluginConfigDirectory();
|
||||||
|
|
||||||
|
_configBaseNodeByProfile = new Dictionary<string, BaseNode>();
|
||||||
|
_configBaseNode = new BaseNode();
|
||||||
|
InitializeBaseNode(_configBaseNode);
|
||||||
|
_configBaseNode.ConfigObjectResetEvent += OnConfigObjectReset;
|
||||||
|
|
||||||
|
_mainConfigWindow = new MainConfigWindow("HSUI Settings");
|
||||||
|
_mainConfigWindow.node = _configBaseNode;
|
||||||
|
_mainConfigWindow.CloseAction = () =>
|
||||||
|
{
|
||||||
|
ConfigClosedEvent?.Invoke(this);
|
||||||
|
|
||||||
|
if (ConfigBaseNode.NeedsSave)
|
||||||
|
{
|
||||||
|
SaveConfigurations();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_needsProfileUpdate)
|
||||||
|
{
|
||||||
|
UpdateCurrentProfile();
|
||||||
|
_needsProfileUpdate = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
string changelog = LoadChangelog();
|
||||||
|
_changelogWindow = new ChangelogWindow("HSUI Changelog v" + Plugin.Version, changelog);
|
||||||
|
_gridWindow = new GridWindow("Grid ##HSUI");
|
||||||
|
|
||||||
|
_windowSystem = new WindowSystem("HSUI_Windows");
|
||||||
|
_windowSystem.AddWindow(_mainConfigWindow);
|
||||||
|
_windowSystem.AddWindow(_changelogWindow);
|
||||||
|
_windowSystem.AddWindow(_gridWindow);
|
||||||
|
|
||||||
|
CheckVersion();
|
||||||
|
|
||||||
|
Plugin.ClientState.Logout += OnLogout;
|
||||||
|
Plugin.JobChangedEvent += OnJobChanged;
|
||||||
|
|
||||||
|
_configBaseNode.CreateNodesIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
~ConfigurationManager()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigBaseNode.ConfigObjectResetEvent -= OnConfigObjectReset;
|
||||||
|
Plugin.ClientState.Logout -= OnLogout;
|
||||||
|
Plugin.JobChangedEvent -= OnJobChanged;
|
||||||
|
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize() { Instance = new ConfigurationManager(); }
|
||||||
|
|
||||||
|
private void OnConfigObjectReset(BaseNode sender)
|
||||||
|
{
|
||||||
|
ResetEvent?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLogout(int type, int code)
|
||||||
|
{
|
||||||
|
SaveConfigurations();
|
||||||
|
ProfilesManager.Instance?.SaveCurrentProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnJobChanged(uint jobId)
|
||||||
|
{
|
||||||
|
UpdateCurrentProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string LoadChangelog()
|
||||||
|
{
|
||||||
|
string path = Path.Combine(Plugin.AssemblyLocation, "changelog.md");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string fullChangelog = File.ReadAllText(path);
|
||||||
|
string versionChangelog = fullChangelog.Split("#", StringSplitOptions.RemoveEmptyEntries)[0];
|
||||||
|
return versionChangelog.Replace(Plugin.Version, "");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error loading changelog: " + e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckVersion()
|
||||||
|
{
|
||||||
|
string path = Path.Combine(ConfigDirectory, "version");
|
||||||
|
|
||||||
|
bool needsBackup = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool needsWrite = false;
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
needsWrite = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PreviousVersion = File.ReadAllText(path);
|
||||||
|
if (PreviousVersion != Plugin.Version)
|
||||||
|
{
|
||||||
|
needsWrite = true;
|
||||||
|
needsBackup = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_changelogWindow.IsOpen = needsWrite;
|
||||||
|
_changelogWindow.AutoClose = true;
|
||||||
|
|
||||||
|
if (needsWrite)
|
||||||
|
{
|
||||||
|
File.WriteAllText(path, Plugin.Version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error checking version: " + e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (needsBackup && PreviousVersion != null)
|
||||||
|
{
|
||||||
|
BackupFiles(PreviousVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error making backup: " + e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BackupFiles(string version)
|
||||||
|
{
|
||||||
|
string backupsRoot = Path.Combine(ConfigDirectory, "Backups");
|
||||||
|
if (!Directory.Exists(backupsRoot))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(backupsRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
string backupPath = Path.Combine(backupsRoot, version);
|
||||||
|
|
||||||
|
foreach (string folderPath in Directory.GetDirectories(ConfigDirectory, "*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
if (folderPath.Contains("Backups")) { continue; }
|
||||||
|
|
||||||
|
Directory.CreateDirectory(folderPath.Replace(ConfigDirectory, backupPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string filePath in Directory.GetFiles(ConfigDirectory, "*.*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
File.Copy(filePath, filePath.Replace(ConfigDirectory, backupPath), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region strata
|
||||||
|
public void OnStrataLevelChanged(PluginConfigObject config)
|
||||||
|
{
|
||||||
|
StrataLevelsChangedEvent?.Invoke(this, config);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region visibility
|
||||||
|
public void OnGlobalVisibilityChanged(VisibilityConfig config)
|
||||||
|
{
|
||||||
|
GlobalVisibilityEvent?.Invoke(this, config);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region windows
|
||||||
|
public void ToggleConfigWindow()
|
||||||
|
{
|
||||||
|
_mainConfigWindow.Toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenConfigWindow()
|
||||||
|
{
|
||||||
|
_mainConfigWindow.IsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseConfigWindow()
|
||||||
|
{
|
||||||
|
_mainConfigWindow.IsOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenChangelogWindow()
|
||||||
|
{
|
||||||
|
_changelogWindow.IsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
_windowSystem.Draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddExtraSectionNode(SectionNode node)
|
||||||
|
{
|
||||||
|
ConfigBaseNode.AddExtraSectionNode(node);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region config getters and setters
|
||||||
|
public PluginConfigObject GetConfigObjectForType(Type type)
|
||||||
|
{
|
||||||
|
MethodInfo? genericMethod = GetType().GetMethod("GetConfigObject");
|
||||||
|
MethodInfo? method = genericMethod?.MakeGenericMethod(type);
|
||||||
|
return (PluginConfigObject)method?.Invoke(this, null)!;
|
||||||
|
}
|
||||||
|
public T GetConfigObject<T>() where T : PluginConfigObject => ConfigBaseNode.GetConfigObject<T>()!;
|
||||||
|
|
||||||
|
public static PluginConfigObject GetDefaultConfigObjectForType(Type type)
|
||||||
|
{
|
||||||
|
MethodInfo? method = type.GetMethod("DefaultConfig", BindingFlags.Public | BindingFlags.Static);
|
||||||
|
return (PluginConfigObject)method?.Invoke(null, null)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigPageNode GetConfigPageNode<T>() where T : PluginConfigObject => ConfigBaseNode.GetConfigPageNode<T>()!;
|
||||||
|
|
||||||
|
public void SetConfigObject(PluginConfigObject configObject) => ConfigBaseNode.SetConfigObject(configObject);
|
||||||
|
|
||||||
|
public List<T> GetObjects<T>() => ConfigBaseNode.GetObjects<T>();
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region load / save / profiles
|
||||||
|
public bool IsFreshInstall()
|
||||||
|
{
|
||||||
|
return Directory.GetDirectories(ConfigDirectory).Length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadOrInitializeFiles()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!IsFreshInstall())
|
||||||
|
{
|
||||||
|
LoadConfigurations();
|
||||||
|
|
||||||
|
// gotta save after initial load store possible version update changes right away
|
||||||
|
SaveConfigurations(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error initializing configurations: " + e.Message);
|
||||||
|
|
||||||
|
if (e.StackTrace != null)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error(e.StackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ForceNeedsSave()
|
||||||
|
{
|
||||||
|
ConfigBaseNode.NeedsSave = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadConfigurations()
|
||||||
|
{
|
||||||
|
PerformV2Migration();
|
||||||
|
ConfigBaseNode.Load(ConfigDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveConfigurations(bool forced = false)
|
||||||
|
{
|
||||||
|
if (!forced && !ConfigBaseNode.NeedsSave)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigBaseNode.Save(ConfigDirectory);
|
||||||
|
|
||||||
|
ProfilesManager.Instance?.SaveCurrentProfile();
|
||||||
|
|
||||||
|
ConfigBaseNode.NeedsSave = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PerformV2Migration()
|
||||||
|
{
|
||||||
|
// create necessary folders
|
||||||
|
string[] newFolders = new string[] { "Other Elements", "Customization" };
|
||||||
|
foreach (string folder in newFolders)
|
||||||
|
{
|
||||||
|
string path = Path.Combine(ConfigDirectory, folder);
|
||||||
|
if (!Directory.Exists(path))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move files
|
||||||
|
Dictionary<string, string> files = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
["Misc\\Experience Bar.json"] = "Other Elements\\Experience Bar.json",
|
||||||
|
["Misc\\GCD Indicator.json"] = "Other Elements\\GCD Indicator.json",
|
||||||
|
["Misc\\Limit Break.json"] = "Other Elements\\Limit Break.json",
|
||||||
|
["Misc\\MP Ticker.json"] = "Other Elements\\MP Ticker.json",
|
||||||
|
["Misc\\Pull Timer.json"] = "Other Elements\\Pull Timer.json",
|
||||||
|
["Misc\\Fonts.json"] = "Customization\\Fonts.json"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (string key in files.Keys)
|
||||||
|
{
|
||||||
|
string v1Path = Path.Combine(ConfigDirectory, key);
|
||||||
|
string v2Path = Path.Combine(ConfigDirectory, files[key]);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(v1Path) && !File.Exists(v2Path))
|
||||||
|
{
|
||||||
|
File.Move(v1Path, v2Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error migrating file \"" + v1Path + "\" to v2 config structure: " + e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateCurrentProfile()
|
||||||
|
{
|
||||||
|
// dont update the profile on job change when the config window is opened
|
||||||
|
if (_mainConfigWindow.IsOpen)
|
||||||
|
{
|
||||||
|
_needsProfileUpdate = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilesManager.Instance?.UpdateCurrentProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? ExportCurrentConfigs()
|
||||||
|
{
|
||||||
|
return ConfigBaseNode.GetBase64String();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnProfileDeleted(string profileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_configBaseNodeByProfile.Remove(profileName);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ImportProfile(string oldProfileName, string profileName, string rawString, bool forceLoad = false)
|
||||||
|
{
|
||||||
|
// cache old profile
|
||||||
|
_configBaseNodeByProfile[oldProfileName] = ConfigBaseNode;
|
||||||
|
|
||||||
|
// load profile from cache or from rawString
|
||||||
|
BaseNode? loadedNode = null;
|
||||||
|
if (forceLoad || !_configBaseNodeByProfile.TryGetValue(profileName, out loadedNode))
|
||||||
|
{
|
||||||
|
ImportProfileNonCached(rawString, out loadedNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedNode == null) { return false; }
|
||||||
|
|
||||||
|
if (IsConfigWindowOpened || string.IsNullOrEmpty(loadedNode.SelectedOptionName))
|
||||||
|
{
|
||||||
|
loadedNode.SelectedOptionName = ConfigBaseNode.SelectedOptionName;
|
||||||
|
loadedNode.RefreshSelectedNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigBaseNode.ConfigObjectResetEvent -= OnConfigObjectReset;
|
||||||
|
ConfigBaseNode = loadedNode;
|
||||||
|
ConfigBaseNode.ConfigObjectResetEvent += OnConfigObjectReset;
|
||||||
|
|
||||||
|
PerformV2Migration();
|
||||||
|
|
||||||
|
ResetEvent?.Invoke(this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ImportProfileNonCached(string rawString, out BaseNode? node)
|
||||||
|
{
|
||||||
|
List<string> importStrings = new List<string>(rawString.Trim().Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries));
|
||||||
|
ImportData[] imports = importStrings.Select(str => new ImportData(str)).ToArray();
|
||||||
|
|
||||||
|
node = new BaseNode();
|
||||||
|
InitializeBaseNode(node);
|
||||||
|
|
||||||
|
Dictionary<Type, PluginConfigObject> oldConfigObjects = new Dictionary<Type, PluginConfigObject>();
|
||||||
|
|
||||||
|
foreach (ImportData importData in imports)
|
||||||
|
{
|
||||||
|
PluginConfigObject? config = importData.GetObject();
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.SetConfigObject(config))
|
||||||
|
{
|
||||||
|
oldConfigObjects.Add(config.GetType(), config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ProfilesManager.Instance != null)
|
||||||
|
{
|
||||||
|
node.AddExtraSectionNode(ProfilesManager.Instance.ProfilesNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region initialization
|
||||||
|
private static void InitializeBaseNode(BaseNode node)
|
||||||
|
{
|
||||||
|
// creates node tree in the right order...
|
||||||
|
foreach (Type type in ConfigObjectTypes)
|
||||||
|
{
|
||||||
|
var genericMethod = node.GetType().GetMethod("GetConfigPageNode");
|
||||||
|
var method = genericMethod?.MakeGenericMethod(type);
|
||||||
|
method?.Invoke(node, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type[] ConfigObjectTypes = new Type[]
|
||||||
|
{
|
||||||
|
// Unit Frames
|
||||||
|
typeof(PlayerUnitFrameConfig),
|
||||||
|
typeof(TargetUnitFrameConfig),
|
||||||
|
typeof(TargetOfTargetUnitFrameConfig),
|
||||||
|
typeof(FocusTargetUnitFrameConfig),
|
||||||
|
|
||||||
|
// Mana Bars
|
||||||
|
typeof(PlayerPrimaryResourceConfig),
|
||||||
|
typeof(TargetPrimaryResourceConfig),
|
||||||
|
typeof(TargetOfTargetPrimaryResourceConfig),
|
||||||
|
typeof(FocusTargetPrimaryResourceConfig),
|
||||||
|
|
||||||
|
// Castbars
|
||||||
|
typeof(PlayerCastbarConfig),
|
||||||
|
typeof(TargetCastbarConfig),
|
||||||
|
typeof(TargetOfTargetCastbarConfig),
|
||||||
|
typeof(FocusTargetCastbarConfig),
|
||||||
|
|
||||||
|
// Buffs and Debuffs
|
||||||
|
typeof(PlayerBuffsListConfig),
|
||||||
|
typeof(PlayerDebuffsListConfig),
|
||||||
|
typeof(TargetBuffsListConfig),
|
||||||
|
typeof(TargetDebuffsListConfig),
|
||||||
|
typeof(FocusTargetBuffsListConfig),
|
||||||
|
typeof(FocusTargetDebuffsListConfig),
|
||||||
|
typeof(CustomEffectsListConfig),
|
||||||
|
|
||||||
|
// Nameplates
|
||||||
|
typeof(NameplatesGeneralConfig),
|
||||||
|
typeof(PlayerNameplateConfig),
|
||||||
|
typeof(EnemyNameplateConfig),
|
||||||
|
typeof(PartyMembersNameplateConfig),
|
||||||
|
typeof(AllianceMembersNameplateConfig),
|
||||||
|
typeof(FriendPlayerNameplateConfig),
|
||||||
|
typeof(OtherPlayerNameplateConfig),
|
||||||
|
typeof(PetNameplateConfig),
|
||||||
|
typeof(NPCNameplateConfig),
|
||||||
|
typeof(MinionNPCNameplateConfig),
|
||||||
|
typeof(ObjectsNameplateConfig),
|
||||||
|
|
||||||
|
// Party Frames
|
||||||
|
typeof(PartyFramesConfig),
|
||||||
|
typeof(PartyFramesHealthBarsConfig),
|
||||||
|
typeof(PartyFramesManaBarConfig),
|
||||||
|
typeof(PartyFramesCastbarConfig),
|
||||||
|
typeof(PartyFramesIconsConfig),
|
||||||
|
typeof(PartyFramesBuffsConfig),
|
||||||
|
typeof(PartyFramesDebuffsConfig),
|
||||||
|
typeof(PartyFramesTrackersConfig),
|
||||||
|
typeof(PartyFramesCooldownListConfig),
|
||||||
|
|
||||||
|
// Party Cooldowns
|
||||||
|
typeof(PartyCooldownsConfig),
|
||||||
|
typeof(PartyCooldownsBarConfig),
|
||||||
|
typeof(PartyCooldownsDataConfig),
|
||||||
|
|
||||||
|
// Enemy List
|
||||||
|
typeof(EnemyListConfig),
|
||||||
|
typeof(EnemyListHealthBarConfig),
|
||||||
|
typeof(EnemyListEnmityIconConfig),
|
||||||
|
typeof(EnemyListSignIconConfig),
|
||||||
|
typeof(EnemyListCastbarConfig),
|
||||||
|
typeof(EnemyListBuffsConfig),
|
||||||
|
typeof(EnemyListDebuffsConfig),
|
||||||
|
|
||||||
|
// Job Specific Bars
|
||||||
|
typeof(PaladinConfig),
|
||||||
|
typeof(WarriorConfig),
|
||||||
|
typeof(DarkKnightConfig),
|
||||||
|
typeof(GunbreakerConfig),
|
||||||
|
|
||||||
|
typeof(WhiteMageConfig),
|
||||||
|
typeof(ScholarConfig),
|
||||||
|
typeof(AstrologianConfig),
|
||||||
|
typeof(SageConfig),
|
||||||
|
|
||||||
|
typeof(MonkConfig),
|
||||||
|
typeof(DragoonConfig),
|
||||||
|
typeof(NinjaConfig),
|
||||||
|
typeof(SamuraiConfig),
|
||||||
|
typeof(ReaperConfig),
|
||||||
|
typeof(ViperConfig),
|
||||||
|
|
||||||
|
typeof(BardConfig),
|
||||||
|
typeof(MachinistConfig),
|
||||||
|
typeof(DancerConfig),
|
||||||
|
|
||||||
|
typeof(BlackMageConfig),
|
||||||
|
typeof(SummonerConfig),
|
||||||
|
typeof(RedMageConfig),
|
||||||
|
typeof(BlueMageConfig),
|
||||||
|
typeof(PictomancerConfig),
|
||||||
|
|
||||||
|
// Other Elements
|
||||||
|
typeof(ExperienceBarConfig),
|
||||||
|
typeof(GCDIndicatorConfig),
|
||||||
|
typeof(HotbarsConfig),
|
||||||
|
typeof(Hotbar1BarConfig),
|
||||||
|
typeof(Hotbar2BarConfig),
|
||||||
|
typeof(Hotbar3BarConfig),
|
||||||
|
typeof(Hotbar4BarConfig),
|
||||||
|
typeof(Hotbar5BarConfig),
|
||||||
|
typeof(Hotbar6BarConfig),
|
||||||
|
typeof(Hotbar7BarConfig),
|
||||||
|
typeof(Hotbar8BarConfig),
|
||||||
|
typeof(Hotbar9BarConfig),
|
||||||
|
typeof(Hotbar10BarConfig),
|
||||||
|
typeof(PullTimerConfig),
|
||||||
|
typeof(LimitBreakConfig),
|
||||||
|
typeof(MPTickerConfig),
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
typeof(TanksColorConfig),
|
||||||
|
typeof(HealersColorConfig),
|
||||||
|
typeof(MeleeColorConfig),
|
||||||
|
typeof(RangedColorConfig),
|
||||||
|
typeof(CastersColorConfig),
|
||||||
|
typeof(RolesColorConfig),
|
||||||
|
typeof(MiscColorConfig),
|
||||||
|
|
||||||
|
// Customization
|
||||||
|
typeof(FontsConfig),
|
||||||
|
typeof(BarTexturesConfig),
|
||||||
|
|
||||||
|
// Visibility
|
||||||
|
typeof(GlobalVisibilityConfig),
|
||||||
|
typeof(HotbarsVisibilityConfig),
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
typeof(HUDOptionsConfig),
|
||||||
|
typeof(WindowClippingConfig),
|
||||||
|
typeof(TooltipsConfig),
|
||||||
|
typeof(GridConfig),
|
||||||
|
|
||||||
|
// Import
|
||||||
|
typeof(ImportConfig)
|
||||||
|
};
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Config.Profiles;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace HSUI.Interface
|
||||||
|
{
|
||||||
|
[Disableable(false)]
|
||||||
|
[Exportable(false)]
|
||||||
|
[Shareable(false)]
|
||||||
|
[Resettable(false)]
|
||||||
|
[Section("Import")]
|
||||||
|
[SubSection("General", 0)]
|
||||||
|
public class ImportConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
private string _importString = "";
|
||||||
|
private bool _importing = false;
|
||||||
|
private string? _errorMessage = null;
|
||||||
|
|
||||||
|
private List<ImportData>? _importDataList = null;
|
||||||
|
private List<bool>? _importDataEnabled = null;
|
||||||
|
|
||||||
|
public new static ImportConfig DefaultConfig() { return new ImportConfig(); }
|
||||||
|
|
||||||
|
[ManualDraw]
|
||||||
|
public bool Draw(ref bool changed)
|
||||||
|
{
|
||||||
|
ImGui.Text("Import string:");
|
||||||
|
|
||||||
|
ImGui.InputText("", ref _importString, 999999);
|
||||||
|
|
||||||
|
ImGui.Text("Here you can import specific parts of a profile.\nIf the string contains more than one part you will be able to select which parts you wish to import.");
|
||||||
|
|
||||||
|
if (ImGui.Button("Import", new Vector2(560, 24)))
|
||||||
|
{
|
||||||
|
_importing = _importString.Length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiHelper.DrawSeparator(1, 1);
|
||||||
|
ImGui.Text("To browse presets made by users of the HSUI community join our Discord and find the #profiles channel.");
|
||||||
|
|
||||||
|
if (ImGui.Button("HSUI Discord", new Vector2(560, 24)))
|
||||||
|
{
|
||||||
|
Utils.OpenUrl("https://discord.gg/xzde5qQayh");
|
||||||
|
}
|
||||||
|
|
||||||
|
// error modal
|
||||||
|
if (_errorMessage != null)
|
||||||
|
{
|
||||||
|
if (ImGuiHelper.DrawErrorModal(_errorMessage))
|
||||||
|
{
|
||||||
|
_importing = false;
|
||||||
|
_errorMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse import string
|
||||||
|
if (_importing && _importDataList == null)
|
||||||
|
{
|
||||||
|
_errorMessage = Parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirmation modal
|
||||||
|
if (_importDataList != null && _importDataList.Count > 0)
|
||||||
|
{
|
||||||
|
var (didConfirm, didClose) = DrawImportConfirmationModal();
|
||||||
|
|
||||||
|
if (didConfirm)
|
||||||
|
{
|
||||||
|
_errorMessage = Import();
|
||||||
|
|
||||||
|
if (_errorMessage == null)
|
||||||
|
{
|
||||||
|
_importString = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didConfirm || didClose)
|
||||||
|
{
|
||||||
|
_importing = false;
|
||||||
|
_importDataList = null;
|
||||||
|
_importDataEnabled = null;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return didConfirm && _errorMessage == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? Import()
|
||||||
|
{
|
||||||
|
if (_importDataList == null || _importDataEnabled == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PluginConfigObject> configObjects = new List<PluginConfigObject>(_importDataList.Count);
|
||||||
|
|
||||||
|
for (int i = 0; i < _importDataList.Count; i++)
|
||||||
|
{
|
||||||
|
if (i >= _importDataEnabled.Count || _importDataEnabled[i] == false)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImportData importData = _importDataList[i];
|
||||||
|
PluginConfigObject? config = importData.GetObject();
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return "Couldn't import \"" + importData.Name + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
configObjects.Add(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (PluginConfigObject config in configObjects)
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.SetConfigObject(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? Parse()
|
||||||
|
{
|
||||||
|
string[] importStrings = _importString.Trim().Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (importStrings.Length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_importDataList = new List<ImportData>(importStrings.Length);
|
||||||
|
_importDataEnabled = new List<bool>(importStrings.Length);
|
||||||
|
|
||||||
|
foreach (var str in importStrings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ImportData importData = new ImportData(str);
|
||||||
|
_importDataList.Add(importData);
|
||||||
|
_importDataEnabled.Add(true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_importDataList = null;
|
||||||
|
_importDataEnabled = null;
|
||||||
|
|
||||||
|
return e is ArgumentException ? e.Message : "Invalid import string!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (bool, bool) DrawImportConfirmationModal()
|
||||||
|
{
|
||||||
|
if (_importDataList == null || _importDataEnabled == null)
|
||||||
|
{
|
||||||
|
return (false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurationManager.Instance.ShowingModalWindow = true;
|
||||||
|
|
||||||
|
bool didConfirm = false;
|
||||||
|
bool didClose = false;
|
||||||
|
|
||||||
|
ImGui.OpenPopup("Import ##HSUI");
|
||||||
|
|
||||||
|
Vector2 center = ImGui.GetMainViewport().GetCenter();
|
||||||
|
ImGui.SetNextWindowPos(center, ImGuiCond.Appearing, new Vector2(0.5f, 0.5f));
|
||||||
|
|
||||||
|
bool p_open = true; // i've no idea what this is used for
|
||||||
|
|
||||||
|
if (ImGui.BeginPopupModal("Import ##HSUI", ref p_open, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoMove))
|
||||||
|
{
|
||||||
|
float width = 300;
|
||||||
|
|
||||||
|
ImGui.Text("Select which parts to import:");
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
if (ImGui.Button("Select All", new Vector2(width / 2f - 5, 24)))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _importDataEnabled.Count; i++)
|
||||||
|
{
|
||||||
|
_importDataEnabled[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Deselect All", new Vector2(width / 2f - 5, 24)))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _importDataEnabled.Count; i++)
|
||||||
|
{
|
||||||
|
_importDataEnabled[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
float height = Math.Min(30 * _importDataList.Count, 400);
|
||||||
|
|
||||||
|
ImGui.BeginChild("import checkboxes", new Vector2(width, height), false);
|
||||||
|
|
||||||
|
for (int i = 0; i < _importDataList.Count; i++)
|
||||||
|
{
|
||||||
|
bool value = _importDataEnabled[i];
|
||||||
|
if (ImGui.Checkbox(_importDataList[i].Name, ref value))
|
||||||
|
{
|
||||||
|
_importDataEnabled[i] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
if (ImGui.Button("OK", new Vector2(width / 2f - 5, 24)))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
didConfirm = true;
|
||||||
|
didClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetItemDefaultFocus();
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Cancel", new Vector2(width / 2f - 5, 24)))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
didClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
// close button on nav
|
||||||
|
else
|
||||||
|
{
|
||||||
|
didClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didClose)
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.ShowingModalWindow = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (didConfirm, didClose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImportData
|
||||||
|
{
|
||||||
|
public readonly Type ConfigType;
|
||||||
|
public readonly string Name;
|
||||||
|
|
||||||
|
public readonly string ImportString;
|
||||||
|
public readonly string JsonString;
|
||||||
|
|
||||||
|
public ImportData(string base64String)
|
||||||
|
{
|
||||||
|
ImportString = base64String;
|
||||||
|
JsonString = ImportExportHelper.Base64DecodeAndDecompress(base64String);
|
||||||
|
|
||||||
|
string? typeString = (string?)JObject.Parse(JsonString)["$type"];
|
||||||
|
if (typeString == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid type");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate DelvUI type names to HSUI for profile/import compatibility
|
||||||
|
if (typeString.Contains("DelvUI"))
|
||||||
|
{
|
||||||
|
typeString = typeString.Replace("DelvUI.", "HSUI.").Replace(", DelvUI", ", HSUI");
|
||||||
|
}
|
||||||
|
|
||||||
|
Type? type = Type.GetType(typeString);
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid type: \"" + typeString + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigType = type;
|
||||||
|
Name = Utils.UserFriendlyConfigName(type.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginConfigObject? GetObject()
|
||||||
|
{
|
||||||
|
MethodInfo? methodInfo = typeof(PluginConfigObject).GetMethod("LoadFromJsonString", BindingFlags.Public | BindingFlags.Static);
|
||||||
|
MethodInfo? function = methodInfo?.MakeGenericMethod(ConfigType);
|
||||||
|
return (PluginConfigObject?)function?.Invoke(this, new object[] { JsonString })!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HSUI.Config
|
||||||
|
{
|
||||||
|
public delegate void ConfigValueChangeEventHandler(PluginConfigObject sender, OnChangeBaseArgs args);
|
||||||
|
|
||||||
|
public enum ChangeType
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
ListAdd = 1,
|
||||||
|
ListRemove = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OnChangeBaseArgs : EventArgs
|
||||||
|
{
|
||||||
|
public string PropertyName { get; }
|
||||||
|
public ChangeType ChangeType { get; private set; }
|
||||||
|
|
||||||
|
public OnChangeBaseArgs(string keyName, ChangeType type = ChangeType.None)
|
||||||
|
{
|
||||||
|
PropertyName = keyName;
|
||||||
|
ChangeType = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OnChangeEventArgs<T> : OnChangeBaseArgs
|
||||||
|
{
|
||||||
|
public T Value { get; }
|
||||||
|
|
||||||
|
public OnChangeEventArgs(string keyName, T value, ChangeType type = ChangeType.None) : base(keyName, type)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IOnChangeEventArgs
|
||||||
|
{
|
||||||
|
public abstract event ConfigValueChangeEventHandler? ValueChangeEvent;
|
||||||
|
|
||||||
|
public abstract void OnValueChanged(OnChangeBaseArgs e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace HSUI.Config
|
||||||
|
{
|
||||||
|
public abstract class PluginConfigObject : IOnChangeEventArgs
|
||||||
|
{
|
||||||
|
public string Version => Plugin.Version;
|
||||||
|
|
||||||
|
[Checkbox("Enabled")]
|
||||||
|
[Order(0, collapseWith = null)]
|
||||||
|
public bool Enabled = true;
|
||||||
|
|
||||||
|
#region convenience properties
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool Exportable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ExportableAttribute? attribute = (ExportableAttribute?)GetType().GetCustomAttribute(typeof(ExportableAttribute), false);
|
||||||
|
return attribute == null || attribute.exportable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool Shareable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ShareableAttribute? attribute = (ShareableAttribute?)GetType().GetCustomAttribute(typeof(ShareableAttribute), false);
|
||||||
|
return attribute == null || attribute.shareable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool Resettable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ResettableAttribute? attribute = (ResettableAttribute?)GetType().GetCustomAttribute(typeof(ResettableAttribute), false);
|
||||||
|
return attribute == null || attribute.resettable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool Disableable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
DisableableAttribute? attribute = (DisableableAttribute?)GetType().GetCustomAttribute(typeof(DisableableAttribute), false);
|
||||||
|
return attribute == null || attribute.disableable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string[]? DisableParentSettings
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
DisableParentSettingsAttribute? attribute = (DisableParentSettingsAttribute?)GetType().GetCustomAttribute(typeof(DisableParentSettingsAttribute), true);
|
||||||
|
return attribute?.DisabledFields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
protected bool ColorEdit4(string label, ref PluginConfigColor color)
|
||||||
|
{
|
||||||
|
var vector = color.Vector;
|
||||||
|
|
||||||
|
if (ImGui.ColorEdit4(label, ref vector))
|
||||||
|
{
|
||||||
|
color.Vector = vector;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PluginConfigObject DefaultConfig()
|
||||||
|
{
|
||||||
|
return null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> GetObjects<T>()
|
||||||
|
{
|
||||||
|
List<T> list = new List<T>();
|
||||||
|
|
||||||
|
Type type = typeof(T);
|
||||||
|
if (this is T obj)
|
||||||
|
{
|
||||||
|
list.Add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate properties
|
||||||
|
PropertyInfo[] properties = GetType().GetProperties();
|
||||||
|
foreach (PropertyInfo property in properties)
|
||||||
|
{
|
||||||
|
object? value = property.GetValue(this);
|
||||||
|
|
||||||
|
if (value is T o)
|
||||||
|
{
|
||||||
|
list.Add(o);
|
||||||
|
}
|
||||||
|
else if (value is PluginConfigObject p)
|
||||||
|
{
|
||||||
|
list.AddRange(p.GetObjects<T>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate fields
|
||||||
|
FieldInfo[] fields = GetType().GetFields();
|
||||||
|
foreach (FieldInfo field in fields)
|
||||||
|
{
|
||||||
|
object? value = field.GetValue(this);
|
||||||
|
|
||||||
|
if (value is T o)
|
||||||
|
{
|
||||||
|
list.Add(o);
|
||||||
|
}
|
||||||
|
else if (value is PluginConfigObject p)
|
||||||
|
{
|
||||||
|
list.AddRange(p.GetObjects<T>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? Load<T>(FileInfo fileInfo) where T : PluginConfigObject
|
||||||
|
{
|
||||||
|
return LoadFromJson<T>(fileInfo.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T? LoadFromJson<T>(string path) where T : PluginConfigObject
|
||||||
|
{
|
||||||
|
if (!File.Exists(path)) { return null; }
|
||||||
|
|
||||||
|
return LoadFromJsonString<T>(File.ReadAllText(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T? LoadFromJsonString<T>(string jsonString) where T : PluginConfigObject
|
||||||
|
{
|
||||||
|
JsonSerializerSettings settings = new JsonSerializerSettings();
|
||||||
|
settings.ContractResolver = new PluginConfigObjectsContractResolver();
|
||||||
|
|
||||||
|
return JsonConvert.DeserializeObject<T>(jsonString, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IOnChangeEventArgs
|
||||||
|
|
||||||
|
// sending event outside of the config
|
||||||
|
public event ConfigValueChangeEventHandler? ValueChangeEvent;
|
||||||
|
|
||||||
|
// received events from the node
|
||||||
|
public void OnValueChanged(OnChangeBaseArgs e)
|
||||||
|
{
|
||||||
|
ValueChangeEvent?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class MovablePluginConfigObject : PluginConfigObject
|
||||||
|
{
|
||||||
|
[JsonIgnore]
|
||||||
|
public string ID;
|
||||||
|
|
||||||
|
[StrataLevel("Strata Level")]
|
||||||
|
[Order(2)]
|
||||||
|
public StrataLevel? Strata;
|
||||||
|
|
||||||
|
public StrataLevel StrataLevel => Strata ?? StrataLevel.LOWEST;
|
||||||
|
|
||||||
|
[DragInt2("Position", min = -4000, max = 4000)]
|
||||||
|
[Order(5)]
|
||||||
|
public Vector2 Position = Vector2.Zero;
|
||||||
|
|
||||||
|
public MovablePluginConfigObject()
|
||||||
|
{
|
||||||
|
ID = $"DelvUI_{GetType().Name}_{Guid.NewGuid()}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class AnchorablePluginConfigObject : MovablePluginConfigObject
|
||||||
|
{
|
||||||
|
[DragInt2("Size", min = 1, max = 4000, isMonitored = true)]
|
||||||
|
[Order(10)]
|
||||||
|
public Vector2 Size;
|
||||||
|
|
||||||
|
[Anchor("Anchor")]
|
||||||
|
[Order(15)]
|
||||||
|
public DrawAnchor Anchor = DrawAnchor.Center;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PluginConfigColor
|
||||||
|
{
|
||||||
|
[JsonIgnore] private float[] _colorMapRatios = { -.8f, -.3f, .1f };
|
||||||
|
|
||||||
|
[JsonIgnore] private Vector4 _vector;
|
||||||
|
|
||||||
|
public PluginConfigColor(Vector4 vector, float[]? colorMapRatios = null)
|
||||||
|
{
|
||||||
|
_vector = vector;
|
||||||
|
|
||||||
|
if (colorMapRatios != null && colorMapRatios.Length >= 3)
|
||||||
|
{
|
||||||
|
_colorMapRatios = colorMapRatios;
|
||||||
|
}
|
||||||
|
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PluginConfigColor FromHex(uint hexColor)
|
||||||
|
{
|
||||||
|
// ARGB to ABGR
|
||||||
|
uint r = (hexColor >> 16) & 0xFF;
|
||||||
|
uint b = hexColor & 0xFF;
|
||||||
|
hexColor = (hexColor & 0xFF00FF00) | (b << 16) | r;
|
||||||
|
|
||||||
|
return new PluginConfigColor(ImGui.ColorConvertU32ToFloat4(hexColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginConfigColor WithAlpha(float alpha)
|
||||||
|
{
|
||||||
|
if (alpha == Vector.W) { return this; }
|
||||||
|
|
||||||
|
return new PluginConfigColor(Vector.WithNewAlpha(alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PluginConfigColor Empty => new(Vector4.Zero);
|
||||||
|
|
||||||
|
public Vector4 Vector
|
||||||
|
{
|
||||||
|
get => _vector;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_vector == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_vector = value;
|
||||||
|
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore] public uint Base { get; private set; }
|
||||||
|
|
||||||
|
[JsonIgnore] public uint Background { get; private set; }
|
||||||
|
|
||||||
|
[JsonIgnore] public uint TopGradient { get; private set; }
|
||||||
|
|
||||||
|
[JsonIgnore] public uint BottomGradient { get; private set; }
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
Base = ImGui.ColorConvertFloat4ToU32(_vector);
|
||||||
|
Background = ImGui.ColorConvertFloat4ToU32(_vector.AdjustColor(_colorMapRatios[0]));
|
||||||
|
TopGradient = ImGui.ColorConvertFloat4ToU32(_vector.AdjustColor(_colorMapRatios[1]));
|
||||||
|
BottomGradient = ImGui.ColorConvertFloat4ToU32(_vector.AdjustColor(_colorMapRatios[2]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
using Dalamud.Logging;
|
||||||
|
using HSUI.Interface.EnemyList;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using HSUI.Interface.Party;
|
||||||
|
using HSUI.Interface.StatusEffects;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace HSUI.Config
|
||||||
|
{
|
||||||
|
public abstract class PluginConfigObjectConverter : JsonConverter
|
||||||
|
{
|
||||||
|
protected Dictionary<string, PluginConfigObjectFieldConverter> FieldConvertersMap = new Dictionary<string, PluginConfigObjectFieldConverter>();
|
||||||
|
|
||||||
|
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
var genericMethod = GetType().GetMethod("ConvertJson");
|
||||||
|
var method = genericMethod?.MakeGenericMethod(objectType);
|
||||||
|
return method?.Invoke(this, new object[] { reader, serializer });
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? ConvertJson<T>(JsonReader reader, JsonSerializer serializer) where T : PluginConfigObject
|
||||||
|
{
|
||||||
|
Type type = typeof(T);
|
||||||
|
T? config = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConstructorInfo? constructor = type.GetConstructor(new Type[] { });
|
||||||
|
if (constructor != null)
|
||||||
|
{
|
||||||
|
config = (T?)Activator.CreateInstance<T>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
config = (T?)ConfigurationManager.GetDefaultConfigObjectForType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// last resource, hackily create an instance without calling the constructor
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
#pragma warning disable SYSLIB0050 // Type or member is obsolete
|
||||||
|
config = (T)FormatterServices.GetUninitializedObject(type);
|
||||||
|
#pragma warning restore SYSLIB0050 // Type or member is obsolete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error($"Error creating a {type.Name}: " + e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config == null) { return null; }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
JObject? jsonObject = (JObject?)serializer.Deserialize(reader);
|
||||||
|
if (jsonObject == null) { return null; }
|
||||||
|
|
||||||
|
Dictionary<string, object> ValuesMap = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// get values from json
|
||||||
|
foreach (JProperty property in jsonObject.Properties())
|
||||||
|
{
|
||||||
|
string propertyName = property.Name;
|
||||||
|
object? value = null;
|
||||||
|
|
||||||
|
// convert values if needed
|
||||||
|
if (FieldConvertersMap.TryGetValue(propertyName, out PluginConfigObjectFieldConverter? fieldConverter) && fieldConverter != null)
|
||||||
|
{
|
||||||
|
(propertyName, value) = fieldConverter.Convert(property.Value);
|
||||||
|
}
|
||||||
|
// read value from json
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FieldInfo? field = type.GetField(propertyName);
|
||||||
|
if (field != null)
|
||||||
|
{
|
||||||
|
value = property.Value.ToObject(field.FieldType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
ValuesMap.Add(propertyName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply values
|
||||||
|
foreach (string key in ValuesMap.Keys)
|
||||||
|
{
|
||||||
|
string[] fields = key.Split(".", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
object? currentObject = config;
|
||||||
|
object value = ValuesMap[key];
|
||||||
|
|
||||||
|
for (int i = 0; i < fields.Length; i++)
|
||||||
|
{
|
||||||
|
FieldInfo? field = currentObject?.GetType().GetField(fields[i]);
|
||||||
|
if (field == null) { break; }
|
||||||
|
|
||||||
|
if (i == fields.Length - 1)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
field.SetValue(currentObject, value);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentObject = field.GetValue(currentObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error($"Error deserializing {type.Name}: " + e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
if (value == null) { return; }
|
||||||
|
|
||||||
|
JObject jsonObject = new JObject();
|
||||||
|
Type type = value.GetType();
|
||||||
|
jsonObject.Add("$type", type.FullName + ", HSUI");
|
||||||
|
|
||||||
|
FieldInfo[] fields = type.GetFields();
|
||||||
|
|
||||||
|
foreach (FieldInfo field in fields)
|
||||||
|
{
|
||||||
|
if (field.GetCustomAttribute<JsonIgnoreAttribute>() != null) { continue; }
|
||||||
|
|
||||||
|
object? fieldValue = field.GetValue(value);
|
||||||
|
if (fieldValue != null)
|
||||||
|
{
|
||||||
|
jsonObject.Add(field.Name, JToken.FromObject(fieldValue, serializer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonObject.WriteTo(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region contract resolver
|
||||||
|
public class PluginConfigObjectsContractResolver : DefaultContractResolver
|
||||||
|
{
|
||||||
|
private static Dictionary<Type, Type> ConvertersMap = new Dictionary<Type, Type>()
|
||||||
|
{
|
||||||
|
[typeof(UnitFrameConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||||
|
[typeof(PlayerUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||||
|
[typeof(TargetUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||||
|
[typeof(TargetOfTargetUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||||
|
[typeof(FocusTargetUnitFrameConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||||
|
|
||||||
|
[typeof(PartyFramesColorsConfig)] = typeof(ColorByHealthFieldsConverter),
|
||||||
|
[typeof(PartyFramesRoleIconConfig)] = typeof(PartyFramesIconsConverter),
|
||||||
|
[typeof(PartyFramesLeaderIconConfig)] = typeof(PartyFramesIconsConverter),
|
||||||
|
[typeof(PartyFramesRaiseTrackerConfig)] = typeof(PartyFramesTrackerConfigConverter),
|
||||||
|
[typeof(PartyFramesInvulnTrackerConfig)] = typeof(PartyFramesTrackerConfigConverter),
|
||||||
|
[typeof(PartyFramesManaBarConfig)] = typeof(PartyFramesManaBarConfigConverter),
|
||||||
|
|
||||||
|
[typeof(StatusEffectsBlacklistConfig)] = typeof(StatusEffectsBlacklistConfigConverter),
|
||||||
|
|
||||||
|
[typeof(HUDOptionsConfig)] = typeof(HUDOptionsConfigConverter),
|
||||||
|
|
||||||
|
[typeof(CastbarConfig)] = typeof(CastbarConfigConverter),
|
||||||
|
[typeof(UnitFrameCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||||
|
[typeof(PlayerCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||||
|
[typeof(TargetCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||||
|
[typeof(TargetOfTargetCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||||
|
[typeof(FocusTargetCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||||
|
[typeof(PartyFramesCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||||
|
[typeof(EnemyListCastbarConfig)] = typeof(CastbarConfigConverter),
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override JsonObjectContract CreateObjectContract(Type objectType)
|
||||||
|
{
|
||||||
|
JsonObjectContract contract = base.CreateObjectContract(objectType);
|
||||||
|
|
||||||
|
if (ConvertersMap.TryGetValue(objectType, out Type? converterType) && converterType != null)
|
||||||
|
{
|
||||||
|
contract.Converter = (JsonConverter?)Activator.CreateInstance(converterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region field converters
|
||||||
|
public abstract class PluginConfigObjectFieldConverter
|
||||||
|
{
|
||||||
|
public readonly string NewFieldPath;
|
||||||
|
public PluginConfigObjectFieldConverter(string newFieldPath)
|
||||||
|
{
|
||||||
|
NewFieldPath = newFieldPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract (string, object) Convert(JToken token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NewTypeFieldConverter<TOld, TNew> : PluginConfigObjectFieldConverter
|
||||||
|
where TOld : struct
|
||||||
|
where TNew : struct
|
||||||
|
{
|
||||||
|
private TNew DefaultValue;
|
||||||
|
private Func<TOld, TNew> Func;
|
||||||
|
|
||||||
|
public NewTypeFieldConverter(string newFieldPath, TNew defaultValue, Func<TOld, TNew> func) : base(newFieldPath)
|
||||||
|
{
|
||||||
|
DefaultValue = defaultValue;
|
||||||
|
Func = func;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override (string, object) Convert(JToken token)
|
||||||
|
{
|
||||||
|
TNew result = DefaultValue;
|
||||||
|
|
||||||
|
TOld? oldValue = token.ToObject<TOld>();
|
||||||
|
if (oldValue.HasValue)
|
||||||
|
{
|
||||||
|
result = Func(oldValue.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (NewFieldPath, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SameTypeFieldConverter<T> : NewTypeFieldConverter<T, T> where T : struct
|
||||||
|
{
|
||||||
|
public SameTypeFieldConverter(string newFieldPath, T defaultValue)
|
||||||
|
: base(newFieldPath, defaultValue, (oldValue) => { return oldValue; })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NewClassFieldConverter<TOld, TNew> : PluginConfigObjectFieldConverter
|
||||||
|
where TOld : class
|
||||||
|
where TNew : class
|
||||||
|
{
|
||||||
|
private TNew DefaultValue;
|
||||||
|
private Func<TOld, TNew> Func;
|
||||||
|
|
||||||
|
public NewClassFieldConverter(string newFieldPath, TNew defaultValue, Func<TOld, TNew> func)
|
||||||
|
: base(newFieldPath)
|
||||||
|
{
|
||||||
|
DefaultValue = defaultValue;
|
||||||
|
Func = func;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override (string, object) Convert(JToken token)
|
||||||
|
{
|
||||||
|
TNew result = DefaultValue;
|
||||||
|
|
||||||
|
TOld? oldValue = token.ToObject<TOld>();
|
||||||
|
if (oldValue != null)
|
||||||
|
{
|
||||||
|
result = Func(oldValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (NewFieldPath, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SameClassFieldConverter<T> : NewClassFieldConverter<T, T> where T : class
|
||||||
|
{
|
||||||
|
public SameClassFieldConverter(string newFieldPath, T defaultValue)
|
||||||
|
: base(newFieldPath, defaultValue, (oldValue) => { return oldValue; })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TypeToClassFieldConverter<TOld, TNew> : PluginConfigObjectFieldConverter
|
||||||
|
where TOld : struct
|
||||||
|
where TNew : class
|
||||||
|
{
|
||||||
|
private TNew DefaultValue;
|
||||||
|
private Func<TOld, TNew> Func;
|
||||||
|
|
||||||
|
public TypeToClassFieldConverter(string newFieldPath, TNew defaultValue, Func<TOld, TNew> func) : base(newFieldPath)
|
||||||
|
{
|
||||||
|
DefaultValue = defaultValue;
|
||||||
|
Func = func;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override (string, object) Convert(JToken token)
|
||||||
|
{
|
||||||
|
TNew result = DefaultValue;
|
||||||
|
|
||||||
|
TOld? oldValue = token.ToObject<TOld>();
|
||||||
|
if (oldValue.HasValue)
|
||||||
|
{
|
||||||
|
result = Func(oldValue.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (NewFieldPath, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Profiles
|
||||||
|
{
|
||||||
|
public static class ImportExportHelper
|
||||||
|
{
|
||||||
|
public static string CompressAndBase64Encode(string jsonString)
|
||||||
|
{
|
||||||
|
using MemoryStream output = new();
|
||||||
|
|
||||||
|
using (DeflateStream gzip = new(output, CompressionLevel.Optimal))
|
||||||
|
{
|
||||||
|
using StreamWriter writer = new(gzip, Encoding.UTF8);
|
||||||
|
writer.Write(jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Convert.ToBase64String(output.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Base64DecodeAndDecompress(string base64String)
|
||||||
|
{
|
||||||
|
var base64EncodedBytes = Convert.FromBase64String(base64String);
|
||||||
|
|
||||||
|
using MemoryStream inputStream = new(base64EncodedBytes);
|
||||||
|
using DeflateStream gzip = new(inputStream, CompressionMode.Decompress);
|
||||||
|
using StreamReader reader = new(gzip, Encoding.UTF8);
|
||||||
|
var decodedString = reader.ReadToEnd();
|
||||||
|
|
||||||
|
return decodedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GenerateExportString(object obj)
|
||||||
|
{
|
||||||
|
JsonSerializerSettings settings = new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
|
||||||
|
TypeNameHandling = TypeNameHandling.Objects
|
||||||
|
};
|
||||||
|
|
||||||
|
var jsonString = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
|
||||||
|
return CompressAndBase64Encode(jsonString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
using HSUI.Helpers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Profiles
|
||||||
|
{
|
||||||
|
public class Profile
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
public bool AutoSwitchEnabled = false;
|
||||||
|
public AutoSwitchData AutoSwitchData = new AutoSwitchData();
|
||||||
|
public int HudLayout;
|
||||||
|
public bool AttachHudEnabled = false;
|
||||||
|
|
||||||
|
public Profile(string name, bool autoSwitchEnabled = false, AutoSwitchData? autoSwitchData = null, bool attachHudEnabled = false, int hudLayout = 0)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
|
||||||
|
AutoSwitchEnabled = autoSwitchEnabled;
|
||||||
|
AutoSwitchData = autoSwitchData ?? AutoSwitchData;
|
||||||
|
|
||||||
|
AttachHudEnabled = attachHudEnabled;
|
||||||
|
HudLayout = hudLayout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AutoSwitchData
|
||||||
|
{
|
||||||
|
public Dictionary<JobRoles, List<bool>> Map;
|
||||||
|
|
||||||
|
public AutoSwitchData()
|
||||||
|
{
|
||||||
|
Map = new Dictionary<JobRoles, List<bool>>();
|
||||||
|
|
||||||
|
JobRoles[] roles = (JobRoles[])Enum.GetValues(typeof(JobRoles));
|
||||||
|
|
||||||
|
foreach (JobRoles role in roles)
|
||||||
|
{
|
||||||
|
int count = JobsHelper.JobsByRole[role].Count;
|
||||||
|
List<bool> list = new List<bool>(count);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
list.Add(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map.Add(role, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetRoleEnabled(JobRoles role)
|
||||||
|
{
|
||||||
|
foreach (bool value in Map[role])
|
||||||
|
{
|
||||||
|
if (!value)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRoleEnabled(JobRoles role, bool value)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Map[role].Count; i++)
|
||||||
|
{
|
||||||
|
Map[role][i] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled(JobRoles role, int index)
|
||||||
|
{
|
||||||
|
if (Map.TryGetValue(role, out List<bool>? list) && list != null)
|
||||||
|
{
|
||||||
|
if (index >= list.Count)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ValidateRolesData()
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
JobRoles[] roles = (JobRoles[])Enum.GetValues(typeof(JobRoles));
|
||||||
|
|
||||||
|
foreach (JobRoles role in roles)
|
||||||
|
{
|
||||||
|
int count = JobsHelper.JobsByRole[role].Count;
|
||||||
|
List<bool> list = Map[role];
|
||||||
|
|
||||||
|
if (list.Count < count)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < count - list.Count; i++)
|
||||||
|
{
|
||||||
|
list.Add(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,372 @@
|
|||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Tree
|
||||||
|
{
|
||||||
|
public delegate void ConfigObjectResetEventHandler(BaseNode sender);
|
||||||
|
|
||||||
|
public class BaseNode : Node
|
||||||
|
{
|
||||||
|
public event ConfigObjectResetEventHandler? ConfigObjectResetEvent;
|
||||||
|
|
||||||
|
private Dictionary<Type, ConfigPageNode> _configPageNodesMap;
|
||||||
|
|
||||||
|
public bool NeedsSave = false;
|
||||||
|
public string? SelectedOptionName = null;
|
||||||
|
|
||||||
|
private List<Node> _extraNodes = new List<Node>();
|
||||||
|
private List<Node> _nodes = new List<Node>();
|
||||||
|
|
||||||
|
public IReadOnlyCollection<Node> Sections => _nodes.AsReadOnly();
|
||||||
|
|
||||||
|
private float _scale => ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public BaseNode()
|
||||||
|
{
|
||||||
|
_configPageNodesMap = new Dictionary<Type, ConfigPageNode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddExtraSectionNode(SectionNode node)
|
||||||
|
{
|
||||||
|
_extraNodes.Add(node);
|
||||||
|
|
||||||
|
_nodes.Clear();
|
||||||
|
CreateNodesIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? GetConfigObject<T>() where T : PluginConfigObject
|
||||||
|
{
|
||||||
|
var pageNode = GetConfigPageNode<T>();
|
||||||
|
|
||||||
|
return pageNode != null ? (T)pageNode.ConfigObject : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveConfigObject<T>() where T : PluginConfigObject
|
||||||
|
{
|
||||||
|
if (_configPageNodesMap.ContainsKey(typeof(T)))
|
||||||
|
{
|
||||||
|
_configPageNodesMap.Remove(typeof(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigPageNode? GetConfigPageNode<T>() where T : PluginConfigObject
|
||||||
|
{
|
||||||
|
if (_configPageNodesMap.TryGetValue(typeof(T), out var node))
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
var configPageNode = GetOrAddConfig<T>();
|
||||||
|
|
||||||
|
if (configPageNode != null && configPageNode.ConfigObject != null)
|
||||||
|
{
|
||||||
|
_configPageNodesMap.Add(typeof(T), configPageNode);
|
||||||
|
|
||||||
|
return configPageNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetConfigPageNode(ConfigPageNode configPageNode)
|
||||||
|
{
|
||||||
|
if (configPageNode.ConfigObject == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_configPageNodesMap[configPageNode.ConfigObject.GetType()] = configPageNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetConfigObject(PluginConfigObject configObject)
|
||||||
|
{
|
||||||
|
if (_configPageNodesMap.TryGetValue(configObject.GetType(), out ConfigPageNode? configPageNode))
|
||||||
|
{
|
||||||
|
configPageNode.ConfigObject = configObject;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool PushStyles()
|
||||||
|
{
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.ScrollbarRounding, 1); //Scrollbar Radius
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.TabRounding, 1); //Tabs Radius Radius
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 1); //Intractable Elements Radius
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.GrabRounding, 1); //Gradable Elements Radius
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.PopupRounding, 1); //Popup Radius
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.ScrollbarSize, 10); //Popup Radius
|
||||||
|
|
||||||
|
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(46f / 255f, 45f / 255f, 46f / 255f, 1f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .2f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .2f));
|
||||||
|
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Separator, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .4f));
|
||||||
|
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ScrollbarBg, new Vector4(20f / 255f, 21f / 255f, 20f / 255f, .7f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ScrollbarGrab, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .7f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ScrollbarGrabActive, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .7f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ScrollbarGrabHovered, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .7f));
|
||||||
|
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Tab, new Vector4(46f / 255f, 45f / 255f, 46f / 255f, 1f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.TabActive, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .7f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.TabHovered, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .2f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.TabUnfocused, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .2f));
|
||||||
|
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, 1f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.CheckMark, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, 1f));
|
||||||
|
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.TableBorderStrong, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, 1f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.TableBorderLight, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .4f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.TableHeaderBg, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, .2f));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopStyles(bool popColors)
|
||||||
|
{
|
||||||
|
if (popColors)
|
||||||
|
{
|
||||||
|
ImGui.PopStyleColor(17);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopStyleVar(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateNodesIfNeeded()
|
||||||
|
{
|
||||||
|
if (_nodes.Count > 0) { return; }
|
||||||
|
|
||||||
|
_nodes.AddRange(_children);
|
||||||
|
_nodes.AddRange(_extraNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshSelectedNode()
|
||||||
|
{
|
||||||
|
if (_nodes.Count == 0) { return; }
|
||||||
|
|
||||||
|
foreach (SectionNode node in _nodes.FindAll(x => x is SectionNode))
|
||||||
|
{
|
||||||
|
node.Selected = node.Name == SelectedOptionName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw(float alpha)
|
||||||
|
{
|
||||||
|
CreateNodesIfNeeded();
|
||||||
|
if (_nodes.Count == 0) { return; }
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
bool didReset = false;
|
||||||
|
|
||||||
|
bool popColors = PushStyles();
|
||||||
|
|
||||||
|
ImGui.BeginGroup(); // Middle section
|
||||||
|
{
|
||||||
|
ImGui.BeginGroup(); // Left
|
||||||
|
{
|
||||||
|
// banner
|
||||||
|
IDalamudTextureWrap? delvUiBanner = Plugin.BannerTexture?.GetWrapOrDefault();
|
||||||
|
if (delvUiBanner != null)
|
||||||
|
{
|
||||||
|
ImGui.SetCursorPos(new Vector2(15 + 150 * _scale / 2f - delvUiBanner.Width / 2f, 5));
|
||||||
|
ImGui.Image(delvUiBanner.Handle, new Vector2(delvUiBanner.Width, delvUiBanner.Height));
|
||||||
|
}
|
||||||
|
|
||||||
|
// version
|
||||||
|
ImGui.SetCursorPos(new Vector2(60 * _scale, 35));
|
||||||
|
ImGui.Text($"v{Plugin.Version}");
|
||||||
|
|
||||||
|
|
||||||
|
// section list
|
||||||
|
if (ImGui.BeginChild("left pane", new Vector2(150 * _scale, -10), true, ImGuiWindowFlags.NoScrollbar))
|
||||||
|
{
|
||||||
|
|
||||||
|
// if no section is selected, select the first
|
||||||
|
if (_nodes.Any() && _nodes.Find(o => o is SectionNode sectionNode && sectionNode.Selected) == null)
|
||||||
|
{
|
||||||
|
SectionNode? selectedSection = (SectionNode?)_nodes.Find(o => o is SectionNode sectionNode && sectionNode.Name == SelectedOptionName);
|
||||||
|
if (selectedSection != null)
|
||||||
|
{
|
||||||
|
selectedSection.Selected = true;
|
||||||
|
SelectedOptionName = selectedSection.Name;
|
||||||
|
}
|
||||||
|
else if (_nodes.Count > 0)
|
||||||
|
{
|
||||||
|
SectionNode node = (SectionNode)_nodes[0];
|
||||||
|
node.Selected = true;
|
||||||
|
SelectedOptionName = node.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (SectionNode selectionNode in _nodes)
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(selectionNode.Name, selectionNode.Selected))
|
||||||
|
{
|
||||||
|
selectionNode.Selected = true;
|
||||||
|
SelectedOptionName = selectionNode.Name;
|
||||||
|
|
||||||
|
foreach (SectionNode otherNode in _nodes.FindAll(x => x != selectionNode))
|
||||||
|
{
|
||||||
|
otherNode.Selected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawExportResetContextMenu(selectionNode, selectionNode.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// changelog button
|
||||||
|
float y = ImGui.GetWindowHeight() - (30 * _scale);
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(255f / 255f, 255f / 255f, 255f / 255f, alpha));
|
||||||
|
ImGui.SetCursorPos(new Vector2(19 * _scale, y));
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.List.ToIconString(), new Vector2(24 * _scale, 24 * _scale)))
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.OpenChangelogWindow();
|
||||||
|
}
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGuiHelper.SetTooltip("Changelog");
|
||||||
|
|
||||||
|
// discord button
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(114f / 255f, 137f / 255f, 218f / 255f, alpha));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(114f / 255f, 137f / 255f, 218f / 255f, alpha * .85f));
|
||||||
|
ImGui.SetCursorPos(new Vector2(48 * _scale, y));
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Link.ToIconString(), new Vector2(24 * _scale, 24 * _scale)))
|
||||||
|
{
|
||||||
|
Utils.OpenUrl("https://discord.gg/xzde5qQayh");
|
||||||
|
}
|
||||||
|
ImGui.PopStyleColor(2);
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGuiHelper.SetTooltip("HSUI Discord");
|
||||||
|
|
||||||
|
// discord button
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(255f / 255f, 94f / 255f, 91f / 255f, alpha));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(255f / 255f, 94f / 255f, 91f / 255f, alpha * .85f));
|
||||||
|
ImGui.SetCursorPos(new Vector2(77 * _scale, y));
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.MugHot.ToIconString(), new Vector2(24 * _scale, 24 * _scale)))
|
||||||
|
{
|
||||||
|
Utils.OpenUrl("https://ko-fi.com/Tischel");
|
||||||
|
}
|
||||||
|
ImGui.PopStyleColor(2);
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGuiHelper.SetTooltip("Tip the developer at ko-fi.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndGroup(); // Left
|
||||||
|
|
||||||
|
|
||||||
|
didReset |= DrawResetModal();
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
ImGui.BeginGroup(); // Right
|
||||||
|
{
|
||||||
|
foreach (SectionNode selectionNode in _nodes)
|
||||||
|
{
|
||||||
|
didReset |= selectionNode.Draw(ref changed, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndGroup(); // Right
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndGroup(); // Middle section
|
||||||
|
|
||||||
|
// close button
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, alpha));
|
||||||
|
ImGui.SetCursorPos(new Vector2(ImGui.GetWindowWidth() - 28 * _scale, 5 * _scale));
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Times.ToIconString(), new Vector2(22 * _scale, 22 * _scale)))
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.CloseConfigWindow();
|
||||||
|
}
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGuiHelper.SetTooltip("Close");
|
||||||
|
|
||||||
|
// unlock button
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, alpha));
|
||||||
|
ImGui.SetCursorPos(new Vector2(ImGui.GetWindowWidth() - 60 * _scale, 5 * _scale));
|
||||||
|
string lockString = ConfigurationManager.Instance.LockHUD ? FontAwesomeIcon.Lock.ToIconString() : FontAwesomeIcon.LockOpen.ToIconString();
|
||||||
|
if (ImGui.Button(lockString, new Vector2(22 * _scale, 22 * _scale)))
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.LockHUD = !ConfigurationManager.Instance.LockHUD;
|
||||||
|
}
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGuiHelper.SetTooltip("Unlock HUD");
|
||||||
|
|
||||||
|
// hide button
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(229f / 255f, 57f / 255f, 57f / 255f, alpha));
|
||||||
|
ImGui.SetCursorPos(new Vector2(ImGui.GetWindowWidth() - 88 * _scale, 5 * _scale));
|
||||||
|
string hideString = ConfigurationManager.Instance.ShowHUD ? FontAwesomeIcon.Eye.ToIconString() : FontAwesomeIcon.EyeSlash.ToIconString();
|
||||||
|
if (ImGui.Button(hideString, new Vector2(26 * _scale, 22 * _scale)))
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.ShowHUD = !ConfigurationManager.Instance.ShowHUD;
|
||||||
|
}
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGuiHelper.SetTooltip(ConfigurationManager.Instance.ShowHUD ? "Hide HUD" : "Show HUD");
|
||||||
|
|
||||||
|
PopStyles(popColors);
|
||||||
|
|
||||||
|
if (didReset)
|
||||||
|
{
|
||||||
|
ConfigObjectResetEvent?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
NeedsSave |= changed | didReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigPageNode? GetOrAddConfig<T>() where T : PluginConfigObject
|
||||||
|
{
|
||||||
|
object[] attributes = typeof(T).GetCustomAttributes(true);
|
||||||
|
|
||||||
|
foreach (object attribute in attributes)
|
||||||
|
{
|
||||||
|
if (attribute is SectionAttribute sectionAttribute)
|
||||||
|
{
|
||||||
|
foreach (SectionNode sectionNode in _children)
|
||||||
|
{
|
||||||
|
if (sectionNode.Name == sectionAttribute.SectionName)
|
||||||
|
{
|
||||||
|
return sectionNode.GetOrAddConfig<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionNode newNode = new();
|
||||||
|
newNode.Name = sectionAttribute.SectionName;
|
||||||
|
newNode.ForceAllowExport = sectionAttribute.ForceAllowExport;
|
||||||
|
_children.Add(newNode);
|
||||||
|
|
||||||
|
return newNode.GetOrAddConfig<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type type = typeof(T);
|
||||||
|
throw new ArgumentException("The provided configuration object does not specify a section: " + type.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
using Dalamud.Logging;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Config.Profiles;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Tree
|
||||||
|
{
|
||||||
|
public class ConfigPageNode : SubSectionNode
|
||||||
|
{
|
||||||
|
private PluginConfigObject _configObject = null!;
|
||||||
|
private List<ConfigNode>? _drawList = null;
|
||||||
|
private Dictionary<string, ConfigPageNode> _nestedConfigPageNodes = null!;
|
||||||
|
|
||||||
|
public PluginConfigObject ConfigObject
|
||||||
|
{
|
||||||
|
get => _configObject;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_configObject = value;
|
||||||
|
GenerateNestedConfigPageNodes();
|
||||||
|
_drawList = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<T> GetObjects<T>()
|
||||||
|
{
|
||||||
|
return _configObject.GetObjects<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateNestedConfigPageNodes()
|
||||||
|
{
|
||||||
|
_nestedConfigPageNodes = new Dictionary<string, ConfigPageNode>();
|
||||||
|
|
||||||
|
FieldInfo[] fields = _configObject.GetType().GetFields();
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
{
|
||||||
|
foreach (var attribute in field.GetCustomAttributes(true))
|
||||||
|
{
|
||||||
|
if (attribute is not NestedConfigAttribute nestedConfigAttribute)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = field.GetValue(_configObject);
|
||||||
|
|
||||||
|
if (value is not PluginConfigObject nestedConfig)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigPageNode configPageNode = new();
|
||||||
|
configPageNode.ConfigObject = nestedConfig;
|
||||||
|
configPageNode.Name = nestedConfigAttribute.friendlyName;
|
||||||
|
|
||||||
|
if (nestedConfig.Disableable)
|
||||||
|
{
|
||||||
|
configPageNode.Name += "##" + nestedConfig.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
_nestedConfigPageNodes.Add(field.Name, configPageNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string? GetBase64String()
|
||||||
|
{
|
||||||
|
if (!AllowShare())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImportExportHelper.GenerateExportString(ConfigObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool AllowExport()
|
||||||
|
{
|
||||||
|
return ConfigObject.Exportable;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool AllowShare()
|
||||||
|
{
|
||||||
|
return ConfigObject.Shareable;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool AllowReset()
|
||||||
|
{
|
||||||
|
return ConfigObject.Resettable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Draw(ref bool changed) { return DrawWithID(ref changed); }
|
||||||
|
|
||||||
|
private bool DrawWithID(ref bool changed, string? ID = null)
|
||||||
|
{
|
||||||
|
bool didReset = false;
|
||||||
|
|
||||||
|
// Only do this stuff the first time the config page is loaded
|
||||||
|
if (_drawList is null)
|
||||||
|
{
|
||||||
|
_drawList = GenerateDrawList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_drawList is not null)
|
||||||
|
{
|
||||||
|
foreach (var fieldNode in _drawList)
|
||||||
|
{
|
||||||
|
didReset |= fieldNode.Draw(ref changed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
didReset |= DrawPortableSection();
|
||||||
|
|
||||||
|
ImGui.NewLine(); // fixes some long pages getting cut off
|
||||||
|
|
||||||
|
return didReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ConfigNode> GenerateDrawList(string? ID = null)
|
||||||
|
{
|
||||||
|
Dictionary<string, ConfigNode> fieldMap = new Dictionary<string, ConfigNode>();
|
||||||
|
|
||||||
|
FieldInfo[] fields = ConfigObject.GetType().GetFields();
|
||||||
|
foreach (var field in fields)
|
||||||
|
{
|
||||||
|
if (ConfigObject.DisableParentSettings != null && ConfigObject.DisableParentSettings.Contains(field.Name))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (object attribute in field.GetCustomAttributes(true))
|
||||||
|
{
|
||||||
|
if (attribute is NestedConfigAttribute nestedConfigAttribute && _nestedConfigPageNodes.TryGetValue(field.Name, out ConfigPageNode? node))
|
||||||
|
{
|
||||||
|
var newNodes = node.GenerateDrawList(node.Name);
|
||||||
|
foreach (var newNode in newNodes)
|
||||||
|
{
|
||||||
|
newNode.Position = nestedConfigAttribute.pos;
|
||||||
|
newNode.Separator = nestedConfigAttribute.separator;
|
||||||
|
newNode.Spacing = nestedConfigAttribute.spacing;
|
||||||
|
newNode.ParentName = nestedConfigAttribute.collapseWith;
|
||||||
|
newNode.Nest = nestedConfigAttribute.nest;
|
||||||
|
newNode.CollapsingHeader = nestedConfigAttribute.collapsingHeader;
|
||||||
|
fieldMap.Add($"{node.Name}_{newNode.Name}", newNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (attribute is OrderAttribute orderAttribute)
|
||||||
|
{
|
||||||
|
var fieldNode = new FieldNode(field, ConfigObject, ID);
|
||||||
|
fieldNode.Position = orderAttribute.pos;
|
||||||
|
fieldNode.ParentName = orderAttribute.collapseWith;
|
||||||
|
if (fieldMap.TryGetValue(field.Name, out var existing) && existing is FieldNode existingFn &&
|
||||||
|
existingFn.Field.DeclaringType != null && field.DeclaringType != null &&
|
||||||
|
existingFn.Field.DeclaringType.IsAssignableFrom(field.DeclaringType) &&
|
||||||
|
existingFn.Field.DeclaringType != field.DeclaringType)
|
||||||
|
{
|
||||||
|
fieldMap[field.Name] = fieldNode;
|
||||||
|
}
|
||||||
|
else if (!fieldMap.ContainsKey(field.Name))
|
||||||
|
{
|
||||||
|
fieldMap.Add(field.Name, fieldNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var manualDrawMethods = ConfigObject.GetType().GetMethods().Where(m => Attribute.IsDefined(m, typeof(ManualDrawAttribute), false));
|
||||||
|
foreach (var method in manualDrawMethods)
|
||||||
|
{
|
||||||
|
string id = $"ManualDraw##{method.GetHashCode()}";
|
||||||
|
fieldMap.Add(id, new ManualDrawNode(method, ConfigObject, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var configNode in fieldMap.Values)
|
||||||
|
{
|
||||||
|
if (configNode.ParentName is not null &&
|
||||||
|
fieldMap.TryGetValue(configNode.ParentName, out ConfigNode? parentNode))
|
||||||
|
{
|
||||||
|
if (!ConfigObject.Disableable &&
|
||||||
|
parentNode.Name.Equals("Enabled") &&
|
||||||
|
parentNode.ID is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentNode is FieldNode parentFieldNode)
|
||||||
|
{
|
||||||
|
parentFieldNode.CollapseControl = true;
|
||||||
|
parentFieldNode.AddChild(configNode.Position, configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldNodes = fieldMap.Values.ToList();
|
||||||
|
fieldNodes.RemoveAll(f => f.IsChild);
|
||||||
|
fieldNodes.Sort((x, y) => x.Position - y.Position);
|
||||||
|
return fieldNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DrawPortableSection()
|
||||||
|
{
|
||||||
|
if (!AllowExport())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiHelper.DrawSeparator(2, 1);
|
||||||
|
|
||||||
|
const float buttonWidth = 120;
|
||||||
|
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
|
||||||
|
float width = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X;
|
||||||
|
ImGui.SetCursorPos(new Vector2(width / 2f - buttonWidth - 5, ImGui.GetCursorPosY()));
|
||||||
|
|
||||||
|
if (ImGui.Button("Export", new Vector2(120, 24)))
|
||||||
|
{
|
||||||
|
var exportString = ImportExportHelper.GenerateExportString(ConfigObject);
|
||||||
|
ImGui.SetClipboardText(exportString);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Reset", new Vector2(120, 24)))
|
||||||
|
{
|
||||||
|
_nodeToReset = this;
|
||||||
|
_nodeToResetName = Utils.UserFriendlyConfigName(ConfigObject.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGui.EndGroup();
|
||||||
|
|
||||||
|
return DrawResetModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Save(string path)
|
||||||
|
{
|
||||||
|
string[] splits = path.Split("\\", StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
string directory = path.Replace(splits.Last(), "");
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
|
string finalPath = path + ".json";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(
|
||||||
|
finalPath,
|
||||||
|
JsonConvert.SerializeObject(
|
||||||
|
ConfigObject,
|
||||||
|
Formatting.Indented,
|
||||||
|
new JsonSerializerSettings { TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, TypeNameHandling = TypeNameHandling.Objects }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error when saving config object: " + e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Load(string path)
|
||||||
|
{
|
||||||
|
if (ConfigObject is not PluginConfigObject) { return; }
|
||||||
|
|
||||||
|
FileInfo finalPath = new(path + ".json");
|
||||||
|
|
||||||
|
// Use reflection to call the LoadForType method, this allows us to specify a type at runtime.
|
||||||
|
// While in general use this is important as the conversion from the superclass 'PluginConfigObject' to a specific subclass (e.g. 'BlackMageHudConfig') would
|
||||||
|
// be handled by Json.NET, when the plugin is reloaded with a different assembly (as is the case when using LivePluginLoader, or updating the plugin in-game)
|
||||||
|
// it fails. In order to fix this we need to specify the specific subclass, in order to do this during runtime we must use reflection to set the generic.
|
||||||
|
MethodInfo? methodInfo = ConfigObject.GetType().GetMethod("Load");
|
||||||
|
MethodInfo? function = methodInfo?.MakeGenericMethod(ConfigObject.GetType());
|
||||||
|
|
||||||
|
object?[] args = new object?[] { finalPath };
|
||||||
|
PluginConfigObject? config = (PluginConfigObject?)function?.Invoke(ConfigObject, args);
|
||||||
|
|
||||||
|
ConfigObject = config ?? ConfigObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Reset()
|
||||||
|
{
|
||||||
|
Type type = ConfigObject.GetType();
|
||||||
|
ImportData? importData = ProfilesManager.Instance?.DefaultImportData(type);
|
||||||
|
|
||||||
|
if (importData == null)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error finding default import data for type " + type.ToString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginConfigObject? config = importData.GetObject();
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error importing default import data for type " + type.ToString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigObject = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ConfigPageNode? GetOrAddConfig<T>() => this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Tree
|
||||||
|
{
|
||||||
|
public abstract class ConfigNode
|
||||||
|
{
|
||||||
|
public bool CollapseControl { get; set; }
|
||||||
|
|
||||||
|
public bool IsChild { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
public bool Nest { get; set; } = true;
|
||||||
|
|
||||||
|
public string? ParentName { get; set; }
|
||||||
|
|
||||||
|
public int Position { get; set; } = Int32.MaxValue;
|
||||||
|
|
||||||
|
public bool Separator { get; set; }
|
||||||
|
|
||||||
|
public bool Spacing { get; set; }
|
||||||
|
|
||||||
|
public bool CollapsingHeader { get; set; }
|
||||||
|
|
||||||
|
public string? ID { get; private set; }
|
||||||
|
|
||||||
|
protected PluginConfigObject ConfigObject { get; set; }
|
||||||
|
|
||||||
|
public ConfigNode(PluginConfigObject configObject, string? id, string name)
|
||||||
|
{
|
||||||
|
ConfigObject = configObject;
|
||||||
|
ID = id;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract bool Draw(ref bool changed, int depth = 0);
|
||||||
|
|
||||||
|
protected void DrawSeparatorOrSpacing()
|
||||||
|
{
|
||||||
|
if (Separator)
|
||||||
|
{
|
||||||
|
ImGuiHelper.DrawSeparator(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Spacing)
|
||||||
|
{
|
||||||
|
ImGuiHelper.DrawSpacing(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void DrawNestIndicator(int depth)
|
||||||
|
{
|
||||||
|
// This draws the L shaped symbols and padding to the left of config items collapsible under a checkbox.
|
||||||
|
// Shift cursor to the right to pad for children with depth more than 1.
|
||||||
|
// 26 is an arbitrary value I found to be around half the width of a checkbox
|
||||||
|
ImGui.SetCursorPos(ImGui.GetCursorPos() + new Vector2(26, 0) * Math.Max((depth - 1), 0));
|
||||||
|
|
||||||
|
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||||
|
{
|
||||||
|
ImGui.TextColored(new Vector4(229f / 255f, 57f / 255f, 57f / 255f, 1f), "\u2514");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.Text("\u2514");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static ConfigAttribute? GetConfigAttribute(FieldInfo field)
|
||||||
|
{
|
||||||
|
return field.GetCustomAttributes(true).Where(a => a is ConfigAttribute).FirstOrDefault() as ConfigAttribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FieldNode : ConfigNode
|
||||||
|
{
|
||||||
|
private SortedDictionary<int, ConfigNode> _childNodes;
|
||||||
|
private ConfigAttribute? _configAttribute;
|
||||||
|
private FieldInfo _mainField;
|
||||||
|
|
||||||
|
public FieldInfo Field => _mainField;
|
||||||
|
|
||||||
|
public FieldNode(FieldInfo mainField, PluginConfigObject configObject, string? id) : base(configObject, id, mainField.Name)
|
||||||
|
{
|
||||||
|
_mainField = mainField;
|
||||||
|
_childNodes = new SortedDictionary<int, ConfigNode>();
|
||||||
|
|
||||||
|
_configAttribute = GetConfigAttribute(mainField);
|
||||||
|
if (_configAttribute is not null)
|
||||||
|
{
|
||||||
|
Separator = _configAttribute.separator;
|
||||||
|
Spacing = _configAttribute.spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddChild(int position, ConfigNode field)
|
||||||
|
{
|
||||||
|
field.IsChild = true;
|
||||||
|
|
||||||
|
while (_childNodes.ContainsKey(position))
|
||||||
|
{
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_childNodes.Add(position, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Draw(ref bool changed, int depth = 0)
|
||||||
|
{
|
||||||
|
bool reset = false;
|
||||||
|
DrawSeparatorOrSpacing();
|
||||||
|
|
||||||
|
if (!Nest)
|
||||||
|
{
|
||||||
|
depth = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth > 0)
|
||||||
|
{
|
||||||
|
DrawNestIndicator(depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool collapsing = CollapsingHeader && ConfigObject.Disableable;
|
||||||
|
|
||||||
|
// Draw the ConfigAttribute
|
||||||
|
if (!collapsing)
|
||||||
|
{
|
||||||
|
DrawConfigAttribute(ref changed, _mainField);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enabled = _mainField.GetValue(ConfigObject) as bool? ?? false;
|
||||||
|
|
||||||
|
// Draw children
|
||||||
|
if (CollapseControl && Attribute.IsDefined(_mainField, typeof(CheckboxAttribute)))
|
||||||
|
{
|
||||||
|
if (collapsing)
|
||||||
|
{
|
||||||
|
if (ImGui.CollapsingHeader(ID + "##CollapsingHeader"))
|
||||||
|
{
|
||||||
|
DrawNestIndicator(depth);
|
||||||
|
DrawConfigAttribute(ref changed, _mainField);
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
reset |= DrawChildren(ref changed, depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!collapsing && enabled)
|
||||||
|
{
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
reset |= DrawChildren(ref changed, depth);
|
||||||
|
ImGui.EndGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DrawChildren(ref bool changed, int depth)
|
||||||
|
{
|
||||||
|
bool reset = false;
|
||||||
|
|
||||||
|
int childDepth = depth + 1;
|
||||||
|
foreach (ConfigNode child in _childNodes.Values)
|
||||||
|
{
|
||||||
|
if (child.Separator)
|
||||||
|
{
|
||||||
|
childDepth = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset |= child.Draw(ref changed, childDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawConfigAttribute(ref bool changed, FieldInfo field)
|
||||||
|
{
|
||||||
|
if (_configAttribute is not null)
|
||||||
|
{
|
||||||
|
changed |= _configAttribute.Draw(field, ConfigObject, ID, CollapsingHeader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ManualDrawNode : ConfigNode
|
||||||
|
{
|
||||||
|
private MethodInfo _drawMethod;
|
||||||
|
|
||||||
|
public ManualDrawNode(MethodInfo method, PluginConfigObject configObject, string? id) : base(configObject, id, id ?? "")
|
||||||
|
{
|
||||||
|
_drawMethod = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Draw(ref bool changed, int depth = 0)
|
||||||
|
{
|
||||||
|
object[] args = new object[] { false };
|
||||||
|
bool? result = (bool?)_drawMethod.Invoke(ConfigObject, args);
|
||||||
|
|
||||||
|
bool arg = (bool)args[0];
|
||||||
|
changed |= arg;
|
||||||
|
return result ?? false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
using HSUI.Helpers;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Tree
|
||||||
|
{
|
||||||
|
public abstract class Node
|
||||||
|
{
|
||||||
|
protected List<Node> _children = new List<Node>();
|
||||||
|
public IReadOnlyList<Node> Children => _children.AsReadOnly();
|
||||||
|
|
||||||
|
public void Add(Node node)
|
||||||
|
{
|
||||||
|
_children.Add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region reset
|
||||||
|
protected Node? _nodeToReset = null;
|
||||||
|
protected string? _nodeToResetName = null;
|
||||||
|
|
||||||
|
public virtual List<T> GetObjects<T>()
|
||||||
|
{
|
||||||
|
List<T> list = new List<T>();
|
||||||
|
|
||||||
|
foreach (Node node in _children)
|
||||||
|
{
|
||||||
|
list.AddRange(node.GetObjects<T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void DrawExportResetContextMenu(Node node, string name)
|
||||||
|
{
|
||||||
|
if (_nodeToReset != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool allowExport = node.AllowExport();
|
||||||
|
bool allowReset = node.AllowReset();
|
||||||
|
if (!allowExport && !allowReset)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_nodeToReset = ImGuiHelper.DrawExportResetContextMenu(node, allowExport, allowReset);
|
||||||
|
_nodeToResetName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool AllowExport()
|
||||||
|
{
|
||||||
|
foreach (Node child in _children)
|
||||||
|
{
|
||||||
|
if (child.AllowExport())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool AllowShare()
|
||||||
|
{
|
||||||
|
foreach (Node child in _children)
|
||||||
|
{
|
||||||
|
if (child.AllowShare())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool AllowReset()
|
||||||
|
{
|
||||||
|
foreach (Node child in _children)
|
||||||
|
{
|
||||||
|
if (child.AllowReset())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool DrawResetModal()
|
||||||
|
{
|
||||||
|
if (_nodeToReset == null || _nodeToResetName == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] lines = new string[] { "Are you sure you want to reset \"" + _nodeToResetName + "\"?" };
|
||||||
|
var (didReset, didClose) = ImGuiHelper.DrawConfirmationModal("Reset?", lines);
|
||||||
|
|
||||||
|
if (didReset)
|
||||||
|
{
|
||||||
|
_nodeToReset.Reset();
|
||||||
|
_nodeToReset = null;
|
||||||
|
}
|
||||||
|
else if (didClose)
|
||||||
|
{
|
||||||
|
_nodeToReset = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return didReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public virtual void Reset()
|
||||||
|
{
|
||||||
|
foreach (Node child in _children)
|
||||||
|
{
|
||||||
|
child.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region save and load
|
||||||
|
public virtual void Save(string path)
|
||||||
|
{
|
||||||
|
foreach (Node child in _children)
|
||||||
|
{
|
||||||
|
child.Save(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Load(string path)
|
||||||
|
{
|
||||||
|
foreach (Node child in _children)
|
||||||
|
{
|
||||||
|
child.Load(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region export
|
||||||
|
public virtual string? GetBase64String()
|
||||||
|
{
|
||||||
|
if (_children == null)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
string base64String = "";
|
||||||
|
|
||||||
|
foreach (Node child in _children)
|
||||||
|
{
|
||||||
|
string? childString = child.GetBase64String();
|
||||||
|
|
||||||
|
if (childString != null && childString.Length > 0)
|
||||||
|
{
|
||||||
|
base64String += "|" + childString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64String;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Tree
|
||||||
|
{
|
||||||
|
public class SectionNode : Node
|
||||||
|
{
|
||||||
|
public bool Selected;
|
||||||
|
public string Name = null!;
|
||||||
|
public bool ForceAllowExport = false;
|
||||||
|
public string? ForceSelectedTabName = null;
|
||||||
|
|
||||||
|
public SectionNode() { }
|
||||||
|
|
||||||
|
protected override bool AllowExport()
|
||||||
|
{
|
||||||
|
if (ForceAllowExport) { return true; }
|
||||||
|
|
||||||
|
return base.AllowExport();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Draw(ref bool changed, float alpha)
|
||||||
|
{
|
||||||
|
if (!Selected)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool didReset = false;
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
|
||||||
|
if (ImGui.BeginChild(
|
||||||
|
"DelvU_Settings_Tab",
|
||||||
|
new Vector2(0, -10),
|
||||||
|
false,
|
||||||
|
ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse
|
||||||
|
))
|
||||||
|
{
|
||||||
|
bool popColors = false;
|
||||||
|
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Tab, new Vector4(45f / 255f, 45f / 255f, 45f / 255f, alpha));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(45f / 255f, 45f / 255f, 45f / 255f, alpha));
|
||||||
|
popColors = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginTabBar("##Tabs", ImGuiTabBarFlags.None))
|
||||||
|
{
|
||||||
|
foreach (SubSectionNode subSectionNode in _children)
|
||||||
|
{
|
||||||
|
if (ForceSelectedTabName != null)
|
||||||
|
{
|
||||||
|
bool a = subSectionNode.Name == ForceSelectedTabName; // no idea how this works
|
||||||
|
ImGuiTabItemFlags flag = subSectionNode.Name == ForceSelectedTabName ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None;
|
||||||
|
|
||||||
|
if (!ImGui.BeginTabItem(subSectionNode.Name, ref a, flag))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!ImGui.BeginTabItem(subSectionNode.Name))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawExportResetContextMenu(subSectionNode, subSectionNode.Name);
|
||||||
|
|
||||||
|
ImGui.BeginChild("subconfig value", new Vector2(0, 0), true);
|
||||||
|
didReset |= subSectionNode.Draw(ref changed);
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabBar();
|
||||||
|
ForceSelectedTabName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (popColors)
|
||||||
|
{
|
||||||
|
ImGui.PopStyleColor(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
didReset |= DrawResetModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
return didReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Save(string path)
|
||||||
|
{
|
||||||
|
foreach (SubSectionNode child in _children)
|
||||||
|
{
|
||||||
|
child.Save(Path.Combine(path, Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Load(string path)
|
||||||
|
{
|
||||||
|
foreach (SubSectionNode child in _children)
|
||||||
|
{
|
||||||
|
child.Load(Path.Combine(path, Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigPageNode? GetOrAddConfig<T>() where T : PluginConfigObject
|
||||||
|
{
|
||||||
|
object[] attributes = typeof(T).GetCustomAttributes(true);
|
||||||
|
|
||||||
|
foreach (object attribute in attributes)
|
||||||
|
{
|
||||||
|
if (attribute is SubSectionAttribute subSectionAttribute)
|
||||||
|
{
|
||||||
|
foreach (SubSectionNode subSectionNode in _children)
|
||||||
|
{
|
||||||
|
if (subSectionNode.Name == subSectionAttribute.SubSectionName)
|
||||||
|
{
|
||||||
|
return subSectionNode.GetOrAddConfig<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subSectionAttribute.Depth == 0)
|
||||||
|
{
|
||||||
|
NestedSubSectionNode newNode = new();
|
||||||
|
newNode.Name = subSectionAttribute.SubSectionName;
|
||||||
|
newNode.Depth = 0;
|
||||||
|
_children.Add(newNode);
|
||||||
|
|
||||||
|
return newNode.GetOrAddConfig<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type type = typeof(T);
|
||||||
|
throw new ArgumentException("The provided configuration object does not specify a sub-section: " + type.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Tree
|
||||||
|
{
|
||||||
|
public abstract class SubSectionNode : Node
|
||||||
|
{
|
||||||
|
public string Name = null!;
|
||||||
|
public int Depth;
|
||||||
|
public string? ForceSelectedTabName = null;
|
||||||
|
|
||||||
|
public abstract bool Draw(ref bool changed);
|
||||||
|
|
||||||
|
public abstract ConfigPageNode? GetOrAddConfig<T>() where T : PluginConfigObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NestedSubSectionNode : SubSectionNode
|
||||||
|
{
|
||||||
|
public NestedSubSectionNode() { }
|
||||||
|
|
||||||
|
public override bool Draw(ref bool changed)
|
||||||
|
{
|
||||||
|
bool didReset = false;
|
||||||
|
|
||||||
|
if (_children.Count > 1)
|
||||||
|
{
|
||||||
|
ImGui.BeginChild(
|
||||||
|
"DelvUI_Tabs_" + Depth,
|
||||||
|
new Vector2(0, ImGui.GetWindowHeight() - 22),
|
||||||
|
false,
|
||||||
|
ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse
|
||||||
|
); // Leave room for 1 line below us
|
||||||
|
|
||||||
|
if (ImGui.BeginTabBar("##tabs" + Depth, ImGuiTabBarFlags.None))
|
||||||
|
{
|
||||||
|
didReset |= DrawSubConfig(ref changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabBar();
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.BeginChild("item" + Depth + " view", new Vector2(0, ImGui.GetWindowHeight() - 20)); // Leave room for 1 line below us
|
||||||
|
|
||||||
|
didReset |= DrawSubConfig(ref changed);
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
return didReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DrawSubConfig(ref bool changed)
|
||||||
|
{
|
||||||
|
bool didReset = false;
|
||||||
|
|
||||||
|
foreach (SubSectionNode subSectionNode in _children)
|
||||||
|
{
|
||||||
|
if (subSectionNode is NestedSubSectionNode)
|
||||||
|
{
|
||||||
|
if (ForceSelectedTabName != null)
|
||||||
|
{
|
||||||
|
bool a = subSectionNode.Name == ForceSelectedTabName; // no idea how this works
|
||||||
|
ImGuiTabItemFlags flag = subSectionNode.Name == ForceSelectedTabName ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None;
|
||||||
|
|
||||||
|
if (!ImGui.BeginTabItem(subSectionNode.Name, ref a, flag))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!ImGui.BeginTabItem(subSectionNode.Name))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawExportResetContextMenu(subSectionNode, subSectionNode.Name);
|
||||||
|
|
||||||
|
ImGui.BeginChild("subconfig" + Depth + " value", new Vector2(0, ImGui.GetWindowHeight()));
|
||||||
|
didReset |= subSectionNode.Draw(ref changed);
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
didReset |= subSectionNode.Draw(ref changed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ForceSelectedTabName = null;
|
||||||
|
|
||||||
|
didReset |= DrawResetModal();
|
||||||
|
|
||||||
|
return didReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Save(string path)
|
||||||
|
{
|
||||||
|
foreach (SubSectionNode child in _children)
|
||||||
|
{
|
||||||
|
child.Save(Path.Combine(path, Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Load(string path)
|
||||||
|
{
|
||||||
|
foreach (SubSectionNode child in _children)
|
||||||
|
{
|
||||||
|
child.Load(Path.Combine(path, Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ConfigPageNode? GetOrAddConfig<T>()
|
||||||
|
{
|
||||||
|
var type = typeof(T);
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
object[] attributes = type.GetCustomAttributes(true);
|
||||||
|
|
||||||
|
foreach (object attribute in attributes)
|
||||||
|
{
|
||||||
|
if (attribute is SubSectionAttribute subSectionAttribute)
|
||||||
|
{
|
||||||
|
if (subSectionAttribute.Depth != Depth + 1)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (SubSectionNode subSectionNode in _children)
|
||||||
|
{
|
||||||
|
if (subSectionNode.Name == subSectionAttribute.SubSectionName)
|
||||||
|
{
|
||||||
|
return subSectionNode.GetOrAddConfig<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NestedSubSectionNode nestedSubSectionNode = new();
|
||||||
|
nestedSubSectionNode.Name = subSectionAttribute.SubSectionName;
|
||||||
|
nestedSubSectionNode.Depth = Depth + 1;
|
||||||
|
_children.Add(nestedSubSectionNode);
|
||||||
|
|
||||||
|
return nestedSubSectionNode.GetOrAddConfig<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (SubSectionNode subSectionNode in _children)
|
||||||
|
{
|
||||||
|
if (subSectionNode.Name == type.FullName && subSectionNode is ConfigPageNode node)
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigPageNode configPageNode = new();
|
||||||
|
configPageNode.ConfigObject = ConfigurationManager.GetDefaultConfigObjectForType(type);
|
||||||
|
configPageNode.Name = type.FullName!;
|
||||||
|
_children.Add(configPageNode);
|
||||||
|
|
||||||
|
return configPageNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Windows
|
||||||
|
{
|
||||||
|
public class ChangelogWindow : Window
|
||||||
|
{
|
||||||
|
public string Changelog { get; set; }
|
||||||
|
private bool _needsToSetSize = true;
|
||||||
|
|
||||||
|
public bool AutoClose = false;
|
||||||
|
private double _openTime = -1;
|
||||||
|
|
||||||
|
private bool _popColors = false;
|
||||||
|
|
||||||
|
public ChangelogWindow(string name, string changelog) : base(name)
|
||||||
|
{
|
||||||
|
Changelog = changelog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PreDraw()
|
||||||
|
{
|
||||||
|
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(10f / 255f, 10f / 255f, 10f / 255f, 0.95f));
|
||||||
|
_popColors = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_needsToSetSize)
|
||||||
|
{
|
||||||
|
float height = ImGui.CalcTextSize(Changelog).Y + 100;
|
||||||
|
ImGui.SetNextWindowSize(new Vector2(500, Math.Min(height, 500)), ImGuiCond.FirstUseEver);
|
||||||
|
_needsToSetSize = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
Vector2 size = ImGui.GetWindowSize();
|
||||||
|
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + size.X - 24);
|
||||||
|
ImGui.TextWrapped(Changelog);
|
||||||
|
|
||||||
|
if (AutoClose &&
|
||||||
|
_openTime > 0 &&
|
||||||
|
ImGui.GetTime() - _openTime > 10)
|
||||||
|
{
|
||||||
|
IsOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PostDraw()
|
||||||
|
{
|
||||||
|
if (_popColors)
|
||||||
|
{
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
_popColors = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnOpen()
|
||||||
|
{
|
||||||
|
_openTime = ImGui.GetTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnClose()
|
||||||
|
{
|
||||||
|
if (AutoClose && InputsHelper.Instance != null)
|
||||||
|
{
|
||||||
|
AutoClose = false;
|
||||||
|
Plugin.LoadTime = ImGui.GetTime() - InputsHelper.InitializationDelay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Windows
|
||||||
|
{
|
||||||
|
public class GridWindow : Window
|
||||||
|
{
|
||||||
|
private bool _popColors = false;
|
||||||
|
public GridWindow(string name) : base(name)
|
||||||
|
{
|
||||||
|
Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoScrollWithMouse;
|
||||||
|
Size = new Vector2(300, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnClose()
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.LockHUD = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PreDraw()
|
||||||
|
{
|
||||||
|
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(10f / 255f, 10f / 255f, 10f / 255f, 0.95f));
|
||||||
|
_popColors = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetNextWindowFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
var configManager = ConfigurationManager.Instance;
|
||||||
|
var node = configManager.GetConfigPageNode<GridConfig>();
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PushItemWidth(150);
|
||||||
|
bool changed = false;
|
||||||
|
node.Draw(ref changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PostDraw()
|
||||||
|
{
|
||||||
|
if (_popColors)
|
||||||
|
{
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
_popColors = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using HSUI.Config.Tree;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Config.Windows
|
||||||
|
{
|
||||||
|
public class MainConfigWindow : Window
|
||||||
|
{
|
||||||
|
public BaseNode? node { get; set; }
|
||||||
|
public Action? CloseAction;
|
||||||
|
|
||||||
|
private float _alpha = 1f;
|
||||||
|
private Vector2 _lastWindowPos = Vector2.Zero;
|
||||||
|
private Vector2 _size = new Vector2(1050, 750);
|
||||||
|
|
||||||
|
private bool _popColors = false;
|
||||||
|
|
||||||
|
public MainConfigWindow(string name) : base(name)
|
||||||
|
{
|
||||||
|
Flags = ImGuiWindowFlags.NoTitleBar;
|
||||||
|
Size = _size;
|
||||||
|
SizeCondition = ImGuiCond.FirstUseEver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnClose()
|
||||||
|
{
|
||||||
|
CloseAction?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckWindowFocus()
|
||||||
|
{
|
||||||
|
Vector2 mousePos = ImGui.GetMousePos();
|
||||||
|
Vector2 endPos = _lastWindowPos + _size;
|
||||||
|
|
||||||
|
return mousePos.X >= _lastWindowPos.X && mousePos.X <= endPos.X &&
|
||||||
|
mousePos.Y >= _lastWindowPos.Y && mousePos.Y <= endPos.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PreDraw()
|
||||||
|
{
|
||||||
|
_alpha = 1;
|
||||||
|
|
||||||
|
HUDOptionsConfig? config = ConfigurationManager.Instance.GetConfigObject<HUDOptionsConfig>();
|
||||||
|
if (config?.DimConfigWindow == true)
|
||||||
|
{
|
||||||
|
_alpha = CheckWindowFocus() ? 1 : 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ConfigurationManager.Instance.OverrideDalamudStyle)
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(0f / 255f, 0f / 255f, 0f / 255f, _alpha));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.BorderShadow, new Vector4(0f / 255f, 0f / 255f, 0f / 255f, _alpha));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(20f / 255f, 21f / 255f, 20f / 255f, _alpha));
|
||||||
|
_popColors = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 1);
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
_lastWindowPos = ImGui.GetWindowPos();
|
||||||
|
|
||||||
|
if (_popColors)
|
||||||
|
{
|
||||||
|
ImGui.PopStyleColor(3);
|
||||||
|
_popColors = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopStyleVar(2);
|
||||||
|
|
||||||
|
node?.Draw(_alpha);
|
||||||
|
|
||||||
|
_size = ImGui.GetWindowSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace HSUI.Enums
|
||||||
|
{
|
||||||
|
public enum BarTextureDrawMode
|
||||||
|
{
|
||||||
|
Stretch = 0,
|
||||||
|
RepeatHorizontal = 1,
|
||||||
|
RepeatVertical = 2,
|
||||||
|
Repeat = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace HSUI.Enums
|
||||||
|
{
|
||||||
|
public enum BlendMode
|
||||||
|
{
|
||||||
|
LAB,
|
||||||
|
LChab,
|
||||||
|
XYZ,
|
||||||
|
RGB,
|
||||||
|
LChuv,
|
||||||
|
Luv,
|
||||||
|
Jzazbz,
|
||||||
|
JzCzhz
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace HSUI.Enums
|
||||||
|
{
|
||||||
|
public enum DamageType
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Slashing = 1,
|
||||||
|
Piercing = 2,
|
||||||
|
Blunt = 3,
|
||||||
|
Magic = 5,
|
||||||
|
Darkness = 6,
|
||||||
|
Physical = 7,
|
||||||
|
LimitBreak = 8
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace HSUI.Enums
|
||||||
|
{
|
||||||
|
public enum DrawAnchor
|
||||||
|
{
|
||||||
|
Center = 0,
|
||||||
|
Left = 1,
|
||||||
|
Right = 2,
|
||||||
|
Top = 3,
|
||||||
|
TopLeft = 4,
|
||||||
|
TopRight = 5,
|
||||||
|
Bottom = 6,
|
||||||
|
BottomLeft = 7,
|
||||||
|
BottomRight = 8
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HSUI.Enums;
|
||||||
|
|
||||||
|
// Hashes from HUD layout (AddonNameHash). Sources: HSUI, HUDManager plugin
|
||||||
|
public enum ElementKind : uint
|
||||||
|
{
|
||||||
|
Hotbar1 = 0xC48D3605, // _ActionBar_a
|
||||||
|
Hotbar2 = 0xFB7B6E1E, // _ActionBar01_a
|
||||||
|
Hotbar3 = 0xF93DD047, // _ActionBar02_a
|
||||||
|
Hotbar4 = 0xF8FFBA70, // _ActionBar03_a
|
||||||
|
Hotbar5 = 0xFDB0ACF5, // _ActionBar04_a
|
||||||
|
Hotbar6 = 0xFC72C6C2, // _ActionBar05_a
|
||||||
|
Hotbar7 = 0xFE34789B, // _ActionBar06_a
|
||||||
|
Hotbar8 = 0xFFF612AC, // _ActionBar07_a
|
||||||
|
Hotbar9 = 0xF4AA5591, // _ActionBar08_a
|
||||||
|
Hotbar10 = 0xF5683FA6, // _ActionBar09_a
|
||||||
|
PetHotbar = 0xD8D188FF, // _ActionBarEx_a
|
||||||
|
CrossHotbar = 0xBA81E8D1, // _ActionCross_a
|
||||||
|
LeftWCrossHotbar = 0x6665735D, // _ActionDoubleCrossL_a
|
||||||
|
RightWCrossHotbar = 0x70DDFD27, // _ActionDoubleCrossR_a
|
||||||
|
ParameterBar = 0x9818E23E, // _ParameterWidget - player HP/MP
|
||||||
|
TargetBar = 0x9139E9FD, // target info
|
||||||
|
FocusTargetBar = 0xC28E7D1F,
|
||||||
|
ProgressBar = 0xECB1E391, // cast bar
|
||||||
|
ExperienceBar = 0x21E32D4E,
|
||||||
|
LimitGauge = 0xC7A40A8A,
|
||||||
|
StatusEffects = 0x4A5B6A16, // player buffs/debuffs
|
||||||
|
StatusInfoEnhancements = 0x1F320624,
|
||||||
|
StatusInfoEnfeeblements = 0x1E7A7A83,
|
||||||
|
TargetInfoHp = 0xBD411F17,
|
||||||
|
TargetInfoProgressBar = 0xCB5BCCCF,
|
||||||
|
TargetInfoStatus = 0x070F4E6B,
|
||||||
|
PartyList = 0x3D3B34F9,
|
||||||
|
EnemyList = 0xB8C30AC5,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ElementKindHelper
|
||||||
|
{
|
||||||
|
public static ElementKind ElementKindByHotBarId(int hotBarId)
|
||||||
|
{
|
||||||
|
return hotBarId switch
|
||||||
|
{
|
||||||
|
0 => ElementKind.Hotbar1,
|
||||||
|
1 => ElementKind.Hotbar2,
|
||||||
|
2 => ElementKind.Hotbar3,
|
||||||
|
3 => ElementKind.Hotbar4,
|
||||||
|
4 => ElementKind.Hotbar5,
|
||||||
|
5 => ElementKind.Hotbar6,
|
||||||
|
6 => ElementKind.Hotbar7,
|
||||||
|
7 => ElementKind.Hotbar8,
|
||||||
|
8 => ElementKind.Hotbar9,
|
||||||
|
9 => ElementKind.Hotbar10,
|
||||||
|
10 => ElementKind.PetHotbar,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(hotBarId))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace HSUI.Enums
|
||||||
|
{
|
||||||
|
public enum StrataLevel
|
||||||
|
{
|
||||||
|
LOWEST = 0,
|
||||||
|
LOW,
|
||||||
|
MID_LOW,
|
||||||
|
MID,
|
||||||
|
MID_HIGH,
|
||||||
|
HIGH,
|
||||||
|
HIGHEST,
|
||||||
|
}
|
||||||
|
}
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
|
using static System.Globalization.CultureInfo;
|
||||||
|
|
||||||
|
namespace HSUI
|
||||||
|
{
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static string Abbreviate(this string str)
|
||||||
|
{
|
||||||
|
if (str.Length > 20) {
|
||||||
|
string[] splits = str.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
for (int i = 0; i < splits.Length - 1; i++)
|
||||||
|
{
|
||||||
|
splits[i] = splits[i][0].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(". ", splits).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FirstName(this string str)
|
||||||
|
{
|
||||||
|
string[] splits = str.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
return splits.Length > 0 ? splits[0] : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string LastName(this string str)
|
||||||
|
{
|
||||||
|
string[] splits = str.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
return splits.Length > 1 ? splits[^1] : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Initials(this string str)
|
||||||
|
{
|
||||||
|
var initials = "";
|
||||||
|
var firstName = FirstName(str);
|
||||||
|
var lastName = LastName(str);
|
||||||
|
|
||||||
|
if (firstName.Length > 0)
|
||||||
|
{
|
||||||
|
initials = firstName[0] + ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastName.Length > 0)
|
||||||
|
{
|
||||||
|
initials += " " + lastName[0] + ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
return initials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Truncated(this string str, int length = 0)
|
||||||
|
{
|
||||||
|
return length > 0 ? str.Substring(0, Math.Min(str.Length, length)) : str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector4 AdjustColor(this Vector4 vec, float correctionFactor)
|
||||||
|
{
|
||||||
|
float red = vec.X;
|
||||||
|
float green = vec.Y;
|
||||||
|
float blue = vec.Z;
|
||||||
|
|
||||||
|
if (correctionFactor < 0)
|
||||||
|
{
|
||||||
|
correctionFactor = 1 + correctionFactor;
|
||||||
|
red *= correctionFactor;
|
||||||
|
green *= correctionFactor;
|
||||||
|
blue *= correctionFactor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
red = (1 - red) * correctionFactor + red;
|
||||||
|
green = (1 - green) * correctionFactor + green;
|
||||||
|
blue = (1 - blue) * correctionFactor + blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Vector4(red, green, blue, vec.W);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector4 WithNewAlpha(this Vector4 vec, float alpha)
|
||||||
|
{
|
||||||
|
return new Vector4(vec.X, vec.Y, vec.Z, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string KiloFormat(this uint num)
|
||||||
|
{
|
||||||
|
return num switch
|
||||||
|
{
|
||||||
|
>= 100000000 => (num / 1000000.0).ToString("#,0M", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
>= 1000000 => (num / 1000000.0).ToString("0.0", ConfigurationManager.Instance.ActiveCultreInfo) + "M",
|
||||||
|
>= 100000 => (num / 1000.0).ToString("#,0K", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
>= 10000 => (num / 1000.0).ToString("0.0", ConfigurationManager.Instance.ActiveCultreInfo) + "K",
|
||||||
|
_ => num.ToString("#,0", ConfigurationManager.Instance.ActiveCultreInfo)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsHorizontal(this BarDirection direction)
|
||||||
|
{
|
||||||
|
return direction == BarDirection.Right || direction == BarDirection.Left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsInverted(this BarDirection direction)
|
||||||
|
{
|
||||||
|
return direction == BarDirection.Left || direction == BarDirection.Up;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Draw(this BarHud[] bars, Vector2 origin)
|
||||||
|
{
|
||||||
|
foreach (BarHud bar in bars)
|
||||||
|
{
|
||||||
|
bar.Draw(origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CheckForUpperCase(this string str)
|
||||||
|
{
|
||||||
|
var culture = CurrentCulture.TextInfo;
|
||||||
|
if (!string.IsNullOrEmpty(str) && char.IsLetter(str[0]) && !char.IsUpper(str[0]))
|
||||||
|
{
|
||||||
|
str = culture.ToTitleCase(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Repeat(this string s, int n)
|
||||||
|
=> new StringBuilder(s.Length * n).Insert(0, s, n).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
<Project Sdk="Dalamud.NET.Sdk/14.0.1">
|
||||||
|
<PropertyGroup Label="Target">
|
||||||
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<TargetFramework>net10.0-windows</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<Platforms>x64</Platforms>
|
||||||
|
<Configurations>Debug;Release</Configurations>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyName>HSUI</AssemblyName>
|
||||||
|
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||||
|
<FileVersion>1.0.0.0</FileVersion>
|
||||||
|
<InformationalVersion>1.0.0.0</InformationalVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RestorePackagesWithLockFile>false</RestorePackagesWithLockFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<DalamudVersion>dev</DalamudVersion>
|
||||||
|
<DalamudLocal>../repos/Dalamud-master/</DalamudLocal>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblySearchPaths>
|
||||||
|
$(AssemblySearchPaths);
|
||||||
|
$(DalamudLocal);
|
||||||
|
$(DalamudLibPath);
|
||||||
|
</AssemblySearchPaths>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\repos\KamiToolKit-master\KamiToolKit.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="$(ProjectDir)HSUI.json" CopyToOutputDirectory="Always" />
|
||||||
|
<Content Include="$(ProjectDir)changelog.md" CopyToOutputDirectory="Always" />
|
||||||
|
<Content Include="$(ProjectDir)Media\**" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Colourful" Version="3.2.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"Author": "Knack117",
|
||||||
|
"Name": "HSUI",
|
||||||
|
"InternalName": "HSUI",
|
||||||
|
"AssemblyVersion": "1.0.0.0",
|
||||||
|
"Description": "HSUI provides a highly configurable HUD replacement for FFXIV, recreated from DelvUI using KamiToolKit, FFXIVClientStructs, and Dalamud. Features unit frames, castbars, job gauges, nameplates, party frames, status effects, enemy list, configurable hotbars with drag-and-drop, and profiles.",
|
||||||
|
"ApplicableVersion": "any",
|
||||||
|
"RepoUrl": "https://github.com/Knack117/HSUI",
|
||||||
|
"Tags": ["UI", "HUD", "Unit Frames", "Nameplates", "Party Frames", "Hotbars"],
|
||||||
|
"Punchline": "A modern HUD replacement built for customization."
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Hit test for HSUI hotbars. Used to determine if the cursor is over an HSUI hotbar
|
||||||
|
/// so we only intercept drag-drop releases when the user is actually dropping on our bars.
|
||||||
|
/// </summary>
|
||||||
|
public static class ActionBarsHitTestHelper
|
||||||
|
{
|
||||||
|
/// <summary>Returns true if the mouse cursor is over any visible HSUI hotbar.</summary>
|
||||||
|
public static bool IsMouseOverAnyHSUIHotbar()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var hotbarsConfig = ConfigurationManager.Instance?.GetConfigObject<HotbarsConfig>();
|
||||||
|
if (hotbarsConfig == null || !hotbarsConfig.Enabled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var hudOptions = ConfigurationManager.Instance?.GetConfigObject<HUDOptionsConfig>();
|
||||||
|
Vector2 origin = ImGui.GetMainViewport().Size / 2f;
|
||||||
|
if (hudOptions != null && hudOptions.UseGlobalHudShift)
|
||||||
|
origin += hudOptions.HudOffset;
|
||||||
|
|
||||||
|
Vector2 mousePos = ImGui.GetMousePos();
|
||||||
|
|
||||||
|
HotbarBarConfig?[] barConfigs = new HotbarBarConfig?[]
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance?.GetConfigObject<Hotbar1BarConfig>(),
|
||||||
|
ConfigurationManager.Instance?.GetConfigObject<Hotbar2BarConfig>(),
|
||||||
|
ConfigurationManager.Instance?.GetConfigObject<Hotbar3BarConfig>(),
|
||||||
|
ConfigurationManager.Instance?.GetConfigObject<Hotbar4BarConfig>(),
|
||||||
|
ConfigurationManager.Instance?.GetConfigObject<Hotbar5BarConfig>(),
|
||||||
|
ConfigurationManager.Instance?.GetConfigObject<Hotbar6BarConfig>(),
|
||||||
|
ConfigurationManager.Instance?.GetConfigObject<Hotbar7BarConfig>(),
|
||||||
|
ConfigurationManager.Instance?.GetConfigObject<Hotbar8BarConfig>(),
|
||||||
|
ConfigurationManager.Instance?.GetConfigObject<Hotbar9BarConfig>(),
|
||||||
|
ConfigurationManager.Instance?.GetConfigObject<Hotbar10BarConfig>()
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var barConfig in barConfigs)
|
||||||
|
{
|
||||||
|
if (barConfig == null) continue;
|
||||||
|
|
||||||
|
Vector2 barSize = ComputeBarSize(barConfig);
|
||||||
|
Vector2 topLeft = Utils.GetAnchoredPosition(origin + barConfig.Position, barSize, barConfig.Anchor);
|
||||||
|
|
||||||
|
if (mousePos.X >= topLeft.X && mousePos.X < topLeft.X + barSize.X &&
|
||||||
|
mousePos.Y >= topLeft.Y && mousePos.Y < topLeft.Y + barSize.Y)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector2 ComputeBarSize(HotbarBarConfig config)
|
||||||
|
{
|
||||||
|
var (cols, _) = config.GetLayoutGrid();
|
||||||
|
int effectiveCols = Math.Min(cols, config.SlotCount);
|
||||||
|
int effectiveRows = (config.SlotCount + effectiveCols - 1) / effectiveCols;
|
||||||
|
float w = effectiveCols * config.SlotSize.X + (effectiveCols - 1) * config.SlotPadding;
|
||||||
|
float h = effectiveRows * config.SlotSize.Y + (effectiveRows - 1) * config.SlotPadding;
|
||||||
|
return new Vector2(w, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,457 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
using KamiToolKit.Controllers;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public sealed class ActionBarsManager : IDisposable
|
||||||
|
{
|
||||||
|
public static ActionBarsManager Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
private AddonController? _addonController;
|
||||||
|
|
||||||
|
private ActionBarsManager()
|
||||||
|
{
|
||||||
|
_addonController = new AddonController("_ActionBar");
|
||||||
|
_addonController.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
Instance = new ActionBarsManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_addonController?.Disable();
|
||||||
|
_addonController = null;
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Slot data for ImGui drawing. Updated each call from game state.
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct SlotInfo
|
||||||
|
{
|
||||||
|
public uint IconId { get; }
|
||||||
|
public bool IsEmpty { get; }
|
||||||
|
public bool IsUsable { get; }
|
||||||
|
public int CooldownPercent { get; }
|
||||||
|
public int CooldownSecondsLeft { get; }
|
||||||
|
public uint ActionId { get; }
|
||||||
|
public RaptureHotbarModule.HotbarSlotType SlotType { get; }
|
||||||
|
/// <summary>Keybind hint from game (user's keybind settings). Empty if unavailable.</summary>
|
||||||
|
public string KeybindHint { get; }
|
||||||
|
|
||||||
|
public SlotInfo(uint iconId, bool isEmpty, bool usable, int cooldownPct, int cooldownSecs, uint actionId = 0, RaptureHotbarModule.HotbarSlotType slotType = 0, string keybindHint = "")
|
||||||
|
{
|
||||||
|
IconId = iconId;
|
||||||
|
IsEmpty = isEmpty;
|
||||||
|
IsUsable = usable;
|
||||||
|
CooldownPercent = cooldownPct;
|
||||||
|
CooldownSecondsLeft = cooldownSecs;
|
||||||
|
ActionId = actionId;
|
||||||
|
SlotType = slotType;
|
||||||
|
KeybindHint = keybindHint ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads keybind hint from HotbarSlot. Uses _keybindHint (slot display) then _popUpKeybindHint (trimmed) as fallback.
|
||||||
|
/// Offsets match RaptureHotbarModule.HotbarSlot: _keybindHint 0xA8, _popUpKeybindHint 0x88.
|
||||||
|
/// </summary>
|
||||||
|
private static unsafe string ReadKeybindHintFromSlot(RaptureHotbarModule.HotbarSlot* slot)
|
||||||
|
{
|
||||||
|
if (slot == null) return "";
|
||||||
|
var sb = (byte*)slot;
|
||||||
|
string? h = Marshal.PtrToStringUTF8((IntPtr)(sb + 0xA8));
|
||||||
|
if (!string.IsNullOrWhiteSpace(h))
|
||||||
|
return h.Trim();
|
||||||
|
string? p = Marshal.PtrToStringUTF8((IntPtr)(sb + 0x88));
|
||||||
|
if (string.IsNullOrWhiteSpace(p)) return "";
|
||||||
|
p = p!.Trim();
|
||||||
|
if (p.StartsWith(" [", StringComparison.Ordinal) && p.EndsWith("]", StringComparison.Ordinal))
|
||||||
|
p = p[2..^1].Trim();
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads hotbar slot data from RaptureHotbarModule. Returns up to slotCount slots.
|
||||||
|
/// hotbarIndex 1-10 maps to StandardHotbars 0-9.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe List<SlotInfo> GetSlotData(int hotbarIndex, int slotCount)
|
||||||
|
{
|
||||||
|
var list = new List<SlotInfo>(slotCount);
|
||||||
|
var module = RaptureHotbarModule.Instance();
|
||||||
|
if (module == null || !module->ModuleReady)
|
||||||
|
return list;
|
||||||
|
|
||||||
|
var hotbars = module->StandardHotbars;
|
||||||
|
int barIdx = Math.Clamp(hotbarIndex, 1, 10) - 1;
|
||||||
|
int count = Math.Clamp(slotCount, 1, 12);
|
||||||
|
|
||||||
|
ref var bar = ref hotbars[barIdx];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var slot = bar.GetHotbarSlot((uint)i);
|
||||||
|
string keybind = ReadKeybindHintFromSlot(slot);
|
||||||
|
|
||||||
|
if (slot == null)
|
||||||
|
{
|
||||||
|
list.Add(new SlotInfo(0, true, false, 0, 0, 0, 0, keybind));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slot->IsEmpty)
|
||||||
|
{
|
||||||
|
list.Add(new SlotInfo(0, true, false, 0, 0, 0, 0, keybind));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secsLeft = 0;
|
||||||
|
int pct = slot->GetSlotActionCooldownPercentage(&secsLeft, 0);
|
||||||
|
bool usable = slot->IsSlotUsable(slot->ApparentSlotType, slot->ApparentActionId);
|
||||||
|
uint iconId = slot->IconId;
|
||||||
|
uint actionId = slot->ApparentActionId;
|
||||||
|
var slotType = slot->ApparentSlotType;
|
||||||
|
|
||||||
|
list.Add(new SlotInfo(iconId, false, usable, pct, secsLeft, actionId, slotType, keybind));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the default game keybind label for a hotbar slot (Hotbar 1: 1,2,...,0,-,=; Bar 2: Ctrl+1..12; etc.).
|
||||||
|
/// hotbarIndex 1–10, slotIndex 0–11. Used to mirror the default hotbar keybind display.
|
||||||
|
/// </summary>
|
||||||
|
public static string GetDefaultKeybindLabel(int hotbarIndex, int slotIndex)
|
||||||
|
{
|
||||||
|
int s = Math.Clamp(slotIndex, 0, 11);
|
||||||
|
string k = s switch
|
||||||
|
{
|
||||||
|
0 => "1", 1 => "2", 2 => "3", 3 => "4", 4 => "5", 5 => "6",
|
||||||
|
6 => "7", 7 => "8", 8 => "9", 9 => "0", 10 => "-", 11 => "=",
|
||||||
|
_ => (s + 1).ToString()
|
||||||
|
};
|
||||||
|
int b = Math.Clamp(hotbarIndex, 1, 10);
|
||||||
|
return b switch
|
||||||
|
{
|
||||||
|
1 => k,
|
||||||
|
2 => "Ctrl+" + k,
|
||||||
|
3 => "Shift+" + k,
|
||||||
|
4 => "Alt+" + k,
|
||||||
|
_ => $"{b}-{s + 1}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execute a hotbar slot. hotbarIndex 1-10, slotIndex 0-based.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe bool ExecuteSlot(int hotbarIndex, int slotIndex)
|
||||||
|
{
|
||||||
|
var module = RaptureHotbarModule.Instance();
|
||||||
|
if (module == null || !module->ModuleReady)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int barIdx = Math.Clamp(hotbarIndex, 1, 10) - 1;
|
||||||
|
int slot = Math.Clamp(slotIndex, 0, 11);
|
||||||
|
module->ExecuteSlotById((uint)barIdx, (uint)slot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swap two hotbar slots. Supports cross-hotbar swap when hotbarA != hotbarB.
|
||||||
|
/// Uses CommandType/CommandId from the slot (not Apparent*) so items and macros swap correctly.
|
||||||
|
/// hotbarIndex 1-10, slot indices 0-based.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="debugLog">When non-null, receives diagnostic info for logging.</param>
|
||||||
|
public unsafe bool SwapSlots(int hotbarA, int slotA, int hotbarB, int slotB, Action<string>? debugLog = null)
|
||||||
|
{
|
||||||
|
var module = RaptureHotbarModule.Instance();
|
||||||
|
if (module == null || !module->ModuleReady)
|
||||||
|
{
|
||||||
|
debugLog?.Invoke($"[SwapSlots] FAIL: module null or not ready");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int barA = Math.Clamp(hotbarA, 1, 10);
|
||||||
|
int barB = Math.Clamp(hotbarB, 1, 10);
|
||||||
|
int a = Math.Clamp(slotA, 0, 11);
|
||||||
|
int b = Math.Clamp(slotB, 0, 11);
|
||||||
|
if (barA == barB && a == b)
|
||||||
|
{
|
||||||
|
debugLog?.Invoke($"[SwapSlots] NOOP: same slot bar={barA} slot={a}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var slotPtrA = module->GetSlotById((uint)(barA - 1), (uint)a);
|
||||||
|
var slotPtrB = module->GetSlotById((uint)(barB - 1), (uint)b);
|
||||||
|
if (slotPtrA == null || slotPtrB == null)
|
||||||
|
{
|
||||||
|
debugLog?.Invoke($"[SwapSlots] FAIL: slotPtr null A={slotPtrA != null} B={slotPtrB != null}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeA = slotPtrA->IsEmpty ? RaptureHotbarModule.HotbarSlotType.Empty : slotPtrA->CommandType;
|
||||||
|
var idA = slotPtrA->IsEmpty ? 0u : slotPtrA->CommandId;
|
||||||
|
var typeB = slotPtrB->IsEmpty ? RaptureHotbarModule.HotbarSlotType.Empty : slotPtrB->CommandType;
|
||||||
|
var idB = slotPtrB->IsEmpty ? 0u : slotPtrB->CommandId;
|
||||||
|
|
||||||
|
debugLog?.Invoke($"[SwapSlots] BEFORE: A(bar{barA} slot{a}) type={typeA} id={idA} | B(bar{barB} slot{b}) type={typeB} id={idB}");
|
||||||
|
|
||||||
|
// Update live slots first (like game drag-drop), then persist
|
||||||
|
slotPtrA->Set(typeB, idB);
|
||||||
|
slotPtrA->LoadIconId();
|
||||||
|
if (typeB == RaptureHotbarModule.HotbarSlotType.Item)
|
||||||
|
slotPtrA->LoadCostDataForSlot(true);
|
||||||
|
slotPtrB->Set(typeA, idA);
|
||||||
|
slotPtrB->LoadIconId();
|
||||||
|
if (typeA == RaptureHotbarModule.HotbarSlotType.Item)
|
||||||
|
slotPtrB->LoadCostDataForSlot(true);
|
||||||
|
module->SetAndSaveSlot((uint)(barA - 1), (uint)a, typeB, idB);
|
||||||
|
module->SetAndSaveSlot((uint)(barB - 1), (uint)b, typeA, idA);
|
||||||
|
|
||||||
|
// Read back to verify
|
||||||
|
slotPtrA = module->GetSlotById((uint)(barA - 1), (uint)a);
|
||||||
|
slotPtrB = module->GetSlotById((uint)(barB - 1), (uint)b);
|
||||||
|
if (slotPtrA != null && slotPtrB != null)
|
||||||
|
{
|
||||||
|
var afterA = slotPtrA->IsEmpty ? "empty" : $"{slotPtrA->CommandType} id={slotPtrA->CommandId}";
|
||||||
|
var afterB = slotPtrB->IsEmpty ? "empty" : $"{slotPtrB->CommandType} id={slotPtrB->CommandId}";
|
||||||
|
debugLog?.Invoke($"[SwapSlots] AFTER: A(bar{barA} slot{a}) {afterA} | B(bar{barB} slot{b}) {afterB}");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Dump HotbarSlot and RaptureMacroModule.Macro memory for a slot (for macro persistence debugging).</summary>
|
||||||
|
/// <param name="hotbarIndex">1-10</param>
|
||||||
|
/// <param name="slotIndex">0-11</param>
|
||||||
|
public unsafe void DumpMacroSlotMemoryToLog(int hotbarIndex, int slotIndex)
|
||||||
|
{
|
||||||
|
var hotbarModule = RaptureHotbarModule.Instance();
|
||||||
|
if (hotbarModule == null || !hotbarModule->ModuleReady)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Information("[HSUI Macro DBG] RaptureHotbarModule not ready");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int barIdx = Math.Clamp(hotbarIndex, 1, 10) - 1;
|
||||||
|
int slot = Math.Clamp(slotIndex, 0, 11);
|
||||||
|
var slotPtr = hotbarModule->GetSlotById((uint)barIdx, (uint)slot);
|
||||||
|
if (slotPtr == null)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Information($"[HSUI Macro DBG] Bar {hotbarIndex} slot {slotIndex}: slot is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var ct = slotPtr->CommandType;
|
||||||
|
var cid = slotPtr->CommandId;
|
||||||
|
Plugin.Logger.Information($"[HSUI Macro DBG] Bar {hotbarIndex} slot {slotIndex}: CommandType={ct} CommandId={cid} ApparentActionId={slotPtr->ApparentActionId} ApparentSlotType={slotPtr->ApparentSlotType} IconId={slotPtr->IconId}");
|
||||||
|
if (ct == RaptureHotbarModule.HotbarSlotType.Macro && cid is >= 1 and <= 200)
|
||||||
|
{
|
||||||
|
byte macroSet = (byte)((cid - 1) / 100);
|
||||||
|
byte macroIdx0Based = (byte)((cid - 1) % 100);
|
||||||
|
uint macroIdx1Based = (uint)(macroIdx0Based + 1); // GetMacro expects 1-based index
|
||||||
|
var macroModule = RaptureMacroModule.Instance();
|
||||||
|
if (macroModule != null)
|
||||||
|
{
|
||||||
|
var macro = macroModule->GetMacro(macroSet, macroIdx1Based);
|
||||||
|
if (macro != null)
|
||||||
|
{
|
||||||
|
string name = macro->Name.ToString() ?? "(null)";
|
||||||
|
Plugin.Logger.Information($"[HSUI Macro DBG] RaptureMacroModule.Macro set={macroSet} idx1Based={macroIdx1Based}: IconId={macro->IconId} MacroIconRowId={macro->MacroIconRowId} Name='{name}'");
|
||||||
|
|
||||||
|
// Raw byte dump (first 0x80 bytes: IconId, MacroIconRowId, start of Name/Utf8String)
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var ptr = (byte*)macro;
|
||||||
|
for (int i = 0; i < 0x80 && i < 0x688; i += 16)
|
||||||
|
{
|
||||||
|
sb.Clear();
|
||||||
|
sb.Append($"[HSUI Macro DBG] Macro+0x{i:X3}:");
|
||||||
|
for (int j = 0; j < 16 && i + j < 0x80; j++)
|
||||||
|
sb.Append($" {(ptr[i + j]):X2}");
|
||||||
|
Plugin.Logger.Information(sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try AddonMacro _macroName (offset 0x798, Utf8String=0x68 each, 100 entries)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var addonAddr = Plugin.GameGui?.GetAddonByName("Macro", 1).Address ?? IntPtr.Zero;
|
||||||
|
if (addonAddr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
var addon = (AddonMacro*)addonAddr;
|
||||||
|
int utf8Size = 0x68;
|
||||||
|
int nameBase = 0x798;
|
||||||
|
var namePtr = (Utf8String*)((byte*)addon + nameBase + macroIdx0Based * utf8Size);
|
||||||
|
string addonName = namePtr->ToString() ?? "(null)";
|
||||||
|
Plugin.Logger.Information($"[HSUI Macro DBG] AddonMacro._macroName[{macroIdx0Based}]: '{addonName}'");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Plugin.Logger.Information("[HSUI Macro DBG] AddonMacro not found or not open");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Information($"[HSUI Macro DBG] AddonMacro read failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try AgentMacro SelectedMacroSet/Index (only relevant when that macro is selected)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var agentModule = FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentModule.Instance();
|
||||||
|
if (agentModule != null)
|
||||||
|
{
|
||||||
|
var macroAgent = agentModule->GetAgentByInternalId(FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentId.Macro);
|
||||||
|
if (macroAgent != null)
|
||||||
|
{
|
||||||
|
var agentMacro = (FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentMacro*)macroAgent;
|
||||||
|
Plugin.Logger.Information($"[HSUI Macro DBG] AgentMacro SelectedMacroSet={agentMacro->SelectedMacroSet} SelectedMacroIndex={agentMacro->SelectedMacroIndex}");
|
||||||
|
if (agentMacro->SelectedMacroSet == macroSet && agentMacro->SelectedMacroIndex == macroIdx1Based)
|
||||||
|
{
|
||||||
|
string rawStr = agentMacro->RawMacroString.ToString() ?? "(null)";
|
||||||
|
Plugin.Logger.Information($"[HSUI Macro DBG] AgentMacro (matches): RawMacroString(len={rawStr.Length})='{(rawStr.Length > 60 ? rawStr[..60] + "..." : rawStr)}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Plugin.Logger.Information("[HSUI Macro DBG] AgentMacro agent is null");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Plugin.Logger.Information("[HSUI Macro DBG] AgentModule.Instance() is null");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Information($"[HSUI Macro DBG] AgentMacro read failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Plugin.Logger.Information($"[HSUI Macro DBG] RaptureMacroModule.GetMacro({macroSet},{macroIdx1Based}) returned null");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Plugin.Logger.Information("[HSUI Macro DBG] RaptureMacroModule.Instance() is null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Dump AddonMacro and AgentMacro state (open Macro menu first for best data).</summary>
|
||||||
|
public unsafe void DumpMacroMenuToLog()
|
||||||
|
{
|
||||||
|
Plugin.Logger.Information("[HSUI MacroMenu DBG] === Macro menu debug ===");
|
||||||
|
|
||||||
|
// AddonMacro
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var addonAddr = Plugin.GameGui?.GetAddonByName("Macro", 1).Address ?? IntPtr.Zero;
|
||||||
|
if (addonAddr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
var addon = (AddonMacro*)addonAddr;
|
||||||
|
Plugin.Logger.Information($"[HSUI MacroMenu DBG] AddonMacro: SelectedPage={addon->SelectedPage} SelectedMacroIndex={addon->SelectedMacroIndex} DefaultIcon={addon->DefaultIcon}");
|
||||||
|
const int utf8Size = 0x68;
|
||||||
|
const int nameBase = 0x798;
|
||||||
|
const int iconBase = 0x604;
|
||||||
|
const int createdBase = 0x3038;
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
var namePtr = (Utf8String*)((byte*)addon + nameBase + i * utf8Size);
|
||||||
|
int icon = *(int*)((byte*)addon + iconBase + i * 4);
|
||||||
|
bool created = *((byte*)addon + createdBase + i) != 0;
|
||||||
|
string name = namePtr->ToString() ?? "(empty)";
|
||||||
|
Plugin.Logger.Information($"[HSUI MacroMenu DBG] AddonMacro[{i}]: Name='{name}' Icon={icon} Created={created}");
|
||||||
|
}
|
||||||
|
Plugin.Logger.Information("[HSUI MacroMenu DBG] ... (showing first 10 of 100)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Plugin.Logger.Information("[HSUI MacroMenu DBG] AddonMacro not found - open Macro menu (Character Config > Hotbars > Macros) first");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Information($"[HSUI MacroMenu DBG] AddonMacro failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentMacro
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var agentModule = FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentModule.Instance();
|
||||||
|
if (agentModule != null)
|
||||||
|
{
|
||||||
|
var macroAgent = agentModule->GetAgentByInternalId(FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentId.Macro);
|
||||||
|
if (macroAgent != null)
|
||||||
|
{
|
||||||
|
var agent = (FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentMacro*)macroAgent;
|
||||||
|
Plugin.Logger.Information($"[HSUI MacroMenu DBG] AgentMacro: SelectedMacroSet={agent->SelectedMacroSet} (0=Individual,1=Shared) SelectedMacroIndex={agent->SelectedMacroIndex}");
|
||||||
|
string raw = agent->RawMacroString.ToString() ?? "(null)";
|
||||||
|
string parsed = agent->ParsedMacroString.ToString() ?? "(null)";
|
||||||
|
Plugin.Logger.Information($"[HSUI MacroMenu DBG] RawMacroString(len={raw.Length}): '{(raw.Length > 80 ? raw[..80] + "..." : raw)}'");
|
||||||
|
Plugin.Logger.Information($"[HSUI MacroMenu DBG] ParsedMacroString(len={parsed.Length}): '{(parsed.Length > 80 ? parsed[..80] + "..." : parsed)}'");
|
||||||
|
Plugin.Logger.Information($"[HSUI MacroMenu DBG] MacroIconCount={agent->MacroIconCount}");
|
||||||
|
|
||||||
|
var clip = &agent->ClipboardMacro;
|
||||||
|
string clipName = clip->Name.ToString() ?? "(null)";
|
||||||
|
Plugin.Logger.Information($"[HSUI MacroMenu DBG] ClipboardMacro: IconId={clip->IconId} MacroIconRowId={clip->MacroIconRowId} Name='{clipName}'");
|
||||||
|
|
||||||
|
var rm = RaptureMacroModule.Instance();
|
||||||
|
if (rm != null)
|
||||||
|
{
|
||||||
|
var selectedMacro = rm->GetMacro(agent->SelectedMacroSet, agent->SelectedMacroIndex);
|
||||||
|
if (selectedMacro != null)
|
||||||
|
{
|
||||||
|
string rName = selectedMacro->Name.ToString() ?? "(null)";
|
||||||
|
Plugin.Logger.Information($"[HSUI MacroMenu DBG] RaptureMacroModule.GetMacro({agent->SelectedMacroSet},{agent->SelectedMacroIndex}): IconId={selectedMacro->IconId} MacroIconRowId={selectedMacro->MacroIconRowId} Name='{rName}'");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Plugin.Logger.Information($"[HSUI MacroMenu DBG] RaptureMacroModule.GetMacro returned null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Plugin.Logger.Information("[HSUI MacroMenu DBG] AgentMacro agent is null");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Plugin.Logger.Information("[HSUI MacroMenu DBG] AgentModule.Instance() is null");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Information($"[HSUI MacroMenu DBG] AgentMacro failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugin.Logger.Information("[HSUI MacroMenu DBG] === end ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Dump all hotbar slot CommandType/CommandId to the log for SwapSlots diagnosis.</summary>
|
||||||
|
public unsafe void DumpSlotStateToLog()
|
||||||
|
{
|
||||||
|
var module = RaptureHotbarModule.Instance();
|
||||||
|
if (module == null || !module->ModuleReady) { Plugin.Logger.Information("[HSUI HotbarSlots] Module not ready"); return; }
|
||||||
|
for (int bar = 1; bar <= 10; bar++)
|
||||||
|
{
|
||||||
|
var sb = new System.Text.StringBuilder();
|
||||||
|
for (int s = 0; s < 12; s++)
|
||||||
|
{
|
||||||
|
var slot = module->GetSlotById((uint)(bar - 1), (uint)s);
|
||||||
|
if (slot == null) { sb.Append("? "); continue; }
|
||||||
|
if (slot->IsEmpty) { sb.Append("-- "); continue; }
|
||||||
|
sb.Append($"{slot->CommandType}:{slot->CommandId} ");
|
||||||
|
}
|
||||||
|
Plugin.Logger.Information($"[HSUI HotbarSlots] Bar {bar}: {sb}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Clear a hotbar slot. hotbarIndex 1-10, slotIndex 0-based.</summary>
|
||||||
|
public unsafe bool ClearSlot(int hotbarIndex, int slotIndex)
|
||||||
|
{
|
||||||
|
var module = RaptureHotbarModule.Instance();
|
||||||
|
if (module == null || !module->ModuleReady) return false;
|
||||||
|
int barIdx = Math.Clamp(hotbarIndex, 1, 10) - 1;
|
||||||
|
int slot = Math.Clamp(slotIndex, 0, 11);
|
||||||
|
var slotPtr = module->GetSlotById((uint)barIdx, (uint)slot);
|
||||||
|
if (slotPtr != null)
|
||||||
|
{
|
||||||
|
slotPtr->Set(RaptureHotbarModule.HotbarSlotType.Empty, 0);
|
||||||
|
slotPtr->LoadIconId();
|
||||||
|
}
|
||||||
|
module->SetAndSaveSlot((uint)barIdx, (uint)slot, RaptureHotbarModule.HotbarSlotType.Empty, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.GameFonts;
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Interface.Textures;
|
||||||
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public struct BarTextureData
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public string Path;
|
||||||
|
public bool IsCustom;
|
||||||
|
|
||||||
|
public BarTextureData(string name, string path, bool isCustom)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Path = path;
|
||||||
|
IsCustom = isCustom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BarTexturesManager : IDisposable
|
||||||
|
{
|
||||||
|
#region Singleton
|
||||||
|
private BarTexturesManager(string basePath)
|
||||||
|
{
|
||||||
|
DefaultBarTexturesPath = Path.GetDirectoryName(basePath) + "\\Media\\Images\\textures\\";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize(string basePath)
|
||||||
|
{
|
||||||
|
Instance = new BarTexturesManager(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarTexturesManager Instance { get; private set; } = null!;
|
||||||
|
private BarTexturesConfig? _config;
|
||||||
|
|
||||||
|
public void LoadConfig()
|
||||||
|
{
|
||||||
|
if (_config != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_config = ConfigurationManager.Instance.GetConfigObject<BarTexturesConfig>();
|
||||||
|
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
||||||
|
|
||||||
|
ReloadTextures();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigReset(ConfigurationManager sender)
|
||||||
|
{
|
||||||
|
_config = sender.GetConfigObject<BarTexturesConfig>();
|
||||||
|
}
|
||||||
|
|
||||||
|
~BarTexturesManager()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public readonly string DefaultBarTexturesPath;
|
||||||
|
public static readonly string DefaultBarTextureName = "Default";
|
||||||
|
|
||||||
|
public bool DefaultFontBuilt { get; private set; }
|
||||||
|
public ImFontPtr DefaultFont { get; private set; } = null;
|
||||||
|
|
||||||
|
private List<BarTextureData> _textures = new List<BarTextureData>();
|
||||||
|
public IReadOnlyCollection<BarTextureData> BarTextures => _textures.AsReadOnly();
|
||||||
|
|
||||||
|
private List<string> _textureNames = new List<string>();
|
||||||
|
public IReadOnlyCollection<string> BarTextureNames => _textureNames.AsReadOnly();
|
||||||
|
|
||||||
|
private Dictionary<string, ISharedImmediateTexture> _cache = new();
|
||||||
|
|
||||||
|
public IDalamudTextureWrap? GetBarTexture(string? name)
|
||||||
|
{
|
||||||
|
if (name == null || name == DefaultBarTextureName) { return null; }
|
||||||
|
|
||||||
|
// get cached texture
|
||||||
|
if (_cache.TryGetValue(name, out ISharedImmediateTexture? cachedTexture) && cachedTexture != null)
|
||||||
|
{
|
||||||
|
return cachedTexture.GetWrapOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// lazy load
|
||||||
|
BarTextureData? data = _textures.FirstOrDefault(o => o.Name == name);
|
||||||
|
if (!data.HasValue) { return null; }
|
||||||
|
|
||||||
|
if (File.Exists(data.Value.Path))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ISharedImmediateTexture? texture = Plugin.TextureProvider.GetFromFile(data.Value.Path);
|
||||||
|
if (texture != null)
|
||||||
|
{
|
||||||
|
_cache.Add(name, texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
return texture?.GetWrapOrDefault();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
(Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Warning($"Image failed to load. {data.Value.Path}: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadTextures()
|
||||||
|
{
|
||||||
|
_textures.Clear();
|
||||||
|
|
||||||
|
// embedded textures
|
||||||
|
_textures.AddRange(TexturesFromPath(DefaultBarTexturesPath, true));
|
||||||
|
|
||||||
|
// custom textures
|
||||||
|
if (_config != null)
|
||||||
|
{
|
||||||
|
_textures.AddRange(TexturesFromPath(_config.ValidatedBarTexturesPath, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by name
|
||||||
|
_textures = _textures.OrderBy(o => o.Name).ToList();
|
||||||
|
|
||||||
|
// default always first
|
||||||
|
_textures.Insert(0, new BarTextureData(DefaultBarTextureName, "", false));
|
||||||
|
|
||||||
|
_textureNames = _textures.Select(o => o.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BarTextureData> TexturesFromPath(string path, bool isCustom)
|
||||||
|
{
|
||||||
|
string[] textures;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string[] allowedExtensions = new string[] { ".png", ".tga" };
|
||||||
|
textures = Directory
|
||||||
|
.GetFiles(path)
|
||||||
|
.Where(file => allowedExtensions.Any(file.ToLower().EndsWith))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
textures = new string[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BarTextureData> result = new List<BarTextureData>(textures.Length);
|
||||||
|
|
||||||
|
for (int i = 0; i < textures.Length; i++)
|
||||||
|
{
|
||||||
|
string name = SanitizedTextureName(textures[i].Replace(path, ""));
|
||||||
|
result.Add(new BarTextureData(name, textures[i], isCustom));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SanitizedTextureName(string name)
|
||||||
|
{
|
||||||
|
return name
|
||||||
|
.Replace(".png", "")
|
||||||
|
.Replace(".PNG", "")
|
||||||
|
.Replace(".tga", "")
|
||||||
|
.Replace(".TGA", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public class ClipRectsHelper
|
||||||
|
{
|
||||||
|
#region Singleton
|
||||||
|
private ClipRectsHelper()
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
||||||
|
OnConfigReset(ConfigurationManager.Instance);
|
||||||
|
|
||||||
|
// other plugins can add clip rects for HSUI
|
||||||
|
// rect start point = vector.X, vector.Y
|
||||||
|
// rect end point = vector.Z, vector.W
|
||||||
|
_thirdPartyClipRects = Plugin.PluginInterface.GetOrCreateData<Dictionary<string, Vector4>>(_sharedDataId, () => new());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize() { Instance = new ClipRectsHelper(); }
|
||||||
|
|
||||||
|
public static ClipRectsHelper Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
~ClipRectsHelper()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
|
||||||
|
|
||||||
|
Plugin.PluginInterface.RelinquishData(_sharedDataId);
|
||||||
|
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private WindowClippingConfig _config = null!;
|
||||||
|
|
||||||
|
private void OnConfigReset(ConfigurationManager sender)
|
||||||
|
{
|
||||||
|
_config = sender.GetConfigObject<WindowClippingConfig>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Enabled => _config.Enabled;
|
||||||
|
public WindowClippingMode? Mode => _config.Enabled ? _config.Mode : null;
|
||||||
|
|
||||||
|
private List<ClipRect> _clipRects = new List<ClipRect>();
|
||||||
|
private List<ClipRect> _extraClipRects = new List<ClipRect>();
|
||||||
|
|
||||||
|
private static Dictionary<string, Vector4> _thirdPartyClipRects = new();
|
||||||
|
private static string _sharedDataId = "HSUI.ClipRects";
|
||||||
|
|
||||||
|
private static List<string> _ignoredAddonNames = new List<string>()
|
||||||
|
{
|
||||||
|
"_FocusTargetInfo",
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly string[] _hotbarAddonNames = { "_ActionBar", "_ActionBar01", "_ActionBar02", "_ActionBar03", "_ActionBar04", "_ActionBar05", "_ActionBar06", "_ActionBar07", "_ActionBar08", "_ActionBar09" };
|
||||||
|
|
||||||
|
public unsafe void Update()
|
||||||
|
{
|
||||||
|
if (!_config.Enabled) { return; }
|
||||||
|
|
||||||
|
_clipRects.Clear();
|
||||||
|
_extraClipRects.Clear();
|
||||||
|
|
||||||
|
// find clip rects for game windows
|
||||||
|
AtkStage* stage = AtkStage.Instance();
|
||||||
|
if (stage == null) { return; }
|
||||||
|
|
||||||
|
RaptureAtkUnitManager* manager = stage->RaptureAtkUnitManager;
|
||||||
|
if (manager == null) { return; }
|
||||||
|
|
||||||
|
AtkUnitList* loadedUnitsList = &manager->AtkUnitManager.AllLoadedUnitsList;
|
||||||
|
if (loadedUnitsList == null) { return; }
|
||||||
|
|
||||||
|
for (int i = 0; i < loadedUnitsList->Count; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AtkUnitBase* addon = *(AtkUnitBase**)Unsafe.AsPointer(ref loadedUnitsList->Entries[i]);
|
||||||
|
if (addon == null || addon->RootNode == null || !addon->IsVisible || addon->WindowNode == null || addon->Scale == 0 || !addon->WindowNode->IsVisible())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string name = addon->NameString;
|
||||||
|
if (_ignoredAddonNames.Contains(name))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float margin = 5 * addon->Scale;
|
||||||
|
float bottomMargin = 13 * addon->Scale;
|
||||||
|
|
||||||
|
Vector2 pos = new Vector2(addon->RootNode->X + margin, addon->RootNode->Y + margin);
|
||||||
|
Vector2 size = new Vector2(
|
||||||
|
addon->RootNode->Width * addon->Scale - margin,
|
||||||
|
addon->RootNode->Height * addon->Scale - bottomMargin
|
||||||
|
);
|
||||||
|
|
||||||
|
// just in case this causes weird issues / crashes (doubt it though...)
|
||||||
|
ClipRect clipRect = new ClipRect(pos, pos + size);
|
||||||
|
if (clipRect.Max.X < clipRect.Min.X || clipRect.Max.Y < clipRect.Min.Y)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_clipRects.Add(clipRect);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_config.ThirdPartyClipRectsEnabled)
|
||||||
|
{
|
||||||
|
// find clip rects from other plugins
|
||||||
|
Dictionary<string, Vector4> dict = _thirdPartyClipRects;
|
||||||
|
foreach (Vector4 vector in dict.Values)
|
||||||
|
{
|
||||||
|
ClipRect clipRect = new ClipRect(new(vector.X, vector.Y), new(vector.Z, vector.W));
|
||||||
|
_clipRects.Add(clipRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ClipRect> ActiveClipRects()
|
||||||
|
{
|
||||||
|
return [.. _clipRects, .. _extraClipRects];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddNameplatesClipRects()
|
||||||
|
{
|
||||||
|
if (!_config.NameplatesClipRectsEnabled) { return; }
|
||||||
|
|
||||||
|
// target cast bar
|
||||||
|
ClipRect? targetCastbarClipRect = GetTargetCastbarClipRect();
|
||||||
|
if (targetCastbarClipRect.HasValue)
|
||||||
|
{
|
||||||
|
_extraClipRects.Add(targetCastbarClipRect.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// hotbars
|
||||||
|
_extraClipRects.AddRange(GetHotbarsClipRects());
|
||||||
|
|
||||||
|
// chat bubbles
|
||||||
|
_extraClipRects.AddRange(GetNPCChatBubbleClipRect());
|
||||||
|
_extraClipRects.AddRange(GetPlayerChatBubbleClipRect());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveNameplatesClipRects()
|
||||||
|
{
|
||||||
|
_extraClipRects.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe ClipRect? GetTargetCastbarClipRect()
|
||||||
|
{
|
||||||
|
if (!_config.TargetCastbarClipRectEnabled) { return null; }
|
||||||
|
|
||||||
|
AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_TargetInfoCastBar", 1).Address;
|
||||||
|
if (addon == null || !addon->IsVisible) { return null; }
|
||||||
|
|
||||||
|
AtkResNode* baseNode = addon->GetNodeById(2);
|
||||||
|
AtkImageNode* imageNode = addon->GetImageNodeById(7);
|
||||||
|
|
||||||
|
if (baseNode == null || !baseNode->IsVisible()) { return null; }
|
||||||
|
if (imageNode == null || !imageNode->IsVisible()) { return null; }
|
||||||
|
|
||||||
|
Vector2 pos = new Vector2(
|
||||||
|
addon->X + (baseNode->X * addon->Scale),
|
||||||
|
addon->Y + (baseNode->Y * addon->Scale)
|
||||||
|
);
|
||||||
|
Vector2 size = new Vector2(
|
||||||
|
imageNode->Width * addon->Scale,
|
||||||
|
imageNode->Height * addon->Scale
|
||||||
|
);
|
||||||
|
|
||||||
|
return new ClipRect(pos, pos + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe List<ClipRect> GetHotbarsClipRects()
|
||||||
|
{
|
||||||
|
List<ClipRect> rects = new List<ClipRect>();
|
||||||
|
if (!_config.HotbarsClipRectsEnabled) { return rects; }
|
||||||
|
|
||||||
|
foreach (string addonName in _hotbarAddonNames)
|
||||||
|
{
|
||||||
|
AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName(addonName, 1).Address;
|
||||||
|
if (addon == null || !addon->IsVisible) { continue; }
|
||||||
|
|
||||||
|
AtkComponentNode* firstNode = addon->GetComponentNodeById(8);
|
||||||
|
AtkComponentNode* lastNode = addon->GetComponentNodeById(19);
|
||||||
|
|
||||||
|
if (firstNode == null || lastNode == null) { continue; }
|
||||||
|
|
||||||
|
|
||||||
|
float margin = 10f * addon->Scale;
|
||||||
|
|
||||||
|
Vector2 min = new Vector2(
|
||||||
|
addon->X + (firstNode->AtkResNode.X * addon->Scale) + margin,
|
||||||
|
addon->Y + (firstNode->AtkResNode.Y * addon->Scale) + margin
|
||||||
|
);
|
||||||
|
Vector2 max = new Vector2(
|
||||||
|
addon->X + (lastNode->AtkResNode.X * addon->Scale) + (lastNode->AtkResNode.Width * addon->Scale) - margin,
|
||||||
|
addon->Y + (lastNode->AtkResNode.Y * addon->Scale) + (lastNode->AtkResNode.Height * addon->Scale) - margin
|
||||||
|
);
|
||||||
|
|
||||||
|
rects.Add(new ClipRect(min, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rects;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe List<ClipRect> GetNPCChatBubbleClipRect()
|
||||||
|
{
|
||||||
|
List<ClipRect> rects = new List<ClipRect>();
|
||||||
|
if (!_config.ChatBubblesNPCClipRectsEnabled) { return rects; }
|
||||||
|
|
||||||
|
var addon = (AddonMiniTalk*) Plugin.GameGui.GetAddonByName("_MiniTalk").Address;
|
||||||
|
if (addon is null)
|
||||||
|
{
|
||||||
|
return rects;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var talkBubble in addon->TalkBubbles) {
|
||||||
|
if (!talkBubble.ComponentNode->IsVisible())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtkNineGridNode* bubbleNineGridNode = talkBubble.BubbleNineGridNode;
|
||||||
|
|
||||||
|
Vector2 position = new Vector2(
|
||||||
|
bubbleNineGridNode->ScreenX,
|
||||||
|
bubbleNineGridNode->ScreenY
|
||||||
|
);
|
||||||
|
Vector2 scale = GetNodeScale((AtkResNode*) bubbleNineGridNode, new Vector2(bubbleNineGridNode->ScaleX, bubbleNineGridNode->ScaleY));
|
||||||
|
Vector2 size = new Vector2(
|
||||||
|
bubbleNineGridNode->Width,
|
||||||
|
bubbleNineGridNode->Height
|
||||||
|
) * scale;
|
||||||
|
|
||||||
|
rects.Add(new ClipRect(position, position + size));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe List<ClipRect> GetPlayerChatBubbleClipRect()
|
||||||
|
{
|
||||||
|
List<ClipRect> rects = new List<ClipRect>();
|
||||||
|
if (!_config.ChatBubblesPlayersClipRectsEnabled) { return rects; }
|
||||||
|
|
||||||
|
AtkUnitBase* addon = (AtkUnitBase*) Plugin.GameGui.GetAddonByName("MiniTalkPlayer").Address;
|
||||||
|
if (addon is null)
|
||||||
|
{
|
||||||
|
return rects;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var node in addon->UldManager.Nodes) {
|
||||||
|
if (node.Value is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Value->GetNodeType() is not NodeType.Component || !node.Value->IsVisible())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtkComponentNode* componentNode = (AtkComponentNode*)node.Value;
|
||||||
|
AtkComponentBase* component = componentNode->GetComponent();
|
||||||
|
if (component is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtkResNode* bubbleNode = component->UldManager.SearchNodeById(4);
|
||||||
|
if (bubbleNode is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 position = new Vector2(
|
||||||
|
componentNode->ScreenX,
|
||||||
|
componentNode->ScreenY
|
||||||
|
);
|
||||||
|
Vector2 scale = GetNodeScale(bubbleNode, new Vector2(bubbleNode->ScaleX, bubbleNode->ScaleY));
|
||||||
|
Vector2 size = new Vector2(
|
||||||
|
bubbleNode->Width,
|
||||||
|
bubbleNode->Height
|
||||||
|
) * scale;
|
||||||
|
|
||||||
|
rects.Add(new ClipRect(position, position + size));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClipRect? GetClipRectForArea(Vector2 pos, Vector2 size)
|
||||||
|
{
|
||||||
|
if (!_config.Enabled) { return null; }
|
||||||
|
|
||||||
|
List<ClipRect> rects = ActiveClipRects();
|
||||||
|
|
||||||
|
foreach (ClipRect clipRect in rects)
|
||||||
|
{
|
||||||
|
ClipRect area = new ClipRect(pos, pos + size);
|
||||||
|
if (clipRect.IntersectsWith(area))
|
||||||
|
{
|
||||||
|
return clipRect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClipRect[] GetInvertedClipRects(ClipRect clipRect)
|
||||||
|
{
|
||||||
|
float maxX = ImGui.GetMainViewport().Size.X;
|
||||||
|
float maxY = ImGui.GetMainViewport().Size.Y;
|
||||||
|
|
||||||
|
Vector2 aboveMin = new Vector2(0, 0);
|
||||||
|
Vector2 aboveMax = new Vector2(maxX, clipRect.Min.Y);
|
||||||
|
Vector2 leftMin = new Vector2(0, clipRect.Min.Y);
|
||||||
|
Vector2 leftMax = new Vector2(clipRect.Min.X, maxY);
|
||||||
|
|
||||||
|
Vector2 rightMin = new Vector2(clipRect.Max.X, clipRect.Min.Y);
|
||||||
|
Vector2 rightMax = new Vector2(maxX, clipRect.Max.Y);
|
||||||
|
Vector2 belowMin = new Vector2(clipRect.Min.X, clipRect.Max.Y);
|
||||||
|
Vector2 belowMax = new Vector2(maxX, maxY);
|
||||||
|
|
||||||
|
ClipRect[] invertedClipRects = new ClipRect[4];
|
||||||
|
invertedClipRects[0] = new ClipRect(aboveMin, aboveMax);
|
||||||
|
invertedClipRects[1] = new ClipRect(leftMin, leftMax);
|
||||||
|
invertedClipRects[2] = new ClipRect(rightMin, rightMax);
|
||||||
|
invertedClipRects[3] = new ClipRect(belowMin, belowMax);
|
||||||
|
|
||||||
|
return invertedClipRects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPointClipped(Vector2 point)
|
||||||
|
{
|
||||||
|
if (!_config.Enabled) { return false; }
|
||||||
|
|
||||||
|
List<ClipRect> rects = ActiveClipRects();
|
||||||
|
|
||||||
|
foreach (ClipRect clipRect in rects)
|
||||||
|
{
|
||||||
|
if (clipRect.Contains(point))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe Vector2 GetNodeScale(AtkResNode* node, Vector2 currentScale) {
|
||||||
|
if (node is null)
|
||||||
|
{
|
||||||
|
return currentScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node->ParentNode is not null) {
|
||||||
|
currentScale.X *= node->ParentNode->GetScaleX();
|
||||||
|
currentScale.Y *= node->ParentNode->GetScaleY();
|
||||||
|
|
||||||
|
return GetNodeScale(node->ParentNode, currentScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ClipRect
|
||||||
|
{
|
||||||
|
public readonly Vector2 Min;
|
||||||
|
public readonly Vector2 Max;
|
||||||
|
|
||||||
|
private readonly Rectangle Rectangle;
|
||||||
|
|
||||||
|
public ClipRect(Vector2 min, Vector2 max)
|
||||||
|
{
|
||||||
|
Vector2 screenSize = ImGui.GetMainViewport().Size;
|
||||||
|
|
||||||
|
Min = Clamp(min, Vector2.Zero, screenSize);
|
||||||
|
Max = Clamp(max, Vector2.Zero, screenSize);
|
||||||
|
|
||||||
|
Vector2 size = Max - Min;
|
||||||
|
|
||||||
|
Rectangle = new Rectangle((int)Min.X, (int)Min.Y, (int)size.X, (int)size.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(Vector2 point)
|
||||||
|
{
|
||||||
|
return Rectangle.Contains((int)point.X, (int)point.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IntersectsWith(ClipRect other)
|
||||||
|
{
|
||||||
|
return Rectangle.IntersectsWith(other.Rectangle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClipRect? Intersect(ClipRect other)
|
||||||
|
{
|
||||||
|
float minX = Math.Max(Min.X, other.Min.X);
|
||||||
|
float minY = Math.Max(Min.Y, other.Min.Y);
|
||||||
|
float maxX = Math.Min(Max.X, other.Max.X);
|
||||||
|
float maxY = Math.Min(Max.Y, other.Max.Y);
|
||||||
|
if (minX >= maxX || minY >= maxY) return null;
|
||||||
|
return new ClipRect(new Vector2(minX, minY), new Vector2(maxX, maxY));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector2 Clamp(Vector2 vector, Vector2 min, Vector2 max)
|
||||||
|
{
|
||||||
|
return new Vector2(Math.Max(min.X, Math.Min(max.X, vector.X)), Math.Max(min.Y, Math.Min(max.Y, vector.Y)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
using Colourful;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public static class ColorUtils
|
||||||
|
{
|
||||||
|
|
||||||
|
//Build our converter objects and store them in a field. This will be used to convert our PluginConfigColors into different color spaces to be used for interpolation
|
||||||
|
private static readonly IColorConverter<RGBColor, LabColor> _rgbToLab = new ConverterBuilder().FromRGB().ToLab().Build();
|
||||||
|
private static readonly IColorConverter<LabColor, RGBColor> _labToRgb = new ConverterBuilder().FromLab().ToRGB().Build();
|
||||||
|
|
||||||
|
private static readonly IColorConverter<RGBColor, LChabColor> _rgbToLChab = new ConverterBuilder().FromRGB().ToLChab().Build();
|
||||||
|
private static readonly IColorConverter<LChabColor, RGBColor> _lchabToRgb = new ConverterBuilder().FromLChab().ToRGB().Build();
|
||||||
|
|
||||||
|
private static readonly IColorConverter<RGBColor, XYZColor> _rgbToXyz = new ConverterBuilder().FromRGB(RGBWorkingSpaces.sRGB).ToXYZ(Illuminants.D65).Build();
|
||||||
|
private static readonly IColorConverter<XYZColor, RGBColor> _xyzToRgb = new ConverterBuilder().FromXYZ(Illuminants.D65).ToRGB(RGBWorkingSpaces.sRGB).Build();
|
||||||
|
|
||||||
|
private static readonly IColorConverter<RGBColor, LChuvColor> _rgbToLChuv = new ConverterBuilder().FromRGB().ToLChuv().Build();
|
||||||
|
private static readonly IColorConverter<LChuvColor, RGBColor> _lchuvToRgb = new ConverterBuilder().FromLChuv().ToRGB().Build();
|
||||||
|
|
||||||
|
private static readonly IColorConverter<RGBColor, LuvColor> _rgbToLuv = new ConverterBuilder().FromRGB().ToLuv().Build();
|
||||||
|
private static readonly IColorConverter<LuvColor, RGBColor> _luvToRgb = new ConverterBuilder().FromLuv().ToRGB().Build();
|
||||||
|
|
||||||
|
private static readonly IColorConverter<RGBColor, JzazbzColor> _rgbToJzazbz = new ConverterBuilder().FromRGB().ToJzazbz().Build();
|
||||||
|
private static readonly IColorConverter<JzazbzColor, RGBColor> _jzazbzToRgb = new ConverterBuilder().FromJzazbz().ToRGB().Build();
|
||||||
|
|
||||||
|
private static readonly IColorConverter<RGBColor, JzCzhzColor> _rgbToJzCzhz = new ConverterBuilder().FromRGB().ToJzCzhz().Build();
|
||||||
|
private static readonly IColorConverter<JzCzhzColor, RGBColor> _jzCzhzToRgb = new ConverterBuilder().FromJzCzhz().ToRGB().Build();
|
||||||
|
|
||||||
|
//Simple LinearInterpolation method. T = [0 , 1]
|
||||||
|
private static float LinearInterpolation(float left, float right, float t)
|
||||||
|
=> left + ((right - left) * t);
|
||||||
|
|
||||||
|
public static PluginConfigColor GetColorByScale(float i, ColorByHealthValueConfig config) =>
|
||||||
|
GetColorByScale(i, config.LowHealthColorThreshold / 100f, config.FullHealthColorThreshold / 100f, config.LowHealthColor, config.FullHealthColor, config.MaxHealthColor, config.UseMaxHealthColor, config.BlendMode);
|
||||||
|
|
||||||
|
//Method used to interpolate two PluginConfigColors
|
||||||
|
//i is scale [0 , 1]
|
||||||
|
//min and max are used for color thresholds. for instance return colorLeft if i < min or return ColorRight if i > max
|
||||||
|
public static PluginConfigColor GetColorByScale(float i, float min, float max, PluginConfigColor colorLeft, PluginConfigColor colorRight, PluginConfigColor colorMax, bool useMaxColor, BlendMode blendMode)
|
||||||
|
{
|
||||||
|
//Set our thresholds where the ratio is the range of values we will use for interpolation.
|
||||||
|
//Values outside this range will either return colorLeft or colorRight
|
||||||
|
float ratio = i;
|
||||||
|
if (min > 0 || max < 1)
|
||||||
|
{
|
||||||
|
if (i < min)
|
||||||
|
{
|
||||||
|
ratio = 0;
|
||||||
|
}
|
||||||
|
else if (i > max)
|
||||||
|
{
|
||||||
|
ratio = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float range = max - min;
|
||||||
|
ratio = (i - min) / range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert our PluginConfigColor to RGBColor
|
||||||
|
RGBColor rgbColorLeft = new RGBColor(colorLeft.Vector.X, colorLeft.Vector.Y, colorLeft.Vector.Z);
|
||||||
|
RGBColor rgbColorRight = new RGBColor(colorRight.Vector.X, colorRight.Vector.Y, colorRight.Vector.Z);
|
||||||
|
|
||||||
|
//Interpolate our Alpha now
|
||||||
|
float alpha = LinearInterpolation(colorLeft.Vector.W, colorRight.Vector.W, ratio);
|
||||||
|
|
||||||
|
if (ratio >= 1 && useMaxColor)
|
||||||
|
{
|
||||||
|
return new PluginConfigColor(new Vector4((float)colorMax.Vector.X, (float)colorMax.Vector.Y, (float)colorMax.Vector.Z, colorMax.Vector.W));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Allow the users to select different blend modes since interpolating between two colors can result in different blending depending on the color space
|
||||||
|
//We convert our RGBColor values into different color spaces. We then interpolate each channel before converting the color back into RGBColor space
|
||||||
|
switch (blendMode)
|
||||||
|
{
|
||||||
|
case BlendMode.LAB:
|
||||||
|
{
|
||||||
|
//convert RGB to LAB
|
||||||
|
LabColor LabLeft = _rgbToLab.Convert(rgbColorLeft);
|
||||||
|
LabColor LabRight = _rgbToLab.Convert(rgbColorRight);
|
||||||
|
|
||||||
|
RGBColor Lab2RGB = _labToRgb.Convert(
|
||||||
|
new LabColor(
|
||||||
|
LinearInterpolation((float)LabLeft.L, (float)LabRight.L, ratio),
|
||||||
|
LinearInterpolation((float)LabLeft.a, (float)LabRight.a, ratio),
|
||||||
|
LinearInterpolation((float)LabLeft.b, (float)LabRight.b, ratio)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Lab2RGB.NormalizeIntensity();
|
||||||
|
return new PluginConfigColor(new Vector4((float)Lab2RGB.R, (float)Lab2RGB.G, (float)Lab2RGB.B, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
case BlendMode.LChab:
|
||||||
|
{
|
||||||
|
//convert RGB to LChab
|
||||||
|
LChabColor LChabLeft = _rgbToLChab.Convert(rgbColorLeft);
|
||||||
|
LChabColor LChabRight = _rgbToLChab.Convert(rgbColorRight);
|
||||||
|
|
||||||
|
RGBColor LChab2RGB = _lchabToRgb.Convert(
|
||||||
|
new LChabColor(
|
||||||
|
LinearInterpolation((float)LChabLeft.L, (float)LChabRight.L, ratio),
|
||||||
|
LinearInterpolation((float)LChabLeft.C, (float)LChabRight.C, ratio),
|
||||||
|
LinearInterpolation((float)LChabLeft.h, (float)LChabRight.h, ratio)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
LChab2RGB.NormalizeIntensity();
|
||||||
|
|
||||||
|
return new PluginConfigColor(new Vector4((float)LChab2RGB.R, (float)LChab2RGB.G, (float)LChab2RGB.B, alpha));
|
||||||
|
}
|
||||||
|
case BlendMode.XYZ:
|
||||||
|
{
|
||||||
|
//convert RGB to XYZ
|
||||||
|
XYZColor XYZLeft = _rgbToXyz.Convert(rgbColorLeft);
|
||||||
|
XYZColor XYZRight = _rgbToXyz.Convert(rgbColorRight);
|
||||||
|
|
||||||
|
RGBColor XYZ2RGB = _xyzToRgb.Convert(
|
||||||
|
new XYZColor(
|
||||||
|
LinearInterpolation((float)XYZLeft.X, (float)XYZRight.X, ratio),
|
||||||
|
LinearInterpolation((float)XYZLeft.Y, (float)XYZRight.Y, ratio),
|
||||||
|
LinearInterpolation((float)XYZLeft.Z, (float)XYZRight.Z, ratio)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
XYZ2RGB.NormalizeIntensity();
|
||||||
|
|
||||||
|
return new PluginConfigColor(new Vector4((float)XYZ2RGB.R, (float)XYZ2RGB.G, (float)XYZ2RGB.B, alpha));
|
||||||
|
}
|
||||||
|
case BlendMode.RGB:
|
||||||
|
{
|
||||||
|
//No conversion needed here because we are already working in RGB space
|
||||||
|
RGBColor newRGB = new RGBColor(
|
||||||
|
LinearInterpolation((float)rgbColorLeft.R, (float)rgbColorRight.R, ratio),
|
||||||
|
LinearInterpolation((float)rgbColorLeft.G, (float)rgbColorRight.G, ratio),
|
||||||
|
LinearInterpolation((float)rgbColorLeft.B, (float)rgbColorRight.B, ratio)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new PluginConfigColor(new Vector4((float)newRGB.R, (float)newRGB.G, (float)newRGB.B, alpha));
|
||||||
|
}
|
||||||
|
case BlendMode.LChuv:
|
||||||
|
{
|
||||||
|
//convert RGB to LChuv
|
||||||
|
LChuvColor LChuvLeft = _rgbToLChuv.Convert(rgbColorLeft);
|
||||||
|
LChuvColor LChuvRight = _rgbToLChuv.Convert(rgbColorRight);
|
||||||
|
|
||||||
|
RGBColor LChuv2RGB = _lchuvToRgb.Convert(
|
||||||
|
new LChuvColor(
|
||||||
|
LinearInterpolation((float)LChuvLeft.L, (float)LChuvRight.L, ratio),
|
||||||
|
LinearInterpolation((float)LChuvLeft.C, (float)LChuvRight.C, ratio),
|
||||||
|
LinearInterpolation((float)LChuvLeft.h, (float)LChuvRight.h, ratio)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
LChuv2RGB.NormalizeIntensity();
|
||||||
|
|
||||||
|
return new PluginConfigColor(new Vector4((float)LChuv2RGB.R, (float)LChuv2RGB.G, (float)LChuv2RGB.B, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
case BlendMode.Luv:
|
||||||
|
{
|
||||||
|
//convert RGB to Luv
|
||||||
|
LuvColor LuvLeft = _rgbToLuv.Convert(rgbColorLeft);
|
||||||
|
LuvColor LuvRight = _rgbToLuv.Convert(rgbColorRight);
|
||||||
|
|
||||||
|
RGBColor Luv2RGB = _luvToRgb.Convert(
|
||||||
|
new LuvColor(
|
||||||
|
LinearInterpolation((float)LuvLeft.L, (float)LuvRight.L, ratio),
|
||||||
|
LinearInterpolation((float)LuvLeft.u, (float)LuvRight.u, ratio),
|
||||||
|
LinearInterpolation((float)LuvLeft.v, (float)LuvRight.v, ratio)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Luv2RGB.NormalizeIntensity();
|
||||||
|
|
||||||
|
return new PluginConfigColor(new Vector4((float)Luv2RGB.R, (float)Luv2RGB.G, (float)Luv2RGB.B, alpha));
|
||||||
|
|
||||||
|
}
|
||||||
|
case BlendMode.Jzazbz:
|
||||||
|
{
|
||||||
|
//convert RGB to Jzazbz
|
||||||
|
JzazbzColor JzazbzLeft = _rgbToJzazbz.Convert(rgbColorLeft);
|
||||||
|
JzazbzColor JzazbzRight = _rgbToJzazbz.Convert(rgbColorRight);
|
||||||
|
|
||||||
|
RGBColor Jzazbz2RGB = _jzazbzToRgb.Convert(
|
||||||
|
new JzazbzColor(
|
||||||
|
LinearInterpolation((float)JzazbzLeft.Jz, (float)JzazbzRight.Jz, ratio),
|
||||||
|
LinearInterpolation((float)JzazbzLeft.az, (float)JzazbzRight.az, ratio),
|
||||||
|
LinearInterpolation((float)JzazbzLeft.bz, (float)JzazbzRight.bz, ratio)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Jzazbz2RGB.NormalizeIntensity();
|
||||||
|
|
||||||
|
return new PluginConfigColor(new Vector4((float)Jzazbz2RGB.R, (float)Jzazbz2RGB.G, (float)Jzazbz2RGB.B, alpha));
|
||||||
|
}
|
||||||
|
case BlendMode.JzCzhz:
|
||||||
|
{
|
||||||
|
//convert RGB to JzCzhz
|
||||||
|
JzCzhzColor JzCzhzLeft = _rgbToJzCzhz.Convert(rgbColorLeft);
|
||||||
|
JzCzhzColor JzCzhzRight = _rgbToJzCzhz.Convert(rgbColorRight);
|
||||||
|
|
||||||
|
RGBColor JzCzhz2RGB = _jzCzhzToRgb.Convert(
|
||||||
|
new JzCzhzColor(
|
||||||
|
LinearInterpolation((float)JzCzhzLeft.Jz, (float)JzCzhzRight.Jz, ratio),
|
||||||
|
LinearInterpolation((float)JzCzhzLeft.Cz, (float)JzCzhzRight.Cz, ratio),
|
||||||
|
LinearInterpolation((float)JzCzhzLeft.hz, (float)JzCzhzRight.hz, ratio)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
JzCzhz2RGB.NormalizeIntensity();
|
||||||
|
|
||||||
|
return new PluginConfigColor(new Vector4((float)JzCzhz2RGB.R, (float)JzCzhz2RGB.G, (float)JzCzhz2RGB.B, alpha));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new(Vector4.One);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PluginConfigColor ColorForActor(IGameObject? actor)
|
||||||
|
{
|
||||||
|
if (actor == null || actor is not ICharacter character)
|
||||||
|
{
|
||||||
|
return GlobalColors.Instance.NPCNeutralColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character.ObjectKind == ObjectKind.Player ||
|
||||||
|
character.SubKind == 9 && character.ClassJob.RowId > 0)
|
||||||
|
{
|
||||||
|
return GlobalColors.Instance.SafeColorForJobId(character.ClassJob.RowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isHostile = Utils.IsHostile(character);
|
||||||
|
|
||||||
|
if (character is IBattleNpc npc)
|
||||||
|
{
|
||||||
|
if ((npc.BattleNpcKind == BattleNpcSubKind.Enemy || npc.BattleNpcKind == BattleNpcSubKind.BattleNpcPart) && isHostile)
|
||||||
|
{
|
||||||
|
return GlobalColors.Instance.NPCHostileColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return GlobalColors.Instance.NPCFriendlyColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isHostile ? GlobalColors.Instance.NPCNeutralColor : GlobalColors.Instance.NPCFriendlyColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PluginConfigColor? ColorForCharacter(
|
||||||
|
IGameObject? gameObject,
|
||||||
|
uint currentHp = 0,
|
||||||
|
uint maxHp = 0,
|
||||||
|
bool useJobColor = false,
|
||||||
|
bool useRoleColor = false,
|
||||||
|
ColorByHealthValueConfig? colorByHealthConfig = null)
|
||||||
|
{
|
||||||
|
ICharacter? character = gameObject as ICharacter;
|
||||||
|
|
||||||
|
if (useJobColor && character != null)
|
||||||
|
{
|
||||||
|
return ColorForActor(character);
|
||||||
|
}
|
||||||
|
else if (useRoleColor)
|
||||||
|
{
|
||||||
|
return character is IPlayerCharacter ?
|
||||||
|
GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId) :
|
||||||
|
ColorForActor(character);
|
||||||
|
}
|
||||||
|
else if (colorByHealthConfig != null && colorByHealthConfig.Enabled && character != null)
|
||||||
|
{
|
||||||
|
var scale = (float)currentHp / Math.Max(1, maxHp);
|
||||||
|
if (colorByHealthConfig.UseJobColorAsMaxHealth)
|
||||||
|
{
|
||||||
|
return GetColorByScale(
|
||||||
|
scale,
|
||||||
|
colorByHealthConfig.LowHealthColorThreshold / 100f,
|
||||||
|
colorByHealthConfig.FullHealthColorThreshold / 100f,
|
||||||
|
colorByHealthConfig.LowHealthColor,
|
||||||
|
colorByHealthConfig.FullHealthColor,
|
||||||
|
ColorForActor(character),
|
||||||
|
colorByHealthConfig.UseMaxHealthColor,
|
||||||
|
colorByHealthConfig.BlendMode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (colorByHealthConfig.UseRoleColorAsMaxHealth)
|
||||||
|
{
|
||||||
|
return GetColorByScale(scale,
|
||||||
|
colorByHealthConfig.LowHealthColorThreshold / 100f,
|
||||||
|
colorByHealthConfig.FullHealthColorThreshold / 100f,
|
||||||
|
colorByHealthConfig.LowHealthColor, colorByHealthConfig.FullHealthColor,
|
||||||
|
character is IPlayerCharacter ? GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId) : ColorForActor(character),
|
||||||
|
colorByHealthConfig.UseMaxHealthColor,
|
||||||
|
colorByHealthConfig.BlendMode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return GetColorByScale(scale, colorByHealthConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using HSUI.Interface.Jobs;
|
||||||
|
using HSUI.Interface.StatusEffects;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public static class DraggablesHelper
|
||||||
|
{
|
||||||
|
public static void DrawGrid(GridConfig config, HUDOptionsConfig? hudConfig, DraggableHudElement? selectedElement)
|
||||||
|
{
|
||||||
|
ImGui.SetNextWindowPos(Vector2.Zero);
|
||||||
|
ImGui.SetNextWindowSize(ImGui.GetMainViewport().Size);
|
||||||
|
|
||||||
|
ImGui.SetNextWindowBgAlpha(config.BackgroundAlpha);
|
||||||
|
|
||||||
|
ImGui.Begin("DelvUI_grid",
|
||||||
|
ImGuiWindowFlags.NoTitleBar
|
||||||
|
| ImGuiWindowFlags.NoScrollbar
|
||||||
|
| ImGuiWindowFlags.AlwaysAutoResize
|
||||||
|
| ImGuiWindowFlags.NoInputs
|
||||||
|
| ImGuiWindowFlags.NoDecoration
|
||||||
|
| ImGuiWindowFlags.NoBringToFrontOnFocus
|
||||||
|
| ImGuiWindowFlags.NoFocusOnAppearing
|
||||||
|
);
|
||||||
|
|
||||||
|
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
|
||||||
|
Vector2 screenSize = ImGui.GetMainViewport().Size;
|
||||||
|
Vector2 offset = hudConfig != null && hudConfig.UseGlobalHudShift ? hudConfig.HudOffset : Vector2.Zero;
|
||||||
|
Vector2 center = screenSize / 2f + offset;
|
||||||
|
|
||||||
|
// grid
|
||||||
|
if (config.ShowGrid)
|
||||||
|
{
|
||||||
|
int count = (int)(Math.Max(screenSize.X, screenSize.Y) / config.GridDivisionsDistance) / 2 + 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var step = i * config.GridDivisionsDistance;
|
||||||
|
|
||||||
|
drawList.AddLine(new Vector2(center.X + step, 0), new Vector2(center.X + step, screenSize.Y), 0x88888888);
|
||||||
|
drawList.AddLine(new Vector2(center.X - step, 0), new Vector2(center.X - step, screenSize.Y), 0x88888888);
|
||||||
|
|
||||||
|
drawList.AddLine(new Vector2(0, center.Y + step), new Vector2(screenSize.X, center.Y + step), 0x88888888);
|
||||||
|
drawList.AddLine(new Vector2(0, center.Y - step), new Vector2(screenSize.X, center.Y - step), 0x88888888);
|
||||||
|
|
||||||
|
if (config.GridSubdivisionCount > 1)
|
||||||
|
{
|
||||||
|
for (int j = 1; j < config.GridSubdivisionCount; j++)
|
||||||
|
{
|
||||||
|
var subStep = j * (config.GridDivisionsDistance / config.GridSubdivisionCount);
|
||||||
|
|
||||||
|
drawList.AddLine(new Vector2(center.X + step + subStep, 0), new Vector2(center.X + step + subStep, screenSize.Y), 0x44888888);
|
||||||
|
drawList.AddLine(new Vector2(center.X - step - subStep, 0), new Vector2(center.X - step - subStep, screenSize.Y), 0x44888888);
|
||||||
|
|
||||||
|
drawList.AddLine(new Vector2(0, center.Y + step + subStep), new Vector2(screenSize.X, center.Y + step + subStep), 0x44888888);
|
||||||
|
drawList.AddLine(new Vector2(0, center.Y - step - subStep), new Vector2(screenSize.X, center.Y - step - subStep), 0x44888888);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// center lines
|
||||||
|
if (config.ShowCenterLines)
|
||||||
|
{
|
||||||
|
drawList.AddLine(new Vector2(center.X, 0), new Vector2(center.X, screenSize.Y), 0xAAFFFFFF);
|
||||||
|
drawList.AddLine(new Vector2(0, center.Y), new Vector2(screenSize.X, center.Y), 0xAAFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.ShowAnchorPoints && selectedElement != null)
|
||||||
|
{
|
||||||
|
Vector2 parentAnchorPos = center + selectedElement.ParentPos();
|
||||||
|
Vector2 anchorPos = parentAnchorPos + selectedElement.GetConfig().Position;
|
||||||
|
|
||||||
|
drawList.AddLine(parentAnchorPos, anchorPos, 0xAA0000FF, 2);
|
||||||
|
|
||||||
|
var anchorSize = new Vector2(10, 10);
|
||||||
|
drawList.AddRectFilled(anchorPos - anchorSize / 2f, anchorPos + anchorSize / 2f, 0xAA0000FF);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawElements(
|
||||||
|
Vector2 origin,
|
||||||
|
HudHelper hudHelper,
|
||||||
|
IList<DraggableHudElement> elements,
|
||||||
|
JobHud? jobHud,
|
||||||
|
DraggableHudElement? selectedElement)
|
||||||
|
{
|
||||||
|
foreach (DraggableHudElement element in elements)
|
||||||
|
{
|
||||||
|
if (!hudHelper.IsElementHidden(element))
|
||||||
|
{
|
||||||
|
element.PrepareForDraw(origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jobHud?.PrepareForDraw(origin);
|
||||||
|
|
||||||
|
bool clip = ConfigurationManager.Instance?.LockHUD == true &&
|
||||||
|
ClipRectsHelper.Instance?.Enabled == true &&
|
||||||
|
ClipRectsHelper.Instance?.Mode == WindowClippingMode.Performance;
|
||||||
|
|
||||||
|
bool needsDraw = true;
|
||||||
|
|
||||||
|
if (clip)
|
||||||
|
{
|
||||||
|
ClipRect? clipRect = ClipRectsHelper.Instance?.GetClipRectForArea(Vector2.Zero, ImGui.GetMainViewport().Size);
|
||||||
|
if (clipRect.HasValue)
|
||||||
|
{
|
||||||
|
needsDraw = false;
|
||||||
|
|
||||||
|
ClipRect[] invertedClipRects = ClipRectsHelper.GetInvertedClipRects(clipRect.Value);
|
||||||
|
for (int i = 0; i < invertedClipRects.Length; i++)
|
||||||
|
{
|
||||||
|
ImGui.PushClipRect(invertedClipRects[i].Min, invertedClipRects[i].Max, false);
|
||||||
|
Draw(origin, hudHelper, elements, jobHud, selectedElement);
|
||||||
|
ImGui.PopClipRect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsDraw)
|
||||||
|
{
|
||||||
|
Draw(origin, hudHelper, elements, jobHud, selectedElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Draw(
|
||||||
|
Vector2 origin,
|
||||||
|
HudHelper hudHelper,
|
||||||
|
IList<DraggableHudElement> elements,
|
||||||
|
JobHud? jobHud,
|
||||||
|
DraggableHudElement? selectedElement)
|
||||||
|
{
|
||||||
|
bool canTakeInput = true;
|
||||||
|
bool jobHudNeedsDraw = jobHud != null && jobHud != selectedElement && !hudHelper.IsElementHidden(jobHud);
|
||||||
|
|
||||||
|
// selected
|
||||||
|
if (selectedElement != null)
|
||||||
|
{
|
||||||
|
if (!hudHelper.IsElementHidden(selectedElement))
|
||||||
|
{
|
||||||
|
selectedElement.CanTakeInputForDrag = true;
|
||||||
|
selectedElement.Draw(origin);
|
||||||
|
canTakeInput = !selectedElement.NeedsInputForDrag;
|
||||||
|
}
|
||||||
|
else if (selectedElement is IHudElementWithMouseOver elementWithMouseOver)
|
||||||
|
{
|
||||||
|
elementWithMouseOver.StopMouseover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all
|
||||||
|
foreach (DraggableHudElement element in elements)
|
||||||
|
{
|
||||||
|
if (element == selectedElement) { continue; }
|
||||||
|
|
||||||
|
if (jobHudNeedsDraw && jobHud != null && element.GetConfig().StrataLevel > jobHud.GetConfig().StrataLevel)
|
||||||
|
{
|
||||||
|
jobHud.CanTakeInputForDrag = canTakeInput;
|
||||||
|
jobHud.Draw(origin);
|
||||||
|
jobHudNeedsDraw = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hudHelper.IsElementHidden(element))
|
||||||
|
{
|
||||||
|
element.CanTakeInputForDrag = canTakeInput;
|
||||||
|
element.Draw(origin);
|
||||||
|
canTakeInput = !canTakeInput ? false : !element.NeedsInputForDrag;
|
||||||
|
}
|
||||||
|
else if (element is IHudElementWithMouseOver elementWithMouseOver)
|
||||||
|
{
|
||||||
|
elementWithMouseOver.StopMouseover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool DrawArrows(Vector2 position, Vector2 size, string tooltipText, out Vector2 offset)
|
||||||
|
{
|
||||||
|
offset = Vector2.Zero;
|
||||||
|
|
||||||
|
var windowFlags = ImGuiWindowFlags.NoScrollbar
|
||||||
|
| ImGuiWindowFlags.NoTitleBar
|
||||||
|
| ImGuiWindowFlags.NoResize
|
||||||
|
| ImGuiWindowFlags.NoBackground
|
||||||
|
| ImGuiWindowFlags.NoDecoration
|
||||||
|
| ImGuiWindowFlags.NoSavedSettings;
|
||||||
|
|
||||||
|
var margin = new Vector2(4, 0);
|
||||||
|
var windowSize = ArrowSize + margin * 2;
|
||||||
|
|
||||||
|
// left, right, up, down
|
||||||
|
var positions = GetArrowPositions(position, size);
|
||||||
|
var offsets = new Vector2[]
|
||||||
|
{
|
||||||
|
new Vector2(-1, 0),
|
||||||
|
new Vector2(1, 0),
|
||||||
|
new Vector2(0, -1),
|
||||||
|
new Vector2(0, 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
var pos = positions[i] - margin;
|
||||||
|
|
||||||
|
ImGui.SetNextWindowSize(windowSize, ImGuiCond.Always);
|
||||||
|
ImGui.SetNextWindowPos(pos);
|
||||||
|
|
||||||
|
ImGui.Begin("DelvUI_draggablesArrow " + i.ToString(), windowFlags);
|
||||||
|
|
||||||
|
// fake button
|
||||||
|
ImGuiP.ArrowButtonEx($"arrow button {i}", (ImGuiDir)i, new Vector2(ArrowSize.X, ArrowSize.Y));
|
||||||
|
if (ImGui.IsMouseHoveringRect(pos, pos + windowSize))
|
||||||
|
{
|
||||||
|
// track click manually to not deal with window focus stuff
|
||||||
|
if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
||||||
|
{
|
||||||
|
offset = offsets[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// tooltip
|
||||||
|
TooltipsHelper.Instance.ShowTooltipOnCursor(tooltipText);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset != Vector2.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2 ArrowSize = new Vector2(40, 40);
|
||||||
|
|
||||||
|
public static Vector2[] GetArrowPositions(Vector2 position, Vector2 size)
|
||||||
|
{
|
||||||
|
return GetArrowPositions(position, size, ArrowSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2[] GetArrowPositions(Vector2 position, Vector2 size, Vector2 arrowSize)
|
||||||
|
{
|
||||||
|
return new Vector2[]
|
||||||
|
{
|
||||||
|
new Vector2(position.X - arrowSize.X + 10, position.Y + size.Y / 2f - arrowSize.Y / 2f - 2),
|
||||||
|
new Vector2(position.X + size.X - 8, position.Y + size.Y / 2f - arrowSize.Y / 2f - 2),
|
||||||
|
new Vector2(position.X + size.X / 2f - arrowSize.X / 2f + 2, position.Y - arrowSize.Y + 1),
|
||||||
|
new Vector2(position.X + size.X / 2f - arrowSize.X / 2f + 2, position.Y + size.Y - 7)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Lumina.Excel;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public enum GradientDirection
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Right,
|
||||||
|
Left,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
CenteredHorizonal
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DrawHelper
|
||||||
|
{
|
||||||
|
private static uint[] ColorArray(PluginConfigColor color, GradientDirection gradientDirection)
|
||||||
|
{
|
||||||
|
return gradientDirection switch
|
||||||
|
{
|
||||||
|
GradientDirection.None => new[] { color.Base, color.Base, color.Base, color.Base },
|
||||||
|
GradientDirection.Right => new[] { color.TopGradient, color.BottomGradient, color.BottomGradient, color.TopGradient },
|
||||||
|
GradientDirection.Left => new[] { color.BottomGradient, color.TopGradient, color.TopGradient, color.BottomGradient },
|
||||||
|
GradientDirection.Up => new[] { color.BottomGradient, color.BottomGradient, color.TopGradient, color.TopGradient },
|
||||||
|
_ => new[] { color.TopGradient, color.TopGradient, color.BottomGradient, color.BottomGradient }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector2 GetBarTextureUV1Vector(Vector2 size, int textureWidth, int textureHeight, BarTextureDrawMode drawMode)
|
||||||
|
{
|
||||||
|
if (drawMode == BarTextureDrawMode.Stretch) { return new Vector2(1); }
|
||||||
|
|
||||||
|
float x = drawMode == BarTextureDrawMode.RepeatVertical ? 1 : (float)size.X / textureWidth;
|
||||||
|
float y = drawMode == BarTextureDrawMode.RepeatHorizontal ? 1 : (float)size.Y / textureHeight;
|
||||||
|
|
||||||
|
return new Vector2(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawBarTexture(Vector2 position, Vector2 size, PluginConfigColor color, string? name, BarTextureDrawMode drawMode, ImDrawListPtr drawList)
|
||||||
|
{
|
||||||
|
IDalamudTextureWrap? texture = BarTexturesManager.Instance?.GetBarTexture(name);
|
||||||
|
if (texture == null)
|
||||||
|
{
|
||||||
|
DrawGradientFilledRect(position, size, color, drawList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 uv0 = new Vector2(0);
|
||||||
|
Vector2 uv1 = GetBarTextureUV1Vector(size, texture.Width, texture.Height, drawMode);
|
||||||
|
|
||||||
|
drawList.AddImage(texture.Handle, position, position + size, uv0, uv1, color.Base);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawGradientFilledRect(Vector2 position, Vector2 size, PluginConfigColor color, ImDrawListPtr drawList)
|
||||||
|
{
|
||||||
|
GradientDirection gradientDirection = ConfigurationManager.Instance.GradientDirection;
|
||||||
|
DrawGradientFilledRect(position, size, color, drawList, gradientDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawGradientFilledRect(Vector2 position, Vector2 size, PluginConfigColor color, ImDrawListPtr drawList, GradientDirection gradientDirection = GradientDirection.Down)
|
||||||
|
{
|
||||||
|
uint[]? colorArray = ColorArray(color, gradientDirection);
|
||||||
|
|
||||||
|
if (gradientDirection == GradientDirection.CenteredHorizonal)
|
||||||
|
{
|
||||||
|
Vector2 halfSize = new(size.X, size.Y / 2f);
|
||||||
|
drawList.AddRectFilledMultiColor(
|
||||||
|
position, position + halfSize,
|
||||||
|
colorArray[0], colorArray[1], colorArray[2], colorArray[3]
|
||||||
|
);
|
||||||
|
|
||||||
|
Vector2 pos = position + new Vector2(0, halfSize.Y);
|
||||||
|
drawList.AddRectFilledMultiColor(
|
||||||
|
pos, pos + halfSize,
|
||||||
|
colorArray[3], colorArray[2], colorArray[1], colorArray[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
drawList.AddRectFilledMultiColor(
|
||||||
|
position, position + size,
|
||||||
|
colorArray[0], colorArray[1], colorArray[2], colorArray[3]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawOutlinedText(string text, Vector2 pos, ImDrawListPtr drawList, int thickness = 1)
|
||||||
|
{
|
||||||
|
DrawOutlinedText(text, pos, 0xFFFFFFFF, 0xFF000000, drawList, thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawOutlinedText(string text, Vector2 pos, uint color, uint outlineColor, ImDrawListPtr drawList, int thickness = 1)
|
||||||
|
{
|
||||||
|
// outline
|
||||||
|
for (int i = 1; i < thickness + 1; i++)
|
||||||
|
{
|
||||||
|
drawList.AddText(new Vector2(pos.X - i, pos.Y + i), outlineColor, text);
|
||||||
|
drawList.AddText(new Vector2(pos.X, pos.Y + i), outlineColor, text);
|
||||||
|
drawList.AddText(new Vector2(pos.X + i, pos.Y + i), outlineColor, text);
|
||||||
|
drawList.AddText(new Vector2(pos.X - i, pos.Y), outlineColor, text);
|
||||||
|
drawList.AddText(new Vector2(pos.X + i, pos.Y), outlineColor, text);
|
||||||
|
drawList.AddText(new Vector2(pos.X - i, pos.Y - i), outlineColor, text);
|
||||||
|
drawList.AddText(new Vector2(pos.X, pos.Y - i), outlineColor, text);
|
||||||
|
drawList.AddText(new Vector2(pos.X + i, pos.Y - i), outlineColor, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// text
|
||||||
|
drawList.AddText(new Vector2(pos.X, pos.Y), color, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawShadowText(string text, Vector2 pos, uint color, uint shadowColor, ImDrawListPtr drawList, int offset = 1, int thickness = 1)
|
||||||
|
{
|
||||||
|
// TODO: Add parameter to allow to choose a direction
|
||||||
|
|
||||||
|
// Shadow
|
||||||
|
for (int i = 0; i < thickness; i++)
|
||||||
|
{
|
||||||
|
drawList.AddText(new Vector2(pos.X + i + offset, pos.Y + i + offset), shadowColor, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text
|
||||||
|
drawList.AddText(new Vector2(pos.X, pos.Y), color, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawIcon<T>(dynamic row, Vector2 position, Vector2 size, bool drawBorder, bool cropIcon, int stackCount = 1) where T : struct, IExcelRow<T>
|
||||||
|
{
|
||||||
|
IDalamudTextureWrap texture = TexturesHelper.GetTexture<T>(row, (uint)Math.Max(0, stackCount - 1));
|
||||||
|
if (texture == null) { return; }
|
||||||
|
|
||||||
|
(Vector2 uv0, Vector2 uv1) = GetTexCoordinates(texture, size, cropIcon);
|
||||||
|
|
||||||
|
ImGui.SetCursorPos(position);
|
||||||
|
ImGui.Image(texture.Handle, size, uv0, uv1);
|
||||||
|
|
||||||
|
if (drawBorder)
|
||||||
|
{
|
||||||
|
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
|
||||||
|
drawList.AddRect(position, position + size, 0xFF000000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawIcon<T>(ImDrawListPtr drawList, dynamic row, Vector2 position, Vector2 size, bool drawBorder, bool cropIcon, int stackCount = 1) where T : struct, IExcelRow<T>
|
||||||
|
{
|
||||||
|
IDalamudTextureWrap texture = TexturesHelper.GetTexture<T>(row, (uint)Math.Max(0, stackCount - 1));
|
||||||
|
if (texture == null) { return; }
|
||||||
|
|
||||||
|
(Vector2 uv0, Vector2 uv1) = GetTexCoordinates(texture, size, cropIcon);
|
||||||
|
|
||||||
|
drawList.AddImage(texture.Handle, position, position + size, uv0, uv1);
|
||||||
|
|
||||||
|
if (drawBorder)
|
||||||
|
{
|
||||||
|
drawList.AddRect(position, position + size, 0xFF000000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawIcon(uint iconId, Vector2 position, Vector2 size, bool drawBorder, ImDrawListPtr drawList)
|
||||||
|
{
|
||||||
|
DrawIcon(iconId, position, size, drawBorder, 0xFFFFFFFF, drawList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawIcon(uint iconId, Vector2 position, Vector2 size, bool drawBorder, float alpha, ImDrawListPtr drawList)
|
||||||
|
{
|
||||||
|
uint a = (uint)(alpha * 255);
|
||||||
|
uint color = 0xFFFFFF + (a << 24);
|
||||||
|
DrawIcon(iconId, position, size, drawBorder, color, drawList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void DrawIcon(uint iconId, Vector2 position, Vector2 size, bool drawBorder, uint color, ImDrawListPtr drawList)
|
||||||
|
{
|
||||||
|
IDalamudTextureWrap? texture = TexturesHelper.GetTextureFromIconId(iconId);
|
||||||
|
if (texture == null) { return; }
|
||||||
|
|
||||||
|
drawList.AddImage(texture.Handle, position, position + size, Vector2.Zero, Vector2.One, color);
|
||||||
|
|
||||||
|
if (drawBorder)
|
||||||
|
{
|
||||||
|
drawList.AddRect(position, position + size, 0xFF000000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (Vector2, Vector2) GetTexCoordinates(IDalamudTextureWrap texture, Vector2 size, bool cropIcon = true)
|
||||||
|
{
|
||||||
|
if (texture == null)
|
||||||
|
{
|
||||||
|
return (Vector2.Zero, Vector2.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status = 24x32, show from 2,7 until 22,26
|
||||||
|
//show from 0,0 until 24,32 for uncropped status icon
|
||||||
|
|
||||||
|
float uv0x = cropIcon ? 4f : 1f;
|
||||||
|
float uv0y = cropIcon ? 14f : 1f;
|
||||||
|
|
||||||
|
float uv1x = cropIcon ? 4f : 1f;
|
||||||
|
float uv1y = cropIcon ? 12f : 1f;
|
||||||
|
|
||||||
|
Vector2 uv0 = new(uv0x / texture.Width, uv0y / texture.Height);
|
||||||
|
Vector2 uv1 = new(1f - uv1x / texture.Width, 1f - uv1y / texture.Height);
|
||||||
|
|
||||||
|
return (uv0, uv1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawIconCooldown(Vector2 position, Vector2 size, float elapsed, float total, ImDrawListPtr drawList)
|
||||||
|
{
|
||||||
|
float completion = elapsed / total;
|
||||||
|
int segments = (int)Math.Ceiling(completion * 4);
|
||||||
|
|
||||||
|
Vector2 center = position + size / 2;
|
||||||
|
|
||||||
|
//Define vertices for top, left, bottom, and right points relative to the center.
|
||||||
|
Vector2[] vertices =
|
||||||
|
[
|
||||||
|
center with {Y = center.Y - size.Y}, // Top
|
||||||
|
center with {X = center.X - size.X}, // Left
|
||||||
|
center with {Y = center.Y + size.Y}, // Bottom
|
||||||
|
center with {X = center.X + size.X} // Right
|
||||||
|
];
|
||||||
|
|
||||||
|
ImGui.PushClipRect(position, position + size, false);
|
||||||
|
for (int i = 0; i < segments; i++)
|
||||||
|
{
|
||||||
|
Vector2 v2 = vertices[i % 4];
|
||||||
|
Vector2 v3 = vertices[(i + 1) % 4];
|
||||||
|
|
||||||
|
|
||||||
|
if (i == segments - 1)
|
||||||
|
{ // If drawing the last segment, adjust the second vertex based on the cooldown.
|
||||||
|
float angle = 2 * MathF.PI * (1 - completion);
|
||||||
|
float cos = MathF.Cos(angle);
|
||||||
|
float sin = MathF.Sin(angle);
|
||||||
|
|
||||||
|
v3 = center + Vector2.Multiply(new Vector2(sin, -cos), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawList.AddTriangleFilled(center, v3, v2, 0xCC000000);
|
||||||
|
}
|
||||||
|
ImGui.PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawOvershield(float shield, Vector2 cursorPos, Vector2 barSize, float height, bool useRatioForHeight, PluginConfigColor color, ImDrawListPtr drawList)
|
||||||
|
{
|
||||||
|
if (shield == 0) { return; }
|
||||||
|
|
||||||
|
float h = useRatioForHeight ? barSize.Y / 100 * height : height;
|
||||||
|
|
||||||
|
DrawGradientFilledRect(cursorPos, new Vector2(Math.Max(1, barSize.X * shield), h), color, drawList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawShield(float shield, float hp, Vector2 cursorPos, Vector2 barSize, float height, bool useRatioForHeight, PluginConfigColor color, ImDrawListPtr drawList)
|
||||||
|
{
|
||||||
|
if (shield == 0) { return; }
|
||||||
|
|
||||||
|
// on full hp just draw overshield
|
||||||
|
if (hp == 1)
|
||||||
|
{
|
||||||
|
DrawOvershield(shield, cursorPos, barSize, height, useRatioForHeight, color, drawList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hp portion
|
||||||
|
float h = useRatioForHeight ? barSize.Y / 100 * Math.Min(100, height) : height;
|
||||||
|
float missingHPRatio = 1 - hp;
|
||||||
|
float s = Math.Min(shield, missingHPRatio);
|
||||||
|
Vector2 shieldStartPos = cursorPos + new Vector2(Math.Max(1, barSize.X * hp), 0);
|
||||||
|
DrawGradientFilledRect(shieldStartPos, new Vector2(Math.Max(1, barSize.X * s), barSize.Y), color, drawList);
|
||||||
|
|
||||||
|
// overshield
|
||||||
|
shield -= s;
|
||||||
|
if (shield <= 0) { return; }
|
||||||
|
|
||||||
|
DrawGradientFilledRect(cursorPos, new Vector2(Math.Max(1, barSize.X * shield), h), color, drawList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawInWindow(string name, Vector2 pos, Vector2 size, bool needsInput, Action<ImDrawListPtr> drawAction)
|
||||||
|
{
|
||||||
|
const ImGuiWindowFlags windowFlags = ImGuiWindowFlags.NoTitleBar |
|
||||||
|
ImGuiWindowFlags.NoScrollbar |
|
||||||
|
ImGuiWindowFlags.NoBackground |
|
||||||
|
ImGuiWindowFlags.NoMove |
|
||||||
|
ImGuiWindowFlags.NoResize;
|
||||||
|
|
||||||
|
bool inputs = InputsHelper.Instance?.IsProxyEnabled == true ? false : needsInput;
|
||||||
|
|
||||||
|
DrawInWindow(name, pos, size, inputs, false, windowFlags, drawAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawInWindow(
|
||||||
|
string name,
|
||||||
|
Vector2 pos,
|
||||||
|
Vector2 size,
|
||||||
|
bool needsInput,
|
||||||
|
bool needsWindow,
|
||||||
|
ImGuiWindowFlags windowFlags,
|
||||||
|
Action<ImDrawListPtr> drawAction)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!ClipRectsHelper.Instance.Enabled || ClipRectsHelper.Instance.Mode == WindowClippingMode.Performance)
|
||||||
|
{
|
||||||
|
drawAction(ImGui.GetWindowDrawList());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
windowFlags |= ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBringToFrontOnFocus;
|
||||||
|
|
||||||
|
if (!needsInput)
|
||||||
|
{
|
||||||
|
windowFlags |= ImGuiWindowFlags.NoInputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipRect? clipRect = ClipRectsHelper.Instance.GetClipRectForArea(pos, size);
|
||||||
|
|
||||||
|
// no clipping needed
|
||||||
|
if (!ClipRectsHelper.Instance.Enabled || !clipRect.HasValue)
|
||||||
|
{
|
||||||
|
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
|
||||||
|
|
||||||
|
if (!needsInput && !needsWindow)
|
||||||
|
{
|
||||||
|
drawAction(drawList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetNextWindowPos(pos);
|
||||||
|
ImGui.SetNextWindowSize(size);
|
||||||
|
|
||||||
|
bool begin = ImGui.Begin(name, windowFlags);
|
||||||
|
if (!begin)
|
||||||
|
{
|
||||||
|
ImGui.End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawAction(drawList);
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
// clip around game's window
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// hide instead of clip?
|
||||||
|
if (ClipRectsHelper.Instance.Mode == WindowClippingMode.Hide) { return; }
|
||||||
|
|
||||||
|
ImGuiWindowFlags flags = windowFlags;
|
||||||
|
if (needsInput && clipRect.Value.Contains(ImGui.GetMousePos()))
|
||||||
|
{
|
||||||
|
flags |= ImGuiWindowFlags.NoInputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipRect[] invertedClipRects = ClipRectsHelper.GetInvertedClipRects(clipRect.Value);
|
||||||
|
for (int i = 0; i < invertedClipRects.Length; i++)
|
||||||
|
{
|
||||||
|
ImGui.SetNextWindowPos(pos);
|
||||||
|
ImGui.SetNextWindowSize(size);
|
||||||
|
ImGuiHelpers.ForceNextWindowMainViewport();
|
||||||
|
|
||||||
|
bool begin = ImGui.Begin(name + "_" + i, flags);
|
||||||
|
if (!begin)
|
||||||
|
{
|
||||||
|
ImGui.End();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PushClipRect(invertedClipRects[i].Min, invertedClipRects[i].Max, false);
|
||||||
|
drawAction(ImGui.GetWindowDrawList());
|
||||||
|
ImGui.PopClipRect();
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using FFXIVClientStructs.FFXIV.Client.LayoutEngine;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
|
using FFXIVClientStructs.STD;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public static class EncryptedStringsHelper
|
||||||
|
{
|
||||||
|
public static unsafe string GetString(string original)
|
||||||
|
{
|
||||||
|
if (!original.StartsWith("_rsv_"))
|
||||||
|
{
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TempLayoutWorld* layoutWorld = (TempLayoutWorld*)LayoutWorld.Instance();
|
||||||
|
StdMap<Utf8String, Pointer<byte>> map = layoutWorld->RsvMap[0];
|
||||||
|
Pointer<byte> demangled = map[new Utf8String(original)];
|
||||||
|
if (demangled.Value != null && Marshal.PtrToStringUTF8((IntPtr)demangled.Value) is { } result)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error reading rsv map:\n" + e.StackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 0x230)]
|
||||||
|
public unsafe struct TempLayoutWorld
|
||||||
|
{
|
||||||
|
[FieldOffset(0x220)] public StdMap<Utf8String, Pointer<byte>>* RsvMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using StructsFramework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public unsafe class ExperienceHelper
|
||||||
|
{
|
||||||
|
#region singleton
|
||||||
|
private static Lazy<ExperienceHelper> _lazyInstance = new Lazy<ExperienceHelper>(() => new ExperienceHelper());
|
||||||
|
private RaptureAtkModule* _raptureAtkModule = null;
|
||||||
|
private const int ExperienceIndex = 2;
|
||||||
|
|
||||||
|
public static ExperienceHelper Instance => _lazyInstance.Value;
|
||||||
|
|
||||||
|
~ExperienceHelper()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lazyInstance = new Lazy<ExperienceHelper>(() => new ExperienceHelper());
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public ExperienceHelper()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddonExp* GetExpAddon()
|
||||||
|
{
|
||||||
|
return (AddonExp*)Plugin.GameGui.GetAddonByName("_Exp", 1).Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint CurrentExp
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
AddonExp* addon = GetExpAddon();
|
||||||
|
return addon != null ? addon->CurrentExp : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint RequiredExp
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
AddonExp* addon = GetExpAddon();
|
||||||
|
return addon != null ? addon->RequiredExp : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint RestedExp
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
AddonExp* addon = GetExpAddon();
|
||||||
|
return addon != null ? addon->RestedExp : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float PercentExp
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
AddonExp* addon = GetExpAddon();
|
||||||
|
return addon != null ? addon->CurrentExpPercent : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool IsMaxLevel()
|
||||||
|
{
|
||||||
|
UIModule* uiModule = StructsFramework.Instance()->GetUIModule();
|
||||||
|
if (uiModule != null)
|
||||||
|
{
|
||||||
|
_raptureAtkModule = uiModule->GetRaptureAtkModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_raptureAtkModule == null || _raptureAtkModule->AtkModule.AtkArrayDataHolder.StringArrayCount <= ExperienceIndex)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stringArrayData = _raptureAtkModule->AtkModule.AtkArrayDataHolder.StringArrays[ExperienceIndex];
|
||||||
|
var expStringArray = stringArrayData->StringArray[69];
|
||||||
|
var expInfoString = MemoryHelper.ReadSeStringNullTerminated(new IntPtr(expStringArray));
|
||||||
|
return expInfoString.TextValue.Contains("-/-");
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error when receiving experience information: " + e.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
using Dalamud.Interface.GameFonts;
|
||||||
|
using Dalamud.Interface.ManagedFontAtlas;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public class FontScope : IDisposable
|
||||||
|
{
|
||||||
|
private readonly IFontHandle? _handle;
|
||||||
|
|
||||||
|
public FontScope(IFontHandle? handle)
|
||||||
|
{
|
||||||
|
_handle = handle;
|
||||||
|
_handle?.Push();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_handle?.Pop();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FontsManager : IDisposable
|
||||||
|
{
|
||||||
|
#region Singleton
|
||||||
|
private FontsManager(string basePath)
|
||||||
|
{
|
||||||
|
DefaultFontsPath = Path.GetDirectoryName(basePath) + "\\Media\\Fonts\\";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize(string basePath)
|
||||||
|
{
|
||||||
|
Instance = new FontsManager(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FontsManager Instance { get; private set; } = null!;
|
||||||
|
private FontsConfig? _config;
|
||||||
|
|
||||||
|
public void LoadConfig()
|
||||||
|
{
|
||||||
|
if (_config != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_config = ConfigurationManager.Instance.GetConfigObject<FontsConfig>();
|
||||||
|
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigReset(ConfigurationManager sender)
|
||||||
|
{
|
||||||
|
_config = sender.GetConfigObject<FontsConfig>();
|
||||||
|
}
|
||||||
|
|
||||||
|
~FontsManager()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public readonly string DefaultFontsPath;
|
||||||
|
|
||||||
|
public bool DefaultFontBuilt { get; private set; }
|
||||||
|
public IFontHandle? DefaultFont { get; private set; } = null!;
|
||||||
|
|
||||||
|
private List<IFontHandle> _fonts = new List<IFontHandle>();
|
||||||
|
public IReadOnlyCollection<IFontHandle> Fonts => _fonts.AsReadOnly();
|
||||||
|
|
||||||
|
public FontScope PushDefaultFont()
|
||||||
|
{
|
||||||
|
if (DefaultFontBuilt && DefaultFont != null)
|
||||||
|
{
|
||||||
|
return new FontScope(DefaultFont);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FontScope(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontScope PushFont(string? fontId)
|
||||||
|
{
|
||||||
|
if (fontId == null || _config == null || !_config.Fonts.ContainsKey(fontId))
|
||||||
|
{
|
||||||
|
return new FontScope(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = _config.Fonts.IndexOfKey(fontId);
|
||||||
|
if (index < 0 || index >= _fonts.Count)
|
||||||
|
{
|
||||||
|
return new FontScope(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FontScope(_fonts[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearFonts()
|
||||||
|
{
|
||||||
|
foreach (IFontHandle font in _fonts)
|
||||||
|
{
|
||||||
|
font.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_fonts.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void BuildFonts()
|
||||||
|
{
|
||||||
|
ClearFonts();
|
||||||
|
DefaultFontBuilt = false;
|
||||||
|
|
||||||
|
FontsConfig config = ConfigurationManager.Instance.GetConfigObject<FontsConfig>();
|
||||||
|
ImGuiIOPtr io = ImGui.GetIO();
|
||||||
|
ushort[]? ranges = GetCharacterRanges(config, io);
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, FontData> fontData in config.Fonts)
|
||||||
|
{
|
||||||
|
bool isGameFont = config.GameFontMap.ContainsValue(fontData.Value.Name);
|
||||||
|
string path = DefaultFontsPath + fontData.Value.Name + ".ttf";
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
path = config.ValidatedFontsPath + fontData.Value.Name + ".ttf";
|
||||||
|
|
||||||
|
if (!File.Exists(path) && !isGameFont)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IFontHandle font;
|
||||||
|
|
||||||
|
if (isGameFont)
|
||||||
|
{
|
||||||
|
GameFontFamily fontFamily = (GameFontFamily)Enum.Parse(
|
||||||
|
typeof(GameFontFamily),
|
||||||
|
config.GameFontMap.FirstOrDefault(x => x.Value == fontData.Value.Name).Key
|
||||||
|
);
|
||||||
|
GameFontStyle style = new GameFontStyle(fontFamily, fontData.Value.Size);
|
||||||
|
|
||||||
|
font = Plugin.UiBuilder.FontAtlas.NewGameFontHandle(style);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
font = Plugin.UiBuilder.FontAtlas.NewDelegateFontHandle
|
||||||
|
(
|
||||||
|
e => e.OnPreBuild
|
||||||
|
(
|
||||||
|
tk => tk.AddFontFromFile
|
||||||
|
(
|
||||||
|
path,
|
||||||
|
new SafeFontConfig
|
||||||
|
{
|
||||||
|
SizePx = fontData.Value.Size,
|
||||||
|
GlyphRanges = ranges
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fonts.Add(font);
|
||||||
|
|
||||||
|
// save default font
|
||||||
|
if (fontData.Key == FontsConfig.DefaultBigFontKey)
|
||||||
|
{
|
||||||
|
DefaultFont = font;
|
||||||
|
DefaultFontBuilt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error($"Error loading font from path {path}:\n{ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe ushort[]? GetCharacterRanges(FontsConfig config, ImGuiIOPtr io)
|
||||||
|
{
|
||||||
|
if (!config.SupportChineseCharacters && !config.SupportKoreanCharacters && !config.SupportCyrillicCharacters)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new ImFontGlyphRangesBuilderPtr(ImGuiNative.ImFontGlyphRangesBuilder());
|
||||||
|
|
||||||
|
if (config.SupportChineseCharacters)
|
||||||
|
{
|
||||||
|
// GetGlyphRangesChineseFull() includes Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs
|
||||||
|
// https://skia.googlesource.com/external/github.com/ocornut/imgui/+/v1.53/extra_fonts/README.txt
|
||||||
|
builder.AddRanges(io.Fonts.GetGlyphRangesChineseFull());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.SupportKoreanCharacters)
|
||||||
|
{
|
||||||
|
builder.AddRanges(io.Fonts.GetGlyphRangesKorean());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.SupportCyrillicCharacters)
|
||||||
|
{
|
||||||
|
builder.AddRanges(io.Fonts.GetGlyphRangesCyrillic());
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.BuildRangesToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
Copyright(c) 2021 0ceal0t (https://github.com/0ceal0t/JobBars)
|
||||||
|
Modifications Copyright(c) 2021 HSUI
|
||||||
|
08/29/2021 - Extracted code to get the GCD state of player actions.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
internal static class GCDHelper
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<uint, uint> JobActionIDs = new()
|
||||||
|
{
|
||||||
|
[JobIDs.GNB] = 16137, // Keen Edge
|
||||||
|
[JobIDs.WAR] = 31, // Heavy Swing
|
||||||
|
[JobIDs.MRD] = 31, // Heavy Swing
|
||||||
|
[JobIDs.DRK] = 3617, // Hard Slash
|
||||||
|
[JobIDs.PLD] = 9, // Fast Blade
|
||||||
|
[JobIDs.GLA] = 9, // Fast Blade
|
||||||
|
|
||||||
|
[JobIDs.SCH] = 163, // Ruin
|
||||||
|
[JobIDs.AST] = 3596, // Malefic
|
||||||
|
[JobIDs.WHM] = 119, // Stone
|
||||||
|
[JobIDs.CNJ] = 119, // Stone
|
||||||
|
[JobIDs.SGE] = 24283, // Dosis
|
||||||
|
|
||||||
|
[JobIDs.BRD] = 97, // Heavy Shot
|
||||||
|
[JobIDs.ARC] = 97, // Heavy Shot
|
||||||
|
[JobIDs.DNC] = 15989, // Cascade
|
||||||
|
[JobIDs.MCH] = 2866, // Split Shot
|
||||||
|
|
||||||
|
[JobIDs.SMN] = 163, // Ruin
|
||||||
|
[JobIDs.ACN] = 163, // Ruin
|
||||||
|
[JobIDs.RDM] = 7504, // Riposte
|
||||||
|
[JobIDs.BLM] = 142, // Blizzard
|
||||||
|
[JobIDs.THM] = 142, // Blizzard
|
||||||
|
[JobIDs.PCT] = 34650, // Fire in Red
|
||||||
|
|
||||||
|
[JobIDs.SAM] = 7477, // Hakaze
|
||||||
|
[JobIDs.NIN] = 2240, // Spinning Edge
|
||||||
|
[JobIDs.ROG] = 2240, // Spinning Edge
|
||||||
|
[JobIDs.MNK] = 53, // Bootshine
|
||||||
|
[JobIDs.PGL] = 53, // Bootshine
|
||||||
|
[JobIDs.DRG] = 75, // True Thrust
|
||||||
|
[JobIDs.LNC] = 75, // True Thrust
|
||||||
|
[JobIDs.RPR] = 24373, // Slice
|
||||||
|
[JobIDs.VPR] = 34606, // Steel Fangs
|
||||||
|
|
||||||
|
[JobIDs.BLU] = 11385 // Water Cannon
|
||||||
|
};
|
||||||
|
|
||||||
|
public static unsafe bool GetGCDInfo(IPlayerCharacter player, out float timeElapsed, out float timeTotal, ActionType actionType = ActionType.Action)
|
||||||
|
{
|
||||||
|
if (player is null || !JobActionIDs.TryGetValue(player.ClassJob.RowId, out var actionId))
|
||||||
|
{
|
||||||
|
timeElapsed = 0;
|
||||||
|
timeTotal = 0;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var actionManager = ActionManager.Instance();
|
||||||
|
var adjustedId = actionManager->GetAdjustedActionId(actionId);
|
||||||
|
timeElapsed = actionManager->GetRecastTimeElapsed(actionType, adjustedId);
|
||||||
|
timeTotal = actionManager->GetRecastTime(actionType, adjustedId);
|
||||||
|
|
||||||
|
return timeElapsed > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Plugin.Ipc;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Lumina.Data.Parsing.Uld;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
|
||||||
|
public class TitleData
|
||||||
|
{
|
||||||
|
public string Title = "";
|
||||||
|
public bool IsPrefix = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class HonorificHelper
|
||||||
|
{
|
||||||
|
private ICallGateSubscriber<int, string>? _getCharacterTitle;
|
||||||
|
|
||||||
|
#region Singleton
|
||||||
|
private HonorificHelper()
|
||||||
|
{
|
||||||
|
_getCharacterTitle = Plugin.PluginInterface.GetIpcSubscriber<int, string>("Honorific.GetCharacterTitle");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize() { Instance = new HonorificHelper(); }
|
||||||
|
|
||||||
|
public static HonorificHelper Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
~HonorificHelper()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public TitleData? GetTitle(IGameObject? actor)
|
||||||
|
{
|
||||||
|
if (_getCharacterTitle == null ||
|
||||||
|
actor == null ||
|
||||||
|
actor.ObjectKind != ObjectKind.Player ||
|
||||||
|
actor is not ICharacter character)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string jsonData = _getCharacterTitle.InvokeFunc(character.ObjectIndex);
|
||||||
|
TitleData? titleData = JsonConvert.DeserializeObject<TitleData>(jsonData ?? string.Empty);
|
||||||
|
return titleData;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Computes HUD layout addon name hashes at runtime using the game's own hash function.
|
||||||
|
* AddonConfigEntry uses CRC32 of "name_a" - UIGlobals.ComputeAddonNameHash does this.
|
||||||
|
* This ensures correct hashes across game patches without hardcoded values.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public static class HudLayoutHashHelper
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, uint> _cache = new();
|
||||||
|
private static DateTime _lastResolveErrorLog = DateTime.MinValue;
|
||||||
|
private const double ResolveErrorLogIntervalSeconds = 10.0;
|
||||||
|
|
||||||
|
/// <summary>Get AddonNameHash for a layout addon. Addon name is without _a suffix (e.g. "_ParameterWidget").</summary>
|
||||||
|
public static uint GetHash(string addonName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(addonName))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (_cache.TryGetValue(addonName, out var cached))
|
||||||
|
return cached;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
uint hash = UIGlobals.ComputeAddonNameHash(addonName);
|
||||||
|
_cache[addonName] = hash;
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if ((now - _lastResolveErrorLog).TotalSeconds >= ResolveErrorLogIntervalSeconds)
|
||||||
|
{
|
||||||
|
_lastResolveErrorLog = now;
|
||||||
|
Plugin.Logger.Warning($"[HSUI] HudLayoutHashHelper: resolver not ready (e.g. '{addonName}'): {ex.Message}");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Dump all HudLayout addon names and their hashes to the log (for debugging).</summary>
|
||||||
|
public static void DumpHudLayoutAddonsToLog()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var span = FFXIVClientStructs.FFXIV.Client.UI.Misc.HudLayoutAddon.GetSpan();
|
||||||
|
Plugin.Logger.Information("[HSUI] HudLayout addon names and hashes (name -> hash):");
|
||||||
|
for (int i = 0; i < span.Length; i++)
|
||||||
|
{
|
||||||
|
ref var addon = ref span[i];
|
||||||
|
if (!addon.AddonName.HasValue) continue;
|
||||||
|
string name = addon.AddonName.ToString() ?? "(null)";
|
||||||
|
if (string.IsNullOrEmpty(name)) continue;
|
||||||
|
uint hash = GetHash(name);
|
||||||
|
Plugin.Logger.Information($" [{i}] {name} -> 0x{hash:X8}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error($"[HSUI] HudLayoutHashHelper.DumpHudLayoutAddonsToLog failed: {ex.Message}\n{ex.StackTrace}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Tree;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public static class ImGuiHelper
|
||||||
|
{
|
||||||
|
public static void SetTooltip(string? message)
|
||||||
|
{
|
||||||
|
if (message == null) { return; }
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawSeparator(int topSpacing, int bottomSpacing)
|
||||||
|
{
|
||||||
|
DrawSpacing(topSpacing);
|
||||||
|
ImGui.Separator();
|
||||||
|
DrawSpacing(bottomSpacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawSpacing(int spacingSize)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < spacingSize; i++)
|
||||||
|
{
|
||||||
|
ImGui.NewLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void NewLineAndTab()
|
||||||
|
{
|
||||||
|
ImGui.NewLine();
|
||||||
|
Tab();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Tab()
|
||||||
|
{
|
||||||
|
ImGui.Text(" ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Node? DrawExportResetContextMenu(Node node, bool canExport, bool canReset)
|
||||||
|
{
|
||||||
|
Node? nodeToReset = null;
|
||||||
|
|
||||||
|
if (ImGui.BeginPopupContextItem("ResetContextMenu"))
|
||||||
|
{
|
||||||
|
if (canExport && ImGui.Selectable("Export"))
|
||||||
|
{
|
||||||
|
var exportString = node.GetBase64String();
|
||||||
|
ImGui.SetClipboardText(exportString ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canReset && ImGui.Selectable("Reset"))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
nodeToReset = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeToReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (bool, bool) DrawConfirmationModal(string title, string message)
|
||||||
|
{
|
||||||
|
return DrawConfirmationModal(title, new string[] { message });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (bool, bool) DrawConfirmationModal(string title, IEnumerable<string> textLines)
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.ShowingModalWindow = true;
|
||||||
|
|
||||||
|
bool didConfirm = false;
|
||||||
|
bool didClose = false;
|
||||||
|
|
||||||
|
ImGui.OpenPopup(title + " ##HSUI");
|
||||||
|
|
||||||
|
Vector2 center = ImGui.GetMainViewport().GetCenter();
|
||||||
|
ImGui.SetNextWindowPos(center, ImGuiCond.Appearing, new Vector2(0.5f, 0.5f));
|
||||||
|
|
||||||
|
bool p_open = true; // i've no idea what this is used for
|
||||||
|
|
||||||
|
if (ImGui.BeginPopupModal(title + " ##HSUI", ref p_open, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoMove))
|
||||||
|
{
|
||||||
|
float width = 300;
|
||||||
|
float height = Math.Min((ImGui.CalcTextSize(" ").Y + 5) * textLines.Count(), 240);
|
||||||
|
|
||||||
|
ImGui.BeginChild("confirmation_modal_message", new Vector2(width, height), false);
|
||||||
|
foreach (string text in textLines)
|
||||||
|
{
|
||||||
|
ImGui.Text(text);
|
||||||
|
}
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("OK", new Vector2(width / 2f - 5, 24)))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
didConfirm = true;
|
||||||
|
didClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetItemDefaultFocus();
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Cancel", new Vector2(width / 2f - 5, 24)))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
didClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
// close button on nav
|
||||||
|
else
|
||||||
|
{
|
||||||
|
didClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didClose)
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.ShowingModalWindow = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (didConfirm, didClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool DrawErrorModal(string message)
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.ShowingModalWindow = true;
|
||||||
|
|
||||||
|
bool didClose = false;
|
||||||
|
ImGui.OpenPopup("Error ##HSUI");
|
||||||
|
|
||||||
|
Vector2 center = ImGui.GetMainViewport().GetCenter();
|
||||||
|
ImGui.SetNextWindowPos(center, ImGuiCond.Appearing, new Vector2(0.5f, 0.5f));
|
||||||
|
|
||||||
|
bool p_open = true; // i've no idea what this is used for
|
||||||
|
if (ImGui.BeginPopupModal("Error ##HSUI", ref p_open, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoMove))
|
||||||
|
{
|
||||||
|
ImGui.Text(message);
|
||||||
|
ImGui.NewLine();
|
||||||
|
|
||||||
|
var textSize = ImGui.CalcTextSize(message).X;
|
||||||
|
|
||||||
|
if (ImGui.Button("OK", new Vector2(textSize, 24)))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
didClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
// close button on nav
|
||||||
|
else
|
||||||
|
{
|
||||||
|
didClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didClose)
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.ShowingModalWindow = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return didClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (bool, bool) DrawInputModal(string title, string message, ref string value)
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.ShowingModalWindow = true;
|
||||||
|
|
||||||
|
bool didConfirm = false;
|
||||||
|
bool didClose = false;
|
||||||
|
|
||||||
|
ImGui.OpenPopup(title + " ##HSUI");
|
||||||
|
|
||||||
|
Vector2 center = ImGui.GetMainViewport().GetCenter();
|
||||||
|
ImGui.SetNextWindowPos(center, ImGuiCond.Appearing, new Vector2(0.5f, 0.5f));
|
||||||
|
|
||||||
|
bool p_open = true; // i've no idea what this is used for
|
||||||
|
|
||||||
|
if (ImGui.BeginPopupModal(title + " ##HSUI", ref p_open, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoMove))
|
||||||
|
{
|
||||||
|
var textSize = ImGui.CalcTextSize(message).X;
|
||||||
|
|
||||||
|
ImGui.Text(message);
|
||||||
|
|
||||||
|
ImGui.PushItemWidth(textSize);
|
||||||
|
ImGui.InputText("", ref value, 64);
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
if (ImGui.Button("OK", new Vector2(textSize / 2f - 5, 24)))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
didConfirm = true;
|
||||||
|
didClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetItemDefaultFocus();
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Cancel", new Vector2(textSize / 2f - 5, 24)))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
didClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
// close button on nav
|
||||||
|
else
|
||||||
|
{
|
||||||
|
didClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didClose)
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.ShowingModalWindow = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (didConfirm, didClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? DrawTextTagsList(string name, ref string searchText)
|
||||||
|
{
|
||||||
|
string? selectedTag = null;
|
||||||
|
|
||||||
|
ImGui.SetNextWindowSize(new(200, 300));
|
||||||
|
|
||||||
|
if (ImGui.BeginPopup(name, ImGuiWindowFlags.NoMove))
|
||||||
|
{
|
||||||
|
if (!ImGui.IsAnyItemActive() && !ImGui.IsAnyItemFocused() && !ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
||||||
|
{
|
||||||
|
ImGui.SetKeyboardFocusHere(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// search
|
||||||
|
ImGui.InputText("", ref searchText, 64);
|
||||||
|
|
||||||
|
List<string> keys = new List<string>();
|
||||||
|
keys.AddRange(TextTagsHelper.TextTags.Keys);
|
||||||
|
keys.AddRange(TextTagsHelper.ExpTags.Keys);
|
||||||
|
keys.AddRange(TextTagsHelper.CharaTextTags.Keys);
|
||||||
|
|
||||||
|
foreach (string key in keys)
|
||||||
|
{
|
||||||
|
if (searchText.Length > 0 && !key.Contains(searchText))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tag
|
||||||
|
if (ImGui.Selectable(key))
|
||||||
|
{
|
||||||
|
selectedTag = key;
|
||||||
|
searchText = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// help tooltip
|
||||||
|
if (ImGui.IsItemHovered() && Plugin.ObjectTable.LocalPlayer != null)
|
||||||
|
{
|
||||||
|
string formattedText = TextTagsHelper.FormattedText(key, Plugin.ObjectTable.LocalPlayer);
|
||||||
|
|
||||||
|
if (formattedText.Length > 0)
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip("Example: " + formattedText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedTag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,621 @@
|
|||||||
|
/*
|
||||||
|
Copyright(c) 2021 attickdoor (https://github.com/attickdoor/MOActionPlugin)
|
||||||
|
Modifications Copyright(c) 2021 HSUI
|
||||||
|
09/21/2021 - Used original's code hooks and action validations while using
|
||||||
|
HSUI's own logic to select a target.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Lumina.Excel;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using static FFXIVClientStructs.FFXIV.Client.Game.ActionManager;
|
||||||
|
using Action = Lumina.Excel.Sheets.Action;
|
||||||
|
using BattleNpcSubKind = Dalamud.Game.ClientState.Objects.Enums.BattleNpcSubKind;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public unsafe class InputsHelper : IDisposable
|
||||||
|
{
|
||||||
|
private delegate bool UseActionDelegate(ActionManager* manager, ActionType actionType, uint actionId, ulong targetId, uint extraParam, UseActionMode mode, uint comboRouteId, bool* outOptAreaTargeted);
|
||||||
|
|
||||||
|
private delegate byte ExecuteSlotByIdDelegate(RaptureHotbarModule* module, uint hotbarId, uint slotId);
|
||||||
|
|
||||||
|
#region Singleton
|
||||||
|
private InputsHelper()
|
||||||
|
{
|
||||||
|
_sheet = Plugin.DataManager.GetExcelSheet<Action>();
|
||||||
|
|
||||||
|
//try
|
||||||
|
//{
|
||||||
|
// /*
|
||||||
|
// Part of setUIMouseOverActorId disassembly signature
|
||||||
|
// .text:00007FF64830FD70 sub_7FF64830FD70 proc near
|
||||||
|
// .text:00007FF64830FD70 48 89 91 90 02 00+mov [rcx+290h], rdx
|
||||||
|
// .text:00007FF64830FD70 00
|
||||||
|
// */
|
||||||
|
|
||||||
|
// _uiMouseOverActorHook = Plugin.GameInteropProvider.HookFromSignature<OnSetUIMouseoverActor>(
|
||||||
|
// "E8 ?? ?? ?? ?? 48 8B 7C 24 ?? 4C 8B 74 24 ?? 83 FD 02",
|
||||||
|
// HandleUIMouseOverActorId
|
||||||
|
// );
|
||||||
|
//}
|
||||||
|
//catch
|
||||||
|
//{
|
||||||
|
// Plugin.Logger.Error("InputsHelper OnSetUIMouseoverActor Hook failed!!!");
|
||||||
|
//}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_requestActionHook = Plugin.GameInteropProvider.HookFromSignature<UseActionDelegate>(
|
||||||
|
ActionManager.Addresses.UseAction.String,
|
||||||
|
HandleRequestAction
|
||||||
|
);
|
||||||
|
_requestActionHook?.Enable();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("InputsHelper UseActionDelegate Hook failed!!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
nint addr = (nint)RaptureHotbarModule.Addresses.ExecuteSlotById.Value;
|
||||||
|
if (addr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
_executeSlotByIdHook = Plugin.GameInteropProvider.HookFromAddress<ExecuteSlotByIdDelegate>(addr, HandleExecuteSlotById);
|
||||||
|
_executeSlotByIdHook.Enable();
|
||||||
|
Plugin.Logger.Info("[HSUI] ExecuteSlotById hook installed (drag-drop overwrite protection)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_executeSlotByIdHook = Plugin.GameInteropProvider.HookFromSignature<ExecuteSlotByIdDelegate>(
|
||||||
|
"4C 8B C9 41 83 F8 10 73 45",
|
||||||
|
HandleExecuteSlotById
|
||||||
|
);
|
||||||
|
_executeSlotByIdHook?.Enable();
|
||||||
|
Plugin.Logger.Info("[HSUI] ExecuteSlotById hook installed via signature");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error($"InputsHelper ExecuteSlotById Hook failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// mouseover setting
|
||||||
|
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
||||||
|
Plugin.Framework.Update += OnFrameworkUpdate;
|
||||||
|
|
||||||
|
OnConfigReset(ConfigurationManager.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize() { Instance = new InputsHelper(); }
|
||||||
|
|
||||||
|
public static InputsHelper Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
public static int InitializationDelay = 5;
|
||||||
|
|
||||||
|
~InputsHelper()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Plugin.Logger.Info("\tDisposing InputsHelper...");
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
|
||||||
|
Plugin.Framework.Update -= OnFrameworkUpdate;
|
||||||
|
|
||||||
|
Plugin.Logger.Info("\t\tDisposing _requestActionHook: " + (_requestActionHook?.Address.ToString("X") ?? "null"));
|
||||||
|
_requestActionHook?.Disable();
|
||||||
|
_requestActionHook?.Dispose();
|
||||||
|
_executeSlotByIdHook?.Disable();
|
||||||
|
_executeSlotByIdHook?.Dispose();
|
||||||
|
|
||||||
|
// give imgui the control of inputs again
|
||||||
|
RestoreWndProc();
|
||||||
|
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private HUDOptionsConfig _config = null!;
|
||||||
|
|
||||||
|
//private Hook<OnSetUIMouseoverActor>? _uiMouseOverActorHook;
|
||||||
|
|
||||||
|
private Hook<UseActionDelegate>? _requestActionHook;
|
||||||
|
private Hook<ExecuteSlotByIdDelegate>? _executeSlotByIdHook;
|
||||||
|
|
||||||
|
private ExcelSheet<Action>? _sheet;
|
||||||
|
|
||||||
|
public bool HandlingMouseInputs { get; private set; } = false;
|
||||||
|
private IGameObject? _target = null;
|
||||||
|
private bool _ignoringMouseover = false;
|
||||||
|
|
||||||
|
public bool IsProxyEnabled => _config.InputsProxyEnabled;
|
||||||
|
|
||||||
|
public void ToggleProxy(bool enabled)
|
||||||
|
{
|
||||||
|
_config.InputsProxyEnabled = enabled;
|
||||||
|
ConfigurationManager.Instance.SaveConfigurations();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTarget(IGameObject? target, bool ignoreMouseover = false)
|
||||||
|
{
|
||||||
|
if (!IsProxyEnabled &&
|
||||||
|
ClipRectsHelper.Instance?.IsPointClipped(ImGui.GetMousePos()) == false)
|
||||||
|
{
|
||||||
|
ImGui.SetNextFrameWantCaptureMouse(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
_target = target;
|
||||||
|
HandlingMouseInputs = true;
|
||||||
|
_ignoringMouseover = ignoreMouseover;
|
||||||
|
|
||||||
|
if (!_ignoringMouseover)
|
||||||
|
{
|
||||||
|
long address = _target != null && _target.GameObjectId != 0 ? (long)_target.Address : 0;
|
||||||
|
SetGameMouseoverTarget(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearTarget()
|
||||||
|
{
|
||||||
|
_target = null;
|
||||||
|
HandlingMouseInputs = false;
|
||||||
|
|
||||||
|
SetGameMouseoverTarget(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartHandlingInputs()
|
||||||
|
{
|
||||||
|
HandlingMouseInputs = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopHandlingInputs()
|
||||||
|
{
|
||||||
|
HandlingMouseInputs = false;
|
||||||
|
_ignoringMouseover = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void SetGameMouseoverTarget(long address)
|
||||||
|
{
|
||||||
|
if (!_config.MouseoverEnabled || _config.MouseoverAutomaticMode || _ignoringMouseover)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIModule* uiModule = Framework.Instance()->GetUIModule();
|
||||||
|
if (uiModule == null) { return; }
|
||||||
|
|
||||||
|
PronounModule* pronounModule = uiModule->GetPronounModule();
|
||||||
|
if (pronounModule == null) { return; }
|
||||||
|
|
||||||
|
pronounModule->UiMouseOverTarget = (GameObject*)address;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigReset(ConfigurationManager sender)
|
||||||
|
{
|
||||||
|
_config = sender.GetConfigObject<HUDOptionsConfig>();
|
||||||
|
}
|
||||||
|
|
||||||
|
//private void HandleUIMouseOverActorId(long arg1, long arg2)
|
||||||
|
//{
|
||||||
|
//Plugin.Logger.Log("MO: {0} - {1}", arg1.ToString("X"), arg2.ToString("X"));
|
||||||
|
//_uiMouseOverActorHook?.Original(arg1, arg2);
|
||||||
|
//}
|
||||||
|
|
||||||
|
private bool HandleRequestAction(
|
||||||
|
ActionManager* manager,
|
||||||
|
ActionType actionType,
|
||||||
|
uint actionId,
|
||||||
|
ulong targetId,
|
||||||
|
uint extraParam,
|
||||||
|
UseActionMode mode,
|
||||||
|
uint comboRouteId,
|
||||||
|
bool* outOptAreaTargeted
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (_requestActionHook == null) { return false; }
|
||||||
|
|
||||||
|
// Block UseAction when we just placed this action via drag-drop (game may execute from drop via path that bypasses WndProc)
|
||||||
|
var (suppressActionId, suppressUntil) = _suppressUseActionForDrop;
|
||||||
|
if (suppressActionId != 0 && actionId == suppressActionId && ImGui.GetTime() < suppressUntil)
|
||||||
|
{
|
||||||
|
if (IsActionBarDragDropDebugEnabled())
|
||||||
|
Plugin.Logger.Information($"[HSUI DragDrop DBG] UseAction SUPPRESSED actionId={actionId} (drop cooldown)");
|
||||||
|
_suppressUseActionForDrop = (0, 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ImGui.GetTime() >= suppressUntil)
|
||||||
|
_suppressUseActionForDrop = (0, 0);
|
||||||
|
|
||||||
|
if (_config.MouseoverEnabled &&
|
||||||
|
_config.MouseoverAutomaticMode &&
|
||||||
|
_target != null &&
|
||||||
|
IsActionValid(actionId, _target) &&
|
||||||
|
!_ignoringMouseover)
|
||||||
|
{
|
||||||
|
return _requestActionHook.Original(manager, actionType, actionId, _target.GameObjectId, extraParam, mode, comboRouteId, outOptAreaTargeted);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _requestActionHook.Original(manager, actionType, actionId, targetId, extraParam, mode, comboRouteId, outOptAreaTargeted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte HandleExecuteSlotById(RaptureHotbarModule* module, uint hotbarId, uint slotId)
|
||||||
|
{
|
||||||
|
var (suppressBar, suppressSlot, suppressUntil) = _suppressExecuteSlotByIdForDrop;
|
||||||
|
if (suppressBar < 10 && hotbarId == suppressBar && slotId == suppressSlot && ImGui.GetTime() < suppressUntil)
|
||||||
|
{
|
||||||
|
if (IsActionBarDragDropDebugEnabled())
|
||||||
|
Plugin.Logger.Information($"[HSUI DragDrop DBG] ExecuteSlotById SUPPRESSED bar={hotbarId} slot={slotId} (drop cooldown)");
|
||||||
|
_suppressExecuteSlotByIdForDrop = (99, 99, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (ImGui.GetTime() >= suppressUntil)
|
||||||
|
_suppressExecuteSlotByIdForDrop = (99, 99, 0);
|
||||||
|
|
||||||
|
return _executeSlotByIdHook != null ? _executeSlotByIdHook.Original(module, hotbarId, slotId) : (byte)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsActionValid(ulong actionID, IGameObject? target)
|
||||||
|
{
|
||||||
|
if (target == null || actionID == 0 || _sheet == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = _sheet.TryGetRow((uint)actionID, out Action action);
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle actions that automatically switch to other actions
|
||||||
|
// ie GNB Continuation or SMN Egi Assaults
|
||||||
|
// these actions dont have an attack type or animation so in these cases
|
||||||
|
// we assume its a hostile spell
|
||||||
|
// if this doesn't work on all cases we can switch to a hardcoded list
|
||||||
|
// of special cases later
|
||||||
|
if (action.AttackType.RowId == 0 && action.AnimationStart.RowId == 0 &&
|
||||||
|
(!action.CanTargetAlly && !action.CanTargetHostile && !action.CanTargetParty && action.CanTargetSelf))
|
||||||
|
{
|
||||||
|
// special case for AST cards and SMN rekindle
|
||||||
|
if (actionID is 37019 or 37020 or 37021 or 25822)
|
||||||
|
{
|
||||||
|
return target is IPlayerCharacter or IBattleNpc { BattleNpcKind: BattleNpcSubKind.Chocobo };
|
||||||
|
}
|
||||||
|
|
||||||
|
return target is IBattleNpc npcTarget && npcTarget.BattleNpcKind == BattleNpcSubKind.Enemy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// friendly player (TODO: pvp? lol)
|
||||||
|
if (target is IPlayerCharacter)
|
||||||
|
{
|
||||||
|
return action.CanTargetAlly || action.CanTargetParty || action.CanTargetSelf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// friendly npc
|
||||||
|
if (target is IBattleNpc npc)
|
||||||
|
{
|
||||||
|
if (npc.BattleNpcKind != BattleNpcSubKind.Enemy)
|
||||||
|
{
|
||||||
|
return action.CanTargetAlly || action.CanTargetParty || action.CanTargetSelf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return action.CanTargetHostile;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region mouseover inputs proxy
|
||||||
|
private bool? _leftButtonClicked = null;
|
||||||
|
public bool LeftButtonClicked => _leftButtonClicked.HasValue ?
|
||||||
|
_leftButtonClicked.Value :
|
||||||
|
(IsProxyEnabled ? false : ImGui.IsMouseClicked(ImGuiMouseButton.Left));
|
||||||
|
|
||||||
|
private bool? _rightButtonClicked = null;
|
||||||
|
public bool RightButtonClicked => _rightButtonClicked.HasValue ?
|
||||||
|
_rightButtonClicked.Value :
|
||||||
|
(IsProxyEnabled ? false : ImGui.IsMouseClicked(ImGuiMouseButton.Right));
|
||||||
|
|
||||||
|
private bool _leftButtonWasDown = false;
|
||||||
|
private bool _rightButtonWasDown = false;
|
||||||
|
|
||||||
|
|
||||||
|
public void ClearClicks()
|
||||||
|
{
|
||||||
|
if (IsProxyEnabled)
|
||||||
|
{
|
||||||
|
WndProcDetour(_wndHandle, WM_LBUTTONUP, 0, 0);
|
||||||
|
WndProcDetour(_wndHandle, WM_RBUTTONUP, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wnd proc detour
|
||||||
|
// if we're "eating" inputs, we only process left and right clicks
|
||||||
|
// any other message is passed along to the ImGui scene
|
||||||
|
private IntPtr WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam)
|
||||||
|
{
|
||||||
|
// When the game has an active hotbar-relevant drag (Action, Macro, Item, etc.) AND the cursor is over
|
||||||
|
// an HSUI hotbar, eat LBUTTONUP so the game doesn't interpret it as a click on the (hidden) default
|
||||||
|
// hotbar and execute the ability. Do NOT eat when cursor is over game UI (Character Config, etc.) —
|
||||||
|
// the game uses the same icon/drag system for config submenus, so we must only intercept when we're
|
||||||
|
// actually dropping on our hotbars.
|
||||||
|
if (msg == WM_LBUTTONUP && IsHotbarRelevantGameDrag() && ActionBarsHitTestHelper.IsMouseOverAnyHSUIHotbar())
|
||||||
|
{
|
||||||
|
if (IsActionBarDragDropDebugEnabled())
|
||||||
|
Plugin.Logger.Information("[HSUI DragDrop DBG] WndProc: EATING LBUTTONUP (hotbar drag over HSUI bar)");
|
||||||
|
TryCancelGameDragDrop();
|
||||||
|
ImGui.GetIO().AddMouseButtonEvent((int)ImGuiMouseButton.Left, false);
|
||||||
|
return (IntPtr)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eat left and right clicks?
|
||||||
|
if (HandlingMouseInputs && IsProxyEnabled)
|
||||||
|
{
|
||||||
|
switch (msg)
|
||||||
|
{
|
||||||
|
// mouse clicks
|
||||||
|
case WM_LBUTTONDOWN:
|
||||||
|
case WM_RBUTTONDOWN:
|
||||||
|
case WM_LBUTTONUP:
|
||||||
|
case WM_RBUTTONUP:
|
||||||
|
|
||||||
|
// if there's not a game window covering the cursor location
|
||||||
|
// we eat the message and handle the inputs manually
|
||||||
|
if (ClipRectsHelper.Instance?.IsPointClipped(ImGui.GetMousePos()) == false)
|
||||||
|
{
|
||||||
|
_leftButtonClicked = _leftButtonWasDown && msg == WM_LBUTTONUP;
|
||||||
|
_rightButtonClicked = _rightButtonWasDown && msg == WM_RBUTTONUP;
|
||||||
|
|
||||||
|
|
||||||
|
_leftButtonWasDown = msg == WM_LBUTTONDOWN;
|
||||||
|
_rightButtonWasDown = msg == WM_RBUTTONDOWN;
|
||||||
|
|
||||||
|
// never eat BUTTONUP messages to prevent clicks from getting stuck!!!
|
||||||
|
if (msg != WM_LBUTTONUP && msg != WM_RBUTTONUP)
|
||||||
|
{
|
||||||
|
// INPUT EATEN!!!
|
||||||
|
return (IntPtr)0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// otherwise we let imgui handle the inputs
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_leftButtonClicked = null;
|
||||||
|
_rightButtonClicked = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call imgui's wnd proc
|
||||||
|
return (IntPtr)CallWindowProc(_imguiWndProcPtr, hWnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnFrameworkUpdate(IFramework framework)
|
||||||
|
{
|
||||||
|
// Keep WndProc hooked when: proxy mode (for mouseover) OR we need to block game drag
|
||||||
|
// release (so dropping on HSUI action bar doesn't execute the ability).
|
||||||
|
bool needHook = IsProxyEnabled || ShouldBlockGameDragRelease();
|
||||||
|
if (needHook && _wndProcPtr == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
HookWndProc();
|
||||||
|
// Only log when we actually installed (HookWndProc can return early during init delay)
|
||||||
|
if (_wndProcPtr != IntPtr.Zero && IsActionBarDragDropDebugEnabled())
|
||||||
|
Plugin.Logger.Information("[HSUI DragDrop DBG] WndProc hook INSTALLED (needHook=true for drag-block)");
|
||||||
|
}
|
||||||
|
else if (!needHook && _wndProcPtr != IntPtr.Zero)
|
||||||
|
RestoreWndProc();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldBlockGameDragRelease()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var hotbarsConfig = ConfigurationManager.Instance?.GetConfigObject<HSUI.Interface.GeneralElements.HotbarsConfig>();
|
||||||
|
return hotbarsConfig != null && hotbarsConfig.Enabled;
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsActionBarDragDropDebugEnabled()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var configs = ConfigurationManager.Instance?.GetObjects<HSUI.Interface.GeneralElements.HotbarBarConfig>();
|
||||||
|
return configs != null && configs.Exists(c => c.DebugDragDrop);
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>When we place an action via drag-drop, suppress the next UseAction for that action to prevent
|
||||||
|
/// the game from executing it (game may interpret drop as click via a path that bypasses WndProc).</summary>
|
||||||
|
private static (uint ActionId, double SuppressUntil) _suppressUseActionForDrop = (0, 0);
|
||||||
|
|
||||||
|
public static void SuppressUseActionAfterDrop(uint actionId, int durationMs = 300)
|
||||||
|
{
|
||||||
|
_suppressUseActionForDrop = (actionId, ImGui.GetTime() + durationMs / 1000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Suppress ExecuteSlotById when we just placed via drag-drop (game hotbar click goes through this).</summary>
|
||||||
|
private static (uint HotbarId, uint SlotId, double SuppressUntil) _suppressExecuteSlotByIdForDrop = (99, 99, 0);
|
||||||
|
|
||||||
|
public static void SuppressExecuteSlotByIdAfterDrop(uint hotbarId, uint slotId, int durationMs = 300)
|
||||||
|
{
|
||||||
|
_suppressExecuteSlotByIdForDrop = (hotbarId, slotId, ImGui.GetTime() + durationMs / 1000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnFrameEnd()
|
||||||
|
{
|
||||||
|
_leftButtonClicked = null;
|
||||||
|
_rightButtonClicked = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HookWndProc()
|
||||||
|
{
|
||||||
|
if (Plugin.LoadTime <= 0 ||
|
||||||
|
ImGui.GetTime() - Plugin.LoadTime < InitializationDelay)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong processId = (ulong)Process.GetCurrentProcess().Id;
|
||||||
|
|
||||||
|
IntPtr hWnd = IntPtr.Zero;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
hWnd = FindWindowExW(IntPtr.Zero, hWnd, "FFXIVGAME", null);
|
||||||
|
if (hWnd == IntPtr.Zero) { return; }
|
||||||
|
|
||||||
|
ulong wndProcessId = 0;
|
||||||
|
GetWindowThreadProcessId(hWnd, ref wndProcessId);
|
||||||
|
|
||||||
|
if (wndProcessId == processId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (hWnd != IntPtr.Zero);
|
||||||
|
|
||||||
|
if (hWnd == IntPtr.Zero) { return; }
|
||||||
|
|
||||||
|
_wndHandle = hWnd;
|
||||||
|
_wndProcDelegate = WndProcDetour;
|
||||||
|
_wndProcPtr = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate);
|
||||||
|
_imguiWndProcPtr = SetWindowLongPtr(hWnd, GWL_WNDPROC, _wndProcPtr);
|
||||||
|
|
||||||
|
Plugin.Logger.Info("Initializing HSUI Inputs v" + Plugin.Version);
|
||||||
|
Plugin.Logger.Info("\tHooking WndProc for window: " + hWnd.ToString("X"));
|
||||||
|
Plugin.Logger.Info("\tOld WndProc: " + _imguiWndProcPtr.ToString("X"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreWndProc()
|
||||||
|
{
|
||||||
|
if (_wndHandle != IntPtr.Zero && _imguiWndProcPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Info("\t\tRestoring WndProc");
|
||||||
|
Plugin.Logger.Info("\t\t\tOld _wndHandle = " + _wndHandle.ToString("X"));
|
||||||
|
Plugin.Logger.Info("\t\t\tOld _imguiWndProcPtr = " + _imguiWndProcPtr.ToString("X"));
|
||||||
|
|
||||||
|
SetWindowLongPtr(_wndHandle, GWL_WNDPROC, _imguiWndProcPtr);
|
||||||
|
Plugin.Logger.Info("\t\t\tDone!");
|
||||||
|
|
||||||
|
_wndHandle = IntPtr.Zero;
|
||||||
|
_imguiWndProcPtr = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr _wndHandle = IntPtr.Zero;
|
||||||
|
private WndProcDelegate _wndProcDelegate = null!;
|
||||||
|
private IntPtr _wndProcPtr = IntPtr.Zero;
|
||||||
|
private IntPtr _imguiWndProcPtr = IntPtr.Zero;
|
||||||
|
|
||||||
|
public delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = true)]
|
||||||
|
public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", EntryPoint = "CallWindowProcW")]
|
||||||
|
public static extern long CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, ulong wParam, long lParam);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", EntryPoint = "FindWindowExW", SetLastError = true)]
|
||||||
|
public static extern IntPtr FindWindowExW(IntPtr hWndParent, IntPtr hWndChildAfter, [MarshalAs(UnmanagedType.LPWStr)] string? lpszClass, [MarshalAs(UnmanagedType.LPWStr)] string? lpszWindow);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", EntryPoint = "GetWindowThreadProcessId", SetLastError = true)]
|
||||||
|
public static extern ulong GetWindowThreadProcessId(IntPtr hWnd, ref ulong id);
|
||||||
|
|
||||||
|
private const uint WM_LBUTTONDOWN = 513;
|
||||||
|
private const uint WM_LBUTTONUP = 514;
|
||||||
|
private const uint WM_RBUTTONDOWN = 516;
|
||||||
|
private const uint WM_RBUTTONUP = 517;
|
||||||
|
|
||||||
|
private const int GWL_WNDPROC = -4;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static unsafe bool IsGameDragDropActive()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stage = AtkStage.Instance();
|
||||||
|
if (stage == null) return false;
|
||||||
|
var dm = (AtkDragDropManager*)Unsafe.AsPointer(ref stage->DragDropManager);
|
||||||
|
return dm != null && dm->IsDragging;
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>True when the game has an active drag that can be placed on a hotbar (Action, Macro, Item, etc.).
|
||||||
|
/// Used to avoid eating LBUTTONUP for other UI drags (e.g. Character Config menus).</summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static unsafe bool IsHotbarRelevantGameDrag()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!IsGameDragDropActive()) return false;
|
||||||
|
var stage = AtkStage.Instance();
|
||||||
|
if (stage == null) return false;
|
||||||
|
var dm = (AtkDragDropManager*)Unsafe.AsPointer(ref stage->DragDropManager);
|
||||||
|
var dd = dm->DragDrop1;
|
||||||
|
if (dd == null) return false;
|
||||||
|
var slotType = UIGlobals.GetHotbarSlotTypeFromDragDropType(dd->DragDropType);
|
||||||
|
return slotType != RaptureHotbarModule.HotbarSlotType.Empty;
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static unsafe void TryCancelGameDragDrop()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stage = AtkStage.Instance();
|
||||||
|
if (stage == null) return;
|
||||||
|
var dm = (AtkDragDropManager*)Unsafe.AsPointer(ref stage->DragDropManager);
|
||||||
|
if (dm != null)
|
||||||
|
dm->CancelDragDrop(true, true);
|
||||||
|
}
|
||||||
|
catch { /* best-effort */ }
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,710 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public enum JobRoles
|
||||||
|
{
|
||||||
|
Tank = 0,
|
||||||
|
Healer = 1,
|
||||||
|
DPSMelee = 2,
|
||||||
|
DPSRanged = 3,
|
||||||
|
DPSCaster = 4,
|
||||||
|
Crafter = 5,
|
||||||
|
Gatherer = 6,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PrimaryResourceTypes
|
||||||
|
{
|
||||||
|
MP = 0,
|
||||||
|
CP = 1,
|
||||||
|
GP = 2,
|
||||||
|
None = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JobsHelper
|
||||||
|
{
|
||||||
|
public static JobRoles RoleForJob(uint jobId)
|
||||||
|
{
|
||||||
|
if (JobRolesMap.TryGetValue(jobId, out var role))
|
||||||
|
{
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JobRoles.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsJobARole(uint jobId, JobRoles role)
|
||||||
|
{
|
||||||
|
if (JobRolesMap.TryGetValue(jobId, out var r))
|
||||||
|
{
|
||||||
|
return r == role;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsJobTank(uint jobId)
|
||||||
|
{
|
||||||
|
return IsJobARole(jobId, JobRoles.Tank);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsJobWithCleanse(uint jobId, int level)
|
||||||
|
{
|
||||||
|
var isOnCleanseJob = _cleanseJobs.Contains(jobId);
|
||||||
|
|
||||||
|
if (jobId == JobIDs.BRD && level < 35)
|
||||||
|
{
|
||||||
|
isOnCleanseJob = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isOnCleanseJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly List<uint> _cleanseJobs = new List<uint>()
|
||||||
|
{
|
||||||
|
JobIDs.CNJ,
|
||||||
|
JobIDs.WHM,
|
||||||
|
JobIDs.SCH,
|
||||||
|
JobIDs.AST,
|
||||||
|
JobIDs.SGE,
|
||||||
|
JobIDs.BRD,
|
||||||
|
JobIDs.BLU
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool IsJobHealer(uint jobId)
|
||||||
|
{
|
||||||
|
return IsJobARole(jobId, JobRoles.Healer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsJobDPS(uint jobId)
|
||||||
|
{
|
||||||
|
if (JobRolesMap.TryGetValue(jobId, out var r))
|
||||||
|
{
|
||||||
|
return r == JobRoles.DPSMelee || r == JobRoles.DPSRanged || r == JobRoles.DPSCaster;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsJobDPSMelee(uint jobId)
|
||||||
|
{
|
||||||
|
return IsJobARole(jobId, JobRoles.DPSMelee);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsJobDPSRanged(uint jobId)
|
||||||
|
{
|
||||||
|
return IsJobARole(jobId, JobRoles.DPSRanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsJobDPSCaster(uint jobId)
|
||||||
|
{
|
||||||
|
return IsJobARole(jobId, JobRoles.DPSCaster);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsJobCrafter(uint jobId)
|
||||||
|
{
|
||||||
|
return IsJobARole(jobId, JobRoles.Crafter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsJobGatherer(uint jobId)
|
||||||
|
{
|
||||||
|
return IsJobARole(jobId, JobRoles.Gatherer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsJobWithRaise(uint jobId, uint level)
|
||||||
|
{
|
||||||
|
var isOnRaiseJob = _raiseJobs.Contains(jobId);
|
||||||
|
|
||||||
|
if ((jobId == JobIDs.RDM && level < 64) || level < 12)
|
||||||
|
{
|
||||||
|
isOnRaiseJob = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isOnRaiseJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly List<uint> _raiseJobs = new List<uint>()
|
||||||
|
{
|
||||||
|
JobIDs.CNJ,
|
||||||
|
JobIDs.WHM,
|
||||||
|
JobIDs.SCH,
|
||||||
|
JobIDs.AST,
|
||||||
|
JobIDs.RDM,
|
||||||
|
JobIDs.SMN,
|
||||||
|
JobIDs.SGE
|
||||||
|
};
|
||||||
|
|
||||||
|
public static uint CurrentPrimaryResource(ICharacter? character)
|
||||||
|
{
|
||||||
|
if (character == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint jobId = character.ClassJob.RowId;
|
||||||
|
|
||||||
|
if (IsJobGatherer(jobId))
|
||||||
|
{
|
||||||
|
return character.CurrentGp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsJobCrafter(jobId))
|
||||||
|
{
|
||||||
|
return character.CurrentCp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.CurrentMp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static uint MaxPrimaryResource(ICharacter? character)
|
||||||
|
{
|
||||||
|
if (character == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint jobId = character.ClassJob.RowId;
|
||||||
|
|
||||||
|
if (IsJobGatherer(jobId))
|
||||||
|
{
|
||||||
|
return character.MaxGp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsJobCrafter(jobId))
|
||||||
|
{
|
||||||
|
return character.MaxCp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return character.MaxMp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static uint GPResourceRate(ICharacter? character)
|
||||||
|
{
|
||||||
|
if (character == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preferably I'd want to check the active traits because these traits are locked behind job quests, but no idea how to check traits.
|
||||||
|
|
||||||
|
// Level 83 Trait 239 (MIN), 240 (BTN), 241 (FSH)
|
||||||
|
if (character.Level >= 83)
|
||||||
|
{
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level 80 Trait 236 (MIN), 237 (BTN), 238 (FSH)
|
||||||
|
if (character.Level >= 80)
|
||||||
|
{
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level 70 Trait 192 (MIN), 193 (BTN), 194 (FSH)
|
||||||
|
if (character.Level >= 70)
|
||||||
|
{
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string TimeTillMaxGP(ICharacter? character)
|
||||||
|
{
|
||||||
|
if (character == null)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
uint jobId = character.ClassJob.RowId;
|
||||||
|
|
||||||
|
if (!IsJobGatherer(jobId))
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
uint gpRate = GPResourceRate(character);
|
||||||
|
|
||||||
|
if (character.CurrentGp == character.MaxGp)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since I'm not using a stopwatch or anything like MPTickHelper here the time will only update every 3 seconds, would be nice if the time ticks down every second.
|
||||||
|
float gpPerSecond = gpRate / 3f;
|
||||||
|
float secondsTillMax = (character.MaxGp - character.CurrentGp) / gpPerSecond;
|
||||||
|
|
||||||
|
return $"{Utils.DurationToString(secondsTillMax)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static uint IconIDForJob(uint jobId)
|
||||||
|
{
|
||||||
|
return jobId + 62000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static uint IconIDForJob(uint jobId, uint style)
|
||||||
|
{
|
||||||
|
if (style < 2)
|
||||||
|
{
|
||||||
|
return IconIDForJob(jobId) + style * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorizedIconIDs.TryGetValue(jobId, out var iconID);
|
||||||
|
return iconID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static uint RoleIconIDForJob(uint jobId, bool specificDPSIcons = false)
|
||||||
|
{
|
||||||
|
var role = RoleForJob(jobId);
|
||||||
|
|
||||||
|
switch (role)
|
||||||
|
{
|
||||||
|
case JobRoles.Tank: return 62581;
|
||||||
|
case JobRoles.Healer: return 62582;
|
||||||
|
|
||||||
|
case JobRoles.DPSMelee:
|
||||||
|
case JobRoles.DPSRanged:
|
||||||
|
case JobRoles.DPSCaster:
|
||||||
|
if (specificDPSIcons && SpecificDPSIcons.TryGetValue(jobId, out var iconId))
|
||||||
|
{
|
||||||
|
return iconId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 62583;
|
||||||
|
}
|
||||||
|
|
||||||
|
case JobRoles.Gatherer:
|
||||||
|
case JobRoles.Crafter:
|
||||||
|
return IconIDForJob(jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static uint RoleIconIDForBattleCompanion => 62043;
|
||||||
|
|
||||||
|
public static Dictionary<uint, JobRoles> JobRolesMap = new Dictionary<uint, JobRoles>()
|
||||||
|
{
|
||||||
|
// tanks
|
||||||
|
[JobIDs.GLA] = JobRoles.Tank,
|
||||||
|
[JobIDs.MRD] = JobRoles.Tank,
|
||||||
|
[JobIDs.PLD] = JobRoles.Tank,
|
||||||
|
[JobIDs.WAR] = JobRoles.Tank,
|
||||||
|
[JobIDs.DRK] = JobRoles.Tank,
|
||||||
|
[JobIDs.GNB] = JobRoles.Tank,
|
||||||
|
|
||||||
|
// healers
|
||||||
|
[JobIDs.CNJ] = JobRoles.Healer,
|
||||||
|
[JobIDs.WHM] = JobRoles.Healer,
|
||||||
|
[JobIDs.SCH] = JobRoles.Healer,
|
||||||
|
[JobIDs.AST] = JobRoles.Healer,
|
||||||
|
[JobIDs.SGE] = JobRoles.Healer,
|
||||||
|
|
||||||
|
// melee dps
|
||||||
|
[JobIDs.PGL] = JobRoles.DPSMelee,
|
||||||
|
[JobIDs.LNC] = JobRoles.DPSMelee,
|
||||||
|
[JobIDs.ROG] = JobRoles.DPSMelee,
|
||||||
|
[JobIDs.MNK] = JobRoles.DPSMelee,
|
||||||
|
[JobIDs.DRG] = JobRoles.DPSMelee,
|
||||||
|
[JobIDs.NIN] = JobRoles.DPSMelee,
|
||||||
|
[JobIDs.SAM] = JobRoles.DPSMelee,
|
||||||
|
[JobIDs.RPR] = JobRoles.DPSMelee,
|
||||||
|
[JobIDs.VPR] = JobRoles.DPSMelee,
|
||||||
|
|
||||||
|
// ranged phys dps
|
||||||
|
[JobIDs.ARC] = JobRoles.DPSRanged,
|
||||||
|
[JobIDs.BRD] = JobRoles.DPSRanged,
|
||||||
|
[JobIDs.MCH] = JobRoles.DPSRanged,
|
||||||
|
[JobIDs.DNC] = JobRoles.DPSRanged,
|
||||||
|
|
||||||
|
// ranged magic dps
|
||||||
|
[JobIDs.THM] = JobRoles.DPSCaster,
|
||||||
|
[JobIDs.ACN] = JobRoles.DPSCaster,
|
||||||
|
[JobIDs.BLM] = JobRoles.DPSCaster,
|
||||||
|
[JobIDs.SMN] = JobRoles.DPSCaster,
|
||||||
|
[JobIDs.RDM] = JobRoles.DPSCaster,
|
||||||
|
[JobIDs.BLU] = JobRoles.DPSCaster,
|
||||||
|
[JobIDs.PCT] = JobRoles.DPSCaster,
|
||||||
|
|
||||||
|
// crafters
|
||||||
|
[JobIDs.CRP] = JobRoles.Crafter,
|
||||||
|
[JobIDs.BSM] = JobRoles.Crafter,
|
||||||
|
[JobIDs.ARM] = JobRoles.Crafter,
|
||||||
|
[JobIDs.GSM] = JobRoles.Crafter,
|
||||||
|
[JobIDs.LTW] = JobRoles.Crafter,
|
||||||
|
[JobIDs.WVR] = JobRoles.Crafter,
|
||||||
|
[JobIDs.ALC] = JobRoles.Crafter,
|
||||||
|
[JobIDs.CUL] = JobRoles.Crafter,
|
||||||
|
|
||||||
|
// gatherers
|
||||||
|
[JobIDs.MIN] = JobRoles.Gatherer,
|
||||||
|
[JobIDs.BOT] = JobRoles.Gatherer,
|
||||||
|
[JobIDs.FSH] = JobRoles.Gatherer,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<JobRoles, List<uint>> JobsByRole = new Dictionary<JobRoles, List<uint>>()
|
||||||
|
{
|
||||||
|
// tanks
|
||||||
|
[JobRoles.Tank] = new List<uint>() {
|
||||||
|
JobIDs.GLA,
|
||||||
|
JobIDs.MRD,
|
||||||
|
JobIDs.PLD,
|
||||||
|
JobIDs.WAR,
|
||||||
|
JobIDs.DRK,
|
||||||
|
JobIDs.GNB,
|
||||||
|
},
|
||||||
|
|
||||||
|
// healers
|
||||||
|
[JobRoles.Healer] = new List<uint>()
|
||||||
|
{
|
||||||
|
JobIDs.CNJ,
|
||||||
|
JobIDs.WHM,
|
||||||
|
JobIDs.SCH,
|
||||||
|
JobIDs.AST,
|
||||||
|
JobIDs.SGE
|
||||||
|
},
|
||||||
|
|
||||||
|
// melee dps
|
||||||
|
[JobRoles.DPSMelee] = new List<uint>() {
|
||||||
|
JobIDs.PGL,
|
||||||
|
JobIDs.LNC,
|
||||||
|
JobIDs.ROG,
|
||||||
|
JobIDs.MNK,
|
||||||
|
JobIDs.DRG,
|
||||||
|
JobIDs.NIN,
|
||||||
|
JobIDs.SAM,
|
||||||
|
JobIDs.RPR,
|
||||||
|
JobIDs.VPR
|
||||||
|
},
|
||||||
|
|
||||||
|
// ranged phys dps
|
||||||
|
[JobRoles.DPSRanged] = new List<uint>()
|
||||||
|
{
|
||||||
|
JobIDs.ARC,
|
||||||
|
JobIDs.BRD,
|
||||||
|
JobIDs.MCH,
|
||||||
|
JobIDs.DNC,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ranged magic dps
|
||||||
|
[JobRoles.DPSCaster] = new List<uint>()
|
||||||
|
{
|
||||||
|
JobIDs.THM,
|
||||||
|
JobIDs.ACN,
|
||||||
|
JobIDs.BLM,
|
||||||
|
JobIDs.SMN,
|
||||||
|
JobIDs.RDM,
|
||||||
|
JobIDs.BLU,
|
||||||
|
JobIDs.PCT
|
||||||
|
},
|
||||||
|
|
||||||
|
// crafters
|
||||||
|
[JobRoles.Crafter] = new List<uint>()
|
||||||
|
{
|
||||||
|
JobIDs.CRP,
|
||||||
|
JobIDs.BSM,
|
||||||
|
JobIDs.ARM,
|
||||||
|
JobIDs.GSM,
|
||||||
|
JobIDs.LTW,
|
||||||
|
JobIDs.WVR,
|
||||||
|
JobIDs.ALC,
|
||||||
|
JobIDs.CUL,
|
||||||
|
},
|
||||||
|
|
||||||
|
// gatherers
|
||||||
|
[JobRoles.Gatherer] = new List<uint>()
|
||||||
|
{
|
||||||
|
JobIDs.MIN,
|
||||||
|
JobIDs.BOT,
|
||||||
|
JobIDs.FSH,
|
||||||
|
},
|
||||||
|
|
||||||
|
// unknown
|
||||||
|
[JobRoles.Unknown] = new List<uint>()
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<uint, string> JobNames = new Dictionary<uint, string>()
|
||||||
|
{
|
||||||
|
// tanks
|
||||||
|
[JobIDs.GLA] = "GLA",
|
||||||
|
[JobIDs.MRD] = "MRD",
|
||||||
|
[JobIDs.PLD] = "PLD",
|
||||||
|
[JobIDs.WAR] = "WAR",
|
||||||
|
[JobIDs.DRK] = "DRK",
|
||||||
|
[JobIDs.GNB] = "GNB",
|
||||||
|
|
||||||
|
// melee dps
|
||||||
|
[JobIDs.PGL] = "PGL",
|
||||||
|
[JobIDs.LNC] = "LNC",
|
||||||
|
[JobIDs.ROG] = "ROG",
|
||||||
|
[JobIDs.MNK] = "MNK",
|
||||||
|
[JobIDs.DRG] = "DRG",
|
||||||
|
[JobIDs.NIN] = "NIN",
|
||||||
|
[JobIDs.SAM] = "SAM",
|
||||||
|
[JobIDs.RPR] = "RPR",
|
||||||
|
[JobIDs.VPR] = "VPR",
|
||||||
|
|
||||||
|
// ranged phys dps
|
||||||
|
[JobIDs.ARC] = "ARC",
|
||||||
|
[JobIDs.BRD] = "BRD",
|
||||||
|
[JobIDs.MCH] = "MCH",
|
||||||
|
[JobIDs.DNC] = "DNC",
|
||||||
|
|
||||||
|
// ranged magic dps
|
||||||
|
[JobIDs.THM] = "THM",
|
||||||
|
[JobIDs.ACN] = "ACN",
|
||||||
|
[JobIDs.BLM] = "BLM",
|
||||||
|
[JobIDs.SMN] = "SMN",
|
||||||
|
[JobIDs.RDM] = "RDM",
|
||||||
|
[JobIDs.BLU] = "BLU",
|
||||||
|
[JobIDs.PCT] = "PCT",
|
||||||
|
|
||||||
|
// healers
|
||||||
|
[JobIDs.CNJ] = "CNJ",
|
||||||
|
[JobIDs.WHM] = "WHM",
|
||||||
|
[JobIDs.SCH] = "SCH",
|
||||||
|
[JobIDs.SGE] = "SGE",
|
||||||
|
[JobIDs.AST] = "AST",
|
||||||
|
|
||||||
|
// crafters
|
||||||
|
[JobIDs.CRP] = "CRP",
|
||||||
|
[JobIDs.BSM] = "BSM",
|
||||||
|
[JobIDs.ARM] = "ARM",
|
||||||
|
[JobIDs.GSM] = "GSM",
|
||||||
|
[JobIDs.LTW] = "LTW",
|
||||||
|
[JobIDs.WVR] = "WVR",
|
||||||
|
[JobIDs.ALC] = "ALC",
|
||||||
|
[JobIDs.CUL] = "CUL",
|
||||||
|
|
||||||
|
// gatherers
|
||||||
|
[JobIDs.MIN] = "MIN",
|
||||||
|
[JobIDs.BOT] = "BOT",
|
||||||
|
[JobIDs.FSH] = "FSH",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<uint, string> JobFullNames = new Dictionary<uint, string>()
|
||||||
|
{
|
||||||
|
// tanks
|
||||||
|
[JobIDs.GLA] = "Gladiator",
|
||||||
|
[JobIDs.MRD] = "Marauder",
|
||||||
|
[JobIDs.PLD] = "Paladin",
|
||||||
|
[JobIDs.WAR] = "Warrior",
|
||||||
|
[JobIDs.DRK] = "Dark Knight",
|
||||||
|
[JobIDs.GNB] = "Gunbreaker",
|
||||||
|
|
||||||
|
// melee dps
|
||||||
|
[JobIDs.PGL] = "Pugilist",
|
||||||
|
[JobIDs.LNC] = "Lancer",
|
||||||
|
[JobIDs.ROG] = "Rogue",
|
||||||
|
[JobIDs.MNK] = "Monk",
|
||||||
|
[JobIDs.DRG] = "Dragoon",
|
||||||
|
[JobIDs.NIN] = "Ninja",
|
||||||
|
[JobIDs.SAM] = "Samurai",
|
||||||
|
[JobIDs.RPR] = "Reaper",
|
||||||
|
[JobIDs.VPR] = "Viper",
|
||||||
|
|
||||||
|
// ranged phys dps
|
||||||
|
[JobIDs.ARC] = "Archer",
|
||||||
|
[JobIDs.BRD] = "Bard",
|
||||||
|
[JobIDs.MCH] = "Machinist",
|
||||||
|
[JobIDs.DNC] = "Dancer",
|
||||||
|
|
||||||
|
// ranged magic dps
|
||||||
|
[JobIDs.THM] = "Thaumaturge",
|
||||||
|
[JobIDs.ACN] = "Arcanist",
|
||||||
|
[JobIDs.BLM] = "Black Mage",
|
||||||
|
[JobIDs.SMN] = "Summoner",
|
||||||
|
[JobIDs.RDM] = "Red Mage",
|
||||||
|
[JobIDs.BLU] = "Blue Mage",
|
||||||
|
[JobIDs.PCT] = "Pictomancer",
|
||||||
|
|
||||||
|
// healers
|
||||||
|
[JobIDs.CNJ] = "Conjurer",
|
||||||
|
[JobIDs.WHM] = "White Mage",
|
||||||
|
[JobIDs.SCH] = "Scholar",
|
||||||
|
[JobIDs.SGE] = "Sage",
|
||||||
|
[JobIDs.AST] = "Astrologian",
|
||||||
|
|
||||||
|
// crafters
|
||||||
|
[JobIDs.CRP] = "Carpenter",
|
||||||
|
[JobIDs.BSM] = "Blacksmith",
|
||||||
|
[JobIDs.ARM] = "Armorer",
|
||||||
|
[JobIDs.GSM] = "Goldsmith",
|
||||||
|
[JobIDs.LTW] = "Leatherworker",
|
||||||
|
[JobIDs.WVR] = "Weaver",
|
||||||
|
[JobIDs.ALC] = "Alchemist",
|
||||||
|
[JobIDs.CUL] = "Culinarian",
|
||||||
|
|
||||||
|
// gatherers
|
||||||
|
[JobIDs.MIN] = "Miner",
|
||||||
|
[JobIDs.BOT] = "Botanist",
|
||||||
|
[JobIDs.FSH] = "Fisher",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<JobRoles, string> RoleNames = new Dictionary<JobRoles, string>()
|
||||||
|
{
|
||||||
|
[JobRoles.Tank] = "Tank",
|
||||||
|
[JobRoles.Healer] = "Healer",
|
||||||
|
[JobRoles.DPSMelee] = "Melee",
|
||||||
|
[JobRoles.DPSRanged] = "Ranged",
|
||||||
|
[JobRoles.DPSCaster] = "Caster",
|
||||||
|
[JobRoles.Crafter] = "Crafter",
|
||||||
|
[JobRoles.Gatherer] = "Gatherer",
|
||||||
|
[JobRoles.Unknown] = "Unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<uint, uint> SpecificDPSIcons = new Dictionary<uint, uint>()
|
||||||
|
{
|
||||||
|
// melee dps
|
||||||
|
[JobIDs.PGL] = 62584,
|
||||||
|
[JobIDs.LNC] = 62584,
|
||||||
|
[JobIDs.ROG] = 62584,
|
||||||
|
[JobIDs.MNK] = 62584,
|
||||||
|
[JobIDs.DRG] = 62584,
|
||||||
|
[JobIDs.NIN] = 62584,
|
||||||
|
[JobIDs.SAM] = 62584,
|
||||||
|
[JobIDs.RPR] = 62584,
|
||||||
|
[JobIDs.VPR] = 62584,
|
||||||
|
|
||||||
|
// ranged phys dps
|
||||||
|
[JobIDs.ARC] = 62586,
|
||||||
|
[JobIDs.BRD] = 62586,
|
||||||
|
[JobIDs.MCH] = 62586,
|
||||||
|
[JobIDs.DNC] = 62586,
|
||||||
|
|
||||||
|
// ranged magic dps
|
||||||
|
[JobIDs.THM] = 62587,
|
||||||
|
[JobIDs.ACN] = 62587,
|
||||||
|
[JobIDs.BLM] = 62587,
|
||||||
|
[JobIDs.SMN] = 62587,
|
||||||
|
[JobIDs.RDM] = 62587,
|
||||||
|
[JobIDs.BLU] = 62587,
|
||||||
|
[JobIDs.PCT] = 62587
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<uint, uint> ColorizedIconIDs = new Dictionary<uint, uint>()
|
||||||
|
{
|
||||||
|
// tanks
|
||||||
|
[JobIDs.GLA] = 94022,
|
||||||
|
[JobIDs.MRD] = 94024,
|
||||||
|
[JobIDs.PLD] = 94079,
|
||||||
|
[JobIDs.WAR] = 94081,
|
||||||
|
[JobIDs.DRK] = 94123,
|
||||||
|
[JobIDs.GNB] = 94130,
|
||||||
|
|
||||||
|
// melee dps
|
||||||
|
[JobIDs.PGL] = 92523,
|
||||||
|
[JobIDs.LNC] = 92525,
|
||||||
|
[JobIDs.ROG] = 92621,
|
||||||
|
[JobIDs.MNK] = 92580,
|
||||||
|
[JobIDs.DRG] = 92582,
|
||||||
|
[JobIDs.NIN] = 92622,
|
||||||
|
[JobIDs.SAM] = 92627,
|
||||||
|
[JobIDs.RPR] = 92632,
|
||||||
|
[JobIDs.VPR] = 92685,
|
||||||
|
|
||||||
|
// ranged phys dps
|
||||||
|
[JobIDs.ARC] = 92526,
|
||||||
|
[JobIDs.BRD] = 92583,
|
||||||
|
[JobIDs.MCH] = 92625,
|
||||||
|
[JobIDs.DNC] = 92631,
|
||||||
|
|
||||||
|
// ranged magic dps
|
||||||
|
[JobIDs.THM] = 92529,
|
||||||
|
[JobIDs.ACN] = 92530,
|
||||||
|
[JobIDs.BLM] = 92585,
|
||||||
|
[JobIDs.SMN] = 92586,
|
||||||
|
[JobIDs.RDM] = 92628,
|
||||||
|
[JobIDs.BLU] = 92629,
|
||||||
|
[JobIDs.PCT] = 92686,
|
||||||
|
|
||||||
|
// healers
|
||||||
|
[JobIDs.CNJ] = 94528,
|
||||||
|
[JobIDs.WHM] = 94584,
|
||||||
|
[JobIDs.SCH] = 94587,
|
||||||
|
[JobIDs.SGE] = 94633,
|
||||||
|
[JobIDs.AST] = 94624,
|
||||||
|
|
||||||
|
// crafters
|
||||||
|
[JobIDs.CRP] = 91031,
|
||||||
|
[JobIDs.BSM] = 91032,
|
||||||
|
[JobIDs.ARM] = 91033,
|
||||||
|
[JobIDs.GSM] = 91034,
|
||||||
|
[JobIDs.LTW] = 91034,
|
||||||
|
[JobIDs.WVR] = 91036,
|
||||||
|
[JobIDs.ALC] = 91037,
|
||||||
|
[JobIDs.CUL] = 91038,
|
||||||
|
|
||||||
|
// gatherers
|
||||||
|
[JobIDs.MIN] = 91039,
|
||||||
|
[JobIDs.BOT] = 91039,
|
||||||
|
[JobIDs.FSH] = 91041,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<JobRoles, PrimaryResourceTypes> PrimaryResourceTypesByRole = new Dictionary<JobRoles, PrimaryResourceTypes>()
|
||||||
|
{
|
||||||
|
[JobRoles.Tank] = PrimaryResourceTypes.MP,
|
||||||
|
[JobRoles.Healer] = PrimaryResourceTypes.MP,
|
||||||
|
[JobRoles.DPSMelee] = PrimaryResourceTypes.MP,
|
||||||
|
[JobRoles.DPSRanged] = PrimaryResourceTypes.MP,
|
||||||
|
[JobRoles.DPSCaster] = PrimaryResourceTypes.MP,
|
||||||
|
[JobRoles.Crafter] = PrimaryResourceTypes.CP,
|
||||||
|
[JobRoles.Gatherer] = PrimaryResourceTypes.GP,
|
||||||
|
[JobRoles.Unknown] = PrimaryResourceTypes.MP
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JobIDs
|
||||||
|
{
|
||||||
|
public const uint GLA = 1;
|
||||||
|
public const uint MRD = 3;
|
||||||
|
public const uint PLD = 19;
|
||||||
|
public const uint WAR = 21;
|
||||||
|
public const uint DRK = 32;
|
||||||
|
public const uint GNB = 37;
|
||||||
|
|
||||||
|
public const uint CNJ = 6;
|
||||||
|
public const uint WHM = 24;
|
||||||
|
public const uint SCH = 28;
|
||||||
|
public const uint AST = 33;
|
||||||
|
public const uint SGE = 40;
|
||||||
|
|
||||||
|
public const uint PGL = 2;
|
||||||
|
public const uint LNC = 4;
|
||||||
|
public const uint ROG = 29;
|
||||||
|
public const uint MNK = 20;
|
||||||
|
public const uint DRG = 22;
|
||||||
|
public const uint NIN = 30;
|
||||||
|
public const uint SAM = 34;
|
||||||
|
public const uint RPR = 39;
|
||||||
|
public const uint VPR = 41;
|
||||||
|
|
||||||
|
public const uint ARC = 5;
|
||||||
|
public const uint BRD = 23;
|
||||||
|
public const uint MCH = 31;
|
||||||
|
public const uint DNC = 38;
|
||||||
|
|
||||||
|
public const uint THM = 7;
|
||||||
|
public const uint ACN = 26;
|
||||||
|
public const uint BLM = 25;
|
||||||
|
public const uint SMN = 27;
|
||||||
|
public const uint RDM = 35;
|
||||||
|
public const uint BLU = 36;
|
||||||
|
public const uint PCT = 42;
|
||||||
|
|
||||||
|
public const uint CRP = 8;
|
||||||
|
public const uint BSM = 9;
|
||||||
|
public const uint ARM = 10;
|
||||||
|
public const uint GSM = 11;
|
||||||
|
public const uint LTW = 12;
|
||||||
|
public const uint WVR = 13;
|
||||||
|
public const uint ALC = 14;
|
||||||
|
public const uint CUL = 15;
|
||||||
|
|
||||||
|
public const uint MIN = 16;
|
||||||
|
public const uint BOT = 17;
|
||||||
|
public const uint FSH = 18;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using Lumina.Excel;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
using Companion = Lumina.Excel.Sheets.Companion;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public class LastUsedCast
|
||||||
|
{
|
||||||
|
private object? _lastUsedAction;
|
||||||
|
|
||||||
|
public readonly bool Interruptible;
|
||||||
|
public readonly ActionType ActionType;
|
||||||
|
public readonly uint CastId;
|
||||||
|
private uint? _iconId;
|
||||||
|
|
||||||
|
public string ActionText { get; private set; } = "";
|
||||||
|
public DamageType DamageType { get; private set; } = DamageType.Unknown;
|
||||||
|
|
||||||
|
public LastUsedCast(uint castId, ActionType actionType, bool interruptible)
|
||||||
|
{
|
||||||
|
CastId = castId;
|
||||||
|
ActionType = actionType;
|
||||||
|
Interruptible = interruptible;
|
||||||
|
|
||||||
|
SetCastProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetCastProperties()
|
||||||
|
{
|
||||||
|
IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
|
||||||
|
ObjectKind? targetKind = target?.ObjectKind;
|
||||||
|
|
||||||
|
switch (targetKind)
|
||||||
|
{
|
||||||
|
case null:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ObjectKind.Aetheryte:
|
||||||
|
ActionText = "Attuning...";
|
||||||
|
_iconId = 112;
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
case ObjectKind.EventObj:
|
||||||
|
case ObjectKind.EventNpc:
|
||||||
|
ActionText = "Interacting...";
|
||||||
|
_iconId = null;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastUsedAction = null;
|
||||||
|
if (CastId == 1 && ActionType != ActionType.Mount)
|
||||||
|
{
|
||||||
|
ActionText = "Interacting...";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionText = "Casting";
|
||||||
|
_iconId = null;
|
||||||
|
|
||||||
|
switch (ActionType)
|
||||||
|
{
|
||||||
|
case ActionType.PetAction:
|
||||||
|
case ActionType.Action:
|
||||||
|
case ActionType.BgcArmyAction:
|
||||||
|
case ActionType.PvPAction:
|
||||||
|
case ActionType.CraftAction:
|
||||||
|
case ActionType.EventAction:
|
||||||
|
Action? action = Plugin.DataManager.GetExcelSheet<Action>()?.GetRow(CastId);
|
||||||
|
ActionText = action?.Name.ToString() ?? "";
|
||||||
|
DamageType = GetDamageType(action);
|
||||||
|
_lastUsedAction = action;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActionType.Mount:
|
||||||
|
Mount? mount = Plugin.DataManager.GetExcelSheet<Mount>()?.GetRow(CastId);
|
||||||
|
ActionText = mount?.Singular.ToString() ?? "";
|
||||||
|
DamageType = DamageType.Unknown;
|
||||||
|
_lastUsedAction = mount;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActionType.EventItem:
|
||||||
|
case ActionType.Item:
|
||||||
|
Item? item = Plugin.DataManager.GetExcelSheet<Item>()?.GetRow(CastId);
|
||||||
|
ActionText = item?.Name.ToString() ?? "Using item...";
|
||||||
|
DamageType = DamageType.Unknown;
|
||||||
|
_lastUsedAction = item;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActionType.Companion:
|
||||||
|
Companion? companion = Plugin.DataManager.GetExcelSheet<Companion>()?.GetRow(CastId);
|
||||||
|
ActionText = companion?.Singular.ToString() ?? "";
|
||||||
|
DamageType = DamageType.Unknown;
|
||||||
|
_lastUsedAction = companion;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
_lastUsedAction = null;
|
||||||
|
ActionText = "Casting...";
|
||||||
|
DamageType = DamageType.Unknown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DamageType GetDamageType(Action? action)
|
||||||
|
{
|
||||||
|
if (!action.HasValue)
|
||||||
|
{
|
||||||
|
return DamageType.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
DamageType damageType = (DamageType)action.Value.AttackType.RowId;
|
||||||
|
|
||||||
|
if (damageType != DamageType.Magic && damageType != DamageType.Darkness && damageType != DamageType.Unknown)
|
||||||
|
{
|
||||||
|
damageType = DamageType.Physical;
|
||||||
|
}
|
||||||
|
|
||||||
|
return damageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDalamudTextureWrap? GetIconTexture()
|
||||||
|
{
|
||||||
|
if (_iconId.HasValue)
|
||||||
|
{
|
||||||
|
return TexturesHelper.GetTexture<Action>(_iconId.Value);
|
||||||
|
}
|
||||||
|
else if (_lastUsedAction is Action action)
|
||||||
|
{
|
||||||
|
return TexturesHelper.GetTextureFromIconId(action.Icon);
|
||||||
|
}
|
||||||
|
else if (_lastUsedAction is Mount mount)
|
||||||
|
{
|
||||||
|
return TexturesHelper.GetTextureFromIconId(mount.Icon);
|
||||||
|
}
|
||||||
|
else if (_lastUsedAction is Item item)
|
||||||
|
{
|
||||||
|
return TexturesHelper.GetTextureFromIconId(item.Icon);
|
||||||
|
}
|
||||||
|
else if (_lastUsedAction is Companion companion)
|
||||||
|
{
|
||||||
|
return TexturesHelper.GetTextureFromIconId(companion.Icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public struct LayoutInfo
|
||||||
|
{
|
||||||
|
public readonly uint TotalRowCount;
|
||||||
|
public readonly uint TotalColCount;
|
||||||
|
public readonly uint RealRowCount;
|
||||||
|
public readonly uint RealColCount;
|
||||||
|
public readonly Vector2 ContentSize;
|
||||||
|
|
||||||
|
public LayoutInfo(uint totalRowCount, uint totalColCount, uint realRowCount, uint realColCount, Vector2 contentSize)
|
||||||
|
{
|
||||||
|
TotalRowCount = totalRowCount;
|
||||||
|
TotalColCount = totalColCount;
|
||||||
|
RealRowCount = realRowCount;
|
||||||
|
RealColCount = realColCount;
|
||||||
|
ContentSize = contentSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LayoutHelper
|
||||||
|
{
|
||||||
|
// Calculates rows and columns. Used for status effect lists and party frames.
|
||||||
|
public static LayoutInfo CalculateLayout(
|
||||||
|
Vector2 maxSize,
|
||||||
|
Vector2 itemSize,
|
||||||
|
uint count,
|
||||||
|
Vector2 padding,
|
||||||
|
bool fillRowsFirst
|
||||||
|
)
|
||||||
|
{
|
||||||
|
uint rowCount = 1;
|
||||||
|
uint colCount = 1;
|
||||||
|
uint realRowCount = 1;
|
||||||
|
uint realColCount = 1;
|
||||||
|
|
||||||
|
if (maxSize.X < itemSize.X)
|
||||||
|
{
|
||||||
|
colCount = count;
|
||||||
|
realColCount = count;
|
||||||
|
}
|
||||||
|
else if (maxSize.Y < itemSize.Y)
|
||||||
|
{
|
||||||
|
rowCount = count;
|
||||||
|
realRowCount = count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (fillRowsFirst)
|
||||||
|
{
|
||||||
|
float remainingWidth = maxSize.X;
|
||||||
|
colCount = 0;
|
||||||
|
while (remainingWidth > 0)
|
||||||
|
{
|
||||||
|
remainingWidth -= (itemSize.X + padding.X);
|
||||||
|
colCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemSize.X * colCount + padding.X * (colCount - 1) > maxSize.X)
|
||||||
|
{
|
||||||
|
colCount = Math.Max(1, colCount - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
rowCount = (uint)Math.Ceiling((double)count / colCount);
|
||||||
|
|
||||||
|
int remaining = (int)(count - colCount);
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
realRowCount++;
|
||||||
|
remaining -= (int)colCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
realColCount = Math.Min(count, colCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float remainingHeight = maxSize.Y;
|
||||||
|
rowCount = 0;
|
||||||
|
while (remainingHeight > 0)
|
||||||
|
{
|
||||||
|
remainingHeight -= (itemSize.Y + padding.Y);
|
||||||
|
rowCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemSize.Y * rowCount + padding.Y * (rowCount - 1) > maxSize.Y)
|
||||||
|
{
|
||||||
|
rowCount = Math.Max(1, rowCount - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
colCount = (uint)Math.Ceiling((double)count / rowCount);
|
||||||
|
|
||||||
|
int remaining = (int)(count - rowCount);
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
realColCount++;
|
||||||
|
remaining -= (int)rowCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
realRowCount = Math.Min(count, rowCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 contentSize = new Vector2(
|
||||||
|
realColCount * itemSize.X + (realColCount - 1) * padding.X,
|
||||||
|
realRowCount * itemSize.Y + (realRowCount - 1) * padding.Y
|
||||||
|
);
|
||||||
|
|
||||||
|
return new LayoutInfo(rowCount, colCount, realRowCount, realColCount, contentSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<GrowthDirections> DirectionOptionsValues = new List<GrowthDirections>()
|
||||||
|
{
|
||||||
|
GrowthDirections.Right | GrowthDirections.Down,
|
||||||
|
GrowthDirections.Right | GrowthDirections.Up,
|
||||||
|
GrowthDirections.Left | GrowthDirections.Down,
|
||||||
|
GrowthDirections.Left | GrowthDirections.Up,
|
||||||
|
GrowthDirections.Centered | GrowthDirections.Up,
|
||||||
|
GrowthDirections.Centered | GrowthDirections.Down,
|
||||||
|
GrowthDirections.Centered | GrowthDirections.Left,
|
||||||
|
GrowthDirections.Centered | GrowthDirections.Right
|
||||||
|
};
|
||||||
|
public static GrowthDirections GrowthDirectionsFromIndex(int index)
|
||||||
|
{
|
||||||
|
if (index > 0 && index < DirectionOptionsValues.Count)
|
||||||
|
{
|
||||||
|
return DirectionOptionsValues[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return DirectionOptionsValues[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int IndexFromGrowthDirections(GrowthDirections directions)
|
||||||
|
{
|
||||||
|
int index = DirectionOptionsValues.FindIndex(d => d == directions);
|
||||||
|
|
||||||
|
return index > 0 ? index : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GetFillsRowsFirst(bool fallback, GrowthDirections directions)
|
||||||
|
{
|
||||||
|
if ((directions & GrowthDirections.Centered) != 0)
|
||||||
|
{
|
||||||
|
if ((directions & GrowthDirections.Up) != 0 || (directions & GrowthDirections.Down) != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if ((directions & GrowthDirections.Left) != 0 || (directions & GrowthDirections.Right) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CalculateAxisDirections(
|
||||||
|
GrowthDirections growthDirections,
|
||||||
|
int row,
|
||||||
|
int col,
|
||||||
|
uint elementCount,
|
||||||
|
Vector2 size,
|
||||||
|
Vector2 iconSize,
|
||||||
|
Vector2 iconPadding,
|
||||||
|
out Vector2 direction,
|
||||||
|
out Vector2 offset)
|
||||||
|
{
|
||||||
|
if ((growthDirections & GrowthDirections.Centered) != 0)
|
||||||
|
{
|
||||||
|
if ((growthDirections & GrowthDirections.Up) != 0 || (growthDirections & GrowthDirections.Down) != 0)
|
||||||
|
{
|
||||||
|
int elementsPerRow = (int)(size.X / (iconSize.X + iconPadding.X));
|
||||||
|
long elementsInRow = Math.Min(elementsPerRow, elementCount - (elementsPerRow * row));
|
||||||
|
|
||||||
|
direction.X = 1;
|
||||||
|
direction.Y = (growthDirections & GrowthDirections.Down) != 0 ? 1 : -1;
|
||||||
|
offset.X = -(iconSize.X + iconPadding.X) * elementsInRow / 2f;
|
||||||
|
offset.Y = direction.Y == 1 ? 0 : -iconSize.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
else// if ((growthDirections & GrowthDirections.Left) != 0 || (growthDirections & GrowthDirections.Right) != 0)
|
||||||
|
{
|
||||||
|
int elementsPerCol = (int)(size.Y / (iconSize.Y + iconPadding.Y));
|
||||||
|
long elementsInCol = Math.Min(elementsPerCol, elementCount - (elementsPerCol * col));
|
||||||
|
|
||||||
|
direction.X = (growthDirections & GrowthDirections.Left) != 0 ? -1 : 1;
|
||||||
|
direction.Y = 1;
|
||||||
|
offset.X = direction.X == 1 ? 0 : -iconSize.X;
|
||||||
|
offset.Y = -(iconSize.Y + iconPadding.Y) * elementsInCol / 2f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
direction.X = (growthDirections & GrowthDirections.Right) != 0 ? 1 : -1;
|
||||||
|
direction.Y = (growthDirections & GrowthDirections.Down) != 0 ? 1 : -1;
|
||||||
|
offset.X = direction.X == 1 ? 0 : -iconSize.X;
|
||||||
|
offset.Y = direction.Y == 1 ? 0 : -iconSize.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2 CalculateStartPosition(Vector2 position, Vector2 size, GrowthDirections growthDirections)
|
||||||
|
{
|
||||||
|
Vector2 area = size;
|
||||||
|
if ((growthDirections & GrowthDirections.Left) != 0)
|
||||||
|
{
|
||||||
|
area.X = -area.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((growthDirections & GrowthDirections.Up) != 0)
|
||||||
|
{
|
||||||
|
area.Y = -area.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 startPos = position;
|
||||||
|
if ((growthDirections & GrowthDirections.Centered) != 0)
|
||||||
|
{
|
||||||
|
if ((growthDirections & GrowthDirections.Up) != 0 || (growthDirections & GrowthDirections.Down) != 0)
|
||||||
|
{
|
||||||
|
startPos.X = position.X - size.X / 2f;
|
||||||
|
}
|
||||||
|
else if ((growthDirections & GrowthDirections.Left) != 0 || (growthDirections & GrowthDirections.Right) != 0)
|
||||||
|
{
|
||||||
|
startPos.Y = position.Y - size.Y / 2f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 endPos = position + area;
|
||||||
|
|
||||||
|
if (endPos.X < position.X)
|
||||||
|
{
|
||||||
|
startPos.X = endPos.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endPos.Y < position.Y)
|
||||||
|
{
|
||||||
|
startPos.Y = endPos.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return startPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (List<Vector2>, Vector2, Vector2) CalculateIconPositions(
|
||||||
|
GrowthDirections directions,
|
||||||
|
uint count,
|
||||||
|
Vector2 position,
|
||||||
|
Vector2 size,
|
||||||
|
Vector2 iconSize,
|
||||||
|
Vector2 iconPadding,
|
||||||
|
bool fillRowsFirst,
|
||||||
|
LayoutInfo layoutInfo)
|
||||||
|
{
|
||||||
|
List<Vector2> list = new List<Vector2>();
|
||||||
|
Vector2 minPos = new Vector2(float.MaxValue, float.MaxValue);
|
||||||
|
Vector2 maxPos = Vector2.Zero;
|
||||||
|
|
||||||
|
int row = 0;
|
||||||
|
int col = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
CalculateAxisDirections(
|
||||||
|
directions,
|
||||||
|
row,
|
||||||
|
col,
|
||||||
|
count,
|
||||||
|
size,
|
||||||
|
iconSize,
|
||||||
|
iconPadding,
|
||||||
|
out Vector2 direction,
|
||||||
|
out Vector2 offset
|
||||||
|
);
|
||||||
|
|
||||||
|
Vector2 pos = new Vector2(
|
||||||
|
position.X + offset.X + iconSize.X * col * direction.X + iconPadding.X * col * direction.X,
|
||||||
|
position.Y + offset.Y + iconSize.Y * row * direction.Y + iconPadding.Y * row * direction.Y
|
||||||
|
);
|
||||||
|
|
||||||
|
minPos.X = Math.Min(pos.X, minPos.X);
|
||||||
|
minPos.Y = Math.Min(pos.Y, minPos.Y);
|
||||||
|
maxPos.X = Math.Max(pos.X + iconSize.X, maxPos.X);
|
||||||
|
maxPos.Y = Math.Max(pos.Y + iconSize.Y, maxPos.Y);
|
||||||
|
|
||||||
|
list.Add(pos);
|
||||||
|
|
||||||
|
// rows / columns
|
||||||
|
if (fillRowsFirst)
|
||||||
|
{
|
||||||
|
col += 1;
|
||||||
|
if (col >= layoutInfo.TotalColCount)
|
||||||
|
{
|
||||||
|
col = 0;
|
||||||
|
row += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
row += 1;
|
||||||
|
if (row >= layoutInfo.TotalRowCount)
|
||||||
|
{
|
||||||
|
row = 0;
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (list, minPos, maxPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum GrowthDirections : short
|
||||||
|
{
|
||||||
|
Up = 1,
|
||||||
|
Down = 2,
|
||||||
|
Left = 4,
|
||||||
|
Right = 8,
|
||||||
|
Centered = 16,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
Copyright(c) 2021 talimity (https://github.com/talimity/mptimer)
|
||||||
|
Modifications Copyright(c) 2021 HSUI
|
||||||
|
08/29/2021 - Mostly using original's code with minimal adaptations
|
||||||
|
for HSUI.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
internal class MPTickHelper : IDisposable
|
||||||
|
{
|
||||||
|
public const double ServerTickRate = 3;
|
||||||
|
protected const float PollingRate = 1 / 30f;
|
||||||
|
private int _lastMpValue = -1;
|
||||||
|
protected double LastTickTime;
|
||||||
|
protected double LastUpdate;
|
||||||
|
|
||||||
|
public MPTickHelper()
|
||||||
|
{
|
||||||
|
Plugin.Framework.Update += FrameworkOnOnUpdateEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double LastTick => LastTickTime;
|
||||||
|
|
||||||
|
private void FrameworkOnOnUpdateEvent(IFramework framework)
|
||||||
|
{
|
||||||
|
var player = Plugin.ObjectTable.LocalPlayer;
|
||||||
|
if (player is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = ImGui.GetTime();
|
||||||
|
if (now - LastUpdate < PollingRate)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LastUpdate = now;
|
||||||
|
|
||||||
|
var mp = player.CurrentMp;
|
||||||
|
|
||||||
|
// account for lucid dreaming screwing up mp calculations
|
||||||
|
var lucidDreamingActive = Utils.StatusListForBattleChara(player).Any(e => e.StatusId == 1204);
|
||||||
|
|
||||||
|
if (!lucidDreamingActive && _lastMpValue < mp)
|
||||||
|
{
|
||||||
|
LastTickTime = now;
|
||||||
|
}
|
||||||
|
else if (LastTickTime + ServerTickRate <= now)
|
||||||
|
{
|
||||||
|
LastTickTime += ServerTickRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastMpValue = (int)mp;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugin.Framework.Update -= FrameworkOnOnUpdateEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~MPTickHelper()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
internal class PetRenamerHelper
|
||||||
|
{
|
||||||
|
private Dictionary<ulong, string>? PetNicknamesDictionary;
|
||||||
|
|
||||||
|
#region Singleton
|
||||||
|
public static void Initialize() { Instance = new PetRenamerHelper(); }
|
||||||
|
|
||||||
|
public static PetRenamerHelper Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
public PetRenamerHelper()
|
||||||
|
{
|
||||||
|
AssignShares();
|
||||||
|
}
|
||||||
|
|
||||||
|
~PetRenamerHelper()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugin.PluginInterface.RelinquishData("PetRenamer.GameObjectRenameDict");
|
||||||
|
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void AssignShares()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PetNicknamesDictionary = Plugin.PluginInterface.GetOrCreateData("PetRenamer.GameObjectRenameDict", () => new Dictionary<ulong, string>());
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetNameForActor(IGameObject actor)
|
||||||
|
{
|
||||||
|
if (PetNicknamesDictionary == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PetNicknamesDictionary.TryGetValue(actor.GameObjectId, out string? nickname))
|
||||||
|
{
|
||||||
|
return nickname;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? GetPetName(IGameObject? actor)
|
||||||
|
{
|
||||||
|
if (actor == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actor.ObjectKind != ObjectKind.Companion && actor.ObjectKind != ObjectKind.BattleNpc)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetNameForActor(actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
Copyright(c) 2021 xorus (https://github.com/xorus/EngageTimer)
|
||||||
|
Modifications Copyright(c) 2021 HSUI
|
||||||
|
09/21/2021 - Extracted code to hook the game's pulltimer functions.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public unsafe class PullTimerHelper
|
||||||
|
{
|
||||||
|
#region Singleton
|
||||||
|
private PullTimerHelper()
|
||||||
|
{
|
||||||
|
PullTimerState = new PullTimerState();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_countdownTimerHook = Plugin.GameInteropProvider.HookFromAddress<AgentInterface.Delegates.Update>(
|
||||||
|
AgentModule.Instance()->GetAgentByInternalId(AgentId.CountDownSettingDialog)->VirtualTable->Update,
|
||||||
|
CountdownTimerFunc);
|
||||||
|
_countdownTimerHook?.Enable();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("PullTimeHelper CountdownTimer Hook failed!!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void Initialize() { Instance = new PullTimerHelper(); }
|
||||||
|
public static PullTimerHelper Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
~PullTimerHelper()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_countdownTimerHook?.Disable();
|
||||||
|
_countdownTimerHook?.Dispose();
|
||||||
|
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private DateTime _combatTimeEnd;
|
||||||
|
private DateTime _combatTimeStart;
|
||||||
|
|
||||||
|
private ulong _agentData;
|
||||||
|
public bool CountDownRunning;
|
||||||
|
|
||||||
|
private int _countDownStallTicks;
|
||||||
|
|
||||||
|
private readonly Hook<AgentInterface.Delegates.Update>? _countdownTimerHook;
|
||||||
|
public float LastCountDownValue;
|
||||||
|
private bool _shouldRestartCombatTimer = true;
|
||||||
|
private bool _lastMaxValueSet = false;
|
||||||
|
|
||||||
|
public readonly PullTimerState PullTimerState;
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (PullTimerState.Mocked)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateCountDown();
|
||||||
|
UpdateEncounterTimer();
|
||||||
|
PullTimerState.InInstance = Plugin.Condition[ConditionFlag.BoundByDuty];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CountdownTimerFunc(AgentInterface* agentInterface, uint frameCount)
|
||||||
|
{
|
||||||
|
_agentData = (ulong)agentInterface;
|
||||||
|
_countdownTimerHook?.Original(agentInterface, frameCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateEncounterTimer()
|
||||||
|
{
|
||||||
|
if (Plugin.Condition[ConditionFlag.InCombat])
|
||||||
|
{
|
||||||
|
PullTimerState.InCombat = true;
|
||||||
|
if (_shouldRestartCombatTimer)
|
||||||
|
{
|
||||||
|
_shouldRestartCombatTimer = false;
|
||||||
|
_combatTimeStart = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
_combatTimeEnd = DateTime.Now;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PullTimerState.InCombat = false;
|
||||||
|
_shouldRestartCombatTimer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PullTimerState.CombatStart = _combatTimeStart;
|
||||||
|
PullTimerState.CombatDuration = _combatTimeEnd - _combatTimeStart;
|
||||||
|
PullTimerState.CombatEnd = _combatTimeEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCountDown()
|
||||||
|
{
|
||||||
|
PullTimerState.CountingDown = false;
|
||||||
|
|
||||||
|
if (_agentData == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte countdownActive = Marshal.PtrToStructure<byte>((IntPtr)_agentData + 0x38);
|
||||||
|
if (countdownActive == 0)
|
||||||
|
{
|
||||||
|
_lastMaxValueSet = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float countDownPointerValue = Marshal.PtrToStructure<float>((IntPtr)_agentData + 0x2c);
|
||||||
|
|
||||||
|
// is last value close enough (workaround for floating point approx)
|
||||||
|
if (Math.Abs(countDownPointerValue - LastCountDownValue) < 0.001f)
|
||||||
|
{
|
||||||
|
_countDownStallTicks++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_countDownStallTicks = 0;
|
||||||
|
CountDownRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_countDownStallTicks > 50)
|
||||||
|
{
|
||||||
|
CountDownRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (countDownPointerValue > 0 && CountDownRunning)
|
||||||
|
{
|
||||||
|
PullTimerState.CountDownValue = countDownPointerValue;
|
||||||
|
PullTimerState.CountingDown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_lastMaxValueSet && CountDownRunning)
|
||||||
|
{
|
||||||
|
PullTimerState.CountDownMax = countDownPointerValue;
|
||||||
|
_lastMaxValueSet = true;
|
||||||
|
}
|
||||||
|
else if (_lastMaxValueSet && countDownPointerValue <= 0)
|
||||||
|
{
|
||||||
|
_lastMaxValueSet = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LastCountDownValue = countDownPointerValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PullTimerState
|
||||||
|
{
|
||||||
|
private bool _inCombat;
|
||||||
|
private bool _countingDown;
|
||||||
|
public TimeSpan CombatDuration { get; set; }
|
||||||
|
public DateTime CombatEnd { get; set; }
|
||||||
|
public DateTime CombatStart { get; set; }
|
||||||
|
|
||||||
|
public bool Mocked { get; set; }
|
||||||
|
|
||||||
|
public bool InCombat
|
||||||
|
{
|
||||||
|
get => _inCombat;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_inCombat == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_inCombat = value;
|
||||||
|
InCombatChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CountingDown
|
||||||
|
{
|
||||||
|
get => _countingDown;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_countingDown == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_countingDown = value;
|
||||||
|
CountingDownChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool InInstance { get; set; }
|
||||||
|
|
||||||
|
public float CountDownValue { get; set; } = 0f;
|
||||||
|
public float CountDownMax { get; set; } = 0f;
|
||||||
|
public event EventHandler? InCombatChanged;
|
||||||
|
public event EventHandler? CountingDownChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public class SmoothHPHelper
|
||||||
|
{
|
||||||
|
private float? _startHp;
|
||||||
|
private float? _targetHp;
|
||||||
|
private float? _lastHp;
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_startHp = null;
|
||||||
|
_targetHp = null;
|
||||||
|
_lastHp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetNextHp(int currentHp, int maxHp, float velocity)
|
||||||
|
{
|
||||||
|
if (!_startHp.HasValue || !_targetHp.HasValue || !_lastHp.HasValue)
|
||||||
|
{
|
||||||
|
_lastHp = currentHp;
|
||||||
|
_startHp = currentHp;
|
||||||
|
_targetHp = currentHp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentHp != _lastHp)
|
||||||
|
{
|
||||||
|
_startHp = _lastHp;
|
||||||
|
_targetHp = currentHp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_startHp.HasValue && _targetHp.HasValue)
|
||||||
|
{
|
||||||
|
float delta = _targetHp.Value - _startHp.Value;
|
||||||
|
float offset = delta * velocity / 100f;
|
||||||
|
_startHp = Math.Clamp(_startHp.Value + offset, 0, maxHp);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastHp = currentHp;
|
||||||
|
return _startHp.HasValue ? (uint)_startHp.Value : (uint)currentHp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
internal class SpellHelper
|
||||||
|
{
|
||||||
|
#region Singleton
|
||||||
|
private static Lazy<SpellHelper> _lazyInstance = new Lazy<SpellHelper>(() => new SpellHelper());
|
||||||
|
|
||||||
|
public static SpellHelper Instance => _lazyInstance.Value;
|
||||||
|
|
||||||
|
~SpellHelper()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lazyInstance = new Lazy<SpellHelper>(() => new SpellHelper());
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private readonly unsafe ActionManager* _actionManager;
|
||||||
|
|
||||||
|
public unsafe SpellHelper()
|
||||||
|
{
|
||||||
|
_actionManager = ActionManager.Instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe uint GetSpellActionId(uint actionId) => _actionManager->GetAdjustedActionId(actionId);
|
||||||
|
|
||||||
|
public unsafe float GetRecastTimeElapsed(uint actionId) => _actionManager->GetRecastTimeElapsed(ActionType.Action, GetSpellActionId(actionId));
|
||||||
|
public unsafe float GetRealRecastTimeElapsed(uint actionId) => _actionManager->GetRecastTimeElapsed(ActionType.Action, actionId);
|
||||||
|
|
||||||
|
public unsafe float GetRecastTime(uint actionId) => _actionManager->GetRecastTime(ActionType.Action, GetSpellActionId(actionId));
|
||||||
|
public unsafe float GetRealRecastTime(uint actionId) => _actionManager->GetRecastTime(ActionType.Action, actionId);
|
||||||
|
|
||||||
|
public unsafe uint GetLastUsedActionId() => _actionManager->Combo.Action;
|
||||||
|
|
||||||
|
public float GetSpellCooldown(uint actionId) => Math.Abs(GetRecastTime(GetSpellActionId(actionId)) - GetRecastTimeElapsed(GetSpellActionId(actionId)));
|
||||||
|
public float GetRealSpellCooldown(uint actionId) => Math.Abs(GetRealRecastTime(actionId) - GetRealRecastTimeElapsed(actionId));
|
||||||
|
|
||||||
|
public int GetSpellCooldownInt(uint actionId)
|
||||||
|
{
|
||||||
|
int cooldown = (int)Math.Ceiling(GetSpellCooldown(actionId) % GetRecastTime(actionId));
|
||||||
|
return Math.Max(0, cooldown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetStackCount(int maxStacks, uint actionId)
|
||||||
|
{
|
||||||
|
int cooldown = GetSpellCooldownInt(actionId);
|
||||||
|
float recastTime = GetRecastTime(actionId);
|
||||||
|
|
||||||
|
if (cooldown <= 0 || recastTime == 0)
|
||||||
|
{
|
||||||
|
return maxStacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxStacks - (int)Math.Ceiling(cooldown / (recastTime / maxStacks));
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool IsActionHighlighted(uint actionId, ActionType type = ActionType.Action) => _actionManager->IsActionHighlighted(type, actionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,531 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using HSUI.Config;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using StructsBattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public static class TextTagsHelper
|
||||||
|
{
|
||||||
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
foreach (string key in HealthTextTags.Keys)
|
||||||
|
{
|
||||||
|
CharaTextTags.Add(key, (chara) => HealthTextTags[key](chara.CurrentHp, chara.MaxHp));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string key in ManaTextTags.Keys)
|
||||||
|
{
|
||||||
|
CharaTextTags.Add(key, (chara) => ManaTextTags[key](JobsHelper.CurrentPrimaryResource(chara), JobsHelper.MaxPrimaryResource(chara)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<string, Func<IGameObject?, string?, int, bool?, string>> TextTags = new Dictionary<string, Func<IGameObject?, string?, int, bool?, string>>()
|
||||||
|
{
|
||||||
|
#region generic names
|
||||||
|
["[name]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidateName(actor, name).
|
||||||
|
Truncated(length).
|
||||||
|
CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[name:first]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidateName(actor, name).
|
||||||
|
FirstName().
|
||||||
|
Truncated(length).
|
||||||
|
CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[name:last]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidateName(actor, name).
|
||||||
|
LastName().
|
||||||
|
Truncated(length).
|
||||||
|
CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[name:initials]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidateName(actor, name).
|
||||||
|
Initials().
|
||||||
|
Truncated(length).
|
||||||
|
CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[name:abbreviate]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidateName(actor, name).
|
||||||
|
Abbreviate().
|
||||||
|
CheckForUpperCase(),
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region player names
|
||||||
|
["[player_name]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidatePlayerName(actor, name, isPlayerName).
|
||||||
|
Truncated(length).
|
||||||
|
CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[player_name:first]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidatePlayerName(actor, name, isPlayerName).
|
||||||
|
FirstName().
|
||||||
|
Truncated(length).
|
||||||
|
CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[player_name:last]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidatePlayerName(actor, name, isPlayerName).
|
||||||
|
LastName().
|
||||||
|
Truncated(length).
|
||||||
|
CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[player_name:initials]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidatePlayerName(actor, name, isPlayerName).
|
||||||
|
Initials().
|
||||||
|
Truncated(length).
|
||||||
|
CheckForUpperCase(),
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region npc names
|
||||||
|
["[npc_name]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidateNPCName(actor, name, isPlayerName).
|
||||||
|
CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[npc_name:first]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidateNPCName(actor, name, isPlayerName).
|
||||||
|
FirstName().
|
||||||
|
CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[npc_name:last]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidateNPCName(actor, name, isPlayerName).
|
||||||
|
LastName().
|
||||||
|
CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[npc_name:initials]"] = (actor, name, length, isPlayerName) =>
|
||||||
|
ValidateNPCName(actor, name, isPlayerName).
|
||||||
|
Initials().
|
||||||
|
CheckForUpperCase(),
|
||||||
|
#endregion
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<string, Func<IGameObject?, string?, string>> ExpTags = new Dictionary<string, Func<IGameObject?, string?, string>>()
|
||||||
|
{
|
||||||
|
#region experience
|
||||||
|
["[exp:current]"] = (actor, name) => ExperienceHelper.Instance.CurrentExp.ToString(),
|
||||||
|
|
||||||
|
["[exp:current-formatted]"] = (actor, name) => ExperienceHelper.Instance.CurrentExp.ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[exp:current-short]"] = (actor, name) => ExperienceHelper.Instance.CurrentExp.KiloFormat(),
|
||||||
|
|
||||||
|
["[exp:required]"] = (actor, name) => ExperienceHelper.Instance.RequiredExp.ToString(),
|
||||||
|
|
||||||
|
["[exp:required-formatted]"] = (actor, name) => ExperienceHelper.Instance.RequiredExp.ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[exp:required-short]"] = (actor, name) => ExperienceHelper.Instance.RequiredExp.KiloFormat(),
|
||||||
|
|
||||||
|
["[exp:required-to-level]"] = (actor, name) => (ExperienceHelper.Instance.RequiredExp - ExperienceHelper.Instance.CurrentExp).ToString(),
|
||||||
|
|
||||||
|
["[exp:required-to-level-formatted]"] = (actor, name) => (ExperienceHelper.Instance.RequiredExp - ExperienceHelper.Instance.CurrentExp).ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[exp:required-to-level-short]"] = (actor, name) => (ExperienceHelper.Instance.RequiredExp - ExperienceHelper.Instance.CurrentExp).KiloFormat(),
|
||||||
|
|
||||||
|
["[exp:rested]"] = (actor, name) => ExperienceHelper.Instance.RestedExp.ToString(),
|
||||||
|
|
||||||
|
["[exp:rested-formatted]"] = (actor, name) => ExperienceHelper.Instance.RestedExp.ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[exp:rested-short]"] = (actor, name) => ExperienceHelper.Instance.RestedExp.KiloFormat(),
|
||||||
|
|
||||||
|
["[exp:percent]"] = (actor, name) => ExperienceHelper.Instance.PercentExp.ToString("N0"),
|
||||||
|
|
||||||
|
["[exp:percent-decimal]"] = (actor, name) => ExperienceHelper.Instance.PercentExp.ToString("N1", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
#endregion
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<string, Func<uint, uint, string>> HealthTextTags = new Dictionary<string, Func<uint, uint, string>>()
|
||||||
|
{
|
||||||
|
#region health
|
||||||
|
["[health:current]"] = (currentHp, maxHp) => currentHp.ToString(),
|
||||||
|
|
||||||
|
["[health:current-formatted]"] = (currentHp, maxHp) => currentHp.ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[health:current-short]"] = (currentHp, maxHp) => currentHp.KiloFormat(),
|
||||||
|
|
||||||
|
["[health:current-percent]"] = (currentHp, maxHp) => currentHp == maxHp ? "100" : (100f * currentHp / Math.Max(1, maxHp)).ToString("N0"),
|
||||||
|
|
||||||
|
["[health:current-percent-short]"] = (currentHp, maxHp) => currentHp == maxHp ? currentHp.KiloFormat() : (100f * currentHp / Math.Max(1, maxHp)).ToString("N0"),
|
||||||
|
|
||||||
|
["[health:max]"] = (currentHp, maxHp) => maxHp.ToString(),
|
||||||
|
|
||||||
|
["[health:max-formatted]"] = (currentHp, maxHp) => maxHp.ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[health:max-short]"] = (currentHp, maxHp) => maxHp.KiloFormat(),
|
||||||
|
|
||||||
|
["[health:percent]"] = (currentHp, maxHp) => (100f * currentHp / Math.Max(1, maxHp)).ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[health:percent-hidden]"] = (currentHp, maxHp) => currentHp == (0 | maxHp) ? "" : (100f * currentHp / Math.Max(1, maxHp)).ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[health:percent-decimal]"] = (currentHp, maxHp) => (100f * currentHp / Math.Max(1f, maxHp)).ToString("N1", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[health:percent-decimal-uniform]"] = (currentHp, maxHp) => ConsistentDigitPercentage(currentHp, maxHp),
|
||||||
|
|
||||||
|
["[health:deficit]"] = (currentHp, maxHp) => currentHp == maxHp ? "0" : $"-{maxHp - currentHp}",
|
||||||
|
|
||||||
|
["[health:deficit-formatted]"] = (currentHp, maxHp) => currentHp == maxHp ? "0" : "-" + (maxHp - currentHp).ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[health:deficit-short]"] = (currentHp, maxHp) => currentHp == maxHp ? "0" : $"-{(maxHp - currentHp).KiloFormat()}",
|
||||||
|
#endregion
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<string, Func<uint, uint, string>> ManaTextTags = new Dictionary<string, Func<uint, uint, string>>()
|
||||||
|
{
|
||||||
|
#region mana
|
||||||
|
["[mana:current]"] = (currentMp, maxMp) => currentMp.ToString(),
|
||||||
|
|
||||||
|
["[mana:current-formatted]"] = (currentMp, maxMp) => currentMp.ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[mana:current-short]"] = (currentMp, maxMp) => currentMp.KiloFormat(),
|
||||||
|
|
||||||
|
["[mana:current-percent]"] = (currentMp, maxMp) => currentMp == maxMp ? "100" : (100f * currentMp / Math.Max(1, maxMp)).ToString("N0"),
|
||||||
|
|
||||||
|
["[mana:current-percent-short]"] = (currentMp, maxMp) => currentMp == maxMp ? currentMp.KiloFormat() : (100f * currentMp / Math.Max(1, maxMp)).ToString("N0"),
|
||||||
|
|
||||||
|
["[mana:max]"] = (currentMp, maxMp) => maxMp.ToString(),
|
||||||
|
|
||||||
|
["[mana:max-formatted]"] = (currentMp, maxMp) => maxMp.ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[mana:max-short]"] = (currentMp, maxMp) => maxMp.KiloFormat(),
|
||||||
|
|
||||||
|
["[mana:percent]"] = (currentMp, maxMp) => (100f * currentMp / Math.Max(1, maxMp)).ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[mana:percent-decimal]"] = (currentMp, maxMp) => (100f * currentMp / Math.Max(1, maxMp)).ToString("N1", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[mana:percent-decimal-uniform]"] = (currentMp, maxMp) => ConsistentDigitPercentage(currentMp, maxMp),
|
||||||
|
|
||||||
|
["[mana:deficit]"] = (currentMp, maxMp) => currentMp == maxMp ? "0" : $"-{maxMp - currentMp}",
|
||||||
|
|
||||||
|
["[mana:deficit-formatted]"] = (currentMp, maxMp) => currentMp == maxMp ? "0" : "-" + (maxMp - currentMp).ToString("N0", ConfigurationManager.Instance.ActiveCultreInfo),
|
||||||
|
|
||||||
|
["[mana:deficit-short]"] = (currentMp, maxMp) => currentMp == maxMp ? "0" : $"-{(maxMp - currentMp).KiloFormat()}",
|
||||||
|
#endregion
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<string, Func<ICharacter, string>> CharaTextTags = new Dictionary<string, Func<ICharacter, string>>()
|
||||||
|
{
|
||||||
|
#region misc
|
||||||
|
["[distance]"] = (chara) => (chara.YalmDistanceX + 1).ToString(),
|
||||||
|
|
||||||
|
["[company]"] = (chara) => chara.CompanyTag.ToString(),
|
||||||
|
|
||||||
|
["[company-formatted]"] = (chara) => !String.IsNullOrEmpty(chara.CompanyTag.ToString()) ? $"«{chara.CompanyTag}»" : "",
|
||||||
|
|
||||||
|
["[level]"] = (chara) => chara.Level > 0 ? chara.Level.ToString() : "-",
|
||||||
|
|
||||||
|
["[level:adjusted]"] = (chara) =>
|
||||||
|
{
|
||||||
|
if (chara is IBattleChara npc)
|
||||||
|
{
|
||||||
|
return GetZoneAdjustedLevel(npc, chara.Level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chara.Level > 0 ? chara.Level.ToString() : "-";
|
||||||
|
},
|
||||||
|
|
||||||
|
["[level:hidden]"] = (chara) => (chara.Level > 1 && chara.Level < 100) ? chara.Level.ToString() : "",
|
||||||
|
|
||||||
|
["[job]"] = (chara) => JobsHelper.JobNames.TryGetValue(chara.ClassJob.RowId, out var jobName) ? jobName : "",
|
||||||
|
|
||||||
|
["[job-full]"] = (chara) => JobsHelper.JobFullNames.TryGetValue(chara.ClassJob.RowId, out var jobName) ? jobName : "",
|
||||||
|
|
||||||
|
["[time-till-max-gp]"] = JobsHelper.TimeTillMaxGP,
|
||||||
|
|
||||||
|
["[chocobo-time]"] = (chara) =>
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
if (chara is IBattleNpc npc && npc.BattleNpcKind == BattleNpcSubKind.Chocobo)
|
||||||
|
{
|
||||||
|
float seconds = UIState.Instance()->Buddy.CompanionInfo.TimeLeft;
|
||||||
|
if (seconds <= 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSpan time = TimeSpan.FromSeconds(seconds);
|
||||||
|
return time.ToString(@"mm\:ss");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<string, Func<string, int, string>> TitleTextTags = new Dictionary<string, Func<string, int, string>>()
|
||||||
|
{
|
||||||
|
#region title
|
||||||
|
["[title]"] = (title, length) => title.Truncated(length).CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[title:first]"] = (title, length) => title.FirstName().Truncated(length).CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[title:last]"] = (title, length) => title.LastName().Truncated(length).CheckForUpperCase(),
|
||||||
|
|
||||||
|
["[title:initials]"] = (title, length) => title.Initials().Truncated(length).CheckForUpperCase(),
|
||||||
|
#endregion
|
||||||
|
};
|
||||||
|
|
||||||
|
private static List<Dictionary<string, Func<uint, uint, string>>> NumericValuesTagMaps = new List<Dictionary<string, Func<uint, uint, string>>>()
|
||||||
|
{
|
||||||
|
HealthTextTags,
|
||||||
|
ManaTextTags
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string ReplaceTagWithString(
|
||||||
|
string tag,
|
||||||
|
IGameObject? actor,
|
||||||
|
string? name = null,
|
||||||
|
uint? current = null,
|
||||||
|
uint? max = null,
|
||||||
|
bool? isPlayerName = null,
|
||||||
|
string? title = null)
|
||||||
|
{
|
||||||
|
int length = 0;
|
||||||
|
ParseLength(ref tag, ref length);
|
||||||
|
|
||||||
|
if (TextTags.TryGetValue(tag, out Func<IGameObject?, string?, int, bool?, string>? func) && func != null)
|
||||||
|
{
|
||||||
|
return func(actor, name, length, isPlayerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ExpTags.TryGetValue(tag, out Func<IGameObject?, string?, string>? expFunc) && expFunc != null)
|
||||||
|
{
|
||||||
|
return expFunc(actor, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actor is ICharacter chara &&
|
||||||
|
CharaTextTags.TryGetValue(tag, out Func<ICharacter, string>? charaFunc) && charaFunc != null)
|
||||||
|
{
|
||||||
|
return charaFunc(chara);
|
||||||
|
}
|
||||||
|
else if (current.HasValue && max.HasValue)
|
||||||
|
{
|
||||||
|
foreach (var map in NumericValuesTagMaps)
|
||||||
|
{
|
||||||
|
if (map.TryGetValue(tag, out Func<uint, uint, string>? numericFunc) && numericFunc != null)
|
||||||
|
{
|
||||||
|
return numericFunc(current.Value, max.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title != null &&
|
||||||
|
TitleTextTags.TryGetValue(tag, out Func<string, int, string>? titlefunc) && titlefunc != null)
|
||||||
|
{
|
||||||
|
return titlefunc(title, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FormattedText(
|
||||||
|
string text,
|
||||||
|
IGameObject? actor,
|
||||||
|
string? name = null,
|
||||||
|
uint? current = null,
|
||||||
|
uint? max = null,
|
||||||
|
bool? isPlayerName = null,
|
||||||
|
string? title = null)
|
||||||
|
{
|
||||||
|
bool isPlayer = (isPlayerName.HasValue && isPlayerName.Value == true) ||
|
||||||
|
(actor != null && actor.ObjectKind == ObjectKind.Player);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// grouping
|
||||||
|
List<string> groups = ParseGroups(text);
|
||||||
|
string result = "";
|
||||||
|
|
||||||
|
foreach (string group in groups)
|
||||||
|
{
|
||||||
|
// tags
|
||||||
|
string groupText = ParseGroup(group, isPlayer);
|
||||||
|
|
||||||
|
MatchCollection matches = Regex.Matches(groupText, @"\[(.*?)\]");
|
||||||
|
string formattedGroupText = matches.Aggregate(groupText, (c, m) =>
|
||||||
|
{
|
||||||
|
string formattedText = ReplaceTagWithString(m.Value, actor, name, current, max, isPlayerName, title);
|
||||||
|
return c.Replace(m.Value, formattedText);
|
||||||
|
});
|
||||||
|
|
||||||
|
result += formattedGroupText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error(e.Message);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> ParseGroups(string text)
|
||||||
|
{
|
||||||
|
MatchCollection matches = Regex.Matches(text, @"\{(.*?)\}");
|
||||||
|
if (matches.Count == 0)
|
||||||
|
{
|
||||||
|
return new List<string>() { text };
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> result = new List<string>();
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
foreach (Match match in matches)
|
||||||
|
{
|
||||||
|
if (index < match.Index)
|
||||||
|
{
|
||||||
|
result.Add(text.Substring(0, match.Index - index));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(text.Substring(match.Index, match.Length));
|
||||||
|
index = match.Index + match.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < text.Length)
|
||||||
|
{
|
||||||
|
result.Add(text.Substring(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ParseGroup(string text, bool isPlayer)
|
||||||
|
{
|
||||||
|
if (!text.Contains("="))
|
||||||
|
{
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPlayer)
|
||||||
|
{
|
||||||
|
if (text.StartsWith("{player="))
|
||||||
|
{
|
||||||
|
text = text.Substring(8);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (text.StartsWith("{npc="))
|
||||||
|
{
|
||||||
|
text = text.Substring(5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int groupEndIndex = text.IndexOf("}");
|
||||||
|
if (groupEndIndex > 0)
|
||||||
|
{
|
||||||
|
text = text.Remove(groupEndIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ParseLength(ref string tag, ref int length)
|
||||||
|
{
|
||||||
|
int index = tag.IndexOf(".");
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
string lengthString = tag.Substring(index + 1);
|
||||||
|
lengthString = lengthString.Substring(0, lengthString.Length - 1);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
length = int.Parse(lengthString);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
tag = tag.Substring(0, tag.Length - lengthString.Length - 2) + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ValidateName(IGameObject? actor, string? name)
|
||||||
|
{
|
||||||
|
string? n = actor?.Name.ToString() ?? name;
|
||||||
|
|
||||||
|
// Detour for PetRenamer
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string? customPetName = PetRenamerHelper.Instance.GetPetName(actor);
|
||||||
|
n = customPetName ?? n;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
return (n == null || n == "") ? "" : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ValidatePlayerName(IGameObject? actor, string? name, bool? isPlayerName = null)
|
||||||
|
{
|
||||||
|
if (isPlayerName.HasValue && isPlayerName.Value == false)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
else if (!isPlayerName.HasValue && actor?.ObjectKind != ObjectKind.Player)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidateName(actor, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ValidateNPCName(IGameObject? actor, string? name, bool? isPlayerName = null)
|
||||||
|
{
|
||||||
|
if (isPlayerName.HasValue && isPlayerName.Value == true)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
else if (!isPlayerName.HasValue && actor?.ObjectKind == ObjectKind.Player)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidateName(actor, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ConsistentDigitPercentage(float currentVal, float maxVal)
|
||||||
|
{
|
||||||
|
var rawPercentage = 100f * currentVal / Math.Max(1f, maxVal);
|
||||||
|
return rawPercentage >= 100 || rawPercentage <= 0 ? rawPercentage.ToString("N0") : rawPercentage.ToString("N1");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetZoneAdjustedLevel(IBattleChara? npc, int fallbackLevel)
|
||||||
|
{
|
||||||
|
if (npc == null)
|
||||||
|
{
|
||||||
|
return fallbackLevel > 0 ? fallbackLevel.ToString() : "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
StructsBattleChara* battleChara = (StructsBattleChara*)npc.Address;
|
||||||
|
ForayInfo forayInfo = battleChara->ForayInfo;
|
||||||
|
|
||||||
|
if (forayInfo.Level > 0)
|
||||||
|
{
|
||||||
|
return forayInfo.Level.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error in getting ZoneAdjustedLevel");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackLevel > 0 ? fallbackLevel.ToString() : "-";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Interface.Textures;
|
||||||
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
using Lumina.Excel;
|
||||||
|
using static Dalamud.Plugin.Services.ITextureProvider;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public class TexturesHelper
|
||||||
|
{
|
||||||
|
public static IDalamudTextureWrap? GetTexture<T>(uint rowId, uint stackCount = 0, bool hdIcon = true) where T : struct, IExcelRow<T>
|
||||||
|
{
|
||||||
|
ExcelSheet<T> sheet = Plugin.DataManager.GetExcelSheet<T>();
|
||||||
|
return sheet == null ? null : GetTexture<T>(sheet.GetRow(rowId), stackCount, hdIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IDalamudTextureWrap? GetTexture<T>(dynamic row, uint stackCount = 0, bool hdIcon = true) where T : struct, IExcelRow<T>
|
||||||
|
{
|
||||||
|
dynamic iconId = row.Icon;
|
||||||
|
return GetTextureFromIconId(iconId, stackCount, hdIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IDalamudTextureWrap? GetTextureFromIconId(uint iconId, uint stackCount = 0, bool hdIcon = true)
|
||||||
|
{
|
||||||
|
GameIconLookup lookup = new GameIconLookup(iconId + stackCount, false, hdIcon);
|
||||||
|
return Plugin.TextureProvider.GetFromGameIcon(lookup).GetWrapOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IDalamudTextureWrap? GetTextureFromPath(string path)
|
||||||
|
{
|
||||||
|
return Plugin.TextureProvider.GetFromGame(path).GetWrapOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public class TooltipsHelper : IDisposable
|
||||||
|
{
|
||||||
|
#region Singleton
|
||||||
|
private TooltipsHelper()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize() { Instance = new TooltipsHelper(); }
|
||||||
|
|
||||||
|
public static TooltipsHelper Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
~TooltipsHelper()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private float MaxWidth => 340 * ImGuiHelpers.GlobalScale;
|
||||||
|
private float Margin => 5 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
private TooltipsConfig _config => ConfigurationManager.Instance.GetConfigObject<TooltipsConfig>();
|
||||||
|
|
||||||
|
private string? _currentTooltipText = null;
|
||||||
|
private Vector2 _textSize;
|
||||||
|
private string? _currentTooltipTitle = null;
|
||||||
|
private Vector2 _titleSize;
|
||||||
|
private string? _previousRawText = null;
|
||||||
|
|
||||||
|
private Vector2 _position;
|
||||||
|
private Vector2 _size;
|
||||||
|
|
||||||
|
private bool _dataIsValid = false;
|
||||||
|
|
||||||
|
public void ShowTooltipOnCursor(string text, string? title = null, uint id = 0, string name = "")
|
||||||
|
{
|
||||||
|
ShowTooltip(text, ImGui.GetMousePos(), title, id, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowTooltip(string text, Vector2 position, string? title = null, uint id = 0, string name = "")
|
||||||
|
{
|
||||||
|
if (text == null)
|
||||||
|
{
|
||||||
|
if (_config.DebugTooltips)
|
||||||
|
Plugin.Logger.Information("[HSUI Tooltip DBG] ShowTooltip skipped: text is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove styling tags from text
|
||||||
|
if (_previousRawText != text)
|
||||||
|
{
|
||||||
|
_currentTooltipText = text;
|
||||||
|
_previousRawText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calcualte title size
|
||||||
|
_titleSize = Vector2.Zero;
|
||||||
|
if (title != null)
|
||||||
|
{
|
||||||
|
_currentTooltipTitle = title;
|
||||||
|
|
||||||
|
if (_config.ShowSourceName && name.Length > 0)
|
||||||
|
{
|
||||||
|
_currentTooltipTitle += $" ({name})";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_config.ShowStatusIDs)
|
||||||
|
{
|
||||||
|
_currentTooltipTitle += " (ID: " + id + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FontsManager.Instance.PushFont(FontsConfig.DefaultSmallFontKey))
|
||||||
|
{
|
||||||
|
_titleSize = ImGui.CalcTextSize(_currentTooltipTitle, false, MaxWidth);
|
||||||
|
_titleSize.Y += Margin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate text size
|
||||||
|
using (FontsManager.Instance.PushFont(FontsConfig.DefaultSmallFontKey))
|
||||||
|
{
|
||||||
|
_textSize = ImGui.CalcTextSize(_currentTooltipText, false, MaxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
_size = new Vector2(Math.Max(_titleSize.X, _textSize.X) + Margin * 2, _titleSize.Y + _textSize.Y + Margin * 2);
|
||||||
|
|
||||||
|
// position tooltip using the given coordinates as bottom center
|
||||||
|
position.X = position.X - _size.X / 2f;
|
||||||
|
position.Y = position.Y - _size.Y;
|
||||||
|
|
||||||
|
// correct tooltips off screen
|
||||||
|
_position = ConstrainPosition(position, _size);
|
||||||
|
|
||||||
|
_dataIsValid = true;
|
||||||
|
if (_config.DebugTooltips)
|
||||||
|
Plugin.Logger.Information($"[HSUI Tooltip DBG] ShowTooltip: title='{title}' textLen={text?.Length ?? 0} textPreview='{(text != null && text.Length > 80 ? text[..80] + "..." : text ?? "")}' pos=({_position.X:F0},{_position.Y:F0})");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveTooltip()
|
||||||
|
{
|
||||||
|
_dataIsValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
if (!_dataIsValid)
|
||||||
|
return;
|
||||||
|
if (ConfigurationManager.Instance.ShowingModalWindow)
|
||||||
|
{
|
||||||
|
if (_config.DebugTooltips)
|
||||||
|
Plugin.Logger.Information("[HSUI Tooltip DBG] Draw SKIPPED: ShowingModalWindow=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bg
|
||||||
|
ImGuiWindowFlags windowFlags =
|
||||||
|
ImGuiWindowFlags.NoTitleBar
|
||||||
|
| ImGuiWindowFlags.NoMove
|
||||||
|
| ImGuiWindowFlags.NoDecoration
|
||||||
|
| ImGuiWindowFlags.NoBackground
|
||||||
|
| ImGuiWindowFlags.NoInputs
|
||||||
|
| ImGuiWindowFlags.NoSavedSettings
|
||||||
|
| ImGuiWindowFlags.NoFocusOnAppearing;
|
||||||
|
|
||||||
|
// imgui clips the left and right borders inside windows for some reason
|
||||||
|
// we make the window bigger so the actual drawable size is the expected one
|
||||||
|
var windowMargin = new Vector2(4, 0);
|
||||||
|
var windowPos = _position - windowMargin;
|
||||||
|
|
||||||
|
ImGui.SetNextWindowPos(windowPos, ImGuiCond.Always);
|
||||||
|
ImGui.SetNextWindowSize(_size + windowMargin * 2);
|
||||||
|
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0);
|
||||||
|
ImGui.Begin("DelvUI_tooltip", windowFlags);
|
||||||
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
|
|
||||||
|
drawList.AddRectFilled(_position, _position + _size, _config.BackgroundColor.Base);
|
||||||
|
|
||||||
|
if (_config.BorderConfig.Enabled)
|
||||||
|
{
|
||||||
|
drawList.AddRect(_position, _position + _size, _config.BorderConfig.Color.Base, 0, ImDrawFlags.None, _config.BorderConfig.Thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no idea why i have to do this
|
||||||
|
float globalScaleCorrection = -15 + 15 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
if (_currentTooltipTitle != null)
|
||||||
|
{
|
||||||
|
// title
|
||||||
|
Vector2 cursorPos;
|
||||||
|
using (FontsManager.Instance.PushFont(FontsConfig.DefaultSmallFontKey))
|
||||||
|
{
|
||||||
|
cursorPos = new Vector2(windowMargin.X + _size.X / 2f - _titleSize.X / 2f, Margin);
|
||||||
|
ImGui.SetCursorPos(cursorPos);
|
||||||
|
ImGui.PushTextWrapPos(cursorPos.X + _titleSize.X + globalScaleCorrection + Margin);
|
||||||
|
ImGui.TextColored(_config.TitleColor.Vector, _currentTooltipTitle);
|
||||||
|
ImGui.PopTextWrapPos();
|
||||||
|
}
|
||||||
|
|
||||||
|
// text
|
||||||
|
using (FontsManager.Instance.PushFont(FontsConfig.DefaultSmallFontKey))
|
||||||
|
{
|
||||||
|
cursorPos = new Vector2(windowMargin.X + _size.X / 2f - _textSize.X / 2f, Margin + _titleSize.Y);
|
||||||
|
ImGui.SetCursorPos(cursorPos);
|
||||||
|
ImGui.PushTextWrapPos(cursorPos.X + _textSize.X + globalScaleCorrection + Margin);
|
||||||
|
ImGui.TextColored(_config.TextColor.Vector, _currentTooltipText);
|
||||||
|
ImGui.PopTextWrapPos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// text
|
||||||
|
using (FontsManager.Instance.PushFont(FontsConfig.DefaultSmallFontKey))
|
||||||
|
{
|
||||||
|
var cursorPos = windowMargin + new Vector2(Margin, Margin);
|
||||||
|
var textWidth = _size.X - Margin * 2;
|
||||||
|
|
||||||
|
ImGui.SetCursorPos(cursorPos);
|
||||||
|
ImGui.PushTextWrapPos(cursorPos.X + textWidth + globalScaleCorrection + Margin);
|
||||||
|
ImGui.TextColored(_config.TextColor.Vector, _currentTooltipText);
|
||||||
|
ImGui.PopTextWrapPos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
ImGui.PopStyleVar();
|
||||||
|
|
||||||
|
if (_config.DebugTooltips)
|
||||||
|
Plugin.Logger.Information($"[HSUI Tooltip DBG] Draw rendered tooltip at ({_position.X:F0},{_position.Y:F0})");
|
||||||
|
RemoveTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 ConstrainPosition(Vector2 position, Vector2 size)
|
||||||
|
{
|
||||||
|
var screenSize = ImGui.GetWindowViewport().Size;
|
||||||
|
|
||||||
|
if (position.X < 0)
|
||||||
|
{
|
||||||
|
position.X = Margin;
|
||||||
|
}
|
||||||
|
else if (position.X + size.X > screenSize.X)
|
||||||
|
{
|
||||||
|
position.X = screenSize.X - size.X - Margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position.Y < 0)
|
||||||
|
{
|
||||||
|
position.Y = Margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Section("Misc")]
|
||||||
|
[SubSection("Tooltips", 0)]
|
||||||
|
public class TooltipsConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static TooltipsConfig DefaultConfig() { return new TooltipsConfig(); }
|
||||||
|
|
||||||
|
[Checkbox("Debug Tooltips")]
|
||||||
|
[Order(3)]
|
||||||
|
public bool DebugTooltips = false;
|
||||||
|
|
||||||
|
[Checkbox("Show Status Effects IDs")]
|
||||||
|
[Order(5)]
|
||||||
|
public bool ShowStatusIDs = false;
|
||||||
|
|
||||||
|
[Checkbox("Show Source Name")]
|
||||||
|
[Order(10)]
|
||||||
|
public bool ShowSourceName = false;
|
||||||
|
|
||||||
|
[ColorEdit4("Background Color")]
|
||||||
|
[Order(15)]
|
||||||
|
public PluginConfigColor BackgroundColor = new PluginConfigColor(new(19f / 255f, 19f / 255f, 19f / 255f, 190f / 250f));
|
||||||
|
|
||||||
|
[ColorEdit4("Title Color")]
|
||||||
|
[Order(20)]
|
||||||
|
public PluginConfigColor TitleColor = new PluginConfigColor(new(255f / 255f, 210f / 255f, 31f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Text Color")]
|
||||||
|
[Order(35)]
|
||||||
|
public PluginConfigColor TextColor = new PluginConfigColor(new(255f / 255f, 255f / 255f, 255f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[NestedConfig("Border", 40, separator = false, spacing = true, collapsingHeader = false)]
|
||||||
|
public TooltipBorderConfig BorderConfig = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class TooltipBorderConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[ColorEdit4("Color")]
|
||||||
|
[Order(5)]
|
||||||
|
public PluginConfigColor Color = new(new Vector4(10f / 255f, 10f / 255f, 10f / 255f, 160f / 255f));
|
||||||
|
|
||||||
|
[DragInt("Thickness", min = 1, max = 100)]
|
||||||
|
[Order(10)]
|
||||||
|
public int Thickness = 4;
|
||||||
|
|
||||||
|
public TooltipBorderConfig()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TooltipBorderConfig(PluginConfigColor color, int thickness)
|
||||||
|
{
|
||||||
|
Color = color;
|
||||||
|
Thickness = thickness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,429 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Game.ClientState.Statuses;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using StructsCharacter = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
|
||||||
|
using StructsCharacterManager = FFXIVClientStructs.FFXIV.Client.Game.Character.CharacterManager;
|
||||||
|
using StructsGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
internal static class Utils
|
||||||
|
{
|
||||||
|
private static uint InvalidGameObjectId = 0xE0000000;
|
||||||
|
|
||||||
|
public static IGameObject? GetBattleChocobo(IGameObject? player)
|
||||||
|
{
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetBuddy(player.GameObjectId, BattleNpcSubKind.Chocobo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IGameObject? GetBuddy(ulong ownerId, BattleNpcSubKind kind)
|
||||||
|
{
|
||||||
|
// only the first 200 elements in the array are relevant due to the order in which SE packs data into the array
|
||||||
|
// we do a step of 2 because its always an actor followed by its companion
|
||||||
|
for (var i = 0; i < 200; i += 2)
|
||||||
|
{
|
||||||
|
var gameObject = Plugin.ObjectTable[i];
|
||||||
|
|
||||||
|
if (gameObject == null || gameObject.GameObjectId == InvalidGameObjectId || gameObject is not IBattleNpc battleNpc)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (battleNpc.BattleNpcKind == kind && battleNpc.OwnerId == ownerId)
|
||||||
|
{
|
||||||
|
return gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IGameObject? GetGameObjectByName(string name)
|
||||||
|
{
|
||||||
|
// only the first 200 elements in the array are relevant due to the order in which SE packs data into the array
|
||||||
|
// we do a step of 2 because its always an actor followed by its companion
|
||||||
|
for (int i = 0; i < 200; i += 2)
|
||||||
|
{
|
||||||
|
IGameObject? gameObject = Plugin.ObjectTable[i];
|
||||||
|
|
||||||
|
if (gameObject == null || gameObject.GameObjectId == InvalidGameObjectId || gameObject.GameObjectId == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameObject.Name.ToString() == name)
|
||||||
|
{
|
||||||
|
return gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe bool IsHostile(IGameObject obj)
|
||||||
|
{
|
||||||
|
StructsGameObject* gameObject = (StructsGameObject*)obj.Address;
|
||||||
|
byte plateType = gameObject->GetNamePlateColorType();
|
||||||
|
|
||||||
|
// 4, 5, 6: Enemy players in PvP
|
||||||
|
// 7: yellow, can be attacked, not engaged
|
||||||
|
// 8: dead
|
||||||
|
// 9: red, engaged with your party
|
||||||
|
// 10: purple, engaged with other party
|
||||||
|
// 11: orange, aggro'd to your party but not attacked yet
|
||||||
|
return plateType >= 4 && plateType <= 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe float ActorShieldValue(IGameObject? actor)
|
||||||
|
{
|
||||||
|
if (actor == null || actor is not ICharacter)
|
||||||
|
{
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
StructsCharacter* chara = (StructsCharacter*)actor.Address;
|
||||||
|
return Math.Min(chara->CharacterData.ShieldValue, 100f) / 100f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsActorCasting(IGameObject? actor)
|
||||||
|
{
|
||||||
|
if (actor is not IBattleChara chara)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return chara.IsCasting;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IStatus> StatusListForActor(IGameObject? obj)
|
||||||
|
{
|
||||||
|
if (obj is IBattleChara chara)
|
||||||
|
{
|
||||||
|
return StatusListForBattleChara(chara);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new List<IStatus>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IStatus> StatusListForBattleChara(IBattleChara? chara)
|
||||||
|
{
|
||||||
|
List<IStatus> statusList = new List<IStatus>();
|
||||||
|
if (chara == null)
|
||||||
|
{
|
||||||
|
return statusList;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
statusList = chara.StatusList.ToList();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
return statusList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string DurationToString(double duration, int decimalCount = 0)
|
||||||
|
{
|
||||||
|
if (duration == 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSpan t = TimeSpan.FromSeconds(duration);
|
||||||
|
|
||||||
|
if (t.Hours >= 1) { return t.Hours + "h"; }
|
||||||
|
if (t.Minutes >= 5) { return t.Minutes + "m"; }
|
||||||
|
if (t.Minutes >= 1) { return $"{t.Minutes}:{t.Seconds:00}"; }
|
||||||
|
|
||||||
|
return duration.ToString("N" + decimalCount, ConfigurationManager.Instance.ActiveCultreInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IStatus? GetTankInvulnerabilityID(IBattleChara actor)
|
||||||
|
{
|
||||||
|
return StatusListForBattleChara(actor).FirstOrDefault(o => o.StatusId is 810 or 811 or 3255 or 1302 or 409 or 1836 or 82);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsOnCleanseJob()
|
||||||
|
{
|
||||||
|
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
|
||||||
|
|
||||||
|
return player != null && JobsHelper.IsJobWithCleanse(player.ClassJob.RowId, player.Level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IGameObject? FindTargetOfTarget(IGameObject? target, IGameObject? player, IObjectTable actors)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dalamud for now has an issue where it is only able to get the target ID of
|
||||||
|
// NON-Networked objects through anything but GetTargetId on ClientStruct Gameobjects.
|
||||||
|
// The bypass converts all Dalamud GameObject Data to ClientStructs GameObject Data and handles it accordingly.
|
||||||
|
int actualTargetId = GetActualTargetId(target);
|
||||||
|
// The Object ID that gets returned from minions is in reality the index
|
||||||
|
// Checking for the correct object ID wouldn't work anyways as you would yet again run into the ObjectID = 0xE0000000 issue
|
||||||
|
if (actualTargetId >= 0 && actualTargetId < actors.Length)
|
||||||
|
{
|
||||||
|
return actors[actualTargetId];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.TargetObjectId == 0 && player != null && player.TargetObjectId == 0)
|
||||||
|
{
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only the first 200 elements in the array are relevant due to the order in which SE packs data into the array
|
||||||
|
// we do a step of 2 because its always an actor followed by its companion
|
||||||
|
for (int i = 0; i < 200; i += 2)
|
||||||
|
{
|
||||||
|
IGameObject? actor = actors[i];
|
||||||
|
if (actor?.GameObjectId == target.TargetObjectId)
|
||||||
|
{
|
||||||
|
return actor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the actual target ID of your targets target.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">Your target</param>
|
||||||
|
/// <returns>Target ID of your targets targer. Returns -1 if old code should be ran.</returns>
|
||||||
|
private static unsafe int GetActualTargetId(IGameObject target)
|
||||||
|
{
|
||||||
|
// We only need to check for companions.
|
||||||
|
// Why not check target.TargetObject?.ObjectKind == ObjectKind.Companion?
|
||||||
|
// Due to the Non-Networked game object bug the game is unaware of what type the object should actually be
|
||||||
|
if (target.TargetObject?.ObjectKind != ObjectKind.Player)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we get the ClientStruct Character of our target (aka the player we are targeting)
|
||||||
|
StructsCharacter targetChara = StructsCharacterManager.Instance()->LookupBattleCharaByEntityId(target.EntityId)->Character;
|
||||||
|
|
||||||
|
// This method is key. GetTargetId() returns the targets player target ID. If it is converted to a hex string and starts with the number 4, it is a minion.
|
||||||
|
// Even though it is a minion, it still returns the players target ID.
|
||||||
|
ulong realTargetID = targetChara.GetTargetId();
|
||||||
|
if (!realTargetID.ToString("X").StartsWith("4"))
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We look up the parents ClientStruct GameObject
|
||||||
|
StructsCharacter* realBattleChara = (StructsCharacter*)StructsCharacterManager.Instance()->LookupBattleCharaByEntityId((uint)realTargetID);
|
||||||
|
if (realBattleChara == null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And get the companion off of that
|
||||||
|
StructsGameObject* companionGameObject = (StructsGameObject*)realBattleChara->CompanionData.CompanionObject;
|
||||||
|
if (companionGameObject == null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We return the index of the object here. Why?
|
||||||
|
// Again due to the bug where ObjectID = 0xE0000000
|
||||||
|
// The index does work and returns the exact minion index.
|
||||||
|
return companionGameObject->ObjectIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2 GetAnchoredPosition(Vector2 position, Vector2 size, DrawAnchor anchor)
|
||||||
|
{
|
||||||
|
return anchor switch
|
||||||
|
{
|
||||||
|
DrawAnchor.Center => position - size / 2f,
|
||||||
|
DrawAnchor.Left => position + new Vector2(0, -size.Y / 2f),
|
||||||
|
DrawAnchor.Right => position + new Vector2(-size.X, -size.Y / 2f),
|
||||||
|
DrawAnchor.Top => position + new Vector2(-size.X / 2f, 0),
|
||||||
|
DrawAnchor.TopLeft => position,
|
||||||
|
DrawAnchor.TopRight => position + new Vector2(-size.X, 0),
|
||||||
|
DrawAnchor.Bottom => position + new Vector2(-size.X / 2f, -size.Y),
|
||||||
|
DrawAnchor.BottomLeft => position + new Vector2(0, -size.Y),
|
||||||
|
DrawAnchor.BottomRight => position + new Vector2(-size.X, -size.Y),
|
||||||
|
_ => position
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string UserFriendlyConfigName(string configTypeName) => UserFriendlyString(configTypeName, "Config");
|
||||||
|
|
||||||
|
public static string UserFriendlyString(string str, string? remove)
|
||||||
|
{
|
||||||
|
string? s = remove != null ? str.Replace(remove, "") : str;
|
||||||
|
|
||||||
|
Regex? regex = new(@"
|
||||||
|
(?<=[A-Z])(?=[A-Z][a-z]) |
|
||||||
|
(?<=[^A-Z])(?=[A-Z]) |
|
||||||
|
(?<=[A-Za-z])(?=[^A-Za-z])",
|
||||||
|
RegexOptions.IgnorePatternWhitespace);
|
||||||
|
|
||||||
|
return regex.Replace(s, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OpenUrl(string url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(url);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// hack because of this: https://github.com/dotnet/corefx/issues/10361
|
||||||
|
if (RuntimeInformation.IsOSPlatform(osPlatform: OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
url = url.Replace("&", "^&");
|
||||||
|
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
Process.Start("xdg-open", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Plugin.Logger.Error("Error trying to open url: " + e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe bool? IsTargetCasting()
|
||||||
|
{
|
||||||
|
AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_TargetInfo", 1).Address;
|
||||||
|
if (addon != null && addon->IsVisible)
|
||||||
|
{
|
||||||
|
AtkImageNode* imageNode = addon->GetImageNodeById(15);
|
||||||
|
return imageNode == null || imageNode->IsVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_TargetInfoCastBar", 1).Address;
|
||||||
|
if (addon != null && addon->IsVisible)
|
||||||
|
{
|
||||||
|
AtkImageNode* imageNode = addon->GetImageNodeById(7);
|
||||||
|
return imageNode != null || imageNode->IsVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe bool? IsFocusTargetCasting()
|
||||||
|
{
|
||||||
|
AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_FocusTargetInfo", 1).Address;
|
||||||
|
if (addon != null && addon->IsVisible)
|
||||||
|
{
|
||||||
|
AtkTextNode* textNode = addon->GetTextNodeById(5);
|
||||||
|
return textNode == null || textNode->IsVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe bool? IsEnemyInListCasting(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index > 7) { return null; }
|
||||||
|
|
||||||
|
AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_EnemyList", 1).Address;
|
||||||
|
if (addon == null || !addon->IsVisible) { return null; }
|
||||||
|
|
||||||
|
uint buttonId = (index == 0) ? 2u : (uint)(20000 + index);
|
||||||
|
AtkComponentButton* button = addon->GetComponentButtonById(buttonId);
|
||||||
|
if (button == null || button->AtkResNode == null || !button->AtkResNode->IsVisible())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtkImageNode* imageNode = button->GetImageNodeById(8);
|
||||||
|
return imageNode == null || imageNode->IsVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe uint? SignIconIDForActor(IGameObject? actor)
|
||||||
|
{
|
||||||
|
if (actor == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SignIconIDForObjectID(actor.GameObjectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe uint? SignIconIDForObjectID(ulong objectId)
|
||||||
|
{
|
||||||
|
MarkingController* markingController = MarkingController.Instance();
|
||||||
|
if (objectId == 0 || objectId == InvalidGameObjectId || markingController == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 17; i++)
|
||||||
|
{
|
||||||
|
if (objectId == markingController->Markers[i])
|
||||||
|
{
|
||||||
|
// attack1-5
|
||||||
|
if (i <= 4)
|
||||||
|
{
|
||||||
|
return (uint)(61201 + i);
|
||||||
|
}
|
||||||
|
// attack6-8
|
||||||
|
else if (i >= 14)
|
||||||
|
{
|
||||||
|
return (uint)(61201 + i - 9);
|
||||||
|
}
|
||||||
|
// shapes
|
||||||
|
else if (i >= 10)
|
||||||
|
{
|
||||||
|
return (uint)(61231 + i - 10);
|
||||||
|
}
|
||||||
|
// ignore1-2
|
||||||
|
else if (i >= 8)
|
||||||
|
{
|
||||||
|
return (uint)(61221 + i - 8);
|
||||||
|
}
|
||||||
|
// bind1-3
|
||||||
|
else if (i >= 5)
|
||||||
|
{
|
||||||
|
return (uint)(61211 + i - 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsHealthLabel(LabelConfig config)
|
||||||
|
{
|
||||||
|
return config.GetText().Contains("[health");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
using Dalamud.Game.ClientState.Party;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
using Dalamud.Plugin.Ipc;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Tree;
|
||||||
|
using HSUI.Interface.Party;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.IO;
|
||||||
|
using static System.Collections.Specialized.BitVector32;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
public enum WhosTalkingState : int
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Speaking = 1,
|
||||||
|
Muted = 2,
|
||||||
|
Deafened = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WhosTalkingHelper
|
||||||
|
{
|
||||||
|
private readonly ICallGateSubscriber<string, int> _getUserState;
|
||||||
|
private Dictionary<string, WhosTalkingState> _cachedStates = new Dictionary<string, WhosTalkingState>();
|
||||||
|
|
||||||
|
private string speakingPath = "";
|
||||||
|
private string mutedPath = "";
|
||||||
|
private string deafenedPath = "";
|
||||||
|
|
||||||
|
#region Singleton
|
||||||
|
private WhosTalkingHelper()
|
||||||
|
{
|
||||||
|
_getUserState = Plugin.PluginInterface.GetIpcSubscriber<string, int>("WT.GetUserState");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string imagesPath = Path.Combine(Plugin.AssemblyLocation, "Media", "Images");
|
||||||
|
|
||||||
|
// speaking
|
||||||
|
speakingPath = Path.Combine(imagesPath, "speaking.png");
|
||||||
|
|
||||||
|
// muted
|
||||||
|
mutedPath = Path.Combine(imagesPath, "muted.png");
|
||||||
|
|
||||||
|
// deafened
|
||||||
|
deafenedPath = Path.Combine(imagesPath, "deafened.png");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize() { Instance = new WhosTalkingHelper(); }
|
||||||
|
|
||||||
|
public static WhosTalkingHelper Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
~WhosTalkingHelper()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
_cachedStates.Clear();
|
||||||
|
|
||||||
|
foreach (IPartyFramesMember member in PartyManager.Instance.GroupMembers)
|
||||||
|
{
|
||||||
|
if (member.Name.Length <= 0) { continue; }
|
||||||
|
|
||||||
|
WhosTalkingState state = WhosTalkingState.None;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
state = (WhosTalkingState)_getUserState.InvokeFunc(member.Name);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
if (!_cachedStates.ContainsKey(member.Name))
|
||||||
|
{
|
||||||
|
_cachedStates.Add(member.Name, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhosTalkingState GetUserState(string name)
|
||||||
|
{
|
||||||
|
if (_cachedStates.TryGetValue(name, out WhosTalkingState state))
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return WhosTalkingState.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDalamudTextureWrap? GetTextureForState(WhosTalkingState state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case WhosTalkingState.Speaking: return Plugin.TextureProvider.GetFromFile(speakingPath).GetWrapOrDefault();
|
||||||
|
case WhosTalkingState.Muted: return Plugin.TextureProvider.GetFromFile(mutedPath).GetWrapOrDefault();
|
||||||
|
case WhosTalkingState.Deafened: return Plugin.TextureProvider.GetFromFile(deafenedPath).GetWrapOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
using Dalamud.Plugin.Ipc;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Tree;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace HSUI.Helpers
|
||||||
|
{
|
||||||
|
internal class WotsitHelper
|
||||||
|
{
|
||||||
|
private readonly ICallGateSubscriber<string, string, string, uint, string> _registerWithSearch;
|
||||||
|
private readonly ICallGateSubscriber<string, bool> _invoke;
|
||||||
|
private readonly ICallGateSubscriber<string, bool> _unregisterAll;
|
||||||
|
|
||||||
|
private Dictionary<string, (SectionNode, SubSectionNode?, NestedSubSectionNode?)> _map = new Dictionary<string, (SectionNode, SubSectionNode?, NestedSubSectionNode?)>();
|
||||||
|
|
||||||
|
#region Singleton
|
||||||
|
private WotsitHelper()
|
||||||
|
{
|
||||||
|
_registerWithSearch = Plugin.PluginInterface.GetIpcSubscriber<string, string, string, uint, string>("FA.RegisterWithSearch");
|
||||||
|
_unregisterAll = Plugin.PluginInterface.GetIpcSubscriber<string, bool>("FA.UnregisterAll");
|
||||||
|
|
||||||
|
_invoke = Plugin.PluginInterface.GetIpcSubscriber<string, bool>("FA.Invoke");
|
||||||
|
_invoke.Subscribe(Invoke);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize() { Instance = new WotsitHelper(); }
|
||||||
|
|
||||||
|
public static WotsitHelper Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
~WotsitHelper()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
|
||||||
|
UnregisterAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
_map.Clear();
|
||||||
|
if (!UnregisterAll())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sections
|
||||||
|
foreach (Node node in ConfigurationManager.Instance.ConfigBaseNode.Sections)
|
||||||
|
{
|
||||||
|
if (node is not SectionNode section) { continue; }
|
||||||
|
|
||||||
|
string guid = _registerWithSearch.InvokeFunc(
|
||||||
|
Plugin.PluginInterface.InternalName,
|
||||||
|
"HSUI Settings: " + section.Name,
|
||||||
|
"HSUI " + section.Name,
|
||||||
|
66472
|
||||||
|
);
|
||||||
|
|
||||||
|
_map.Add(guid, (section, null, null));
|
||||||
|
|
||||||
|
// sub sections
|
||||||
|
foreach (SubSectionNode subSection in section.Children)
|
||||||
|
{
|
||||||
|
guid = _registerWithSearch.InvokeFunc(
|
||||||
|
Plugin.PluginInterface.InternalName,
|
||||||
|
"HSUI Settings: " + section.Name + " > " + subSection.Name,
|
||||||
|
"HSUI " + subSection.Name,
|
||||||
|
66472
|
||||||
|
);
|
||||||
|
|
||||||
|
_map.Add(guid, (section, subSection, null));
|
||||||
|
|
||||||
|
// nested sub sections
|
||||||
|
foreach (SubSectionNode nestedSubSection in subSection.Children)
|
||||||
|
{
|
||||||
|
if (nestedSubSection is not NestedSubSectionNode nestedNode) { continue; }
|
||||||
|
|
||||||
|
guid = _registerWithSearch.InvokeFunc(
|
||||||
|
Plugin.PluginInterface.InternalName,
|
||||||
|
"HSUI Settings: " + section.Name + " > " + subSection.Name + " > " + nestedNode.Name,
|
||||||
|
"HSUI " + nestedNode.Name,
|
||||||
|
66472
|
||||||
|
);
|
||||||
|
|
||||||
|
_map.Add(guid, (section, subSection, nestedNode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invoke(string guid)
|
||||||
|
{
|
||||||
|
//_map.TryGetValue()
|
||||||
|
if (_map.TryGetValue(guid, out var value) && value.Item1 != null)
|
||||||
|
{
|
||||||
|
SectionNode section = value.Item1;
|
||||||
|
ConfigurationManager.Instance.ConfigBaseNode.SelectedOptionName = section.Name;
|
||||||
|
ConfigurationManager.Instance.ConfigBaseNode.RefreshSelectedNode();
|
||||||
|
|
||||||
|
SubSectionNode? subSectionNode = value.Item2;
|
||||||
|
if (subSectionNode != null)
|
||||||
|
{
|
||||||
|
section.ForceSelectedTabName = subSectionNode.Name;
|
||||||
|
|
||||||
|
NestedSubSectionNode? nestedSubSectionNode = value.Item3;
|
||||||
|
if (nestedSubSectionNode != null)
|
||||||
|
{
|
||||||
|
subSectionNode.ForceSelectedTabName = nestedSubSectionNode.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurationManager.Instance.OpenConfigWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UnregisterAll()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_unregisterAll.InvokeFunc(Plugin.PluginInterface.InternalName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using System.Numerics;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using HSUI.Enums;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.Bars
|
||||||
|
{
|
||||||
|
[Exportable(false)]
|
||||||
|
public class BarConfig : AnchorablePluginConfigObject
|
||||||
|
{
|
||||||
|
[ColorEdit4("Background Color")]
|
||||||
|
[Order(16)]
|
||||||
|
public PluginConfigColor BackgroundColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 50f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Fill Color")]
|
||||||
|
[Order(25)]
|
||||||
|
public PluginConfigColor FillColor;
|
||||||
|
|
||||||
|
[Combo("Fill Direction", new string[] { "Left", "Right", "Up", "Down" })]
|
||||||
|
[Order(30)]
|
||||||
|
public BarDirection FillDirection;
|
||||||
|
|
||||||
|
[BarTexture("Bar Texture", spacing = true, help = "Default means the bar will be drawn using the global gradient configuration for bars found in Colors > Misc.")]
|
||||||
|
[Order(31)]
|
||||||
|
public string BarTextureName = "";
|
||||||
|
|
||||||
|
[BarTextureDrawMode("Draw Mode")]
|
||||||
|
[Order(32)]
|
||||||
|
public BarTextureDrawMode BarTextureDrawMode = BarTextureDrawMode.Stretch;
|
||||||
|
|
||||||
|
[Checkbox("Show Border", spacing = true)]
|
||||||
|
[Order(35)]
|
||||||
|
public bool DrawBorder = true;
|
||||||
|
|
||||||
|
[ColorEdit4("Border Color")]
|
||||||
|
[Order(36, collapseWith = nameof(DrawBorder))]
|
||||||
|
public PluginConfigColor BorderColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[DragInt("Border Thickness", min = 1, max = 10)]
|
||||||
|
[Order(37, collapseWith = nameof(DrawBorder))]
|
||||||
|
public int BorderThickness = 1;
|
||||||
|
|
||||||
|
[NestedConfig("Shadow", 40, spacing = true)]
|
||||||
|
public ShadowConfig ShadowConfig = new ShadowConfig() { Enabled = false };
|
||||||
|
|
||||||
|
[Checkbox("Hide When Inactive", spacing = true)]
|
||||||
|
[Order(41)]
|
||||||
|
public bool HideWhenInactive = false;
|
||||||
|
|
||||||
|
public BarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor, BarDirection fillDirection = BarDirection.Right)
|
||||||
|
{
|
||||||
|
Position = position;
|
||||||
|
Size = size;
|
||||||
|
FillColor = fillColor;
|
||||||
|
FillDirection = fillDirection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class BarGlowConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[ColorEdit4("Color")]
|
||||||
|
[Order(5)]
|
||||||
|
public PluginConfigColor Color = new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 50f / 100f));
|
||||||
|
|
||||||
|
[DragInt("Size", min = 1, max = 100)]
|
||||||
|
[Order(25)]
|
||||||
|
public int Size = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BarDirection
|
||||||
|
{
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Up,
|
||||||
|
Down
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.Bars
|
||||||
|
{
|
||||||
|
public class BarHud
|
||||||
|
{
|
||||||
|
private string ID { get; set; }
|
||||||
|
|
||||||
|
private Rect BackgroundRect { get; set; } = new Rect();
|
||||||
|
|
||||||
|
private List<Rect> ForegroundRects { get; set; } = new List<Rect>();
|
||||||
|
|
||||||
|
private List<LabelHud> LabelHuds { get; set; } = new List<LabelHud>();
|
||||||
|
|
||||||
|
private bool DrawBorder { get; set; }
|
||||||
|
|
||||||
|
private PluginConfigColor? BorderColor { get; set; }
|
||||||
|
|
||||||
|
private int BorderThickness { get; set; }
|
||||||
|
|
||||||
|
private DrawAnchor Anchor { get; set; }
|
||||||
|
|
||||||
|
private IGameObject? Actor { get; set; }
|
||||||
|
|
||||||
|
private PluginConfigColor? GlowColor { get; set; }
|
||||||
|
|
||||||
|
private int GlowSize { get; set; }
|
||||||
|
|
||||||
|
private float? Current;
|
||||||
|
private float? Max;
|
||||||
|
|
||||||
|
private ShadowConfig? ShadowConfig { get; set; }
|
||||||
|
|
||||||
|
private string? BarTextureName { get; set; }
|
||||||
|
private BarTextureDrawMode BarTextureDrawMode { get; set; }
|
||||||
|
|
||||||
|
public bool NeedsInputs = false;
|
||||||
|
|
||||||
|
public BarHud(
|
||||||
|
string id,
|
||||||
|
bool drawBorder = true,
|
||||||
|
PluginConfigColor? borderColor = null,
|
||||||
|
int borderThickness = 1,
|
||||||
|
DrawAnchor anchor = DrawAnchor.TopLeft,
|
||||||
|
IGameObject? actor = null,
|
||||||
|
PluginConfigColor? glowColor = null,
|
||||||
|
int? glowSize = 1,
|
||||||
|
float? current = null,
|
||||||
|
float? max = null,
|
||||||
|
ShadowConfig? shadowConfig = null,
|
||||||
|
string? barTextureName = null,
|
||||||
|
BarTextureDrawMode barTextureDrawMode = BarTextureDrawMode.Stretch)
|
||||||
|
{
|
||||||
|
ID = id;
|
||||||
|
DrawBorder = drawBorder;
|
||||||
|
BorderColor = borderColor;
|
||||||
|
BorderThickness = borderThickness;
|
||||||
|
Anchor = anchor;
|
||||||
|
Actor = actor;
|
||||||
|
GlowColor = glowColor;
|
||||||
|
GlowSize = glowSize ?? 1;
|
||||||
|
Current = current;
|
||||||
|
Max = max;
|
||||||
|
ShadowConfig = shadowConfig;
|
||||||
|
BarTextureName = barTextureName;
|
||||||
|
BarTextureDrawMode = barTextureDrawMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BarHud(BarConfig config, IGameObject? actor = null, BarGlowConfig? glowConfig = null, float? current = null, float? max = null)
|
||||||
|
: this(config.ID,
|
||||||
|
config.DrawBorder,
|
||||||
|
config.BorderColor,
|
||||||
|
config.BorderThickness,
|
||||||
|
config.Anchor,
|
||||||
|
actor,
|
||||||
|
glowConfig?.Color,
|
||||||
|
glowConfig?.Size,
|
||||||
|
current,
|
||||||
|
max,
|
||||||
|
null,
|
||||||
|
config.BarTextureName,
|
||||||
|
config.BarTextureDrawMode)
|
||||||
|
{
|
||||||
|
BackgroundRect = new Rect(config.Position, config.Size, config.BackgroundColor);
|
||||||
|
ShadowConfig = config.ShadowConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BarHud SetBackground(Rect rect)
|
||||||
|
{
|
||||||
|
BackgroundRect = rect;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BarHud AddForegrounds(params Rect[] rects)
|
||||||
|
{
|
||||||
|
ForegroundRects.AddRange(rects);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BarHud AddLabels(params LabelConfig[]? labels)
|
||||||
|
{
|
||||||
|
if (labels != null)
|
||||||
|
{
|
||||||
|
foreach (LabelConfig config in labels)
|
||||||
|
{
|
||||||
|
var labelHud = new LabelHud(config);
|
||||||
|
LabelHuds.Add(labelHud);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BarHud SetGlow(PluginConfigColor color, int size = 1)
|
||||||
|
{
|
||||||
|
GlowColor = color;
|
||||||
|
GlowSize = size;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw(Vector2 origin)
|
||||||
|
{
|
||||||
|
var barPos = Utils.GetAnchoredPosition(origin, BackgroundRect.Size, Anchor);
|
||||||
|
var backgroundPos = barPos + BackgroundRect.Position;
|
||||||
|
|
||||||
|
DrawRects(barPos, backgroundPos);
|
||||||
|
|
||||||
|
// labels
|
||||||
|
foreach (LabelHud label in LabelHuds)
|
||||||
|
{
|
||||||
|
label.Draw(backgroundPos, BackgroundRect.Size, Actor, null, (uint?)Current, (uint?)Max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<(StrataLevel, Action)> GetDrawActions(Vector2 origin, StrataLevel strataLevel)
|
||||||
|
{
|
||||||
|
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
|
||||||
|
|
||||||
|
var barPos = Utils.GetAnchoredPosition(origin, BackgroundRect.Size, Anchor);
|
||||||
|
var backgroundPos = barPos + BackgroundRect.Position;
|
||||||
|
|
||||||
|
drawActions.Add((strataLevel, () =>
|
||||||
|
{
|
||||||
|
DrawRects(barPos, backgroundPos);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// labels
|
||||||
|
foreach (LabelHud label in LabelHuds)
|
||||||
|
{
|
||||||
|
drawActions.Add((label.GetConfig().StrataLevel, () =>
|
||||||
|
{
|
||||||
|
label.Draw(backgroundPos, BackgroundRect.Size, Actor, null, (uint?)Current, (uint?)Max);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return drawActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRects(Vector2 barPos, Vector2 backgroundPos)
|
||||||
|
{
|
||||||
|
DrawHelper.DrawInWindow(ID, backgroundPos, BackgroundRect.Size, NeedsInputs, (drawList) =>
|
||||||
|
{
|
||||||
|
// Draw background
|
||||||
|
drawList.AddRectFilled(backgroundPos, backgroundPos + BackgroundRect.Size, BackgroundRect.Color.Base);
|
||||||
|
|
||||||
|
// Draw Shadow
|
||||||
|
if (ShadowConfig != null && ShadowConfig.Enabled)
|
||||||
|
{
|
||||||
|
// Right Side
|
||||||
|
drawList.AddRectFilled(backgroundPos + new Vector2(BackgroundRect.Size.X, ShadowConfig.Offset), backgroundPos + BackgroundRect.Size + new Vector2(ShadowConfig.Offset, ShadowConfig.Offset) + new Vector2(ShadowConfig.Thickness - 1, ShadowConfig.Thickness - 1), ShadowConfig.Color.Base);
|
||||||
|
|
||||||
|
// Bottom Size
|
||||||
|
drawList.AddRectFilled(backgroundPos + new Vector2(ShadowConfig.Offset, BackgroundRect.Size.Y), backgroundPos + BackgroundRect.Size + new Vector2(ShadowConfig.Offset, ShadowConfig.Offset) + new Vector2(ShadowConfig.Thickness - 1, ShadowConfig.Thickness - 1), ShadowConfig.Color.Base);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw foregrounds
|
||||||
|
foreach (Rect rect in ForegroundRects)
|
||||||
|
{
|
||||||
|
DrawHelper.DrawBarTexture(barPos + rect.Position, rect.Size, rect.Color, BarTextureName, BarTextureDrawMode, drawList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw Border
|
||||||
|
if (DrawBorder)
|
||||||
|
{
|
||||||
|
drawList.AddRect(backgroundPos, backgroundPos + BackgroundRect.Size, BorderColor?.Base ?? 0xFF000000, 0, ImDrawFlags.None, BorderThickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw Glow
|
||||||
|
if (GlowColor != null)
|
||||||
|
{
|
||||||
|
var glowPosition = new Vector2(backgroundPos.X - 1, backgroundPos.Y - 1);
|
||||||
|
var glowSize = new Vector2(BackgroundRect.Size.X + 2, BackgroundRect.Size.Y + 2);
|
||||||
|
|
||||||
|
drawList.AddRect(glowPosition, glowPosition + glowSize, GlowColor.Base, 0, ImDrawFlags.None, GlowSize);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,441 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Game.ClientState.Statuses;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.Bars
|
||||||
|
{
|
||||||
|
public class BarUtilities
|
||||||
|
{
|
||||||
|
public static BarHud GetProgressBar(ProgressBarConfig config, float current, float max, float min = 0f, IGameObject? actor = null, PluginConfigColor? fillColor = null, BarGlowConfig? barGlowConfig = null)
|
||||||
|
{
|
||||||
|
return GetProgressBar(config, config.ThresholdConfig, new LabelConfig[] { config.Label }, current, max, min, actor, fillColor, barGlowConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarHud GetProgressBar(
|
||||||
|
BarConfig config,
|
||||||
|
ThresholdConfig? thresholdConfig,
|
||||||
|
LabelConfig[]? labelConfigs,
|
||||||
|
float current,
|
||||||
|
float max,
|
||||||
|
float min = 0f,
|
||||||
|
IGameObject? actor = null,
|
||||||
|
PluginConfigColor? fillColor = null,
|
||||||
|
BarGlowConfig? glowConfig = null,
|
||||||
|
PluginConfigColor? backgroundColor = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
BarHud bar = new(config, actor, glowConfig, current, max);
|
||||||
|
|
||||||
|
PluginConfigColor color = fillColor ?? config.FillColor;
|
||||||
|
if (thresholdConfig != null)
|
||||||
|
{
|
||||||
|
color = thresholdConfig.ChangeColor && thresholdConfig.IsActive(current) ? thresholdConfig.Color : color;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect foreground = GetFillRect(config.Position, config.Size, config.FillDirection, color, current, max, min);
|
||||||
|
bar.AddForegrounds(foreground);
|
||||||
|
bar.AddLabels(labelConfigs);
|
||||||
|
|
||||||
|
if (backgroundColor != null)
|
||||||
|
{
|
||||||
|
Rect bg = new Rect(config.Position, config.Size, backgroundColor);
|
||||||
|
bar.SetBackground(bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddThresholdMarker(bar, config, thresholdConfig, max, min);
|
||||||
|
|
||||||
|
return bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarHud? GetProcBar(
|
||||||
|
ProgressBarConfig config,
|
||||||
|
IPlayerCharacter player,
|
||||||
|
uint statusId,
|
||||||
|
float maxDuration,
|
||||||
|
bool trackDuration = true)
|
||||||
|
{
|
||||||
|
return GetProcBar(config, player, new List<uint> { statusId }, new List<float> { maxDuration }, trackDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarHud? GetProcBar(
|
||||||
|
ProgressBarConfig config,
|
||||||
|
IPlayerCharacter player,
|
||||||
|
List<uint> statusIDs,
|
||||||
|
List<float> maxDurations,
|
||||||
|
bool trackDuration = true)
|
||||||
|
{
|
||||||
|
if (statusIDs.Count == 0 || maxDurations.Count == 0) { return null; }
|
||||||
|
|
||||||
|
IStatus? status = Utils.StatusListForBattleChara(player).FirstOrDefault(o => statusIDs.Contains(o.StatusId));
|
||||||
|
if (status == null && config.HideWhenInactive)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
float duration = Math.Abs(status?.RemainingTime ?? 0);
|
||||||
|
|
||||||
|
if (trackDuration)
|
||||||
|
{
|
||||||
|
int index = status != null ? statusIDs.IndexOf(status.StatusId) : 0;
|
||||||
|
config.Label.SetValue(duration);
|
||||||
|
return GetProgressBar(config, duration, maxDurations[index], 0, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Label.SetText("");
|
||||||
|
return GetBar(config, duration <= 0 ? 0 : 1, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarHud? GetDoTBar(
|
||||||
|
ProgressBarConfig config,
|
||||||
|
IPlayerCharacter player,
|
||||||
|
IGameObject? target,
|
||||||
|
uint statusId,
|
||||||
|
float maxDuration)
|
||||||
|
{
|
||||||
|
return GetDoTBar(config, player, target, new List<uint> { statusId }, new List<float> { maxDuration });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarHud? GetDoTBar(
|
||||||
|
ProgressBarConfig config,
|
||||||
|
IPlayerCharacter player,
|
||||||
|
IGameObject? target,
|
||||||
|
List<uint> statusIDs,
|
||||||
|
List<float> maxDurations)
|
||||||
|
{
|
||||||
|
if (statusIDs.Count == 0 || maxDurations.Count == 0) { return null; }
|
||||||
|
|
||||||
|
IStatus? status = null;
|
||||||
|
|
||||||
|
if (target != null && target is IBattleChara targetChara)
|
||||||
|
{
|
||||||
|
status = Utils.StatusListForBattleChara(targetChara).FirstOrDefault(o => o.SourceId == player.GameObjectId && statusIDs.Contains(o.StatusId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == null && config.HideWhenInactive)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = status != null ? statusIDs.IndexOf(status.StatusId) : 0;
|
||||||
|
float duration = Math.Abs(status?.RemainingTime ?? 0);
|
||||||
|
float maxDuration = maxDurations[index];
|
||||||
|
|
||||||
|
config.Label.SetValue(duration);
|
||||||
|
return GetProgressBar(config, duration, maxDuration, 0, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddThresholdMarker(BarHud bar, BarConfig config, ThresholdConfig? thresholdConfig, float max, float min)
|
||||||
|
{
|
||||||
|
if (thresholdConfig == null || !thresholdConfig.Enabled || !thresholdConfig.ShowMarker)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float thresholdPercent = Math.Clamp(thresholdConfig.Value / (max - min), 0f, 1f);
|
||||||
|
Vector2 offset = GetFillDirectionOffset(
|
||||||
|
new Vector2(config.Size.X * thresholdPercent, config.Size.Y * thresholdPercent),
|
||||||
|
config.FillDirection
|
||||||
|
);
|
||||||
|
|
||||||
|
Vector2 markerSize = config.FillDirection.IsHorizontal() ?
|
||||||
|
new Vector2(thresholdConfig.MarkerSize, config.Size.Y) :
|
||||||
|
new Vector2(config.Size.X, thresholdConfig.MarkerSize);
|
||||||
|
|
||||||
|
Vector2 markerPos = config.FillDirection.IsInverted() ?
|
||||||
|
config.Position + GetFillDirectionOffset(config.Size, config.FillDirection) - offset :
|
||||||
|
config.Position + offset;
|
||||||
|
|
||||||
|
Vector2 anchoredPos = Utils.GetAnchoredPosition(markerPos, markerSize, config.FillDirection.IsHorizontal() ? DrawAnchor.Top : DrawAnchor.Left);
|
||||||
|
Rect marker = new(anchoredPos, markerSize, thresholdConfig.MarkerColor);
|
||||||
|
bar.AddForegrounds(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tuple is <foregroundColor, percent fill, labels>
|
||||||
|
public static BarHud[] GetChunkedBars(
|
||||||
|
ChunkedBarConfig config,
|
||||||
|
Tuple<PluginConfigColor, float, LabelConfig?>[] chunks,
|
||||||
|
IGameObject? actor,
|
||||||
|
BarGlowConfig glowConfig)
|
||||||
|
{
|
||||||
|
List<bool> chunksToGlowList = new();
|
||||||
|
for (int i = 0; i < chunks.Length; i++)
|
||||||
|
{
|
||||||
|
chunksToGlowList.Add(chunks[i].Item2 >= 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetChunkedBars(config, chunks, actor, glowConfig, chunksToGlowList.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarHud[] GetChunkedBars(
|
||||||
|
ChunkedBarConfig config,
|
||||||
|
Tuple<PluginConfigColor, float, LabelConfig?>[] chunks,
|
||||||
|
IGameObject? actor,
|
||||||
|
BarGlowConfig? glowConfig = null,
|
||||||
|
bool[]? chunksToGlow = null)
|
||||||
|
{
|
||||||
|
BarHud[] bars = new BarHud[chunks.Length];
|
||||||
|
Vector2 pos = Utils.GetAnchoredPosition(config.Position, config.Size, config.Anchor);
|
||||||
|
|
||||||
|
for (int i = 0; i < chunks.Length; i++)
|
||||||
|
{
|
||||||
|
Vector2 chunkPos, chunkSize;
|
||||||
|
if (config.FillDirection.IsHorizontal())
|
||||||
|
{
|
||||||
|
chunkSize = new Vector2((config.Size.X - config.Padding * (chunks.Length - 1)) / chunks.Length, config.Size.Y);
|
||||||
|
chunkPos = pos + new Vector2((chunkSize.X + config.Padding) * i, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
chunkSize = new Vector2(config.Size.X, (config.Size.Y - config.Padding * (chunks.Length - 1)) / chunks.Length);
|
||||||
|
chunkPos = pos + new Vector2(0, (chunkSize.Y + config.Padding) * i);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect background = new(chunkPos, chunkSize, config.BackgroundColor);
|
||||||
|
Rect foreground = GetFillRect(chunkPos, chunkSize, config.FillDirection, chunks[i].Item1, chunks[i].Item2, 1f, 0f);
|
||||||
|
BarGlowConfig? glow = (glowConfig?.Enabled == true && chunksToGlow?[i] == true) ? glowConfig : null;
|
||||||
|
|
||||||
|
bars[i] = new BarHud(config.ID + i,
|
||||||
|
config.DrawBorder,
|
||||||
|
config.BorderColor,
|
||||||
|
config.BorderThickness,
|
||||||
|
actor: actor,
|
||||||
|
glowColor: glow?.Color,
|
||||||
|
glowSize: glow?.Size,
|
||||||
|
barTextureName: config.BarTextureName,
|
||||||
|
barTextureDrawMode: config.BarTextureDrawMode,
|
||||||
|
shadowConfig: config.ShadowConfig
|
||||||
|
);
|
||||||
|
bars[i].SetBackground(background);
|
||||||
|
bars[i].AddForegrounds(foreground);
|
||||||
|
|
||||||
|
LabelConfig? label = chunks[i].Item3;
|
||||||
|
if (label is not null)
|
||||||
|
{
|
||||||
|
bars[i].AddLabels(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarHud[] GetChunkedBars(
|
||||||
|
ChunkedBarConfig config,
|
||||||
|
int chunks,
|
||||||
|
float current,
|
||||||
|
float max,
|
||||||
|
float min = 0f,
|
||||||
|
IGameObject? actor = null,
|
||||||
|
LabelConfig?[]? labels = null,
|
||||||
|
PluginConfigColor? fillColor = null,
|
||||||
|
PluginConfigColor? partialFillColor = null,
|
||||||
|
BarGlowConfig? glowConfig = null,
|
||||||
|
bool[]? chunksToGlow = null)
|
||||||
|
{
|
||||||
|
float chunkRange = (max - min) / chunks;
|
||||||
|
|
||||||
|
var barChunks = new Tuple<PluginConfigColor, float, LabelConfig?>[chunks];
|
||||||
|
for (int i = 0; i < chunks; i++)
|
||||||
|
{
|
||||||
|
int barIndex = config.FillDirection.IsInverted() ? chunks - i - 1 : i;
|
||||||
|
float chunkMin = min + chunkRange * i;
|
||||||
|
float chunkMax = min + chunkRange * (i + 1);
|
||||||
|
float chunkPercent = Math.Clamp((current - chunkMin) / (chunkMax - chunkMin), 0f, 1f);
|
||||||
|
|
||||||
|
PluginConfigColor chunkColor = partialFillColor != null && current < chunkMax ? partialFillColor : fillColor ?? config.FillColor;
|
||||||
|
barChunks[barIndex] = new Tuple<PluginConfigColor, float, LabelConfig?>(chunkColor, chunkPercent, labels?[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glowConfig != null && chunksToGlow == null)
|
||||||
|
{
|
||||||
|
return GetChunkedBars(config, barChunks, actor, glowConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetChunkedBars(config, barChunks, actor, glowConfig, chunksToGlow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarHud[] GetChunkedProgressBars(
|
||||||
|
ChunkedProgressBarConfig config,
|
||||||
|
int chunks,
|
||||||
|
float current,
|
||||||
|
float max,
|
||||||
|
float min = 0f,
|
||||||
|
IGameObject? actor = null,
|
||||||
|
BarGlowConfig? glowConfig = null,
|
||||||
|
PluginConfigColor? fillColor = null,
|
||||||
|
int thresholdChunk = 1,
|
||||||
|
bool[]? chunksToGlow = null,
|
||||||
|
int forceLabelIndex = -1)
|
||||||
|
{
|
||||||
|
var color = fillColor ?? config.FillColor;
|
||||||
|
|
||||||
|
if (config.UseChunks)
|
||||||
|
{
|
||||||
|
NumericLabelConfig?[] labels = new NumericLabelConfig?[chunks];
|
||||||
|
for (int i = 0; i < chunks; i++)
|
||||||
|
{
|
||||||
|
float chunkRange = (max - min) / chunks;
|
||||||
|
float chunkMin = min + chunkRange * i;
|
||||||
|
float chunkMax = min + chunkRange * (i + 1);
|
||||||
|
float chunkPercent = Math.Clamp((current - chunkMin) / (chunkMax - chunkMin), 0f, 1f);
|
||||||
|
|
||||||
|
NumericLabelConfig? label = config.Label;
|
||||||
|
if (forceLabelIndex == -1)
|
||||||
|
{
|
||||||
|
switch (config.LabelMode)
|
||||||
|
{
|
||||||
|
case LabelMode.AllChunks:
|
||||||
|
label = config.Label.Clone(i);
|
||||||
|
label.SetValue(Math.Clamp(current - chunkMin, 0, chunkRange));
|
||||||
|
break;
|
||||||
|
case LabelMode.ActiveChunk:
|
||||||
|
label = chunkPercent < 1f && chunkPercent > 0f ? config.Label.Clone(i) : null;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
label = forceLabelIndex == i ? config.Label : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
labels[i] = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
var partialColor = config.UsePartialFillColor ? config.PartialFillColor : null;
|
||||||
|
return GetChunkedBars(config, chunks, current, max, min, actor, labels, color, partialColor, glowConfig, chunksToGlow);
|
||||||
|
}
|
||||||
|
|
||||||
|
var threshold = GetThresholdConfigForChunk(config, thresholdChunk, chunks, min, max);
|
||||||
|
BarHud bar = GetProgressBar(config, threshold, new LabelConfig[] { config.Label }, current, max, min, actor, color, glowConfig);
|
||||||
|
return new BarHud[] { bar };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Rect[] GetShieldForeground(
|
||||||
|
ShieldConfig shieldConfig,
|
||||||
|
Vector2 pos,
|
||||||
|
Vector2 size,
|
||||||
|
Vector2 healthFillSize,
|
||||||
|
BarDirection fillDirection,
|
||||||
|
float shieldPercent,
|
||||||
|
float currentHp,
|
||||||
|
float maxHp,
|
||||||
|
PluginConfigColor? color = null)
|
||||||
|
{
|
||||||
|
float shieldValue = shieldPercent * maxHp;
|
||||||
|
float overshield = shieldConfig.FillHealthFirst ? Math.Max(shieldValue + currentHp - maxHp, 0f) : shieldValue;
|
||||||
|
float shieldSize = shieldConfig.Height;
|
||||||
|
PluginConfigColor c = color ?? shieldConfig.Color;
|
||||||
|
|
||||||
|
if (!shieldConfig.HeightInPixels)
|
||||||
|
{
|
||||||
|
shieldSize = (fillDirection.IsHorizontal() ? size.Y : size.X) * shieldConfig.Height / 100f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var overshieldSize = fillDirection.IsHorizontal()
|
||||||
|
? new Vector2(size.X, Math.Min(shieldSize, size.Y))
|
||||||
|
: new Vector2(Math.Min(shieldSize, size.X), size.Y);
|
||||||
|
|
||||||
|
Rect overshieldFill = GetFillRect(pos, overshieldSize, fillDirection, c, overshield, maxHp);
|
||||||
|
|
||||||
|
if (shieldConfig.FillHealthFirst && currentHp < maxHp)
|
||||||
|
{
|
||||||
|
var shieldPos = fillDirection.IsInverted() ? pos : pos + GetFillDirectionOffset(healthFillSize, fillDirection);
|
||||||
|
var shieldFillSize = size - GetFillDirectionOffset(healthFillSize, fillDirection);
|
||||||
|
var healthFillShieldSize = fillDirection.IsHorizontal()
|
||||||
|
? new Vector2(shieldFillSize.X, Math.Min(shieldSize, size.Y))
|
||||||
|
: new Vector2(Math.Min(shieldSize, size.X), shieldFillSize.Y);
|
||||||
|
|
||||||
|
Rect shieldFill = GetFillRect(shieldPos, healthFillShieldSize, fillDirection, c, shieldValue - overshield, maxHp - currentHp, 0f);
|
||||||
|
return new[] { overshieldFill, shieldFill };
|
||||||
|
}
|
||||||
|
|
||||||
|
return new[] { overshieldFill };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BarHud GetBar(
|
||||||
|
BarConfig Config,
|
||||||
|
float current,
|
||||||
|
float max,
|
||||||
|
float min = 0f,
|
||||||
|
IGameObject? actor = null,
|
||||||
|
PluginConfigColor? fillColor = null,
|
||||||
|
BarGlowConfig? glowConfig = null,
|
||||||
|
LabelConfig[]? labels = null)
|
||||||
|
{
|
||||||
|
Rect foreground = GetFillRect(Config.Position, Config.Size, Config.FillDirection, fillColor ?? Config.FillColor, current, max, min);
|
||||||
|
|
||||||
|
BarHud bar = new BarHud(Config, actor, glowConfig);
|
||||||
|
bar.AddForegrounds(foreground);
|
||||||
|
bar.AddLabels(labels);
|
||||||
|
|
||||||
|
return bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the horizonal or vertical offset depending on the fill direction.
|
||||||
|
/// </summary>
|
||||||
|
public static Vector2 GetFillDirectionOffset(Vector2 size, BarDirection fillDirection)
|
||||||
|
{
|
||||||
|
return fillDirection.IsHorizontal() ? new(size.X, 0) : new(0, size.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Rect GetFillRect(Vector2 pos, Vector2 size, BarDirection fillDirection, PluginConfigColor color, float current, float max, float min = 0f)
|
||||||
|
{
|
||||||
|
float fillPercent = max == 0 ? 1f : Math.Clamp((current - min) / (max - min), 0f, 1f);
|
||||||
|
|
||||||
|
Vector2 fillPos = Vector2.Zero;
|
||||||
|
Vector2 fillSize = fillDirection.IsHorizontal() ? new(size.X * fillPercent, size.Y) : new(size.X, size.Y * fillPercent);
|
||||||
|
if (fillDirection == BarDirection.Left)
|
||||||
|
{
|
||||||
|
fillPos = Utils.GetAnchoredPosition(new(size.X, 0), fillSize, DrawAnchor.TopRight);
|
||||||
|
}
|
||||||
|
else if (fillDirection == BarDirection.Up)
|
||||||
|
{
|
||||||
|
fillPos = Utils.GetAnchoredPosition(new(0, size.Y), fillSize, DrawAnchor.BottomLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Rect(pos + fillPos, fillSize, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ThresholdConfig GetThresholdConfigForChunk(ChunkedProgressBarConfig config, int chunk, int chunks, float min, float max) =>
|
||||||
|
new ThresholdConfig
|
||||||
|
{
|
||||||
|
ThresholdType = ThresholdType.Below,
|
||||||
|
Color = config.PartialFillColor,
|
||||||
|
Enabled = config.UsePartialFillColor,
|
||||||
|
Value = (max - min) / chunks * chunk,
|
||||||
|
ChangeColor = true,
|
||||||
|
ShowMarker = false
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void AddShield(BarHud bar, BarConfig config, ShieldConfig shieldConfig, ICharacter character, Vector2 fillSize, PluginConfigColor? color = null)
|
||||||
|
{
|
||||||
|
if (shieldConfig.Enabled)
|
||||||
|
{
|
||||||
|
float shield = Utils.ActorShieldValue(character);
|
||||||
|
if (shield > 0f)
|
||||||
|
{
|
||||||
|
bar.AddForegrounds(
|
||||||
|
GetShieldForeground(
|
||||||
|
shieldConfig,
|
||||||
|
config.Position,
|
||||||
|
config.Size,
|
||||||
|
fillSize,
|
||||||
|
config.FillDirection,
|
||||||
|
shield,
|
||||||
|
character.CurrentHp,
|
||||||
|
character.MaxHp,
|
||||||
|
color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.Bars
|
||||||
|
{
|
||||||
|
[Exportable(false)]
|
||||||
|
public class ChunkedBarConfig : BarConfig
|
||||||
|
{
|
||||||
|
[DragInt("Padding", min = -4000, max = 4000)]
|
||||||
|
[Order(45)]
|
||||||
|
public int Padding = 2;
|
||||||
|
|
||||||
|
public ChunkedBarConfig(
|
||||||
|
Vector2 position,
|
||||||
|
Vector2 size,
|
||||||
|
PluginConfigColor fillColor,
|
||||||
|
int padding = 2) : base(position, size, fillColor)
|
||||||
|
{
|
||||||
|
Padding = padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class ChunkedProgressBarConfig : ChunkedBarConfig
|
||||||
|
{
|
||||||
|
[Checkbox("Show In Chunks", spacing = true)]
|
||||||
|
[Order(46)]
|
||||||
|
public bool UseChunks = true;
|
||||||
|
|
||||||
|
[RadioSelector("Show Text on All Chunks", "Show Text on Active Chunk")]
|
||||||
|
[Order(47, collapseWith = nameof(UseChunks))]
|
||||||
|
public LabelMode LabelMode;
|
||||||
|
|
||||||
|
[Checkbox("Use Partial Fill Color", spacing = true)]
|
||||||
|
[Order(50)]
|
||||||
|
public bool UsePartialFillColor = false;
|
||||||
|
|
||||||
|
[ColorEdit4("Partial Fill Color")]
|
||||||
|
[Order(55, collapseWith = nameof(UsePartialFillColor))]
|
||||||
|
public PluginConfigColor PartialFillColor;
|
||||||
|
|
||||||
|
[NestedConfig("Bar Text", 1000, separator = false, spacing = true)]
|
||||||
|
public NumericLabelConfig Label;
|
||||||
|
|
||||||
|
public ChunkedProgressBarConfig(
|
||||||
|
Vector2 position,
|
||||||
|
Vector2 size,
|
||||||
|
PluginConfigColor fillColor,
|
||||||
|
int padding = 2,
|
||||||
|
PluginConfigColor? partialFillColor = null) : base(position, size, fillColor, padding)
|
||||||
|
{
|
||||||
|
Label = new NumericLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
|
||||||
|
Label.Enabled = false;
|
||||||
|
|
||||||
|
PartialFillColor = partialFillColor ?? new PluginConfigColor(new(180f / 255f, 180f / 255f, 180f / 255f, 100f / 100f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("LabelMode", "UsePartialFillColor", "PartialFillColor")]
|
||||||
|
[Exportable(false)]
|
||||||
|
public class StacksWithDurationBarConfig : ChunkedProgressBarConfig
|
||||||
|
{
|
||||||
|
public StacksWithDurationBarConfig(
|
||||||
|
Vector2 position,
|
||||||
|
Vector2 size,
|
||||||
|
PluginConfigColor fillColor,
|
||||||
|
int padding = 2,
|
||||||
|
PluginConfigColor? partialFillColor = null) : base(position, size, fillColor, padding)
|
||||||
|
{
|
||||||
|
UseChunks = true;
|
||||||
|
UsePartialFillColor = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LabelMode
|
||||||
|
{
|
||||||
|
AllChunks,
|
||||||
|
ActiveChunk
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.Bars
|
||||||
|
{
|
||||||
|
[Exportable(false)]
|
||||||
|
public class ProgressBarConfig : BarConfig
|
||||||
|
{
|
||||||
|
[NestedConfig("Threshold", 45)]
|
||||||
|
public ThresholdConfig ThresholdConfig = new ThresholdConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Bar Text", 1000)]
|
||||||
|
public NumericLabelConfig Label;
|
||||||
|
|
||||||
|
public ProgressBarConfig(
|
||||||
|
Vector2 position,
|
||||||
|
Vector2 size,
|
||||||
|
PluginConfigColor fillColor,
|
||||||
|
BarDirection fillDirection = BarDirection.Right,
|
||||||
|
PluginConfigColor? threshHoldColor = null,
|
||||||
|
float threshold = 0f) : base(position, size, fillColor, fillDirection)
|
||||||
|
{
|
||||||
|
Label = new NumericLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
|
||||||
|
ThresholdConfig.Color = threshHoldColor ?? ThresholdConfig.Color;
|
||||||
|
ThresholdConfig.Value = threshold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class ThresholdConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[DragFloat("Threshold Value", min = 0f, max = 10000f)]
|
||||||
|
[Order(10)]
|
||||||
|
public float Value = 0f;
|
||||||
|
|
||||||
|
[Checkbox("Change Color")]
|
||||||
|
[Order(15)]
|
||||||
|
public bool ChangeColor = true;
|
||||||
|
|
||||||
|
[Combo("Activate Above/Below Threshold", "Above", "Below")]
|
||||||
|
[Order(20, collapseWith = nameof(ChangeColor))]
|
||||||
|
public ThresholdType ThresholdType = ThresholdType.Below;
|
||||||
|
|
||||||
|
[ColorEdit4("Color")]
|
||||||
|
[Order(25, collapseWith = nameof(ChangeColor))]
|
||||||
|
public PluginConfigColor Color = new PluginConfigColor(new(230f / 255f, 33f / 255f, 33f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[Checkbox("Show Threshold Marker")]
|
||||||
|
[Order(30)]
|
||||||
|
public bool ShowMarker = false;
|
||||||
|
|
||||||
|
[DragInt("Threshold Marker Size", min = 0, max = 10000)]
|
||||||
|
[Order(35, collapseWith = nameof(ShowMarker))]
|
||||||
|
public int MarkerSize = 2;
|
||||||
|
|
||||||
|
[ColorEdit4("Threshold Marker Color")]
|
||||||
|
[Order(40, collapseWith = nameof(ShowMarker))]
|
||||||
|
public PluginConfigColor MarkerColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
public bool IsActive(float current)
|
||||||
|
{
|
||||||
|
return Enabled && (ThresholdType == ThresholdType.Below && current < Value ||
|
||||||
|
ThresholdType == ThresholdType.Above && current > Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThresholdConfig()
|
||||||
|
{
|
||||||
|
Enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ThresholdType
|
||||||
|
{
|
||||||
|
Above,
|
||||||
|
Below
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.Bars
|
||||||
|
{
|
||||||
|
public class Rect
|
||||||
|
{
|
||||||
|
public Vector2 Position { get; set; }
|
||||||
|
|
||||||
|
public Vector2 Size { get; set; }
|
||||||
|
|
||||||
|
public PluginConfigColor Color { get; set; }
|
||||||
|
|
||||||
|
public Rect(Vector2 pos, Vector2 size, PluginConfigColor? color = null)
|
||||||
|
{
|
||||||
|
Position = pos;
|
||||||
|
Size = size;
|
||||||
|
Color = color ?? new PluginConfigColor(new(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rect() : this(new(0, 0), new(0, 0), new PluginConfigColor(new(0, 0, 0, 0))) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
using Dalamud.Logging;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface
|
||||||
|
{
|
||||||
|
public delegate void DraggableHudElementSelectHandler(DraggableHudElement element);
|
||||||
|
|
||||||
|
public class DraggableHudElement : HudElement
|
||||||
|
{
|
||||||
|
public DraggableHudElement(MovablePluginConfigObject config, string? displayName = null) : base(config)
|
||||||
|
{
|
||||||
|
_displayName = displayName ?? ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event DraggableHudElementSelectHandler? SelectEvent;
|
||||||
|
public bool Selected = false;
|
||||||
|
|
||||||
|
private string _displayName;
|
||||||
|
protected bool _windowPositionSet = false;
|
||||||
|
private Vector2 _lastWindowPos = Vector2.Zero;
|
||||||
|
private Vector2 _positionOffset;
|
||||||
|
private Vector2 _contentMargin = new Vector2(4, 0);
|
||||||
|
|
||||||
|
private bool _draggingEnabled = false;
|
||||||
|
public bool DraggingEnabled
|
||||||
|
{
|
||||||
|
get => _draggingEnabled;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_draggingEnabled = value;
|
||||||
|
|
||||||
|
if (_draggingEnabled)
|
||||||
|
{
|
||||||
|
_windowPositionSet = false;
|
||||||
|
_minPos = null;
|
||||||
|
_maxPos = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanTakeInputForDrag = false;
|
||||||
|
public bool NeedsInputForDrag { get; private set; } = false;
|
||||||
|
|
||||||
|
public virtual Vector2 ParentPos() { return Vector2.Zero; } // override
|
||||||
|
|
||||||
|
protected sealed override void CreateDrawActions(Vector2 origin)
|
||||||
|
{
|
||||||
|
if (_draggingEnabled)
|
||||||
|
{
|
||||||
|
AddDrawAction(_config.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
DrawDraggableArea(origin);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawChildren(origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void DrawChildren(Vector2 origin) { }
|
||||||
|
|
||||||
|
private bool CalculateNeedsInput(Vector2 pos, Vector2 size, bool selected)
|
||||||
|
{
|
||||||
|
Vector2 mousePos = ImGui.GetMousePos();
|
||||||
|
|
||||||
|
if (ImGui.IsMouseHoveringRect(pos, pos + size))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selected)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var arrowsPos = DraggablesHelper.GetArrowPositions(pos, size);
|
||||||
|
|
||||||
|
foreach (Vector2 arrowPos in arrowsPos)
|
||||||
|
{
|
||||||
|
if (ImGui.IsMouseHoveringRect(arrowPos, arrowPos + DraggablesHelper.ArrowSize))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void DrawDraggableArea(Vector2 origin)
|
||||||
|
{
|
||||||
|
var windowFlags = ImGuiWindowFlags.NoScrollbar
|
||||||
|
| ImGuiWindowFlags.NoTitleBar
|
||||||
|
| ImGuiWindowFlags.NoResize
|
||||||
|
| ImGuiWindowFlags.NoBackground
|
||||||
|
| ImGuiWindowFlags.NoDecoration
|
||||||
|
| ImGuiWindowFlags.NoSavedSettings;
|
||||||
|
|
||||||
|
// always update size
|
||||||
|
var size = MaxPos - MinPos + _contentMargin * 2;
|
||||||
|
ImGui.SetNextWindowSize(size, ImGuiCond.Always);
|
||||||
|
|
||||||
|
// needs input?
|
||||||
|
NeedsInputForDrag = CanTakeInputForDrag && CalculateNeedsInput(_lastWindowPos, size, Selected);
|
||||||
|
|
||||||
|
if (!NeedsInputForDrag)
|
||||||
|
{
|
||||||
|
windowFlags |= ImGuiWindowFlags.NoMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set initial position
|
||||||
|
if (!_windowPositionSet)
|
||||||
|
{
|
||||||
|
ImGui.SetNextWindowPos(origin + MinPos - _contentMargin);
|
||||||
|
_windowPositionSet = true;
|
||||||
|
|
||||||
|
_positionOffset = _config.Position - MinPos + _contentMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update config object position
|
||||||
|
ImGui.Begin(ID + "_dragArea", windowFlags);
|
||||||
|
var windowPos = ImGui.GetWindowPos();
|
||||||
|
_lastWindowPos = windowPos;
|
||||||
|
_config.Position = windowPos + _positionOffset - origin;
|
||||||
|
|
||||||
|
// check selection
|
||||||
|
var tooltipText = "x: " + _config.Position.X.ToString() + " y: " + _config.Position.Y.ToString();
|
||||||
|
|
||||||
|
if (NeedsInputForDrag && ImGui.IsMouseHoveringRect(windowPos, windowPos + size))
|
||||||
|
{
|
||||||
|
bool cliked = ImGui.IsMouseClicked(ImGuiMouseButton.Left) || ImGui.IsMouseDown(ImGuiMouseButton.Left);
|
||||||
|
if (cliked && !Selected)
|
||||||
|
{
|
||||||
|
SelectEvent?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tooltip
|
||||||
|
TooltipsHelper.Instance.ShowTooltipOnCursor(tooltipText);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw window
|
||||||
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
|
var contentPos = windowPos + _contentMargin;
|
||||||
|
var contentSize = size - _contentMargin * 2;
|
||||||
|
|
||||||
|
// draw draggable indicators
|
||||||
|
drawList.AddRectFilled(contentPos, contentPos + contentSize, 0x88444444, 3);
|
||||||
|
|
||||||
|
var lineColor = Selected ? 0xEEFFFFFF : 0x66FFFFFF;
|
||||||
|
drawList.AddRect(contentPos, contentPos + contentSize, lineColor, 3, ImDrawFlags.None, 2);
|
||||||
|
drawList.AddLine(contentPos + new Vector2(contentSize.X / 2f, 0), contentPos + new Vector2(contentSize.X / 2, contentSize.Y), lineColor);
|
||||||
|
drawList.AddLine(contentPos + new Vector2(0, contentSize.Y / 2f), contentPos + new Vector2(contentSize.X, contentSize.Y / 2), lineColor);
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
|
||||||
|
// arrows
|
||||||
|
if (Selected)
|
||||||
|
{
|
||||||
|
if (DraggablesHelper.DrawArrows(windowPos, size, tooltipText, out var movement))
|
||||||
|
{
|
||||||
|
_minPos = null;
|
||||||
|
_maxPos = null;
|
||||||
|
_config.Position += movement;
|
||||||
|
_windowPositionSet = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// element name
|
||||||
|
var textSize = ImGui.CalcTextSize(_displayName);
|
||||||
|
var textColor = Selected ? 0xFFFFFFFF : 0xEEFFFFFF;
|
||||||
|
var textOutlineColor = Selected ? 0xFF000000 : 0xEE000000;
|
||||||
|
DrawHelper.DrawOutlinedText(_displayName, contentPos + contentSize / 2f - textSize / 2f, textColor, textOutlineColor, drawList);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region draggable area
|
||||||
|
protected Vector2? _minPos = null;
|
||||||
|
public Vector2 MinPos
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_minPos != null)
|
||||||
|
{
|
||||||
|
return (Vector2)_minPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (positions, sizes) = ChildrenPositionsAndSizes();
|
||||||
|
if (positions.Count == 0 || sizes.Count == 0)
|
||||||
|
{
|
||||||
|
return Vector2.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
float minX = float.MaxValue;
|
||||||
|
float minY = float.MaxValue;
|
||||||
|
|
||||||
|
var anchorConfig = _config as AnchorablePluginConfigObject;
|
||||||
|
for (int i = 0; i < positions.Count; i++)
|
||||||
|
{
|
||||||
|
var pos = GetAnchoredPosition(positions[i], sizes[i], anchorConfig?.Anchor ?? DrawAnchor.Center);
|
||||||
|
minX = Math.Min(minX, pos.X);
|
||||||
|
minY = Math.Min(minY, pos.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
_minPos = new Vector2(minX, minY);
|
||||||
|
return (Vector2)_minPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector2? _maxPos = null;
|
||||||
|
public Vector2 MaxPos
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_maxPos != null)
|
||||||
|
{
|
||||||
|
return (Vector2)_maxPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (positions, sizes) = ChildrenPositionsAndSizes();
|
||||||
|
if (positions.Count == 0 || sizes.Count == 0)
|
||||||
|
{
|
||||||
|
return Vector2.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
float maxX = float.MinValue;
|
||||||
|
float maxY = float.MinValue;
|
||||||
|
|
||||||
|
var anchorConfig = _config as AnchorablePluginConfigObject;
|
||||||
|
for (int i = 0; i < positions.Count; i++)
|
||||||
|
{
|
||||||
|
var pos = GetAnchoredPosition(positions[i], sizes[i], anchorConfig?.Anchor ?? DrawAnchor.Center) + sizes[i];
|
||||||
|
maxX = Math.Max(maxX, pos.X);
|
||||||
|
maxY = Math.Max(maxY, pos.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
_maxPos = new Vector2(maxX, maxY);
|
||||||
|
return (Vector2)_maxPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void FlagDraggableAreaDirty()
|
||||||
|
{
|
||||||
|
_minPos = null;
|
||||||
|
_maxPos = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Vector2 GetAnchoredPosition(Vector2 position, Vector2 size, DrawAnchor anchor)
|
||||||
|
{
|
||||||
|
return Utils.GetAnchoredPosition(ParentPos() + position, size, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||||
|
{
|
||||||
|
return (new List<Vector2>(), new List<Vector2>());
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ParentAnchoredDraggableHudElement : DraggableHudElement
|
||||||
|
{
|
||||||
|
public ParentAnchoredDraggableHudElement(MovablePluginConfigObject config, string? displayName = null)
|
||||||
|
: base(config, displayName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool AnchorToParent { get; }
|
||||||
|
protected virtual DrawAnchor ParentAnchor { get; }
|
||||||
|
public AnchorablePluginConfigObject? ParentConfig { get; set; }
|
||||||
|
|
||||||
|
private Vector2? _lastParentPosition = null;
|
||||||
|
|
||||||
|
private bool IsAnchored => AnchorToParent && ParentConfig != null;
|
||||||
|
|
||||||
|
public override Vector2 ParentPos()
|
||||||
|
{
|
||||||
|
if (!IsAnchored)
|
||||||
|
{
|
||||||
|
return Vector2.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 parentAnchoredPos = Utils.GetAnchoredPosition(ParentConfig!.Position, ParentConfig!.Size, ParentConfig!.Anchor);
|
||||||
|
return Utils.GetAnchoredPosition(parentAnchoredPos, -ParentConfig!.Size, ParentAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawDraggableArea(Vector2 origin)
|
||||||
|
{
|
||||||
|
// if the parent moved, update own draggable area
|
||||||
|
if (IsAnchored && (_lastParentPosition == null || _lastParentPosition != ParentConfig!.Position))
|
||||||
|
{
|
||||||
|
_windowPositionSet = false;
|
||||||
|
_minPos = null;
|
||||||
|
_maxPos = null;
|
||||||
|
_lastParentPosition = ParentConfig!.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.DrawDraggableArea(origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using HSUI.Interface.StatusEffects;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.EnemyList
|
||||||
|
{
|
||||||
|
public enum EnemyListGrowthDirection
|
||||||
|
{
|
||||||
|
Down = 0,
|
||||||
|
Up
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Enemy List", true)]
|
||||||
|
[SubSection("General", 0)]
|
||||||
|
public class EnemyListConfig : MovablePluginConfigObject
|
||||||
|
{
|
||||||
|
public new static EnemyListConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var config = new EnemyListConfig();
|
||||||
|
Vector2 screenSize = ImGui.GetMainViewport().Size;
|
||||||
|
config.Position = new Vector2(screenSize.X * 0.2f, -screenSize.Y * 0.2f);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Checkbox("Preview", isMonitored = true)]
|
||||||
|
[Order(4)]
|
||||||
|
public bool Preview = false;
|
||||||
|
|
||||||
|
[Combo("Growth Direction", "Down", "Up", spacing = true)]
|
||||||
|
[Order(20)]
|
||||||
|
public EnemyListGrowthDirection GrowthDirection = EnemyListGrowthDirection.Down;
|
||||||
|
|
||||||
|
[DragInt("Vertical Padding", min = 0, max = 500)]
|
||||||
|
[Order(25)]
|
||||||
|
public int VerticalPadding = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[DisableParentSettings("Position", "Anchor", "HideWhenInactive")]
|
||||||
|
[Section("Enemy List", true)]
|
||||||
|
[SubSection("Health Bar", 0)]
|
||||||
|
public class EnemyListHealthBarConfig : BarConfig
|
||||||
|
{
|
||||||
|
[NestedConfig("Name Label", 70)]
|
||||||
|
public EditableLabelConfig NameLabel = new EditableLabelConfig(new Vector2(-5, 12), "[name]", DrawAnchor.TopRight, DrawAnchor.BottomRight);
|
||||||
|
|
||||||
|
[NestedConfig("Health Label", 80)]
|
||||||
|
public EditableLabelConfig HealthLabel = new EditableLabelConfig(new Vector2(30, 0), "[health:percent]%", DrawAnchor.Left, DrawAnchor.Left);
|
||||||
|
|
||||||
|
[NestedConfig("Order Label", 90)]
|
||||||
|
public DefaultFontLabelConfig OrderLabel = new DefaultFontLabelConfig(new Vector2(5, 0), "", DrawAnchor.Left, DrawAnchor.Left);
|
||||||
|
|
||||||
|
[NestedConfig("Colors", 100)]
|
||||||
|
public EnemyListHealthBarColorsConfig Colors = new EnemyListHealthBarColorsConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Change Alpha Based on Range", 110)]
|
||||||
|
public EnemyListRangeConfig RangeConfig = new EnemyListRangeConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Use Smooth Transitions", 120)]
|
||||||
|
public SmoothHealthConfig SmoothHealthConfig = new SmoothHealthConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Custom Mouseover Area", 130)]
|
||||||
|
public MouseoverAreaConfig MouseoverAreaConfig = new MouseoverAreaConfig();
|
||||||
|
|
||||||
|
public new static EnemyListHealthBarConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
Vector2 size = new Vector2(180, 40);
|
||||||
|
|
||||||
|
var config = new EnemyListHealthBarConfig(Vector2.Zero, size, new PluginConfigColor(new(233f / 255f, 4f / 255f, 4f / 255f, 100f / 100f)));
|
||||||
|
config.Colors.ColorByHealth.Enabled = false;
|
||||||
|
|
||||||
|
config.NameLabel.FontID = FontsConfig.DefaultMediumFontKey;
|
||||||
|
config.HealthLabel.FontID = FontsConfig.DefaultMediumFontKey;
|
||||||
|
|
||||||
|
config.MouseoverAreaConfig.Enabled = false;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnemyListHealthBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor, BarDirection fillDirection = BarDirection.Right)
|
||||||
|
: base(position, size, fillColor, fillDirection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Disableable(false)]
|
||||||
|
[Exportable(false)]
|
||||||
|
public class EnemyListHealthBarColorsConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[NestedConfig("Color Based On Health Value", 30, collapsingHeader = false)]
|
||||||
|
public ColorByHealthValueConfig ColorByHealth = new ColorByHealthValueConfig();
|
||||||
|
|
||||||
|
[Checkbox("Highlight When Hovering With Cursor Or Soft Targeting", spacing = true)]
|
||||||
|
[Order(40)]
|
||||||
|
public bool ShowHighlight = true;
|
||||||
|
|
||||||
|
[ColorEdit4("Highlight Color")]
|
||||||
|
[Order(41, collapseWith = nameof(ShowHighlight))]
|
||||||
|
public PluginConfigColor HighlightColor = new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 5f / 100f));
|
||||||
|
|
||||||
|
[Checkbox("Missing Health Color", spacing = true)]
|
||||||
|
[Order(45)]
|
||||||
|
public bool UseMissingHealthBar = false;
|
||||||
|
|
||||||
|
[ColorEdit4("Color" + "##MissingHealth")]
|
||||||
|
[Order(46, collapseWith = nameof(UseMissingHealthBar))]
|
||||||
|
public PluginConfigColor HealthMissingColor = new PluginConfigColor(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Target Border Color", spacing = true)]
|
||||||
|
[Order(50)]
|
||||||
|
public PluginConfigColor TargetBordercolor = new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[DragInt("Target Border Thickness", min = 1, max = 10)]
|
||||||
|
[Order(51)]
|
||||||
|
public int TargetBorderThickness = 1;
|
||||||
|
|
||||||
|
[Checkbox("Show Enmity Border Colors", spacing = true)]
|
||||||
|
[Order(60)]
|
||||||
|
public bool ShowEnmityBorderColors = true;
|
||||||
|
|
||||||
|
[ColorEdit4("Enmity Leader Color")]
|
||||||
|
[Order(61, collapseWith = nameof(ShowEnmityBorderColors))]
|
||||||
|
public PluginConfigColor EnmityLeaderBorderColor = new PluginConfigColor(new Vector4(255f / 255f, 40f / 255f, 40f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Enmity Close To Leader Color")]
|
||||||
|
[Order(62, collapseWith = nameof(ShowEnmityBorderColors))]
|
||||||
|
public PluginConfigColor EnmitySecondBorderColor = new PluginConfigColor(new Vector4(255f / 255f, 175f / 255f, 40f / 255f, 100f / 100f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class EnemyListRangeConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[DragInt("Range (yalms)", min = 1, max = 500)]
|
||||||
|
[Order(5)]
|
||||||
|
public int Range = 30;
|
||||||
|
|
||||||
|
[DragFloat("Alpha", min = 1, max = 100)]
|
||||||
|
[Order(10)]
|
||||||
|
public float Alpha = 25;
|
||||||
|
|
||||||
|
|
||||||
|
public float AlphaForDistance(int distance, float alpha = 100f)
|
||||||
|
{
|
||||||
|
if (!Enabled)
|
||||||
|
{
|
||||||
|
return 100f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return distance > Range ? Alpha : alpha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("FrameAnchor")]
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Enemy List", true)]
|
||||||
|
[SubSection("Enmity Icon", 0)]
|
||||||
|
public class EnemyListEnmityIconConfig : IconConfig
|
||||||
|
{
|
||||||
|
[Anchor("Health Bar Anchor")]
|
||||||
|
[Order(16)]
|
||||||
|
public DrawAnchor HealthBarAnchor = DrawAnchor.TopLeft;
|
||||||
|
|
||||||
|
public new static EnemyListEnmityIconConfig DefaultConfig() =>
|
||||||
|
new EnemyListEnmityIconConfig(new Vector2(5), new Vector2(24), DrawAnchor.Center, DrawAnchor.TopLeft);
|
||||||
|
|
||||||
|
public EnemyListEnmityIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
|
||||||
|
: base(position, size, anchor, frameAnchor)
|
||||||
|
{
|
||||||
|
HealthBarAnchor = frameAnchor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("FrameAnchor")]
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Enemy List", true)]
|
||||||
|
[SubSection("Sign Icon", 0)]
|
||||||
|
public class EnemyListSignIconConfig : SignIconConfig
|
||||||
|
{
|
||||||
|
[Anchor("Health Bar Anchor")]
|
||||||
|
[Order(16)]
|
||||||
|
public DrawAnchor HealthBarAnchor = DrawAnchor.TopLeft;
|
||||||
|
|
||||||
|
[Checkbox("Replace Order Label", help = "When enabled and if the enemy has a sign assigned, the sign icon will be drawn instead of the order label.")]
|
||||||
|
[Order(30)]
|
||||||
|
public bool ReplaceOrderLabel = true;
|
||||||
|
|
||||||
|
public new static EnemyListSignIconConfig DefaultConfig() =>
|
||||||
|
new EnemyListSignIconConfig(new Vector2(0), new Vector2(30), DrawAnchor.Center, DrawAnchor.Left);
|
||||||
|
|
||||||
|
public EnemyListSignIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
|
||||||
|
: base(position, size, anchor, frameAnchor)
|
||||||
|
{
|
||||||
|
HealthBarAnchor = frameAnchor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("AnchorToUnitFrame", "UnitFrameAnchor", "HideWhenInactive", "FillDirection")]
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Enemy List", true)]
|
||||||
|
[SubSection("Castbar", 0)]
|
||||||
|
public class EnemyListCastbarConfig : TargetCastbarConfig
|
||||||
|
{
|
||||||
|
public new static EnemyListCastbarConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = new Vector2(180, 10);
|
||||||
|
|
||||||
|
var castNameConfig = new LabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
|
||||||
|
castNameConfig.FontID = FontsConfig.DefaultMediumFontKey;
|
||||||
|
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
|
||||||
|
castTimeConfig.Enabled = false;
|
||||||
|
castTimeConfig.FontID = FontsConfig.DefaultMediumFontKey;
|
||||||
|
castTimeConfig.NumberFormat = 1;
|
||||||
|
|
||||||
|
var config = new EnemyListCastbarConfig(Vector2.Zero, size, castNameConfig, castTimeConfig);
|
||||||
|
config.HealthBarAnchor = DrawAnchor.Bottom;
|
||||||
|
config.Anchor = DrawAnchor.Bottom;
|
||||||
|
config.ShowIcon = false;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Anchor("Health Bar Anchor")]
|
||||||
|
[Order(16)]
|
||||||
|
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
|
||||||
|
|
||||||
|
public EnemyListCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
|
||||||
|
: base(position, size, castNameConfig, castTimeConfig)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Enemy List", true)]
|
||||||
|
[SubSection("Buffs", 0)]
|
||||||
|
public class EnemyListBuffsConfig : EnemyListStatusEffectsListConfig
|
||||||
|
{
|
||||||
|
public new static EnemyListBuffsConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var durationConfig = new LabelConfig(new Vector2(0, -4), "", DrawAnchor.Bottom, DrawAnchor.Center);
|
||||||
|
var stacksConfig = new LabelConfig(new Vector2(-3, 4), "", DrawAnchor.TopRight, DrawAnchor.Center);
|
||||||
|
stacksConfig.Color = new(Vector4.UnitW);
|
||||||
|
stacksConfig.OutlineColor = new(Vector4.One);
|
||||||
|
|
||||||
|
var iconConfig = new StatusEffectIconConfig(durationConfig, stacksConfig);
|
||||||
|
iconConfig.DispellableBorderConfig.Enabled = false;
|
||||||
|
iconConfig.Size = new Vector2(24, 24);
|
||||||
|
|
||||||
|
var pos = new Vector2(5, 8);
|
||||||
|
var size = new Vector2(iconConfig.Size.X * 4 + 6, iconConfig.Size.Y);
|
||||||
|
|
||||||
|
var config = new EnemyListBuffsConfig(DrawAnchor.TopRight, pos, size, true, false, false, GrowthDirections.Right | GrowthDirections.Down, iconConfig);
|
||||||
|
config.Limit = 4;
|
||||||
|
config.ShowPermanentEffects = true;
|
||||||
|
config.IconConfig.DispellableBorderConfig.Enabled = false;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnemyListBuffsConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||||
|
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||||
|
: base(anchor, position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Enemy List", true)]
|
||||||
|
[SubSection("Debuffs", 0)]
|
||||||
|
public class EnemyListDebuffsConfig : EnemyListStatusEffectsListConfig
|
||||||
|
{
|
||||||
|
public new static EnemyListDebuffsConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var durationConfig = new LabelConfig(new Vector2(0, -4), "", DrawAnchor.Bottom, DrawAnchor.Center);
|
||||||
|
var stacksConfig = new LabelConfig(new Vector2(-3, 4), "", DrawAnchor.TopRight, DrawAnchor.Center);
|
||||||
|
stacksConfig.Color = new(Vector4.UnitW);
|
||||||
|
stacksConfig.OutlineColor = new(Vector4.One);
|
||||||
|
|
||||||
|
var iconConfig = new StatusEffectIconConfig(durationConfig, stacksConfig);
|
||||||
|
iconConfig.Size = new Vector2(24, 24);
|
||||||
|
|
||||||
|
var pos = new Vector2(-5, 8);
|
||||||
|
var size = new Vector2(iconConfig.Size.X * 4 + 6, iconConfig.Size.Y);
|
||||||
|
|
||||||
|
var config = new EnemyListDebuffsConfig(DrawAnchor.TopLeft, pos, size, false, true, false, GrowthDirections.Left | GrowthDirections.Down, iconConfig);
|
||||||
|
config.Limit = 4;
|
||||||
|
config.ShowPermanentEffects = true;
|
||||||
|
config.IconConfig.DispellableBorderConfig.Enabled = false;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnemyListDebuffsConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||||
|
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||||
|
: base(anchor, position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EnemyListStatusEffectsListConfig : StatusEffectsListConfig
|
||||||
|
{
|
||||||
|
[Anchor("Health Bar Anchor")]
|
||||||
|
[Order(4)]
|
||||||
|
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
|
||||||
|
|
||||||
|
public EnemyListStatusEffectsListConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||||
|
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||||
|
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||||
|
{
|
||||||
|
HealthBarAnchor = anchor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using Dalamud.Memory;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Arrays;
|
||||||
|
using StructsFramework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.EnemyList
|
||||||
|
{
|
||||||
|
public unsafe class EnemyListHelper
|
||||||
|
{
|
||||||
|
private List<EnemyListData> _enemiesData = new List<EnemyListData>();
|
||||||
|
public IReadOnlyCollection<EnemyListData> EnemiesData => _enemiesData.AsReadOnly();
|
||||||
|
public int EnemyCount => _enemiesData.Count;
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
_enemiesData.Clear();
|
||||||
|
|
||||||
|
var enemyListNumberInstance = EnemyListNumberArray.Instance();
|
||||||
|
var enemyNumberArrayEnemies = enemyListNumberInstance->Enemies;
|
||||||
|
int enemyCount = *(int*)((byte*)enemyListNumberInstance + 0x04);
|
||||||
|
//TODO: Change it to the correct property when it lands in CS
|
||||||
|
//int enemyCount = enemyListNumberInstance->Unk1;
|
||||||
|
|
||||||
|
if(enemyCount == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < enemyCount; i++)
|
||||||
|
{
|
||||||
|
int entityId = enemyNumberArrayEnemies[i].EntityId;
|
||||||
|
int? letter = GetEnemyLetter(entityId, i);
|
||||||
|
int enmityLevel = GetEnmityLevelForIndex(i);
|
||||||
|
_enemiesData.Add(new EnemyListData(entityId, letter, enmityLevel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int? GetEnemyLetter(int objectId, int index)
|
||||||
|
{
|
||||||
|
var enemyStringArrayMembers = EnemyListStringArray.Instance()->Members;
|
||||||
|
if (enemyStringArrayMembers.IsEmpty || enemyStringArrayMembers.Length <= index)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string name = enemyStringArrayMembers[index].EnemyName;
|
||||||
|
|
||||||
|
bool isMarked = Utils.SignIconIDForObjectID((uint)objectId) != null;
|
||||||
|
char letterSymbol = isMarked && name.Length > 1 ? name[2] : name[0];
|
||||||
|
return letterSymbol - 57457;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetEnmityLevelForIndex(int index)
|
||||||
|
{
|
||||||
|
// gets enmity level by checking texture in enemy list addon
|
||||||
|
|
||||||
|
AtkUnitBase* enemyList = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_EnemyList", 1).Address;
|
||||||
|
if (enemyList == null || enemyList->RootNode == null) { return 0; }
|
||||||
|
|
||||||
|
int id = index == 0 ? 2 : 20000 + index; // makes no sense but it is what it is (blame SE)
|
||||||
|
AtkResNode* node = enemyList->GetNodeById((uint)id);
|
||||||
|
if (node == null || node->GetComponent() == null) { return 0; }
|
||||||
|
|
||||||
|
AtkImageNode* imageNode = (AtkImageNode*)node->GetComponent()->UldManager.SearchNodeById(13);
|
||||||
|
if (imageNode == null) { return 0; }
|
||||||
|
|
||||||
|
return Math.Min(4, imageNode->PartId + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct EnemyListData
|
||||||
|
{
|
||||||
|
public int EntityId;
|
||||||
|
public int? LetterIndex;
|
||||||
|
public int EnmityLevel;
|
||||||
|
|
||||||
|
public EnemyListData(int entityId, int? letterIndex, int enmityLevel)
|
||||||
|
{
|
||||||
|
EntityId = entityId;
|
||||||
|
LetterIndex = letterIndex;
|
||||||
|
EnmityLevel = enmityLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,428 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using HSUI.Interface.StatusEffects;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.EnemyList
|
||||||
|
{
|
||||||
|
public class EnemyListHud : DraggableHudElement, IHudElementWithMouseOver, IHudElementWithPreview
|
||||||
|
{
|
||||||
|
private EnemyListConfig Config => (EnemyListConfig)_config;
|
||||||
|
private EnemyListConfigs Configs;
|
||||||
|
|
||||||
|
private EnemyListHelper _helper = new EnemyListHelper();
|
||||||
|
|
||||||
|
private List<SmoothHPHelper> _smoothHPHelpers = new List<SmoothHPHelper>();
|
||||||
|
|
||||||
|
private const int MaxEnemyCount = 8;
|
||||||
|
private List<float> _previewValues = new List<float>(MaxEnemyCount);
|
||||||
|
|
||||||
|
private bool _wasHovering = false;
|
||||||
|
|
||||||
|
private LabelHud _nameLabelHud;
|
||||||
|
private LabelHud _healthLabelHud;
|
||||||
|
private LabelHud _orderLabelHud;
|
||||||
|
private List<EnemyListCastbarHud> _castbarHud;
|
||||||
|
private StatusEffectsListHud _buffsListHud;
|
||||||
|
private StatusEffectsListHud _debuffsListHud;
|
||||||
|
|
||||||
|
private IDalamudTextureWrap? _iconsTexture => TexturesHelper.GetTextureFromPath("ui/uld/enemylist_hr1.tex");
|
||||||
|
|
||||||
|
public EnemyListHud(EnemyListConfig config, string displayName) : base(config, displayName)
|
||||||
|
{
|
||||||
|
Configs = EnemyListConfigs.GetConfigs();
|
||||||
|
|
||||||
|
config.ValueChangeEvent += OnConfigPropertyChanged;
|
||||||
|
|
||||||
|
_nameLabelHud = new LabelHud(Configs.HealthBar.NameLabel);
|
||||||
|
_healthLabelHud = new LabelHud(Configs.HealthBar.HealthLabel);
|
||||||
|
_orderLabelHud = new LabelHud(Configs.HealthBar.OrderLabel);
|
||||||
|
|
||||||
|
_castbarHud = new List<EnemyListCastbarHud>();
|
||||||
|
_buffsListHud = new StatusEffectsListHud(Configs.Buffs);
|
||||||
|
_debuffsListHud = new StatusEffectsListHud(Configs.Debuffs);
|
||||||
|
|
||||||
|
for (int i = 0; i < MaxEnemyCount; i++)
|
||||||
|
{
|
||||||
|
_smoothHPHelpers.Add(new SmoothHPHelper());
|
||||||
|
_castbarHud.Add(new EnemyListCastbarHud(Configs.CastBar));
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InternalDispose()
|
||||||
|
{
|
||||||
|
_config.ValueChangeEvent -= OnConfigPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigPropertyChanged(object sender, OnChangeBaseArgs args)
|
||||||
|
{
|
||||||
|
if (args.PropertyName == "Preview")
|
||||||
|
{
|
||||||
|
UpdatePreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePreview()
|
||||||
|
{
|
||||||
|
_previewValues.Clear();
|
||||||
|
if (!Config.Preview) { return; }
|
||||||
|
|
||||||
|
Random RNG = new Random((int)ImGui.GetTime());
|
||||||
|
|
||||||
|
for (int i = 0; i < MaxEnemyCount; i++)
|
||||||
|
{
|
||||||
|
_previewValues.Add(RNG.Next(0, 101) / 100f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||||
|
{
|
||||||
|
Vector2 size = new Vector2(Configs.HealthBar.Size.X, MaxEnemyCount * Configs.HealthBar.Size.Y + (MaxEnemyCount - 1) * Config.VerticalPadding);
|
||||||
|
Vector2 pos = Config.GrowthDirection == EnemyListGrowthDirection.Down ? Config.Position : Config.Position - new Vector2(0, size.Y);
|
||||||
|
|
||||||
|
return (new List<Vector2>() { pos + size / 2f }, new List<Vector2>() { size });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopPreview()
|
||||||
|
{
|
||||||
|
Config.Preview = false;
|
||||||
|
|
||||||
|
foreach (EnemyListCastbarHud castbar in _castbarHud)
|
||||||
|
{
|
||||||
|
castbar.StopPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffsListHud.StopPreview();
|
||||||
|
_debuffsListHud.StopPreview();
|
||||||
|
Configs.HealthBar.MouseoverAreaConfig.Preview = false;
|
||||||
|
Configs.SignIcon.Preview = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopMouseover()
|
||||||
|
{
|
||||||
|
if (_wasHovering)
|
||||||
|
{
|
||||||
|
InputsHelper.Instance.ClearTarget();
|
||||||
|
_wasHovering = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawChildren(Vector2 origin)
|
||||||
|
{
|
||||||
|
if (!Config.Enabled) { return; }
|
||||||
|
|
||||||
|
_helper.Update();
|
||||||
|
|
||||||
|
int count = Math.Min(MaxEnemyCount, Config.Preview ? MaxEnemyCount : _helper.EnemyCount);
|
||||||
|
uint fakeMaxHp = 100000;
|
||||||
|
|
||||||
|
ICharacter? mouseoverTarget = null;
|
||||||
|
bool hovered = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
// hp bar
|
||||||
|
ICharacter? character = Config.Preview ? null : Plugin.ObjectTable.SearchById((uint)_helper.EnemiesData.ElementAt(i).EntityId) as ICharacter;
|
||||||
|
|
||||||
|
uint currentHp = Config.Preview ? (uint)(_previewValues[i] * fakeMaxHp) : character?.CurrentHp ?? fakeMaxHp;
|
||||||
|
uint maxHp = Config.Preview ? fakeMaxHp : character?.MaxHp ?? fakeMaxHp;
|
||||||
|
int enmityLevel = Config.Preview ? Math.Max(4, i + 1) : _helper.EnemiesData.ElementAt(i).EnmityLevel;
|
||||||
|
|
||||||
|
if (Configs.HealthBar.SmoothHealthConfig.Enabled)
|
||||||
|
{
|
||||||
|
currentHp = _smoothHPHelpers[i].GetNextHp((int)currentHp, (int)maxHp, Configs.HealthBar.SmoothHealthConfig.Velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
int direction = Config.GrowthDirection == EnemyListGrowthDirection.Down ? 1 : -1;
|
||||||
|
float y = Config.Position.Y + i * direction * Configs.HealthBar.Size.Y + i * direction * Config.VerticalPadding;
|
||||||
|
Vector2 pos = new Vector2(Config.Position.X, y);
|
||||||
|
|
||||||
|
PluginConfigColor fillColor = GetColor(character, currentHp, maxHp);
|
||||||
|
PluginConfigColor bgColor = Configs.HealthBar.BackgroundColor;
|
||||||
|
if (Configs.HealthBar.RangeConfig.Enabled)
|
||||||
|
{
|
||||||
|
fillColor = GetDistanceColor(character, fillColor);
|
||||||
|
bgColor = GetDistanceColor(character, bgColor);
|
||||||
|
}
|
||||||
|
Rect background = new Rect(pos, Configs.HealthBar.Size, bgColor);
|
||||||
|
|
||||||
|
PluginConfigColor borderColor = GetBorderColor(character, enmityLevel);
|
||||||
|
Rect healthFill = BarUtilities.GetFillRect(pos, Configs.HealthBar.Size, Configs.HealthBar.FillDirection, fillColor, currentHp, maxHp);
|
||||||
|
|
||||||
|
BarHud bar = new BarHud(
|
||||||
|
Configs.HealthBar.ID + $"_{i}",
|
||||||
|
Configs.HealthBar.DrawBorder,
|
||||||
|
borderColor,
|
||||||
|
GetBorderThickness(character),
|
||||||
|
DrawAnchor.TopLeft,
|
||||||
|
current: currentHp,
|
||||||
|
max: maxHp,
|
||||||
|
shadowConfig: Configs.HealthBar.ShadowConfig,
|
||||||
|
barTextureName: Configs.HealthBar.BarTextureName,
|
||||||
|
barTextureDrawMode: Configs.HealthBar.BarTextureDrawMode
|
||||||
|
);
|
||||||
|
|
||||||
|
bar.NeedsInputs = true;
|
||||||
|
bar.SetBackground(background);
|
||||||
|
bar.AddForegrounds(healthFill);
|
||||||
|
|
||||||
|
if (Configs.HealthBar.Colors.UseMissingHealthBar)
|
||||||
|
{
|
||||||
|
Vector2 healthMissingSize = Configs.HealthBar.Size - BarUtilities.GetFillDirectionOffset(healthFill.Size, Configs.HealthBar.FillDirection);
|
||||||
|
Vector2 healthMissingPos = Configs.HealthBar.FillDirection.IsInverted() ? pos : pos + BarUtilities.GetFillDirectionOffset(healthFill.Size, Configs.HealthBar.FillDirection);
|
||||||
|
PluginConfigColor? color = Configs.HealthBar.RangeConfig.Enabled ? GetDistanceColor(character, Configs.HealthBar.Colors.HealthMissingColor) : Configs.HealthBar.Colors.HealthMissingColor;
|
||||||
|
bar.AddForegrounds(new Rect(healthMissingPos, healthMissingSize, color));
|
||||||
|
}
|
||||||
|
|
||||||
|
// highlight
|
||||||
|
var (areaStart, areaEnd) = Configs.HealthBar.MouseoverAreaConfig.GetArea(origin + pos, Configs.HealthBar.Size);
|
||||||
|
bool isHovering = character != null && ImGui.IsMouseHoveringRect(areaStart, areaEnd);
|
||||||
|
bool isSoftTarget = character != null && character.EntityId == Plugin.TargetManager.SoftTarget?.EntityId;
|
||||||
|
if (isHovering || isSoftTarget)
|
||||||
|
{
|
||||||
|
if (Configs.HealthBar.Colors.ShowHighlight)
|
||||||
|
{
|
||||||
|
Rect highlight = new Rect(pos, Configs.HealthBar.Size, Configs.HealthBar.Colors.HighlightColor);
|
||||||
|
bar.AddForegrounds(highlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseoverTarget = character;
|
||||||
|
hovered = isHovering;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddDrawActions(bar.GetDrawActions(origin, Configs.HealthBar.StrataLevel));
|
||||||
|
|
||||||
|
// mouseover area
|
||||||
|
BarHud? mouseoverAreaBar = Configs.HealthBar.MouseoverAreaConfig.GetBar(
|
||||||
|
pos,
|
||||||
|
Configs.HealthBar.Size,
|
||||||
|
Configs.HealthBar.ID + "_mouseoverArea"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mouseoverAreaBar != null)
|
||||||
|
{
|
||||||
|
AddDrawActions(mouseoverAreaBar.GetDrawActions(origin, StrataLevel.HIGHEST));
|
||||||
|
}
|
||||||
|
|
||||||
|
// enmity icon
|
||||||
|
if (_iconsTexture != null && Configs.EnmityIcon.Enabled)
|
||||||
|
{
|
||||||
|
var parentPos = Utils.GetAnchoredPosition(origin + pos, -Configs.HealthBar.Size, Configs.EnmityIcon.HealthBarAnchor);
|
||||||
|
var iconPos = Utils.GetAnchoredPosition(parentPos + Configs.EnmityIcon.Position, Configs.EnmityIcon.Size, Configs.EnmityIcon.Anchor);
|
||||||
|
int enmityIndex = Config.Preview ? Math.Min(3, i) : _helper.EnemiesData.ElementAt(i).EnmityLevel - 1;
|
||||||
|
|
||||||
|
AddDrawAction(Configs.EnmityIcon.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
DrawHelper.DrawInWindow(ID + "_enmityIcon", iconPos, Configs.EnmityIcon.Size, false, (drawList) =>
|
||||||
|
{
|
||||||
|
float w = 48f / _iconsTexture.Width;
|
||||||
|
float h = 48f / _iconsTexture.Height;
|
||||||
|
Vector2 uv0 = new Vector2(w * enmityIndex, 0.48f);
|
||||||
|
Vector2 uv1 = new Vector2(w * (enmityIndex + 1), 0.48f + h);
|
||||||
|
drawList.AddImage(_iconsTexture.Handle, iconPos, iconPos + Configs.EnmityIcon.Size, uv0, uv1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// sign icon
|
||||||
|
uint? signIconId = null;
|
||||||
|
if (Configs.SignIcon.Enabled)
|
||||||
|
{
|
||||||
|
signIconId = Configs.SignIcon.IconID(character);
|
||||||
|
if (signIconId.HasValue)
|
||||||
|
{
|
||||||
|
var parentPos = Utils.GetAnchoredPosition(origin + pos, -Configs.HealthBar.Size, Configs.SignIcon.HealthBarAnchor);
|
||||||
|
var iconPos = Utils.GetAnchoredPosition(parentPos + Configs.SignIcon.Position, Configs.SignIcon.Size, Configs.SignIcon.Anchor);
|
||||||
|
|
||||||
|
AddDrawAction(Configs.SignIcon.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
DrawHelper.DrawInWindow(ID + "_signIcon", iconPos, Configs.SignIcon.Size, false, (drawList) =>
|
||||||
|
{
|
||||||
|
DrawHelper.DrawIcon(signIconId.Value, iconPos, Configs.SignIcon.Size, false, drawList);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// labels
|
||||||
|
string? name = Config.Preview ? "Fake Name" : null;
|
||||||
|
AddDrawAction(Configs.HealthBar.NameLabel.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
_nameLabelHud.Draw(origin + pos, Configs.HealthBar.Size, character, name, currentHp, maxHp);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddDrawAction(Configs.HealthBar.HealthLabel.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
_healthLabelHud.Draw(origin + pos, Configs.HealthBar.Size, character, name, currentHp, maxHp);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!signIconId.HasValue || !Configs.SignIcon.ReplaceOrderLabel)
|
||||||
|
{
|
||||||
|
int letter = i;
|
||||||
|
if (!Config.Preview && _helper.EnemiesData.ElementAt(i).LetterIndex.HasValue)
|
||||||
|
{
|
||||||
|
letter = _helper.EnemiesData.ElementAt(i).LetterIndex!.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
string str = char.ConvertFromUtf32(0xE071 + letter).ToString();
|
||||||
|
AddDrawAction(Configs.HealthBar.OrderLabel.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
Configs.HealthBar.OrderLabel.SetText(str);
|
||||||
|
_orderLabelHud.Draw(origin + pos, Configs.HealthBar.Size);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// buffs / debuffs
|
||||||
|
var buffsPos = Utils.GetAnchoredPosition(origin + pos, -Configs.HealthBar.Size, Configs.Buffs.HealthBarAnchor);
|
||||||
|
AddDrawAction(Configs.Buffs.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
_buffsListHud.Actor = character;
|
||||||
|
_buffsListHud.PrepareForDraw(buffsPos);
|
||||||
|
_buffsListHud.Draw(buffsPos);
|
||||||
|
});
|
||||||
|
|
||||||
|
var debuffsPos = Utils.GetAnchoredPosition(origin + pos, -Configs.HealthBar.Size, Configs.Debuffs.HealthBarAnchor);
|
||||||
|
AddDrawAction(Configs.Debuffs.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
_debuffsListHud.Actor = character;
|
||||||
|
_debuffsListHud.PrepareForDraw(debuffsPos);
|
||||||
|
_debuffsListHud.Draw(debuffsPos);
|
||||||
|
});
|
||||||
|
|
||||||
|
// castbar
|
||||||
|
EnemyListCastbarHud castbar = _castbarHud[i];
|
||||||
|
castbar.EnemyListIndex = i;
|
||||||
|
|
||||||
|
var castbarPos = Utils.GetAnchoredPosition(origin + pos, -Configs.HealthBar.Size, Configs.CastBar.HealthBarAnchor);
|
||||||
|
AddDrawAction(Configs.CastBar.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
castbar.Actor = character;
|
||||||
|
castbar.PrepareForDraw(castbarPos);
|
||||||
|
castbar.Draw(castbarPos);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// mouseover
|
||||||
|
bool ignoreMouseover = Configs.HealthBar.MouseoverAreaConfig.Enabled && Configs.HealthBar.MouseoverAreaConfig.Ignore;
|
||||||
|
if (hovered && mouseoverTarget != null)
|
||||||
|
{
|
||||||
|
_wasHovering = true;
|
||||||
|
InputsHelper.Instance.SetTarget(mouseoverTarget, ignoreMouseover);
|
||||||
|
|
||||||
|
// left click
|
||||||
|
if (InputsHelper.Instance.LeftButtonClicked)
|
||||||
|
{
|
||||||
|
Plugin.TargetManager.Target = mouseoverTarget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_wasHovering)
|
||||||
|
{
|
||||||
|
InputsHelper.Instance.ClearTarget();
|
||||||
|
_wasHovering = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PluginConfigColor GetColor(ICharacter? character, uint currentHp = 0, uint maxHp = 0)
|
||||||
|
{
|
||||||
|
if (Configs.HealthBar.Colors.ColorByHealth.Enabled && (character != null || Config.Preview))
|
||||||
|
{
|
||||||
|
var scale = (float)currentHp / Math.Max(1, maxHp);
|
||||||
|
return ColorUtils.GetColorByScale(scale, Configs.HealthBar.Colors.ColorByHealth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Configs.HealthBar.FillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PluginConfigColor GetBorderColor(ICharacter? character, int enmityLevel)
|
||||||
|
{
|
||||||
|
IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
|
||||||
|
if (character != null && target != null && character.EntityId == target.EntityId)
|
||||||
|
{
|
||||||
|
return Configs.HealthBar.Colors.TargetBordercolor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Configs.HealthBar.Colors.ShowEnmityBorderColors)
|
||||||
|
{
|
||||||
|
return Configs.HealthBar.BorderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return enmityLevel switch
|
||||||
|
{
|
||||||
|
>= 3 => Configs.HealthBar.Colors.EnmityLeaderBorderColor,
|
||||||
|
>= 1 => Configs.HealthBar.Colors.EnmitySecondBorderColor,
|
||||||
|
_ => Configs.HealthBar.BorderColor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetBorderThickness(ICharacter? character)
|
||||||
|
{
|
||||||
|
IGameObject? target = Plugin.TargetManager.Target;
|
||||||
|
if (character != null && character == target)
|
||||||
|
{
|
||||||
|
return Configs.HealthBar.Colors.TargetBorderThickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Configs.HealthBar.BorderThickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PluginConfigColor GetDistanceColor(ICharacter? character, PluginConfigColor color)
|
||||||
|
{
|
||||||
|
byte distance = character != null ? character.YalmDistanceX : byte.MaxValue;
|
||||||
|
float currentAlpha = color.Vector.W * 100f;
|
||||||
|
float alpha = Configs.HealthBar.RangeConfig.AlphaForDistance(distance, currentAlpha) / 100f;
|
||||||
|
|
||||||
|
return color.WithAlpha(alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region utils
|
||||||
|
public struct EnemyListConfigs
|
||||||
|
{
|
||||||
|
public EnemyListHealthBarConfig HealthBar;
|
||||||
|
public EnemyListEnmityIconConfig EnmityIcon;
|
||||||
|
public EnemyListSignIconConfig SignIcon;
|
||||||
|
public EnemyListCastbarConfig CastBar;
|
||||||
|
public EnemyListBuffsConfig Buffs;
|
||||||
|
public EnemyListDebuffsConfig Debuffs;
|
||||||
|
|
||||||
|
public EnemyListConfigs(
|
||||||
|
EnemyListHealthBarConfig healthBar,
|
||||||
|
EnemyListEnmityIconConfig enmityIcon,
|
||||||
|
EnemyListSignIconConfig signIcon,
|
||||||
|
EnemyListCastbarConfig castBar,
|
||||||
|
EnemyListBuffsConfig buffs,
|
||||||
|
EnemyListDebuffsConfig debuffs)
|
||||||
|
{
|
||||||
|
HealthBar = healthBar;
|
||||||
|
EnmityIcon = enmityIcon;
|
||||||
|
SignIcon = signIcon;
|
||||||
|
CastBar = castBar;
|
||||||
|
Buffs = buffs;
|
||||||
|
Debuffs = debuffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EnemyListConfigs GetConfigs()
|
||||||
|
{
|
||||||
|
return new EnemyListConfigs(
|
||||||
|
ConfigurationManager.Instance.GetConfigObject<EnemyListHealthBarConfig>(),
|
||||||
|
ConfigurationManager.Instance.GetConfigObject<EnemyListEnmityIconConfig>(),
|
||||||
|
ConfigurationManager.Instance.GetConfigObject<EnemyListSignIconConfig>(),
|
||||||
|
ConfigurationManager.Instance.GetConfigObject<EnemyListCastbarConfig>(),
|
||||||
|
ConfigurationManager.Instance.GetConfigObject<EnemyListBuffsConfig>(),
|
||||||
|
ConfigurationManager.Instance.GetConfigObject<EnemyListDebuffsConfig>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,159 @@
|
|||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.ImGuiFileDialog;
|
||||||
|
using Dalamud.Memory.Exceptions;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[Disableable(false)]
|
||||||
|
[Section("Customization")]
|
||||||
|
[SubSection("Bar Textures", 0)]
|
||||||
|
public class BarTexturesConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static BarTexturesConfig DefaultConfig() { return new BarTexturesConfig(); }
|
||||||
|
|
||||||
|
public string BarTexturesPath = "C:\\";
|
||||||
|
|
||||||
|
[JsonIgnore] public string ValidatedBarTexturesPath => ValidatePath(BarTexturesPath);
|
||||||
|
|
||||||
|
[JsonIgnore] private int _inputBarTexture = 0;
|
||||||
|
[JsonIgnore] private int _drawModeIndex = 0;
|
||||||
|
[JsonIgnore] private Vector4 _color = new Vector4(229 / 255f, 57 / 255f, 57 / 255f, 1);
|
||||||
|
[JsonIgnore] private PluginConfigColor _pluginConfigColor = PluginConfigColor.FromHex(0xFFE53939);
|
||||||
|
[JsonIgnore] private FileDialogManager _fileDialogManager = new FileDialogManager();
|
||||||
|
[JsonIgnore] private bool _applying = false;
|
||||||
|
|
||||||
|
private string ValidatePath(string path)
|
||||||
|
{
|
||||||
|
if (path.EndsWith("\\") || path.EndsWith("/"))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path + "\\";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectFolder()
|
||||||
|
{
|
||||||
|
Action<bool, string> callback = (finished, path) =>
|
||||||
|
{
|
||||||
|
if (finished && path.Length > 0)
|
||||||
|
{
|
||||||
|
BarTexturesPath = path;
|
||||||
|
BarTexturesManager.Instance?.ReloadTextures();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_fileDialogManager.OpenFolderDialog("Select Bar Textures Folder", callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ManualDraw]
|
||||||
|
public bool Draw(ref bool changed)
|
||||||
|
{
|
||||||
|
if (BarTexturesManager.Instance == null) { return false; }
|
||||||
|
|
||||||
|
string[] textureNames = BarTexturesManager.Instance.BarTextureNames.ToArray();
|
||||||
|
string[] drawModes = new string[] { "Stretch", "Repeat Horizontal", "Repeat Vertical", "Repeat" };
|
||||||
|
|
||||||
|
if (ImGui.BeginChild("Bar Textures", new Vector2(800, 400), false, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
|
||||||
|
{
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
ImGui.Text("Custom Bar Textures path");
|
||||||
|
|
||||||
|
ImGuiHelper.Tab();
|
||||||
|
if (ImGui.InputText("", ref BarTexturesPath, 200, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
BarTexturesManager.Instance?.ReloadTextures();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Folder.ToIconString(), new Vector2(0, 0)))
|
||||||
|
{
|
||||||
|
SelectFolder();
|
||||||
|
}
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
ImGui.Text("Preview");
|
||||||
|
ImGuiHelper.Tab();
|
||||||
|
ImGui.Combo("Bar Texture ##bar texture", ref _inputBarTexture, textureNames);
|
||||||
|
|
||||||
|
ImGuiHelper.Tab();
|
||||||
|
ImGui.Combo("Draw Mode", ref _drawModeIndex, drawModes);
|
||||||
|
|
||||||
|
ImGuiHelper.Tab();
|
||||||
|
if (ImGui.ColorEdit4("Color", ref _color))
|
||||||
|
{
|
||||||
|
_pluginConfigColor = new PluginConfigColor(_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textureNames.Length > _inputBarTexture)
|
||||||
|
{
|
||||||
|
// draw preview
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
Vector2 pos = ImGui.GetWindowPos() + ImGui.GetCursorPos();
|
||||||
|
Vector2 size = new Vector2(512, 64);
|
||||||
|
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
|
||||||
|
|
||||||
|
DrawHelper.DrawBarTexture(
|
||||||
|
pos,
|
||||||
|
size,
|
||||||
|
_pluginConfigColor,
|
||||||
|
textureNames[_inputBarTexture],
|
||||||
|
(BarTextureDrawMode)_drawModeIndex,
|
||||||
|
drawList
|
||||||
|
);
|
||||||
|
|
||||||
|
ImGuiHelper.DrawSpacing(3);
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
if (ImGui.Button("Apply to all bars", new Vector2(200, 30)))
|
||||||
|
{
|
||||||
|
_applying = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
_fileDialogManager.Draw();
|
||||||
|
|
||||||
|
if (_applying)
|
||||||
|
{
|
||||||
|
string[] lines = new string[] { "This will replace the Bar Texture", "and Draw Mode for ALL bars!", "THIS CAN'T BE UNDONE!", "Are you sure?" };
|
||||||
|
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Apply to ALL bars?", lines);
|
||||||
|
|
||||||
|
if (didConfirm)
|
||||||
|
{
|
||||||
|
List<BarConfig> barConfigs = ConfigurationManager.Instance.GetObjects<BarConfig>();
|
||||||
|
foreach (BarConfig barConfig in barConfigs)
|
||||||
|
{
|
||||||
|
barConfig.BarTextureName = textureNames[_inputBarTexture];
|
||||||
|
barConfig.BarTextureDrawMode = (BarTextureDrawMode)_drawModeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didConfirm || didClose)
|
||||||
|
{
|
||||||
|
_applying = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[Section("Castbars")]
|
||||||
|
[SubSection("Player", 0)]
|
||||||
|
public class PlayerCastbarConfig : UnitFrameCastbarConfig
|
||||||
|
{
|
||||||
|
[Checkbox("Use Job Color", spacing = true)]
|
||||||
|
[Order(19)]
|
||||||
|
public bool UseJobColor = false;
|
||||||
|
|
||||||
|
[Checkbox("Slide Cast", spacing = true)]
|
||||||
|
[Order(60)]
|
||||||
|
public bool ShowSlideCast = true;
|
||||||
|
|
||||||
|
[DragInt("Time (milliseconds)", min = 0, max = 10000)]
|
||||||
|
[Order(61, collapseWith = nameof(ShowSlideCast))]
|
||||||
|
public int SlideCastTime = 500;
|
||||||
|
|
||||||
|
[ColorEdit4("Color ##SlidecastColor")]
|
||||||
|
[Order(62, collapseWith = nameof(ShowSlideCast))]
|
||||||
|
public PluginConfigColor SlideCastColor = new PluginConfigColor(new(190f / 255f, 28f / 255f, 57f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
public PlayerCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
|
||||||
|
: base(position, size, castNameConfig, castTimeConfig)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static PlayerCastbarConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = new Vector2(254, 24);
|
||||||
|
var pos = new Vector2(0, HUDConstants.PlayerCastbarY);
|
||||||
|
|
||||||
|
var castNameConfig = new LabelConfig(new Vector2(5, 0), "", DrawAnchor.Left, DrawAnchor.Left);
|
||||||
|
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
|
||||||
|
castTimeConfig.NumberFormat = 1;
|
||||||
|
|
||||||
|
return new PlayerCastbarConfig(pos, size, castNameConfig, castTimeConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Section("Castbars")]
|
||||||
|
[SubSection("Target", 0)]
|
||||||
|
public class TargetCastbarConfig : UnitFrameCastbarConfig
|
||||||
|
{
|
||||||
|
[Checkbox("Interruptable Color", spacing = true)]
|
||||||
|
[Order(50)]
|
||||||
|
public bool ShowInterruptableColor = true;
|
||||||
|
|
||||||
|
[ColorEdit4("Interruptable")]
|
||||||
|
[Order(51, collapseWith = nameof(ShowInterruptableColor))]
|
||||||
|
public PluginConfigColor InterruptableColor = new PluginConfigColor(new(255f / 255f, 87f / 255f, 113f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[Checkbox("Damage Type Colors", spacing = true)]
|
||||||
|
[Order(60)]
|
||||||
|
public bool UseColorForDamageTypes = true;
|
||||||
|
|
||||||
|
[ColorEdit4("Physical")]
|
||||||
|
[Order(61, collapseWith = nameof(UseColorForDamageTypes))]
|
||||||
|
public PluginConfigColor PhysicalDamageColor = new PluginConfigColor(new(190f / 255f, 28f / 255f, 57f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Magical")]
|
||||||
|
[Order(62, collapseWith = nameof(UseColorForDamageTypes))]
|
||||||
|
public PluginConfigColor MagicalDamageColor = new PluginConfigColor(new(0f / 255f, 72f / 255f, 179f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Darkness")]
|
||||||
|
[Order(63, collapseWith = nameof(UseColorForDamageTypes))]
|
||||||
|
public PluginConfigColor DarknessDamageColor = new PluginConfigColor(new(188f / 255f, 19f / 255f, 254f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
public TargetCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
|
||||||
|
: base(position, size, castNameConfig, castTimeConfig)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public new static TargetCastbarConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = new Vector2(254, 24);
|
||||||
|
var pos = new Vector2(0, HUDConstants.BaseHUDOffsetY / 2f - size.Y / 2);
|
||||||
|
|
||||||
|
var castNameConfig = new LabelConfig(new Vector2(5, 0), "", DrawAnchor.Left, DrawAnchor.Left);
|
||||||
|
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
|
||||||
|
castTimeConfig.NumberFormat = 1;
|
||||||
|
|
||||||
|
return new TargetCastbarConfig(pos, size, castNameConfig, castTimeConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Section("Castbars")]
|
||||||
|
[SubSection("Target of Target", 0)]
|
||||||
|
public class TargetOfTargetCastbarConfig : TargetCastbarConfig
|
||||||
|
{
|
||||||
|
public TargetOfTargetCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
|
||||||
|
: base(position, size, castNameConfig, castTimeConfig)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public new static TargetOfTargetCastbarConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = new Vector2(120, 24);
|
||||||
|
var pos = new Vector2(0, -1);
|
||||||
|
|
||||||
|
var castNameConfig = new LabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
|
||||||
|
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
|
||||||
|
castTimeConfig.Enabled = false;
|
||||||
|
castTimeConfig.NumberFormat = 1;
|
||||||
|
|
||||||
|
var config = new TargetOfTargetCastbarConfig(pos, size, castNameConfig, castTimeConfig);
|
||||||
|
config.Anchor = DrawAnchor.Top;
|
||||||
|
config.AnchorToUnitFrame = true;
|
||||||
|
config.ShowIcon = false;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Section("Castbars")]
|
||||||
|
[SubSection("Focus Target", 0)]
|
||||||
|
public class FocusTargetCastbarConfig : TargetCastbarConfig
|
||||||
|
{
|
||||||
|
public FocusTargetCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
|
||||||
|
: base(position, size, castNameConfig, castTimeConfig)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public new static FocusTargetCastbarConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = new Vector2(120, 24);
|
||||||
|
var pos = new Vector2(0, -1);
|
||||||
|
|
||||||
|
var castNameConfig = new LabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
|
||||||
|
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
|
||||||
|
castTimeConfig.Enabled = false;
|
||||||
|
castTimeConfig.NumberFormat = 1;
|
||||||
|
|
||||||
|
var config = new FocusTargetCastbarConfig(pos, size, castNameConfig, castTimeConfig);
|
||||||
|
config.Anchor = DrawAnchor.Top;
|
||||||
|
config.AnchorToUnitFrame = true;
|
||||||
|
config.ShowIcon = false;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class UnitFrameCastbarConfig : CastbarConfig
|
||||||
|
{
|
||||||
|
[Checkbox("Anchor to Unit Frame")]
|
||||||
|
[Order(16)]
|
||||||
|
public bool AnchorToUnitFrame = false;
|
||||||
|
|
||||||
|
[Anchor("Unit Frame Anchor")]
|
||||||
|
[Order(17, collapseWith = nameof(AnchorToUnitFrame))]
|
||||||
|
public DrawAnchor UnitFrameAnchor = DrawAnchor.Bottom;
|
||||||
|
|
||||||
|
public UnitFrameCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
|
||||||
|
: base(position, size, castNameConfig, castTimeConfig)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("HideWhenInactive")]
|
||||||
|
public abstract class CastbarConfig : BarConfig
|
||||||
|
{
|
||||||
|
[Checkbox("Preview")]
|
||||||
|
[Order(3)]
|
||||||
|
public bool Preview = false;
|
||||||
|
|
||||||
|
[Checkbox("Show Ability Icon")]
|
||||||
|
[Order(4)]
|
||||||
|
public bool ShowIcon = true;
|
||||||
|
|
||||||
|
[Checkbox("Reverse Fill Background Color")]
|
||||||
|
[Order(5)]
|
||||||
|
public bool UseReverseFill = false;
|
||||||
|
|
||||||
|
[Checkbox("Show Current Cast Time + Max Cast Time")]
|
||||||
|
[Order(6)]
|
||||||
|
public bool ShowMaxCastTime = false;
|
||||||
|
|
||||||
|
[Checkbox("Truncate Cast Name", help = "This will automatically truncate the cast name if it's too long and won't fit inside the bar.")]
|
||||||
|
[Order(7)]
|
||||||
|
public bool TruncateCastName = false;
|
||||||
|
|
||||||
|
[Checkbox("Separate Icon", spacing = true)]
|
||||||
|
[Order(100)]
|
||||||
|
public bool SeparateIcon = false;
|
||||||
|
|
||||||
|
[DragInt2("Custom Icon Position", min = -500, max = 500)]
|
||||||
|
[Order(101, collapseWith = nameof(SeparateIcon))]
|
||||||
|
public Vector2 CustomIconPosition = Vector2.Zero;
|
||||||
|
|
||||||
|
[DragInt2("Custom Icon Size", min = 1, max = 500)]
|
||||||
|
[Order(101, collapseWith = nameof(SeparateIcon))]
|
||||||
|
public Vector2 CustomIconSize = new Vector2(40);
|
||||||
|
|
||||||
|
[NestedConfig("Cast Name", 500)]
|
||||||
|
public LabelConfig CastNameLabel;
|
||||||
|
|
||||||
|
[NestedConfig("Cast Time", 505)]
|
||||||
|
public NumericLabelConfig CastTimeLabel;
|
||||||
|
|
||||||
|
[ColorEdit4("Color" + "##ReverseFill")]
|
||||||
|
[Order(515, collapseWith = nameof(UseReverseFill))]
|
||||||
|
public PluginConfigColor ReverseFillColor = new(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
public CastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
|
||||||
|
: base(position, size, new PluginConfigColor(new(0f / 255f, 162f / 255f, 252f / 255f, 100f / 100f)), BarDirection.Right)
|
||||||
|
{
|
||||||
|
CastNameLabel = castNameConfig;
|
||||||
|
CastTimeLabel = castTimeConfig;
|
||||||
|
|
||||||
|
Strata = StrataLevel.MID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CastbarConfigConverter : PluginConfigObjectConverter
|
||||||
|
{
|
||||||
|
public CastbarConfigConverter()
|
||||||
|
{
|
||||||
|
SameClassFieldConverter<LabelConfig> name = new SameClassFieldConverter<LabelConfig>(
|
||||||
|
"CastNameLabel",
|
||||||
|
new LabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center)
|
||||||
|
);
|
||||||
|
|
||||||
|
NewClassFieldConverter<LabelConfig, NumericLabelConfig> time = new NewClassFieldConverter<LabelConfig, NumericLabelConfig>(
|
||||||
|
"CastTimeLabel",
|
||||||
|
new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right),
|
||||||
|
(oldValue) =>
|
||||||
|
{
|
||||||
|
NumericLabelConfig label = new NumericLabelConfig(oldValue.Position, "", oldValue.FrameAnchor, oldValue.TextAnchor);
|
||||||
|
label.Enabled = oldValue.Enabled;
|
||||||
|
label.FontID = oldValue.FontID;
|
||||||
|
label.NumberFormat = 1;
|
||||||
|
label.Color = oldValue.Color;
|
||||||
|
label.OutlineColor = oldValue.OutlineColor;
|
||||||
|
label.ShadowConfig = oldValue.ShadowConfig;
|
||||||
|
label.UseJobColor = oldValue.UseJobColor;
|
||||||
|
|
||||||
|
return label;
|
||||||
|
});
|
||||||
|
|
||||||
|
FieldConvertersMap.Add("CastNameConfig", name);
|
||||||
|
FieldConvertersMap.Add("CastTimeConfig", time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return objectType == typeof(CastbarConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,509 @@
|
|||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using HSUI.Interface.EnemyList;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using LuminaAction = Lumina.Excel.Sheets.Action;
|
||||||
|
using StructsBattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public class CastbarHud : ParentAnchoredDraggableHudElement, IHudElementWithActor, IHudElementWithAnchorableParent, IHudElementWithPreview
|
||||||
|
{
|
||||||
|
private CastbarConfig Config => (CastbarConfig)_config;
|
||||||
|
private readonly LabelHud _castNameLabel;
|
||||||
|
private readonly LabelHud _castTimeLabel;
|
||||||
|
|
||||||
|
protected LastUsedCast? LastUsedCast;
|
||||||
|
|
||||||
|
public IGameObject? Actor { get; set; }
|
||||||
|
|
||||||
|
protected override bool AnchorToParent => Config is UnitFrameCastbarConfig { AnchorToUnitFrame: true };
|
||||||
|
protected override DrawAnchor ParentAnchor => Config is UnitFrameCastbarConfig config ? config.UnitFrameAnchor : DrawAnchor.Center;
|
||||||
|
|
||||||
|
public CastbarHud(CastbarConfig config, string? displayName = null) : base(config, displayName)
|
||||||
|
{
|
||||||
|
_castNameLabel = new LabelHud(config.CastNameLabel);
|
||||||
|
_castTimeLabel = new LabelHud(config.CastTimeLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopPreview()
|
||||||
|
{
|
||||||
|
Config.Preview = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes() => (new List<Vector2> { Config.Position }, new List<Vector2> { Config.Size });
|
||||||
|
|
||||||
|
public override void DrawChildren(Vector2 origin)
|
||||||
|
{
|
||||||
|
if (!Config.Enabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.Preview &&
|
||||||
|
(Actor == null || Actor is not ICharacter || Actor.ObjectKind != ObjectKind.Player && Actor.ObjectKind != ObjectKind.BattleNpc))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateCurrentCast(out float currentCastTime, out float totalCastTime);
|
||||||
|
if (totalCastTime == 0 || currentCastTime >= totalCastTime)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ShouldShow() && !Config.Preview)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 size = GetSize();
|
||||||
|
IDalamudTextureWrap? iconTexture = LastUsedCast?.GetIconTexture();
|
||||||
|
bool validIcon = Config.Preview ? true : iconTexture is not null;
|
||||||
|
Vector2 iconSize = Config.ShowIcon && validIcon && !Config.SeparateIcon ? new Vector2(size.Y, size.Y) : Vector2.Zero;
|
||||||
|
|
||||||
|
PluginConfigColor fillColor = GetColor();
|
||||||
|
Rect background = new(Config.Position, size, Config.BackgroundColor);
|
||||||
|
Rect progress = BarUtilities.GetFillRect(Config.Position, size, Config.FillDirection, fillColor, currentCastTime, totalCastTime);
|
||||||
|
|
||||||
|
BarHud bar = new(Config, Actor);/**/
|
||||||
|
bar.SetBackground(background);
|
||||||
|
|
||||||
|
if (Config.UseReverseFill)
|
||||||
|
{
|
||||||
|
Vector2 reverseFillSize = size - BarUtilities.GetFillDirectionOffset(progress.Size, Config.FillDirection);
|
||||||
|
Vector2 reverseFillPos = Config.FillDirection.IsInverted()
|
||||||
|
? Config.Position
|
||||||
|
: Config.Position + BarUtilities.GetFillDirectionOffset(progress.Size, Config.FillDirection);
|
||||||
|
|
||||||
|
PluginConfigColor reverseFillColor = Config.ReverseFillColor;
|
||||||
|
bar.AddForegrounds(new Rect(reverseFillPos, reverseFillSize, reverseFillColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
AddExtras(bar, totalCastTime, iconTexture);
|
||||||
|
|
||||||
|
bar.AddForegrounds(progress);
|
||||||
|
|
||||||
|
Vector2 pos = origin + ParentPos();
|
||||||
|
AddDrawActions(bar.GetDrawActions(pos, Config.StrataLevel));
|
||||||
|
|
||||||
|
// icon
|
||||||
|
Vector2 startPos = Config.Position + Utils.GetAnchoredPosition(pos, size, Config.Anchor);
|
||||||
|
if (Config.ShowIcon && validIcon)
|
||||||
|
{
|
||||||
|
Vector2 finalIconPos = Config.SeparateIcon ? startPos + Config.CustomIconPosition : startPos;
|
||||||
|
Vector2 finalIconSize = Config.SeparateIcon ? Config.CustomIconSize : iconSize;
|
||||||
|
|
||||||
|
AddDrawAction(Config.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
DrawHelper.DrawInWindow(ID + "_icon", finalIconPos, finalIconSize, false, (drawList) =>
|
||||||
|
{
|
||||||
|
ImGui.SetCursorPos(finalIconPos);
|
||||||
|
|
||||||
|
IDalamudTextureWrap? texture = Config.Preview ? TexturesHelper.GetTexture<LuminaAction>(3577) : iconTexture;
|
||||||
|
if (texture != null)
|
||||||
|
{
|
||||||
|
ImGui.Image(texture.Handle, finalIconSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.DrawBorder)
|
||||||
|
{
|
||||||
|
drawList.AddRect(finalIconPos, finalIconPos + finalIconSize, Config.BorderColor.Base, 0, ImDrawFlags.None, Config.BorderThickness);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// cast time
|
||||||
|
bool isTimeLeftAnchored = Config.CastTimeLabel.TextAnchor is DrawAnchor.Left or DrawAnchor.TopLeft or DrawAnchor.BottomLeft;
|
||||||
|
Vector2 timePos = Config.ShowIcon && isTimeLeftAnchored ? startPos + new Vector2(iconSize.X, 0) : startPos;
|
||||||
|
float value = Config.Preview ? 0.5f : totalCastTime - currentCastTime;
|
||||||
|
|
||||||
|
if (Config.ShowMaxCastTime)
|
||||||
|
{
|
||||||
|
string format = Config.CastTimeLabel.NumberFormat.ToString();
|
||||||
|
Config.CastTimeLabel.SetText(
|
||||||
|
value.ToString("N" + format, ConfigurationManager.Instance.ActiveCultreInfo) +
|
||||||
|
" / " +
|
||||||
|
totalCastTime.ToString("N" + format, ConfigurationManager.Instance.ActiveCultreInfo)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Config.CastTimeLabel.SetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddDrawAction(Config.CastTimeLabel.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
_castTimeLabel.Draw(timePos, size, Actor);
|
||||||
|
});
|
||||||
|
|
||||||
|
// cast name
|
||||||
|
bool isNameLeftAnchored = Config.CastNameLabel.TextAnchor is DrawAnchor.Left or DrawAnchor.TopLeft or DrawAnchor.BottomLeft;
|
||||||
|
Vector2 namePos = Config.ShowIcon && isNameLeftAnchored ? startPos + new Vector2(iconSize.X, 0) : startPos;
|
||||||
|
|
||||||
|
string original = CustomCastName() ?? (LastUsedCast?.ActionText ?? "");
|
||||||
|
string castName = EncryptedStringsHelper.GetString(original).CheckForUpperCase() ?? original;
|
||||||
|
|
||||||
|
if (Config.TruncateCastName)
|
||||||
|
{
|
||||||
|
castName = TruncatedCastName(castName) ?? castName;
|
||||||
|
}
|
||||||
|
|
||||||
|
Config.CastNameLabel.SetText(Config.Preview ? "Cast Name" : castName);
|
||||||
|
|
||||||
|
AddDrawAction(Config.CastNameLabel.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
_castNameLabel.Draw(namePos, size, Actor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void UpdateCurrentCast(out float currentCastTime, out float totalCastTime)
|
||||||
|
{
|
||||||
|
if (Config.Preview || Actor is not IBattleChara battleChara)
|
||||||
|
{
|
||||||
|
currentCastTime = Config.Preview ? 0.5f : 0f;
|
||||||
|
totalCastTime = 1f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float current = 0;
|
||||||
|
float total = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
current = battleChara.CurrentCastTime;
|
||||||
|
StructsBattleChara* chara = (StructsBattleChara*)battleChara.Address;
|
||||||
|
CastInfo* castInfo = chara->GetCastInfo();
|
||||||
|
|
||||||
|
if (castInfo != null)
|
||||||
|
{
|
||||||
|
total = castInfo->TotalCastTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
currentCastTime = 0;
|
||||||
|
totalCastTime = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Utils.IsActorCasting(battleChara) && current <= 0)
|
||||||
|
{
|
||||||
|
currentCastTime = 0;
|
||||||
|
totalCastTime = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCastTime = current;
|
||||||
|
totalCastTime = total;
|
||||||
|
|
||||||
|
uint currentCastId = battleChara.CastActionId;
|
||||||
|
ActionType currentCastType = (ActionType)battleChara.CastActionType;
|
||||||
|
|
||||||
|
if (LastUsedCast == null || LastUsedCast.CastId != currentCastId || LastUsedCast.ActionType != currentCastType)
|
||||||
|
{
|
||||||
|
LastUsedCast = new LastUsedCast(currentCastId, currentCastType, battleChara.IsCastInterruptible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? TruncatedCastName(string text)
|
||||||
|
{
|
||||||
|
if (text.Length <= 5)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelConfig castNamelabel = Config.CastNameLabel;
|
||||||
|
LabelConfig castTimeLabel = Config.CastTimeLabel;
|
||||||
|
|
||||||
|
|
||||||
|
Vector2 size;
|
||||||
|
|
||||||
|
using (FontsManager.Instance.PushFont(castNamelabel.FontID))
|
||||||
|
{
|
||||||
|
size = ImGui.CalcTextSize(text) * castNamelabel.GetFontScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
float maxWidth = Config.Size.X;
|
||||||
|
|
||||||
|
if (!Config.SeparateIcon)
|
||||||
|
{
|
||||||
|
maxWidth -= Config.Size.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.CastTimeLabel.Enabled)
|
||||||
|
{
|
||||||
|
using (FontsManager.Instance.PushFont(Config.CastTimeLabel.FontID))
|
||||||
|
{
|
||||||
|
maxWidth -= (ImGui.CalcTextSize("XX.X") * castTimeLabel.GetFontScale()).X;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size.X > maxWidth)
|
||||||
|
{
|
||||||
|
return TruncatedCastName(text.Substring(0, text.Length - 5) + "...");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void AddExtras(BarHud bar, float totalCastTime, IDalamudTextureWrap? iconTexture)
|
||||||
|
{
|
||||||
|
// override
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string? CustomCastName()
|
||||||
|
{
|
||||||
|
// override
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual PluginConfigColor GetColor() => Config.FillColor;
|
||||||
|
public virtual Vector2 GetSize() => Config.Size;
|
||||||
|
|
||||||
|
public virtual bool ShouldShow() => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlayerCastbarHud : CastbarHud
|
||||||
|
{
|
||||||
|
private PlayerCastbarConfig Config => (PlayerCastbarConfig)_config;
|
||||||
|
|
||||||
|
public PlayerCastbarHud(PlayerCastbarConfig config, string displayName) : base(config, displayName)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override unsafe string? CustomCastName()
|
||||||
|
{
|
||||||
|
AddonCastBar* castBar = (AddonCastBar*)Plugin.GameGui.GetAddonByName("_CastBar", 1).Address;
|
||||||
|
if (castBar == null) { return null; }
|
||||||
|
|
||||||
|
AtkTextNode* node = castBar->GetTextNodeById(4);
|
||||||
|
if (node == null) { return null; }
|
||||||
|
|
||||||
|
return node->GetText().ExtractText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AddExtras(BarHud bar, float totalCastTime, IDalamudTextureWrap? iconTexture)
|
||||||
|
{
|
||||||
|
if (!Config.ShowSlideCast || Config.SlideCastTime <= 0 || Config.Preview)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect slideCast;
|
||||||
|
|
||||||
|
if (Config.FillDirection.IsHorizontal())
|
||||||
|
{
|
||||||
|
float slideCastWidth = Math.Min(Config.Size.X, Config.SlideCastTime / 1000f * Config.Size.X / totalCastTime);
|
||||||
|
Vector2 size = new(slideCastWidth, Config.Size.Y);
|
||||||
|
slideCast = new(Config.Position + Config.Size - size, size, Config.SlideCastColor);
|
||||||
|
|
||||||
|
if (Config.FillDirection is BarDirection.Left)
|
||||||
|
{
|
||||||
|
bool validIcon = iconTexture is not null;
|
||||||
|
Vector2 iconSize = Config.ShowIcon && validIcon ? new Vector2(Config.Size.Y, Config.Size.Y) : Vector2.Zero;
|
||||||
|
slideCast = Config.ShowIcon ? new Rect(Config.Position, size + new Vector2(iconSize.X, 0), Config.SlideCastColor) : new Rect(Config.Position, size, Config.SlideCastColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float slideCastHeight = Math.Min(Config.Size.Y, Config.SlideCastTime / 1000f * Config.Size.Y / totalCastTime);
|
||||||
|
Vector2 size = new(Config.Size.X, slideCastHeight);
|
||||||
|
slideCast = new(Config.Position + Config.Size - size, size, Config.SlideCastColor);
|
||||||
|
|
||||||
|
if (Config.FillDirection is BarDirection.Up)
|
||||||
|
{
|
||||||
|
slideCast = new(Config.Position, size, Config.SlideCastColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bar.AddForegrounds(slideCast);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PluginConfigColor GetColor()
|
||||||
|
{
|
||||||
|
if (!Config.UseJobColor || Actor is not ICharacter)
|
||||||
|
{
|
||||||
|
return Config.FillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICharacter? chara = (ICharacter)Actor;
|
||||||
|
PluginConfigColor? color = GlobalColors.Instance.ColorForJobId(chara.ClassJob.RowId);
|
||||||
|
return color ?? Config.FillColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TargetCastbarHud : CastbarHud
|
||||||
|
{
|
||||||
|
private TargetCastbarConfig Config => (TargetCastbarConfig)_config;
|
||||||
|
|
||||||
|
public TargetCastbarHud(TargetCastbarConfig config, string? displayName = null) : base(config, displayName)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PluginConfigColor GetColor()
|
||||||
|
{
|
||||||
|
if (Config.ShowInterruptableColor && LastUsedCast?.Interruptible == true)
|
||||||
|
{
|
||||||
|
return Config.InterruptableColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.UseColorForDamageTypes)
|
||||||
|
{
|
||||||
|
return Config.FillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LastUsedCast != null)
|
||||||
|
{
|
||||||
|
switch (LastUsedCast.DamageType)
|
||||||
|
{
|
||||||
|
case DamageType.Physical:
|
||||||
|
case DamageType.Blunt:
|
||||||
|
case DamageType.Slashing:
|
||||||
|
case DamageType.Piercing:
|
||||||
|
return Config.PhysicalDamageColor;
|
||||||
|
|
||||||
|
case DamageType.Magic:
|
||||||
|
return Config.MagicalDamageColor;
|
||||||
|
|
||||||
|
case DamageType.Darkness:
|
||||||
|
return Config.DarknessDamageColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Config.FillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override unsafe bool ShouldShow()
|
||||||
|
{
|
||||||
|
bool? targetCasting = Utils.IsTargetCasting();
|
||||||
|
if (targetCasting.HasValue)
|
||||||
|
{
|
||||||
|
return targetCasting.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FocusTargetCastbarHud : TargetCastbarHud
|
||||||
|
{
|
||||||
|
private TargetCastbarConfig Config => (TargetCastbarConfig)_config;
|
||||||
|
|
||||||
|
public FocusTargetCastbarHud(TargetCastbarConfig config, string? displayName = null) : base(config, displayName)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override unsafe bool ShouldShow()
|
||||||
|
{
|
||||||
|
bool? focusTargetCasting = Utils.IsFocusTargetCasting();
|
||||||
|
if (focusTargetCasting.HasValue)
|
||||||
|
{
|
||||||
|
return focusTargetCasting.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TargetOfTargetCastbarHud : TargetCastbarHud
|
||||||
|
{
|
||||||
|
private TargetCastbarConfig Config => (TargetCastbarConfig)_config;
|
||||||
|
|
||||||
|
public TargetOfTargetCastbarHud(TargetCastbarConfig config, string? displayName = null) : base(config, displayName)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override unsafe bool ShouldShow()
|
||||||
|
{
|
||||||
|
IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
|
||||||
|
if (Actor == target)
|
||||||
|
{
|
||||||
|
bool? targetCasting = Utils.IsTargetCasting();
|
||||||
|
if (targetCasting.HasValue)
|
||||||
|
{
|
||||||
|
return targetCasting.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IGameObject? focusTarget = Plugin.TargetManager.FocusTarget;
|
||||||
|
if (Actor == focusTarget)
|
||||||
|
{
|
||||||
|
bool? focusTargetCasting = Utils.IsFocusTargetCasting();
|
||||||
|
if (focusTargetCasting.HasValue)
|
||||||
|
{
|
||||||
|
return focusTargetCasting.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EnemyListCastbarHud : TargetCastbarHud
|
||||||
|
{
|
||||||
|
private EnemyListCastbarConfig Config => (EnemyListCastbarConfig)_config;
|
||||||
|
|
||||||
|
public int EnemyListIndex = 0;
|
||||||
|
|
||||||
|
public EnemyListCastbarHud(EnemyListCastbarConfig config, string? displayName = null) : base(config, displayName)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override unsafe bool ShouldShow()
|
||||||
|
{
|
||||||
|
bool? casting = Utils.IsEnemyInListCasting(EnemyListIndex);
|
||||||
|
if (casting.HasValue)
|
||||||
|
{
|
||||||
|
return casting.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NameplateCastbarHud : TargetOfTargetCastbarHud
|
||||||
|
{
|
||||||
|
private NameplateCastbarConfig Config => (NameplateCastbarConfig)_config;
|
||||||
|
|
||||||
|
private Vector2 _customSize = new Vector2(0);
|
||||||
|
public Vector2 ParentSize { get; set; } = new Vector2(0);
|
||||||
|
|
||||||
|
public NameplateCastbarHud(NameplateCastbarConfig config, string? displayName = null) : base(config, displayName)
|
||||||
|
{
|
||||||
|
_customSize = Config.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawChildren(Vector2 origin)
|
||||||
|
{
|
||||||
|
// calculate size
|
||||||
|
float x = Config.MatchWidth ? ParentSize.X : Config.Size.X;
|
||||||
|
float y = Config.MatchHeight ? ParentSize.Y : Config.Size.Y;
|
||||||
|
_customSize = new Vector2(x, y);
|
||||||
|
|
||||||
|
// draw
|
||||||
|
base.DrawChildren(origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 GetSize() => _customSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Dalamud.Interface;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[Section("Other Elements")]
|
||||||
|
[SubSection("Experience Bar", 0)]
|
||||||
|
public class ExperienceBarConfig : BarConfig
|
||||||
|
{
|
||||||
|
[Checkbox("Hide When Downsynced")]
|
||||||
|
[Order(44, collapseWith = nameof(HideWhenInactive))]
|
||||||
|
public bool HideWhenDownsynced = false;
|
||||||
|
|
||||||
|
[Checkbox("Use Job Color")]
|
||||||
|
[Order(45)]
|
||||||
|
public bool UseJobColor = false;
|
||||||
|
|
||||||
|
[Checkbox("Show Rested Exp")]
|
||||||
|
[Order(50)]
|
||||||
|
public bool ShowRestedExp = true;
|
||||||
|
|
||||||
|
[ColorEdit4("Rested Exp Color")]
|
||||||
|
[Order(55, collapseWith = nameof(ShowRestedExp))]
|
||||||
|
public PluginConfigColor RestedExpColor = new PluginConfigColor(new Vector4(110f / 255f, 197f / 255f, 207f / 255f, 50f / 100f));
|
||||||
|
|
||||||
|
[NestedConfig("Left Text", 60)]
|
||||||
|
public EditableLabelConfig LeftLabel;
|
||||||
|
|
||||||
|
[NestedConfig("Right Text", 61)]
|
||||||
|
public EditableLabelConfig RightLabel;
|
||||||
|
|
||||||
|
[NestedConfig("Sanctuary Icon", 62)]
|
||||||
|
public IconLabelConfig SanctuaryLabel = new IconLabelConfig(new Vector2(5, 0), FontAwesomeIcon.Moon, DrawAnchor.Right, DrawAnchor.Left);
|
||||||
|
|
||||||
|
[NestedConfig("Visibility", 70)]
|
||||||
|
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
|
||||||
|
|
||||||
|
public ExperienceBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor)
|
||||||
|
{
|
||||||
|
LeftLabel = new EditableLabelConfig(new Vector2(5, 0), "[job] Lv[level] EXP [exp:current-short]/[exp:required-short]", DrawAnchor.BottomLeft, DrawAnchor.TopLeft);
|
||||||
|
RightLabel = new EditableLabelConfig(new Vector2(-5, 0), "([exp:percent]%)", DrawAnchor.BottomRight, DrawAnchor.TopRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static ExperienceBarConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
return new ExperienceBarConfig(
|
||||||
|
new Vector2(0, -ImGui.GetMainViewport().Size.Y * 0.45f),
|
||||||
|
new Vector2(860, 10),
|
||||||
|
new PluginConfigColor(new Vector4(211f / 255f, 166f / 255f, 79f / 255f, 100f / 100f)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public class ExperienceBarHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
|
||||||
|
{
|
||||||
|
private ExperienceBarConfig Config => (ExperienceBarConfig)_config;
|
||||||
|
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
|
||||||
|
|
||||||
|
public IGameObject? Actor { get; set; } = null;
|
||||||
|
|
||||||
|
private ExperienceHelper _helper = new ExperienceHelper();
|
||||||
|
private IconLabelHud _sanctuaryLabel;
|
||||||
|
|
||||||
|
public ExperienceBarHud(ExperienceBarConfig config, string displayName) : base(config, displayName)
|
||||||
|
{
|
||||||
|
Config.SanctuaryLabel.IconId = FontAwesomeIcon.Moon;
|
||||||
|
_sanctuaryLabel = new IconLabelHud(Config.SanctuaryLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||||
|
{
|
||||||
|
return (new List<Vector2>() { Config.Position }, new List<Vector2>() { Config.Size });
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawChildren(Vector2 origin)
|
||||||
|
{
|
||||||
|
if (!Config.Enabled ||
|
||||||
|
Actor is null ||
|
||||||
|
Config.HideWhenInactive && (Plugin.ObjectTable.LocalPlayer?.Level ?? 0) >= 100 ||
|
||||||
|
(Config.HideWhenInactive && Config.HideWhenDownsynced && _helper.IsMaxLevel()))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint current = ExperienceHelper.Instance.CurrentExp;
|
||||||
|
uint required = ExperienceHelper.Instance.RequiredExp;
|
||||||
|
uint rested = Config.ShowRestedExp ? ExperienceHelper.Instance.RestedExp : 0;
|
||||||
|
|
||||||
|
// Exp progress bar
|
||||||
|
PluginConfigColor expFillColor = Config.UseJobColor ? ColorUtils.ColorForActor(Actor) : Config.FillColor;
|
||||||
|
Rect expBar = BarUtilities.GetFillRect(Config.Position, Config.Size, Config.FillDirection, expFillColor, current, required);
|
||||||
|
|
||||||
|
// Rested exp bar
|
||||||
|
var restedPos = Config.FillDirection.IsInverted() ? Config.Position : Config.Position + BarUtilities.GetFillDirectionOffset(expBar.Size, Config.FillDirection);
|
||||||
|
var restedSize = Config.Size - BarUtilities.GetFillDirectionOffset(expBar.Size, Config.FillDirection);
|
||||||
|
Rect restedBar = BarUtilities.GetFillRect(restedPos, restedSize, Config.FillDirection, Config.RestedExpColor, rested, required, 0f);
|
||||||
|
|
||||||
|
BarHud bar = new BarHud(Config, Actor);
|
||||||
|
bar.AddForegrounds(expBar, restedBar);
|
||||||
|
bar.AddLabels(Config.LeftLabel, Config.RightLabel);
|
||||||
|
|
||||||
|
AddDrawActions(bar.GetDrawActions(origin, Config.StrataLevel));
|
||||||
|
|
||||||
|
// sanctuary icon
|
||||||
|
if (IsInSanctuary())
|
||||||
|
{
|
||||||
|
AddDrawAction(Config.SanctuaryLabel.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
var pos = Utils.GetAnchoredPosition(origin, Config.Size, Config.Anchor);
|
||||||
|
_sanctuaryLabel.Draw(pos + Config.Position, Config.Size, Actor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool IsInSanctuary()
|
||||||
|
{
|
||||||
|
AddonExp* addon = ExperienceHelper.Instance.GetExpAddon();
|
||||||
|
if (addon == null) { return false; }
|
||||||
|
AtkImageNode* sanctuaryNode = addon->GetImageNodeById(3);
|
||||||
|
if (sanctuaryNode == null) { return false; }
|
||||||
|
|
||||||
|
return sanctuaryNode->IsVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.ImGuiFileDialog;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Interface.GameFonts;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public struct FontData
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public int Size;
|
||||||
|
|
||||||
|
public FontData(string name, int size)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Disableable(false)]
|
||||||
|
[Section("Customization")]
|
||||||
|
[SubSection("Fonts", 0)]
|
||||||
|
public class FontsConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static FontsConfig DefaultConfig() { return new FontsConfig(); }
|
||||||
|
|
||||||
|
public string FontsPath = "C:\\";
|
||||||
|
[JsonIgnore] public string ValidatedFontsPath => ValidatePath(FontsPath);
|
||||||
|
|
||||||
|
public SortedList<string, FontData> Fonts = new SortedList<string, FontData>();
|
||||||
|
public bool SupportChineseCharacters = false;
|
||||||
|
public bool SupportKoreanCharacters = false;
|
||||||
|
public bool SupportCyrillicCharacters = false;
|
||||||
|
[JsonIgnore] public readonly Dictionary<string, string> GameFontMap = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"Axis", "axis-ffxiv"},
|
||||||
|
{"Jupiter", "jupiter-ffxiv"},
|
||||||
|
{"JupiterNumeric", "jupiter-numeric-ffxiv"},
|
||||||
|
{"MiedingerMid", "meidinger-ffxiv"},
|
||||||
|
{"Meidinger", "meidinger-numberic-ffxiv"},
|
||||||
|
{"TrumpGothic", "trumpgothic-ffxiv"},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
[JsonIgnore] public static readonly List<string> DefaultFontsKeys = new List<string>() { "Expressway_18", "Expressway_14", "Expressway_12" };
|
||||||
|
|
||||||
|
[JsonIgnore] public static string DefaultBigFontKey => DefaultFontsKeys[0];
|
||||||
|
[JsonIgnore] public static string DefaultMediumFontKey => DefaultFontsKeys[1];
|
||||||
|
[JsonIgnore] public static string DefaultSmallFontKey => DefaultFontsKeys[2];
|
||||||
|
|
||||||
|
[JsonIgnore] private int _inputFont = 0;
|
||||||
|
[JsonIgnore] private int _inputSize = 23;
|
||||||
|
|
||||||
|
[JsonIgnore] private string[] _fonts = null!;
|
||||||
|
[JsonIgnore] private string[] _sizes = null!;
|
||||||
|
|
||||||
|
[JsonIgnore] private int _removingIndex = -1;
|
||||||
|
[JsonIgnore] private int _applyingIndex = -1!;
|
||||||
|
|
||||||
|
[JsonIgnore] private FileDialogManager _fileDialogManager = new FileDialogManager();
|
||||||
|
|
||||||
|
public FontsConfig()
|
||||||
|
{
|
||||||
|
ReloadFonts();
|
||||||
|
|
||||||
|
// default fonts
|
||||||
|
foreach (string key in DefaultFontsKeys)
|
||||||
|
{
|
||||||
|
if (!Fonts.ContainsKey(key))
|
||||||
|
{
|
||||||
|
string[] str = key.Split("_", StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var defaultFont = new FontData(str[0], int.Parse(str[1]));
|
||||||
|
Fonts.Add(key, defaultFont);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sizes
|
||||||
|
_sizes = new string[100];
|
||||||
|
for (int i = 0; i < _sizes.Length; i++)
|
||||||
|
{
|
||||||
|
_sizes[i] = (i + 1).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDefaultFont(string key)
|
||||||
|
{
|
||||||
|
return DefaultFontsKeys.Contains(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ValidatePath(string path)
|
||||||
|
{
|
||||||
|
if (path.EndsWith("\\") || path.EndsWith("/"))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path + "\\";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] FontsFromPath(string path)
|
||||||
|
{
|
||||||
|
string[] fonts;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fonts = Directory.GetFiles(path, "*.ttf");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
fonts = new string[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < fonts.Length; i++)
|
||||||
|
{
|
||||||
|
fonts[i] = fonts[i]
|
||||||
|
.Replace(path, "")
|
||||||
|
.Replace(".ttf", "")
|
||||||
|
.Replace(".TTF", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fonts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] FontsFromGame()
|
||||||
|
{
|
||||||
|
string[] gameFontArray = Enum.GetNames(typeof(GameFontFamily)).Skip(1).ToArray();
|
||||||
|
string[] fonts = new string[gameFontArray.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < gameFontArray.Length; i++)
|
||||||
|
{
|
||||||
|
fonts[i] = GameFontMap[gameFontArray[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return fonts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReloadFonts()
|
||||||
|
{
|
||||||
|
string defaultFontsPath = ValidatePath(FontsManager.Instance.DefaultFontsPath);
|
||||||
|
string[] defaultFonts = FontsFromPath(defaultFontsPath);
|
||||||
|
string[] gameFonts = FontsFromGame();
|
||||||
|
string[] userFonts = FontsFromPath(ValidatedFontsPath);
|
||||||
|
|
||||||
|
_fonts = new string[defaultFonts.Length + gameFonts.Length + userFonts.Length];
|
||||||
|
defaultFonts.CopyTo(_fonts, 0);
|
||||||
|
gameFonts.CopyTo(_fonts, defaultFonts.Length);
|
||||||
|
userFonts.CopyTo(_fonts, defaultFonts.Length + gameFonts.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AddNewEntry(int font, int size)
|
||||||
|
{
|
||||||
|
if (font < 0 || font > _fonts.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size <= 0 || size > _sizes.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string fontName = _fonts[font];
|
||||||
|
string key = fontName + "_" + size.ToString();
|
||||||
|
|
||||||
|
if (Fonts.ContainsKey(key))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FontData fontData = new FontData(fontName, size);
|
||||||
|
Fonts.Add(key, fontData);
|
||||||
|
|
||||||
|
FontsManager.Instance.BuildFonts();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectFolder()
|
||||||
|
{
|
||||||
|
Action<bool, string> callback = (finished, path) =>
|
||||||
|
{
|
||||||
|
if (finished && path.Length > 0)
|
||||||
|
{
|
||||||
|
FontsPath = path;
|
||||||
|
ReloadFonts();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_fileDialogManager.OpenFolderDialog("Select Fonts Folder", callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ManualDraw]
|
||||||
|
public bool Draw(ref bool changed)
|
||||||
|
{
|
||||||
|
ImGuiTableFlags flags =
|
||||||
|
ImGuiTableFlags.RowBg |
|
||||||
|
ImGuiTableFlags.Borders |
|
||||||
|
ImGuiTableFlags.BordersOuter |
|
||||||
|
ImGuiTableFlags.BordersInner |
|
||||||
|
ImGuiTableFlags.ScrollY |
|
||||||
|
ImGuiTableFlags.SizingFixedSame;
|
||||||
|
|
||||||
|
if (ImGui.BeginChild("Fonts", new Vector2(800, 500), false, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
|
||||||
|
{
|
||||||
|
if (_fonts.Length == 0)
|
||||||
|
{
|
||||||
|
ImGuiHelper.Tab();
|
||||||
|
ImGui.Text("Default font not found in \"%appdata%/Roaming/XIVLauncher/InstalledPlugins/HSUI/Media/Fonts/Expressway.ttf\"");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
if (ImGui.InputText("Path", ref FontsPath, 200, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
ReloadFonts();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Folder.ToIconString(), new Vector2(0, 0)))
|
||||||
|
{
|
||||||
|
SelectFolder();
|
||||||
|
}
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
ImGuiHelper.Tab();
|
||||||
|
ImGui.Combo("Font ##font", ref _inputFont, _fonts, 10);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
if (ImGui.Button("\uf2f9", new Vector2(0, 0)))
|
||||||
|
{
|
||||||
|
ReloadFonts();
|
||||||
|
}
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
ImGuiHelper.Tab();
|
||||||
|
ImGui.Combo("Size ##size", ref _inputSize, _sizes, 10);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), new Vector2(0, 0)))
|
||||||
|
{
|
||||||
|
changed |= AddNewEntry(_inputFont, _inputSize + 1);
|
||||||
|
}
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
if (ImGui.BeginTable("table", 3, flags, new Vector2(326, 300)))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 0, 0);
|
||||||
|
ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, 0, 1);
|
||||||
|
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 0, 2);
|
||||||
|
|
||||||
|
ImGui.TableSetupScrollFreeze(0, 1);
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
for (int i = 0; i < Fonts.Count; i++)
|
||||||
|
{
|
||||||
|
var key = Fonts.Keys[i];
|
||||||
|
var fontData = Fonts.Values[i];
|
||||||
|
|
||||||
|
ImGui.PushID(i.ToString());
|
||||||
|
ImGui.TableNextRow(ImGuiTableRowFlags.None);
|
||||||
|
|
||||||
|
// icon
|
||||||
|
if (ImGui.TableSetColumnIndex(0))
|
||||||
|
{
|
||||||
|
ImGui.Text(fontData.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// id
|
||||||
|
if (ImGui.TableSetColumnIndex(1))
|
||||||
|
{
|
||||||
|
ImGui.Text(fontData.Size.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove
|
||||||
|
if (ImGui.TableSetColumnIndex(2))
|
||||||
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero);
|
||||||
|
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.ArrowAltCircleUp.ToIconString()))
|
||||||
|
{
|
||||||
|
_applyingIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsDefaultFont(key))
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString()))
|
||||||
|
{
|
||||||
|
_removingIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGui.PopStyleColor(3);
|
||||||
|
}
|
||||||
|
ImGui.PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
if (ImGui.Checkbox("Support Chinese", ref SupportChineseCharacters))
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
FontsManager.Instance.BuildFonts();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Checkbox("Support Korean", ref SupportKoreanCharacters))
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
FontsManager.Instance.BuildFonts();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Checkbox("Support Cyrillic", ref SupportCyrillicCharacters))
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
FontsManager.Instance.BuildFonts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply confirmation
|
||||||
|
if (_applyingIndex >= 0)
|
||||||
|
{
|
||||||
|
string[] lines = new string[] { "Are you sure you want to apply this font", "to all labels using a font with the same size?" };
|
||||||
|
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Apply to all labels?", lines);
|
||||||
|
|
||||||
|
if (didConfirm)
|
||||||
|
{
|
||||||
|
var (key, font) = Fonts.ElementAt(_applyingIndex);
|
||||||
|
|
||||||
|
List<LabelConfig> labelConfigs = ConfigurationManager.Instance.GetObjects<LabelConfig>();
|
||||||
|
foreach (LabelConfig label in labelConfigs)
|
||||||
|
{
|
||||||
|
if (label.FontID != null && Fonts.TryGetValue(label.FontID, out FontData value))
|
||||||
|
{
|
||||||
|
if (font.Size == value.Size)
|
||||||
|
{
|
||||||
|
label.FontID = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changed = true;
|
||||||
|
ConfigurationManager.Instance.SaveConfigurations(forced: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didConfirm || didClose)
|
||||||
|
{
|
||||||
|
_applyingIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete confirmation
|
||||||
|
if (_removingIndex >= 0)
|
||||||
|
{
|
||||||
|
string[] lines = new string[] { "Are you sure you want to remove this font?" };
|
||||||
|
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Remove custom font?", lines);
|
||||||
|
|
||||||
|
if (didConfirm)
|
||||||
|
{
|
||||||
|
Fonts.RemoveAt(_removingIndex);
|
||||||
|
FontsManager.Instance.BuildFonts();
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didConfirm || didClose)
|
||||||
|
{
|
||||||
|
_removingIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
_fileDialogManager.Draw();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[DisableParentSettings("Size")]
|
||||||
|
[Section("Other Elements")]
|
||||||
|
[SubSection("GCD Indicator", 0)]
|
||||||
|
public class GCDIndicatorConfig : AnchorablePluginConfigObject
|
||||||
|
{
|
||||||
|
[Checkbox("Always Show")]
|
||||||
|
[Order(3)]
|
||||||
|
public bool AlwaysShow = false;
|
||||||
|
|
||||||
|
[Checkbox("Anchor To Mouse")]
|
||||||
|
[Order(4)]
|
||||||
|
public bool AnchorToMouse = false;
|
||||||
|
|
||||||
|
[ColorEdit4("Background Color")]
|
||||||
|
[Order(16)]
|
||||||
|
public PluginConfigColor BackgroundColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 50f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Color")]
|
||||||
|
[Order(17)]
|
||||||
|
public PluginConfigColor FillColor = new PluginConfigColor(new(220f / 255f, 220f / 255f, 220f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[Checkbox("Show Border")]
|
||||||
|
[Order(18)]
|
||||||
|
public bool ShowBorder = true;
|
||||||
|
|
||||||
|
[Checkbox("Instant GCDs only", spacing = true)]
|
||||||
|
[Order(19)]
|
||||||
|
public bool InstantGCDsOnly = false;
|
||||||
|
|
||||||
|
[Checkbox("Only show when under GCD Threshold", spacing = true)]
|
||||||
|
[Order(20)]
|
||||||
|
public bool LimitGCDThreshold = false;
|
||||||
|
|
||||||
|
[DragFloat("GCD Threshold", velocity = 0.01f)]
|
||||||
|
[Order(21, collapseWith = nameof(LimitGCDThreshold))]
|
||||||
|
public float GCDThreshold = 1.50f;
|
||||||
|
|
||||||
|
[Checkbox("Show GCD Queue Indicator", spacing = true)]
|
||||||
|
[Order(24)]
|
||||||
|
public bool ShowGCDQueueIndicator = true;
|
||||||
|
|
||||||
|
[ColorEdit4("GCD Queue Color")]
|
||||||
|
[Order(25, collapseWith = nameof(ShowGCDQueueIndicator))]
|
||||||
|
public PluginConfigColor QueueColor = new PluginConfigColor(new(13f / 255f, 207f / 255f, 31f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[Checkbox("Circular Mode", spacing = true)]
|
||||||
|
[Order(30)]
|
||||||
|
public bool CircularMode = false;
|
||||||
|
|
||||||
|
[DragInt("Radius")]
|
||||||
|
[Order(35, collapseWith = nameof(CircularMode))]
|
||||||
|
public int CircleRadius = 40;
|
||||||
|
|
||||||
|
[DragInt("Thickness")]
|
||||||
|
[Order(40, collapseWith = nameof(CircularMode))]
|
||||||
|
public int CircleThickness = 10;
|
||||||
|
|
||||||
|
[DragInt("Start Angle", min = 0, max = 359)]
|
||||||
|
[Order(45, collapseWith = nameof(CircularMode))]
|
||||||
|
public int CircleStartAngle = 0;
|
||||||
|
|
||||||
|
[Checkbox("Rotate CCW")]
|
||||||
|
[Order(50, collapseWith = nameof(CircularMode))]
|
||||||
|
public bool RotateCCW = false;
|
||||||
|
|
||||||
|
[NestedConfig("Bar Mode", 45, collapsingHeader = false)]
|
||||||
|
public GCDBarConfig Bar = new GCDBarConfig(
|
||||||
|
new Vector2(0, HUDConstants.BaseHUDOffsetY + 21),
|
||||||
|
new Vector2(254, 8),
|
||||||
|
PluginConfigColor.Empty
|
||||||
|
);
|
||||||
|
|
||||||
|
[NestedConfig("Visibility", 70)]
|
||||||
|
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
|
||||||
|
|
||||||
|
public new static GCDIndicatorConfig DefaultConfig() { return new GCDIndicatorConfig() { Enabled = false, Strata = StrataLevel.MID_HIGH }; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("Position", "Anchor", "HideWhenInactive", "FillColor", "BackgroundColor", "DrawBorder")]
|
||||||
|
[Exportable(false)]
|
||||||
|
public class GCDBarConfig : BarConfig
|
||||||
|
{
|
||||||
|
public GCDBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor, BarDirection fillDirection = BarDirection.Right)
|
||||||
|
: base(position, size, fillColor, fillDirection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
using System;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using HSUI.Config;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public class GCDIndicatorHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
|
||||||
|
{
|
||||||
|
private GCDIndicatorConfig Config => (GCDIndicatorConfig)_config;
|
||||||
|
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
|
||||||
|
|
||||||
|
public IGameObject? Actor { get; set; } = null;
|
||||||
|
|
||||||
|
private bool _wasBarEnabled = true;
|
||||||
|
private bool _wasCircularModeEnabled = false;
|
||||||
|
private float _lastTotalCastTime = 0;
|
||||||
|
|
||||||
|
public GCDIndicatorHud(GCDIndicatorConfig config, string displayName) : base(config, displayName) { }
|
||||||
|
|
||||||
|
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||||
|
{
|
||||||
|
var (pos, size) = GetPositionAndSize(Vector2.Zero);
|
||||||
|
|
||||||
|
if (Config.CircularMode)
|
||||||
|
{
|
||||||
|
pos -= size / 2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (new List<Vector2>() { pos }, new List<Vector2>() { size });
|
||||||
|
}
|
||||||
|
|
||||||
|
private (Vector2, Vector2) GetPositionAndSize(Vector2 origin)
|
||||||
|
{
|
||||||
|
Vector2 pos = Config.AnchorToMouse ? ImGui.GetMousePos() + Config.Position : origin + Config.Position;
|
||||||
|
Vector2 size = Config.Bar.Size;
|
||||||
|
|
||||||
|
if (Config.CircularMode)
|
||||||
|
{
|
||||||
|
size = new Vector2(Config.CircleRadius * 2, Config.CircleRadius * 2);
|
||||||
|
pos += size / 2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (pos, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawDraggableArea(Vector2 origin)
|
||||||
|
{
|
||||||
|
if (Config.AnchorToMouse)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.DrawDraggableArea(origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawChildren(Vector2 origin)
|
||||||
|
{
|
||||||
|
CheckToggles();
|
||||||
|
|
||||||
|
if (!Config.Enabled || Actor == null || Actor is not IPlayerCharacter)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GCDHelper.GetGCDInfo((IPlayerCharacter)Actor, out var elapsed, out var total);
|
||||||
|
|
||||||
|
if (!Config.AlwaysShow && total == 0)
|
||||||
|
{
|
||||||
|
_lastTotalCastTime = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastTotalCastTime == 0 && Utils.IsActorCasting(Actor))
|
||||||
|
{
|
||||||
|
_lastTotalCastTime = ((IBattleChara)Actor).TotalCastTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scale = elapsed / total;
|
||||||
|
if (scale <= 0)
|
||||||
|
{
|
||||||
|
_lastTotalCastTime = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool instantGCDsOnly = Config.InstantGCDsOnly && _lastTotalCastTime != 0;
|
||||||
|
bool thresholdGCDs = Config.LimitGCDThreshold && _lastTotalCastTime > Config.GCDThreshold;
|
||||||
|
|
||||||
|
if (instantGCDsOnly || thresholdGCDs)
|
||||||
|
{
|
||||||
|
if (Config.AlwaysShow)
|
||||||
|
{
|
||||||
|
elapsed = 0;
|
||||||
|
total = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Config.Bar.Position = Config.Position;
|
||||||
|
Config.Bar.Anchor = Config.Anchor;
|
||||||
|
Config.Bar.BackgroundColor = Config.BackgroundColor;
|
||||||
|
Config.Bar.FillColor = Config.FillColor;
|
||||||
|
Config.Bar.DrawBorder = Config.ShowBorder;
|
||||||
|
|
||||||
|
if (Config.Bar.Enabled)
|
||||||
|
{
|
||||||
|
DrawNormalBar(origin, elapsed, total);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var (pos, size) = GetPositionAndSize(origin);
|
||||||
|
pos = Utils.GetAnchoredPosition(pos, size, Config.Anchor);
|
||||||
|
|
||||||
|
AddDrawAction(_config.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
DrawCircularIndicator(pos, Config.CircleRadius, elapsed, total);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckToggles()
|
||||||
|
{
|
||||||
|
bool barEnabledChanged = _wasBarEnabled != Config.Bar.Enabled;
|
||||||
|
if (barEnabledChanged)
|
||||||
|
{
|
||||||
|
Config.CircularMode = !Config.Bar.Enabled;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool circularModeChanged = _wasCircularModeEnabled != Config.CircularMode;
|
||||||
|
if (circularModeChanged)
|
||||||
|
{
|
||||||
|
Config.Bar.Enabled = !Config.CircularMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_wasBarEnabled = Config.Bar.Enabled;
|
||||||
|
_wasCircularModeEnabled = Config.CircularMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCircularIndicator(Vector2 position, float radius, float current, float total)
|
||||||
|
{
|
||||||
|
total = Config.AlwaysShow && total == 0 ? 1 : total;
|
||||||
|
current = Config.AlwaysShow && current == 0 ? total : current;
|
||||||
|
|
||||||
|
var size = new Vector2(radius * 2);
|
||||||
|
DrawHelper.DrawInWindow(ID, position - size / 2, size, false, (drawList) =>
|
||||||
|
{
|
||||||
|
current = Math.Min(current, total);
|
||||||
|
|
||||||
|
// controls how smooth the arc looks
|
||||||
|
const int segments = 100;
|
||||||
|
const float queueTime = 0.5f;
|
||||||
|
float startAngle = 0f;
|
||||||
|
float endAngle = 2f * (float)Math.PI;
|
||||||
|
float offset = (float)(-Math.PI / 2f + (Config.CircleStartAngle * (Math.PI / 180f)));
|
||||||
|
|
||||||
|
if (Config.RotateCCW)
|
||||||
|
{
|
||||||
|
startAngle *= -1;
|
||||||
|
endAngle *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.AlwaysShow && current == total)
|
||||||
|
{
|
||||||
|
drawList.PathArcTo(position, radius, startAngle + offset, endAngle + offset, segments);
|
||||||
|
drawList.PathStroke(Config.FillColor.Base, ImDrawFlags.None, Config.CircleThickness);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// always draw until the queue threshold
|
||||||
|
float progressAngle = Math.Min(current, total - (Config.ShowGCDQueueIndicator ? queueTime : 0f)) / total * endAngle;
|
||||||
|
|
||||||
|
// drawing an arc with thickness to make it look like an annular sector
|
||||||
|
drawList.PathArcTo(position, radius, startAngle + offset, progressAngle + offset, segments);
|
||||||
|
drawList.PathStroke(Config.FillColor.Base, ImDrawFlags.None, Config.CircleThickness);
|
||||||
|
|
||||||
|
// draw the queue indicator
|
||||||
|
if (Config.ShowGCDQueueIndicator && current > total - queueTime)
|
||||||
|
{
|
||||||
|
float oldAngle = progressAngle - 0.0003f * total * endAngle;
|
||||||
|
progressAngle = current / total * endAngle;
|
||||||
|
drawList.PathArcTo(position, radius, oldAngle + offset, progressAngle + offset, segments);
|
||||||
|
drawList.PathStroke(Config.QueueColor.Base, ImDrawFlags.None, Config.CircleThickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
// anything that remains is background
|
||||||
|
drawList.PathArcTo(position, radius, progressAngle + offset, endAngle + offset, segments);
|
||||||
|
drawList.PathStroke(Config.BackgroundColor.Base, ImDrawFlags.None, Config.CircleThickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.ShowBorder)
|
||||||
|
{
|
||||||
|
drawList.PathArcTo(position, radius - Config.CircleThickness / 2f, 0, endAngle, segments);
|
||||||
|
drawList.PathStroke(0xFF000000, ImDrawFlags.None, 1);
|
||||||
|
|
||||||
|
drawList.PathArcTo(position, radius + Config.CircleThickness / 2f, 0, endAngle, segments);
|
||||||
|
drawList.PathStroke(0xFF000000, ImDrawFlags.None, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawNormalBar(Vector2 origin, float current, float total)
|
||||||
|
{
|
||||||
|
GCDBarConfig config = Config.Bar;
|
||||||
|
|
||||||
|
Rect mainRect = BarUtilities.GetFillRect(config.Position, config.Size, config.FillDirection, config.FillColor, current, total, 0);
|
||||||
|
BarHud bar = new BarHud(config, null, null);
|
||||||
|
bar.AddForegrounds(mainRect);
|
||||||
|
|
||||||
|
float currentPercent = current / total;
|
||||||
|
float percentNonQueue = total != 0 ? 1F - (500f / 1000f) / total : 0;
|
||||||
|
|
||||||
|
if (percentNonQueue > 0 && currentPercent >= percentNonQueue && Config.ShowGCDQueueIndicator)
|
||||||
|
{
|
||||||
|
float scale = 1 - percentNonQueue;
|
||||||
|
Vector2 size = config.FillDirection.IsHorizontal() ?
|
||||||
|
new Vector2(config.Size.X * scale, config.Size.Y) :
|
||||||
|
new Vector2(config.Size.X, config.Size.Y * scale);
|
||||||
|
|
||||||
|
Vector2 pos = config.Position;
|
||||||
|
if (config.FillDirection == BarDirection.Right)
|
||||||
|
{
|
||||||
|
pos.X += config.Size.X * percentNonQueue;
|
||||||
|
}
|
||||||
|
else if (config.FillDirection == BarDirection.Down)
|
||||||
|
{
|
||||||
|
pos.Y += config.Size.Y * percentNonQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect foreground = BarUtilities.GetFillRect(pos, size, config.FillDirection, Config.QueueColor, currentPercent - percentNonQueue, scale, 0);
|
||||||
|
bar.AddForegrounds(foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddDrawActions(bar.GetDrawActions(origin, _config.StrataLevel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,478 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Party;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public class GlobalColors : IDisposable
|
||||||
|
{
|
||||||
|
#region Singleton
|
||||||
|
private MiscColorConfig _miscColorConfig = null!;
|
||||||
|
private RolesColorConfig _rolesColorConfig = null!;
|
||||||
|
|
||||||
|
private Dictionary<uint, PluginConfigColor> ColorMap = null!;
|
||||||
|
|
||||||
|
private GlobalColors()
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
||||||
|
OnConfigReset(ConfigurationManager.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigReset(ConfigurationManager sender)
|
||||||
|
{
|
||||||
|
_miscColorConfig = sender.GetConfigObject<MiscColorConfig>();
|
||||||
|
_rolesColorConfig = sender.GetConfigObject<RolesColorConfig>();
|
||||||
|
|
||||||
|
var tanksColorConfig = sender.GetConfigObject<TanksColorConfig>();
|
||||||
|
var healersColorConfig = sender.GetConfigObject<HealersColorConfig>();
|
||||||
|
var meleeColorConfig = sender.GetConfigObject<MeleeColorConfig>();
|
||||||
|
var rangedColorConfig = sender.GetConfigObject<RangedColorConfig>();
|
||||||
|
var castersColorConfig = sender.GetConfigObject<CastersColorConfig>();
|
||||||
|
|
||||||
|
ColorMap = new Dictionary<uint, PluginConfigColor>()
|
||||||
|
{
|
||||||
|
// tanks
|
||||||
|
[JobIDs.GLA] = tanksColorConfig.GLAColor,
|
||||||
|
[JobIDs.MRD] = tanksColorConfig.MRDColor,
|
||||||
|
[JobIDs.PLD] = tanksColorConfig.PLDColor,
|
||||||
|
[JobIDs.WAR] = tanksColorConfig.WARColor,
|
||||||
|
[JobIDs.DRK] = tanksColorConfig.DRKColor,
|
||||||
|
[JobIDs.GNB] = tanksColorConfig.GNBColor,
|
||||||
|
|
||||||
|
// healers
|
||||||
|
[JobIDs.CNJ] = healersColorConfig.CNJColor,
|
||||||
|
[JobIDs.WHM] = healersColorConfig.WHMColor,
|
||||||
|
[JobIDs.SCH] = healersColorConfig.SCHColor,
|
||||||
|
[JobIDs.AST] = healersColorConfig.ASTColor,
|
||||||
|
[JobIDs.SGE] = healersColorConfig.SGEColor,
|
||||||
|
|
||||||
|
// melee
|
||||||
|
[JobIDs.PGL] = meleeColorConfig.PGLColor,
|
||||||
|
[JobIDs.LNC] = meleeColorConfig.LNCColor,
|
||||||
|
[JobIDs.ROG] = meleeColorConfig.ROGColor,
|
||||||
|
[JobIDs.MNK] = meleeColorConfig.MNKColor,
|
||||||
|
[JobIDs.DRG] = meleeColorConfig.DRGColor,
|
||||||
|
[JobIDs.NIN] = meleeColorConfig.NINColor,
|
||||||
|
[JobIDs.SAM] = meleeColorConfig.SAMColor,
|
||||||
|
[JobIDs.RPR] = meleeColorConfig.RPRColor,
|
||||||
|
[JobIDs.VPR] = meleeColorConfig.VPRColor,
|
||||||
|
|
||||||
|
// ranged
|
||||||
|
[JobIDs.ARC] = rangedColorConfig.ARCColor,
|
||||||
|
[JobIDs.BRD] = rangedColorConfig.BRDColor,
|
||||||
|
[JobIDs.MCH] = rangedColorConfig.MCHColor,
|
||||||
|
[JobIDs.DNC] = rangedColorConfig.DNCColor,
|
||||||
|
|
||||||
|
// casters
|
||||||
|
[JobIDs.THM] = castersColorConfig.THMColor,
|
||||||
|
[JobIDs.ACN] = castersColorConfig.ACNColor,
|
||||||
|
[JobIDs.BLM] = castersColorConfig.BLMColor,
|
||||||
|
[JobIDs.SMN] = castersColorConfig.SMNColor,
|
||||||
|
[JobIDs.RDM] = castersColorConfig.RDMColor,
|
||||||
|
[JobIDs.PCT] = castersColorConfig.PCTColor,
|
||||||
|
[JobIDs.BLU] = castersColorConfig.BLUColor,
|
||||||
|
|
||||||
|
// crafters
|
||||||
|
[JobIDs.CRP] = _rolesColorConfig.HANDColor,
|
||||||
|
[JobIDs.BSM] = _rolesColorConfig.HANDColor,
|
||||||
|
[JobIDs.ARM] = _rolesColorConfig.HANDColor,
|
||||||
|
[JobIDs.GSM] = _rolesColorConfig.HANDColor,
|
||||||
|
[JobIDs.LTW] = _rolesColorConfig.HANDColor,
|
||||||
|
[JobIDs.WVR] = _rolesColorConfig.HANDColor,
|
||||||
|
[JobIDs.ALC] = _rolesColorConfig.HANDColor,
|
||||||
|
[JobIDs.CUL] = _rolesColorConfig.HANDColor,
|
||||||
|
|
||||||
|
// gatherers
|
||||||
|
[JobIDs.MIN] = _rolesColorConfig.LANDColor,
|
||||||
|
[JobIDs.BOT] = _rolesColorConfig.LANDColor,
|
||||||
|
[JobIDs.FSH] = _rolesColorConfig.LANDColor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
Instance = new GlobalColors();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GlobalColors Instance { get; private set; } = null!;
|
||||||
|
|
||||||
|
~GlobalColors()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
|
||||||
|
Instance = null!;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public PluginConfigColor? ColorForJobId(uint jobId) => ColorMap.TryGetValue(jobId, out PluginConfigColor? color) ? color : null;
|
||||||
|
|
||||||
|
public PluginConfigColor SafeColorForJobId(uint jobId) => ColorForJobId(jobId) ?? _miscColorConfig.NPCNeutralColor;
|
||||||
|
|
||||||
|
public PluginConfigColor? RoleColorForJobId(uint jobId)
|
||||||
|
{
|
||||||
|
JobRoles role = JobsHelper.RoleForJob(jobId);
|
||||||
|
|
||||||
|
return role switch
|
||||||
|
{
|
||||||
|
JobRoles.Tank => _rolesColorConfig.TankRoleColor,
|
||||||
|
JobRoles.Healer => _rolesColorConfig.HealerRoleColor,
|
||||||
|
JobRoles.DPSMelee => _rolesColorConfig.UseSpecificDPSColors ? _rolesColorConfig.MeleeDPSRoleColor : _rolesColorConfig.DPSRoleColor,
|
||||||
|
JobRoles.DPSRanged => _rolesColorConfig.UseSpecificDPSColors ? _rolesColorConfig.RangedDPSRoleColor : _rolesColorConfig.DPSRoleColor,
|
||||||
|
JobRoles.DPSCaster => _rolesColorConfig.UseSpecificDPSColors ? _rolesColorConfig.CasterDPSRoleColor : _rolesColorConfig.DPSRoleColor,
|
||||||
|
JobRoles.Gatherer => _rolesColorConfig.LANDColor,
|
||||||
|
JobRoles.Crafter => _rolesColorConfig.HANDColor,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginConfigColor SafeRoleColorForJobId(uint jobId) => RoleColorForJobId(jobId) ?? _miscColorConfig.NPCNeutralColor;
|
||||||
|
|
||||||
|
public PluginConfigColor EmptyUnitFrameColor => _miscColorConfig.EmptyUnitFrameColor;
|
||||||
|
public PluginConfigColor EmptyColor => _miscColorConfig.EmptyColor;
|
||||||
|
public PluginConfigColor PartialFillColor => _miscColorConfig.PartialFillColor;
|
||||||
|
public PluginConfigColor NPCFriendlyColor => _miscColorConfig.NPCFriendlyColor;
|
||||||
|
public PluginConfigColor NPCHostileColor => _miscColorConfig.NPCHostileColor;
|
||||||
|
public PluginConfigColor NPCNeutralColor => _miscColorConfig.NPCNeutralColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Disableable(false)]
|
||||||
|
[Section("Colors")]
|
||||||
|
[SubSection("Tanks", 0)]
|
||||||
|
public class TanksColorConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static TanksColorConfig DefaultConfig() { return new TanksColorConfig(); }
|
||||||
|
|
||||||
|
[ColorEdit4("Paladin", spacing = true)]
|
||||||
|
[Order(5)]
|
||||||
|
public PluginConfigColor PLDColor = new PluginConfigColor(new(168f / 255f, 210f / 255f, 230f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Dark Knight")]
|
||||||
|
[Order(10)]
|
||||||
|
public PluginConfigColor DRKColor = new PluginConfigColor(new(209f / 255f, 38f / 255f, 204f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Warrior")]
|
||||||
|
[Order(15)]
|
||||||
|
public PluginConfigColor WARColor = new PluginConfigColor(new(207f / 255f, 38f / 255f, 33f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Gunbreaker")]
|
||||||
|
[Order(20)]
|
||||||
|
public PluginConfigColor GNBColor = new PluginConfigColor(new(121f / 255f, 109f / 255f, 48f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Gladiator", spacing = true)]
|
||||||
|
[Order(25)]
|
||||||
|
public PluginConfigColor GLAColor = new PluginConfigColor(new(168f / 255f, 210f / 255f, 230f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Marauder")]
|
||||||
|
[Order(30)]
|
||||||
|
public PluginConfigColor MRDColor = new PluginConfigColor(new(207f / 255f, 38f / 255f, 33f / 255f, 100f / 100f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Disableable(false)]
|
||||||
|
[Section("Colors")]
|
||||||
|
[SubSection("Healers", 0)]
|
||||||
|
public class HealersColorConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static HealersColorConfig DefaultConfig() { return new HealersColorConfig(); }
|
||||||
|
|
||||||
|
[ColorEdit4("Scholar", spacing = true)]
|
||||||
|
[Order(5)]
|
||||||
|
public PluginConfigColor SCHColor = new PluginConfigColor(new(134f / 255f, 87f / 255f, 255f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("White Mage")]
|
||||||
|
[Order(10)]
|
||||||
|
public PluginConfigColor WHMColor = new PluginConfigColor(new(255f / 255f, 240f / 255f, 220f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Astrologian")]
|
||||||
|
[Order(15)]
|
||||||
|
public PluginConfigColor ASTColor = new PluginConfigColor(new(255f / 255f, 231f / 255f, 74f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Sage")]
|
||||||
|
[Order(20)]
|
||||||
|
public PluginConfigColor SGEColor = new PluginConfigColor(new(144f / 255f, 176f / 255f, 255f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Conjurer", spacing = true)]
|
||||||
|
[Order(25)]
|
||||||
|
public PluginConfigColor CNJColor = new PluginConfigColor(new(255f / 255f, 240f / 255f, 220f / 255f, 100f / 100f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Disableable(false)]
|
||||||
|
[Section("Colors")]
|
||||||
|
[SubSection("Melee", 0)]
|
||||||
|
public class MeleeColorConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static MeleeColorConfig DefaultConfig() { return new MeleeColorConfig(); }
|
||||||
|
|
||||||
|
[ColorEdit4("Monk", spacing = true)]
|
||||||
|
[Order(5)]
|
||||||
|
public PluginConfigColor MNKColor = new PluginConfigColor(new(214f / 255f, 156f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Ninja")]
|
||||||
|
[Order(10)]
|
||||||
|
public PluginConfigColor NINColor = new PluginConfigColor(new(175f / 255f, 25f / 255f, 100f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Dragoon")]
|
||||||
|
[Order(15)]
|
||||||
|
public PluginConfigColor DRGColor = new PluginConfigColor(new(65f / 255f, 100f / 255f, 205f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Samurai")]
|
||||||
|
[Order(20)]
|
||||||
|
public PluginConfigColor SAMColor = new PluginConfigColor(new(228f / 255f, 109f / 255f, 4f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Reaper")]
|
||||||
|
[Order(25)]
|
||||||
|
public PluginConfigColor RPRColor = new PluginConfigColor(new(150f / 255f, 90f / 255f, 144f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Viper")]
|
||||||
|
[Order(25)]
|
||||||
|
public PluginConfigColor VPRColor = new PluginConfigColor(new(16f / 255f, 130f / 255f, 16f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Pugilist", spacing = true)]
|
||||||
|
[Order(30)]
|
||||||
|
public PluginConfigColor PGLColor = new PluginConfigColor(new(214f / 255f, 156f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Rogue")]
|
||||||
|
[Order(35)]
|
||||||
|
public PluginConfigColor ROGColor = new PluginConfigColor(new(175f / 255f, 25f / 255f, 100f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Lancer")]
|
||||||
|
[Order(40)]
|
||||||
|
public PluginConfigColor LNCColor = new PluginConfigColor(new(65f / 255f, 100f / 255f, 205f / 255f, 100f / 100f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Disableable(false)]
|
||||||
|
[Section("Colors")]
|
||||||
|
[SubSection("Ranged", 0)]
|
||||||
|
public class RangedColorConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static RangedColorConfig DefaultConfig() { return new RangedColorConfig(); }
|
||||||
|
|
||||||
|
[ColorEdit4("Bard", spacing = true)]
|
||||||
|
[Order(5)]
|
||||||
|
public PluginConfigColor BRDColor = new PluginConfigColor(new(145f / 255f, 186f / 255f, 94f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Machinist")]
|
||||||
|
[Order(10)]
|
||||||
|
public PluginConfigColor MCHColor = new PluginConfigColor(new(110f / 255f, 225f / 255f, 214f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Dancer")]
|
||||||
|
[Order(15)]
|
||||||
|
public PluginConfigColor DNCColor = new PluginConfigColor(new(226f / 255f, 176f / 255f, 175f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Archer", separator = true)]
|
||||||
|
[Order(20)]
|
||||||
|
public PluginConfigColor ARCColor = new PluginConfigColor(new(145f / 255f, 186f / 255f, 94f / 255f, 100f / 100f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Disableable(false)]
|
||||||
|
[Section("Colors")]
|
||||||
|
[SubSection("Caster", 0)]
|
||||||
|
public class CastersColorConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static CastersColorConfig DefaultConfig() { return new CastersColorConfig(); }
|
||||||
|
|
||||||
|
[ColorEdit4("Black Mage", spacing = true)]
|
||||||
|
[Order(5)]
|
||||||
|
public PluginConfigColor BLMColor = new PluginConfigColor(new(165f / 255f, 121f / 255f, 214f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Summoner")]
|
||||||
|
[Order(10)]
|
||||||
|
public PluginConfigColor SMNColor = new PluginConfigColor(new(45f / 255f, 155f / 255f, 120f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Red Mage")]
|
||||||
|
[Order(15)]
|
||||||
|
public PluginConfigColor RDMColor = new PluginConfigColor(new(232f / 255f, 123f / 255f, 123f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Pictomancer")]
|
||||||
|
[Order(15)]
|
||||||
|
public PluginConfigColor PCTColor = new PluginConfigColor(new(252f / 255f, 146f / 255f, 225f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Blue Mage", spacing = true)]
|
||||||
|
[Order(20)]
|
||||||
|
public PluginConfigColor BLUColor = new PluginConfigColor(new(0f / 255f, 185f / 255f, 247f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Thaumaturge")]
|
||||||
|
[Order(25)]
|
||||||
|
public PluginConfigColor THMColor = new PluginConfigColor(new(165f / 255f, 121f / 255f, 214f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Arcanist")]
|
||||||
|
[Order(30)]
|
||||||
|
public PluginConfigColor ACNColor = new PluginConfigColor(new(45f / 255f, 155f / 255f, 120f / 255f, 100f / 100f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Disableable(false)]
|
||||||
|
[Section("Colors")]
|
||||||
|
[SubSection("Roles", 0)]
|
||||||
|
public class RolesColorConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static RolesColorConfig DefaultConfig() { return new RolesColorConfig(); }
|
||||||
|
|
||||||
|
[ColorEdit4("Tank")]
|
||||||
|
[Order(10)]
|
||||||
|
public PluginConfigColor TankRoleColor = new PluginConfigColor(new(21f / 255f, 28f / 255f, 100f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("DPS")]
|
||||||
|
[Order(15)]
|
||||||
|
public PluginConfigColor DPSRoleColor = new PluginConfigColor(new(153f / 255f, 23f / 255f, 23f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Healer")]
|
||||||
|
[Order(20)]
|
||||||
|
public PluginConfigColor HealerRoleColor = new PluginConfigColor(new(46f / 255f, 125f / 255f, 50f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Disciple of the Land", spacing = true)]
|
||||||
|
[Order(25)]
|
||||||
|
public PluginConfigColor LANDColor = new PluginConfigColor(new(99f / 255f, 172f / 255f, 14f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Disciple of the Hand")]
|
||||||
|
[Order(30)]
|
||||||
|
public PluginConfigColor HANDColor = new PluginConfigColor(new(99f / 255f, 172f / 255f, 14f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[Checkbox("Use Specific DPS Colors", spacing = true)]
|
||||||
|
[Order(35)]
|
||||||
|
public bool UseSpecificDPSColors = false;
|
||||||
|
|
||||||
|
[ColorEdit4("Melee DPS")]
|
||||||
|
[Order(40, collapseWith = nameof(UseSpecificDPSColors))]
|
||||||
|
public PluginConfigColor MeleeDPSRoleColor = new PluginConfigColor(new(151f / 255f, 56f / 255f, 56f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Ranged DPS")]
|
||||||
|
[Order(40, collapseWith = nameof(UseSpecificDPSColors))]
|
||||||
|
public PluginConfigColor RangedDPSRoleColor = new PluginConfigColor(new(250f / 255f, 185f / 255f, 67f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Caster DPS")]
|
||||||
|
[Order(40, collapseWith = nameof(UseSpecificDPSColors))]
|
||||||
|
public PluginConfigColor CasterDPSRoleColor = new PluginConfigColor(new(154f / 255f, 82f / 255f, 193f / 255f, 100f / 100f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Disableable(false)]
|
||||||
|
[Section("Colors")]
|
||||||
|
[SubSection("Misc", 0)]
|
||||||
|
public class MiscColorConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static MiscColorConfig DefaultConfig() { return new MiscColorConfig(); }
|
||||||
|
|
||||||
|
[Combo("Gradient Type For Bars", "Flat Color", "Right", "Left", "Up", "Down", "Centered Horizontal", spacing = true)]
|
||||||
|
[Order(4)]
|
||||||
|
public GradientDirection GradientDirection = GradientDirection.Down;
|
||||||
|
|
||||||
|
[ColorEdit4("Empty Unit Frame", separator = true)]
|
||||||
|
[Order(5)]
|
||||||
|
public PluginConfigColor EmptyUnitFrameColor = new PluginConfigColor(new(0f / 255f, 0f / 255f, 0f / 255f, 95f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Empty Bar")]
|
||||||
|
[Order(10)]
|
||||||
|
public PluginConfigColor EmptyColor = new PluginConfigColor(new(0f / 255f, 0f / 255f, 0f / 255f, 50f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Partially Filled Bar")]
|
||||||
|
[Order(15)]
|
||||||
|
public PluginConfigColor PartialFillColor = new PluginConfigColor(new(180f / 255f, 180f / 255f, 180f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("NPC Friendly", separator = true)]
|
||||||
|
[Order(20)]
|
||||||
|
public PluginConfigColor NPCFriendlyColor = new PluginConfigColor(new(99f / 255f, 172f / 255f, 14f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("NPC Hostile")]
|
||||||
|
[Order(25)]
|
||||||
|
public PluginConfigColor NPCHostileColor = new PluginConfigColor(new(233f / 255f, 4f / 255f, 4f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("NPC Neutral")]
|
||||||
|
[Order(30)]
|
||||||
|
public PluginConfigColor NPCNeutralColor = new PluginConfigColor(new(218f / 255f, 157f / 255f, 46f / 255f, 100f / 100f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class ColorByHealthValueConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
|
||||||
|
[Checkbox("Use Max Health Color")]
|
||||||
|
[Order(5)]
|
||||||
|
public bool UseMaxHealthColor = false;
|
||||||
|
|
||||||
|
[ColorEdit4("Max Health Color")]
|
||||||
|
[Order(10, collapseWith = nameof(UseMaxHealthColor))]
|
||||||
|
public PluginConfigColor MaxHealthColor = new PluginConfigColor(new(18f / 255f, 18f / 255f, 18f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[Checkbox("Job Color as Max Health Color")]
|
||||||
|
[Order(15, collapseWith = nameof(UseMaxHealthColor))]
|
||||||
|
public bool UseJobColorAsMaxHealth = false;
|
||||||
|
|
||||||
|
[Checkbox("Job Role as Max Health Color")]
|
||||||
|
[Order(20, collapseWith = nameof(UseMaxHealthColor))]
|
||||||
|
public bool UseRoleColorAsMaxHealth = false;
|
||||||
|
|
||||||
|
[ColorEdit4("High Health Color")]
|
||||||
|
[Order(25)]
|
||||||
|
public PluginConfigColor FullHealthColor = new PluginConfigColor(new(0f / 255f, 255f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Low Health Color")]
|
||||||
|
[Order(30)]
|
||||||
|
public PluginConfigColor LowHealthColor = new PluginConfigColor(new(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[DragFloat("Max Health Color Above Health %", min = 50f, max = 100f, velocity = 1f)]
|
||||||
|
[Order(35)]
|
||||||
|
public float FullHealthColorThreshold = 75f;
|
||||||
|
|
||||||
|
[DragFloat("Low Health Color Below Health %", min = 0f, max = 50f, velocity = 1f)]
|
||||||
|
[Order(40)]
|
||||||
|
public float LowHealthColorThreshold = 25f;
|
||||||
|
|
||||||
|
[Combo("Blend Mode", "LAB", "LChab", "XYZ", "RGB", "LChuv", "Luv", "Jzazbz", "JzCzhz")]
|
||||||
|
[Order(45)]
|
||||||
|
public BlendMode BlendMode = BlendMode.LAB;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ColorByHealthFieldsConverter : PluginConfigObjectConverter
|
||||||
|
{
|
||||||
|
public ColorByHealthFieldsConverter()
|
||||||
|
{
|
||||||
|
SameTypeFieldConverter<bool> enabled =
|
||||||
|
new SameTypeFieldConverter<bool>("ColorByHealth.Enabled", false);
|
||||||
|
FieldConvertersMap.Add("UseColorBasedOnHealthValue", enabled);
|
||||||
|
|
||||||
|
SameClassFieldConverter<PluginConfigColor> fullHealth =
|
||||||
|
new SameClassFieldConverter<PluginConfigColor>("ColorByHealth.FullHealthColor", new PluginConfigColor(new(0f / 255f, 255f / 255f, 0f / 255f, 100f / 100f)));
|
||||||
|
FieldConvertersMap.Add("FullHealthColor", fullHealth);
|
||||||
|
|
||||||
|
SameClassFieldConverter<PluginConfigColor> lowHealth =
|
||||||
|
new SameClassFieldConverter<PluginConfigColor>("ColorByHealth.LowHealthColor", new PluginConfigColor(new(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f)));
|
||||||
|
FieldConvertersMap.Add("LowHealthColor", lowHealth);
|
||||||
|
|
||||||
|
SameTypeFieldConverter<float> fullThreshold = new SameTypeFieldConverter<float>("ColorByHealth.FullHealthColorThreshold", 75f);
|
||||||
|
FieldConvertersMap.Add("FullHealthColorThreshold", fullThreshold);
|
||||||
|
|
||||||
|
SameTypeFieldConverter<float> lowThreshold = new SameTypeFieldConverter<float>("ColorByHealth.LowHealthColorThreshold", 25f);
|
||||||
|
FieldConvertersMap.Add("LowHealthColorThreshold", lowThreshold);
|
||||||
|
|
||||||
|
SameTypeFieldConverter<BlendMode> blendMode = new SameTypeFieldConverter<BlendMode>("ColorByHealth.BlendMode", BlendMode.LAB);
|
||||||
|
FieldConvertersMap.Add("blendMode", blendMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return objectType == typeof(PartyFramesColorsConfig) ||
|
||||||
|
objectType == typeof(UnitFrameConfig) ||
|
||||||
|
objectType == typeof(PlayerUnitFrameConfig) ||
|
||||||
|
objectType == typeof(TargetUnitFrameConfig) ||
|
||||||
|
objectType == typeof(TargetOfTargetUnitFrameConfig) ||
|
||||||
|
objectType == typeof(FocusTargetUnitFrameConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[Disableable(false)]
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Visibility")]
|
||||||
|
[SubSection("Global", 0)]
|
||||||
|
public class GlobalVisibilityConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static GlobalVisibilityConfig DefaultConfig() { return new GlobalVisibilityConfig(); }
|
||||||
|
|
||||||
|
[NestedConfig("Visibility", 50, collapsingHeader = false)]
|
||||||
|
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private bool _applying = false;
|
||||||
|
|
||||||
|
[ManualDraw]
|
||||||
|
public bool Draw(ref bool changed)
|
||||||
|
{
|
||||||
|
ImGui.NewLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Apply to all elements", new Vector2(200, 30)))
|
||||||
|
{
|
||||||
|
_applying = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_applying)
|
||||||
|
{
|
||||||
|
string[] lines = new string[] { "This will replace the visibility settings", "for ALL HSUI elements!", "Are you sure?" };
|
||||||
|
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Apply?", lines);
|
||||||
|
|
||||||
|
if (didConfirm)
|
||||||
|
{
|
||||||
|
ConfigurationManager.Instance.OnGlobalVisibilityChanged(VisibilityConfig);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didConfirm || didClose)
|
||||||
|
{
|
||||||
|
_applying = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Misc")]
|
||||||
|
[SubSection("Grid", 0)]
|
||||||
|
public class GridConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static GridConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var config = new GridConfig();
|
||||||
|
config.Enabled = false;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DragFloat("Background Alpha", min = 0, max = 1, velocity = .05f)]
|
||||||
|
[Order(10)]
|
||||||
|
public float BackgroundAlpha = 0.3f;
|
||||||
|
|
||||||
|
[Checkbox("Show Center Lines")]
|
||||||
|
[Order(15)]
|
||||||
|
public bool ShowCenterLines = true;
|
||||||
|
[Checkbox("Show Anchor Points")]
|
||||||
|
[Order(20)]
|
||||||
|
|
||||||
|
public bool ShowAnchorPoints = true;
|
||||||
|
[Checkbox("Grid Divisions", spacing = true)]
|
||||||
|
[Order(25)]
|
||||||
|
public bool ShowGrid = true;
|
||||||
|
|
||||||
|
[DragInt("Divisions Distance", min = 50, max = 500)]
|
||||||
|
[Order(30, collapseWith = nameof(ShowGrid))]
|
||||||
|
public int GridDivisionsDistance = 50;
|
||||||
|
|
||||||
|
[DragInt("Subdivision Count", min = 1, max = 10)]
|
||||||
|
[Order(35, collapseWith = nameof(ShowGrid))]
|
||||||
|
public int GridSubdivisionCount = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[Disableable(false)]
|
||||||
|
[Section("Misc")]
|
||||||
|
[SubSection("HUD Options", 0)]
|
||||||
|
public class HUDOptionsConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[Checkbox("Global HUD Position")]
|
||||||
|
[Order(5)]
|
||||||
|
public bool UseGlobalHudShift = false;
|
||||||
|
|
||||||
|
[DragInt2("Position", min = -4000, max = 4000)]
|
||||||
|
[Order(6, collapseWith = nameof(UseGlobalHudShift))]
|
||||||
|
public Vector2 HudOffset = new(0, 0);
|
||||||
|
|
||||||
|
[Checkbox("Dim HSUI's settings window when not focused")]
|
||||||
|
[Order(10)]
|
||||||
|
public bool DimConfigWindow = false;
|
||||||
|
|
||||||
|
[Checkbox("Automatically disable HUD elements preview", help = "If enabled, all HUD elements preview modes are disabled when HSUI's setting window is closed.")]
|
||||||
|
[Order(11)]
|
||||||
|
public bool AutomaticPreviewDisabling = true;
|
||||||
|
|
||||||
|
[Checkbox("Use HSUI style", help = "If enabled, HSUI will use its own style for the setting window instead of the general Dalamud style.")]
|
||||||
|
[Order(12)]
|
||||||
|
public bool OverrideDalamudStyle = true;
|
||||||
|
|
||||||
|
[Checkbox("Mouseover", separator = true)]
|
||||||
|
[Order(15)]
|
||||||
|
public bool MouseoverEnabled = true;
|
||||||
|
|
||||||
|
[Checkbox("Automatic Mode", help =
|
||||||
|
"When enabled: All your actions will automatically assume mouseover when your cursor is on top of a unit frame.\n" +
|
||||||
|
"Mouseover macros or other mouseover plugins are not necessary and WON'T WORK in this mode!\n\n" +
|
||||||
|
"When disabled: HSUI unit frames will behave like the game's ones.\n" +
|
||||||
|
"You'll need to use mouseover macros or other mouseover related plugins in this mode.")]
|
||||||
|
[Order(16, collapseWith = nameof(MouseoverEnabled))]
|
||||||
|
public bool MouseoverAutomaticMode = true;
|
||||||
|
|
||||||
|
//[Checkbox("Support Special Mouse Clicks", isMonitored = true, spacing = true, help =
|
||||||
|
// "When enabled HSUI will attempt to support special mouse binds (mousewheel, M4, M5, etc) when the cursor\n" +
|
||||||
|
// "is hovering on top of HSUI's unit frames.\n\n" +
|
||||||
|
// "If you don't have actions bound to these mouse buttons, it is adviced that you leave this feature disabled.\n\n" +
|
||||||
|
// "This feature can cause some issues such as click inputs not working in HSUI, or through out the game.\n" +
|
||||||
|
// "If you run into these kinds of issues, you can try reloading HSUI, restarting the game, or disabling this feature.")]
|
||||||
|
//[Order(17)]
|
||||||
|
public bool InputsProxyEnabled = false;
|
||||||
|
|
||||||
|
[Checkbox("Hide Default HUD When Replaced", isMonitored = true, separator = true, help =
|
||||||
|
"When enabled, HSUI automatically hides the default game HUD elements that HSUI replaces.\n" +
|
||||||
|
"For example: when HSUI hotbars are on, game hotbars are hidden; when HSUI unit frames are on, game parameter/target bars are hidden; etc.")]
|
||||||
|
[Order(38)]
|
||||||
|
public bool HideDefaultHudWhenReplaced = true;
|
||||||
|
|
||||||
|
[Checkbox("Hide Default Job Gauges", isMonitored = true)]
|
||||||
|
[Order(40)]
|
||||||
|
public bool HideDefaultJobGauges = false;
|
||||||
|
|
||||||
|
[Checkbox("Hide Default Castbar", isMonitored = true)]
|
||||||
|
[Order(45)]
|
||||||
|
public bool HideDefaultCastbar = false;
|
||||||
|
|
||||||
|
[Checkbox("Hide Default Pulltimer", isMonitored = true)]
|
||||||
|
[Order(50)]
|
||||||
|
public bool HideDefaultPulltimer = false;
|
||||||
|
|
||||||
|
[Checkbox("Use Regional Number Format", help = "When enabled, HSUI will use your system's regional format settings when showing numbers.\nWhen disabled, HSUI will use English number formatting instead.", separator = true)]
|
||||||
|
[Order(60)]
|
||||||
|
public bool UseRegionalNumberFormats = true;
|
||||||
|
|
||||||
|
public new static HUDOptionsConfig DefaultConfig() => new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HUDOptionsConfigConverter : PluginConfigObjectConverter
|
||||||
|
{
|
||||||
|
public HUDOptionsConfigConverter()
|
||||||
|
{
|
||||||
|
Func<Vector2, Vector2[]> func = (value) =>
|
||||||
|
{
|
||||||
|
Vector2[] array = new Vector2[4];
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
array[i] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
};
|
||||||
|
|
||||||
|
TypeToClassFieldConverter<Vector2, Vector2[]> castBar = new TypeToClassFieldConverter<Vector2, Vector2[]>(
|
||||||
|
"CastBarOriginalPositions",
|
||||||
|
new Vector2[] { Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero },
|
||||||
|
func
|
||||||
|
);
|
||||||
|
|
||||||
|
TypeToClassFieldConverter<Vector2, Vector2[]> pullTimer = new TypeToClassFieldConverter<Vector2, Vector2[]>(
|
||||||
|
"PulltimerOriginalPositions",
|
||||||
|
new Vector2[] { Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero },
|
||||||
|
func
|
||||||
|
);
|
||||||
|
|
||||||
|
NewClassFieldConverter<Dictionary<string, Vector2>, Dictionary<string, Vector2>[]> jobGauge =
|
||||||
|
new NewClassFieldConverter<Dictionary<string, Vector2>, Dictionary<string, Vector2>[]>(
|
||||||
|
"JobGaugeOriginalPositions",
|
||||||
|
new Dictionary<string, Vector2>[] { new(), new(), new(), new() },
|
||||||
|
(oldValue) =>
|
||||||
|
{
|
||||||
|
Dictionary<string, Vector2>[] array = new Dictionary<string, Vector2>[4];
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
array[i] = oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
});
|
||||||
|
|
||||||
|
FieldConvertersMap.Add("CastBarOriginalPosition", castBar);
|
||||||
|
FieldConvertersMap.Add("PulltimerOriginalPosition", pullTimer);
|
||||||
|
FieldConvertersMap.Add("JobGaugeOriginalPosition", jobGauge);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return objectType == typeof(HUDOptionsConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Config for a single hotbar (1-10). HotbarIndex is set by the containing config.
|
||||||
|
/// </summary>
|
||||||
|
public class HotbarBarConfig : AnchorablePluginConfigObject
|
||||||
|
{
|
||||||
|
internal int HotbarIndex { get; set; } = 1;
|
||||||
|
|
||||||
|
[RadioSelector("12×1", "6×2", "4×3", "3×4", "2×6", "1×12", Label = "Bar Layout")]
|
||||||
|
[Order(20)]
|
||||||
|
public int BarLayout = 0; // 0=12x1, 1=6x2, 2=4x3, 3=3x4, 4=2x6, 5=1x12
|
||||||
|
|
||||||
|
/// <summary>Get (columns, rows) for the current BarLayout.</summary>
|
||||||
|
public (int Cols, int Rows) GetLayoutGrid() => BarLayout switch
|
||||||
|
{
|
||||||
|
0 => (12, 1),
|
||||||
|
1 => (6, 2),
|
||||||
|
2 => (4, 3),
|
||||||
|
3 => (3, 4),
|
||||||
|
4 => (2, 6),
|
||||||
|
5 => (1, 12),
|
||||||
|
_ => (12, 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
[DragInt("Slot Count", min = 1, max = 12)]
|
||||||
|
[Order(21)]
|
||||||
|
public int SlotCount = 12;
|
||||||
|
|
||||||
|
[DragInt2("Slot Size", min = 24, max = 96)]
|
||||||
|
[Order(22)]
|
||||||
|
public Vector2 SlotSize = new Vector2(40, 40);
|
||||||
|
|
||||||
|
[DragInt("Slot Padding", min = 0, max = 16)]
|
||||||
|
[Order(23)]
|
||||||
|
public int SlotPadding = 2;
|
||||||
|
|
||||||
|
[Checkbox("Show Cooldown Overlay")]
|
||||||
|
[Order(24)]
|
||||||
|
public bool ShowCooldownOverlay = true;
|
||||||
|
|
||||||
|
[Checkbox("Show Cooldown Numbers")]
|
||||||
|
[Order(25)]
|
||||||
|
public bool ShowCooldownNumbers = true;
|
||||||
|
|
||||||
|
[Checkbox("Show Border")]
|
||||||
|
[Order(26)]
|
||||||
|
public bool ShowBorder = true;
|
||||||
|
|
||||||
|
[Checkbox("Show Tooltips")]
|
||||||
|
[Order(27)]
|
||||||
|
public bool ShowTooltips = true;
|
||||||
|
|
||||||
|
[Checkbox("Show Keybinds")]
|
||||||
|
[Order(28)]
|
||||||
|
public bool ShowSlotNumbers = true;
|
||||||
|
|
||||||
|
[Checkbox("Show Combo Highlight")]
|
||||||
|
[Order(29)]
|
||||||
|
public bool ShowComboHighlight = true;
|
||||||
|
|
||||||
|
[Checkbox("Debug Drag & Drop")]
|
||||||
|
[Order(30)]
|
||||||
|
public bool DebugDragDrop = false;
|
||||||
|
|
||||||
|
[NestedConfig("Visibility", 70)]
|
||||||
|
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
|
||||||
|
|
||||||
|
public static HotbarBarConfig DefaultConfig(int hotbarIndex)
|
||||||
|
{
|
||||||
|
var config = new HotbarBarConfig { HotbarIndex = hotbarIndex };
|
||||||
|
ApplyDefaults(config, hotbarIndex);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void ApplyDefaults(HotbarBarConfig config, int hotbarIndex)
|
||||||
|
{
|
||||||
|
var viewport = ImGui.GetMainViewport().Size;
|
||||||
|
float yOffset = 60 + (hotbarIndex - 1) * 50;
|
||||||
|
config.Position = new Vector2(0, -viewport.Y * 0.5f + yOffset);
|
||||||
|
config.Anchor = DrawAnchor.Top;
|
||||||
|
config.Size = new Vector2(12 * 40 + 11 * 2, 40);
|
||||||
|
config.HotbarIndex = hotbarIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Hotbars", true)]
|
||||||
|
[SubSection("General", 0)]
|
||||||
|
public class HotbarsConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[Checkbox("Use HSUI Hotbars", help = "When enabled, HSUI hotbars replace the default game hotbars.")]
|
||||||
|
[Order(1, collapseWith = null)]
|
||||||
|
public new bool Enabled = true;
|
||||||
|
|
||||||
|
[NestedConfig("Drag & Drop Options", 5, separator = true, collapseWith = null)]
|
||||||
|
public HotbarsGeneralOptionsConfig GeneralOptions = new();
|
||||||
|
|
||||||
|
public new static HotbarsConfig DefaultConfig() => new HotbarsConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HotbarsGeneralOptionsConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[Checkbox("Enable drag and drop from game UI", help = "When enabled, you can drag actions, macros, and items from the Actions menu, Macro menu, and Inventory onto HSUI hotbars.")]
|
||||||
|
[Order(1)]
|
||||||
|
public bool EnableDragDropFromGame = true;
|
||||||
|
|
||||||
|
[Checkbox("Enable Shift+drag to rearrange", help = "When enabled, holding Shift and dragging a hotbar slot lets you swap it with another slot or rearrange your hotbar.")]
|
||||||
|
[Order(2)]
|
||||||
|
public bool EnableShiftDragToRearrange = true;
|
||||||
|
|
||||||
|
[Checkbox("Enable release outside to clear slot", help = "When enabled, releasing a picked-up icon outside of HSUI hotbars clears that slot.")]
|
||||||
|
[Order(3)]
|
||||||
|
public bool EnableReleaseOutsideToClear = true;
|
||||||
|
|
||||||
|
public new static HotbarsGeneralOptionsConfig DefaultConfig() => new();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Hotbars", true)]
|
||||||
|
[SubSection("Hotbar 1", 0)]
|
||||||
|
public class Hotbar1BarConfig : HotbarBarConfig
|
||||||
|
{
|
||||||
|
public Hotbar1BarConfig() => HotbarIndex = 1;
|
||||||
|
public new static Hotbar1BarConfig DefaultConfig() { var c = new Hotbar1BarConfig(); ApplyDefaults(c, 1); return c; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Hotbars", true)]
|
||||||
|
[SubSection("Hotbar 2", 0)]
|
||||||
|
public class Hotbar2BarConfig : HotbarBarConfig
|
||||||
|
{
|
||||||
|
public Hotbar2BarConfig() => HotbarIndex = 2;
|
||||||
|
public new static Hotbar2BarConfig DefaultConfig() { var c = new Hotbar2BarConfig(); ApplyDefaults(c, 2); return c; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Hotbars", true)]
|
||||||
|
[SubSection("Hotbar 3", 0)]
|
||||||
|
public class Hotbar3BarConfig : HotbarBarConfig
|
||||||
|
{
|
||||||
|
public Hotbar3BarConfig() => HotbarIndex = 3;
|
||||||
|
public new static Hotbar3BarConfig DefaultConfig() { var c = new Hotbar3BarConfig(); ApplyDefaults(c, 3); return c; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Hotbars", true)]
|
||||||
|
[SubSection("Hotbar 4", 0)]
|
||||||
|
public class Hotbar4BarConfig : HotbarBarConfig
|
||||||
|
{
|
||||||
|
public Hotbar4BarConfig() => HotbarIndex = 4;
|
||||||
|
public new static Hotbar4BarConfig DefaultConfig() { var c = new Hotbar4BarConfig(); ApplyDefaults(c, 4); return c; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Hotbars", true)]
|
||||||
|
[SubSection("Hotbar 5", 0)]
|
||||||
|
public class Hotbar5BarConfig : HotbarBarConfig
|
||||||
|
{
|
||||||
|
public Hotbar5BarConfig() => HotbarIndex = 5;
|
||||||
|
public new static Hotbar5BarConfig DefaultConfig() { var c = new Hotbar5BarConfig(); ApplyDefaults(c, 5); return c; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Hotbars", true)]
|
||||||
|
[SubSection("Hotbar 6", 0)]
|
||||||
|
public class Hotbar6BarConfig : HotbarBarConfig
|
||||||
|
{
|
||||||
|
public Hotbar6BarConfig() => HotbarIndex = 6;
|
||||||
|
public new static Hotbar6BarConfig DefaultConfig() { var c = new Hotbar6BarConfig(); ApplyDefaults(c, 6); return c; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Hotbars", true)]
|
||||||
|
[SubSection("Hotbar 7", 0)]
|
||||||
|
public class Hotbar7BarConfig : HotbarBarConfig
|
||||||
|
{
|
||||||
|
public Hotbar7BarConfig() => HotbarIndex = 7;
|
||||||
|
public new static Hotbar7BarConfig DefaultConfig() { var c = new Hotbar7BarConfig(); ApplyDefaults(c, 7); return c; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Hotbars", true)]
|
||||||
|
[SubSection("Hotbar 8", 0)]
|
||||||
|
public class Hotbar8BarConfig : HotbarBarConfig
|
||||||
|
{
|
||||||
|
public Hotbar8BarConfig() => HotbarIndex = 8;
|
||||||
|
public new static Hotbar8BarConfig DefaultConfig() { var c = new Hotbar8BarConfig(); ApplyDefaults(c, 8); return c; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Hotbars", true)]
|
||||||
|
[SubSection("Hotbar 9", 0)]
|
||||||
|
public class Hotbar9BarConfig : HotbarBarConfig
|
||||||
|
{
|
||||||
|
public Hotbar9BarConfig() => HotbarIndex = 9;
|
||||||
|
public new static Hotbar9BarConfig DefaultConfig() { var c = new Hotbar9BarConfig(); ApplyDefaults(c, 9); return c; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Hotbars", true)]
|
||||||
|
[SubSection("Hotbar 10", 0)]
|
||||||
|
public class Hotbar10BarConfig : HotbarBarConfig
|
||||||
|
{
|
||||||
|
public Hotbar10BarConfig() => HotbarIndex = 10;
|
||||||
|
public new static Hotbar10BarConfig DefaultConfig() { var c = new Hotbar10BarConfig(); ApplyDefaults(c, 10); return c; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[Disableable(false)]
|
||||||
|
[Exportable(false)]
|
||||||
|
[Section("Visibility")]
|
||||||
|
[SubSection("Hotbars", 0)]
|
||||||
|
public class HotbarsVisibilityConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static HotbarsVisibilityConfig DefaultConfig() { return new HotbarsVisibilityConfig(); }
|
||||||
|
|
||||||
|
[NestedConfig("Hotbar 1", 50)]
|
||||||
|
public VisibilityConfig HotbarConfig1 = new VisibilityConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Hotbar 2", 51)]
|
||||||
|
public VisibilityConfig HotbarConfig2 = new VisibilityConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Hotbar 3", 52)]
|
||||||
|
public VisibilityConfig HotbarConfig3 = new VisibilityConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Hotbar 4", 53)]
|
||||||
|
public VisibilityConfig HotbarConfig4 = new VisibilityConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Hotbar 5", 54)]
|
||||||
|
public VisibilityConfig HotbarConfig5 = new VisibilityConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Hotbar 6", 55)]
|
||||||
|
public VisibilityConfig HotbarConfig6 = new VisibilityConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Hotbar 7", 56)]
|
||||||
|
public VisibilityConfig HotbarConfig7 = new VisibilityConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Hotbar 8", 57)]
|
||||||
|
public VisibilityConfig HotbarConfig8 = new VisibilityConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Hotbar 9", 58)]
|
||||||
|
public VisibilityConfig HotbarConfig9 = new VisibilityConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Hotbar 10", 59)]
|
||||||
|
public VisibilityConfig HotbarConfig10 = new VisibilityConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Cross Hotbar", 60)]
|
||||||
|
public VisibilityConfig HotbarConfigCross = new VisibilityConfig();
|
||||||
|
|
||||||
|
private List<VisibilityConfig> _configs;
|
||||||
|
public List<VisibilityConfig> GetHotbarConfigs() => _configs;
|
||||||
|
|
||||||
|
public HotbarsVisibilityConfig()
|
||||||
|
{
|
||||||
|
_configs = new List<VisibilityConfig>() {
|
||||||
|
HotbarConfig1,
|
||||||
|
HotbarConfig2,
|
||||||
|
HotbarConfig3,
|
||||||
|
HotbarConfig4,
|
||||||
|
HotbarConfig5,
|
||||||
|
HotbarConfig6,
|
||||||
|
HotbarConfig7,
|
||||||
|
HotbarConfig8,
|
||||||
|
HotbarConfig9,
|
||||||
|
HotbarConfig10
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Party;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public class IconConfig : AnchorablePluginConfigObject
|
||||||
|
{
|
||||||
|
[Anchor("Frame Anchor")]
|
||||||
|
[Order(16)]
|
||||||
|
public DrawAnchor FrameAnchor = DrawAnchor.Center;
|
||||||
|
|
||||||
|
// don't remove (used by json converter)
|
||||||
|
public IconConfig()
|
||||||
|
{
|
||||||
|
Strata = StrataLevel.MID_HIGH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
|
||||||
|
{
|
||||||
|
Position = position;
|
||||||
|
Size = size;
|
||||||
|
Anchor = anchor;
|
||||||
|
FrameAnchor = frameAnchor;
|
||||||
|
|
||||||
|
Strata = StrataLevel.MID_HIGH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IconWithLabelConfig : IconConfig
|
||||||
|
{
|
||||||
|
[NestedConfig("Label", 20)]
|
||||||
|
public NumericLabelConfig NumericLabel = new NumericLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
|
||||||
|
|
||||||
|
public IconWithLabelConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
|
||||||
|
: base(position, size, anchor, frameAnchor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RoleJobIconConfig : IconConfig
|
||||||
|
{
|
||||||
|
public RoleJobIconConfig() : base() { }
|
||||||
|
|
||||||
|
public RoleJobIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
|
||||||
|
: base(position, size, anchor, frameAnchor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Combo("Style", "Style 1", "Style 2", "Style 3", spacing = true)]
|
||||||
|
[Order(25)]
|
||||||
|
public int Style = 0;
|
||||||
|
|
||||||
|
[Checkbox("Use Role Icons", spacing = true)]
|
||||||
|
[Order(30)]
|
||||||
|
public bool UseRoleIcons = false;
|
||||||
|
|
||||||
|
[Checkbox("Use Specific DPS Role Icons")]
|
||||||
|
[Order(35, collapseWith = nameof(UseRoleIcons))]
|
||||||
|
public bool UseSpecificDPSRoleIcons = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SignIconConfig : IconConfig
|
||||||
|
{
|
||||||
|
public SignIconConfig() : base() { }
|
||||||
|
|
||||||
|
public SignIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
|
||||||
|
: base(position, size, anchor, frameAnchor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Checkbox("Preview")]
|
||||||
|
[Order(35)]
|
||||||
|
public bool Preview = false;
|
||||||
|
|
||||||
|
public uint? IconID(IGameObject? actor)
|
||||||
|
{
|
||||||
|
if (Preview)
|
||||||
|
{
|
||||||
|
return 61231;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.SignIconIDForActor(actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NameplateIconConfig : IconConfig
|
||||||
|
{
|
||||||
|
public NameplateIconConfig() : base() { }
|
||||||
|
|
||||||
|
public NameplateIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
|
||||||
|
: base(position, size, anchor, frameAnchor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Combo("Nameplate Label Anchor", new string[] { "Name", "Title", "Highest", "Lowest" }, spacing = true)]
|
||||||
|
[Order(17)]
|
||||||
|
public NameplateLabelAnchor NameplateLabelAnchor = NameplateLabelAnchor.Name;
|
||||||
|
|
||||||
|
[Checkbox("Prioritize Health Bar as Anchor when visible", help = "When enabled, the icon will anchor to the Health Bar if it's visible.\nIf the Health Bar disappears, it will anchor back to the desired label.")]
|
||||||
|
[Order(18)]
|
||||||
|
public bool PrioritizeHealthBarAnchor = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NameplatePlayerIconConfig : NameplateIconConfig
|
||||||
|
{
|
||||||
|
public NameplatePlayerIconConfig() : base() { }
|
||||||
|
|
||||||
|
public NameplatePlayerIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
|
||||||
|
: base(position, size, anchor, frameAnchor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Checkbox("Only show disconnected icon", spacing = true)]
|
||||||
|
[Order(19)]
|
||||||
|
public bool OnlyShowDisconnected = false;
|
||||||
|
|
||||||
|
public bool ShouldDrawIcon(int iconId)
|
||||||
|
{
|
||||||
|
if (!OnlyShowDisconnected) { return true; }
|
||||||
|
|
||||||
|
return (iconId >= 61503 && iconId <= 61505) ||
|
||||||
|
(iconId >= 61553 && iconId <= 61555);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NameplateRoleJobIconConfig : RoleJobIconConfig
|
||||||
|
{
|
||||||
|
public NameplateRoleJobIconConfig() : base() { }
|
||||||
|
|
||||||
|
public NameplateRoleJobIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
|
||||||
|
: base(position, size, anchor, frameAnchor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Combo("Nameplate Label Anchor", new string[] { "Name", "Title", "Highest", "Lowest" }, spacing = true)]
|
||||||
|
[Order(17)]
|
||||||
|
public NameplateLabelAnchor NameplateLabelAnchor = NameplateLabelAnchor.Name;
|
||||||
|
|
||||||
|
[Checkbox("Prioritize Health Bar as Anchor when visible", help = "When enabled, the icon will anchor to the Health Bar if it's visible.\nIf the Health Bar disappears, it will anchor back to the desired label.")]
|
||||||
|
[Order(18)]
|
||||||
|
public bool PrioritizeHealthBarAnchor = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PartyFramesIconsConverter : PluginConfigObjectConverter
|
||||||
|
{
|
||||||
|
public PartyFramesIconsConverter()
|
||||||
|
{
|
||||||
|
SameTypeFieldConverter<DrawAnchor> converter = new SameTypeFieldConverter<DrawAnchor>("FrameAnchor", DrawAnchor.Center);
|
||||||
|
FieldConvertersMap.Add("HealthBarAnchor", converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return objectType == typeof(PartyFramesRoleIconConfig) ||
|
||||||
|
objectType == typeof(PartyFramesLeaderIconConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum NameplateLabelAnchor
|
||||||
|
{
|
||||||
|
Name = 0,
|
||||||
|
Title = 1,
|
||||||
|
Highest = 2,
|
||||||
|
Lowest = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
using Dalamud.Interface;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[Exportable(false)]
|
||||||
|
public class EditableLabelConfig : LabelConfig
|
||||||
|
{
|
||||||
|
[InputText("Text")]
|
||||||
|
[Order(10)]
|
||||||
|
public string Text;
|
||||||
|
|
||||||
|
public EditableLabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
|
||||||
|
: base(position, text, frameAnchor, textAnchor)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetText() => Text;
|
||||||
|
|
||||||
|
public override void SetText(string text)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class EditableNonFormattableLabelConfig : LabelConfig
|
||||||
|
{
|
||||||
|
[InputText("Text", formattable = false)]
|
||||||
|
[Order(10)]
|
||||||
|
public string Text;
|
||||||
|
|
||||||
|
public EditableNonFormattableLabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
|
||||||
|
: base(position, text, frameAnchor, textAnchor)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetText() => Text;
|
||||||
|
|
||||||
|
public override void SetText(string text)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class NumericLabelConfig : LabelConfig
|
||||||
|
{
|
||||||
|
[Combo("Number Format", "No Decimals (i.e. \"12\")", "One Decimal (i.e. \"12.3\")", "Two Decimals (i.e. \"12.34\")")]
|
||||||
|
[Order(10)]
|
||||||
|
public int NumberFormat;
|
||||||
|
|
||||||
|
[Combo("Rounding Mode", "Truncate", "Floor", "Ceil", "Round")]
|
||||||
|
[Order(15)]
|
||||||
|
public int NumberFunction;
|
||||||
|
|
||||||
|
[Checkbox("Hide Text When Zero")]
|
||||||
|
[Order(65)]
|
||||||
|
public bool HideIfZero = false;
|
||||||
|
|
||||||
|
public NumericLabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
|
||||||
|
: base(position, text, frameAnchor, textAnchor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValue(float value)
|
||||||
|
{
|
||||||
|
if (value == 0)
|
||||||
|
{
|
||||||
|
_text = HideIfZero ? string.Empty : "0";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int aux = (int)Math.Pow(10, NumberFormat);
|
||||||
|
double textValue = value * aux;
|
||||||
|
|
||||||
|
textValue = NumberFunction switch
|
||||||
|
{
|
||||||
|
0 => Math.Truncate(textValue),
|
||||||
|
1 => Math.Floor(textValue),
|
||||||
|
2 => Math.Ceiling(textValue),
|
||||||
|
3 => Math.Round(textValue),
|
||||||
|
var _ => Math.Truncate(textValue)
|
||||||
|
};
|
||||||
|
|
||||||
|
double v = textValue / aux;
|
||||||
|
_text = v.ToString($"F{NumberFormat}", ConfigurationManager.Instance.ActiveCultreInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override NumericLabelConfig Clone(int index) =>
|
||||||
|
new NumericLabelConfig(Position, _text, FrameAnchor, TextAnchor)
|
||||||
|
{
|
||||||
|
Color = Color,
|
||||||
|
OutlineColor = OutlineColor,
|
||||||
|
ShadowConfig = ShadowConfig,
|
||||||
|
ShowOutline = ShowOutline,
|
||||||
|
FontID = FontID,
|
||||||
|
UseJobColor = UseJobColor,
|
||||||
|
Enabled = Enabled,
|
||||||
|
HideIfZero = HideIfZero,
|
||||||
|
ID = ID + "_{index}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("FontID")]
|
||||||
|
[Exportable(false)]
|
||||||
|
public class IconLabelConfig : LabelConfig
|
||||||
|
{
|
||||||
|
[DragFloat("Scale", min = 1, max = 5, velocity = 0.05f)]
|
||||||
|
[Order(11)]
|
||||||
|
public float FontScale = 1;
|
||||||
|
|
||||||
|
public FontAwesomeIcon IconId;
|
||||||
|
|
||||||
|
public IconLabelConfig(Vector2 position, FontAwesomeIcon iconId, DrawAnchor frameAnchor, DrawAnchor textAnchor) : base(position, "", frameAnchor, textAnchor)
|
||||||
|
{
|
||||||
|
IconId = iconId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetText() => IconId.ToIconString();
|
||||||
|
public override float GetFontScale() => FontScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("FontID")]
|
||||||
|
[Exportable(false)]
|
||||||
|
public class DefaultFontLabelConfig : LabelConfig
|
||||||
|
{
|
||||||
|
[DragFloat("Scale", min = 1, max = 5, velocity = 0.05f)]
|
||||||
|
[Order(11)]
|
||||||
|
public float FontScale = 1;
|
||||||
|
|
||||||
|
public DefaultFontLabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
|
||||||
|
: base(position, text, frameAnchor, textAnchor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool UseSystemFont() => true;
|
||||||
|
public override float GetFontScale() => FontScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class LabelConfig : MovablePluginConfigObject
|
||||||
|
{
|
||||||
|
[JsonIgnore] protected string _text;
|
||||||
|
|
||||||
|
[Font]
|
||||||
|
[Order(15)]
|
||||||
|
public string? FontID = null;
|
||||||
|
|
||||||
|
[Anchor("Frame Anchor")]
|
||||||
|
[Order(20)]
|
||||||
|
public DrawAnchor FrameAnchor = DrawAnchor.Center;
|
||||||
|
|
||||||
|
[Anchor("Text Anchor")]
|
||||||
|
[Order(25)]
|
||||||
|
public DrawAnchor TextAnchor = DrawAnchor.TopLeft;
|
||||||
|
|
||||||
|
[ColorEdit4("Color ##Text")]
|
||||||
|
[Order(30)]
|
||||||
|
public PluginConfigColor Color = new PluginConfigColor(Vector4.One);
|
||||||
|
|
||||||
|
[Checkbox("Outline")]
|
||||||
|
[Order(35)]
|
||||||
|
public bool ShowOutline = true;
|
||||||
|
|
||||||
|
[ColorEdit4("Color ##Outline")]
|
||||||
|
[Order(40, collapseWith = nameof(ShowOutline))]
|
||||||
|
public PluginConfigColor OutlineColor = new PluginConfigColor(Vector4.UnitW);
|
||||||
|
|
||||||
|
[NestedConfig("Shadow", 45)]
|
||||||
|
public ShadowConfig ShadowConfig = new ShadowConfig() { Enabled = false };
|
||||||
|
|
||||||
|
[Checkbox("Use Job Color", spacing = true)]
|
||||||
|
[Order(60)]
|
||||||
|
public bool UseJobColor = false;
|
||||||
|
|
||||||
|
[Checkbox("Use Role Color")]
|
||||||
|
[Order(65)]
|
||||||
|
public bool UseRoleColor = false;
|
||||||
|
|
||||||
|
public LabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
|
||||||
|
{
|
||||||
|
Position = position;
|
||||||
|
_text = text;
|
||||||
|
FrameAnchor = frameAnchor;
|
||||||
|
TextAnchor = textAnchor;
|
||||||
|
Position = position;
|
||||||
|
|
||||||
|
Strata = StrataLevel.HIGHEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetText() => _text;
|
||||||
|
|
||||||
|
public virtual void SetText(string text)
|
||||||
|
{
|
||||||
|
_text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual PluginConfigColor GetColor() => Color;
|
||||||
|
|
||||||
|
public virtual PluginConfigColor GetOutlineColor() => OutlineColor;
|
||||||
|
|
||||||
|
public virtual bool UseSystemFont() => false;
|
||||||
|
public virtual float GetFontScale() => 1;
|
||||||
|
|
||||||
|
public virtual LabelConfig Clone(int index) =>
|
||||||
|
new LabelConfig(Position, _text, FrameAnchor, TextAnchor)
|
||||||
|
{
|
||||||
|
Color = Color,
|
||||||
|
OutlineColor = OutlineColor,
|
||||||
|
ShadowConfig = ShadowConfig,
|
||||||
|
ShowOutline = ShowOutline,
|
||||||
|
FontID = FontID,
|
||||||
|
UseJobColor = UseJobColor,
|
||||||
|
Enabled = Enabled,
|
||||||
|
ID = ID + "_{index}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public class LabelHud : HudElement
|
||||||
|
{
|
||||||
|
private LabelConfig Config => (LabelConfig)_config;
|
||||||
|
|
||||||
|
public LabelHud(LabelConfig config) : base(config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CreateDrawActions(Vector2 origin)
|
||||||
|
{
|
||||||
|
// unused
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw(Vector2 origin)
|
||||||
|
{
|
||||||
|
Draw(origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Draw(
|
||||||
|
Vector2 origin,
|
||||||
|
Vector2? parentSize = null,
|
||||||
|
IGameObject? actor = null,
|
||||||
|
string? actorName = null,
|
||||||
|
uint? actorCurrentHp = null,
|
||||||
|
uint? actorMaxHp = null,
|
||||||
|
bool? isPlayerName = null,
|
||||||
|
string? title = null)
|
||||||
|
{
|
||||||
|
if (!Config.Enabled || Config.GetText() == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? text = actor == null && actorName == null && actorCurrentHp == null && actorMaxHp == null && title == null ?
|
||||||
|
Config.GetText() :
|
||||||
|
TextTagsHelper.FormattedText(Config.GetText(), actor, actorName, actorCurrentHp, actorMaxHp, isPlayerName, title);
|
||||||
|
|
||||||
|
DrawLabel(text, origin, parentSize ?? Vector2.Zero, actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void DrawLabel(string text, Vector2 parentPos, Vector2 parentSize, IGameObject? actor = null)
|
||||||
|
{
|
||||||
|
Vector2 size;
|
||||||
|
Vector2 pos;
|
||||||
|
|
||||||
|
if (Config.UseSystemFont())
|
||||||
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.DefaultFont);
|
||||||
|
size = ImGui.CalcTextSize(text) * Config.GetFontScale();
|
||||||
|
pos = Utils.GetAnchoredPosition(Utils.GetAnchoredPosition(parentPos + Config.Position, -parentSize, Config.FrameAnchor), size, Config.TextAnchor);
|
||||||
|
ImGui.PopFont();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (FontsManager.Instance.PushFont(Config.FontID))
|
||||||
|
{
|
||||||
|
size = ImGui.CalcTextSize(text) * Config.GetFontScale();
|
||||||
|
pos = Utils.GetAnchoredPosition(Utils.GetAnchoredPosition(parentPos + Config.Position, -parentSize, Config.FrameAnchor), size, Config.TextAnchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawLabel(text, pos, size, Color(actor));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawLabel(string text, Vector2 pos, Vector2 size, PluginConfigColor color, float? alpha = null)
|
||||||
|
{
|
||||||
|
if (!Config.Enabled) { return; }
|
||||||
|
|
||||||
|
PluginConfigColor fillColor = color;
|
||||||
|
PluginConfigColor shadowColor = Config.ShadowConfig.Color;
|
||||||
|
PluginConfigColor outlineColor = Config.GetOutlineColor();
|
||||||
|
|
||||||
|
if (alpha.HasValue)
|
||||||
|
{
|
||||||
|
fillColor = fillColor.WithAlpha(alpha.Value);
|
||||||
|
shadowColor = shadowColor.WithAlpha(alpha.Value);
|
||||||
|
outlineColor = outlineColor.WithAlpha(alpha.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action<ImDrawListPtr> action = (ImDrawListPtr drawList) =>
|
||||||
|
{
|
||||||
|
if (Config.ShadowConfig.Enabled)
|
||||||
|
{
|
||||||
|
DrawHelper.DrawShadowText(text, pos, fillColor.Base, shadowColor.Base, drawList, Config.ShadowConfig.Offset, Config.ShadowConfig.Thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.ShowOutline)
|
||||||
|
{
|
||||||
|
DrawHelper.DrawOutlinedText(text, pos, fillColor.Base, outlineColor.Base, drawList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.ShowOutline && !Config.ShadowConfig.Enabled)
|
||||||
|
{
|
||||||
|
drawList.AddText(pos, fillColor.Base, text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DrawHelper.DrawInWindow(ID, pos, size, false, (drawList) =>
|
||||||
|
{
|
||||||
|
if (Config.UseSystemFont())
|
||||||
|
{
|
||||||
|
ImGui.SetWindowFontScale(Config.GetFontScale());
|
||||||
|
ImGui.PushFont(UiBuilder.DefaultFont);
|
||||||
|
action(drawList);
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGui.SetWindowFontScale(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (FontsManager.Instance.PushFont(Config.FontID))
|
||||||
|
{
|
||||||
|
action(drawList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual PluginConfigColor Color(IGameObject? actor = null)
|
||||||
|
{
|
||||||
|
switch (Config.UseJobColor)
|
||||||
|
{
|
||||||
|
case true when (actor is ICharacter || actor is IBattleNpc battleNpc && battleNpc.ClassJob.RowId > 0):
|
||||||
|
return ColorUtils.ColorForActor(actor);
|
||||||
|
case true when actor is not ICharacter:
|
||||||
|
return GlobalColors.Instance.NPCFriendlyColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Config.UseRoleColor)
|
||||||
|
{
|
||||||
|
case true when (actor is ICharacter || actor is IBattleNpc battleNpc && battleNpc.ClassJob.RowId > 0):
|
||||||
|
{
|
||||||
|
ICharacter? character = actor as ICharacter;
|
||||||
|
return character != null && character.ClassJob.RowId > 0 ?
|
||||||
|
GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId) :
|
||||||
|
ColorUtils.ColorForActor(character);
|
||||||
|
}
|
||||||
|
case true when actor is not ICharacter:
|
||||||
|
return GlobalColors.Instance.NPCFriendlyColor;
|
||||||
|
default:
|
||||||
|
return Config.GetColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual (string, Vector2, Vector2, PluginConfigColor) PreCalculate(
|
||||||
|
Vector2 origin,
|
||||||
|
Vector2? parentSize = null,
|
||||||
|
IGameObject? actor = null,
|
||||||
|
string? actorName = null,
|
||||||
|
uint? actorCurrentHp = null,
|
||||||
|
uint? actorMaxHp = null,
|
||||||
|
bool? isPlayerName = null,
|
||||||
|
string? title = null)
|
||||||
|
{
|
||||||
|
if (!Config.Enabled || Config.GetText() == null)
|
||||||
|
{
|
||||||
|
return ("", Vector2.Zero, Vector2.Zero, Color(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
string? text = actor == null && actorName == null && actorCurrentHp == null && actorMaxHp == null && title == null ?
|
||||||
|
Config.GetText() :
|
||||||
|
TextTagsHelper.FormattedText(Config.GetText(), actor, actorName, actorCurrentHp, actorMaxHp, isPlayerName, title);
|
||||||
|
|
||||||
|
Vector2 pSize = parentSize ?? Vector2.Zero;
|
||||||
|
Vector2 size;
|
||||||
|
Vector2 pos;
|
||||||
|
|
||||||
|
if (Config.UseSystemFont())
|
||||||
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.DefaultFont);
|
||||||
|
size = ImGui.CalcTextSize(text) * Config.GetFontScale();
|
||||||
|
pos = Utils.GetAnchoredPosition(Utils.GetAnchoredPosition(origin + Config.Position, -pSize, Config.FrameAnchor), size, Config.TextAnchor);
|
||||||
|
ImGui.PopFont();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (FontsManager.Instance.PushFont(Config.FontID))
|
||||||
|
{
|
||||||
|
size = ImGui.CalcTextSize(text) * Config.GetFontScale();
|
||||||
|
pos = Utils.GetAnchoredPosition(Utils.GetAnchoredPosition(origin + Config.Position, -pSize, Config.FrameAnchor), size, Config.TextAnchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (text, pos, size, Color(actor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IconLabelHud : LabelHud
|
||||||
|
{
|
||||||
|
private IconLabelConfig Config => (IconLabelConfig)_config;
|
||||||
|
|
||||||
|
public IconLabelHud(IconLabelConfig config) : base(config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw(Vector2 origin,
|
||||||
|
Vector2? parentSize = null,
|
||||||
|
IGameObject? actor = null,
|
||||||
|
string? actorName = null,
|
||||||
|
uint? actorCurrentHp = null,
|
||||||
|
uint? actorMaxHp = null,
|
||||||
|
bool? isPlayerName = null,
|
||||||
|
string? title = null)
|
||||||
|
{
|
||||||
|
string? text = Config.GetText();
|
||||||
|
if (!Config.Enabled || text == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawLabel(text, origin, parentSize ?? Vector2.Zero, actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawLabel(string text, Vector2 parentPos, Vector2 parentSize, IGameObject? actor = null)
|
||||||
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
Vector2 size = ImGui.CalcTextSize(text) * Config.GetFontScale();
|
||||||
|
Vector2 pos = Utils.GetAnchoredPosition(Utils.GetAnchoredPosition(parentPos + Config.Position, -parentSize, Config.FrameAnchor), size, Config.TextAnchor);
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
DrawHelper.DrawInWindow(ID, pos, size, false, (drawList) =>
|
||||||
|
{
|
||||||
|
ImGui.SetWindowFontScale(Config.GetFontScale());
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
|
||||||
|
PluginConfigColor? color = Color(actor);
|
||||||
|
|
||||||
|
if (Config.ShadowConfig.Enabled)
|
||||||
|
{
|
||||||
|
DrawHelper.DrawShadowText(text, pos, color.Base, Config.ShadowConfig.Color.Base, drawList, Config.ShadowConfig.Offset, Config.ShadowConfig.Thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.ShowOutline)
|
||||||
|
{
|
||||||
|
DrawHelper.DrawOutlinedText(text, pos, color.Base, Config.OutlineColor.Base, drawList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.ShowOutline && !Config.ShadowConfig.Enabled)
|
||||||
|
{
|
||||||
|
drawList.AddText(pos, color.Base, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGui.SetWindowFontScale(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[Section("Other Elements")]
|
||||||
|
[SubSection("Limit Break", 0)]
|
||||||
|
public class LimitBreakConfig : ChunkedProgressBarConfig
|
||||||
|
{
|
||||||
|
[NestedConfig("Visibility", 70)]
|
||||||
|
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
|
||||||
|
|
||||||
|
public LimitBreakConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static LimitBreakConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var config = new LimitBreakConfig(
|
||||||
|
new Vector2(0, -ImGui.GetMainViewport().Size.Y * 0.4f),
|
||||||
|
new Vector2(500, 10),
|
||||||
|
new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 0f / 255f, 100f / 100f)));
|
||||||
|
|
||||||
|
config.HideWhenInactive = true;
|
||||||
|
config.UsePartialFillColor = true;
|
||||||
|
config.PartialFillColor = new PluginConfigColor(new Vector4(0f / 255f, 181f / 255f, 255f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public class LimitBreakHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
|
||||||
|
{
|
||||||
|
private LimitBreakConfig Config => (LimitBreakConfig)_config;
|
||||||
|
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
|
||||||
|
|
||||||
|
public IGameObject? Actor { get; set; } = null;
|
||||||
|
|
||||||
|
public LimitBreakHud(LimitBreakConfig config, string displayName) : base(config, displayName) { }
|
||||||
|
|
||||||
|
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||||
|
{
|
||||||
|
return (new List<Vector2>() { Config.Position }, new List<Vector2>() { Config.Size });
|
||||||
|
}
|
||||||
|
|
||||||
|
public override unsafe void DrawChildren(Vector2 origin)
|
||||||
|
{
|
||||||
|
Config.Label.SetText("");
|
||||||
|
|
||||||
|
if (!Config.Enabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LimitBreakController* lbController = LimitBreakController.Instance();
|
||||||
|
AddonHWDAetherGauge* caGauge = (AddonHWDAetherGauge*) Plugin.GameGui.GetAddonByName("HWDAetherGauge", 1).Address;
|
||||||
|
|
||||||
|
int currentLimitBreak = lbController->CurrentUnits;
|
||||||
|
int maxLimitBreak = lbController->BarUnits * lbController->BarCount;
|
||||||
|
int limitBreakChunks = lbController->BarCount;
|
||||||
|
|
||||||
|
if (caGauge != null)
|
||||||
|
{
|
||||||
|
currentLimitBreak = caGauge->MaxGaugeValue;
|
||||||
|
maxLimitBreak = 1000;
|
||||||
|
limitBreakChunks = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
int valuePerChunk = limitBreakChunks == 0 ? 0 : maxLimitBreak / limitBreakChunks;
|
||||||
|
int currentChunksFilled = valuePerChunk == 0 ? 0 : currentLimitBreak / valuePerChunk;
|
||||||
|
|
||||||
|
if (Config.HideWhenInactive && limitBreakChunks == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Config.Label.SetValue(currentChunksFilled);
|
||||||
|
|
||||||
|
BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config, limitBreakChunks, currentLimitBreak, maxLimitBreak);
|
||||||
|
foreach (BarHud bar in bars)
|
||||||
|
{
|
||||||
|
AddDrawActions(bar.GetDrawActions(origin, Config.StrataLevel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[DisableParentSettings("Position")]
|
||||||
|
[Section("Other Elements")]
|
||||||
|
[SubSection("MP Ticker", 0)]
|
||||||
|
public class MPTickerConfig : MovablePluginConfigObject
|
||||||
|
{
|
||||||
|
[Checkbox("Hide on Full MP", spacing = false)]
|
||||||
|
[Order(15)]
|
||||||
|
public bool HideOnFullMP = true;
|
||||||
|
|
||||||
|
[Checkbox("Enable Only for BLM")]
|
||||||
|
[Order(20)]
|
||||||
|
public bool EnableOnlyForBLM = false;
|
||||||
|
|
||||||
|
[Checkbox("Show Only During Umbral Ice")]
|
||||||
|
[Order(25, collapseWith = nameof(EnableOnlyForBLM))]
|
||||||
|
public bool ShowOnlyDuringUmbralIce = true;
|
||||||
|
|
||||||
|
[NestedConfig("MP Ticker Bar", 30)]
|
||||||
|
public MPTickerBarConfig Bar = new MPTickerBarConfig(
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(254, 8),
|
||||||
|
new PluginConfigColor(new(240f / 255f, 92f / 255f, 232f / 255f, 100f / 100f))
|
||||||
|
);
|
||||||
|
|
||||||
|
[NestedConfig("Visibility", 70)]
|
||||||
|
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
|
||||||
|
|
||||||
|
public new static MPTickerConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var config = new MPTickerConfig();
|
||||||
|
config.Enabled = false;
|
||||||
|
config.Bar.Position = new Vector2(0, HUDConstants.BaseHUDOffsetY + 27);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Disableable(false)]
|
||||||
|
[DisableParentSettings("HideWhenInactive")]
|
||||||
|
[Exportable(false)]
|
||||||
|
public class MPTickerBarConfig : BarConfig
|
||||||
|
{
|
||||||
|
[NestedConfig("Fire III Threshold (BLM only)", 50, separator = false, spacing = true)]
|
||||||
|
public MPTickerFire3ThresholdConfig Fire3Threshold = new MPTickerFire3ThresholdConfig();
|
||||||
|
|
||||||
|
public MPTickerBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
|
||||||
|
: base(position, size, fillColor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[DisableParentSettings("Value")]
|
||||||
|
public class MPTickerFire3ThresholdConfig : ThresholdConfig
|
||||||
|
{
|
||||||
|
[DragFloat("Estimated Fire III Cast Time", min = 0f, max = 10)]
|
||||||
|
[Order(11)]
|
||||||
|
public float Fire3CastTime = 1.5f;
|
||||||
|
|
||||||
|
public MPTickerFire3ThresholdConfig()
|
||||||
|
{
|
||||||
|
Enabled = false;
|
||||||
|
ThresholdType = ThresholdType.Above;
|
||||||
|
ShowMarker = true;
|
||||||
|
MarkerColor = new(new Vector4(255f / 255f, 136f / 255f, 0 / 255f, 90f / 100f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using HSUI.Helpers;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Game.ClientState.JobGauge.Types;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public class MPTickerHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
|
||||||
|
{
|
||||||
|
private MPTickerConfig Config => (MPTickerConfig)_config;
|
||||||
|
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
|
||||||
|
|
||||||
|
private MPTickHelper _mpTickHelper = null!;
|
||||||
|
public IGameObject? Actor { get; set; } = null;
|
||||||
|
|
||||||
|
public MPTickerHud(MPTickerConfig config, string displayName) : base(config, displayName) { }
|
||||||
|
|
||||||
|
protected override void InternalDispose()
|
||||||
|
{
|
||||||
|
_mpTickHelper?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||||
|
{
|
||||||
|
return (new List<Vector2>() { Config.Position + Config.Bar.Position },
|
||||||
|
new List<Vector2>() { Config.Bar.Size });
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawChildren(Vector2 origin)
|
||||||
|
{
|
||||||
|
if (!Config.Enabled || Actor == null || Actor is not IPlayerCharacter player)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// full mp
|
||||||
|
if (Config.HideOnFullMP && player.CurrentMp >= player.MaxMp)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BLM specific settings
|
||||||
|
if (Config.EnableOnlyForBLM)
|
||||||
|
{
|
||||||
|
if (player.ClassJob.RowId != JobIDs.BLM)
|
||||||
|
{
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var gauge = Plugin.JobGauges.Get<BLMGauge>();
|
||||||
|
if (Config.ShowOnlyDuringUmbralIce && !gauge.InUmbralIce)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_mpTickHelper ??= new MPTickHelper();
|
||||||
|
|
||||||
|
var now = ImGui.GetTime();
|
||||||
|
var scale = (float)((now - _mpTickHelper.LastTick) / MPTickHelper.ServerTickRate);
|
||||||
|
|
||||||
|
if (scale <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scale > 1)
|
||||||
|
{
|
||||||
|
scale = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPTickerFire3ThresholdConfig? thresholdConfig = GetFire3ThresholdConfig();
|
||||||
|
BarHud bar = BarUtilities.GetProgressBar(Config.Bar, thresholdConfig, null, scale, 1, 0, fillColor: Config.Bar.FillColor);
|
||||||
|
|
||||||
|
AddDrawActions(bar.GetDrawActions(origin + Config.Position, _config.StrataLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
private MPTickerFire3ThresholdConfig? GetFire3ThresholdConfig()
|
||||||
|
{
|
||||||
|
if (Actor is not IPlayerCharacter player || player.ClassJob.RowId != JobIDs.BLM)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPTickerFire3ThresholdConfig config = Config.Bar.Fire3Threshold;
|
||||||
|
if (!config.Enabled)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool leyLinesActive = Utils.StatusListForBattleChara(player).Any(e => e.StatusId == 738);
|
||||||
|
float castTime = config.Fire3CastTime * (leyLinesActive ? 0.85f : 1f);
|
||||||
|
|
||||||
|
// tick rate is 3s
|
||||||
|
// adding 0.3f as "safety net"
|
||||||
|
config.Value = (3 - castTime + 0.3f) / 3;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[DisableParentSettings("HideWhenInactive", "Label")]
|
||||||
|
[Section("Mana Bars")]
|
||||||
|
[SubSection("Player", 0)]
|
||||||
|
public class PlayerPrimaryResourceConfig : UnitFramePrimaryResourceConfig
|
||||||
|
{
|
||||||
|
public PlayerPrimaryResourceConfig(Vector2 position, Vector2 size)
|
||||||
|
: base(position, size)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static PlayerPrimaryResourceConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = new Vector2(HUDConstants.DefaultBigUnitFrameSize.X, 10);
|
||||||
|
var pos = new Vector2(0, 0);
|
||||||
|
|
||||||
|
var config = new PlayerPrimaryResourceConfig(pos, size);
|
||||||
|
config.Anchor = DrawAnchor.Bottom;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("HideWhenInactive", "Label")]
|
||||||
|
[Section("Mana Bars")]
|
||||||
|
[SubSection("Target", 0)]
|
||||||
|
public class TargetPrimaryResourceConfig : UnitFramePrimaryResourceConfig
|
||||||
|
{
|
||||||
|
public TargetPrimaryResourceConfig(Vector2 position, Vector2 size)
|
||||||
|
: base(position, size)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static TargetPrimaryResourceConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = new Vector2(HUDConstants.DefaultBigUnitFrameSize.X, 10);
|
||||||
|
var pos = new Vector2(0, 0);
|
||||||
|
|
||||||
|
var config = new TargetPrimaryResourceConfig(pos, size);
|
||||||
|
config.Anchor = DrawAnchor.Bottom;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("HideWhenInactive", "Label")]
|
||||||
|
[Section("Mana Bars")]
|
||||||
|
[SubSection("Target of Target", 0)]
|
||||||
|
public class TargetOfTargetPrimaryResourceConfig : UnitFramePrimaryResourceConfig
|
||||||
|
{
|
||||||
|
public TargetOfTargetPrimaryResourceConfig(Vector2 position, Vector2 size)
|
||||||
|
: base(position, size)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static TargetOfTargetPrimaryResourceConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = new Vector2(HUDConstants.DefaultSmallUnitFrameSize.X, 10);
|
||||||
|
var pos = new Vector2(0, 0);
|
||||||
|
|
||||||
|
var config = new TargetOfTargetPrimaryResourceConfig(pos, size);
|
||||||
|
config.Anchor = DrawAnchor.Bottom;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("HideWhenInactive", "Label")]
|
||||||
|
[Section("Mana Bars")]
|
||||||
|
[SubSection("Focus Target", 0)]
|
||||||
|
public class FocusTargetPrimaryResourceConfig : UnitFramePrimaryResourceConfig
|
||||||
|
{
|
||||||
|
public FocusTargetPrimaryResourceConfig(Vector2 position, Vector2 size)
|
||||||
|
: base(position, size)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static FocusTargetPrimaryResourceConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = new Vector2(HUDConstants.DefaultSmallUnitFrameSize.X, 10);
|
||||||
|
var pos = new Vector2(0, 0);
|
||||||
|
|
||||||
|
var config = new FocusTargetPrimaryResourceConfig(pos, size);
|
||||||
|
config.Anchor = DrawAnchor.Bottom;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class UnitFramePrimaryResourceConfig : PrimaryResourceConfig
|
||||||
|
{
|
||||||
|
[Checkbox("Anchor to Unit Frame")]
|
||||||
|
[Order(16)]
|
||||||
|
public bool AnchorToUnitFrame = true;
|
||||||
|
|
||||||
|
[Anchor("Unit Frame Anchor")]
|
||||||
|
[Order(17, collapseWith = nameof(AnchorToUnitFrame))]
|
||||||
|
public DrawAnchor UnitFrameAnchor = DrawAnchor.Bottom;
|
||||||
|
|
||||||
|
[NestedConfig("Visibility", 1200)]
|
||||||
|
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
|
||||||
|
|
||||||
|
public UnitFramePrimaryResourceConfig(Vector2 position, Vector2 size)
|
||||||
|
: base(position, size)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class PrimaryResourceConfig : ProgressBarConfig
|
||||||
|
{
|
||||||
|
[Checkbox("Use Job Color", spacing = true)]
|
||||||
|
[Order(19)]
|
||||||
|
public bool UseJobColor = false;
|
||||||
|
|
||||||
|
[Checkbox("Hide When Full", spacing = true)]
|
||||||
|
[Order(41)]
|
||||||
|
public bool HidePrimaryResourceWhenFull = false;
|
||||||
|
|
||||||
|
[NestedConfig("Label", 1000, separator = false, spacing = true)]
|
||||||
|
public EditableLabelConfig ValueLabel = new EditableLabelConfig(Vector2.Zero, "[mana:current]", DrawAnchor.Center, DrawAnchor.Center);
|
||||||
|
|
||||||
|
public PrimaryResourceConfig(Vector2 position, Vector2 size)
|
||||||
|
: base(position, size, new(new(0 / 255f, 162f / 255f, 252f / 255f, 100f / 100f)))
|
||||||
|
{
|
||||||
|
Strata = StrataLevel.LOW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using System;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using HSUI.Interface.Party;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public class PrimaryResourceHud : ParentAnchoredDraggableHudElement, IHudElementWithActor, IHudElementWithAnchorableParent, IHudElementWithVisibilityConfig
|
||||||
|
{
|
||||||
|
private PrimaryResourceConfig Config => (PrimaryResourceConfig)_config;
|
||||||
|
public VisibilityConfig? VisibilityConfig => Config is UnitFramePrimaryResourceConfig config ? config.VisibilityConfig : null;
|
||||||
|
|
||||||
|
public PrimaryResourceTypes ResourceType = PrimaryResourceTypes.MP;
|
||||||
|
|
||||||
|
private IGameObject? _actor;
|
||||||
|
public IGameObject? Actor
|
||||||
|
{
|
||||||
|
get => _actor;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is IPlayerCharacter chara)
|
||||||
|
{
|
||||||
|
_actor = value;
|
||||||
|
|
||||||
|
JobRoles role = JobsHelper.RoleForJob(chara.ClassJob.RowId);
|
||||||
|
ResourceType = JobsHelper.PrimaryResourceTypesByRole[role];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_actor = null;
|
||||||
|
ResourceType = PrimaryResourceTypes.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IPartyFramesMember? PartyMember;
|
||||||
|
|
||||||
|
protected override bool AnchorToParent => Config is UnitFramePrimaryResourceConfig config ? config.AnchorToUnitFrame : false;
|
||||||
|
protected override DrawAnchor ParentAnchor => Config is UnitFramePrimaryResourceConfig config ? config.UnitFrameAnchor : DrawAnchor.Center;
|
||||||
|
|
||||||
|
public PrimaryResourceHud(PrimaryResourceConfig config, string? displayName = null) : base(config, displayName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||||
|
{
|
||||||
|
return (new List<Vector2>() { Config.Position }, new List<Vector2>() { Config.Size });
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawChildren(Vector2 origin)
|
||||||
|
{
|
||||||
|
if (!Config.Enabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PartyMember == null && (ResourceType == PrimaryResourceTypes.None || Actor == null || Actor is not IPlayerCharacter))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICharacter? chara = Actor != null ? (ICharacter)Actor : null;
|
||||||
|
uint current = chara == null ? PartyMember?.MP ?? 0 : 0;
|
||||||
|
uint max = chara == null ? PartyMember?.MaxMP ?? 0 : 0;
|
||||||
|
|
||||||
|
if (chara != null)
|
||||||
|
{
|
||||||
|
GetResources(ref current, ref max, chara);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.HidePrimaryResourceWhenFull && current == max)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BarHud bar = BarUtilities.GetProgressBar(
|
||||||
|
Config,
|
||||||
|
Config.ThresholdConfig,
|
||||||
|
new LabelConfig[] { Config.ValueLabel },
|
||||||
|
current,
|
||||||
|
max,
|
||||||
|
0,
|
||||||
|
chara,
|
||||||
|
GetColor()
|
||||||
|
);
|
||||||
|
|
||||||
|
Vector2 pos = origin + ParentPos();
|
||||||
|
AddDrawActions(bar.GetDrawActions(pos, Config.StrataLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetResources(ref uint current, ref uint max, ICharacter actor)
|
||||||
|
{
|
||||||
|
switch (ResourceType)
|
||||||
|
{
|
||||||
|
case PrimaryResourceTypes.MP:
|
||||||
|
current = actor.CurrentMp;
|
||||||
|
max = actor.MaxMp;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PrimaryResourceTypes.CP:
|
||||||
|
current = actor.CurrentCp;
|
||||||
|
max = actor.MaxCp;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PrimaryResourceTypes.GP:
|
||||||
|
current = actor.CurrentGp;
|
||||||
|
max = actor.MaxGp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual PluginConfigColor GetColor()
|
||||||
|
{
|
||||||
|
if (!Config.UseJobColor)
|
||||||
|
{
|
||||||
|
return Config.FillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PartyMember != null)
|
||||||
|
{
|
||||||
|
return GlobalColors.Instance.SafeColorForJobId(PartyMember.JobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ColorUtils.ColorForActor(Actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[Section("Other Elements")]
|
||||||
|
[SubSection("Pull Timer", 0)]
|
||||||
|
public class PullTimerConfig : ProgressBarConfig
|
||||||
|
{
|
||||||
|
[Checkbox("Use Job Color")]
|
||||||
|
[Order(45)]
|
||||||
|
public bool UseJobColor = false;
|
||||||
|
|
||||||
|
public PullTimerConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static PullTimerConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var config = new PullTimerConfig(
|
||||||
|
new Vector2(0, HUDConstants.BaseHUDOffsetY - 35),
|
||||||
|
new Vector2(254, 20),
|
||||||
|
new PluginConfigColor(new Vector4(233f / 255f, 4f / 255f, 4f / 255f, 100f / 100f)));
|
||||||
|
|
||||||
|
config.HideWhenInactive = true;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public class PullTimerHud : DraggableHudElement, IHudElementWithActor
|
||||||
|
{
|
||||||
|
private PullTimerConfig Config => (PullTimerConfig)_config;
|
||||||
|
|
||||||
|
public IGameObject? Actor { get; set; } = null;
|
||||||
|
|
||||||
|
public PullTimerHud(PullTimerConfig config, string displayName) : base(config, displayName) { }
|
||||||
|
|
||||||
|
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||||
|
{
|
||||||
|
return (new List<Vector2>() { Config.Position }, new List<Vector2>() { Config.Size });
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawChildren(Vector2 origin)
|
||||||
|
{
|
||||||
|
PullTimerState helper = PullTimerHelper.Instance.PullTimerState;
|
||||||
|
|
||||||
|
Config.Label.SetValue(helper.CountDownValue);
|
||||||
|
|
||||||
|
if (!helper.CountingDown)
|
||||||
|
{
|
||||||
|
Config.Label.SetText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.Enabled || Actor is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.HideWhenInactive && !helper.CountingDown)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginConfigColor? fillColor = Config.UseJobColor ? ColorUtils.ColorForActor(Actor) : null;
|
||||||
|
|
||||||
|
BarHud bar = BarUtilities.GetProgressBar(Config,
|
||||||
|
helper.CountDownValue,
|
||||||
|
helper.CountDownMax, 0F, Actor, fillColor);
|
||||||
|
|
||||||
|
AddDrawActions(bar.GetDrawActions(origin, Config.StrataLevel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[Exportable(false)]
|
||||||
|
public class ShadowConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[ColorEdit4("Color")]
|
||||||
|
[Order(5)]
|
||||||
|
public PluginConfigColor Color = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[DragInt("Thickness", min = 1, max = 20)]
|
||||||
|
[Order(10)]
|
||||||
|
public int Thickness = 1;
|
||||||
|
|
||||||
|
[DragInt("Offset", min = 0, max = 20)]
|
||||||
|
[Order(15)]
|
||||||
|
public int Offset = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
[DisableParentSettings("HideWhenInactive", "HideHealthIfPossible", "RangeConfig", "EnemyRangeConfig")]
|
||||||
|
[Section("Unit Frames")]
|
||||||
|
[SubSection("Player", 0)]
|
||||||
|
public class PlayerUnitFrameConfig : UnitFrameConfig
|
||||||
|
{
|
||||||
|
[NestedConfig("Tank Stance Indicator", 122, spacing = true)]
|
||||||
|
public TankStanceIndicatorConfig TankStanceIndicatorConfig = new TankStanceIndicatorConfig();
|
||||||
|
|
||||||
|
public PlayerUnitFrameConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
|
||||||
|
: base(position, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static PlayerUnitFrameConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = HUDConstants.DefaultBigUnitFrameSize;
|
||||||
|
var pos = new Vector2(-HUDConstants.UnitFramesOffsetX - size.X / 2f, HUDConstants.BaseHUDOffsetY);
|
||||||
|
|
||||||
|
var leftLabelConfig = new EditableLabelConfig(new Vector2(5, 0), "[name]", DrawAnchor.TopLeft, DrawAnchor.BottomLeft);
|
||||||
|
var rightLabelConfig = new EditableLabelConfig(new Vector2(-5, 0), "[health:current-short] | [health:percent]", DrawAnchor.TopRight, DrawAnchor.BottomRight);
|
||||||
|
var optionalLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
|
||||||
|
|
||||||
|
var config = new PlayerUnitFrameConfig(pos, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TankStanceCorner
|
||||||
|
{
|
||||||
|
TopLeft = 0,
|
||||||
|
TopRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomRight
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class TankStanceIndicatorConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[Combo("Corner", "Top Left", "Top Right", "Bottom Left", "Bottom Right")]
|
||||||
|
[Order(5)]
|
||||||
|
public TankStanceCorner Corner = TankStanceCorner.BottomLeft;
|
||||||
|
|
||||||
|
[DragFloat2("Size", min = 1, max = 500)]
|
||||||
|
[Order(10)]
|
||||||
|
public Vector2 Size = new Vector2(HUDConstants.DefaultBigUnitFrameSize.Y - 20, HUDConstants.DefaultBigUnitFrameSize.Y - 20);
|
||||||
|
|
||||||
|
[DragInt("Thickness", min = 2, max = 20)]
|
||||||
|
[Order(15)]
|
||||||
|
public int Thickess = 4;
|
||||||
|
|
||||||
|
[ColorEdit4("Active Color")]
|
||||||
|
[Order(20)]
|
||||||
|
public PluginConfigColor ActiveColor = new PluginConfigColor(new Vector4(0f / 255f, 255f / 255f, 255f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[ColorEdit4("Inactive Color")]
|
||||||
|
[Order(25)]
|
||||||
|
public PluginConfigColor InactiveColor = new PluginConfigColor(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("HideWhenInactive")]
|
||||||
|
[Section("Unit Frames")]
|
||||||
|
[SubSection("Target", 0)]
|
||||||
|
public class TargetUnitFrameConfig : UnitFrameConfig
|
||||||
|
{
|
||||||
|
public TargetUnitFrameConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
|
||||||
|
: base(position, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static TargetUnitFrameConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = HUDConstants.DefaultBigUnitFrameSize;
|
||||||
|
var pos = new Vector2(HUDConstants.UnitFramesOffsetX + size.X / 2f, HUDConstants.BaseHUDOffsetY);
|
||||||
|
|
||||||
|
var leftLabelConfig = new EditableLabelConfig(new Vector2(5, 0), "[health:current-short] | [health:percent]", DrawAnchor.TopLeft, DrawAnchor.BottomLeft);
|
||||||
|
var rightLabelConfig = new EditableLabelConfig(new Vector2(-5, 0), "[name]", DrawAnchor.TopRight, DrawAnchor.BottomRight);
|
||||||
|
var optionalLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
|
||||||
|
|
||||||
|
return new TargetUnitFrameConfig(pos, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("HideWhenInactive")]
|
||||||
|
[Section("Unit Frames")]
|
||||||
|
[SubSection("Target of Target", 0)]
|
||||||
|
public class TargetOfTargetUnitFrameConfig : UnitFrameConfig
|
||||||
|
{
|
||||||
|
public TargetOfTargetUnitFrameConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
|
||||||
|
: base(position, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static TargetOfTargetUnitFrameConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = HUDConstants.DefaultSmallUnitFrameSize;
|
||||||
|
var pos = new Vector2(
|
||||||
|
HUDConstants.UnitFramesOffsetX + HUDConstants.DefaultBigUnitFrameSize.X + 6 + size.X / 2f,
|
||||||
|
HUDConstants.BaseHUDOffsetY - 15
|
||||||
|
);
|
||||||
|
|
||||||
|
var leftLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "[name]", DrawAnchor.Top, DrawAnchor.Bottom);
|
||||||
|
var rightLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.TopLeft);
|
||||||
|
var optionalLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.BottomLeft);
|
||||||
|
|
||||||
|
return new TargetOfTargetUnitFrameConfig(pos, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("HideWhenInactive")]
|
||||||
|
[Section("Unit Frames")]
|
||||||
|
[SubSection("Focus Target", 0)]
|
||||||
|
public class FocusTargetUnitFrameConfig : UnitFrameConfig
|
||||||
|
{
|
||||||
|
public FocusTargetUnitFrameConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
|
||||||
|
: base(position, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static FocusTargetUnitFrameConfig DefaultConfig()
|
||||||
|
{
|
||||||
|
var size = HUDConstants.DefaultSmallUnitFrameSize;
|
||||||
|
var pos = new Vector2(
|
||||||
|
-HUDConstants.UnitFramesOffsetX - HUDConstants.DefaultBigUnitFrameSize.X - 6 - size.X / 2f,
|
||||||
|
HUDConstants.BaseHUDOffsetY - 15
|
||||||
|
);
|
||||||
|
|
||||||
|
var leftLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "[name]", DrawAnchor.Top, DrawAnchor.Bottom);
|
||||||
|
var rightLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
|
||||||
|
var optionalLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Bottom, DrawAnchor.Bottom);
|
||||||
|
|
||||||
|
return new FocusTargetUnitFrameConfig(pos, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisableParentSettings("HideWhenInactive")]
|
||||||
|
public class UnitFrameConfig : BarConfig
|
||||||
|
{
|
||||||
|
[Checkbox("Use Job Color", spacing = true)]
|
||||||
|
[Order(45)]
|
||||||
|
public bool UseJobColor = true;
|
||||||
|
|
||||||
|
[Checkbox("Use Role Color")]
|
||||||
|
[Order(46)]
|
||||||
|
public bool UseRoleColor = false;
|
||||||
|
|
||||||
|
[NestedConfig("Color Based On Health Value", 50, collapsingHeader = false)]
|
||||||
|
public ColorByHealthValueConfig ColorByHealth = new ColorByHealthValueConfig();
|
||||||
|
|
||||||
|
[Checkbox("Job Color As Background Color", spacing = true)]
|
||||||
|
[Order(50)]
|
||||||
|
public bool UseJobColorAsBackgroundColor = false;
|
||||||
|
|
||||||
|
[Checkbox("Role Color As Background Color")]
|
||||||
|
[Order(51)]
|
||||||
|
public bool UseRoleColorAsBackgroundColor = false;
|
||||||
|
|
||||||
|
[Checkbox("Missing Health Color")]
|
||||||
|
[Order(55)]
|
||||||
|
public bool UseMissingHealthBar = false;
|
||||||
|
|
||||||
|
[Checkbox("Job Color As Missing Health Color")]
|
||||||
|
[Order(56, collapseWith = nameof(UseMissingHealthBar))]
|
||||||
|
public bool UseJobColorAsMissingHealthColor = false;
|
||||||
|
|
||||||
|
[Checkbox("Role Color As Missing Health Color")]
|
||||||
|
[Order(57, collapseWith = nameof(UseMissingHealthBar))]
|
||||||
|
public bool UseRoleColorAsMissingHealthColor = false;
|
||||||
|
|
||||||
|
[ColorEdit4("Color" + "##MissingHealth")]
|
||||||
|
[Order(60, collapseWith = nameof(UseMissingHealthBar))]
|
||||||
|
public PluginConfigColor HealthMissingColor = new PluginConfigColor(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
|
||||||
|
[Checkbox("Death Indicator Background Color", spacing = true)]
|
||||||
|
[Order(61)]
|
||||||
|
public bool UseDeathIndicatorBackgroundColor = false;
|
||||||
|
|
||||||
|
[ColorEdit4("Color" + "##DeathIndicator")]
|
||||||
|
[Order(62, collapseWith = nameof(UseDeathIndicatorBackgroundColor))]
|
||||||
|
public PluginConfigColor DeathIndicatorBackgroundColor = new PluginConfigColor(new Vector4(204f / 255f, 3f / 255f, 3f / 255f, 50f / 100f));
|
||||||
|
|
||||||
|
[Checkbox("Tank Invulnerability", spacing = true)]
|
||||||
|
[Order(95)]
|
||||||
|
public bool ShowTankInvulnerability = true;
|
||||||
|
|
||||||
|
[Checkbox("Tank Invulnerability Custom Color")]
|
||||||
|
[Order(100, collapseWith = nameof(ShowTankInvulnerability))]
|
||||||
|
public bool UseCustomInvulnerabilityColor = true;
|
||||||
|
|
||||||
|
[ColorEdit4("Tank Invulnerability Color ##TankInvulnerabilityCustom")]
|
||||||
|
[Order(105, collapseWith = nameof(UseCustomInvulnerabilityColor))]
|
||||||
|
public PluginConfigColor CustomInvulnerabilityColor = new PluginConfigColor(new Vector4(211f / 255f, 235f / 255f, 215f / 245f, 50f / 100f));
|
||||||
|
|
||||||
|
[Checkbox("Walking Dead Custom Color")]
|
||||||
|
[Order(110, collapseWith = nameof(ShowTankInvulnerability))]
|
||||||
|
public bool UseCustomWalkingDeadColor = true;
|
||||||
|
|
||||||
|
[ColorEdit4("Walking Dead Color ##TankWalkingDeadCustom")]
|
||||||
|
[Order(115, collapseWith = nameof(UseCustomWalkingDeadColor))]
|
||||||
|
public PluginConfigColor CustomWalkingDeadColor = new PluginConfigColor(new Vector4(158f / 255f, 158f / 255f, 158f / 255f, 50f / 100f));
|
||||||
|
|
||||||
|
[NestedConfig("Use Smooth Transitions", 120, collapsingHeader = false)]
|
||||||
|
public SmoothHealthConfig SmoothHealthConfig = new SmoothHealthConfig();
|
||||||
|
|
||||||
|
[Checkbox("Hide Health if Possible", spacing = true, help = "This will hide any label that has a health tag if the character doesn't have health (ie minions, friendly npcs, etc)")]
|
||||||
|
[Order(121)]
|
||||||
|
public bool HideHealthIfPossible = true;
|
||||||
|
|
||||||
|
[NestedConfig("Left Text", 125)]
|
||||||
|
public EditableLabelConfig LeftLabelConfig = null!;
|
||||||
|
|
||||||
|
[NestedConfig("Right Text", 130)]
|
||||||
|
public EditableLabelConfig RightLabelConfig = null!;
|
||||||
|
|
||||||
|
[NestedConfig("Optional Text", 131)]
|
||||||
|
public EditableLabelConfig OptionalLabelConfig = null!;
|
||||||
|
|
||||||
|
[NestedConfig("Role/Job Icon", 135)]
|
||||||
|
public RoleJobIconConfig RoleIconConfig = new RoleJobIconConfig(
|
||||||
|
new Vector2(5, 0),
|
||||||
|
new Vector2(30, 30),
|
||||||
|
DrawAnchor.Left,
|
||||||
|
DrawAnchor.Left
|
||||||
|
);
|
||||||
|
|
||||||
|
[NestedConfig("Sign Icon", 136)]
|
||||||
|
public SignIconConfig SignIconConfig = new SignIconConfig(
|
||||||
|
new Vector2(0, 0),
|
||||||
|
new Vector2(30, 30),
|
||||||
|
DrawAnchor.Center,
|
||||||
|
DrawAnchor.Top
|
||||||
|
);
|
||||||
|
|
||||||
|
[NestedConfig("Shields", 140)]
|
||||||
|
public ShieldConfig ShieldConfig = new ShieldConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Change Friendly Alpha Based on Range", 145)]
|
||||||
|
public UnitFramesRangeConfig RangeConfig = new();
|
||||||
|
|
||||||
|
[NestedConfig("Change Enemy Alpha Based on Range", 146)]
|
||||||
|
public UnitFramesRangeConfig EnemyRangeConfig = new();
|
||||||
|
|
||||||
|
[NestedConfig("Custom Mouseover Area", 150)]
|
||||||
|
public MouseoverAreaConfig MouseoverAreaConfig = new MouseoverAreaConfig();
|
||||||
|
|
||||||
|
[NestedConfig("Visibility", 200)]
|
||||||
|
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
|
||||||
|
|
||||||
|
public UnitFrameConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
|
||||||
|
: base(position, size, new PluginConfigColor(new(40f / 255f, 40f / 255f, 40f / 255f, 100f / 100f)))
|
||||||
|
{
|
||||||
|
Position = position;
|
||||||
|
Size = size;
|
||||||
|
LeftLabelConfig = leftLabelConfig;
|
||||||
|
RightLabelConfig = rightLabelConfig;
|
||||||
|
OptionalLabelConfig = optionalLabelConfig;
|
||||||
|
BackgroundColor = new PluginConfigColor(new(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||||
|
RoleIconConfig.Enabled = false;
|
||||||
|
SignIconConfig.Enabled = false;
|
||||||
|
ColorByHealth.Enabled = false;
|
||||||
|
MouseoverAreaConfig.Enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnitFrameConfig() : base(Vector2.Zero, Vector2.Zero, PluginConfigColor.Empty) { } // don't remove
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class ShieldConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[DragInt("Thickness")]
|
||||||
|
[Order(5)]
|
||||||
|
public int Height = 26; // Should be 'Size' instead of 'Height' but leaving as is to avoid breaking configs
|
||||||
|
|
||||||
|
[Checkbox("Thickness in Pixels")]
|
||||||
|
[Order(10)]
|
||||||
|
public bool HeightInPixels = false;
|
||||||
|
|
||||||
|
[Checkbox("Fill Health First")]
|
||||||
|
[Order(15)]
|
||||||
|
public bool FillHealthFirst = true;
|
||||||
|
|
||||||
|
[ColorEdit4("Color ##Shields")]
|
||||||
|
[Order(20)]
|
||||||
|
public PluginConfigColor Color = new PluginConfigColor(new Vector4(198f / 255f, 210f / 255f, 255f / 255f, 70f / 100f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class SmoothHealthConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[DragFloat("Velocity", min = 1f, max = 100f)]
|
||||||
|
[Order(5)]
|
||||||
|
public float Velocity = 25f;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class MouseoverAreaConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[Checkbox("Preview")]
|
||||||
|
[Order(5)]
|
||||||
|
public bool Preview = false;
|
||||||
|
|
||||||
|
[Checkbox("Ignore Mouseover", help = "Enabling this will make it so this element is ignored by mouseover completely.\nThe area can still be defined for left and right clicks.")]
|
||||||
|
[Order(6)]
|
||||||
|
public bool Ignore = false;
|
||||||
|
|
||||||
|
[DragInt2("Top Left Offset", min = -500, max = 500)]
|
||||||
|
[Order(10)]
|
||||||
|
public Vector2 TopLeftOffset = Vector2.Zero;
|
||||||
|
|
||||||
|
[DragInt2("Bottom Right Offset", min = -500, max = 500)]
|
||||||
|
[Order(11)]
|
||||||
|
public Vector2 BottomRightOffset = Vector2.Zero;
|
||||||
|
|
||||||
|
public MouseoverAreaConfig()
|
||||||
|
{
|
||||||
|
Enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (Vector2, Vector2) GetArea(Vector2 pos, Vector2 size)
|
||||||
|
{
|
||||||
|
if (!Enabled) { return (pos, pos + size); }
|
||||||
|
|
||||||
|
Vector2 start = pos + TopLeftOffset;
|
||||||
|
Vector2 end = pos + size + BottomRightOffset;
|
||||||
|
|
||||||
|
return (start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BarHud? GetBar(Vector2 pos, Vector2 size, string id, DrawAnchor anchor = DrawAnchor.TopLeft)
|
||||||
|
{
|
||||||
|
if (!Enabled || !Preview) { return null; }
|
||||||
|
|
||||||
|
BarHud bar = new BarHud(
|
||||||
|
id,
|
||||||
|
true,
|
||||||
|
new(Vector4.One),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
|
var barPos = Utils.GetAnchoredPosition(Vector2.Zero, size, anchor);
|
||||||
|
var (start, end) = GetArea(barPos + pos, size);
|
||||||
|
Rect background = new Rect(start, end - start, new(new(1, 1, 1, 0.5f)));
|
||||||
|
bar.SetBackground(background);
|
||||||
|
|
||||||
|
return bar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
public class UnitFramesRangeConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[DragInt("Range (yalms)", min = 1, max = 500)]
|
||||||
|
[Order(5)]
|
||||||
|
public int Range = 30;
|
||||||
|
|
||||||
|
[DragFloat("Alpha", min = 1, max = 100)]
|
||||||
|
[Order(10)]
|
||||||
|
public float Alpha = 24;
|
||||||
|
|
||||||
|
[Checkbox("Use Additional Range Check")]
|
||||||
|
[Order(15)]
|
||||||
|
public bool UseAdditionalRangeCheck = false;
|
||||||
|
|
||||||
|
[DragInt("Additional Range (yalms)", min = 1, max = 500)]
|
||||||
|
[Order(20, collapseWith = nameof(UseAdditionalRangeCheck))]
|
||||||
|
public int AdditionalRange = 15;
|
||||||
|
|
||||||
|
[DragFloat("Additional Alpha", min = 1, max = 100)]
|
||||||
|
[Order(25, collapseWith = nameof(UseAdditionalRangeCheck))]
|
||||||
|
public float AdditionalAlpha = 60;
|
||||||
|
|
||||||
|
public float AlphaForDistance(int distance, float alpha = 100f)
|
||||||
|
{
|
||||||
|
if (!Enabled)
|
||||||
|
{
|
||||||
|
return 100f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UseAdditionalRangeCheck)
|
||||||
|
{
|
||||||
|
return distance > Range ? Alpha : alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Range > AdditionalRange)
|
||||||
|
{
|
||||||
|
return distance > Range ? Alpha : (distance > AdditionalRange ? AdditionalAlpha : alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
return distance > AdditionalRange ? AdditionalAlpha : (distance > Range ? Alpha : alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,528 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Game.ClientState.Statuses;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Enums;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using HSUI.Interface.Bars;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using BattleChara = Dalamud.Game.ClientState.Objects.Types.IBattleChara;
|
||||||
|
using BattleNpcSubKind = Dalamud.Game.ClientState.Objects.Enums.BattleNpcSubKind;
|
||||||
|
using Character = Dalamud.Game.ClientState.Objects.Types.ICharacter;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public unsafe class UnitFrameHud(UnitFrameConfig config, string displayName)
|
||||||
|
: DraggableHudElement(config, displayName), IHudElementWithActor, IHudElementWithMouseOver, IHudElementWithPreview, IHudElementWithVisibilityConfig
|
||||||
|
{
|
||||||
|
public UnitFrameConfig Config => (UnitFrameConfig)_config;
|
||||||
|
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
|
||||||
|
|
||||||
|
private SmoothHPHelper _smoothHPHelper = new SmoothHPHelper();
|
||||||
|
|
||||||
|
public IGameObject? Actor { get; set; }
|
||||||
|
|
||||||
|
private bool _wasHovering = false;
|
||||||
|
|
||||||
|
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||||
|
{
|
||||||
|
return (new List<Vector2>() { Config.Position }, new List<Vector2>() { Config.Size });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopPreview()
|
||||||
|
{
|
||||||
|
Config.MouseoverAreaConfig.Preview = false;
|
||||||
|
Config.SignIconConfig.Preview = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopMouseover()
|
||||||
|
{
|
||||||
|
if (_wasHovering)
|
||||||
|
{
|
||||||
|
InputsHelper.Instance.ClearTarget();
|
||||||
|
_wasHovering = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawChildren(Vector2 origin)
|
||||||
|
{
|
||||||
|
if (!Config.Enabled || Actor == null)
|
||||||
|
{
|
||||||
|
StopMouseover();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawExtras(origin, Actor);
|
||||||
|
|
||||||
|
if (Actor is Character character)
|
||||||
|
{
|
||||||
|
DrawCharacter(origin, character);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DrawFriendlyNPC(origin, Actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if mouse is hovering over the box properly
|
||||||
|
var startPos = Utils.GetAnchoredPosition(origin + Config.Position, Config.Size, Config.Anchor);
|
||||||
|
var (areaStart, areaEnd) = Config.MouseoverAreaConfig.GetArea(startPos, Config.Size);
|
||||||
|
bool isHovering = ImGui.IsMouseHoveringRect(areaStart, areaEnd);
|
||||||
|
bool ignoreMouseover = Config.MouseoverAreaConfig.Enabled && Config.MouseoverAreaConfig.Ignore;
|
||||||
|
|
||||||
|
if (isHovering && !DraggingEnabled)
|
||||||
|
{
|
||||||
|
_wasHovering = true;
|
||||||
|
InputsHelper.Instance.SetTarget(Actor, ignoreMouseover);
|
||||||
|
|
||||||
|
if (InputsHelper.Instance.LeftButtonClicked)
|
||||||
|
{
|
||||||
|
Plugin.TargetManager.Target = Actor;
|
||||||
|
}
|
||||||
|
else if (InputsHelper.Instance.RightButtonClicked)
|
||||||
|
{
|
||||||
|
AgentModule.Instance()->GetAgentHUD()->OpenContextMenuFromTarget((GameObject*)Actor.Address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_wasHovering)
|
||||||
|
{
|
||||||
|
InputsHelper.Instance.ClearTarget();
|
||||||
|
_wasHovering = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void DrawExtras(Vector2 origin, IGameObject? actor)
|
||||||
|
{
|
||||||
|
// override
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCharacter(Vector2 pos, Character character)
|
||||||
|
{
|
||||||
|
uint currentHp = character.CurrentHp;
|
||||||
|
uint maxHp = character.MaxHp;
|
||||||
|
|
||||||
|
// fixes weird bug with npcs
|
||||||
|
if (maxHp == 1)
|
||||||
|
{
|
||||||
|
currentHp = 1;
|
||||||
|
}
|
||||||
|
else if (Config.SmoothHealthConfig.Enabled)
|
||||||
|
{
|
||||||
|
currentHp = _smoothHPHelper.GetNextHp((int)currentHp, (int)maxHp, Config.SmoothHealthConfig.Velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginConfigColor fillColor = ColorUtils.ColorForCharacter(
|
||||||
|
character,
|
||||||
|
currentHp,
|
||||||
|
maxHp,
|
||||||
|
Config.UseJobColor,
|
||||||
|
Config.UseRoleColor,
|
||||||
|
Config.ColorByHealth
|
||||||
|
) ?? Config.FillColor;
|
||||||
|
|
||||||
|
Rect background = new Rect(Config.Position, Config.Size, BackgroundColor(character));
|
||||||
|
if (Config.RangeConfig.Enabled || Config.EnemyRangeConfig.Enabled)
|
||||||
|
{
|
||||||
|
fillColor = GetDistanceColor(character, fillColor);
|
||||||
|
background.Color = GetDistanceColor(character, background.Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect healthFill = BarUtilities.GetFillRect(Config.Position, Config.Size, Config.FillDirection, fillColor, currentHp, maxHp);
|
||||||
|
|
||||||
|
BarHud bar = new BarHud(Config, character);
|
||||||
|
bar.NeedsInputs = true;
|
||||||
|
bar.SetBackground(background);
|
||||||
|
bar.AddForegrounds(healthFill);
|
||||||
|
bar.AddLabels(GetLabels(maxHp));
|
||||||
|
|
||||||
|
if (Config.UseMissingHealthBar)
|
||||||
|
{
|
||||||
|
Vector2 healthMissingSize = Config.Size - BarUtilities.GetFillDirectionOffset(healthFill.Size, Config.FillDirection);
|
||||||
|
Vector2 healthMissingPos = Config.FillDirection.IsInverted()
|
||||||
|
? Config.Position
|
||||||
|
: Config.Position + BarUtilities.GetFillDirectionOffset(healthFill.Size, Config.FillDirection);
|
||||||
|
|
||||||
|
PluginConfigColor missingHealthColor = Config.UseJobColorAsMissingHealthColor && character is BattleChara
|
||||||
|
? GlobalColors.Instance.SafeColorForJobId(character!.ClassJob.RowId)
|
||||||
|
: Config.UseRoleColorAsMissingHealthColor && character is BattleChara
|
||||||
|
? GlobalColors.Instance.SafeRoleColorForJobId(character!.ClassJob.RowId)
|
||||||
|
: Config.HealthMissingColor;
|
||||||
|
|
||||||
|
if (Config.UseDeathIndicatorBackgroundColor && character is BattleChara { CurrentHp: <= 0 })
|
||||||
|
{
|
||||||
|
missingHealthColor = Config.DeathIndicatorBackgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.UseCustomInvulnerabilityColor && character is BattleChara battleChara)
|
||||||
|
{
|
||||||
|
IStatus? tankInvuln = Utils.GetTankInvulnerabilityID(battleChara);
|
||||||
|
if (tankInvuln is not null)
|
||||||
|
{
|
||||||
|
missingHealthColor = Config.CustomInvulnerabilityColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.RangeConfig.Enabled || Config.EnemyRangeConfig.Enabled)
|
||||||
|
{
|
||||||
|
missingHealthColor = GetDistanceColor(character, missingHealthColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
bar.AddForegrounds(new Rect(healthMissingPos, healthMissingSize, missingHealthColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
// shield
|
||||||
|
BarUtilities.AddShield(bar, Config, Config.ShieldConfig, character, healthFill.Size);
|
||||||
|
|
||||||
|
// draw action
|
||||||
|
AddDrawActions(bar.GetDrawActions(pos, Config.StrataLevel));
|
||||||
|
|
||||||
|
// mouseover area
|
||||||
|
BarHud? mouseoverAreaBar = Config.MouseoverAreaConfig.GetBar(
|
||||||
|
Config.Position,
|
||||||
|
Config.Size,
|
||||||
|
Config.ID + "_mouseoverArea",
|
||||||
|
Config.Anchor
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mouseoverAreaBar != null)
|
||||||
|
{
|
||||||
|
AddDrawActions(mouseoverAreaBar.GetDrawActions(pos, StrataLevel.HIGHEST));
|
||||||
|
}
|
||||||
|
|
||||||
|
// role/job icon
|
||||||
|
if (Config.RoleIconConfig.Enabled && character is IPlayerCharacter)
|
||||||
|
{
|
||||||
|
uint jobId = character.ClassJob.RowId;
|
||||||
|
uint iconId = Config.RoleIconConfig.UseRoleIcons ?
|
||||||
|
JobsHelper.RoleIconIDForJob(jobId, Config.RoleIconConfig.UseSpecificDPSRoleIcons) :
|
||||||
|
JobsHelper.IconIDForJob(jobId, (uint)Config.RoleIconConfig.Style);
|
||||||
|
|
||||||
|
if (iconId > 0)
|
||||||
|
{
|
||||||
|
var barPos = Utils.GetAnchoredPosition(pos, Config.Size, Config.Anchor);
|
||||||
|
var parentPos = Utils.GetAnchoredPosition(barPos + Config.Position, -Config.Size, Config.RoleIconConfig.FrameAnchor);
|
||||||
|
var iconPos = Utils.GetAnchoredPosition(parentPos + Config.RoleIconConfig.Position, Config.RoleIconConfig.Size, Config.RoleIconConfig.Anchor);
|
||||||
|
|
||||||
|
AddDrawAction(Config.RoleIconConfig.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
DrawHelper.DrawInWindow(ID + "_jobIcon", iconPos, Config.RoleIconConfig.Size, false, (drawList) =>
|
||||||
|
{
|
||||||
|
DrawHelper.DrawIcon(iconId, iconPos, Config.RoleIconConfig.Size, false, drawList);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sign icon
|
||||||
|
if (Config.SignIconConfig.Enabled)
|
||||||
|
{
|
||||||
|
uint? iconId = Config.SignIconConfig.IconID(character);
|
||||||
|
if (iconId.HasValue)
|
||||||
|
{
|
||||||
|
var barPos = Utils.GetAnchoredPosition(pos, Config.Size, Config.Anchor);
|
||||||
|
var parentPos = Utils.GetAnchoredPosition(barPos + Config.Position, -Config.Size, Config.SignIconConfig.FrameAnchor);
|
||||||
|
var iconPos = Utils.GetAnchoredPosition(parentPos + Config.SignIconConfig.Position, Config.SignIconConfig.Size, Config.SignIconConfig.Anchor);
|
||||||
|
|
||||||
|
AddDrawAction(Config.SignIconConfig.StrataLevel, () =>
|
||||||
|
{
|
||||||
|
DrawHelper.DrawInWindow(ID + "_signIcon", iconPos, Config.SignIconConfig.Size, false, (drawList) =>
|
||||||
|
{
|
||||||
|
DrawHelper.DrawIcon(iconId.Value, iconPos, Config.SignIconConfig.Size, false, drawList);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LabelConfig[] GetLabels(uint maxHp)
|
||||||
|
{
|
||||||
|
List<LabelConfig> labels = new List<LabelConfig>();
|
||||||
|
|
||||||
|
if (Config.HideHealthIfPossible && maxHp <= 1)
|
||||||
|
{
|
||||||
|
if (!Utils.IsHealthLabel(Config.LeftLabelConfig))
|
||||||
|
{
|
||||||
|
labels.Add(Config.LeftLabelConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Utils.IsHealthLabel(Config.RightLabelConfig))
|
||||||
|
{
|
||||||
|
labels.Add(Config.RightLabelConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Utils.IsHealthLabel(Config.OptionalLabelConfig))
|
||||||
|
{
|
||||||
|
labels.Add(Config.OptionalLabelConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
labels.Add(Config.LeftLabelConfig);
|
||||||
|
labels.Add(Config.RightLabelConfig);
|
||||||
|
labels.Add(Config.OptionalLabelConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private PluginConfigColor GetDistanceColor(Character? character, PluginConfigColor color)
|
||||||
|
{
|
||||||
|
byte distance = character != null ? character.YalmDistanceX : byte.MaxValue;
|
||||||
|
float currentAlpha = color.Vector.W * 100f;
|
||||||
|
float alpha = Config.RangeConfig.AlphaForDistance(distance, currentAlpha) / 100f;
|
||||||
|
|
||||||
|
if (character is IBattleNpc { BattleNpcKind: BattleNpcSubKind.Enemy or BattleNpcSubKind.BattleNpcPart } && Config.EnemyRangeConfig.Enabled)
|
||||||
|
{
|
||||||
|
alpha = Config.EnemyRangeConfig.AlphaForDistance(distance, currentAlpha) / 100f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return color.WithAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void GetNPCHpValues(IGameObject? actor, out uint currentHp, out uint maxHp)
|
||||||
|
{
|
||||||
|
currentHp = 0;
|
||||||
|
maxHp = 0;
|
||||||
|
|
||||||
|
var player = Plugin.ObjectTable.LocalPlayer;
|
||||||
|
if (player == null || actor == null || player.TargetObject == null || actor.GameObjectId != player.TargetObject.GameObjectId)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtkUnitBase* TargetWidget = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_TargetInfoMainTarget", 1).Address;
|
||||||
|
if (TargetWidget != null)
|
||||||
|
{
|
||||||
|
AtkTextNode* textNode = TargetWidget->GetTextNodeById(11);
|
||||||
|
string integrityText = textNode->NodeText.ToString();
|
||||||
|
|
||||||
|
// not a gathering node or node at 100%, nothing to do
|
||||||
|
if (!integrityText.Contains("%"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
currentHp = Convert.ToUInt32((integrityText.Replace("%", "")));
|
||||||
|
maxHp = 100;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFriendlyNPC(Vector2 pos, IGameObject? actor)
|
||||||
|
{
|
||||||
|
GetNPCHpValues(actor, out uint currentHp, out uint maxHp);
|
||||||
|
|
||||||
|
BarHud bar = new BarHud(Config, actor);
|
||||||
|
bar.AddLabels(GetLabels(0));
|
||||||
|
|
||||||
|
if (maxHp == 0)
|
||||||
|
{
|
||||||
|
bar.AddForegrounds(new Rect(Config.Position, Config.Size, ColorUtils.ColorForActor(actor)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Config.SmoothHealthConfig.Enabled)
|
||||||
|
{
|
||||||
|
currentHp = _smoothHPHelper.GetNextHp((int)currentHp, (int)maxHp, Config.SmoothHealthConfig.Velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginConfigColor fillColor = ColorUtils.ColorForCharacter(
|
||||||
|
actor,
|
||||||
|
currentHp,
|
||||||
|
maxHp,
|
||||||
|
colorByHealthConfig: Config.ColorByHealth
|
||||||
|
) ?? Config.FillColor;
|
||||||
|
|
||||||
|
Rect background = new Rect(Config.Position, Config.Size, Config.BackgroundColor);
|
||||||
|
Rect healthFill = BarUtilities.GetFillRect(Config.Position, Config.Size, Config.FillDirection, fillColor, currentHp, maxHp);
|
||||||
|
|
||||||
|
bar.NeedsInputs = true;
|
||||||
|
bar.SetBackground(background);
|
||||||
|
bar.AddForegrounds(healthFill);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddDrawActions(bar.GetDrawActions(pos, Config.StrataLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PluginConfigColor BackgroundColor(Character? chara)
|
||||||
|
{
|
||||||
|
if (Config.ShowTankInvulnerability &&
|
||||||
|
!Config.UseMissingHealthBar &&
|
||||||
|
chara is BattleChara battleChara)
|
||||||
|
{
|
||||||
|
IStatus? tankInvuln = Utils.GetTankInvulnerabilityID(battleChara);
|
||||||
|
|
||||||
|
if (tankInvuln != null)
|
||||||
|
{
|
||||||
|
PluginConfigColor color;
|
||||||
|
if (Config.UseCustomInvulnerabilityColor)
|
||||||
|
{
|
||||||
|
color = Config.CustomInvulnerabilityColor;
|
||||||
|
}
|
||||||
|
else if (tankInvuln.StatusId == 811 && Config.UseCustomWalkingDeadColor)
|
||||||
|
{
|
||||||
|
color = Config.CustomWalkingDeadColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
color = new PluginConfigColor(GlobalColors.Instance.SafeColorForJobId(chara.ClassJob.RowId).Vector.AdjustColor(-.8f));
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chara is BattleChara)
|
||||||
|
{
|
||||||
|
if (Config.UseJobColorAsBackgroundColor)
|
||||||
|
{
|
||||||
|
return GlobalColors.Instance.SafeColorForJobId(chara.ClassJob.RowId);
|
||||||
|
}
|
||||||
|
else if (Config.UseRoleColorAsBackgroundColor)
|
||||||
|
{
|
||||||
|
return GlobalColors.Instance.SafeRoleColorForJobId(chara.ClassJob.RowId);
|
||||||
|
}
|
||||||
|
else if (Config.UseDeathIndicatorBackgroundColor && chara.CurrentHp <= 0)
|
||||||
|
{
|
||||||
|
return Config.DeathIndicatorBackgroundColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Config.BackgroundColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GlobalColors.Instance.EmptyUnitFrameColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlayerUnitFrameHud : UnitFrameHud
|
||||||
|
{
|
||||||
|
public new PlayerUnitFrameConfig Config => (PlayerUnitFrameConfig)_config;
|
||||||
|
|
||||||
|
public PlayerUnitFrameHud(PlayerUnitFrameConfig config, string displayName) : base(config, displayName)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawExtras(Vector2 origin, IGameObject? actor)
|
||||||
|
{
|
||||||
|
TankStanceIndicatorConfig config = Config.TankStanceIndicatorConfig;
|
||||||
|
|
||||||
|
if (!config.Enabled || actor is not IPlayerCharacter chara) { return; }
|
||||||
|
|
||||||
|
uint jobId = chara.ClassJob.RowId;
|
||||||
|
if (JobsHelper.RoleForJob(jobId) != JobRoles.Tank) { return; }
|
||||||
|
|
||||||
|
var tankStanceBuff = Utils.StatusListForBattleChara(chara).Where(o =>
|
||||||
|
o.StatusId == 79 || // IRON WILL
|
||||||
|
o.StatusId == 91 || // DEFIANCE
|
||||||
|
o.StatusId == 392 || // ROYAL GUARD
|
||||||
|
o.StatusId == 393 || // IRON WILL
|
||||||
|
o.StatusId == 743 || // GRIT
|
||||||
|
o.StatusId == 1396 || // DEFIANCE
|
||||||
|
o.StatusId == 1397 || // GRIT
|
||||||
|
o.StatusId == 1833 // ROYAL GUARD
|
||||||
|
);
|
||||||
|
|
||||||
|
PluginConfigColor color = tankStanceBuff.Any() ? config.ActiveColor : config.InactiveColor;
|
||||||
|
|
||||||
|
Vector2 pos = GetTankStanceCornerOrigin(origin);
|
||||||
|
var (verticalDir, horizontalDir) = GetTankStanceLinesDirections();
|
||||||
|
|
||||||
|
pos = new Vector2(pos.X + config.Thickess * -horizontalDir, pos.Y + config.Thickess * -verticalDir);
|
||||||
|
Vector2 vSize = new Vector2(config.Thickess * horizontalDir, (config.Size.Y + config.Thickess) * verticalDir);
|
||||||
|
Vector2 vEndPos = pos + vSize;
|
||||||
|
Vector2 hSize = new Vector2((config.Size.X + config.Thickess) * horizontalDir, config.Thickess * verticalDir);
|
||||||
|
Vector2 hEndPos = pos + hSize;
|
||||||
|
|
||||||
|
Vector2 startPos = new Vector2(Math.Min(pos.X, hEndPos.X), Math.Min(pos.Y, hEndPos.Y));
|
||||||
|
Vector2 endPos = new Vector2(Math.Max(pos.X, hEndPos.X), Math.Max(pos.Y, hEndPos.Y)); ;
|
||||||
|
|
||||||
|
AddDrawAction(StrataLevel.LOWEST, () =>
|
||||||
|
{
|
||||||
|
DrawHelper.DrawInWindow(ID + "_TankStance", startPos, endPos - startPos, false, (drawList) =>
|
||||||
|
{
|
||||||
|
// TODO: clean up hacky math
|
||||||
|
// there's some 1px errors prob due to negative sizes
|
||||||
|
// couldn't figure it out so I did the hacky fixes
|
||||||
|
|
||||||
|
// vertical
|
||||||
|
|
||||||
|
drawList.AddRectFilled(pos, vEndPos, color.Base);
|
||||||
|
|
||||||
|
if (config.Corner == TankStanceCorner.TopRight)
|
||||||
|
{
|
||||||
|
drawList.AddLine(pos, pos + new Vector2(0, vSize.Y + 1), 0xFF000000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
drawList.AddLine(pos, pos + new Vector2(0, vSize.Y), 0xFF000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawList.AddLine(pos + vSize, pos + vSize + new Vector2(-vSize.X, 0), 0xFF000000);
|
||||||
|
|
||||||
|
// horizontal
|
||||||
|
drawList.AddRectFilled(pos, hEndPos, color.Base);
|
||||||
|
|
||||||
|
if (config.Corner == TankStanceCorner.BottomLeft)
|
||||||
|
{
|
||||||
|
drawList.AddLine(pos, pos + new Vector2(hSize.X + 1, 0), 0xFF000000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
drawList.AddLine(pos, pos + new Vector2(hSize.X, 0), 0xFF000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.Corner == TankStanceCorner.BottomRight)
|
||||||
|
{
|
||||||
|
drawList.AddLine(pos + new Vector2(0, 1), pos + new Vector2(0, hSize.Y), 0xFF000000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
drawList.AddLine(pos, pos + new Vector2(0, hSize.Y), 0xFF000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawList.AddLine(pos + hSize, pos + hSize + new Vector2(0, -hSize.Y), 0xFF000000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 GetTankStanceCornerOrigin(Vector2 origin)
|
||||||
|
{
|
||||||
|
var topLeft = Utils.GetAnchoredPosition(origin + Config.Position, Config.Size, Config.Anchor);
|
||||||
|
|
||||||
|
return Config.TankStanceIndicatorConfig.Corner switch
|
||||||
|
{
|
||||||
|
TankStanceCorner.TopRight => topLeft + new Vector2(Config.Size.X - 1, 0),
|
||||||
|
TankStanceCorner.BottomLeft => topLeft + new Vector2(0, Config.Size.Y - 1),
|
||||||
|
TankStanceCorner.BottomRight => topLeft + Config.Size - Vector2.One,
|
||||||
|
_ => topLeft
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private (int, int) GetTankStanceLinesDirections()
|
||||||
|
{
|
||||||
|
return Config.TankStanceIndicatorConfig.Corner switch
|
||||||
|
{
|
||||||
|
TankStanceCorner.TopLeft => (1, 1),
|
||||||
|
TankStanceCorner.TopRight => (1, -1),
|
||||||
|
TankStanceCorner.BottomLeft => (-1, 1),
|
||||||
|
_ => (-1, -1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Interface.GeneralElements;
|
||||||
|
using HSUI.Interface.Party;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace HSUI.Interface
|
||||||
|
{
|
||||||
|
[Exportable(false)]
|
||||||
|
public class VisibilityConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
[Checkbox("Hide outside of combat")]
|
||||||
|
[Order(5)]
|
||||||
|
public bool HideOutsideOfCombat = false;
|
||||||
|
|
||||||
|
[Checkbox("Hide in combat")]
|
||||||
|
[Order(6)]
|
||||||
|
public bool HideInCombat = false;
|
||||||
|
|
||||||
|
[Checkbox("Hide in Gold Saucer")]
|
||||||
|
[Order(7)]
|
||||||
|
public bool HideInGoldSaucer = false;
|
||||||
|
|
||||||
|
[Checkbox("Hide while at full HP")]
|
||||||
|
[Order(8)]
|
||||||
|
public bool HideOnFullHP = false;
|
||||||
|
|
||||||
|
[Checkbox("Hide when in duty")]
|
||||||
|
[Order(9)]
|
||||||
|
public bool HideInDuty = false;
|
||||||
|
|
||||||
|
[Checkbox("Hide in Island Sanctuary")]
|
||||||
|
[Order(10)]
|
||||||
|
public bool HideInIslandSanctuary = false;
|
||||||
|
|
||||||
|
[Checkbox("Hide in PvP")]
|
||||||
|
[Order(11)]
|
||||||
|
public bool HideInPvP = false;
|
||||||
|
|
||||||
|
[Checkbox("Always show when in duty")]
|
||||||
|
[Order(20)]
|
||||||
|
public bool ShowInDuty = false;
|
||||||
|
|
||||||
|
[Checkbox("Always show when weapon is drawn")]
|
||||||
|
[Order(21)]
|
||||||
|
public bool ShowOnWeaponDrawn = false;
|
||||||
|
|
||||||
|
[Checkbox("Always show when crafting")]
|
||||||
|
[Order(22)]
|
||||||
|
public bool ShowWhileCrafting = false;
|
||||||
|
|
||||||
|
[Checkbox("Always show when gathering")]
|
||||||
|
[Order(23)]
|
||||||
|
public bool ShowWhileGathering = false;
|
||||||
|
|
||||||
|
[Checkbox("Always show while in a party")]
|
||||||
|
[Order(24)]
|
||||||
|
public bool ShowInParty = false;
|
||||||
|
|
||||||
|
[Checkbox("Always show while in Island Sanctuary")]
|
||||||
|
[Order(25)]
|
||||||
|
public bool ShowInIslandSanctuary = false;
|
||||||
|
|
||||||
|
[Checkbox("Always show while in PvP")]
|
||||||
|
[Order(26)]
|
||||||
|
public bool ShowInPvP = false;
|
||||||
|
|
||||||
|
[Checkbox("Always show while target exists")]
|
||||||
|
[Order(27)]
|
||||||
|
public bool ShowWhileTargetExists = false;
|
||||||
|
|
||||||
|
|
||||||
|
private bool IsInCombat() => Plugin.Condition[ConditionFlag.InCombat];
|
||||||
|
|
||||||
|
private bool IsInDuty() => Plugin.Condition[ConditionFlag.BoundByDuty];
|
||||||
|
|
||||||
|
private bool IsCrafting() => Plugin.Condition[ConditionFlag.Crafting] || Plugin.Condition[ConditionFlag.ExecutingCraftingAction];
|
||||||
|
|
||||||
|
private bool IsGathering() => Plugin.Condition[ConditionFlag.Gathering] || Plugin.Condition[ConditionFlag.ExecutingGatheringAction];
|
||||||
|
|
||||||
|
private bool HasWeaponDrawn() => (Plugin.ObjectTable.LocalPlayer != null && Plugin.ObjectTable.LocalPlayer.StatusFlags.HasFlag(StatusFlags.WeaponOut));
|
||||||
|
|
||||||
|
private bool IsInGoldSaucer() => _goldSaucerIDs.Any(id => id == Plugin.ClientState.TerritoryType);
|
||||||
|
|
||||||
|
private bool IsInIslandSanctuary() => Plugin.ClientState.TerritoryType == 1055;
|
||||||
|
|
||||||
|
private readonly uint[] _goldSaucerIDs = { 144, 388, 389, 390, 391, 579, 792, 899, 941 };
|
||||||
|
|
||||||
|
public bool IsElementVisible(HudElement? element = null)
|
||||||
|
{
|
||||||
|
if (!Enabled) { return true; }
|
||||||
|
if (!ConfigurationManager.Instance.LockHUD) { return true; }
|
||||||
|
if (element != null && element.GetType() == typeof(PlayerCastbarHud)) { return true; }
|
||||||
|
if (element != null && !element.GetConfig().Enabled) { return false; }
|
||||||
|
|
||||||
|
bool isInIslandSanctuary = IsInIslandSanctuary();
|
||||||
|
bool isInDuty = IsInDuty() && !isInIslandSanctuary;
|
||||||
|
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
|
||||||
|
|
||||||
|
// show
|
||||||
|
if (ShowInDuty && isInDuty) { return true; }
|
||||||
|
|
||||||
|
if (ShowOnWeaponDrawn && HasWeaponDrawn()) { return true; }
|
||||||
|
|
||||||
|
if (ShowWhileCrafting && IsCrafting()) { return true; }
|
||||||
|
|
||||||
|
if (ShowWhileGathering && IsGathering()) { return true; }
|
||||||
|
|
||||||
|
if (ShowInParty && PartyManager.Instance.MemberCount > 1) { return true; }
|
||||||
|
|
||||||
|
if (ShowInIslandSanctuary && isInIslandSanctuary) { return true; }
|
||||||
|
|
||||||
|
if (ShowInPvP && Plugin.ClientState.IsPvP) { return true; }
|
||||||
|
|
||||||
|
if (ShowWhileTargetExists && player != null && player.TargetObject != null) { return true; }
|
||||||
|
|
||||||
|
|
||||||
|
// hide
|
||||||
|
if (HideOutsideOfCombat && !IsInCombat()) { return false; }
|
||||||
|
|
||||||
|
if (HideInCombat && IsInCombat()) { return false; }
|
||||||
|
|
||||||
|
if (HideInGoldSaucer && IsInGoldSaucer()) { return false; }
|
||||||
|
|
||||||
|
if (HideOnFullHP && player != null && player.CurrentHp == player.MaxHp) { return false; }
|
||||||
|
|
||||||
|
if (HideInDuty && isInDuty) { return false; }
|
||||||
|
|
||||||
|
if (HideInIslandSanctuary && isInIslandSanctuary) { return false; }
|
||||||
|
|
||||||
|
if (HideInPvP && Plugin.ClientState.IsPvP) { return false; }
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyFrom(VisibilityConfig config)
|
||||||
|
{
|
||||||
|
Enabled = config.Enabled;
|
||||||
|
|
||||||
|
HideOutsideOfCombat = config.HideOutsideOfCombat;
|
||||||
|
HideInCombat = config.HideInCombat;
|
||||||
|
HideInGoldSaucer = config.HideInGoldSaucer;
|
||||||
|
HideOnFullHP = config.HideOnFullHP;
|
||||||
|
HideInDuty = config.HideInDuty;
|
||||||
|
HideInIslandSanctuary = config.HideInIslandSanctuary;
|
||||||
|
HideInPvP = config.HideInPvP;
|
||||||
|
|
||||||
|
ShowInDuty = config.ShowInDuty;
|
||||||
|
ShowOnWeaponDrawn = config.ShowOnWeaponDrawn;
|
||||||
|
ShowWhileCrafting = config.ShowWhileCrafting;
|
||||||
|
ShowWhileGathering = config.ShowWhileGathering;
|
||||||
|
ShowInParty = config.ShowInParty;
|
||||||
|
ShowInIslandSanctuary = config.ShowInIslandSanctuary;
|
||||||
|
ShowInPvP = config.ShowInPvP;
|
||||||
|
ShowWhileTargetExists = config.ShowWhileTargetExists;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VisibilityConfig()
|
||||||
|
{
|
||||||
|
Enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using HSUI.Config.Attributes;
|
||||||
|
using HSUI.Helpers;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
|
||||||
|
namespace HSUI.Interface.GeneralElements
|
||||||
|
{
|
||||||
|
public enum WindowClippingMode
|
||||||
|
{
|
||||||
|
Full,
|
||||||
|
Hide,
|
||||||
|
Performance
|
||||||
|
}
|
||||||
|
|
||||||
|
[Exportable(false)]
|
||||||
|
[Disableable(false)]
|
||||||
|
[Section("Misc")]
|
||||||
|
[SubSection("Window Clipping", 0)]
|
||||||
|
public class WindowClippingConfig : PluginConfigObject
|
||||||
|
{
|
||||||
|
public new static WindowClippingConfig DefaultConfig() => new WindowClippingConfig();
|
||||||
|
|
||||||
|
public WindowClippingMode Mode = WindowClippingMode.Full;
|
||||||
|
|
||||||
|
public bool NameplatesClipRectsEnabled = true;
|
||||||
|
public bool TargetCastbarClipRectEnabled = false;
|
||||||
|
public bool HotbarsClipRectsEnabled = false;
|
||||||
|
public bool ChatBubblesPlayersClipRectsEnabled = true;
|
||||||
|
public bool ChatBubblesNPCClipRectsEnabled = true;
|
||||||
|
|
||||||
|
public bool ThirdPartyClipRectsEnabled = true;
|
||||||
|
|
||||||
|
private bool _showConfirmationDialog = false;
|
||||||
|
|
||||||
|
[ManualDraw]
|
||||||
|
public bool Draw(ref bool changed)
|
||||||
|
{
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
|
||||||
|
if (ImGui.Checkbox("Enabled", ref Enabled))
|
||||||
|
{
|
||||||
|
if (Enabled)
|
||||||
|
{
|
||||||
|
Enabled = false;
|
||||||
|
_showConfirmationDialog = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirmation dialog
|
||||||
|
if (_showConfirmationDialog)
|
||||||
|
{
|
||||||
|
string[] lines = new string[] { "THIS FEATURE IS KNOWN TO CAUSE RANDOM", "CRASHES TO A SMALL PORTION OF USERS!!!", "Are you sure you want to enable it?" };
|
||||||
|
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("WARNING!", lines);
|
||||||
|
|
||||||
|
if (didConfirm)
|
||||||
|
{
|
||||||
|
Enabled = true;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didConfirm || didClose)
|
||||||
|
{
|
||||||
|
_showConfirmationDialog = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Enabled) { return changed; }
|
||||||
|
|
||||||
|
// mode
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text("Mode: ");
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.RadioButton("Full", Mode == WindowClippingMode.Full))
|
||||||
|
{
|
||||||
|
Mode = WindowClippingMode.Full;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.RadioButton("Hide", Mode == WindowClippingMode.Hide))
|
||||||
|
{
|
||||||
|
Mode = WindowClippingMode.Hide;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.RadioButton("Performance", Mode == WindowClippingMode.Performance))
|
||||||
|
{
|
||||||
|
Mode = WindowClippingMode.Performance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nameplates
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
changed |= ImGui.Checkbox("Enable special clipping for Nameplates", ref NameplatesClipRectsEnabled);
|
||||||
|
ImGuiHelper.SetTooltip("When enabled, Nameplates will get covered by game UI elements that wouldn't normally cover HSUI elements.");
|
||||||
|
|
||||||
|
if (NameplatesClipRectsEnabled)
|
||||||
|
{
|
||||||
|
ImGuiHelper.Tab(); ImGuiHelper.Tab();
|
||||||
|
changed |= ImGui.Checkbox("Default Target Castbar", ref TargetCastbarClipRectEnabled);
|
||||||
|
ImGuiHelper.SetTooltip("When enabled, the game's target castbar will not be covered by HSUI Nameplates.\nFor players that prefer to use the default target cast bar over HSUI's.");
|
||||||
|
|
||||||
|
ImGuiHelper.Tab(); ImGuiHelper.Tab();
|
||||||
|
changed |= ImGui.Checkbox("Hotbars", ref HotbarsClipRectsEnabled);
|
||||||
|
ImGuiHelper.SetTooltip("When enabled, active hotbar will not be covered by HSUI Nameplates.\nNote that the way this is calculated is not perfect and it might not work well for hotbars that have empty slots.");
|
||||||
|
|
||||||
|
ImGuiHelper.Tab(); ImGuiHelper.Tab();
|
||||||
|
changed |= ImGui.Checkbox("NPC Chat Bubbles", ref ChatBubblesNPCClipRectsEnabled);
|
||||||
|
|
||||||
|
ImGuiHelper.Tab(); ImGuiHelper.Tab();
|
||||||
|
changed |= ImGui.Checkbox("Player Chat Bubbles", ref ChatBubblesPlayersClipRectsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// third party
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
changed |= ImGui.Checkbox("Enable clipping for other plugins", ref ThirdPartyClipRectsEnabled);
|
||||||
|
ImGuiHelper.SetTooltip("When enabled, other plugins' windows can also be clipped so HSUI elements don't cover them.\nPlease note that this requires the developer of each third party plugin to implement the feature.");
|
||||||
|
|
||||||
|
// text
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
switch (Mode)
|
||||||
|
{
|
||||||
|
case WindowClippingMode.Full:
|
||||||
|
ImGui.Text("HSUI will attempt to not cover game windows in this mode by clipping around them.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WindowClippingMode.Hide:
|
||||||
|
ImGui.Text("HSUI will attempt to not cover game windows in this mode by not drawing an element if its touching a game window.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WindowClippingMode.Performance:
|
||||||
|
ImGui.Text("Window Clipping functionality will be reduced in favor of performance.\nOnly one game window will be clipped at a time. This might yield unexpected / ugly results.\n\nNote: This mode won't work well with Nameplates.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiHelper.NewLineAndTab();
|
||||||
|
ImGui.Text("If you're experiencing random crashes or bad performance, we recommend you try a different mode\nor disable Window Clipping altogether");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
using HSUI.Config;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using HSUI.Enums;
|
||||||
|
|
||||||
|
namespace HSUI.Interface
|
||||||
|
{
|
||||||
|
public abstract class HudElement : IDisposable
|
||||||
|
{
|
||||||
|
protected MovablePluginConfigObject _config;
|
||||||
|
public MovablePluginConfigObject GetConfig() { return _config; }
|
||||||
|
|
||||||
|
public string ID => _config.ID;
|
||||||
|
|
||||||
|
private Dictionary<StrataLevel, List<Action>> _drawActions = new Dictionary<StrataLevel, List<Action>>();
|
||||||
|
|
||||||
|
public HudElement(MovablePluginConfigObject config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrepareForDraw(Vector2 origin)
|
||||||
|
{
|
||||||
|
_drawActions.Clear();
|
||||||
|
CreateDrawActions(origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Draw(Vector2 origin)
|
||||||
|
{
|
||||||
|
// iterate like this so it goes in order
|
||||||
|
StrataLevel[] levels = (StrataLevel[])Enum.GetValues(typeof(StrataLevel));
|
||||||
|
foreach (StrataLevel key in levels)
|
||||||
|
{
|
||||||
|
_drawActions.TryGetValue(key, out List<Action>? drawActions);
|
||||||
|
if (drawActions == null) { continue; }
|
||||||
|
|
||||||
|
foreach (Action drawAction in _drawActions[key])
|
||||||
|
{
|
||||||
|
drawAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AddDrawAction(StrataLevel strataLevel, Action drawAction)
|
||||||
|
{
|
||||||
|
_drawActions.TryGetValue(strataLevel, out List<Action>? drawActions);
|
||||||
|
|
||||||
|
if (drawActions == null)
|
||||||
|
{
|
||||||
|
drawActions = new List<Action>();
|
||||||
|
_drawActions.Add(strataLevel, drawActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawActions.Add(drawAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AddDrawActions(List<(StrataLevel, Action)> drawActions)
|
||||||
|
{
|
||||||
|
foreach ((StrataLevel strataLevel, Action drawAction) in drawActions)
|
||||||
|
{
|
||||||
|
AddDrawAction(strataLevel, drawAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void CreateDrawActions(Vector2 origin);
|
||||||
|
|
||||||
|
~HudElement()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InternalDispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void InternalDispose()
|
||||||
|
{
|
||||||
|
// override
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IHudElementWithActor
|
||||||
|
{
|
||||||
|
public IGameObject? Actor { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IHudElementWithAnchorableParent
|
||||||
|
{
|
||||||
|
public AnchorablePluginConfigObject? ParentConfig { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IHudElementWithMouseOver
|
||||||
|
{
|
||||||
|
public void StopMouseover();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IHudElementWithPreview
|
||||||
|
{
|
||||||
|
public void StopPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IHudElementWithVisibilityConfig
|
||||||
|
{
|
||||||
|
public VisibilityConfig? VisibilityConfig { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user