Messages and text
Lesson 11.2
Mods talk to players with text: chat messages, hints above the hotbar, coloured warnings. This lesson shows the ways to send text and how to make it colourful and translatable.
Chat and the action bar
player:send_message("Welcome back!") -- in chat
player:send_action_bar("+1 ruby") -- above the hotbar, fades after a moment
Both accept any value. Numbers and other values are turned into text with tostring, so player:send_message(42) works.
To send a message to everybody online:
cl.entity.server_broadcast("The dragon has been defeated!")
Use chat for things players should be able to read again later, and the action bar for quick feedback that would only clutter the chat.
Text objects
A plain string is always white. For colours and translations you build a text object with cl.text:
local warning = cl.text.literal("Careful, the floor is lava.")
warning:color(0xFF5555)
player:send_message(warning)
cl.text.literal(s) makes text from a string. text:color(rgb) sets its colour as a number 0xRRGGBB, the same way colours are written on the web: two hex digits each for red, green and blue.
Some colours to start with, the same as Minecraft’s chat colours:
| Colour | Value |
|---|---|
| Red | 0xFF5555 |
| Gold | 0xFFAA00 |
| Yellow | 0xFFFF55 |
| Green | 0x55FF55 |
| Aqua | 0x55FFFF |
| Blue | 0x5555FF |
| Light purple | 0xFF55FF |
| Gray | 0xAAAAAA |
| White | 0xFFFFFF |
color changes the text object itself and also returns it, so calls can be chained: cl.text.literal("Careful!"):color(0xFF5555).
Joining pieces
parent:append(child) puts one text after another. Each piece keeps its own colour; a piece without a colour takes the colour of the text it was appended to.
local function shop_message(what)
local msg = cl.text.literal("[Shop] ")
msg:color(0xFFAA00)
local body = cl.text.literal("You bought " .. what .. ".")
body:color(0xFFFFFF)
msg:append(body)
return msg
end
player:send_message(shop_message("a ruby"))
The player sees [Shop] in gold and the rest in white.
text:to_plain() gives the text back as a plain string, without colours. That is handy for logging:
cl.log(shop_message("a ruby"):to_plain()) --> [Shop] You bought a ruby.
Translations
A good mod speaks the player’s language. Instead of writing the English text in your script, you give it a key, and the text for each language lives in a file:
assets/my_mod/lang/en_us.json:
{
"my_mod.welcome": "Welcome to the server!",
"my_mod.rule": "Please don't grief."
}
assets/my_mod/lang/de_de.json:
{
"my_mod.welcome": "Willkommen auf dem Server!",
"my_mod.rule": "Bitte nichts zerstören."
}
player:send_message(cl.text.translatable("my_mod.welcome"))
The game looks the key up in the player’s language and falls back to en_us. Start keys with your mod id so they never clash with other mods.
Translated text is a text object too, so color and append work on it.
Who translates?
The player’s game translates the key, not the server. Players who don’t have your mod installed (and so don’t have its language files) see the key itself, like my_mod.welcome. If your mod has "sync": "optional", plain strings are the safer choice for messages.
Bold, italic and more
Five styles can be switched on with a method each. They return the text, so they chain:
local title = cl.text.literal("Ruby Shop"):bold():underlined():color(0xFF55FF)
player:send_message(title)
| Method | Looks like |
|---|---|
bold() |
thick letters |
italic() |
slanted letters |
underlined() |
a line under the text |
strikethrough() |
a line through the text |
obfuscated() |
letters that keep changing, like the “magic” text in the End Poem |
Pass false to switch a style off on purpose: italic(false). That matters where the game makes text italic by default, like item names. Styles are passed on to appended pieces, like colours, unless a piece switches them off.
Click and hover
Chat text can react to the mouse. text:hover(...) sets a tooltip that shows while the mouse is over the text, and text:click(action, value) says what a click does:
local link = cl.text.literal("[Open the wiki]"):color(0x55FFFF):underlined()
link:click("open_url", "https://example.com/ruby-wiki")
link:hover("Opens the Ruby Tools wiki in your browser")
player:send_message(link)
| Action | What a click does | Value |
|---|---|---|
"open_url" |
asks the player, then opens the address in the browser | an address starting with https:// or http:// |
"run_command" |
runs a command as the player | "/spawn" (the / is added if you leave it out) |
"suggest_command" |
puts text into the chat box for the player to edit and send | "/msg Steve " |
"copy_to_clipboard" |
copies the value | any text |
hover takes a string or a text object, so the tooltip can have colours too. A clickable “[Accept]” and “[Deny]” pair is a nice way to ask a question:
local function button(label, command, rgb)
return cl.text.literal(label):color(rgb):bold():click("run_command", command):hover("Click to run " .. command)
end
local msg = cl.text.literal("Trade 3 rubies for a diamond? ")
msg:append(button("[Accept]", "/rubytrade accept", 0x55FF55))
msg:append(" ")
msg:append(button("[Deny]", "/rubytrade deny", 0xFF5555))
player:send_message(msg)
append also takes a plain string, like the space above.
Values in translations
A translated message often needs values, like “You have 7 rubies”. In the language file, %s marks where a value goes:
{ "my_mod.rubies": "You have %s rubies, %s." }
Pass the values after the key. They can be strings, numbers or text objects:
player:send_message(cl.text.translatable("my_mod.rubies", 7, player.name))
--> You have 7 rubies, Steve.
If a language needs the values in another order, it writes %2$s and %1$s for the second and first value. Text objects as values keep their own colours and styles: cl.text.translatable("my_mod.rubies", cl.text.literal("7"):bold(), player.name).
Titles
A title is the big text in the middle of the screen, with an optional smaller subtitle below it:
player:send_title("Round 2", "Get ready!")
player:send_title("Go!", nil, { fade_in = 5, stay = 20, fade_out = 10 })
The times are in ticks (20 ticks are one second). Without them the player’s current times are kept; Minecraft starts with 10, 70 and 20. A nil title with a subtitle changes only the subtitle of the title that is showing. player:clear_title() removes the title right away, and player:clear_title(true) also puts the times back to 10, 70 and 20.
The action bar has no times of its own: send_action_bar text always fades after about three seconds, as in Minecraft.
Messages only you see
Everything above is sent by the server to a player. A client mod can also show text that only this player sees, without asking the server:
cl.client.message("Shader reloaded.") -- a chat line
cl.client.action_bar("Zoom: 4x") -- above the hotbar
cl.client.title("Photo mode", "Press F2 to take a picture")
They take the same strings and text objects, styles and clicks included. Nothing goes over the network, so they work on any server, even one without your mod. Only the client has them: on a server they are nil.
Try it
Write a function rubies_message(n) that returns a text object: “You have " in gray, then the number in light purple, then " rubies.” in gray. Send it to every player who joins, with the number 7.
Show a solution
local function piece(s, rgb)
local t = cl.text.literal(s)
t:color(rgb)
return t
end
local function rubies_message(n)
local msg = piece("You have ", 0xAAAAAA)
msg:append(piece(tostring(n), 0xFF55FF))
msg:append(piece(" rubies.", 0xAAAAAA))
return msg
end
cl.on("player.join", function(event)
event.player:send_message(rubies_message(7))
end)
A small helper like piece saves a lot of repeated lines when you build coloured text.
Recap: send_message and send_action_bar take strings or text objects; cl.text.literal and cl.text.translatable (with values) make text, color, bold and the other styles change how it looks, click and hover make it react to the mouse, and append joins pieces. send_title shows a title, and cl.client.message shows text only this player sees.