Skip to content

Config

Lesson 13.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 and define each option with a default:

local config = cl.config.open("common")

config:define_bool("welcome.enabled", true)
config:define_str("welcome.text", "Welcome to the server!")
config:define_i32("rubies.on_join", 1, 0, 64)
config:define_f64("rubies.chance", 0.25, 0.0, 1.0)

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.

Function Option type
config:define_bool(path, default) true or false
config:define_i32(path, default, min, max) A whole number between min and max
config:define_f64(path, default, min, max) A number between min and max
config:define_str(path, default) A string

Reading options

cl.on("player.join", function(event)
  if config:get_bool("welcome.enabled") then
    event.player:send_message(config:get_str("welcome.text"))
  end
  local n = config:get_i32("rubies.on_join")
  if n > 0 then
    event.player:give("my_mod:ruby", n)
  end
end)

Use the getter that matches the type: get_bool, get_i32, get_f64, get_str. Read the value when you need it, as here, rather than once at the start. Then a changed value (below) is used straight away.

Changing options

config:set_i32("rubies.on_join", 3)
config:save()

set_* changes the value in memory; save() writes the file. Values outside the min and max you defined are refused with an invalid_argument error.

A good place for this is an admin command, for example /myconfig rubies 3.

Three files

cl.config.open(scope) takes one of three scopes. Each has its own file:

Scope File For
"common" config/my_mod.json Most options.
"client" config/my_mod-client.json Options about this player’s screen and sound, like HUD position.
"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.

mod.config

mod.config is the common file read into a plain Lua table when your mod loads:

print(mod.config["welcome.text"])   --> Welcome to the server!

It is a snapshot from load time, and the keys are the full dotted paths. It is handy for a quick look, but cl.config is the better tool: it knows defaults, limits and saving.

Help text for players

config:comment(path, text) adds a short explanation to an option. Players see it as the tooltip in the config screen (below):

config:define_i32("rubies.on_join", 1, 0, 64)
config:comment("rubies.on_join", "Rubies every player gets when they join")

The config screen

Players don’t have to edit the file. In the Mods screen (from the pause menu or the options), they pick your mod and press Config. The screen is made from your definitions:

Option Control
define_bool An ON / OFF button
define_i32 and define_f64 A slider between min and max
define_str A text box

The label is your language entry config.<mod id>.<path> (for example "config.my_mod.rubies.on_join": "Rubies on join"), or the path written nicely (“Rubies On Join”) if there is none. Options from the client and server files get their own headings. Changes are checked against your limits and saved when the player presses Done.

When the file changes

A player may change an option while the game runs: in the config screen, by editing the file, or with the operator command /clight reload-config. Your mod sees new values right away, because the getters read the current value. CLight notices an edited file within about a second, and a hot reload of your script reads the file again too.

If your mod has to react, for example to rebuild something from the values, use config:on_reload:

config:on_reload(function(event)
  cl.log("%s changed, rewards are now %d", event.file, config:get_i32("rubies.on_join"))
end)

event.file is the file name, like "my_mod.json". A value the player wrote that has the wrong type or is out of range is replaced by your default, like when the file is first read. The handler only runs when a value really changed.

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
local config = cl.config.open("common")
config:define_i32("join_message.color", 0x55FF55, 0, 0xFFFFFF)

cl.on("player.join", function(event)
  local msg = cl.text.literal("Welcome, " .. event.player.name .. "!")
  msg:color(config:get_i32("join_message.color"))
  event.player:send_message(msg)
end)

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: cl.config.open("common") opens your mod’s config file, define_* sets options with defaults, get_* reads them, and set_* plus save() changes them. Players can change options in the Mods screen, and config:on_reload tells you when they did.