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.
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".
| int | whole number — 151, 1_000 |
| string | text in quotes — "Rock" |
| bool | true / false |
| tile | a spot on the map — tile(303, 553) |
| npc object item… | a thing you found (read .name, .tile with a dot) |
| list | several of a thing — what a for walks over |
| bag | your own record — named fields you put in and read back; see Bags & JSON |
| none | nothing 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