template mode / TemplateEngine
hud/TemplateEngine.java. Parses free-text lines with interpolated
{stat} tokens into a List<Token>, cached per-list, re-rendered
every frame. This page documents the actual parser behavior, not a spec someone wrote
separately from it — read it next to the source if you're modifying the grammar.
token grammar
line := (literal | token)*
token := "{" body "}"
body := name (":" modifiers)?
modifiers:= modifier ("," modifier)*
modifier := decimals | "graph=" bool | "color=" hexcolor
decimals := [0-9]+ ; 0–6 inclusive
hexcolor := "#" (hex{3} | hex{6})
bool := "true" | "false"
name is matched case-insensitively against StatRegistry.byToken()
after lowercasing (String.toLowerCase(Locale.ROOT)). Whitespace around a modifier
is trimmed before matching.
escaping
| input | output |
|---|---|
{{ | literal { |
}} | literal } |
lone } (no preceding matched {) | literal }, passed through as-is |
{ with no closing } anywhere in the line | rest of the line from that point is literal text |
modifiers
| modifier | effect | example |
|---|---|---|
:N | decimal places (bare integer, no key). Only valid on stats where supportsDecimals() is true. | {tps:2} |
graph=true | render as rolling graph instead of text. Only valid where supportsGraph() is true. Must be the line's only content in practice, though the parser itself doesn't enforce that — layout does. | {fps:graph=true} |
color=#RRGGBB | colors just this token, independent of the line's base color. 3-digit shorthand accepted and expanded (#5F5 → #55FF55). | {tps:color=#55FF55} |
Modifiers combine via commas: {cpu:0,color=#FF5555}. color= and
graph=true on the same token is not a parse error, but functionally a graph draws
its own coloring regardless — the two don't compose visually even though nothing stops you
from writing it.
failure modes — every one returns null from tryParseTokenBody
A malformed token is never silently dropped. On parse failure the whole
{body} — braces included — is appended back into the surrounding literal text
verbatim, and a one-time-per-list warning is logged
(WARNED_BAD_TOKENS, a Map<listId, Set<badBody>>, so the same
typo doesn't spam the log every frame it's rendered).
| input | why it fails |
|---|---|
{} | empty body |
{tpss} | unknown token name — no match in StatRegistry.byToken() |
{tps:} | colon present, nothing after it (modifiers.isBlank()) |
{tps:1,,graph=true} | empty modifier between two commas |
{tps:abc} | bare modifier isn't a parseable integer |
{tps:99} | decimals outside 0–6 |
{entities:2} | decimals given, but ENTITIES.supportsDecimals() == false |
{biome:graph=true} | graph requested, but BIOME.supportsGraph() == false |
{tps:graph=maybe} | graph= value isn't literally true/false |
{tps:color=notahex} | fails the hex regex [0-9a-fA-F]{3}|[0-9a-fA-F]{6} |
{tps:unknownkey=1} | modifier key isn't graph or color |
rendering
render(tokens) — plain string concatenation, used where color doesn't matter.
renderRuns(tokens, baseColor) — the one the HUD actually uses. Walks the token
list and coalesces adjacent same-colored output into ColoredRun records, so a line
with no color= modifiers anywhere renders as exactly one run, not one run per token.
Literal text always takes the line's base color; a stat token takes its own color()
if set, else falls back to base.
renderStat(token) calls StatDefinition.rawValue(decimals) — never
format() — since template lines are label-free by design (you type your own label
as literal text if you want one). As a safety net against a stat forgetting to override
rawValue(), it strips a leading "Label: " prefix if one slipped
through (same logic MineTunerRenderer uses for classic mode's showPrefix=false).
caching
getParsedLines(cfg) joins cfg.templateLines with a NUL separator
(\u0000 — chosen because it can't appear in a normal template string) and compares
against the cached join for that list id. Cache hit → return the cached
List<List<Token>> unchanged. Miss → reparse every line, replace the cache
entry. Keyed by cfg.id, so parsing happens once per edit, not once per frame.
invalidate(listId) — called on list delete — drops both the parse cache and the
per-list warned-tokens set for that id.