Python
Lifecycle, settings and stopping
Python · Updated 09 November 2026
The order of the callbacks, how to pace on server ticks, settings fields and the cooperative stop.
The lifecycle
on_config_update(config)runs once beforeon_start, with the settings in force. It runs again after each change the user commits, on the script's thread betweenon_loopcalls.on_start(ctx)runs once. Keepctx.on_loop()runs repeatedly. It returns the delay in milliseconds until the next call. A negative value stops the script.on_stop()runs once at the end.
Each script runs on a thread of its own.
Pace on server ticks
The game state has three counters, and only one of them is for pacing:
server_tick: the 0.6 second server tick. Pace against this one. It is-1until a world is loaded.game_cycle: the client's frame counter, about 20 ms. It is not a timer for scripts.publish_seq: an internal counter. Never compare it withgame_cycle.
Pacing off the wrong counter makes a script run about 30 times too fast. Use ctx.sleep_ticks(n), which waits for the server tick to move on by n:
def on_loop(self) -> int:
...
if not self.ctx.sleep_ticks(1):
return -1
return 0
sleep_ticks returns False when the script is being stopped, so return a negative value then.
Settings
Declare settings with get_config_fields(). The GUI draws them in the script's panel and saves them per script:
from botwithus.api import BotScript, choice_field, int_field, script_manifest
@script_manifest(name="Cutter", category="Woodcutting")
class Cutter(BotScript):
def get_config_fields(self):
return [int_field("logs", "Logs to cut", 28, min=1, max=1000),
choice_field("tree", "Tree", ["Oak", "Willow", "Yew"], default="Willow")]
def on_config_update(self, config):
self.logs, self.tree = config["logs"], config["tree"]
def on_loop(self) -> int:
return 600
- The field kinds are
int_field,string_field,bool_field,choice_fieldanditem_id_field. Foritem_id_field,-1means "no item". ctx.configholds the same values as a read-only mapping.- A script is refused when it is listed or started if a
choicedefault is not one of its options, achoicehas no options, anitem_iddefault is below-1, or astringdefault is longer than 256 bytes withoutmax_len=. - From a console (
bwu_host --py) the settings are their defaults.
author defaults to "" and shows as "Unknown author". category groups the script in the GUI.
Stopping: cooperative, never forced
When the GUI stops a script:
ctx.sleep(ms)andctx.sleep_ticks(n)returnFalsewithin 250 ms;- no further
on_loopruns; on_stopis called.
The whole stop must finish within 10 seconds, or the worker process is ended.
The framework never interrupts your code. A script blocked in time.sleep or in a long call of its own only sees the stop once that call returns. So:
- wait with
ctx.sleepandctx.sleep_ticks, nevertime.sleep; - check
ctx.stopping(orctx.is_stop_requested()) inside any long loop of your own; - call
ctx.stop_self()to end the script from anywhere.
If the game client exits, the run ends the same way: no further on_loop, then on_stop.
Next: API tour.