Skip to content

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 a CL_EV_ constant: ev::BlockBreak is CL_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 */ in clight.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.