feat(chat): CT-A4 — speaker names render in retail's tag colour
Campaign CT slice A4, and the first slice of Group A that shows on screen: a player's name in a chat line now draws green while the rest of the line keeps its message colour. The colour is AUTHORED, not computed. Retail keeps two parallel index-selected colour arrays on the text element and refreshes both from the same caller index on every append (UIElement_Text::AppendStringInfoWithFont @0x00469DE0): property 0x1B for ordinary glyphs, 0x1D for glyphs under an open tag. Property 0x1D is read exactly the way 0x1B already was, carried on ElementInfo, and seeded onto UiText beside DefaultColor. Measured on the chat transcript (0x2100006F / 0x10000011) as RGB(0,178,0). It deliberately does NOT go into RetailChatColorTable. That table is the runtime-built per-LogTextType mapping; the tag colour is per-element authored data, and filing it there would put it somewhere it would look right in tests and be wrong in principle. RunsForFragment is the load-bearing piece and is pure. Wrapping can drop the space it broke on, so a fragment is NOT simply the next N characters of the line — BuildLines locates each fragment in the source text to keep the span offsets honest, and the mapper clips spans to the fragment window. A tag straddling a wrap break is therefore split across both fragments and stays green on both, instead of changing colour mid-word. Two guards worth naming. A fragment containing no tag returns NULL rather than a single-run list, so the overwhelming majority of lines keep the existing flat draw path untouched. And an element authoring no 0x1D falls back to the line colour, so a name never renders in a colour nobody chose. The run/fragment contract is property-tested across every substring of a tell line, because CT-A1's RunsMatchLine refuses mismatched runs by silently falling back to flat text — a mapping bug here would degrade quietly rather than fail. Solution builds clean; full hermetic gate green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
44fb74f8a6
commit
53395e4de4
6 changed files with 318 additions and 2 deletions
|
|
@ -131,6 +131,22 @@ public sealed class ElementInfo
|
|||
/// </summary>
|
||||
public Vector4? FontColor;
|
||||
|
||||
/// <summary>
|
||||
/// Authored TAG font colour (dat property <c>0x1D</c>), the colour retail
|
||||
/// gives a tagged glyph run — a clickable speaker name — as distinct from
|
||||
/// <see cref="FontColor"/> (<c>0x1B</c>) for everything else.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The two are parallel index-selected arrays refreshed from the same
|
||||
/// caller index on every append
|
||||
/// (<c>UIElement_Text::AppendStringInfoWithFont @0x00469DE0</c>), and a
|
||||
/// glyph takes this one only while a tag is open. Measured on the chat
|
||||
/// transcript (0x2100006F / 0x10000011) as RGB(0,178,0); it is AUTHORED
|
||||
/// per element, not built by the runtime chat colour table, so it does not
|
||||
/// belong in <c>RetailChatColorTable</c>.
|
||||
/// </remarks>
|
||||
public Vector4? TagFontColor;
|
||||
|
||||
/// <summary>
|
||||
/// Outline flag from dat <c>Properties[0x21]</c> (<c>BoolBaseProperty</c>). Retail
|
||||
/// <c>UIElement_Text::SetOutline @0x0046a81c</c> / <c>m_bitField & 0x10</c>.
|
||||
|
|
@ -576,6 +592,7 @@ public static class ElementReader
|
|||
// FontColor: derived wins when it has an explicit (non-null) color; otherwise inherit the base.
|
||||
// Null means "dat carried no 0x1B property" — so null-derived does NOT override a non-null base.
|
||||
FontColor = derived.FontColor ?? base_.FontColor,
|
||||
TagFontColor = derived.TagFontColor ?? base_.TagFontColor,
|
||||
// Outline: derived wins when true (the dat property 0x21 was present and read as
|
||||
// true); otherwise inherit the base. False-derived never overrides a true base —
|
||||
// matching the FontDid/HJustify "non-default wins" convention.
|
||||
|
|
@ -669,6 +686,25 @@ public static class ElementReader
|
|||
}
|
||||
}
|
||||
|
||||
// Tag font colour (0x1D) — same shape as 0x1B above, read the same way.
|
||||
if (info.TryGetEffectiveProperty(0x1Du, out var tagColor))
|
||||
{
|
||||
UiPropertyValue? tagValue = tagColor.Kind == UiPropertyKind.Color
|
||||
? tagColor
|
||||
: tagColor.Kind == UiPropertyKind.Array
|
||||
&& tagColor.ArrayValue.Count > 0
|
||||
&& tagColor.ArrayValue[0].Kind == UiPropertyKind.Color
|
||||
? tagColor.ArrayValue[0]
|
||||
: null;
|
||||
if (tagValue is not null)
|
||||
{
|
||||
var t = tagValue.ColorValue;
|
||||
float alpha = t.Alpha == 0 ? 1f : t.Alpha / 255f;
|
||||
info.TagFontColor =
|
||||
new Vector4(t.Red / 255f, t.Green / 255f, t.Blue / 255f, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
// Outline (0x21): BoolBaseProperty. Retail SetOutline @0x0046a81c / m_bitField & 0x10.
|
||||
if (info.TryGetEffectiveProperty(0x21u, out var outline)
|
||||
&& outline.Kind == UiPropertyKind.Bool)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue