From 7a61e11e37c10ca16d8cd3a0d84c5584be131f52 Mon Sep 17 00:00:00 2001 From: Jorg Date: Mon, 2 Mar 2026 13:33:30 -0600 Subject: [PATCH] fix: Duty List hide-unless-hovered + tooltip job gauge cost section formatting Duty List & Scenario Guide: - Fix 'Hide unless hovered' visibility option not working. Override GetScreenBounds() in DutyListScenarioHud so the hover hit-test rect matches the drawn panel position exactly (same formula as draw code). Tooltips (game-style formatting): - Treat job gauge/resource costs as a new section with section label color (e.g. Soul Gauge Cost, Beast Gauge Cost, Lily Cost, Kenki, Ninki, Cartridge, Oath, Polyglot, Addersgall, Astral/Lunar Sign, Battery/Heat). - In BuildFormattedActionTooltipBody: recognize gauge cost lines in the stats block and emit them as their own section (newline + green label). - Fix tooltip break when stats and description share the first block: switch to description on first non-stats line instead of dropping it, so ability description text is never lost. Made-with: Cursor --- Helpers/TooltipsHelper.cs | 47 ++++++++++++++----- .../GeneralElements/DutyListScenarioHud.cs | 7 +++ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/Helpers/TooltipsHelper.cs b/Helpers/TooltipsHelper.cs index 9838dec..bcab63f 100644 --- a/Helpers/TooltipsHelper.cs +++ b/Helpers/TooltipsHelper.cs @@ -463,8 +463,9 @@ namespace HSUI.Helpers if (string.IsNullOrEmpty(description)) return result; - // Pattern: section labels to color (Additional Effect, Duration, X Effect, Maximum Charges, Blood Gauge Cost, Combo, roles) - var sectionRegex = new Regex(@"\b(Additional Effect:|Duration:|Maximum Charges:|Blood Gauge Cost:|Combo Action:|Combo Potency:|Combo Bonus:|Tank:|Melee:|Ranged:|Caster:|Healer:|[A-Za-z][A-Za-z0-9\s]+ Effect:)", RegexOptions.None); + // Pattern: section labels to color (Additional Effect, Duration, X Effect, Maximum Charges, job gauge costs, Combo, roles) + // Job gauge/resource costs: Soul (RPR), Blood/Beast (DRK/WAR), Kenki/Ninki (SAM/NIN), Cartridge (GNB), Oath (PLD), Lily (WHM), etc. + var sectionRegex = new Regex(@"\b(Additional Effect:|Duration:|Maximum Charges:|Blood Gauge Cost:|Soul Gauge Cost:|Beast Gauge Cost:|Kenki Gauge Cost:|Kenki Cost:|Ninki Gauge Cost:|Ninki Cost:|Cartridge Cost:|Oath Gauge Cost:|Lily Cost:|Polyglot Cost:|Addersgall Cost:|Addersting Cost:|Astral Sign Cost:|Lunar Sign Cost:|Battery Gauge Cost:|Heat Gauge Cost:|[A-Za-z][A-Za-z0-9\s]*\s+Cost:|Combo Action:|Combo Potency:|Combo Bonus:|Tank:|Melee:|Ranged:|Caster:|Healer:|[A-Za-z][A-Za-z0-9\s]+ Effect:)", RegexOptions.None); // Pattern: "Grants X X Effect:" - captures status name X var grantsRegex = new Regex(@"Grants\s+(.+?)\s+\1\s+Effect:", RegexOptions.Singleline); @@ -579,25 +580,33 @@ namespace HSUI.Helpers var secondaryColor = config.SecondaryLabelColor.Vector; var blocks = body.Split(new[] { "\n\n" }, StringSplitOptions.None); - int descStart = 0; bool hasStats = false; - // Stats block: may contain "Potency: X" and "Cast: ... | Recast: ..." on separate lines + // Job gauge/resource cost line pattern (e.g. "Soul Gauge Cost: 50", "Lily Cost: 1") - treat as own section with section color + var gaugeCostLineRegex = new Regex(@"^(.+?\s+Cost:)\s*(.*)$", RegexOptions.None); + // Stats block: may contain "Potency: X", "Cast: ... | Recast: ...", and job gauge cost lines. + // Once we hit a line that doesn't match any of these, treat it and the rest as description (don't drop it). + List descBlocks = new List(); + bool inDescription = false; for (int i = 0; i < blocks.Length; i++) { string block = blocks[i].Trim(); var lines = block.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); - bool blockIsStats = false; + List descLinesInBlock = new List(); foreach (string line in lines) { string part = line.Trim(); + if (inDescription) + { + descLinesInBlock.Add(part); + continue; + } if (part.StartsWith("Potency:", StringComparison.OrdinalIgnoreCase)) { if (hasStats) result.Add(new TooltipSegment("\n", textColor)); result.Add(new TooltipSegment("Potency:", secondaryColor)); result.Add(new TooltipSegment(" " + part.Substring(8).TrimStart(), textColor)); hasStats = true; - blockIsStats = true; } else if (part.Contains("Cast:") || part.Contains("Recast:")) { @@ -605,19 +614,35 @@ namespace HSUI.Helpers var statsSegs = FormatActionStats(part, textColor, secondaryColor); result.AddRange(statsSegs); hasStats = true; - blockIsStats = true; + } + else if (gaugeCostLineRegex.IsMatch(part)) + { + var match = gaugeCostLineRegex.Match(part); + string label = match.Groups[1].Value.TrimEnd(); + string value = match.Groups[2].Value.Trim(); + if (hasStats) result.Add(new TooltipSegment("\n", textColor)); + result.Add(new TooltipSegment(label + (string.IsNullOrEmpty(value) ? "" : " "), sectionColor)); + if (!string.IsNullOrEmpty(value)) + result.Add(new TooltipSegment(value, textColor)); + hasStats = true; + } + else + { + inDescription = true; + descLinesInBlock.Add(part); } } - if (!blockIsStats) + if (descLinesInBlock.Count > 0) + descBlocks.Add(string.Join("\n", descLinesInBlock)); + if (inDescription) { - descStart = i; + descBlocks.AddRange(blocks.Skip(i + 1).Select(b => b.Trim()).Where(b => b.Length > 0)); break; } - descStart = i + 1; } // Description: exactly one newline after stats, then description with compact section spacing - string desc = string.Join("\n", blocks.Skip(descStart)).Trim(); + string desc = string.Join("\n\n", descBlocks).Trim(); if (!string.IsNullOrEmpty(desc)) { if (hasStats) result.Add(new TooltipSegment("\n", textColor)); diff --git a/Interface/GeneralElements/DutyListScenarioHud.cs b/Interface/GeneralElements/DutyListScenarioHud.cs index abd8831..e190523 100644 --- a/Interface/GeneralElements/DutyListScenarioHud.cs +++ b/Interface/GeneralElements/DutyListScenarioHud.cs @@ -21,6 +21,13 @@ namespace HSUI.Interface.GeneralElements return (new List { Config.Position }, new List { Config.Size }); } + /// Screen-space bounds for visibility "hide unless hovered" must match the draw position exactly. + public override (Vector2 min, Vector2 max) GetScreenBounds(Vector2 origin) + { + Vector2 topLeft = Utils.GetAnchoredPosition(origin + ParentPos() + Config.Position, Config.Size, Config.Anchor); + return (topLeft, topLeft + Config.Size); + } + public override void DrawChildren(Vector2 origin) { if (!Config.Enabled)