Skip to content

The event list

Lesson 5.3

This page lists every event the game sends today, with its payload struct and fields. Come back to it whenever you write a new handler.

How to read the tables:

  • 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 cl_event_cancel(&e->header).
  • Fields in bold can be changed (/* mut */ in clight.h).
  • Every payload also starts with header, which you only need for cancelling.

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.

Event Side Use it for
CL_EV_LIFECYCLE_REGISTER both Registering items, blocks and network channels.
CL_EV_LIFECYCLE_REGISTRIES_FROZEN both After this, nothing more can be registered.
CL_EV_LIFECYCLE_COMMON_SETUP both Setup that needs the registered content.
CL_EV_LIFECYCLE_CLIENT_SETUP client Keybinds, HUD layers and other client things.
CL_EV_LIFECYCLE_SERVER_SETUP server Server setup.
CL_EV_LIFECYCLE_SERVER_STARTING server A world is about to open.
CL_EV_LIFECYCLE_SERVER_STARTED server The world is open.
CL_EV_LIFECYCLE_SERVER_STOPPING server The world is closing. Save your data here.
CL_EV_LIFECYCLE_SERVER_STOPPED server The world is closed.
CL_EV_LIFECYCLE_SHUTDOWN both The game is quitting.

mod_main runs before all of them, so subscribing there catches every one. In singleplayer, each world you open runs SERVER_STARTING to SERVER_STOPPED again.

A few more come while the game runs:

Event Side Happens when
CL_EV_LIFECYCLE_LEVEL_LOAD server A world (level) is loaded.
CL_EV_LIFECYCLE_LEVEL_SAVE server A world is saved: on autosave, /save-all and when closing. A good moment to save your own data.
CL_EV_LIFECYCLE_LEVEL_UNLOAD server A world is unloaded.
CL_EV_LIFECYCLE_DATA_RELOADED server Data packs were reloaded with /reload.
CL_EV_LIFECYCLE_RESOURCES_RELOADED client Resource packs were reloaded (F3 + T, or a pack change).
CL_EV_LIFECYCLE_MOD_RELOAD 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
CL_EV_TICK_SERVER_PRE server tick_count: the number of the tick
CL_EV_TICK_SERVER_POST server tick_count
CL_EV_TICK_LEVEL_PRE server level, tick_count: once per world per tick
CL_EV_TICK_LEVEL_POST server level, tick_count
CL_EV_TICK_CLIENT_PRE client
CL_EV_TICK_CLIENT_POST 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
CL_EV_PLAYER_JOIN cl_ev_player server player
CL_EV_PLAYER_LEAVE cl_ev_player server player
CL_EV_PLAYER_RESPAWN cl_ev_player server player
CL_EV_PLAYER_CHAT cl_ev_player_chat server yes player, message
CL_EV_PLAYER_COMMAND cl_ev_player_command server yes player, command (without the /)
CL_EV_PLAYER_INTERACT_BLOCK cl_ev_player_interact_block server yes player, level, hand, pos, face
CL_EV_PLAYER_INTERACT_ITEM cl_ev_player_interact_item server yes player, level, hand, stack
CL_EV_PLAYER_ATTACK_ENTITY 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, don’t release it.

Blocks

Event Payload Side Cancel Fields
CL_EV_BLOCK_BREAK cl_ev_block_break server yes player, level, pos, state, drop_items
CL_EV_BLOCK_PLACE cl_ev_block_place server yes placer, level, pos, state, hand, replaced_state

state is the block state as a number. Compare it with cl_block_state_is(e->state, CL_STR("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
CL_EV_ENTITY_DAMAGE cl_ev_entity_damage server yes entity, level, source, attacker, amount
CL_EV_ENTITY_DEATH cl_ev_entity_death server yes entity, level, source, attacker

entity is the hurt or dying entity; cl_entity_is_player(e->entity) tells you whether it is a player. attacker is whoever caused it, or a null handle. source is the damage type as an interned id; get its name with cl_ident_name:

char src[64];
size_t len = 0;
cl_ident_name(e->source, src, sizeof src, &len);   // "minecraft:fall", "minecraft:player_attack", ...

Cancelling entity.death skips the death. Give the entity some health back with cl_living_set_health(e->entity, 4.0f) in the same handler, or it has none left.

Commands

Event Payload Side Fields
CL_EV_SERVER_COMMAND_REGISTER 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
CL_EV_CLIENT_CONNECT cl_ev_client_connect client address: the server address
CL_EV_CLIENT_DISCONNECT cl_ev_lifecycle client
CL_EV_CLIENT_SCREEN_OPEN cl_ev_screen_open client screen_id
CL_EV_CLIENT_KEY cl_ev_key client yes key, scancode, action, modifiers, screen_open
CL_EV_CLIENT_CHAT_SEND cl_ev_chat_send client yes message: what the player is about to send
CL_EV_CLIENT_CHAT_RECEIVED cl_ev_chat_received client yes message, overlay (true for action-bar text)
CL_EV_CLIENT_SOUND_PLAY 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 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_easy.h>

static void on_player_join(void* payload, void* user) {
    (void)user;
    cl_entity player = CL_EVENT(cl_ev_player, payload)->player;
    char name[64];
    cl_easy_player_name(player, name, sizeof name);
    cl_easy_send_messagef(player, "Hi %s, have fun!", name);
}

static void on_death(void* payload, void* user) {
    (void)user;
    cl_ev_entity_death* e = CL_EVENT(cl_ev_entity_death, payload);
    if (!cl_entity_is_player(e->entity)) return;   // only players

    char name[64], src[64];
    size_t len = 0;
    cl_easy_player_name(e->entity, name, sizeof name);
    if (cl_ident_name(e->source, src, sizeof src, &len) != CL_OK) snprintf(src, sizeof src, "something");
    CL_LOG("%s died (%s)", name, src);
}

static cl_result mod_main(void) {
    cl_result r = CL_ON(CL_EV_PLAYER_JOIN, on_player_join);
    if (r == CL_OK) r = CL_ON(CL_EV_ENTITY_DEATH, on_death);
    return r;
}

CLIGHT_MOD_ENTRY(mod_main)

cl_easy_player_name works on e->entity because it is a player here; for other entities it would give an empty name.

Recap: startup events 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 payload struct to use with CL_EVENT.