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, List) ChildrenPositionsAndSizes() { return (new List(), new List()); } 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); } }); } } }