尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Start here to write a script for the Project X client. xclient.dev
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Scripts are RuneLite plugins compiled against the Project X client.
You write them with one import, net.runelite.client.plugins.projectx.bot.*, which gives short names
like Inventory, Bank, Walker, Objects and Npcs:
var rock = Objects.query().nameEquals("Copper rocks").nearestReachable();
if (rock != null) rock.interact("Mine");
Npcs.query("Cow").nearest().interact("Attack");
The full guide is docs/SCRIPTING.md
in the client repo. This repository is a complete, small script you can rename and build on. It works on an object
(a tree, a rock) until the inventory is full, banks at the nearest bank, walks back and carries
on, and it shows the parts every store script should have:
reliability(), so it stops itself
cleanly instead of clicking forever when something goes wrong;ScriptPanel, SkillTracker) every Project X script uses;Loadout.Your source stays yours. The store needs your source to review a build, but only the jar is ever given to players.
Full documentation, including an API cookbook and the common traps, is at xclient.dev/osrs/developer/docs.
You need a JDK 11 installed (for example Temurin 11). Gradle uses it for the build whatever your default Java is, so it is fine to have newer ones too.
git clone https://github.com/iEasyScript/xclient-script-template.git
cd xclient-script-template
./gradlew build
That produces two files:
| File | What it is |
|---|---|
build/libs/TemplatePlugin.jar | The script. This is what you upload |
build/distributions/TemplatePlugin-source.zip | Its source, which the store asks for alongside the jar |
src/main/java/net/runelite/client/plugins/projectx/templateplugin/
TemplatePlugin.java lifecycle and wiring; the @PluginDescriptor is your script's identity
TemplateScript.java the loop, on a background thread: states, guarded steps, recovery
TemplateConfig.java the settings the player can change, including the inventory setup
TemplateOverlay.java the stats panel
build.gradle finds the client jar, builds the jar and the source zip
gradle.properties pluginName and the client version to compile against
To make it yours:
MyCoolPlugin.pluginName in gradle.properties to the new plugin class name. The jar is named after
it, and the client finds your script by it.TemplateConfig.GROUP to something unique to your script.The build compiles against the Project X client jar. It finds one in this order:
-PprojectxClientPath=/path/to/projectx-1.0.79.jar, if you pass it;~/.projectx/projectx-<version>.jar
(%USERPROFILE%\.projectx\ on Windows);projectx-<version>.jar from the
client's GitHub release.<version> is projectxClientVersion in gradle.properties. The first line of the build says
which jar it used.
Always build against the client players are running. Item, object and widget ids are Java
constants, and the compiler copies their values into your classes. A jar built against an old
client keeps the old values even when it runs on a new one, which is how a script ends up
clicking the wrong widget after a game update. When a new client is released, bump
projectxClientVersion, rebuild, and upload a new version.
You do not have to publish to try your script. Copy the jar into
%USERPROFILE%\.runelite\projectx-plugins\dev\
(~/.runelite/projectx-plugins/dev/ elsewhere) and restart the client. It loads from there and
stays loaded. Needs client 1.0.77 or later. Since 1.0.78 the My Scripts window has a Store tab
and a Local tab, and your dev-folder scripts show up under Local.
projectx-plugins/: the client keeps only scripts the
store knows about there, and switches the rest off shortly after start-up.Also worth doing:
decide() in
the template), keep each state's method short, and log state changes. The session log is in
~/.runelite/logs/projectx/../gradlew build.build/libs/<pluginName>.jar and
build/distributions/<pluginName>-source.zip.version in your @PluginDescriptor. Each version can be uploaded
once, so bump it for every build.A reviewer reads the source, then approves or rejects with a note. For later versions, use Upload new version on your script's page. The portal explains pricing and payouts in full.
They are easy to mix up, and mixing them up stops your script loading.
| What it is | Now | Where it goes | |
|---|---|---|---|
| Client release | What the client is called | 1.0.79 | projectxClientVersion in gradle.properties |
| Plugin API | What the client offers scripts | 2.7.0 | minClientVersion in your @PluginDescriptor |
minClientVersion is the oldest plugin API your script needs. A client that offers less will not
load the script (the log says Plugin X needs plugin API 2.7.0 or higher), and the plugin hub
will not install it there. Set it to the API level that added the newest thing you use:
| Plugin API | Client | Added |
|---|---|---|
2.6.25 | 1.0.31 | ScriptPanel, ScriptPanelOverlay, SkillTracker |
2.6.29 | 1.0.47 | ProjectX.withDirectInput, Prayers.flick / flickQuickPrayer |
2.6.30 | 1.0.49 | BoatNavigator; Walker.walkTo sails to targets at sea |
2.6.31 | 1.0.56 | Script.reliability() and the util.reliability package |
2.7.0 | 1.0.79 | The simple API: the bot package (Inventory, Objects.query(), Loadout, ...) |
The template uses the simple API, so it declares 2.7.0. Putting a client release number such as
1.0.79 there is wrong: as an API level it means "anything", so nothing is checked. isExternal = true
must be set for the check to apply at all.
Threads. Three threads matter, and each has one job.
| Thread | Runs | Rule |
|---|---|---|
| Your script's executor | the loop in run() | The only place to click, walk or wait |
| Client thread | overlays, @Subscribe event handlers, clientThread.invoke(...) | Read and return. Never click, sleep or wait |
| Swing UI thread | startUp(), shutDown(), the config panel | Start or stop the loop and return |
sleepUntil after it is measuring the wrong moment and the action looks like
it failed. Search and click from the script thread, as the loop does:
var tree = Objects.query("Tree").nearest(); // the search hops threads for you
if (tree != null) tree.interact("Chop down"); // click from the script thread
ProjectX.getClientThread().runOnClientThreadOptional(() -> ...). The helpers (Inventory, Player,
Bank and the rest) already do this for you. Avoid clientThread.invoke(() -> someBoolean()): a lambda that returns a
boolean picks the overload that re-runs it every tick until it returns true, and returns
nothing.sleepUntil(() -> condition, timeoutMs).shutdown(). Call super.shutdown(), then clear your own state, so the next
start is clean.build() and render() cheap. They run every frame. No waiting, no clicking, no
network calls (such as GrandExchange.getPrice).Bank.withdrawX(name, amount) only matches a bank stack holding at least amount. Ask
for 28 when the bank has 20 and nothing happens. Use Math.min(amount, Bank.count(name, true)).Inventory.hasItem("axe") matches a pickaxe
(partial), while Inventory.contains("axe") does not (exact). Most name methods have an
overload with a boolean exact parameter: pass true unless you mean "contains".Inventory.count(name) counts slots, not a stack. Use itemQuantity(name) for coins,
runes and other stackables.SkillTracker resets whenever the start time you pass changes. Store
System.currentTimeMillis() once when the script starts and pass that; never now - runtime.ProjectX.status themselves (opening the bank, clicking an object). Keep your
own status field for the stats panel, as the template does.Rs2* names still work but are deprecated. Use the short names: Objects.query(),
Npcs.query(), Inventory, Bank. The full old-to-new table is in the client repo's
docs/api-migration.tsv.net.runelite.api.gameval.InterfaceID and ItemID, never raw
numbers. Game updates move widgets; the named ids move with them when you rebuild.BSD 2-Clause, like the client. Do what you like with this example, including keeping your own script closed source.