Config
Lesson 8.3
Players like to adjust mods: a different reward, a feature turned off, a longer cooldown. A config file lets them do that without touching your code. CLight keeps one small JSON file per mod in the game’s config folder.
Defining options
Open the config once, keep it in your mod, and define each option with a default:
class ConfigMod final : public clight::Mod {
public:
void on_construct() override {
auto cfg = clight::config::open(CL_CONFIG_COMMON);
if (!cfg) {
clight::log::error("no config: {}", cfg.error());
return;
}
cfg_ = *cfg;
cfg_.define_bool("welcome.enabled", true);
cfg_.define_str("welcome.text", "Welcome to the server!");
cfg_.define_i32("rubies.on_join", 1, 0, 64);
cfg_.define_f64("rubies.chance", 0.25, 0.0, 1.0);
}
private:
clight::Config cfg_;
};
The first time, this creates config/my_mod.json in the Celestial folder:
{
"rubies.chance": 0.25,
"rubies.on_join": 1,
"welcome.enabled": true,
"welcome.text": "Welcome to the server!"
}
The names are paths with dots, so related options sort together. Defining an option that is already in the file keeps the player’s value; only missing options get the default and are written to the file straight away. A value in the file that is the wrong type or outside min and max is replaced by the default.
| Method | Option type |
|---|---|
cfg.define_bool(path, def) |
true or false |
cfg.define_i32(path, def, min, max) |
A whole number between min and max |
cfg.define_f64(path, def, min, max) |
A number between min and max |
cfg.define_str(path, def) |
A string |
Reading options
void on_player_join(clight::Config cfg, clight::Entity p) {
if (cfg.get_bool("welcome.enabled")) {
p.send_message(cfg.get_str("welcome.text").value_or("Welcome!"));
}
int32_t n = cfg.get_i32("rubies.on_join");
if (n > 0) p.give("my_mod:ruby", n);
}
Use the getter that matches the type: get_bool, get_i32, get_f64, and get_str, which returns an Expected<std::string>. Read the value when you need it, as here, rather than copying it once at the start. Then a changed value (below) is used straight away.
clight::Config is a borrowed handle, like clight::Entity: copying it is fine, and it doesn’t need releasing.
Changing options
cfg.set_i32("rubies.on_join", 3);
cfg.save();
set_* changes the value in memory; save() writes the file. Values outside the min and max you defined are refused with CL_E_INVALID_ARGUMENT in the Expected that set_i32 returns.
A good place for this is an admin command, for example /myconfig rubies 3 with permission level 2.
Three files
clight::config::open(scope) takes one of three scopes. Each has its own file:
| Scope | File | For |
|---|---|---|
CL_CONFIG_COMMON |
config/my_mod.json |
Most options. |
CL_CONFIG_CLIENT |
config/my_mod-client.json |
Options about this player’s screen and sound, like the HUD position. |
CL_CONFIG_SERVER |
config/my_mod-server.json |
Rules for the world, like rewards. |
The scopes only decide the file name; a server doesn’t send its config to players.
When players edit the file
CLight notices when a player edits the file (below).
Your own code can’t open the config folder with std::ifstream: the sandbox only lets a mod see its own folder (see Memory in the sandbox). The config functions are the way in.
The config screen and reloads
Players don’t have to edit the file. In the Mods screen they pick your mod and press Config: a toggle for each bool, a slider for numbers with a range, a text box for strings. The label is your language entry config.<mod id>.<path>, or the path written nicely. cfg_.comment("rubies.on_join", "Rubies every player gets when they join") adds a tooltip.
The values can change while the game runs: from that screen, when a player edits the file (CLight reads it again within about a second), with the operator command /clight reload-config, or cfg_.reload(). Getters always read the current value. To react to a change, listen to config.reloaded; it fires once per changed file:
clight::on<clight::ev::ConfigReloaded>([](cl_ev_config_reloaded& e) {
clight::log::info("{} changed", std::string_view(e.file.ptr, e.file.len));
}, {.filter = "my_mod"});
Try it
Add a config option join_message.color (a whole number, default 0x55FF55, from 0 to 0xFFFFFF) and use it as the colour of a welcome message.
Show a solution
#include <clight.hpp>
class ColorWelcomeMod final : public clight::Mod {
public:
void on_construct() override {
auto cfg = clight::config::open(CL_CONFIG_COMMON);
if (!cfg) return;
cfg_ = *cfg;
cfg_.define_i32("join_message.color", 0x55FF55, 0, 0xFFFFFF);
clight::on<clight::ev::PlayerJoin>([this](cl_ev_player& e) {
clight::Entity p = e.player;
clight::Text msg = clight::literal("Welcome, " + p.name() + "!");
msg.color(static_cast<cl_color>(cfg_.get_i32("join_message.color")));
p.send_message(msg);
}).detach();
}
private:
clight::Config cfg_;
};
CLIGHT_MOD(ColorWelcomeMod)
In the file the colour appears as a plain number (5635925 for 0x55FF55), because JSON has no hex numbers. Players can write any colour as a decimal number there.
Recap: clight::config::open(CL_CONFIG_COMMON) opens your mod’s config file, define_* sets options with defaults and limits, get_* reads them, and set_* plus save() changes them.