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

  1. Run gradle build in the woodcutter folder. The JAR lands in build/libs/woodcutter.jar.
  2. Copy it to ~/.botwithus/scripts.
  3. 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.