Python
API tour
Python · Updated 09 November 2026
The context, game state, actions, inventories, entity queries, walking and events.
This tour builds on the framework's example script, which reports state, counts nearby NPCs and walks into Lumbridge Castle, going through its door on the way:
from botwithus.api import (
BotScript, Npcs, ScriptContext, Tile, bool_field, int_field, script_manifest)
@script_manifest(name="Example", version="1.1", author="botwithus",
description="Reports state and walks into Lumbridge Castle.", category="Other")
class Example(BotScript):
GOAL = Tile(3208, 3220, 0)
def get_config_fields(self):
return [
int_field("radius", "NPC count radius", 8, min=1, max=32,
description="How far around the player to count NPCs, in tiles"),
bool_field("walk", "Walk into the castle", True),
]
def on_config_update(self, config: dict) -> None:
self.radius = config["radius"]
self.walk = config["walk"]
def on_start(self, ctx: ScriptContext) -> None:
self.ctx = ctx
self.arrived = False
def on_loop(self) -> int:
snap = self.ctx.snapshot()
me = snap.self_player
if not me.has_tile:
return 1000
near = Npcs(snap).within(me.tile, self.radius).count()
self.ctx.log.info("tick=%s tile=%s npcs<=%s=%s", snap.server_tick, me.tile,
self.radius, near)
nav = self.ctx.navigation
if self.walk and nav is not None and not self.arrived:
self.arrived = nav.walk(self.GOAL, radius=2)
if self.arrived:
self.ctx.log.info("arrived at goal")
if not self.ctx.sleep_ticks(1):
return -1
return 0
def on_stop(self) -> None:
self.ctx.log.info("stopped")
The context
| Attribute | What it is |
|---|---|
ctx.snapshot() |
the current game state |
ctx.self_player |
your player, from the current state |
ctx.game |
GameApi: actions and live reads |
ctx.navigation |
SceneWalker: walking (can be None if navigation is off) |
ctx.events |
EventBus: game events |
ctx.log |
the script's logger |
ctx.config |
the current settings, read-only |
Your player
me.has_tile is the check to make before anything that needs your position. A player can be valid and still have no tile for a moment, for example while the game moves it in the scene. me.tile is a Tile(x, y, plane). Other fields include animation_id, moving, combat_level and skills.
Finding things
Npcs, Players, SceneObjects and GroundItems turn the snapshot into a query:
near = Npcs(snap).within(me.tile, self.radius).count()
A query supports where(predicate), within(tile, radius), all(), count(), first() and nearest(tile). Scenery entries have a type_id, so you can pick out one kind of object with where:
tree = SceneObjects(snap).where(lambda loc: loc.type_id == TREE_ID).nearest(me.tile)
Inventories
backpack(snap), bank(snap) and equipment(snap) give a read-only view with contains(item_id), count(item_id), free_slots(), is_full(), is_empty() and first(item_id).
Acting
Actions are built by small functions in botwithus.api and queued with ctx.game.queue_action(...):
object_action(loc, slot): right-click optionslot(1 to 6) on a scene object;npc_action(npc, slot): the same on an NPC;ground_item_action(item, slot): the same on a ground item;backpack_action(slot, option): click backpack slotslot(counted from 0) with menu optionoption(counted from 1);walk_to(x, y),component_click(...),dialogue_option(...).
if tree is not None:
self.ctx.game.queue_action(object_action(tree, 1))
queue_actions([...]) sends several actions at once. The game queues all of them or none.
backpack_action has no default option on purpose. The position of "Drop" in an item's menu depends on the item.
Walking
ctx.navigation offers two styles:
walk(goal)is a blocking full walk. It plans the route, walks it and carries out its transitions (doors, stairs, teleports, dialogue), then returnsTruewhen it arrives. The example uses it, because the castle is behind a door.walk_ex(goal)does the same and returns a detailed result.walk_to(goal)plans and queues one step towardsgoal, then returns whether you have arrived. Call it once per loop. It walks only up to doors, stairs and other transitions, so it cannot get through a closed door on its own.
Both need a position to start from. Check ctx.self_player.has_tile first: walk_to and walk return False without one, and walk_ex raises.
Events
ctx.events.subscribe(EventType, handler) registers a handler and returns a function that unsubscribes it. Event types include ChatMessage, VarpChanged, VarbitChanged, ActionExecuted, ActionRefused, BreakStarted and BreakEnded.
Breaks
ctx.game.schedule_break(300) holds the action queue for 300 seconds. Fractions are allowed. ctx.game.interrupt_break() ends a break early.
Varps and varbits
Read them with ctx.game.read_varp(id) / read_varbit(id). Each read carries a state (SET, DEFAULT_NOT_SET_CLIENTSIDE, NO_SUCH_VARP, UNAVAILABLE) beside its value. Decide on the state, not the value alone: a value of -1 can mean "set to -1" or "not set". In the lobby and for the first few ticks in the world, every read is UNAVAILABLE.
Next: Logs and crash files.