Developer docs
Scripting API
2.7.0.One import
import net.runelite.client.plugins.projectx.bot.*;
SceneObject rock = Objects.query("Copper rocks").nearestReachable();
if (rock != null) {
rock.interact("Mine");
}
Npcs.query("Cow").interact("Attack");
if (Inventory.isFull()) {
Bank.depositAll();
}Rules worth knowing from day one:
- Wait with
sleepUntil(condition, timeoutMs), not a fixedsleep, when you are waiting for the game to change. - Check for
null: a search that finds nothing returns null fromnearest().Npcs.query("Cow").interact("Attack")searches and clicks in one go and returns false when there is no cow. - Don't keep entities between loops. Search again each time; the scene changes every tick.
- Object names are the in-game names: the rock is
Copper rocks, notCopper rock.
Entry points
All in net.runelite.client.plugins.projectx.bot.
| Class | What it is for |
|---|---|
Objects | Rocks, trees, doors, banks, ladders: Objects.query(...) |
Npcs | NPCs: Npcs.query(...) |
GroundItems | Items on the ground: GroundItems.query(...) |
Players | Other players (and you): Players.query(...), Players.local() |
Inventory | The items you carry |
Bank, DepositBox | Banking |
Equipment | What you wear |
Player | Your own character: health, run energy, animation, eating |
Walker, PathApi, Minimap | Walking and route finding |
Widgets, Dialogue, Tabs | Interfaces, chat dialogues, side-panel tabs |
Prayers | Prayers, plus the prayer constants (Prayers.PIETY) |
Magic, Spells, CombatSpells, Spellbooks, Staffs, Tomes, Thralls | Magic |
Combat, Pvp, Slayer, Cannon, NpcData | Combat |
GrandExchange, Shop, Items | Trading and item lookups (ids, prices) |
Keyboard, Camera | Input and camera |
Rand | Random numbers and human-like delays (Rand.between(600, 900)) |
Antiban, AntibanSettings | The built-in antiban behaviour |
Loadout | Load a saved inventory setup: new Loadout("Mining", mainScheduledFuture) |
Foods, Potions, EnsouledHeads | Item data |
GemBag, LogBasket, RunePouch, ExplorersRing | Containers and special items |
Tiles, LocalPoints, Worlds, Boats, BoatNavigator | Tiles, coordinates, worlds, sailing |
Searching: query()
Every search has three parts: where to look, filters, and a terminal that gives the answer.
SceneObject tree = Objects.query("Oak tree").nearestReachable(); // by exact name
Npc banker = Npcs.query().nameEquals("Banker").nearest(); // same, long form
FloorItem coins = GroundItems.query().withId(ItemID.COINS).within(10).first();
List<Npc> goblins = Npcs.query().withNameContains("goblin").list();
int nearby = Players.query().within(15).count();
boolean clicked = Objects.query().withIds(ROCK_IDS).interact("Mine"); // search + clickFilters (chain as many as you like): nameEquals(name), withName(name), withNames(a, b), withNameContains(text), withId(id), withIds(ids...), within(tiles), within(point, tiles), where(entity -> ...), fromWorldView(). Names compare ignoring case.
Terminals (end the search): nearest(), nearest(maxTiles), nearestReachable(), first(), firstReachable(), list(), count(), interact(action).
Name searches off the game thread. A name filter reads each entity's name from the game, one hop per entity. If you search by name a lot, the ...OnClientThread() terminals (nearestOnClientThread(), firstOnClientThread(), toListOnClientThread()) do the whole search in one hop and are much cheaper.
Npcs.stream(), Objects.stream(), GroundItems.stream() and Players.stream() give everything in the scene this tick as a Java Stream.
What a search returns
| Search | Returns | Useful methods |
|---|---|---|
Objects.query() | SceneObject | interact(action), getId(), getName(), getWorldLocation(), isReachable() |
Npcs.query() | Npc | interact(action), getHealthPercentage(), isInteractingWithPlayer(), isMoving() |
GroundItems.query() | FloorItem | pickup(), interact("Take"), getQuantity(), getTotalGeValue(), isOwned() |
Players.query() | PlayerModel | getName(), getCombatLevel(), isFriend(), getWorldLocation() |
interact(action) is the same as click(action), which also still works. It returns true when the click was sent. The names avoid clashing with RuneLite's own types: SceneObject (not GameObject), Npc (RuneLite has NPC), FloorItem, PlayerModel (RuneLite has Player).
Constants: prayers, spells, food
Prayers.toggle(Prayers.PROTECT_MELEE, true);
Magic.cast(Spells.HIGH_LEVEL_ALCHEMY);
for (Rs2Food food : Foods.values()) { ... }Prayers.PIETY is Rs2PrayerEnum.PIETY: a variable that holds one still has the old enum type (Rs2PrayerEnum, Rs2Spells, Rs2Food, ...).
Name clashes to know about
Playeris the helper for your character. RuneLite also hasnet.runelite.api.Player; if a file needs both, write RuneLite's out in full.Objectsshares its name withjava.util.Objects. Don't importjava.util.Objects;bot.Objectshas the sameequals,hash,requireNonNull, ... methods.- With
import net.runelite.api.*;orimport java.util.*;in the same file, importPlayerorObjectsby name so Java knows which one you mean.
Old names
Every old Rs2* name keeps working: scripts compiled against them run unchanged. They are marked deprecated so new code picks the short names. To move code over, rename; the methods are the same.
| Old name | New name |
|---|---|
Rs2Inventory | Inventory |
Rs2Player | Player |
Rs2Bank | Bank |
Rs2Walker | Walker |
Rs2Widget | Widgets |
Rs2Dialogue | Dialogue |
Rs2Equipment | Equipment |
Rs2Random | Rand |
Rs2GrandExchange | GrandExchange |
Rs2Keyboard | Keyboard |
Rs2Magic | Magic |
Rs2Combat | Combat |
Rs2Camera | Camera |
Rs2Tab | Tabs |
Rs2Shop | Shop |
Rs2DepositBox | DepositBox |
Rs2Potion | Potions |
Rs2Prayer | Prayers |
Rs2GameObject | Objects |
Rs2Npc | Npcs |
Rs2GroundItem | GroundItems |
Rs2InventorySetup | Loadout |
Rs2ItemManager | Items |
Rs2Tile | Tiles |
Rs2NpcModel (api.npc.models) | Npc |
Rs2TileObjectModel (api.tileobject.models) | SceneObject |
Rs2TileItemModel | FloorItem |
Rs2PlayerModel (api.player.models) | PlayerModel |
Searches move from the cache getters to the entry points: ProjectX.getRs2NpcCache().query() becomes Npcs.query(), ....query().withName("X") becomes ....query("X"), model.click(action) becomes model.interact(action), and query.toList() becomes query.list().