BotWorld2004 Scripting API
Main menu - Bot Guide - Run Bot - Home

BotWorld2004 Scripting API

Bots are written in TypeScript against @rs2b0t/api, the stable scripting surface used by the BotWorld2004 web client. This page is the complete practical reference.

Every bot extends a base class and is registered with defineBot. Scripts run inside the real client. Always await interactions and verify the result using Execution.delayUntil.

Getting started

Create an in-tree script under src/bot/scripts/, or copy the script template for an external project. The entry module default-exports defineBot({...}).

import { defineBot, Execution, Game, LoopingBot } from '@rs2b0t/api';

class MyBot extends LoopingBot {
  override async onStart() {
    await Execution.delayUntil(() => Game.ingame(), 0);
    this.log('BotWorld2004 bot started');
  }
  async loop() {
    // one iteration of work
    await Execution.delayTicks(1);
  }
}

export default defineBot({ name: 'MyBot', create: () => new MyBot() });

Register in-tree scripts from src/bot/scripts/index.ts. External builds can be loaded through the client panel's Load URL control.

Bot base classes and lifecycle

Most scripts extend LoopingBot, TaskBot or TreeBot, all based on AbstractBot.

abstract class AbstractBot {
  loopDelay: number;
  readonly settings: SettingsBag;
  onStart?(): void | Promise<void>;
  onStop?(): void;
  onPause?(): void;
  onResume?(): void;
  onPaint?(ctx: CanvasRenderingContext2D): void;
  log(message: string): void;
  protected on<K>(event, callback): void;
}

onStop runs after a normal stop and after a crash. Event handlers are automatically removed. Event callbacks should set flags; perform interactions in loop().

LoopingBot

abstract class LoopingBot extends AbstractBot {
  abstract loop(): number | void | Promise<number | void>;
}

TaskBot

Each loop executes the first task whose validate() returns true. Add highest-priority tasks first.

interface Task {
  validate(): boolean | Promise<boolean>;
  execute(): void | Promise<void>;
}
abstract class TaskBot extends LoopingBot {
  protected add(...tasks: Task[]): void;
}

TreeBot

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

Execution is the supported way to wait. It allows Stop and Pause to unwind the bot correctly.

Execution.delay(ms: number): Promise<void>
Execution.delayTicks(n: number): Promise<void>
Execution.delayUntil(condition: () => boolean, timeoutMs = 6000): Promise<boolean>
const before = Inventory.used();
await item.interact('Bury');
const completed = await Execution.delayUntil(() => Inventory.used() < before, 3000);

Game state

MethodResult
Game.ingame()Whether the player is logged in and the scene is ready.
Game.tile()Local player's WorldTile, or null before login.
Game.energy()Run energy.
Game.weight()Carried weight.
Game.inCombat()Whether the player's combat health bar is showing.
Game.tick()Server ticks since client boot.

Entities and queries

Npcs.query(): EntityQuery<Npc>
Players.query(): EntityQuery<Player>
Locs.query(): EntityQuery<Loc>
GroundItems.query(): EntityQuery<GroundItem>

Queries can be chained using name(...), action(...), within(distance), inside(area) and where(predicate). Finish with results(), nearest(), first(), exists() or count().

const guard = Npcs.query().name('Guard').action('Pickpocket').within(3).nearest();
const oak = Locs.query().name('Oak').within(6).nearest();
const coins = GroundItems.query().name('Coins').within(12).nearest();

Entities expose a tile and distance. Interactive entities provide actions() and interact(action). Interaction does not automatically walk to distant targets—walk into range first.

Inventory and equipment

Inventory.items(): InvItem[]
Inventory.first(name: string): InvItem | null
Inventory.contains(name: string): boolean
Inventory.used(): number
Inventory.isFull(): boolean
Equipment.items(): InvItem[]
Equipment.contains(name: string): boolean

An InvItem exposes name, id, slot, count, actions(), interact(action) and useOn(target).

const raw = Inventory.first('Raw shrimps');
const range = Locs.query().name('Range').within(3).nearest();
if (raw && range) await raw.useOn(range);

Bank

Bank.isOpen(): boolean
Bank.items(): BankItemSnapshot[]
Bank.count(name: string): number
Bank.withdraw(name: string, operation?: string): boolean | Promise<boolean>
Bank.deposit(name: string, operation?: string): boolean | Promise<boolean>
Bank.depositInventory(): Promise<void>

Open the bank by interacting with a banker or booth first. Names match exactly. Operations are visible in each item's ops, such as Withdraw-10 or Withdraw-All.

Skills

Skills.index(name: string): number
Skills.level(name: string): number
Skills.effective(name: string): number
Skills.xp(name: string): number

Chat dialog

ChatDialog.isOpen(): boolean
ChatDialog.canContinue(): boolean
ChatDialog.continue(): Promise<boolean>
ChatDialog.options(): string[]
ChatDialog.chooseOption(match?: string): Promise<boolean>
ChatDialog.isMakeMenu(): boolean
ChatDialog.makeProducts(): string[]
ChatDialog.make(match?: string): Promise<boolean>

Movement

Traversal.walkTo(destination: WorldTile, options?: {
  radius?: number;
  timeoutMs?: number;
  log?: (message: string) => void;
}): Promise<boolean>
Traversal.preload(): void
Traversal.remaining(): number

Traversal.walkTo uses world pathfinding, door and transport links, and stuck recovery. Unwalkable targets snap to a nearby reachable tile.

const reached = await Traversal.walkTo(
  { x: 2662, z: 3305, level: 0 },
  { radius: 0, log: message => this.log(message) }
);

Events

Subscribe with this.on() from a bot. Supported events include tick, chat.message, skill.xp, skill.level, inventory.changed and varp.changed.

this.on('skill.xp', event => {
  if (event.name === 'prayer') this.xpGained += event.delta;
});

Settings

A settings schema becomes a form in the bot panel. Access resolved values through this.settings.

export default defineBot({
  name: 'Miner',
  settingsSchema: {
    rock: { type: 'string', default: 'Copper rocks', label: 'Rock' },
    world: { type: 'boolean', default: true, label: 'World-hop when crowded' }
  },
  create: () => new Miner()
});

const rock = this.settings.str('rock', 'Copper rocks');

Setting types are boolean, number, string, string[] and tile. Readers are bool, num, str, list, tile and raw.

World primitives

interface WorldTile { x: number; z: number; level: number; }
class Tile {
  constructor(x: number, z: number, level?: number);
  static from(tile: WorldTile): Tile;
  distanceTo(other: WorldTile): number;
  translate(dx: number, dz: number): Tile;
  equals(other: WorldTile): boolean;
}
abstract class Area {
  static rectangular(a: WorldTile, b: WorldTile): Area;
  static circular(center: WorldTile, radius: number): Area;
  contains(tile: WorldTile): boolean;
  getRandomTile(): Tile;
}

Item acquisition

type ItemNeed = { name: string; count: number; source: ItemSource };
held(name: string): number
hasAll(needs: ItemNeed[]): boolean
class AcquireTask implements Task {
  constructor(bot, needs: ItemNeed[]);
}

Add AcquireTask near the top of a TaskBot's priority list to gather, buy or withdraw required items.

Registering a bot

interface BotManifestInput {
  name: string;
  description?: string;
  version?: string;
  category?: string;
  tags?: string[];
  settingsSchema?: SettingsSchema;
  create(): AbstractBot;
}
defineBot(manifest: BotManifestInput): BotManifest
registerScript(manifest: BotManifestInput, origin?: string): void

Full example

This BotWorld2004 example loots and buries nearby bones, confirms changes, tracks XP and draws a small overlay.

import { defineBot, Execution, Game, GroundItems, Inventory, LoopingBot } from '@rs2b0t/api';

class BoneBurier extends LoopingBot {
  private buried = 0;
  private xpGained = 0;

  override async onStart(): Promise<void> {
    await Execution.delayUntil(() => Game.ingame(), 0);
    this.log('BotWorld2004 BoneBurier started');
    this.on('skill.xp', e => {
      if (e.name === 'prayer') this.xpGained += e.delta;
    });
  }

  async loop(): Promise<void> {
    const bones = Inventory.first('Bones');
    if (bones) {
      const before = Inventory.used();
      await bones.interact('Bury');
      if (await Execution.delayUntil(() => Inventory.used() < before, 3000)) this.buried++;
      return;
    }
    const ground = GroundItems.query().name('Bones').within(10).nearest();
    if (ground && !Inventory.isFull()) {
      const before = Inventory.used();
      await ground.interact('Take');
      await Execution.delayUntil(() => Inventory.used() > before, 5000);
      return;
    }
    await Execution.delayTicks(2);
  }

  override onStop(): void {
    this.log(`stopped — ${this.buried} buried, +${this.xpGained} prayer xp`);
  }

  override onPaint(ctx: CanvasRenderingContext2D): void {
    ctx.font = '12px monospace';
    ctx.fillStyle = '#ffb15b';
    ctx.fillText(`BoneBurier: ${this.buried} buried`, 12, 22);
  }
}

export default defineBot({
  name: 'BoneBurier', version: '1.0.0',
  description: 'Loots and buries nearby bones in BotWorld2004',
  create: () => new BoneBurier()
});

Back to top   |   Return to Bot Guide   |   Open Bot Client