Lua
Lifecycle, pacing and settings
Lua · Updated 09 November 2026
on_loop returns server ticks, not milliseconds. Settings fields and stopping.
The lifecycle
on_config_update(ctx, config)runs with the initial settings, if you define it.on_start(ctx)runs once.on_loop(ctx)runs repeatedly.on_stop(ctx)runs once at the end.
Only on_loop is required. If on_config_update raises, neither on_start nor on_stop runs.
on_loop returns server ticks
This is the biggest difference from Python and Java. In Lua,
on_loopreturns a delay in server ticks (about 0.6 seconds each), not milliseconds. A negative value stops the script.
| Language | return 5 from on_loop means |
|---|---|
| Lua | wait 5 server ticks, about 3 seconds |
| Python | wait 5 milliseconds |
| Java | wait 5 milliseconds |
Porting a Python or Java script? Divide its millisecond delays by 600.
The API gives you ctx:sleep_ticks(n) rather than a millisecond timer, on purpose. server_tick is the only clock to pace on. game_cycle and publish_seq are exposed but are not timers, and pacing off them makes a script run about 30 times too fast.
ctx:sleep_ticks(n) returns true when the ticks have passed and false when the script is stopping. If the server tick stalls (in the lobby, or with a frozen client) it keeps waiting.
Settings
Declare settings as config_fields. The GUI shows them as a form:
local config = require("botwithus.config")
script.config_fields = {
config.int_field("logs", "Logs per trip", 28, { min = 1, max = 28 }),
config.choice_field("tree", "Tree", { "Tree", "Oak", "Willow" }, "Oak"),
config.bool_field("bank", "Bank the logs", true),
config.string_field("note", "Note", "", { max_len = 64 }),
config.item_id_field("food", "Food"),
}
function script.on_config_update(ctx, cfg)
end
cfgholds every field, for example{ logs = 28, tree = "Oak", bank = true, note = "", food = -1 }.on_config_updateruns once beforeon_start, and again between loops whenever the user changes a setting.ctx.configalways holds the settings in force.item_id_fielddefaults to-1, meaning "no item".- A script is refused before it runs if a
choicedefault is not one of its options, achoicehas no options, anitem_iddefault is below-1, or astringdefault is over 256 bytes without a largermax_len. - On the command line the script gets the defaults.
Stopping
When the GUI stops a run:
ctx:stopping()turnstrue;ctx:sleep_ticks(n)returnsfalseat once;- no further
on_loopruns; on_stopruns.
A script stuck in a busy loop of its own does not see the stop, and after 10 seconds the GUI ends the process. So wait with ctx:sleep_ticks, and check ctx:stopping() inside any long loop. A blocking walk (ctx.game:walk(...)) also ends early on a stop, with its normal "cancelled" result.
When the game client goes away, the run ends on its own: no further on_loop, then on_stop. In on_stop the game reads still return the last state, so you can log where the character was, but actions no longer reach the client.
Next: Modules, require and walking.