Merge pull request 'fix: Duty List hide-unless-hovered + tooltip job gauge cost section formatting' (#3) from feature/improvement into main

This commit was merged in pull request #3.
This commit is contained in:
2026-03-03 00:39:30 +00:00
2 changed files with 43 additions and 11 deletions
+37 -12
View File
@@ -463,8 +463,9 @@ namespace HSUI.Helpers
if (string.IsNullOrEmpty(description)) if (string.IsNullOrEmpty(description))
return result; return result;
// Pattern: section labels to color (Additional Effect, Duration, X Effect, Maximum Charges, Blood Gauge Cost, Combo, roles) // Pattern: section labels to color (Additional Effect, Duration, X Effect, Maximum Charges, job gauge costs, 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); // 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 // Pattern: "Grants X X Effect:" - captures status name X
var grantsRegex = new Regex(@"Grants\s+(.+?)\s+\1\s+Effect:", RegexOptions.Singleline); 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 secondaryColor = config.SecondaryLabelColor.Vector;
var blocks = body.Split(new[] { "\n\n" }, StringSplitOptions.None); var blocks = body.Split(new[] { "\n\n" }, StringSplitOptions.None);
int descStart = 0;
bool hasStats = false; 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<string> descBlocks = new List<string>();
bool inDescription = false;
for (int i = 0; i < blocks.Length; i++) for (int i = 0; i < blocks.Length; i++)
{ {
string block = blocks[i].Trim(); string block = blocks[i].Trim();
var lines = block.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); var lines = block.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
bool blockIsStats = false; List<string> descLinesInBlock = new List<string>();
foreach (string line in lines) foreach (string line in lines)
{ {
string part = line.Trim(); string part = line.Trim();
if (inDescription)
{
descLinesInBlock.Add(part);
continue;
}
if (part.StartsWith("Potency:", StringComparison.OrdinalIgnoreCase)) if (part.StartsWith("Potency:", StringComparison.OrdinalIgnoreCase))
{ {
if (hasStats) result.Add(new TooltipSegment("\n", textColor)); if (hasStats) result.Add(new TooltipSegment("\n", textColor));
result.Add(new TooltipSegment("Potency:", secondaryColor)); result.Add(new TooltipSegment("Potency:", secondaryColor));
result.Add(new TooltipSegment(" " + part.Substring(8).TrimStart(), textColor)); result.Add(new TooltipSegment(" " + part.Substring(8).TrimStart(), textColor));
hasStats = true; hasStats = true;
blockIsStats = true;
} }
else if (part.Contains("Cast:") || part.Contains("Recast:")) else if (part.Contains("Cast:") || part.Contains("Recast:"))
{ {
@@ -605,19 +614,35 @@ namespace HSUI.Helpers
var statsSegs = FormatActionStats(part, textColor, secondaryColor); var statsSegs = FormatActionStats(part, textColor, secondaryColor);
result.AddRange(statsSegs); result.AddRange(statsSegs);
hasStats = true; hasStats = true;
blockIsStats = true;
} }
} else if (gaugeCostLineRegex.IsMatch(part))
if (!blockIsStats)
{ {
descStart = i; 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 (descLinesInBlock.Count > 0)
descBlocks.Add(string.Join("\n", descLinesInBlock));
if (inDescription)
{
descBlocks.AddRange(blocks.Skip(i + 1).Select(b => b.Trim()).Where(b => b.Length > 0));
break; break;
} }
descStart = i + 1;
} }
// Description: exactly one newline after stats, then description with compact section spacing // 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 (!string.IsNullOrEmpty(desc))
{ {
if (hasStats) result.Add(new TooltipSegment("\n", textColor)); if (hasStats) result.Add(new TooltipSegment("\n", textColor));
@@ -21,6 +21,13 @@ namespace HSUI.Interface.GeneralElements
return (new List<Vector2> { Config.Position }, new List<Vector2> { Config.Size }); return (new List<Vector2> { Config.Position }, new List<Vector2> { Config.Size });
} }
/// <summary>Screen-space bounds for visibility "hide unless hovered" must match the draw position exactly.</summary>
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) public override void DrawChildren(Vector2 origin)
{ {
if (!Config.Enabled) if (!Config.Enabled)