Keys and client events
Lesson 14.2
Client mods react to what the player does at the keyboard and what the game window shows. This lesson covers keybinds, the client tick, and the client events for chat and keys.
A keybind
cl.on("lifecycle.client_setup", function()
local key = cl.keybind.register{
id = "my_mod:say_hi",
default_key = "key.keyboard.g",
category = "key.categories.my_mod",
}
cl.on("tick.client_post", function()
while key:consume_click() do
cl.log("G was pressed")
end
end)
end)
- Register keybinds in
lifecycle.client_setup. They are client-only. default_keyuses vanilla’s key names:"key.keyboard.g","key.keyboard.f6","key.keyboard.left.alt","key.keyboard.keypad.5", and for mouse buttons"key.mouse.left","key.mouse.right","key.mouse.middle","key.mouse.4"and"key.mouse.5"(the side buttons)."key.keyboard.unknown"starts it unbound.key:consume_click()returnstrueonce for each press since the last check, thenfalse. Check it every client tick; thewhilehandles two quick presses in one tick.key:is_down()istruewhile the key is held, for things like “zoom while holding”.
By default a keybind only reacts in the game, not while a menu or the chat is open. context = "gui" makes it work only in menus, and context = "any" everywhere.
In the Controls screen
Every mod keybind appears in Options > Controls > Key Binds, under its category, next to Minecraft’s own. Players can bind it to any key or mouse button and reset it, and the game saves their choice in options.txt like any other key. So always read the key through key:consume_click() and key:is_down(), never by checking a fixed key yourself. key:key() tells you what it is bound to right now, for example to show it in a hint:
cl.log("Press " .. key:key() .. " to say hi") --> Press key.keyboard.g to say hi
Give the keybind and its category a name in your language file. The keys are key. plus the id with : replaced by ., and the category as you wrote it:
{
"key.my_mod.say_hi": "Say hi",
"key.categories.my_mod": "My Mod"
}
Without language entries, the Controls screen shows label if you gave one (label = "Say hi" in register), else the id made readable (“Say Hi”), and your mod’s name as the category when you left category out.
The client tick
tick.client_post runs 20 times a second in the game window, also on servers without your mod. It is the right place for checking keys and updating anything your HUD shows:
local height = 0
cl.on("tick.client_post", function()
local me = cl.entity.client_player()
if me then
height = math.floor(me.pos.y)
end
end)
cl.entity.client_player() is you, the player at this computer, or nil when no world is open. On the client you can read it like any player: pos, health, inventory_get. Changing things (give, teleport) is for the server.
Chat on the client
client.chat_send happens when you press Enter in chat, before the message leaves your game. Cancel it to keep the message to yourself, which makes chat-style commands for client mods possible:
cl.on("client.chat_send", function(e)
if e.message == "!where" then
e:cancel() -- never sent to the server
local me = cl.entity.client_player()
cl.log("I am at", cl.pos(me.pos))
end
end)
client.chat_received happens when a message arrives. You can change e.message or cancel it to hide it:
-- Hide join and leave messages.
cl.on("client.chat_received", function(e)
if e.message:find("joined the game") or e.message:find("left the game") then
e:cancel()
end
end)
e.message is the text without colours, and e.overlay is true for action-bar messages.
Raw keys
client.key reports every key press and release, before the game handles it:
cl.on("client.key", function(e)
if e.action == 1 and not e.screen_open then
cl.log("key code", e.key)
end
end)
| Field | Means |
|---|---|
key |
The key’s code, as in GLFW (71 is G, 32 is space). |
action |
1 pressed, 0 released, 2 held down and repeating. |
modifiers |
Held modifier keys: add 1 shift, 2 control, 4 alt. |
screen_open |
true while a menu or the chat is open. |
Cancelling client.key hides the key from the game. Use it sparingly: players expect their keys to work. For your own features, a keybind is nearly always the better choice.
Other client events
| Event | Happens when |
|---|---|
client.connect |
You join a server (e.address) or a singleplayer world. |
client.disconnect |
You leave it. |
client.screen_open |
A menu opens (e.screen_id). |
Try it
Make a keybind on H that logs your current health. Holding Left Alt while you press it should log your position too.
Show a solution
cl.on("lifecycle.client_setup", function()
local health_key = cl.keybind.register{
id = "my_mod:show_health",
default_key = "key.keyboard.h",
category = "key.categories.my_mod",
}
local alt_key = cl.keybind.register{
id = "my_mod:with_position",
default_key = "key.keyboard.left.alt",
category = "key.categories.my_mod",
}
cl.on("tick.client_post", function()
while health_key:consume_click() do
local me = cl.entity.client_player()
if me then
cl.log("health: %.1f", me.health)
if alt_key:is_down() then
cl.log("position:", cl.pos(me.pos))
end
end
end
end)
end)
The second keybind is only used for is_down, so it works as a modifier.
Recap: register keybinds in lifecycle.client_setup and check consume_click in tick.client_post; players rebind them in the Controls screen; client.chat_send, client.chat_received and client.key let client mods react to chat and keys.