feat(player): port retail augmentation stat chain

This commit is contained in:
Erik 2026-07-31 08:08:23 +02:00
parent 0cb60d98a0
commit 461a1fb7b4
22 changed files with 953 additions and 138 deletions

View file

@ -32,6 +32,11 @@ public sealed class UiText : UiElement, IUiDatStateful
/// <summary>One display line: pre-formatted text + its colour.</summary>
public readonly record struct Line(string Text, Vector4 Color);
/// <summary>
/// One inline fragment in a retail <c>AppendTextWithFont</c> line.
/// </summary>
public readonly record struct TextRun(string Text, Vector4 Color);
/// <summary>A caret position: a line index into the cached line list plus a
/// character index (0..line.Text.Length, i.e. a caret slot between glyphs).</summary>
public readonly record struct Pos(int Line, int Col);
@ -39,6 +44,13 @@ public sealed class UiText : UiElement, IUiDatStateful
/// <summary>Provider of the lines to show, oldest-first. Polled each frame.</summary>
public Func<IReadOnlyList<Line>> LinesProvider { get; set; } = static () => Array.Empty<Line>();
/// <summary>
/// Optional inline fragments for a static one-line element. When present
/// this reproduces retail's per-append font-state colors while preserving
/// the element's authored alignment as one composed line.
/// </summary>
public Func<IReadOnlyList<TextRun>>? RunsProvider { get; set; }
/// <summary>Font for the transcript; falls back to the context default.</summary>
public BitmapFont? Font { get; set; }
@ -381,6 +393,12 @@ public sealed class UiText : UiElement, IUiDatStateful
private void DrawClippedText(UiRenderContext ctx)
{
if (OneLine && RunsProvider is { } runsProvider)
{
DrawSingleLineRuns(ctx, runsProvider());
return;
}
// Static centered single-line mode (vitals cur/max numbers etc.): draw the first
// line centered H+V (or H+Top/Bottom per VerticalJustify) with the SAME formula
// UIElement_Meter used for its label, then skip the scroll/selection machinery entirely.
@ -533,6 +551,54 @@ public sealed class UiText : UiElement, IUiDatStateful
}
}
private void DrawSingleLineRuns(
UiRenderContext ctx,
IReadOnlyList<TextRun> runs)
{
if (runs.Count == 0) return;
UiDatFont? datFont = DatFont;
BitmapFont? bitmapFont = datFont is null
? Font ?? ctx.DefaultFont
: null;
if (datFont is null && bitmapFont is null) return;
float totalWidth = 0f;
foreach (TextRun run in runs)
{
totalWidth += datFont is not null
? datFont.MeasureWidth(run.Text)
: bitmapFont!.MeasureWidth(run.Text);
}
float x = Centered
? Math.Max(Padding, (Width - totalWidth) * 0.5f)
: RightAligned
? Math.Max(Padding, Width - Padding - totalWidth)
: Padding;
float lineHeight = datFont?.LineHeight ?? bitmapFont!.LineHeight;
float y = VOffset(
Height,
lineHeight,
Padding,
VerticalJustify);
foreach (TextRun run in runs)
{
if (run.Text.Length == 0) continue;
if (datFont is not null)
{
ctx.DrawStringDat(datFont, run.Text, x, y, run.Color);
x += datFont.MeasureWidth(run.Text);
}
else
{
ctx.DrawString(run.Text, x, y, run.Color, bitmapFont);
x += bitmapFont!.MeasureWidth(run.Text);
}
}
}
/// <summary>
/// True when any vertical portion of a line intersects a text viewport. Retail
/// clips the glyphs at the viewport edge; it does not require the full line box to fit.