Java
API tour
Java · Updated 09 November 2026
Snapshot reads, GameAPI actions, inventory, entity queries, walking, events, settings and a custom UI.
Everything starts from the ScriptContext you receive in onStart:
| Call | Gives you |
|---|---|
ctx.getGameAPI() |
GameAPI: game state and actions |
ctx.getNavigation() |
Navigation: blocking walks |
ctx.getEventBus() |
EventBus: game events |
ctx.getScriptContext() |
a debug publisher for status annotations |
Reading state vs acting
The host keeps a snapshot of the game that is refreshed every tick. Reading from it is cheap and never waits on the game:
GameSnapshot snap = api.snapshot();
LocalPlayer lp = api.getLocalPlayer();
if (lp != null) {
log.debug("at ({}, {}, plane {}) anim={}", lp.tileX(), lp.tileY(), lp.plane(), lp.animationId());
}
Actions such as clicking an object, an NPC or an item are queued to the game through GameAPI and the entity objects it returns. They take effect on the game's next ticks, so give them time before you check the result.
Inventory
api.backpack(), api.bank() and api.equipment() return inventory containers with the usual checks:
Backpack backpack = api.backpack();
if (backpack.isFull()) {
// drop or bank
}
int logs = backpack.count(LOGS_ITEM_ID);
if (backpack.contains(LOGS_ITEM_ID)) {
backpack.interactFirst(LOGS_ITEM_ID, "Drop");
}
Containers also have freeSlots(), getItems(), and name-based versions such as contains(String) and count(String).
Finding things: entity queries
api.npcs(), api.players(), api.objects() (scenery) and api.groundItems() give you fluent queries:
SceneObject tree = api.objects().query()
.namedExact("Tree")
.withinDistance(15)
.nearest();
if (tree != null) {
tree.interact("Chop down");
}
Queries can filter with named, namedExact, withId, within, withinDistance and filter(...), and finish with nearest(), first(), all(), count() or exists(). NPCs, objects and ground items all have interact(String option).
Walking
ctx.getNavigation() walks for you. walkWorldPath(x, y, plane) plans a route across the world, walks it and blocks until it finishes. It returns a WalkResult:
WalkResult result = ctx.getNavigation().walkWorldPath(targetX, targetY, targetPlane);
switch (result) {
case ARRIVED -> log.info("Arrived at ({}, {})", targetX, targetY);
case CANCELLED -> log.warn("Walk cancelled");
case FAILED -> log.warn("Walk failed");
case TIMEOUT -> log.warn("Walk timed out");
}
walkTo(x, y) walks within the loaded area. Both have versions that take a timeout, and cancelWalk() stops a walk in progress.
Events
Subscribe in onStart to react to game events instead of polling for them:
EventBus events = ctx.getEventBus();
events.subscribe(ActionExecutedEvent.class, this::handleActionEvent);
private void handleActionEvent(ActionExecutedEvent event) {
log.debug("Action {} {} {} {}", event.actionId(), event.param1(), event.param2(), event.param3());
}
Other events include ChatMessageEvent, VarChangeEvent, VarbitChangeEvent, LoginStateChangeEvent, BreakStartedEvent and BreakEndedEvent.
Settings (live config)
Declare settings with getConfigFields(). The host draws a form for them and saves them per script:
@Override
public List<ConfigField> getConfigFields() {
return List.of(
ConfigField.intField("loopDelay", "Loop Delay (ms)", 5000),
ConfigField.boolField("verbose", "Verbose Logging", true),
ConfigField.choiceField("mode", "Operating Mode",
List.of("Passive", "Active", "Aggressive"), "Passive"));
}
@Override
public void onConfigUpdate(ScriptConfig config) {
this.loopDelay = config.getInt("loopDelay", 5000);
this.verbose = config.getBoolean("verbose", true);
this.mode = config.getString("mode", "Passive");
}
There are five kinds of field: intField, stringField, boolField, choiceField and itemIdField.
When the client is logged in to an account and the script declares settings, onConfigUpdate runs once at start, after onStart and before the first onLoop, with the saved values or the defaults. It runs again every time the user presses Apply while the script runs. Settings are saved per account, in ~/.botwithus/config/<account>/<scriptName>.json. Delete that file to go back to the defaults.
A custom UI
For more than a settings form, return a ScriptUI from getUI(). It is drawn with ImGui on the script's Script UI tab:
@Override
public ScriptUI getUI() {
return () -> {
ImGui.text("Status: running");
if (ImGui.button("Do something")) {
// handle click
}
ImGui.progressBar(progress, -1, 0, "Progress");
};
}
This needs requires imgui.binding; in module-info.java and ImGui as compileOnly (see Project setup).
render()runs every frame while the tab is showing. Keep it fast.- Everything you draw stays inside the panel. A mistake in your UI shows an error in the panel instead of taking the host down.
- Do not call OpenGL from
render(). - A
render()that never returns still freezes the host's UI.