Markup from Java

Sometimes a markup string is more convenient than building spans by hand: template-style messages, config-driven text, or user-provided strings all fit that mold. You can parse a markup string in Java and feed the result directly into ImmersiveMessage.fromMarkup (the easy path), or use MarkupParser.parseFull when you need access to the intermediate data.

For most callers, pass markup straight to ImmersiveMessage.fromMarkup. It calls the parser internally and applies any message-level effects and attributes for you:

ImmersiveMessage msg = ImmersiveMessage.fromMarkup(100f, "[shake] <gold><bold>Alert:</bold></gold> zone is active");
EmbersTextAPI.sendMessage(player, msg);

Nothing else required. Skip the rest of this page unless you need to inspect or reuse the parsed data.

When you need the spans, message effects, or the stripped markup string before constructing a message, call the parser explicitly:

// Returns List<TextSpan> -- bracket tags are treated as plain text here
List<TextSpan> spans = MarkupParser.parse("<red>Fire</red> damage: " + value);
// Returns a ParseResult that includes message-level bracket tags
ParseResult result = MarkupParser.parseFull("[shake] <red>Fire</red> damage: " + value);

parse is the lower-level call. It handles <...> tags only and returns spans directly. parseFull also strips out [...] bracket tags (message effects and message attributes), returning a ParseResult with everything separated.

ParseResult is a record with four fields:

FieldTypeContents
spans()List<TextSpan>styled text spans, ready to pass to ImmersiveMessage.fromSpans
messageEffects()List<MessageEffect>effects applied to the whole message at HUD render time, parsed from [effectName ...] bracket tags
messageAttributes()List<MessageAttribute>anchor, scale, offset, background, and similar layout properties parsed from [attrName ...] bracket tags
strippedMarkup()Stringthe markup string with bracket tags removed, useful as a plain-text-ish label or for logging

ParseResult.empty() returns a no-op result when you need a safe default.

Build an ImmersiveMessage from the spans, then apply effects and attributes yourself if you need them:

ParseResult result = MarkupParser.parseFull(markupString);
ImmersiveMessage msg = ImmersiveMessage.fromSpans(100f, result.spans());
for (MessageEffect effect : result.messageEffects()) {
msg.addMessageEffect(effect);
}
for (MessageAttribute attr : result.messageAttributes()) {
attr.apply(msg);
}
EmbersTextAPI.sendMessage(player, msg);

ImmersiveMessage.fromMarkup does exactly this internally. The explicit form is only worth writing when you need to inspect or filter the effects before applying them.

extractDuration(String markup): scans for a <dur:N> tag and returns Object[] { float duration, String remainingMarkup }. The float is -1f if no tag was found. Useful when duration is embedded in a config string rather than passed separately.

containsLangTag(String text): returns true if the string contains a <lang:...> or <lang ...> tag. Use this to skip lang resolution in hot paths when you know the string probably doesn’t need it.

toPlainText(List<TextSpan> spans): concatenates span.getContent() across the list. No styling, no effects, just the raw character sequences.

fromPlainText(String text): wraps a plain string in a single unstyled TextSpan inside a List. Useful when an API expects spans but you only have a plain string.

By default the parser resolves <lang:key> tags using Language.getInstance(). You can replace this with your own lookup, useful if you want to use a custom translation table or resolve keys differently on the server side:

MarkupParser.setLangResolver((key, args) -> {
String translated = MyTranslations.get(key);
return translated != null ? translated : key;
});

Wire this once at mod initialization. Passing null to setLangResolver resets it back to the default Language-based resolver.

Pick markup when:

  • The text is static or mostly static and you want to see the styled string at a glance.
  • You’re loading message definitions from a config or data file.
  • A designer or content author is writing the strings and prefers human-readable format.

Pick spans when:

  • Text content comes from runtime data (scores, player names, computed values).
  • Colors or effects are determined at runtime and embedding them in a format string would be awkward.
  • You’re generating many per-player variants in a loop.
  • You need different effects on different segments and dynamic string construction would require careful escaping.