Navigation: Documentation Home โบ Server Owner Guide โบ Script Intro Related: ่ชๅฎไนGUI.md ยท ้ ็ฝฎๆไปถ.md ยท ็ฉๅๆบๅผ็จ.md ยท Developer / ่ๆฌAPI.md
๐ Script Intro (Server Owner Perspective) โ
This page teaches you how to extend QinhCoreLib (QCL) logic using JavaScript scripts. You don't need to know Java, and you don't need to build a pluginโjust drop a .js file into a directory, and you can give a GUI custom conditions and actions like "block opening if level is too low" or "click to deduct money and give an item".
๐ค Why You Need Scripts โ
QCL's GUIs and sub-plugin hooks all support "conditions" and "actions". Plain YAML config covers most common needs (permissions, items, coinsโฆ), but it falls short for needs like these:
| Need | Plain YAML | Script |
|---|---|---|
| "Only allow opening the shop at level โฅ 30" | โ Not flexible enough | โ
canOpen returns a boolean |
| "Click to buy: deduct 100 coins first then give the item, reject if not enough money" | โ ๏ธ Splits into multiple steps, hard to judge | โ One function handles it |
| "Dynamically decide based on the player's PAPI variable" | โ | โ
qcl.placeholder(...) |
Scripts are a "logic back door" opened for server owners: use a small snippet of JS to express "conditions" and "actions".
๐งฉ Engine Notes (Read This First) โ
QCL's script engine is GraalVM JavaScript (GraalJS).
- The server (Paper / Purpur) needs to be able to load the GraalJS runtime library.
- If the runtime library is unavailable, script calls return the failure code
SCRIPT_UNAVAILABLEdirectly (it won't crash the server, but all script logic stops working). - After startup, watch the console to confirm the script system loaded correctly.
๐ Where Scripts Go โ
| Namespace | Disk Directory | Whose |
|---|---|---|
global | plugins/QinhCoreLib/scripts/global/ | Server owner's shared scripts |
qinhitems | plugins/QinhItems/scripts/ | Namespace registered by the sub-plugin QinhItems |
| Others | Registered by each sub-plugin itself | Sub-plugins |
๐ก A namespace is a mapping from a name to a directory. When a server owner writes their own scripts, just drop them into
scripts/global/, and the namespace when referencing them isglobal(which can also be omitted, see below). Sub-plugins can register their own namespace and point its script directory at their own plugin folder.
๐ Reference Syntax Explained โ
A script reference is a string in three parts:
namespace:relative-path.js:functionName| Part | Description | Omission Rule |
|---|---|---|
namespace | Decides which directory to look in | Omitted โ defaults to global |
relative-path.js | File path relative to the namespace directory | .js can be omitted, appended automatically |
functionName | Which function in the file to call | Omitted โ uses config's javascript.default-function (default main) |
Example Comparison โ
| Form | Equivalent Meaning |
|---|---|
qinhitems:hooks/craft.js:check | qinhitems namespace โ hooks/craft.js โ call check |
global:example.js | global โ example.js โ call main (default function name) |
example.js:onClick | global (namespace omitted) โ example.js โ call onClick |
example | global โ example.js (.js appended automatically) โ call main |
โ All three parts can be omitted; the shortest form is just
scriptName, which callsmaininscripts/global/scriptName.js.
โ๏ธ Config Options (config.yml) โ
Three script-related options, located in the main config:
| Key | Default | Description |
|---|---|---|
javascript.enabled | true | Master switch; turning it off disables all script calls |
javascript.default-function | main | Default function called when the reference omits the function name |
javascript.debug.print-stacktrace | false | When enabled, prints the full stack trace to the console on script errors (a troubleshooting lifesaver) |
javascript:
enabled: true
default-function: main # Called when the function name is omitted
debug:
print-stacktrace: false # Change to true when troubleshooting๐ After changing config or adding new scripts, run
/qcl reloadto rescan the scripts directory.
โ๏ธ Write Your First Script (Step by Step) โ
Step 1: Create the File โ
Create a new hello.js under plugins/QinhCoreLib/scripts/global/:
// hello.js โโ Note: use English for key names/function names, Chinese only for comments
function main(ctx) {
// ctx.player() may be null (when not triggered by a player)
var p = ctx.player();
qcl.logInfo("hello.js was called, player=" + (p ? p.getName() : "null"));
return true; // Returning true means success
}Step 2: Reload โ
/qcl reloadStep 3: Reference global:hello.js (or the shorthand hello) somewhere to trigger it. โ
Inside a script you can always get two injected objects:
qcl(API) andctx(context). When called by a GUI, the function signature is fixed asfunction name(ctx){ ... }.
๐ฆ ctx โโ The Context Object โ
ctx represents "the scene of this call" (who triggered it, what data it carried).
| Method | Returns | Description |
|---|---|---|
ctx.player() | Player or null | The triggering player; null when not triggered by a playerโalways null-check |
ctx.pluginName() | String | The name of the plugin that triggered it |
ctx.get(key) | Value | Read data carried by the context |
ctx.set(key, value) | โ | Write context data (for use by later steps) |
ctx.vars() | Variable collection | Get all context variables |
๐ ๏ธ qcl โโ The API Object โ
qcl is the toolbox QCL exports for scripts to use.
| Category | Method | Description |
|---|---|---|
| Logging | qcl.logInfo(msg) / logWarn(msg) / logError(msg) | Print a log to the console (common for debugging) |
| Bridging | qcl.bridgeStatusNames() | List of currently connected external plugin bridge names |
| Placeholder | qcl.placeholder(text) | Parse text using PlaceholderAPI (including %...%) |
| Economy ยท Query | qcl.economyHas(amount, provider?, currency?) | Whether the player has enough money, returns a boolean |
| Economy ยท Withdraw | qcl.economyWithdraw(amount, provider?, currency?) | Deduct money, returns a boolean |
| Economy ยท Deposit | qcl.economyDeposit(amount, provider?, currency?) | Give money, returns a boolean |
| Item ยท Parse | qcl.itemParse(ref) | Parse an item source reference into an item |
| Item ยท Give | qcl.itemGive(ref, amount) | Give an item to the player, returns a boolean |
| Scheduling ยท Sync | qcl.runSync(task) | Throw a task back to the main thread to execute |
| Scheduling ยท Delayed | qcl.runSyncLater(ticks, task) | Execute on the main thread after N ticks of delay |
| Scheduling ยท Wait | qcl.runSyncAndWait(task) | Execute on the main thread and wait for the result |
๐ฐ The economy methods'
provider/currencyare optional parameters; if not provided, the default economy is used. The itemrefis the unified item source reference (see ็ฉๅๆบๅผ็จ.md).
๐ Return Value Rules (Very Important) โ
What a script function "returns" determines whether QCL considers it a success or failure:
| Return Value | Verdict | Notes |
|---|---|---|
null / undefined / true | โ Success | Returning nothing also counts as success |
false | โ Failure | The failure message is "script returned false" |
| String / Number | โ Success with value | Carries the value back along with it |
{ success: ..., message: ... } | Judged by success | You can customize the failure message |
function check(ctx) {
var p = ctx.player();
if (!p) return false;
if (p.getLevel() < 30) {
return { success: false, message: "&cYour level is below 30!" };
}
return true;
}๐ผ๏ธ Attaching Conditions and Actions in a GUI โ
The most common use for scripts is to wire logic into a GUI's open condition and click action.
Usage 1: Open Condition (view-requirement) โ
view-requirement:
type: javascript
value: global:example.js:canOpen # Returns false โ don't allow openingUsage 2: Click Action (click-actions) โ
click-actions:
- type: javascript # or run_script
value: global:example.js:onClick๐งช Complete Example A: Level Gate for Opening a GUI โ
scripts/global/shop_gate.js
function canOpen(ctx) {
var p = ctx.player();
if (!p) return false;
if (p.getLevel() < 30) {
return { success: false, message: "&cYou need level 30 to enter the shop" };
}
return true;
}GUI Config (excerpt)
view-requirement:
type: javascript
value: global:shop_gate.js:canOpen๐งช Complete Example B: Click to Buy (Deduct Money, Give Item) โ
scripts/global/buy_sword.js
function buy(ctx) {
var p = ctx.player();
if (!p) return { success: false, message: "&cOnly players can buy" };
// 1) Check whether there's enough money first
if (!qcl.economyHas(100)) {
return { success: false, message: "&cNot enough coins, need 100" };
}
// 2) Deduct money
if (!qcl.economyWithdraw(100)) {
return { success: false, message: "&cWithdrawal failed" };
}
// 3) Give the item (item source reference, 1 of it)
qcl.itemGive("qinhitems-fire_sword", 1);
return { success: true, message: "&aPurchase successful!" };
}GUI Config (excerpt)
click-actions:
- type: javascript
value: global:buy_sword.js:buyOrder is critical: check first โ then deduct โ then give. If any step fails,
returna failure object to avoid "money deducted but no item given".
๐ Built-in Example Scripts Explained Function by Function โ
QCL ships with two example scripts that you can copy and modify directly:
scripts/global/example.js โ
function main(ctx) {
qcl.logInfo("example.js main() was called, player=" + (ctx.player() ? ctx.player().getName() : "null"));
return true;
}
function canOpen(ctx) {
var player = ctx.player();
if (!player) return false;
return player.getLevel() >= 0; // An always-true example condition
}
function onClick(ctx) {
var player = ctx.player();
if (!player) return false;
qcl.logInfo("onClick: " + player.getName());
return true;
}| Function | Purpose | How to Reference It in a GUI |
|---|---|---|
main | Default entry point; logs then returns success | Called when the function name is omitted |
canOpen | Open condition example (null-check + level check) | value: global:example.js:canOpen |
onClick | Click action example (logging) | value: global:example.js:onClick |
scripts/global/qcl_status.js โ
Contains the formatStatus function, which appends output lines to the /qcl status detail command. This is a demonstration of "sub-plugin / system-level" usage; server owners generally don't need to touch it.
๐ Sandbox Restrictions โ
Scripts run in a restricted sandbox. The allowed and forbidden lists:
| โ Allowed | โ Forbidden |
|---|---|
Calling the exported qcl API | Creating threads |
Operating on Bukkit's Player object | File read/write (IO) |
| Plain JS logic | Reflecting arbitrary Java classes |
| Network access |
Want to do something outside the sandbox (like reading an external file)? That's beyond the scope of server owner scripts; you should switch to a sub-plugin or dedicated development capabilities, see ่ๆฌAPI.md.
๐ Error Codes and Troubleshooting โ
When a script call fails, QCL gives an error code:
| Error Code | Meaning | How to Fix |
|---|---|---|
SCRIPT_UNAVAILABLE | GraalJS runtime library unavailable | Switch to Paper/Purpur, confirm GraalJS is loaded |
SCRIPT_PARSE_FAILED | The reference format is written wrong | Check the spelling of namespace:path:functionName |
SCRIPT_NOT_FOUND | The file doesn't exist | Confirm the .js file is in the right directory and that you ran /qcl reload |
SCRIPT_FUNCTION_MISSING | The function name doesn't exist | Confirm the file has this function with matching case |
SCRIPT_FAILED | The script threw an exception during execution | Enable print-stacktrace to see the stack trace |
๐ Debugging Tips โ
- Enable the stack trace: change
javascript.debug.print-stacktracetotrue, and the console will print the full stack trace when a script errors, so you can tell which line blew up at a glance. - Add logging: insert
qcl.logInfo("got here: " + variable)at key steps to observe the execution path. - Null-check first: almost all bugs come from
ctx.player()beingnulland not being handledโdevelop the habit ofif (!p) return .... - Always reload after changes: only
/qcl reloadrescans scripts; otherwise your changes won't take effect. - Case-sensitive: namespaces, file names, and function names are all case-sensitive; copy them exactly and don't casually change the case.
โก๏ธ Continue Reading โ
- ่ชๅฎไนGUI.md โโ Wiring scripts into a GUI's conditions and actions
- ็ฉๅๆบๅผ็จ.md โโ The reference format used by
qcl.itemGive/itemParse - ้
็ฝฎๆไปถ.md โโ Full explanation of the three
javascript.*config options - Developer / ่ๆฌAPI.md โโ Deeper script capabilities and API details