7a61e11e37
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
280 lines
13 KiB
C#
280 lines
13 KiB
C#
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface;
|
|
using HSUI.Config;
|
|
using HSUI.Helpers;
|
|
using HSUI.Interface;
|
|
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
|
|
namespace HSUI.Interface.GeneralElements
|
|
{
|
|
public class DutyListScenarioHud : DraggableHudElement, IHudElementWithVisibilityConfig
|
|
{
|
|
private DutyListScenarioConfig Config => (DutyListScenarioConfig)_config;
|
|
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
|
|
|
|
public DutyListScenarioHud(DutyListScenarioConfig config, string displayName)
|
|
: base(config, displayName) { }
|
|
|
|
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
|
{
|
|
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)
|
|
{
|
|
if (!Config.Enabled)
|
|
return;
|
|
|
|
var dutyListEntries = DutyListScenarioHelper.GetDutyListEntries();
|
|
var scenarioEntries = DutyListScenarioHelper.GetScenarioGuideEntries();
|
|
var dutyFinderInfo = DutyListScenarioHelper.GetDutyFinderQueueInfo();
|
|
var dutyInfo = DutyListScenarioHelper.GetDutyInfo();
|
|
|
|
bool hasDutyList = dutyListEntries.Count > 0;
|
|
bool hasScenario = scenarioEntries.Count > 0;
|
|
// Show queue info when in queue; show duty info when in duty (not in queue). Same section, different content.
|
|
bool hasDutyFinder = Config.ShowDutyFinderSection && dutyFinderInfo != null;
|
|
bool hasDutyInfo = Config.ShowDutyFinderSection && dutyInfo != null;
|
|
|
|
if (!hasDutyList && !hasScenario && !hasDutyFinder && !hasDutyInfo)
|
|
return;
|
|
|
|
Vector2 basePos = Utils.GetAnchoredPosition(origin + Config.Position, Config.Size, Config.Anchor);
|
|
Vector2 iconSize = new Vector2(Config.IconSize, Config.IconSize);
|
|
float lineHeight = Config.IconSize + 4;
|
|
float padding = 6;
|
|
float contentWidth = Config.Size.X - padding * 2;
|
|
|
|
AddDrawAction(Config.StrataLevel, () =>
|
|
{
|
|
DrawHelper.DrawInWindow(ID, basePos, Config.Size, true, (drawList) =>
|
|
{
|
|
drawList.AddRectFilled(basePos, basePos + Config.Size, Config.BackgroundColor.Base, 4);
|
|
drawList.PushClipRect(basePos, basePos + Config.Size, true);
|
|
|
|
float y = basePos.Y + padding;
|
|
uint textColor = Config.TextColor.Base;
|
|
|
|
if (Config.FontID != "Default" && FontsManager.Instance != null)
|
|
{
|
|
using (FontsManager.Instance.PushFont(Config.FontID))
|
|
{
|
|
ImGui.SetWindowFontScale(Config.FontScale);
|
|
DrawContent(drawList, ref y, basePos.X + padding, contentWidth, lineHeight, iconSize, textColor, hasDutyList, hasScenario, hasDutyFinder, hasDutyInfo, dutyListEntries, scenarioEntries, dutyFinderInfo, dutyInfo);
|
|
ImGui.SetWindowFontScale(1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGui.PushFont(UiBuilder.DefaultFont);
|
|
ImGui.SetWindowFontScale(Config.FontScale);
|
|
DrawContent(drawList, ref y, basePos.X + padding, contentWidth, lineHeight, iconSize, textColor, hasDutyList, hasScenario, hasDutyFinder, hasDutyInfo, dutyListEntries, scenarioEntries, dutyFinderInfo, dutyInfo);
|
|
ImGui.SetWindowFontScale(1);
|
|
ImGui.PopFont();
|
|
}
|
|
drawList.PopClipRect();
|
|
});
|
|
});
|
|
}
|
|
|
|
private static string TruncateText(string text, float maxWidth)
|
|
{
|
|
if (ImGui.CalcTextSize(text).X <= maxWidth)
|
|
return text;
|
|
string suffix = "...";
|
|
while (text.Length > 1 && ImGui.CalcTextSize(text + suffix).X > maxWidth)
|
|
text = text[..^1];
|
|
return text + suffix;
|
|
}
|
|
|
|
private void DrawContent(
|
|
ImDrawListPtr drawList,
|
|
ref float y,
|
|
float x,
|
|
float contentWidth,
|
|
float lineHeight,
|
|
Vector2 iconSize,
|
|
uint textColor,
|
|
bool hasDutyList,
|
|
bool hasScenario,
|
|
bool hasDutyFinder,
|
|
bool hasDutyInfo,
|
|
List<DutyListScenarioHelper.DutyListEntry> dutyListEntries,
|
|
List<DutyListScenarioHelper.ScenarioGuideEntry> scenarioEntries,
|
|
DutyListScenarioHelper.DutyFinderQueueInfo? dutyFinderInfo,
|
|
DutyListScenarioHelper.DutyInfo? dutyInfo)
|
|
{
|
|
const uint TankIconId = 62581;
|
|
const uint HealerIconId = 62582;
|
|
const uint DPSIconId = 62583;
|
|
|
|
if (hasDutyInfo && dutyInfo != null)
|
|
{
|
|
uint titleColor = Config.DutyFinderTitleColor.Base;
|
|
uint detailColor = Config.DutyFinderDetailColor.Base;
|
|
|
|
drawList.AddText(new Vector2(x, y), titleColor, "Duty Information");
|
|
y += lineHeight;
|
|
|
|
string dutyText = TruncateText(dutyInfo.DutyName, contentWidth);
|
|
drawList.AddText(new Vector2(x, y), detailColor, dutyText);
|
|
y += lineHeight;
|
|
|
|
if (!string.IsNullOrEmpty(dutyInfo.TimerText))
|
|
{
|
|
drawList.AddText(new Vector2(x, y), detailColor, TruncateText(dutyInfo.TimerText, contentWidth));
|
|
y += lineHeight;
|
|
}
|
|
|
|
foreach (var obj in dutyInfo.Objectives)
|
|
{
|
|
string line = string.IsNullOrWhiteSpace(obj.Text) ? "???" : obj.Text;
|
|
if (!string.IsNullOrWhiteSpace(obj.Progress))
|
|
line += ": " + obj.Progress;
|
|
drawList.AddText(new Vector2(x, y), detailColor, TruncateText(line, contentWidth));
|
|
y += lineHeight;
|
|
}
|
|
|
|
y += 4;
|
|
if (hasScenario || hasDutyList)
|
|
{
|
|
float divY = y + 2;
|
|
drawList.AddRectFilled(new Vector2(x, divY), new Vector2(x + contentWidth, divY + 2f), Config.DividerColor.Base);
|
|
y += 8;
|
|
}
|
|
}
|
|
else if (hasDutyFinder && dutyFinderInfo != null)
|
|
{
|
|
uint titleColor = Config.DutyFinderTitleColor.Base;
|
|
uint detailColor = Config.DutyFinderDetailColor.Base;
|
|
|
|
drawList.AddText(new Vector2(x, y), titleColor, "Duty Finder");
|
|
y += lineHeight;
|
|
|
|
string dutyText = TruncateText(dutyFinderInfo.DutyName, contentWidth);
|
|
drawList.AddText(new Vector2(x, y), detailColor, dutyText);
|
|
y += lineHeight;
|
|
|
|
string statusText = TruncateText(dutyFinderInfo.StatusText, contentWidth);
|
|
drawList.AddText(new Vector2(x, y), detailColor, statusText);
|
|
y += lineHeight;
|
|
|
|
float roleX = x;
|
|
roleX += 4;
|
|
DrawHelper.DrawIcon(TankIconId, new Vector2(roleX, y), new Vector2(iconSize.X * 0.8f, iconSize.Y * 0.8f), false, detailColor, drawList);
|
|
roleX += iconSize.X * 0.8f + 2;
|
|
drawList.AddText(new Vector2(roleX, y), detailColor, $":{dutyFinderInfo.TanksFound}/{dutyFinderInfo.TanksNeeded}");
|
|
roleX += ImGui.CalcTextSize($":{dutyFinderInfo.TanksFound}/{dutyFinderInfo.TanksNeeded}").X + 6;
|
|
DrawHelper.DrawIcon(HealerIconId, new Vector2(roleX, y), new Vector2(iconSize.X * 0.8f, iconSize.Y * 0.8f), false, detailColor, drawList);
|
|
roleX += iconSize.X * 0.8f + 2;
|
|
drawList.AddText(new Vector2(roleX, y), detailColor, $":{dutyFinderInfo.HealersFound}/{dutyFinderInfo.HealersNeeded}");
|
|
roleX += ImGui.CalcTextSize($":{dutyFinderInfo.HealersFound}/{dutyFinderInfo.HealersNeeded}").X + 6;
|
|
DrawHelper.DrawIcon(DPSIconId, new Vector2(roleX, y), new Vector2(iconSize.X * 0.8f, iconSize.Y * 0.8f), false, detailColor, drawList);
|
|
roleX += iconSize.X * 0.8f + 2;
|
|
drawList.AddText(new Vector2(roleX, y), detailColor, $":{dutyFinderInfo.DPSFound}/{dutyFinderInfo.DPSNeeded}");
|
|
y += lineHeight;
|
|
|
|
string elapsedStr = $"{(int)dutyFinderInfo.TimeElapsed.TotalMinutes}:{dutyFinderInfo.TimeElapsed.Seconds:D2}";
|
|
string timeLine = "Time Elapsed: " + elapsedStr;
|
|
if (!string.IsNullOrEmpty(dutyFinderInfo.AverageWaitText))
|
|
timeLine += " / Average Wait Time: " + dutyFinderInfo.AverageWaitText;
|
|
timeLine = TruncateText(timeLine, contentWidth);
|
|
drawList.AddText(new Vector2(x, y), detailColor, timeLine);
|
|
y += lineHeight + 4;
|
|
|
|
if (hasScenario || hasDutyList)
|
|
{
|
|
float divY = y + 2;
|
|
drawList.AddRectFilled(new Vector2(x, divY), new Vector2(x + contentWidth, divY + 2f), Config.DividerColor.Base);
|
|
y += 8;
|
|
}
|
|
}
|
|
|
|
if (hasScenario)
|
|
{
|
|
foreach (var entry in scenarioEntries)
|
|
{
|
|
float lineX = x;
|
|
if (Config.ShowQuestIcons && entry.IconId != 0)
|
|
{
|
|
DrawHelper.DrawIcon(entry.IconId, new Vector2(lineX, y), iconSize, false, textColor, drawList);
|
|
lineX += iconSize.X + 4;
|
|
}
|
|
|
|
string text = TruncateText($"{entry.Label}: Lv. {entry.Level} {entry.QuestName}", contentWidth - (lineX - x));
|
|
var textSize = ImGui.CalcTextSize(text);
|
|
drawList.AddText(new Vector2(lineX, y), textColor, text);
|
|
|
|
// Use full line height for hit area so the second line (Job) doesn't get covered by the first (MSQ)
|
|
if (ImGui.IsMouseHoveringRect(new Vector2(lineX, y), new Vector2(x + contentWidth, y + lineHeight))
|
|
&& ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
|
{
|
|
DutyListScenarioHelper.OpenMapForQuestIssuer(entry.QuestRowId);
|
|
}
|
|
|
|
y += lineHeight;
|
|
}
|
|
if (hasDutyList)
|
|
{
|
|
float divY = y + 2;
|
|
drawList.AddRectFilled(new Vector2(x, divY), new Vector2(x + contentWidth, divY + 2f), Config.DividerColor.Base);
|
|
y += 8;
|
|
}
|
|
}
|
|
|
|
if (hasDutyList)
|
|
{
|
|
uint objectiveColor = ImGui.ColorConvertFloat4ToU32(new Vector4(0.78f, 0.78f, 0.78f, 1f));
|
|
|
|
foreach (var entry in dutyListEntries)
|
|
{
|
|
float lineX = x;
|
|
if (Config.ShowQuestIcons && entry.IconId != 0)
|
|
{
|
|
DrawHelper.DrawIcon(entry.IconId, new Vector2(lineX, y), iconSize, false, textColor, drawList);
|
|
lineX += iconSize.X + 4;
|
|
}
|
|
|
|
string nameText = TruncateText(entry.QuestName, contentWidth - (lineX - x));
|
|
var nameSize = ImGui.CalcTextSize(nameText);
|
|
drawList.AddText(new Vector2(lineX, y), textColor, nameText);
|
|
|
|
if (entry.QuestId != 0
|
|
&& ImGui.IsMouseHoveringRect(new Vector2(lineX, y), new Vector2(lineX + nameSize.X, y + nameSize.Y))
|
|
&& ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
|
{
|
|
DutyListScenarioHelper.OpenJournalForQuest(entry.QuestId);
|
|
}
|
|
|
|
y += lineHeight;
|
|
|
|
if (!string.IsNullOrWhiteSpace(entry.ObjectiveText))
|
|
{
|
|
float objX = lineX + 8;
|
|
string objText = TruncateText("• " + entry.ObjectiveText, contentWidth - (objX - x));
|
|
var objSize = ImGui.CalcTextSize(objText);
|
|
drawList.AddText(new Vector2(objX, y), objectiveColor, objText);
|
|
|
|
if (entry.QuestId != 0
|
|
&& ImGui.IsMouseHoveringRect(new Vector2(objX, y), new Vector2(objX + objSize.X, y + objSize.Y))
|
|
&& ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
|
{
|
|
DutyListScenarioHelper.OpenMapForQuestObjective(entry.QuestId, entry.Sequence);
|
|
}
|
|
|
|
y += lineHeight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|