Skip to content

Setting up

Lesson 3.1

In this lesson you install the tools, make a new C++ mod and look at what is inside it.

What you need

  • Celestial itself. The download from the Celestial website is a folder with Celestial.exe in it.
  • The CLight SDK. It ships with Celestial, in the clight-sdk folder next to Celestial.exe. CLight is the name of the modding system, and the SDK is its toolbox: the clight tool, the headers (including the C++ wrapper clight.hpp), templates and examples.
  • wasi-sdk, the C and C++ compiler for WebAssembly. The clight tool downloads it for you.
  • A text editor. Visual Studio Code with Microsoft’s C/C++ extension works well, but any editor is fine.

You don’t need Visual Studio, MinGW or any other compiler. C++ mods are not Windows programs: they are compiled to WebAssembly (WASM), a portable format that the game runs in a sandbox. That is why one .clmod file works for every player.

Put the clight tool on your PATH

The SDK’s command-line tool is clight-sdk/bin/clight.exe. Add that bin folder to your PATH so you can type clight in any terminal:

  1. Press the Windows key and search for Edit environment variables for your account.
  2. Select Path, click Edit, then New, and paste the full path of the bin folder, for example C:\Games\Celestial\clight-sdk\bin.
  3. Click OK and open a new terminal window.

Install the compiler

Run clight setup once:

> clight setup
downloading https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-34/wasi-sdk-34.0-x86_64-windows.tar.gz
verified sha256, extracting...
installed wasi-sdk-34 at C:\Games\Celestial\clight-sdk\wasi-sdk

It downloads the official wasi-sdk release from GitHub, checks it against a known checksum, and unpacks it into clight-sdk/wasi-sdk. It is a big download (a full clang compiler with the C++ standard library), so give it a minute. You only do this once; clight setup --force installs it again if something went wrong.

If you already have wasi-sdk somewhere else, set the WASI_SDK_PATH environment variable to its folder, and clight uses that one instead.

Now check the whole installation:

> clight doctor
CLight SDK 0.1.0 (API 1.24)
  ok   SDK folder  C:\Games\Celestial\clight-sdk
  ok   clight.h  C:\Games\Celestial\clight-sdk\include
  ok   wasi-sdk (C/C++ mods)  C:\Games\Celestial\clight-sdk\wasi-sdk 34.0
  ok   Celestial  C:\Games\Celestial\Celestial.exe
  Lua mods need nothing else.

All four lines should say ok. A MISSING line tells you what to do next. (clight.h is the C API; clight.hpp sits next to it in the same folder.)

Make a new mod

Go to the folder where you keep your projects and run clight new cpp:

> clight new cpp my_mod
created C:\Projects\my_mod (cpp, 5 files)
next:  cd my_mod  &&  clight build  &&  clight run

my_mod is the mod’s id. Every mod has one, and it has to be unique. The rules: lowercase letters, digits and _, starting with a letter, 2 to 64 characters. ruby_tools is fine, RubyTools and 2cool are not.

The display name is made from the id (my_mod becomes “My Mod”). Pick another with --name:

> clight new cpp ruby_tools --name "Ruby Tools"

What’s inside

my_mod/
  clight.mod.json            the manifest: id, version, name, licence, ...
  src/
    main.cpp                 your code starts here
  assets/
    my_mod/
      lang/
        en_us.json           texts players see, by language
  CMakeLists.txt             only if you want to build with CMake (optional)
  .gitignore

A bigger mod adds more folders next to these:

Folder What goes in it
src/ Your .cpp files. clight build compiles every .cpp, .cc, .cxx and .c file in here, also in subfolders. Headers you write go here too.
assets/<id>/ Resource-pack files: textures, models, sounds, language files, GUI files.
datapack/<id>/ Data-pack files: recipes, loot tables, tags.
icon.png The mod’s icon, square, 64 to 256 pixels.
LICENSE The licence text.
build/ Made by clight: object files, the compiled bin/my_mod.wasm and the packed mod. Don’t edit it.

Your first source file

Open src/main.cpp. The template already does something useful:

// My Mod: a CLight mod in C++, compiled to WebAssembly by `clight build`.
// clight.hpp wraps the C API: plain strings, RAII handles, lambdas.
#include <clight.hpp>

class Mod final : public clight::Mod {
public:
    // Runs once when the mod loads: subscribe to events here.
    void on_construct() override {
        clight::log::info("{} {} loaded", clight::mod_id(), clight::mod_version());

        // When a player joins, say hello.
        clight::on<clight::ev::PlayerJoin>([](cl_ev_player& event) {
            clight::Entity player = event.player;

            player.send_message("Welcome, " + player.name() + "!");
            // translated instead: player.send_message(clight::text::translatable("my_mod.welcome"));
        }).detach();  // keep the handler for the mod's lifetime
    }
};

CLIGHT_MOD(Mod)

Read it from the bottom up:

  • CLIGHT_MOD(Mod) tells the game which class is your mod. When the mod loads, it creates one object of that class. It takes the place of main, and it must appear exactly once in your mod.
  • class Mod final : public clight::Mod is your mod. clight::Mod is a base class with a virtual function for each moment of the game’s startup. You override the ones you need. (The template calls its class Mod too; inside clight:: and outside it, those are two different names.)
  • on_construct runs once, right when the mod loads. It tells the game what the mod wants to hear about.
  • clight::on<clight::ev::PlayerJoin>(...) means “when a player joins, call this lambda”. The lambda gets the event’s details as event.
  • .detach() keeps the handler for as long as the mod runs. Without it, the handler would be removed again at the end of the line, and the compiler warns you about that. Listening to events explains why.
  • clight::Entity player = event.player; wraps the player in a small class with useful methods, like name() and send_message().
  • clight::log::info writes a line to the game’s log. {} is replaced by the next value, like in std::format.

You will meet all of these properly in The C++ wrapper.

The sandbox

Your mod runs in a sandbox. It can call the game API and read its own folder, and nothing else: no other files, no programs, no network, no native code. A mod can’t read your saves or your account. That is why players can install mods without worrying.

Editor help

To get completion and error squiggles, tell your editor where the headers are. With VS Code and the C/C++ extension, create .vscode/c_cpp_properties.json in your mod folder (change the paths to yours):

{
  "configurations": [
    {
      "name": "CLight",
      "includePath": ["${workspaceFolder}/src", "C:/Games/Celestial/clight-sdk/include"],
      "defines": ["__wasm__"],
      "compilerPath": "C:/Games/Celestial/clight-sdk/wasi-sdk/bin/clang++.exe",
      "compilerArgs": ["--target=wasm32-wasip1", "-fno-exceptions"],
      "cppStandard": "c++23"
    }
  ]
}

clight.hpp is generated from the same description as clight.h, and every C function in clight.h has a short comment: what it does, where it may run, and since which API version it exists. When you wonder what a wrapper method does, look up the C function it calls; the names match (player.send_message calls cl_player_send_message).

Try it

Make a mod called hello_world with the display name “Hello World”. Change the welcome message so it says “Welcome, Alex! Hello World is installed.” (with the player’s real name).

Show a solution
> clight new cpp hello_world --name "Hello World"

In src/main.cpp, change only the message line:

player.send_message("Welcome, " + player.name() + "! Hello World is installed.");

player.name() gives a std::string, so + joins the pieces into one string (Concatenation on W3Schools). The next lesson shows how to build the mod and see the message in the game.

Recap: put clight-sdk/bin on your PATH, run clight setup once for the compiler, make a mod with clight new cpp <id>, and write your mod as a class in src/main.cpp, created by CLIGHT_MOD.