The event list
Lesson 5.3
This page lists every event the game sends today, with its clight::ev type, its payload struct and its fields. Come back to it whenever you write a new handler.
How to read the tables:
- The event column is the type you put into
clight::on<clight::ev::...>. In C, the same event is aCL_EV_constant:ev::BlockBreakisCL_EV_BLOCK_BREAK. - The payload is the struct your lambda takes by reference:
[](cl_ev_block_break& e). - Side says where the event happens. Server events run where the world is simulated: in singleplayer, or on a server that has your mod. Client events run in the player’s game window. Contexts and sides explains the difference.
- Cancel marks events you can stop with
clight::cancel(e.header). - Fields in bold can be changed (
/* mut */inclight.h). - Every payload also starts with
header, which you only need for cancelling and results.
Startup and shutdown
These run once, in this order, while the game starts or a world opens. Their payload is cl_ev_lifecycle, and you won’t need its fields. The ones with a clight::Mod function can be used either way: override the function, or subscribe to the event.
| Event | Side | clight::Mod function |
Use it for |
|---|---|---|---|
ev::LifecycleRegister |
both | on_register |
Registering items, blocks and network channels. |
ev::LifecycleRegistriesFrozen |
both | on_registries_frozen |
After this, nothing more can be registered. |
ev::LifecycleCommonSetup |
both | on_common_setup |
Setup that needs the registered content. |
ev::LifecycleClientSetup |
client | on_client_setup |
Keybinds, HUD layers and other client things. |
ev::LifecycleServerSetup |
server | on_server_setup |
Server setup. |
ev::LifecycleServerStarting |
server | on_server_starting |
A world is about to open. |
ev::LifecycleServerStarted |
server | The world is open. | |
ev::LifecycleServerStopping |
server | The world is closing. Save your data here. | |
ev::LifecycleServerStopped |
server | on_server_stopped |
The world is closed. |
ev::LifecycleShutdown |
both | The game is quitting. |
on_construct runs before all of them, so subscribing there catches every one. In singleplayer, each world you open runs ServerStarting to ServerStopped again.
A few more come while the game runs:
| Event | Side | Happens when |
|---|---|---|
ev::LifecycleLevelLoad |
server | A world (level) is loaded. |
ev::LifecycleLevelSave |
server | A world is saved: on autosave, /save-all and when closing. A good moment to save your own data. |
ev::LifecycleLevelUnload |
server | A world is unloaded. |
ev::LifecycleDataReloaded |
server | Data packs were reloaded with /reload. |
ev::LifecycleResourcesReloaded |
client | Resource packs were reloaded (F3 + T, or a pack change). |
ev::LifecycleModReload |
both | A mod’s code was hot reloaded. Today only Lua mods hot reload, so a C++ mod sees this for other mods only. |
Ticks
The game runs 20 ticks per second. These events run every tick, so keep their handlers short. Payload: cl_ev_tick.
| Event | Side | Fields |
|---|---|---|
ev::TickServerPre |
server | tick_count: the number of the tick |
ev::TickServerPost |
server | tick_count |
ev::TickLevelPre |
server | level, tick_count: once per world per tick |
ev::TickLevelPost |
server | level, tick_count |
ev::TickClientPre |
client | |
ev::TickClientPost |
client |
Pre runs before the game’s own work for that tick, Post after it. When in doubt, use Post. The level ticks run for each world (overworld, Nether, End) separately, with level set.
Players
| Event | Payload | Side | Cancel | Fields |
|---|---|---|---|---|
ev::PlayerJoin |
cl_ev_player |
server | player |
|
ev::PlayerLeave |
cl_ev_player |
server | player |
|
ev::PlayerRespawn |
cl_ev_player |
server | player |
|
ev::PlayerChat |
cl_ev_player_chat |
server | yes | player, message |
ev::PlayerCommand |
cl_ev_player_command |
server | yes | player, command (without the /) |
ev::PlayerInteractBlock |
cl_ev_player_interact_block |
server | yes | player, level, hand, pos, face |
ev::PlayerInteractItem |
cl_ev_player_interact_item |
server | yes | player, level, hand, stack |
ev::PlayerAttackEntity |
cl_ev_player_attack_entity |
server | yes | player, target |
hand is CL_HAND_MAIN or CL_HAND_OFF. face is the side of the block that was clicked, CL_DIRECTION_DOWN to CL_DIRECTION_EAST. stack is a borrowed copy of the stack in the hand: read it with clight::stack::is(e.stack, "minecraft:stick") and the other clight::stack functions, and don’t put it into a clight::ItemStack, which would release it.
Blocks
| Event | Payload | Side | Cancel | Fields |
|---|---|---|---|---|
ev::BlockBreak |
cl_ev_block_break |
server | yes | level, pos, state, player, tool, xp, drop_items |
ev::BlockPlace |
cl_ev_block_place |
server | yes | level, pos, state, placer, hand, replaced_state |
state is the block state as a number. Compare it with clight::registry::block_state_is(e.state, "minecraft:stone").
tool is a borrowed copy of the stack in the player’s main hand (read it, don’t release it), and xp is the experience the block will drop. Set xp to 0 to drop none, or raise it for a bonus. Both are filled in for vanilla blocks; your own mod blocks drop no experience unless you set xp.
Entities
| Event | Payload | Side | Cancel | Fields |
|---|---|---|---|---|
ev::EntityDamage |
cl_ev_entity_damage |
server | yes | entity, level, source, attacker, amount |
ev::EntityDeath |
cl_ev_entity_death |
server | yes | entity, level, source, attacker |
entity is the hurt or dying entity; clight::Entity(e.entity).is_player() tells you whether it is a player. attacker is whoever caused it, or an empty handle. source is the damage type as an interned id; get its name with clight::core::ident_name:
std::string src = clight::core::ident_name(e.source).value_or("unknown"); // "minecraft:fall", "minecraft:player_attack", ...
Cancelling entity.death skips the death. Give the entity some health back with clight::Entity(e.entity).set_health(4.0f) in the same handler, or it has none left.
Commands
| Event | Payload | Side | Fields |
|---|---|---|---|
ev::ServerCommandRegister |
cl_ev_command_register |
server | dedicated: true on a dedicated server |
Register your commands in here. Commands shows how.
Client
| Event | Payload | Side | Cancel | Fields |
|---|---|---|---|---|
ev::ClientConnect |
cl_ev_client_connect |
client | address: the server address |
|
ev::ClientDisconnect |
cl_ev_lifecycle |
client | ||
ev::ClientScreenOpen |
cl_ev_screen_open |
client | screen_id |
|
ev::ClientKey |
cl_ev_key |
client | yes | key, scancode, action, modifiers, screen_open |
ev::ClientChatSend |
cl_ev_chat_send |
client | yes | message: what the player is about to send |
ev::ClientChatReceived |
cl_ev_chat_received |
client | yes | message, overlay (true for action-bar text) |
ev::ClientSoundPlay |
cl_ev_sound_play |
client | yes | sound, category, pos, volume, pitch, positional |
Client events also fire on servers that don’t have your mod. That is what makes client-only mods possible.
Most of these events can be filtered to one block, item, entity type or screen id; Changing and cancelling events shows how.
Try it
Write one handler that greets a player when they join, and another that logs when a player died and what killed them (the damage source).
Show a solution
#include <clight.hpp>
class DeathLogMod final : public clight::Mod {
public:
void on_construct() override {
clight::on<clight::ev::PlayerJoin>([](cl_ev_player& e) {
clight::Entity player = e.player;
player.send_message("Hi " + player.name() + ", have fun!");
}).detach();
clight::on<clight::ev::EntityDeath>([](cl_ev_entity_death& e) {
clight::Entity dead = e.entity;
if (!dead.is_player()) return; // only players
std::string src = clight::core::ident_name(e.source).value_or("something");
clight::log::info("{} died ({})", dead.name(), src);
}).detach();
}
};
CLIGHT_MOD(DeathLogMod)
name() works on dead because it is a player here; for other entities it would give an empty string.
Recap: startup events (or the clight::Mod functions) register and set up, tick events run 20 times a second, and player, block, entity and client events report what happens; the table tells you the clight::ev type and the payload struct your lambda takes.