Skip to content

The C++ wrapper

Lesson 4.1

The CLight API is a C API: plain functions that take handles, string-and-length pairs and output pointers. That is great for stability, but a little wordy for small jobs like “give this player an apple”. The SDK’s clight.hpp wraps all of it in C++: classes, lambdas, std::string and short helpers that do those jobs in one line. This lesson shows the parts you will use every day.

One include for everything

#include <clight.hpp>

clight.hpp includes clight.h, the full C API, so with this one line you have both. Everything from the wrapper lives in the namespace clight::; everything from the C API starts with cl_ or CL_. You can mix them freely, and the wrapper’s classes turn into the C handles by themselves where a C function wants one.

Your mod is a class

class RubyMod final : public clight::Mod {
public:
    void on_construct() override { /* the mod was just loaded */ }
    void on_register() override { /* register items and blocks */ }
    void on_client_setup() override { /* keybinds and HUD layers */ }
};

CLIGHT_MOD(RubyMod)

clight::Mod has an empty virtual function for each step of the game’s startup. Override the ones you need:

Function Runs
on_construct() Once, right when the mod is loaded. Subscribe to events here.
on_register() When items, blocks and network channels are registered.
on_registries_frozen() After registering ends.
on_common_setup() Setup that needs the registered content.
on_client_setup() In a game with a window: keybinds, HUD layers.
on_server_setup() Server setup.
on_server_starting() A world is about to open.
on_server_stopped() The world is closed.
on_unload() The mod is being unloaded, when the game quits.

CLIGHT_MOD(RubyMod) creates one RubyMod when the mod loads, calls the functions at the right moments, and deletes the object at the end. Put it in exactly one .cpp file.

Members of your class (int visitors_ = 0;) live as long as the mod does, which makes them a good home for state. W3Schools explains virtual and override in Virtual functions.

clight::on: events as lambdas

clight::on<clight::ev::PlayerJoin>([](cl_ev_player& e) {
    clight::Entity player = e.player;
    player.send_message("Hello!");
}).detach();
  • clight::ev::PlayerJoin names the event. Every event has a type in clight::ev, and The event list has them all.
  • The lambda gets the event’s details as a reference to its payload struct. Each event knows its own struct: PlayerJoin gives a cl_ev_player&, BlockBreak a cl_ev_block_break&. Write the wrong one and it doesn’t compile. If you would rather not spell it, [](auto& e) works too.
  • .detach() keeps the handler for the mod’s lifetime. Listening to events explains what happens without it.

clight::Entity and its helpers

Players and other entities arrive as cl_entity handles. Wrap one in clight::Entity and it gets methods:

Method Does
player.name() The player’s name as a std::string, like "Alex". "" for other entities.
player.give("minecraft:apple", 3) Gives items. What doesn’t fit is dropped at the player’s feet. The count is optional: give("minecraft:apple") gives one.
player.send_message("Hi!") A chat message to this player.
player.send_action_bar("Hi!") A message above the hotbar.
player.pos() The position as a cl_vec3 (.x, .y, .z).
player.count_item("minecraft:diamond") How many of an item the player has.
player.take("minecraft:diamond", 3) Removes up to 3 and returns how many it removed.
player.consume_held(1) Uses up items from the stack in the main hand.

And three free functions:

Function Does
clight::broadcast("Hello everyone") A chat message to every player on the server.
clight::item("minecraft:apple", 3) An item stack you can give, put into menus or compare.
clight::literal("Hi!") A text you can colour and combine (see Messages and text).

And one for the world: clight::Level(level).set_block(pos, "minecraft:stone") places a block.

All of them take a std::string_view, so you can pass a string literal, a std::string, or a std::string_view. The ones that do something return true when it worked, and you are free to ignore that.

Item and block ids are written namespace:name. Vanilla things use minecraft:, your own things use your mod id: "my_mod:ruby".

clight::log

You met it already: std::format-style logging, one function per level.

clight::log::info("{} players online", 3);
clight::log::warn("that's odd: {}", "no rubies");

A small mod with only easy helpers

#include <clight.hpp>

// A gift for everyone who joins, and a hello to the whole server.
class BreadMod final : public clight::Mod {
public:
    void on_construct() override {
        clight::on<clight::ev::PlayerJoin>([](cl_ev_player& e) {
            clight::Entity player = e.player;

            player.give("minecraft:bread", 3);
            player.send_action_bar("Here is some bread for the road.");
            clight::broadcast(player.name() + " just arrived!");
        }).detach();
    }
};

CLIGHT_MOD(BreadMod)

When you need more

The easy helpers cover the everyday jobs. For everything else, the wrapper has a namespace per part of the API, with the same names as the C functions:

Namespace For C functions
clight::entity entities and players, server_players(), client_player() cl_entity_*, cl_player_*, cl_living_*
clight::level worlds, server_overworld(), client_level() cl_level_*
clight::registry registering items and blocks, looking up ids cl_item_*, cl_block_*
clight::text texts with colours and translations cl_text_*
clight::cmd, clight::sched, clight::config commands, timers, config files cl_cmd_*, cl_sched_*, cl_config_*
clight::gui, clight::hud, clight::screen, clight::menu drawing and windows cl_gui_*, cl_hud_*, cl_screen_*, cl_widget_*, cl_menu_*
clight::sound, clight::net sound, payloads and buffers cl_sound_*, cl_music_*, cl_net_*, cl_buf_*
clight::core, clight::mods, clight::events versions, features, the mod itself, event details cl_core_*, cl_mod_*, cl_event_*

clight::Entity and clight::Level have most of their namespace as methods too: player.position() and clight::entity::position(player) are the same call. The next lessons explain the ideas the wrapper is built on: Handles and strings, Errors and Expected, and Contexts and sides. Once you know those, every part of the API reads the same way.

A few corners of the C API have no wrapper yet (keybinds, for example). There you call the C function directly; the lessons show how.

Try it

When a player joins, give them one minecraft:compass and send them two chat messages: “Welcome, NAME!” and “Your compass points to spawn.”

Show a solution
#include <clight.hpp>

class CompassMod final : public clight::Mod {
public:
    void on_construct() override {
        clight::on<clight::ev::PlayerJoin>([](cl_ev_player& e) {
            clight::Entity player = e.player;

            player.give("minecraft:compass");
            player.send_message("Welcome, " + player.name() + "!");
            player.send_message("Your compass points to spawn.");
        }).detach();
    }
};

CLIGHT_MOD(CompassMod)

give gives one when you leave out the count. The first message is built with + from a literal and the std::string that name() returns; the second is fixed text.

Recap: #include <clight.hpp> gives you the whole API plus the wrapper: your mod is a clight::Mod class, clight::on<ev::...> subscribes lambdas, clight::Entity has helpers like name(), give and send_message, and clight::log writes formatted lines.