Manual › Scripting API › Bots
Bots
Bot base classes #
All bots extend AbstractBot (usually via LoopingBot, TaskBot, or
TreeBot).
Lifecycle hooks #
abstract class AbstractBot {
loopDelay: number; // legacy pacing; 600 = one server tick (see loopCadence)
loopCadence: LoopCadence | null; // optional explicit frame | server-tick | time
readonly settings: SettingsBag; // resolved run parameters
onStart?(): void | Promise<void>; // before the first loop
onStop?(): void; // after stop AND after a crash — clean up here
onPause?(): void;
onResume?(): void;
onPaint?(ctx: CanvasRenderingContext2D): void; // overlay HUD, every redraw
recoveryAnchor?(): Tile | null; // watchdog recovery walks back here when the bot is 8+ tiles away
grindTargets(): string[]; // NPC names this bot fights on purpose, handed to the random-event detector
ignoredRandoms(): string[]; // random-event names this bot never pauses for, re-read each detect
log(msg: string): void;
on<K>(event, cb): void; // event subscription, auto-removed on stop; public so a task can subscribe for its bot
}
onStopruns on both a clean stop and a crash, release resources here.onPaintdraws the overlay HUD; its widgets are in Paint.- Event callbacks (
this.on) fire mid-frame: set flags orlog, and keep the work inloop().
LoopingBot #
The common case: implement loop(). Return a number to override pacing for the
next iteration (0 = next frame, 600 = next server tick, other = wall-clock ms).
Set loopCadence on the bot for an explicit { kind: 'frame' | 'server-tick' | 'time' }
policy (see #427).
abstract class LoopingBot extends AbstractBot {
abstract loop(): number | void | Promise<number | void>;
}
TaskBot #
A priority list of tasks. Each loop, the first task whose validate()
returns true has its execute() run.
interface Task {
validate(): boolean | Promise<boolean>;
execute(): void | Promise<void>;
}
abstract class TaskBot extends LoopingBot {
protected add(...tasks: Task[]): void; // usually in onStart, highest priority first
}
class Fighter extends TaskBot {
override onStart() {
this.add(
{ validate: () => Game.energy() < 20, execute: async () => { /* rest */ } },
{ validate: () => !Game.inCombat(), execute: async () => { /* attack */ } },
);
}
}
TreeBot #
A behaviour tree. Walk BranchTask.validate() from root() until a LeafTask,
then run it, once per loop.
abstract class BranchTask { validate(): boolean; success(): TreeNode; failure(): TreeNode; }
abstract class LeafTask { execute(): void | Promise<void>; }
type TreeNode = BranchTask | LeafTask;
abstract class TreeBot extends LoopingBot { abstract root(): TreeNode; }
Execution #
The only legal way to sleep. Awaiting anything else escapes the runtime. Stop can't unwind it and the watchdog warns.
Execution.delay(ms: number): Promise<void> // wall-clock
Execution.delayTicks(n: number): Promise<void> // n server ticks (~600ms each)
Execution.delayUntil(cond: () => boolean, timeoutMs = 6000): Promise<boolean>
Execution.delayUntilTicks(cond: () => boolean, maxTicks: number): Promise<boolean> // the same, bounded in server ticks
delayUntil resolves true when cond() holds (checked once per frame),
false on timeout. Use it to confirm an action landed:
const before = Inventory.used();
await item.interact('Bury');
const ok = await Execution.delayUntil(() => Inventory.used() < before, 3000);
Registering a bot #
interface BotManifestInput {
name: string;
description?: string;
version?: string;
category?: string; // filter chip in the library (e.g. "Mining")
tags?: string[]; // free-form search labels
settingsSchema?: SettingsSchema;
create(): AbstractBot;
}
function defineBot(manifest: BotManifestInput): BotManifest; // default-export this
function registerScript(manifest: BotManifestInput, origin?: string): void; // imperative
Default-export defineBot({...}) from your entry module. The URL loader calls
registerScript for you; in-tree scripts are registered from
src/bot/scripts/index.ts.