Initial release: HSUI v1.0.0.0 - HUD replacement with configurable hotbars
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
namespace HSUI.Interface.StatusEffects
|
||||
{
|
||||
public class CustomEffectsListHud : StatusEffectsListHud
|
||||
{
|
||||
public CustomEffectsListHud(StatusEffectsListConfig config, string displayName) : base(config, displayName)
|
||||
{
|
||||
}
|
||||
|
||||
public IGameObject? TargetActor { get; set; } = null!;
|
||||
|
||||
protected override List<StatusEffectData> StatusEffectsData()
|
||||
{
|
||||
var list = StatusEffectDataList(TargetActor);
|
||||
list.AddRange(StatusEffectDataList(Actor));
|
||||
|
||||
// cull duplicate statuses from the same source
|
||||
list = list.GroupBy(s => new { s.Status.StatusId, s.Status.SourceObject.Id })
|
||||
.Select(status => status.First())
|
||||
.ToList();
|
||||
|
||||
// show mine or permanent first
|
||||
if (Config.ShowMineFirst || Config.ShowPermanentFirst)
|
||||
{
|
||||
return OrderByMineOrPermanentFirst(list);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,859 @@
|
||||
using Dalamud.Interface;
|
||||
using HSUI.Config;
|
||||
using HSUI.Config.Attributes;
|
||||
using HSUI.Enums;
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface.Bars;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace HSUI.Interface.StatusEffects
|
||||
{
|
||||
[Section("Buffs and Debuffs")]
|
||||
[SubSection("Player Buffs", 0)]
|
||||
public class PlayerBuffsListConfig : UnitFrameStatusEffectsListConfig
|
||||
{
|
||||
public new static PlayerBuffsListConfig DefaultConfig()
|
||||
{
|
||||
var screenSize = ImGui.GetMainViewport().Size;
|
||||
var pos = new Vector2(screenSize.X * 0.38f, -screenSize.Y * 0.45f);
|
||||
var iconConfig = new StatusEffectIconConfig();
|
||||
iconConfig.DispellableBorderConfig.Enabled = false;
|
||||
|
||||
return new PlayerBuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, true, false, true, GrowthDirections.Left | GrowthDirections.Down, iconConfig);
|
||||
}
|
||||
|
||||
public PlayerBuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Section("Buffs and Debuffs")]
|
||||
[SubSection("Player Debuffs", 0)]
|
||||
public class PlayerDebuffsListConfig : UnitFrameStatusEffectsListConfig
|
||||
{
|
||||
public new static PlayerDebuffsListConfig DefaultConfig()
|
||||
{
|
||||
var screenSize = ImGui.GetMainViewport().Size;
|
||||
var pos = new Vector2(screenSize.X * 0.38f, -screenSize.Y * 0.45f + HUDConstants.DefaultStatusEffectsListSize.Y);
|
||||
var iconConfig = new StatusEffectIconConfig();
|
||||
|
||||
return new PlayerDebuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, false, true, true, GrowthDirections.Left | GrowthDirections.Down, iconConfig);
|
||||
}
|
||||
|
||||
public PlayerDebuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Section("Buffs and Debuffs")]
|
||||
[SubSection("Target Buffs", 0)]
|
||||
public class TargetBuffsListConfig : UnitFrameStatusEffectsListConfig
|
||||
{
|
||||
public new static TargetBuffsListConfig DefaultConfig()
|
||||
{
|
||||
var pos = new Vector2(0, -1);
|
||||
var iconConfig = new StatusEffectIconConfig();
|
||||
iconConfig.DispellableBorderConfig.Enabled = false;
|
||||
|
||||
var config = new TargetBuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, true, false, true, GrowthDirections.Right | GrowthDirections.Up, iconConfig);
|
||||
config.AnchorToUnitFrame = true;
|
||||
config.UnitFrameAnchor = DrawAnchor.TopLeft;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public TargetBuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Section("Buffs and Debuffs")]
|
||||
[SubSection("Target Debuffs", 0)]
|
||||
public class TargetDebuffsListConfig : UnitFrameStatusEffectsListConfig
|
||||
{
|
||||
public new static TargetDebuffsListConfig DefaultConfig()
|
||||
{
|
||||
var pos = new Vector2(0, -85);
|
||||
var iconConfig = new StatusEffectIconConfig();
|
||||
iconConfig.DispellableBorderConfig.Enabled = false;
|
||||
|
||||
var config = new TargetDebuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, false, true, true, GrowthDirections.Right | GrowthDirections.Up, iconConfig);
|
||||
config.AnchorToUnitFrame = true;
|
||||
config.UnitFrameAnchor = DrawAnchor.TopLeft;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public TargetDebuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Section("Buffs and Debuffs")]
|
||||
[SubSection("Focus Target Buffs", 0)]
|
||||
public class FocusTargetBuffsListConfig : UnitFrameStatusEffectsListConfig
|
||||
{
|
||||
public new static FocusTargetBuffsListConfig DefaultConfig()
|
||||
{
|
||||
var pos = new Vector2(0, -1);
|
||||
var iconConfig = new StatusEffectIconConfig();
|
||||
iconConfig.DispellableBorderConfig.Enabled = false;
|
||||
|
||||
var config = new FocusTargetBuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, true, false, true, GrowthDirections.Right | GrowthDirections.Up, iconConfig);
|
||||
config.AnchorToUnitFrame = true;
|
||||
config.UnitFrameAnchor = DrawAnchor.TopLeft;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public FocusTargetBuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Section("Buffs and Debuffs")]
|
||||
[SubSection("Focus Target Debuffs", 0)]
|
||||
public class FocusTargetDebuffsListConfig : UnitFrameStatusEffectsListConfig
|
||||
{
|
||||
public new static FocusTargetDebuffsListConfig DefaultConfig()
|
||||
{
|
||||
var pos = new Vector2(0, -85);
|
||||
var iconConfig = new StatusEffectIconConfig();
|
||||
iconConfig.DispellableBorderConfig.Enabled = false;
|
||||
|
||||
var config = new FocusTargetDebuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, false, true, true, GrowthDirections.Right | GrowthDirections.Up, iconConfig);
|
||||
config.AnchorToUnitFrame = true;
|
||||
config.UnitFrameAnchor = DrawAnchor.TopLeft;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public FocusTargetDebuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class UnitFrameStatusEffectsListConfig : StatusEffectsListConfig
|
||||
{
|
||||
[Checkbox("Anchor to Unit Frame")]
|
||||
[Order(16)]
|
||||
public bool AnchorToUnitFrame = false;
|
||||
|
||||
[Anchor("Unit Frame Anchor")]
|
||||
[Order(17, collapseWith = nameof(AnchorToUnitFrame))]
|
||||
public DrawAnchor UnitFrameAnchor = DrawAnchor.TopLeft;
|
||||
|
||||
[NestedConfig("Visibility", 200)]
|
||||
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
|
||||
|
||||
public UnitFrameStatusEffectsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class StatusEffectsListConfig : MovablePluginConfigObject
|
||||
{
|
||||
public bool ShowBuffs;
|
||||
public bool ShowDebuffs;
|
||||
|
||||
[DragInt2("Size", min = 1, max = 4000)]
|
||||
[Order(15)]
|
||||
public Vector2 Size;
|
||||
|
||||
[DragInt2("Icon Padding", min = 0, max = 500)]
|
||||
[Order(19)]
|
||||
public Vector2 IconPadding = new(2, 2);
|
||||
|
||||
[Checkbox("Preview", isMonitored = true)]
|
||||
[Order(20)]
|
||||
public bool Preview;
|
||||
|
||||
[Checkbox("Fill Rows First", spacing = true)]
|
||||
[Order(25)]
|
||||
public bool FillRowsFirst = true;
|
||||
|
||||
[Combo("Icons Growth Direction",
|
||||
"Right and Down",
|
||||
"Right and Up",
|
||||
"Left and Down",
|
||||
"Left and Up",
|
||||
"Centered and Up",
|
||||
"Centered and Down",
|
||||
"Centered and Left",
|
||||
"Centered and Right"
|
||||
)]
|
||||
[Order(30)]
|
||||
public int Directions;
|
||||
|
||||
[DragInt("Limit (-1 for no limit)", min = -1, max = 1000)]
|
||||
[Order(35)]
|
||||
public int Limit = -1;
|
||||
|
||||
[Checkbox("Permanent Effects", spacing = true)]
|
||||
[Order(40)]
|
||||
public bool ShowPermanentEffects;
|
||||
|
||||
[Checkbox("Permanent Effects First")]
|
||||
[Order(41)]
|
||||
public bool ShowPermanentFirst;
|
||||
|
||||
[Checkbox("Only My Effects", spacing = true)]
|
||||
[Order(42)]
|
||||
public bool ShowOnlyMine = false;
|
||||
|
||||
[Checkbox("My Effects First")]
|
||||
[Order(43)]
|
||||
public bool ShowMineFirst = false;
|
||||
|
||||
[Checkbox("Pet As Own Effect")]
|
||||
[Order(44)]
|
||||
public bool IncludePetAsOwn = false;
|
||||
|
||||
[Checkbox("Sort by Duration", spacing = true, help = "If enabled, \"Permanent Effects First\" and \"My Effects First\" will be ignored!")]
|
||||
[Order(45)]
|
||||
public bool SortByDuration = false;
|
||||
|
||||
[RadioSelector("Ascending", "Descending")]
|
||||
[Order(46, collapseWith = nameof(SortByDuration))]
|
||||
public StatusEffectDurationSortType DurationSortType = StatusEffectDurationSortType.Ascending;
|
||||
|
||||
[Checkbox("Tooltips", spacing = true)]
|
||||
[Order(47)]
|
||||
public bool ShowTooltips = true;
|
||||
|
||||
[Checkbox("Disable Interaction", help = "Enabling this will disable right clicking buffs off, or the shortcut to blacklist/whitelist a status effect.")]
|
||||
[Order(48)]
|
||||
public bool DisableInteraction = false;
|
||||
|
||||
[Checkbox("Hide when Dead")]
|
||||
[Order(49)]
|
||||
public bool HideWhenDead = true;
|
||||
|
||||
[NestedConfig("Icons", 65)]
|
||||
public StatusEffectIconConfig IconConfig;
|
||||
|
||||
[NestedConfig("Filter Status Effects", 70, separator = true, spacing = false, collapsingHeader = false)]
|
||||
public StatusEffectsBlacklistConfig BlacklistConfig = new StatusEffectsBlacklistConfig();
|
||||
|
||||
|
||||
public StatusEffectsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
{
|
||||
Position = position;
|
||||
Size = size;
|
||||
ShowBuffs = showBuffs;
|
||||
ShowDebuffs = showDebuffs;
|
||||
ShowPermanentEffects = showPermanentEffects;
|
||||
|
||||
SetGrowthDirections(growthDirections);
|
||||
|
||||
IconConfig = iconConfig;
|
||||
|
||||
Strata = StrataLevel.HIGH;
|
||||
}
|
||||
|
||||
private void SetGrowthDirections(GrowthDirections growthDirections)
|
||||
{
|
||||
Directions = LayoutHelper.IndexFromGrowthDirections(growthDirections);
|
||||
}
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
[Disableable(false)]
|
||||
public class StatusEffectIconConfig : PluginConfigObject
|
||||
{
|
||||
[DragInt2("Icon Size", min = 1, max = 1000)]
|
||||
[Order(5)]
|
||||
public Vector2 Size = new(40, 40);
|
||||
|
||||
[Checkbox("Crop Icon", spacing = true)]
|
||||
[Order(20)]
|
||||
public bool CropIcon = true;
|
||||
|
||||
[NestedConfig("Border", 25, collapseWith = nameof(CropIcon), collapsingHeader = false)]
|
||||
public StatusEffectIconBorderConfig BorderConfig = new();
|
||||
|
||||
[NestedConfig("Shadow", 26, collapseWith = nameof(CropIcon), collapsingHeader = false)]
|
||||
public ShadowConfig ShadowConfig = new ShadowConfig() { Enabled = false };
|
||||
|
||||
[NestedConfig("Dispellable Effects Border", 30, collapseWith = nameof(CropIcon), collapsingHeader = false)]
|
||||
public StatusEffectIconBorderConfig DispellableBorderConfig = new(new PluginConfigColor(new Vector4(141f / 255f, 206f / 255f, 229f / 255f, 100f / 100f)), 2);
|
||||
|
||||
[NestedConfig("My Effects Border", 35, collapseWith = nameof(CropIcon), collapsingHeader = false)]
|
||||
public StatusEffectIconBorderConfig OwnedBorderConfig = new(new PluginConfigColor(new Vector4(35f / 255f, 179f / 255f, 69f / 255f, 100f / 100f)), 1);
|
||||
|
||||
[NestedConfig("Duration", 50)]
|
||||
public LabelConfig DurationLabelConfig;
|
||||
|
||||
[NestedConfig("Stacks", 60)]
|
||||
public LabelConfig StacksLabelConfig;
|
||||
|
||||
public StatusEffectIconConfig(LabelConfig? durationLabelConfig = null, LabelConfig? stacksLabelConfig = null)
|
||||
{
|
||||
DurationLabelConfig = durationLabelConfig ?? StatusEffectsListsDefaults.DefaultDurationLabelConfig();
|
||||
StacksLabelConfig = stacksLabelConfig ?? StatusEffectsListsDefaults.DefaultStacksLabelConfig();
|
||||
}
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
public class StatusEffectIconBorderConfig : PluginConfigObject
|
||||
{
|
||||
[ColorEdit4("Color")]
|
||||
[Order(5)]
|
||||
public PluginConfigColor Color = new(Vector4.UnitW);
|
||||
|
||||
[DragInt("Thickness", min = 1, max = 100)]
|
||||
[Order(10)]
|
||||
public int Thickness = 1;
|
||||
|
||||
public StatusEffectIconBorderConfig()
|
||||
{
|
||||
}
|
||||
|
||||
public StatusEffectIconBorderConfig(PluginConfigColor color, int thickness)
|
||||
{
|
||||
Color = color;
|
||||
Thickness = thickness;
|
||||
}
|
||||
}
|
||||
|
||||
internal class StatusEffectsListsDefaults
|
||||
{
|
||||
internal static LabelConfig DefaultDurationLabelConfig()
|
||||
{
|
||||
return new LabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
|
||||
}
|
||||
|
||||
internal static LabelConfig DefaultStacksLabelConfig()
|
||||
{
|
||||
var config = new LabelConfig(new Vector2(16, -11), "", DrawAnchor.Center, DrawAnchor.Center);
|
||||
config.Color = new(Vector4.UnitW);
|
||||
config.OutlineColor = new(Vector4.One);
|
||||
config.ShadowConfig.Color = new(Vector4.One);
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FilterType
|
||||
{
|
||||
Blacklist,
|
||||
Whitelist
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
public class StatusEffectsBlacklistConfig : PluginConfigObject
|
||||
{
|
||||
[RadioSelector(typeof(FilterType))]
|
||||
[Order(5)]
|
||||
public FilterType FilterType;
|
||||
|
||||
public SortedList<string, uint> List = new SortedList<string, uint>();
|
||||
|
||||
[JsonIgnore] private string? _errorMessage = null;
|
||||
[JsonIgnore] private string? _importString = null;
|
||||
[JsonIgnore] private bool _clearingList = false;
|
||||
|
||||
private string KeyName(Status status)
|
||||
{
|
||||
return $"{status.Name}[{status.RowId.ToString()}]";
|
||||
}
|
||||
|
||||
public bool StatusAllowed(Status status)
|
||||
{
|
||||
bool inList = List.Any(pair => pair.Value == status.RowId);
|
||||
if ((inList && FilterType == FilterType.Blacklist) || (!inList && FilterType == FilterType.Whitelist))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool AddNewEntry(Status? status)
|
||||
{
|
||||
if (status != null && status.HasValue && !List.ContainsKey(KeyName(status.Value)))
|
||||
{
|
||||
List.Add(KeyName(status.Value), status.Value.RowId);
|
||||
_input = "";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AddNewEntry(string input, ExcelSheet<Status>? sheet)
|
||||
{
|
||||
if (input.Length > 0 && sheet != null)
|
||||
{
|
||||
List<Status> statusToAdd = new List<Status>();
|
||||
|
||||
// try id
|
||||
if (uint.TryParse(input, out uint uintValue))
|
||||
{
|
||||
if (uintValue > 0)
|
||||
{
|
||||
Status? status = sheet.GetRow(uintValue);
|
||||
if (status != null && status.HasValue)
|
||||
{
|
||||
statusToAdd.Add(status.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try name
|
||||
if (statusToAdd.Count == 0)
|
||||
{
|
||||
var enumerator = sheet.GetEnumerator();
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
Status item = enumerator.Current;
|
||||
if (item.Name.ToString().ToLower() == input.ToLower())
|
||||
{
|
||||
statusToAdd.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool added = false;
|
||||
foreach (Status status in statusToAdd)
|
||||
{
|
||||
added |= AddNewEntry(status);
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string ExportList()
|
||||
{
|
||||
string exportString = "";
|
||||
|
||||
for (int i = 0; i < List.Keys.Count; i++)
|
||||
{
|
||||
exportString += List.Keys[i] + "|";
|
||||
exportString += List.Values[i] + "|";
|
||||
}
|
||||
|
||||
return exportString;
|
||||
}
|
||||
|
||||
private string? ImportList(string importString)
|
||||
{
|
||||
SortedList<string, uint> tmpList = new SortedList<string, uint>();
|
||||
|
||||
try
|
||||
{
|
||||
string[] strings = importString.Trim().Split("|", StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int i = 0; i < strings.Length; i += 2)
|
||||
{
|
||||
if (i + 1 >= strings.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
string key = strings[i];
|
||||
uint value = uint.Parse(strings[i + 1]);
|
||||
|
||||
tmpList.Add(key, value);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "Error importing list!";
|
||||
}
|
||||
|
||||
List = tmpList;
|
||||
return null;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
private string _input = "";
|
||||
|
||||
[ManualDraw]
|
||||
public bool Draw(ref bool changed)
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var flags =
|
||||
ImGuiTableFlags.RowBg |
|
||||
ImGuiTableFlags.Borders |
|
||||
ImGuiTableFlags.BordersOuter |
|
||||
ImGuiTableFlags.BordersInner |
|
||||
ImGuiTableFlags.ScrollY |
|
||||
ImGuiTableFlags.SizingFixedSame;
|
||||
|
||||
var sheet = Plugin.DataManager.GetExcelSheet<Status>();
|
||||
var iconSize = new Vector2(30, 30);
|
||||
var indexToRemove = -1;
|
||||
|
||||
if (ImGui.BeginChild("Filter Effects", new Vector2(0, 360), false, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
|
||||
{
|
||||
ImGui.Text(" ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text("Type an ID or Name");
|
||||
|
||||
ImGui.Text(" ");
|
||||
ImGui.SameLine();
|
||||
ImGui.PushItemWidth(300);
|
||||
if (ImGui.InputText("", ref _input, 64, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||
{
|
||||
changed |= AddNewEntry(_input, sheet);
|
||||
ImGui.SetKeyboardFocusHere(-1);
|
||||
}
|
||||
|
||||
// add
|
||||
ImGui.SameLine();
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), new Vector2(0, 0)))
|
||||
{
|
||||
changed |= AddNewEntry(_input, sheet);
|
||||
ImGui.SetKeyboardFocusHere(-2);
|
||||
}
|
||||
|
||||
// export
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 154);
|
||||
if (ImGui.Button(FontAwesomeIcon.Upload.ToIconString(), new Vector2(0, 0)))
|
||||
{
|
||||
ImGui.SetClipboardText(ExportList());
|
||||
ImGui.OpenPopup("export_succes_popup");
|
||||
}
|
||||
ImGui.PopFont();
|
||||
ImGuiHelper.SetTooltip("Export List to Clipboard");
|
||||
|
||||
// export success popup
|
||||
if (ImGui.BeginPopup("export_succes_popup"))
|
||||
{
|
||||
ImGui.Text("List exported to clipboard!");
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
// import
|
||||
ImGui.SameLine();
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button(FontAwesomeIcon.Download.ToIconString(), new Vector2(0, 0)))
|
||||
{
|
||||
_importString = ImGui.GetClipboardText();
|
||||
}
|
||||
ImGui.PopFont();
|
||||
ImGuiHelper.SetTooltip("Import List from Clipboard");
|
||||
|
||||
// clear
|
||||
ImGui.SameLine();
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), new Vector2(0, 0)))
|
||||
{
|
||||
_clearingList = true;
|
||||
}
|
||||
ImGui.PopFont();
|
||||
ImGuiHelper.SetTooltip("Clear List");
|
||||
|
||||
ImGui.Text(" ");
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.BeginTable("table", 4, flags, new Vector2(583, List.Count > 0 ? 200 : 40)))
|
||||
{
|
||||
ImGui.TableSetupColumn("Icon", ImGuiTableColumnFlags.WidthFixed, 0, 0);
|
||||
ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed, 0, 1);
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 0, 2);
|
||||
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, 0, 3);
|
||||
|
||||
ImGui.TableSetupScrollFreeze(0, 1);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
for (int i = 0; i < List.Count; i++)
|
||||
{
|
||||
var id = List.Values[i];
|
||||
var name = List.Keys[i];
|
||||
var row = sheet?.GetRow(id);
|
||||
|
||||
if (_input != "" && !name.ToUpper().Contains(_input.ToUpper()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui.PushID(i.ToString());
|
||||
ImGui.TableNextRow(ImGuiTableRowFlags.None, iconSize.Y);
|
||||
|
||||
// icon
|
||||
if (ImGui.TableSetColumnIndex(0))
|
||||
{
|
||||
if (row != null)
|
||||
{
|
||||
DrawHelper.DrawIcon<Status>(row, ImGui.GetCursorPos(), iconSize, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
// id
|
||||
if (ImGui.TableSetColumnIndex(1))
|
||||
{
|
||||
ImGui.Text(id.ToString());
|
||||
}
|
||||
|
||||
// name
|
||||
if (ImGui.TableSetColumnIndex(2))
|
||||
{
|
||||
ImGui.Text(row != null && row.HasValue ? row.Value.Name.ToString() : name);
|
||||
}
|
||||
|
||||
// remove
|
||||
if (ImGui.TableSetColumnIndex(3))
|
||||
{
|
||||
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.Trash.ToIconString(), iconSize))
|
||||
{
|
||||
changed = true;
|
||||
indexToRemove = i;
|
||||
}
|
||||
ImGui.PopFont();
|
||||
ImGui.PopStyleColor(3);
|
||||
}
|
||||
ImGui.PopID();
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
ImGui.Text(" ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text("Tip: You can [Ctrl + Alt + Shift] + Left Click on a status effect to automatically add it to the list.");
|
||||
|
||||
}
|
||||
|
||||
if (indexToRemove >= 0)
|
||||
{
|
||||
List.RemoveAt(indexToRemove);
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
|
||||
// error message
|
||||
if (_errorMessage != null)
|
||||
{
|
||||
if (ImGuiHelper.DrawErrorModal(_errorMessage))
|
||||
{
|
||||
_errorMessage = null;
|
||||
}
|
||||
}
|
||||
|
||||
// import confirmation
|
||||
if (_importString != null)
|
||||
{
|
||||
string[] message = new string[] {
|
||||
"All the elements in the list will be replaced.",
|
||||
"Are you sure you want to import?"
|
||||
};
|
||||
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Import?", message);
|
||||
|
||||
if (didConfirm)
|
||||
{
|
||||
_errorMessage = ImportList(_importString);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (didConfirm || didClose)
|
||||
{
|
||||
_importString = null;
|
||||
}
|
||||
}
|
||||
|
||||
// clear confirmation
|
||||
if (_clearingList)
|
||||
{
|
||||
string message = "Are you sure you want to clear the list?";
|
||||
|
||||
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Clear List?", message);
|
||||
|
||||
if (didConfirm)
|
||||
{
|
||||
List.Clear();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (didConfirm || didClose)
|
||||
{
|
||||
_clearingList = false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class StatusEffectsBlacklistConfigConverter : PluginConfigObjectConverter
|
||||
{
|
||||
public StatusEffectsBlacklistConfigConverter()
|
||||
{
|
||||
NewTypeFieldConverter<bool, FilterType> converter;
|
||||
converter = new NewTypeFieldConverter<bool, FilterType>("FilterType", FilterType.Blacklist, (oldValue) =>
|
||||
{
|
||||
return oldValue ? FilterType.Whitelist : FilterType.Blacklist;
|
||||
});
|
||||
|
||||
FieldConvertersMap.Add("UseAsWhitelist", converter);
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(StatusEffectsBlacklistConfig);
|
||||
}
|
||||
}
|
||||
|
||||
[Section("Buffs and Debuffs")]
|
||||
[SubSection("Custom Effects", 0)]
|
||||
public class CustomEffectsListConfig : StatusEffectsListConfig
|
||||
{
|
||||
public new static CustomEffectsListConfig DefaultConfig()
|
||||
{
|
||||
var iconConfig = new StatusEffectIconConfig();
|
||||
iconConfig.DispellableBorderConfig.Enabled = false;
|
||||
iconConfig.Size = new Vector2(30, 30);
|
||||
|
||||
var pos = new Vector2(0, HUDConstants.BaseHUDOffsetY);
|
||||
var size = new Vector2(250, iconConfig.Size.Y * 3 + 10);
|
||||
|
||||
var config = new CustomEffectsListConfig(pos, size, true, true, false, GrowthDirections.Centered | GrowthDirections.Up, iconConfig);
|
||||
config.Enabled = false;
|
||||
config.Directions = 5;
|
||||
|
||||
// pre-populated white list
|
||||
config.BlacklistConfig.FilterType = FilterType.Whitelist;
|
||||
|
||||
ExcelSheet<Status>? sheet = Plugin.DataManager.GetExcelSheet<Status>();
|
||||
if (sheet != null)
|
||||
{
|
||||
// Left Eye
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1184));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1454));
|
||||
|
||||
// Battle Litany
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(786));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1414));
|
||||
|
||||
// Brotherhood
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1185));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2174));
|
||||
|
||||
// Battle Voice
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(141));
|
||||
|
||||
// Devilment
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1825));
|
||||
|
||||
// Technical Finish
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1822));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2050));
|
||||
|
||||
// Standard Finish
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1821));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2024));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2105));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2113));
|
||||
|
||||
// Embolden
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1239));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1297));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2282));
|
||||
|
||||
// Devotion
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1213));
|
||||
|
||||
// Divination
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1878));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2034));
|
||||
|
||||
// Chain Stratagem
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1221));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1406));
|
||||
|
||||
// Radiant Finale
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2722));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2964));
|
||||
|
||||
// Arcane Circle
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2599));
|
||||
|
||||
// Searing Light
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2703));
|
||||
|
||||
// Trick Attack
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(638));
|
||||
|
||||
// ------ AST Card Buffs -------
|
||||
// The Balance
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(829));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1338));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1882));
|
||||
|
||||
// The Bole
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(830));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1339));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1883));
|
||||
|
||||
// The Arrow
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(831));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1884));
|
||||
|
||||
// The Spear
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(832));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1885));
|
||||
|
||||
// The Ewer
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(833));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1340));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1886));
|
||||
|
||||
// The Spire
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(834));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1341));
|
||||
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1887));
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public CustomEffectsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public enum StatusEffectDurationSortType
|
||||
{
|
||||
Ascending,
|
||||
Descending
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,589 @@
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Utility;
|
||||
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.Linq;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using LuminaStatus = Lumina.Excel.Sheets.Status;
|
||||
using StatusStruct = FFXIVClientStructs.FFXIV.Client.Game.Status;
|
||||
|
||||
namespace HSUI.Interface.StatusEffects
|
||||
{
|
||||
public class StatusEffectsListHud : ParentAnchoredDraggableHudElement, IHudElementWithActor, IHudElementWithAnchorableParent, IHudElementWithPreview, IHudElementWithMouseOver, IHudElementWithVisibilityConfig
|
||||
{
|
||||
protected StatusEffectsListConfig Config => (StatusEffectsListConfig)_config;
|
||||
public VisibilityConfig? VisibilityConfig => Config is UnitFrameStatusEffectsListConfig config ? config.VisibilityConfig : null;
|
||||
|
||||
private LayoutInfo _layoutInfo;
|
||||
|
||||
internal static int StatusEffectListsSize = 60;
|
||||
private StatusStruct[]? _fakeEffects = null;
|
||||
|
||||
private LabelHud _durationLabel;
|
||||
private LabelHud _stacksLabel;
|
||||
public IGameObject? Actor { get; set; } = null;
|
||||
|
||||
private bool _wasHovering = false;
|
||||
private bool NeedsSpecialInput => !ClipRectsHelper.Instance.Enabled || ClipRectsHelper.Instance.Mode == WindowClippingMode.Performance;
|
||||
|
||||
protected override bool AnchorToParent => Config is UnitFrameStatusEffectsListConfig config ? config.AnchorToUnitFrame : false;
|
||||
protected override DrawAnchor ParentAnchor => Config is UnitFrameStatusEffectsListConfig config ? config.UnitFrameAnchor : DrawAnchor.Center;
|
||||
|
||||
public StatusEffectsListHud(StatusEffectsListConfig config, string? displayName = null) : base(config, displayName)
|
||||
{
|
||||
_config.ValueChangeEvent += OnConfigPropertyChanged;
|
||||
|
||||
_durationLabel = new LabelHud(config.IconConfig.DurationLabelConfig);
|
||||
_stacksLabel = new LabelHud(config.IconConfig.StacksLabelConfig);
|
||||
|
||||
UpdatePreview();
|
||||
}
|
||||
|
||||
~StatusEffectsListHud()
|
||||
{
|
||||
_config.ValueChangeEvent -= OnConfigPropertyChanged;
|
||||
}
|
||||
|
||||
public void StopPreview()
|
||||
{
|
||||
Config.Preview = false;
|
||||
UpdatePreview();
|
||||
}
|
||||
|
||||
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||
{
|
||||
Vector2 pos = LayoutHelper.CalculateStartPosition(Config.Position, Config.Size, LayoutHelper.GrowthDirectionsFromIndex(Config.Directions));
|
||||
return (new List<Vector2>() { pos + Config.Size / 2f }, new List<Vector2>() { Config.Size });
|
||||
}
|
||||
|
||||
public void StopMouseover()
|
||||
{
|
||||
if (_wasHovering && NeedsSpecialInput)
|
||||
{
|
||||
InputsHelper.Instance.StopHandlingInputs();
|
||||
_wasHovering = false;
|
||||
}
|
||||
}
|
||||
|
||||
private uint CalculateLayout(List<StatusEffectData> list)
|
||||
{
|
||||
var effectCount = (uint)list.Count;
|
||||
var count = Config.Limit >= 0 ? Math.Min((uint)Config.Limit, effectCount) : effectCount;
|
||||
|
||||
if (count <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
_layoutInfo = LayoutHelper.CalculateLayout(
|
||||
Config.Size,
|
||||
Config.IconConfig.Size,
|
||||
count,
|
||||
Config.IconPadding,
|
||||
LayoutHelper.GetFillsRowsFirst(Config.FillRowsFirst, LayoutHelper.GrowthDirectionsFromIndex(Config.Directions))
|
||||
);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
protected string GetStatusActorName(StatusStruct status)
|
||||
{
|
||||
var character = Plugin.ObjectTable.SearchById(status.SourceObject.Id);
|
||||
return character == null ? "" : character.Name.ToString();
|
||||
}
|
||||
|
||||
protected virtual List<StatusEffectData> StatusEffectsData()
|
||||
{
|
||||
var list = StatusEffectDataList(Actor);
|
||||
|
||||
// sort by duration
|
||||
if (Config.SortByDuration)
|
||||
{
|
||||
list.Sort((a, b) =>
|
||||
{
|
||||
float aTime = a.Data.IsPermanent || a.Data.IsFcBuff ? float.MaxValue : a.Status.RemainingTime;
|
||||
float bTime = b.Data.IsPermanent || b.Data.IsFcBuff ? float.MaxValue : b.Status.RemainingTime;
|
||||
|
||||
if (Config.DurationSortType == StatusEffectDurationSortType.Ascending)
|
||||
{
|
||||
return aTime.CompareTo(bTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
return bTime.CompareTo(aTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
// show mine or permanent first
|
||||
else if (Config.ShowMineFirst || Config.ShowPermanentFirst)
|
||||
{
|
||||
return OrderByMineOrPermanentFirst(list);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
protected unsafe List<StatusEffectData> StatusEffectDataList(IGameObject? actor)
|
||||
{
|
||||
List<StatusEffectData> list = new List<StatusEffectData>();
|
||||
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
|
||||
IBattleChara? character = null;
|
||||
int count = StatusEffectListsSize;
|
||||
|
||||
if (_fakeEffects == null)
|
||||
{
|
||||
if (actor == null || actor is not IBattleChara battleChara)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
if (Config.HideWhenDead && (battleChara.IsDead || battleChara.CurrentHp <= 0))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
character = (IBattleChara)actor;
|
||||
|
||||
try
|
||||
{
|
||||
count = Math.Min(count, character.StatusList.Length);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else
|
||||
{
|
||||
count = Config.Limit == -1 ? _fakeEffects.Length : Math.Min(Config.Limit, _fakeEffects.Length);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
// status
|
||||
StatusStruct* status = null;
|
||||
|
||||
if (_fakeEffects != null)
|
||||
{
|
||||
var fakeStruct = _fakeEffects![i];
|
||||
status = &fakeStruct;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
status = character?.StatusList[i] == null ? null : (StatusStruct*)character.StatusList[i]!.Address;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (status == null || status->StatusId == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// data
|
||||
LuminaStatus? data = null;
|
||||
|
||||
if (_fakeEffects != null)
|
||||
{
|
||||
data = Plugin.DataManager.GetExcelSheet<LuminaStatus>()?.GetRow(status->StatusId);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
data = character?.StatusList[i]?.GameData.Value;
|
||||
} catch { }
|
||||
}
|
||||
|
||||
if (data == null || !data.HasValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// filter "invisible" status effects
|
||||
if (data.Value.Icon == 0 || data.Value.Name.ToString().Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// dont filter anything on preview mode
|
||||
if (_fakeEffects != null)
|
||||
{
|
||||
list.Add(new StatusEffectData(*status, data.Value));
|
||||
continue;
|
||||
}
|
||||
|
||||
// buffs
|
||||
if (!Config.ShowBuffs && data.Value.StatusCategory == 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// debuffs
|
||||
if (!Config.ShowDebuffs && data.Value.StatusCategory != 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// permanent
|
||||
if (!Config.ShowPermanentEffects && data.Value.IsPermanent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// only mine
|
||||
var mine = player?.GameObjectId == status->SourceObject.Id;
|
||||
|
||||
if (Config.IncludePetAsOwn)
|
||||
{
|
||||
mine = player?.GameObjectId == status->SourceObject.Id || IsStatusFromPlayerPet(*status);
|
||||
}
|
||||
|
||||
if (Config.ShowOnlyMine && !mine)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// blacklist
|
||||
if (Config.BlacklistConfig.Enabled && !Config.BlacklistConfig.StatusAllowed(data.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(new StatusEffectData(*status, data.Value));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
protected bool IsStatusFromPlayerPet(StatusStruct status)
|
||||
{
|
||||
var buddy = Plugin.BuddyList.PetBuddy;
|
||||
|
||||
if (buddy == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return buddy.EntityId == status.SourceObject.Id;
|
||||
}
|
||||
|
||||
protected List<StatusEffectData> OrderByMineOrPermanentFirst(List<StatusEffectData> list)
|
||||
{
|
||||
var player = Plugin.ObjectTable.LocalPlayer;
|
||||
if (player == null)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
if (Config.ShowMineFirst && Config.ShowPermanentFirst)
|
||||
{
|
||||
return list.OrderByDescending(x => x.Status.SourceObject.Id == player.GameObjectId && x.Data.IsPermanent || x.Data.IsFcBuff)
|
||||
.ThenByDescending(x => x.Status.SourceObject.Id == player.GameObjectId)
|
||||
.ThenByDescending(x => x.Data.IsPermanent)
|
||||
.ThenByDescending(x => x.Data.IsFcBuff)
|
||||
.ToList();
|
||||
}
|
||||
else if (Config.ShowMineFirst && !Config.ShowPermanentFirst)
|
||||
{
|
||||
return list.OrderByDescending(x => x.Status.SourceObject.Id == player.GameObjectId)
|
||||
.ToList();
|
||||
}
|
||||
else if (!Config.ShowMineFirst && Config.ShowPermanentFirst)
|
||||
{
|
||||
return list.OrderByDescending(x => x.Data.IsPermanent)
|
||||
.ThenByDescending(x => x.Data.IsFcBuff)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public override void DrawChildren(Vector2 origin)
|
||||
{
|
||||
if (!Config.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_fakeEffects == null && (Actor == null || Actor.ObjectKind != ObjectKind.Player && Actor.ObjectKind != ObjectKind.BattleNpc))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate layout
|
||||
List<StatusEffectData> list = StatusEffectsData();
|
||||
|
||||
// area
|
||||
GrowthDirections growthDirections = LayoutHelper.GrowthDirectionsFromIndex(Config.Directions);
|
||||
Vector2 position = origin + GetAnchoredPosition(Config.Position, Config.Size, DrawAnchor.TopLeft);
|
||||
Vector2 areaPos = LayoutHelper.CalculateStartPosition(position, Config.Size, growthDirections);
|
||||
Vector2 margin = new Vector2(14, 10);
|
||||
|
||||
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
|
||||
|
||||
// no need to do anything else if there are no effects
|
||||
if (list.Count == 0)
|
||||
{
|
||||
if (_wasHovering && NeedsSpecialInput)
|
||||
{
|
||||
_wasHovering = false;
|
||||
InputsHelper.Instance.StopHandlingInputs();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate icon positions
|
||||
uint count = CalculateLayout(list);
|
||||
var (iconPositions, minPos, maxPos) = LayoutHelper.CalculateIconPositions(
|
||||
growthDirections,
|
||||
count,
|
||||
position,
|
||||
Config.Size,
|
||||
Config.IconConfig.Size,
|
||||
Config.IconPadding,
|
||||
LayoutHelper.GetFillsRowsFirst(Config.FillRowsFirst, growthDirections),
|
||||
_layoutInfo
|
||||
);
|
||||
|
||||
// window
|
||||
// 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
|
||||
Vector2 windowPos = minPos - margin;
|
||||
Vector2 windowSize = maxPos - minPos;
|
||||
|
||||
AddDrawAction(Config.StrataLevel, () =>
|
||||
{
|
||||
DrawHelper.DrawInWindow(ID, windowPos, windowSize + margin * 2, !Config.DisableInteraction, (drawList) =>
|
||||
{
|
||||
// area
|
||||
if (Config.Preview)
|
||||
{
|
||||
drawList.AddRectFilled(areaPos, areaPos + Config.Size, 0x88000000);
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
Vector2 iconPos = iconPositions[i];
|
||||
var statusEffectData = list[i];
|
||||
|
||||
// shadow
|
||||
if (Config.IconConfig.ShadowConfig! != null && Config.IconConfig.ShadowConfig.Enabled)
|
||||
{
|
||||
// Right Side
|
||||
drawList.AddRectFilled(iconPos + new Vector2(Config.IconConfig.Size.X, Config.IconConfig.ShadowConfig.Offset), iconPos + Config.IconConfig.Size + new Vector2(Config.IconConfig.ShadowConfig.Offset, Config.IconConfig.ShadowConfig.Offset) + new Vector2(Config.IconConfig.ShadowConfig.Thickness - 1, Config.IconConfig.ShadowConfig.Thickness - 1), Config.IconConfig.ShadowConfig.Color.Base);
|
||||
|
||||
// Bottom Size
|
||||
drawList.AddRectFilled(iconPos + new Vector2(Config.IconConfig.ShadowConfig.Offset, Config.IconConfig.Size.Y), iconPos + Config.IconConfig.Size + new Vector2(Config.IconConfig.ShadowConfig.Offset, Config.IconConfig.ShadowConfig.Offset) + new Vector2(Config.IconConfig.ShadowConfig.Thickness - 1, Config.IconConfig.ShadowConfig.Thickness - 1), Config.IconConfig.ShadowConfig.Color.Base);
|
||||
}
|
||||
|
||||
// icon
|
||||
var cropIcon = Config.IconConfig.CropIcon;
|
||||
int stackCount = cropIcon ? 1 : statusEffectData.Data.MaxStacks > 0 ? statusEffectData.Status.Param : 0;
|
||||
DrawHelper.DrawIcon<LuminaStatus>(drawList, statusEffectData.Data, iconPos, Config.IconConfig.Size, false, cropIcon, stackCount);
|
||||
|
||||
// border
|
||||
var borderConfig = GetBorderConfig(statusEffectData);
|
||||
if (borderConfig != null && cropIcon)
|
||||
{
|
||||
drawList.AddRect(iconPos, iconPos + Config.IconConfig.Size, borderConfig.Color.Base, 0, ImDrawFlags.None, borderConfig.Thickness);
|
||||
}
|
||||
|
||||
// Draw dispell indicator above dispellable status effect on uncropped icons
|
||||
if (borderConfig != null && !cropIcon && statusEffectData.Data.CanDispel)
|
||||
{
|
||||
var dispellIndicatorColor = new Vector4(141f / 255f, 206f / 255f, 229f / 255f, 100f / 100f);
|
||||
// 24x32
|
||||
drawList.AddRectFilled(
|
||||
iconPos + new Vector2(Config.IconConfig.Size.X * .07f, Config.IconConfig.Size.Y * .07f),
|
||||
iconPos + new Vector2(Config.IconConfig.Size.X * .93f, Config.IconConfig.Size.Y * .14f),
|
||||
ImGui.ColorConvertFloat4ToU32(dispellIndicatorColor),
|
||||
8f
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
StatusEffectData? hoveringData = null;
|
||||
IGameObject? character = Actor;
|
||||
|
||||
// labels need to be drawn separated since they have their own window for clipping
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
Vector2 iconPos = iconPositions[i];
|
||||
StatusEffectData statusEffectData = list[i];
|
||||
|
||||
// duration
|
||||
if (Config.IconConfig.DurationLabelConfig.Enabled &&
|
||||
!statusEffectData.Data.IsPermanent &&
|
||||
!statusEffectData.Data.IsFcBuff)
|
||||
{
|
||||
AddDrawAction(Config.IconConfig.DurationLabelConfig.StrataLevel, () =>
|
||||
{
|
||||
double duration = Math.Round(Math.Abs(statusEffectData.Status.RemainingTime));
|
||||
Config.IconConfig.DurationLabelConfig.SetText(Utils.DurationToString(duration));
|
||||
_durationLabel.Draw(iconPos, Config.IconConfig.Size, character);
|
||||
});
|
||||
}
|
||||
|
||||
// stacks
|
||||
if (Config.IconConfig.StacksLabelConfig.Enabled &&
|
||||
statusEffectData.Data.MaxStacks > 0 &&
|
||||
statusEffectData.Status.Param > 0 &&
|
||||
!statusEffectData.Data.IsFcBuff)
|
||||
{
|
||||
AddDrawAction(Config.IconConfig.StacksLabelConfig.StrataLevel, () =>
|
||||
{
|
||||
Config.IconConfig.StacksLabelConfig.SetText($"{statusEffectData.Status.Param}");
|
||||
_stacksLabel.Draw(iconPos, Config.IconConfig.Size, character);
|
||||
});
|
||||
}
|
||||
|
||||
// tooltips / interaction
|
||||
if (ImGui.IsMouseHoveringRect(iconPos, iconPos + Config.IconConfig.Size))
|
||||
{
|
||||
hoveringData = statusEffectData;
|
||||
}
|
||||
}
|
||||
|
||||
if (hoveringData.HasValue)
|
||||
{
|
||||
StatusEffectData data = hoveringData.Value;
|
||||
|
||||
if (NeedsSpecialInput)
|
||||
{
|
||||
_wasHovering = true;
|
||||
InputsHelper.Instance.StartHandlingInputs();
|
||||
}
|
||||
|
||||
// tooltip
|
||||
if (Config.ShowTooltips)
|
||||
{
|
||||
TooltipsHelper.Instance.ShowTooltipOnCursor(
|
||||
EncryptedStringsHelper.GetString(data.Data.Description.ToDalamudString().ToString()),
|
||||
EncryptedStringsHelper.GetString(data.Data.Name.ToString()),
|
||||
data.Status.StatusId,
|
||||
GetStatusActorName(data.Status)
|
||||
);
|
||||
}
|
||||
|
||||
bool leftClick = InputsHelper.Instance.HandlingMouseInputs ? InputsHelper.Instance.LeftButtonClicked : ImGui.GetIO().MouseClicked[0];
|
||||
bool rightClick = InputsHelper.Instance.HandlingMouseInputs ? InputsHelper.Instance.RightButtonClicked : ImGui.GetIO().MouseClicked[1];
|
||||
|
||||
// remove buff on right click
|
||||
bool isFromPlayer = data.Status.SourceObject.Id == Plugin.ObjectTable.LocalPlayer?.GameObjectId;
|
||||
bool isTheEcho = data.Status.SourceObject.Id is 42 or 239;
|
||||
|
||||
if (data.Data.StatusCategory == 1 && (isFromPlayer || isTheEcho) && rightClick)
|
||||
{
|
||||
StatusManager.ExecuteStatusOff(data.Status.StatusId, data.Status.SourceObject.ObjectId);
|
||||
|
||||
if (NeedsSpecialInput)
|
||||
{
|
||||
_wasHovering = false;
|
||||
InputsHelper.Instance.StopHandlingInputs();
|
||||
}
|
||||
}
|
||||
|
||||
// automatic add to black list with ctrl+alt+shift click
|
||||
if (Config.BlacklistConfig.Enabled &&
|
||||
ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyAlt && ImGui.GetIO().KeyShift && leftClick)
|
||||
{
|
||||
Config.BlacklistConfig.AddNewEntry(data.Data);
|
||||
ConfigurationManager.Instance.ForceNeedsSave();
|
||||
|
||||
if (NeedsSpecialInput)
|
||||
{
|
||||
_wasHovering = false;
|
||||
InputsHelper.Instance.StopHandlingInputs();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_wasHovering && NeedsSpecialInput)
|
||||
{
|
||||
_wasHovering = false;
|
||||
InputsHelper.Instance.StopHandlingInputs();
|
||||
}
|
||||
}
|
||||
|
||||
public StatusEffectIconBorderConfig? GetBorderConfig(StatusEffectData statusEffectData)
|
||||
{
|
||||
StatusEffectIconBorderConfig? borderConfig = null;
|
||||
|
||||
bool isFromPlayerPet = false;
|
||||
if (Config.IncludePetAsOwn)
|
||||
{
|
||||
isFromPlayerPet = IsStatusFromPlayerPet(statusEffectData.Status);
|
||||
}
|
||||
|
||||
if (Config.IconConfig.OwnedBorderConfig.Enabled && (statusEffectData.Status.SourceObject.Id == Plugin.ObjectTable.LocalPlayer?.GameObjectId || isFromPlayerPet))
|
||||
{
|
||||
borderConfig = Config.IconConfig.OwnedBorderConfig;
|
||||
}
|
||||
else if (Config.IconConfig.DispellableBorderConfig.Enabled && statusEffectData.Data.CanDispel)
|
||||
{
|
||||
borderConfig = Config.IconConfig.DispellableBorderConfig;
|
||||
}
|
||||
else if (Config.IconConfig.BorderConfig.Enabled)
|
||||
{
|
||||
borderConfig = Config.IconConfig.BorderConfig;
|
||||
}
|
||||
|
||||
return borderConfig;
|
||||
}
|
||||
|
||||
private void OnConfigPropertyChanged(object? sender, OnChangeBaseArgs args)
|
||||
{
|
||||
if (args.PropertyName == "Preview")
|
||||
{
|
||||
UpdatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void UpdatePreview()
|
||||
{
|
||||
if (!Config.Preview)
|
||||
{
|
||||
_fakeEffects = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var RNG = new Random((int)ImGui.GetTime());
|
||||
_fakeEffects = new StatusStruct[StatusEffectListsSize];
|
||||
|
||||
for (int i = 0; i < StatusEffectListsSize; i++)
|
||||
{
|
||||
var fakeStruct = new StatusStruct();
|
||||
|
||||
// forcing "triplecast" buff first to always be able to test stacks
|
||||
fakeStruct.StatusId = i == 0 ? (ushort)1211 : (ushort)RNG.Next(1, 200);
|
||||
fakeStruct.RemainingTime = RNG.Next(1, 30);
|
||||
fakeStruct.Param = (byte)RNG.Next(1, 3);
|
||||
fakeStruct.SourceObject.Id = 0;
|
||||
|
||||
_fakeEffects[i] = fakeStruct;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct StatusEffectData
|
||||
{
|
||||
public StatusStruct Status;
|
||||
public LuminaStatus Data;
|
||||
|
||||
public StatusEffectData(StatusStruct status, LuminaStatus data)
|
||||
{
|
||||
Status = status;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user