Wiki home
Java
A complete example
Java · Updated 09 November 2026
Every file of a small working script project, ready to build.
This is a whole project you can copy, build and drop into the host. The script chops the nearest tree until the backpack is full, then drops the logs and starts again.
woodcutter/
├── settings.gradle.kts
├── build.gradle.kts
└── src/main/
├── java/
│ ├── module-info.java
│ └── my/woodcutter/WoodcutterScript.java
└── resources/META-INF/services/com.botwithus.bot.api.BotScript
settings.gradle.kts
rootProject.name = "woodcutter"
build.gradle.kts
plugins {
java
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
repositories {
mavenCentral()
maven { url = uri("https://botwithus.github.io/maven") }
}
dependencies {
compileOnly("com.botwithus:bot-api:1.5.0")
}
src/main/java/module-info.java
module my.woodcutter {
requires com.botwithus.bot.api;
provides com.botwithus.bot.api.BotScript with my.woodcutter.WoodcutterScript;
}
src/main/java/my/woodcutter/WoodcutterScript.java
package my.woodcutter;
import com.botwithus.bot.api.BotScript;
import com.botwithus.bot.api.GameAPI;
import com.botwithus.bot.api.ScriptCategory;
import com.botwithus.bot.api.ScriptContext;
import com.botwithus.bot.api.ScriptManifest;
import com.botwithus.bot.api.entities.SceneObject;
import com.botwithus.bot.api.inventory.Backpack;
import com.botwithus.bot.api.snapshot.LocalPlayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ScriptManifest(
name = "Woodcutter",
version = "1.0",
author = "You",
description = "Chops trees and drops logs when full",
category = ScriptCategory.WOODCUTTING
)
public class WoodcutterScript implements BotScript {
private static final Logger log = LoggerFactory.getLogger(WoodcutterScript.class);
private static final int LOGS_ITEM_ID = 1511;
private static final int CHOPPING_DISTANCE_TILES = 15;
private static final int LOOP_AFTER_CHOP_MS = 1200;
private static final int LOOP_WAITING_MS = 600;
private static final int LOOP_STATE_TRANSITION_MS = 300;
private static final int LOOP_DROPPING_MS = 800;
private static final int LOOP_ON_ERROR_MS = 5000;
private enum State { CHOPPING, DROPPING }
private GameAPI api;
private Backpack backpack;
private State state;
@Override
public void onStart(ScriptContext ctx) {
this.api = ctx.getGameAPI();
this.backpack = api.backpack();
this.state = State.CHOPPING;
log.info("Started!");
}
@Override
public int onLoop() {
try {
return switch (state) {
case CHOPPING -> handleChopping();
case DROPPING -> handleDropping();
};
} catch (RuntimeException e) {
log.error("Error in onLoop", e);
return LOOP_ON_ERROR_MS;
}
}
private int handleChopping() {
if (backpack.isFull()) {
log.info("Inventory full, switching to dropping.");
state = State.DROPPING;
return LOOP_STATE_TRANSITION_MS;
}
if (isAnimating()) {
return LOOP_WAITING_MS;
}
SceneObject tree = api.objects().query()
.namedExact("Tree")
.withinDistance(CHOPPING_DISTANCE_TILES)
.nearest();
if (tree == null) {
log.warn("No tree found within {} tiles.", CHOPPING_DISTANCE_TILES);
return LOOP_WAITING_MS;
}
tree.interact("Chop down");
return LOOP_AFTER_CHOP_MS;
}
private int handleDropping() {
if (!backpack.contains(LOGS_ITEM_ID)) {
log.info("No logs remaining, switching to chopping.");
state = State.CHOPPING;
return LOOP_STATE_TRANSITION_MS;
}
backpack.interactFirst(LOGS_ITEM_ID, "Drop");
return LOOP_DROPPING_MS;
}
private boolean isAnimating() {
LocalPlayer lp = api.getLocalPlayer();
return lp != null && lp.animationId() != -1;
}
@Override
public void onStop() {
log.info("Stopped.");
}
}
src/main/resources/META-INF/services/com.botwithus.bot.api.BotScript
my.woodcutter.WoodcutterScript
Build and run
- Run
gradle buildin thewoodcutterfolder. The JAR lands inbuild/libs/woodcutter.jar. - Copy it to
~/.botwithus/scripts. - In the Java host, reload the scripts, then start Woodcutter on a client standing near some trees.
interactFirst(id, "Drop") clicks the option by its name, so it works wherever Drop sits in the item's menu.
Next: Anatomy of a script.