v1.0.8.14: Mouse GCD Indicator (ring around cursor)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-11 00:36:56 -05:00
parent d2eb40b450
commit c5b8e48072
8 changed files with 228 additions and 5 deletions
@@ -0,0 +1,74 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[DisableParentSettings("Position")]
[Section("Other Elements")]
[SubSection("Mouse GCD Indicator", 0)]
public class MouseGCDIndicatorConfig : MovablePluginConfigObject
{
[Checkbox("Always Show")]
[Order(3)]
public bool AlwaysShow = 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));
[DragInt("Radius")]
[Order(35)]
public int CircleRadius = 40;
[DragInt("Thickness")]
[Order(40)]
public int CircleThickness = 10;
[DragInt("Start Angle", min = 0, max = 359)]
[Order(45)]
public int CircleStartAngle = 0;
[Checkbox("Rotate CCW")]
[Order(50)]
public bool RotateCCW = false;
[DragInt2("Cursor Center Offset", min = -64, max = 64)]
[Order(55)]
public Vector2 CursorCenterOffset = new Vector2(8, 16);
[NestedConfig("Visibility", 70)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
public new static MouseGCDIndicatorConfig DefaultConfig() { return new MouseGCDIndicatorConfig() { Enabled = false, Strata = StrataLevel.MID_HIGH }; }
}
}
@@ -0,0 +1,140 @@
using System;
using HSUI.Helpers;
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;
namespace HSUI.Interface.GeneralElements
{
public class MouseGCDIndicatorHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
{
private MouseGCDIndicatorConfig Config => (MouseGCDIndicatorConfig)_config;
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
public IGameObject? Actor { get; set; } = null;
private float _lastTotalCastTime = 0;
public MouseGCDIndicatorHud(MouseGCDIndicatorConfig config, string displayName) : base(config, displayName) { }
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
return (new List<Vector2>(), new List<Vector2>());
}
protected override void DrawDraggableArea(Vector2 origin)
{
return;
}
public override void DrawChildren(Vector2 origin)
{
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;
}
}
Vector2 center = ImGui.GetMousePos() + Config.CursorCenterOffset;
AddDrawAction(_config.StrataLevel, () =>
{
DrawCircularIndicator(center, Config.CircleRadius, elapsed, total);
});
}
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);
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
{
float progressAngle = Math.Min(current, total - (Config.ShowGCDQueueIndicator ? queueTime : 0f)) / total * endAngle;
drawList.PathArcTo(position, radius, startAngle + offset, progressAngle + offset, segments);
drawList.PathStroke(Config.FillColor.Base, ImDrawFlags.None, Config.CircleThickness);
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);
}
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);
}
});
}
}
}