Docs · The language

The language

The parts of a .bot script that are not commands — the events, the ways to make decisions and loops, the kinds of name, states, and waiting. Commands (the verbs like interact and nearestNpc) each have their own page in the reference on the left; this page is the grammar that holds them together.

Events — when your code runs

A script does not run top to bottom. You hang code off events, and the engine calls them at the right moment. A script has either one on loop or some states — never both — plus any of the others.

on start

Runs once, before anything else. Set up variables, ask each x() guard once, print a hello.

on loop

The heartbeat. Runs, ends, and runs again — it does not loop by itself. End it with return <ms> to say how long to wait before the next pass; return 0 comes straight back.

on stop

Runs once when the script is stopped. Print a total, tidy up. It does not run when you close the client window — that shuts the process down without unloading scripts, so press stop first if the script has something to say.

on message(line, from)

Fires the instant a game line arrives, between passes, so a flag it sets is already true on the very next on loop. from is the sender for a private message, "" for a public or system line. One per script.

on render — drawing the overlay

A draw-only event, pumped about 30 times a second while the loop idles, where a script paints a HUD over the game. It is big enough to have its own page: Drawing & paint.

Reacting to what the game says
var resting = false

# runs the moment a line arrives, between passes
on message(line, from) {
    if contains(lower(line), "you are tired") { resting = true }
    if contains(lower(line), ", well done")   { resting = false }
}

on loop {
    if resting { return 1000 }
    # ... do the work ...
    return 600
}

Fold the line with lower() and test it with contains() — a message is rarely the exact string you would compare with ==. If you would rather ask than react, lastServerMessage() reads the most recent line and is legal inside a wait until.

Kinds of value

Every value has a kind and the engine never quietly converts. 151 and "151" are not the same — quotes mean a name, so bankWithdraw("500", 10) looks for an item named "500".

intwhole number — 151, 1_000
stringtext in quotes — "Rock"
booltrue / false
tilea spot on the map — tile(303, 553)
npc object item…a thing you found (read .name, .tile with a dot)
listseveral of a thing — what a for walks over
bagyour own record — named fields you put in and read back; see Bags & JSON
nonenothing was found. A type written with ? can be none, and the checker will not let you use it until you have tested it

Read a property with a dot and no brackets: rock.tile.x. Anything that needs a second value is a command instead: distance(a, b), not a.distanceTo(b). A handle's properties are the reading taken when you found it, not a fresh one — find things again inside the loop that uses them.

bag — the script's own record

The closest thing the language has to a class. newBag() makes one; bagPut(b, "hp", 30) gives it named fields (numbers, texts, flags, tiles, nested bags, and lists of any of those); typed reads take them back — bagNumber(b, "hp") is an int? (none when missing or a different type), bagNumberOr(b, "hp", 0) an int. Bags are mutable and compared by identity (== asks "same bag"), unlike lists, which stay immutable. A list<bag> is a table — all the usual list commands work on it, and bag can be written as a type: var rows = bagList(), fun f(bag b) -> int, list<bag> in a fun's signature. The whole story — tables, queries, saving, JSON — is on Bags & JSON.

Decisions & loops

if invIsFull() {
    become banking
} else if invCount() > 20 {
    print("nearly there")
}

for plant in objectsByDistance(name: "Flax", within: 6) {
    if interact(plant, "Pick") { return 900 }
}

if needs a yes/no answer — if invCount() > 0, not if invCount().

for x in <list> walks a list once — the items a query like objectsByDistance(...) returns, nearest first.

Inside a block, return <ms> ends the whole pass right there; stop ends the script.

Waiting

Two ways to pause. wait <ms> sleeps for a fixed time. wait until <condition> sleeps until something becomes true — and always needs a timeout, because an untimed one is the single loop that can never finish. Only reading commands may appear in the condition; an acting command (one marked does something) is refused there.

wait 600                                  # a fixed pause

wait until bankIsOpen() timeout 6000 else {
    print("the bank did not open")
    return 1000
}

Names — var, let, setting, fun

var and let

var survives between passes — trip counters, flags. let lives for one pass and is gone at its end. Nothing is created just by writing a name; declare it first.

var trips = 0
let rock = nearestObject(id: 102, within: 6)

setting

A dial the person running the bot can turn — it shows up on the settings panel and reading it in the script gives its value.

setting int rockId = 151 "Which rock"
setting bool bank = true "Bank the ore"

fun — your own commands

Give a piece of logic a name. A fun may take arguments and may return a value with -> type. Call it like any command.

fun bagIsFull() -> bool {
    return invFree() == 0
}

on loop {
    if bagIsFull() { become banking }
    return 600
}

States — two jobs, one bot

A mining bot mines, then banks. Give each job a name with state. The first state is where it starts; become <name> says which runs next. A script has either one on loop or some states, never both.

state mining {
    if invIsFull() { become banking }
    let rock = nearestObject(id: 102, within: 6)
    if rock == none { return 1000 }
    interact(rock, "Mine")
    return 900
}

state banking {
    if invCount() == 0 { become mining }
    # ... walk to the bank and deposit ...
    return 1000
}

Enums — your own set of names

A script can declare its own closed set of names and use it as a type. Each enum is a type of its own: a member never equals a string or a member of another enum, and the checker refuses the comparison. A setting with an enum type appears as a dropdown of the members.

enum Mode { POWER, BANK, SELL }

setting Mode mode = Mode.POWER "What to do with ores"

if mode == Mode.BANK { become banking }
print(mode)        # Mode.POWER
print(mode.name)   # POWER