导航:文档首页 › 服主指南 › 脚本入门 相关:自定义GUI.md · 配置文件.md · 物品源引用.md · 开发者 / 脚本API.md
📜 脚本入门(服主视角)
本篇教你用 JavaScript 脚本给 QinhCoreLib(QCL)扩展逻辑。你不需要会写 Java,也不用打包插件——把一个 .js 文件丢进目录,就能给 GUI 加上「等级不够不让开」「点一下扣钱给物品」这类自定义条件和动作。
🤔 为什么需要脚本
QCL 的 GUI、子插件钩子都支持「条件」和「动作」。普通 YAML 配置能覆盖大多数常见需求(权限、物品、金币……),但遇到下面这类需求就力不从心:
| 需求 | 普通 YAML | 脚本 |
|---|---|---|
| 「等级 ≥ 30 才能打开商店」 | ❌ 不够灵活 | ✅ canOpen 返回布尔 |
| 「点击购买:先扣 100 金币再给物品,钱不够就拒绝」 | ⚠️ 拆成多步、难判定 | ✅ 一个函数搞定 |
| 「根据玩家 PAPI 变量动态决定」 | ❌ | ✅ qcl.placeholder(...) |
脚本就是给服主开的一道「逻辑后门」:用一小段 JS 表达「条件」与「动作」。
🧩 引擎说明(先读这段)
QCL 的脚本引擎是 GraalVM JavaScript(GraalJS)。
- 需要服务端(Paper / Purpur)能加载 GraalJS 运行库。
- 如果运行库不可用,脚本调用会直接返回失败码
SCRIPT_UNAVAILABLE(不会让服务器崩,但脚本逻辑全部失效)。 - 启动后留意控制台,确认脚本系统正常加载。
📁 脚本放哪
| 命名空间 | 磁盘目录 | 谁的 |
|---|---|---|
global | plugins/QinhCoreLib/scripts/global/ | 服主公共脚本 |
qinhitems | plugins/QinhItems/scripts/ | 子插件 QinhItems 注册的命名空间 |
| 其他 | 由各子插件自行注册 | 子插件 |
💡 命名空间是一个名字到目录的映射。服主自己写脚本,放进
scripts/global/即可,引用时命名空间就是global(也可省略,见下文)。子插件可以注册自己的命名空间,把脚本目录指向它自己的插件文件夹。
🔗 引用语法详解
脚本引用是一个字符串,三段式:
命名空间:相对路径.js:函数名| 段 | 说明 | 省略规则 |
|---|---|---|
命名空间 | 决定去哪个目录找 | 省略 → 默认 global |
相对路径.js | 相对命名空间目录的文件路径 | .js 可省略,自动补 |
函数名 | 调用文件里的哪个函数 | 省略 → 用 config 的 javascript.default-function(默认 main) |
例子对照
| 写法 | 等价含义 |
|---|---|
qinhitems:hooks/craft.js:check | qinhitems 命名空间 → hooks/craft.js → 调 check |
global:example.js | global → example.js → 调 main(默认函数名) |
example.js:onClick | global(省略命名空间)→ example.js → 调 onClick |
example | global → example.js(自动补 .js)→ 调 main |
✅ 三段都能省,最短可以只写
脚本名,就会去scripts/global/脚本名.js调main。
⚙️ 配置项(config.yml)
脚本相关三项,位于主配置:
| 键 | 默认 | 说明 |
|---|---|---|
javascript.enabled | true | 总开关,关掉后所有脚本调用失效 |
javascript.default-function | main | 引用省略函数名时调用的默认函数 |
javascript.debug.print-stacktrace | false | 开启后脚本出错时在控制台打印完整堆栈(排错神器) |
javascript:
enabled: true
default-function: main # 省略函数名时调它
debug:
print-stacktrace: false # 排错时改成 true🔄 改完 config 或新增脚本后执行
/qcl reload,会重新扫描 scripts 目录。
✍️ 写你的第一个脚本(逐步)
第 1 步:建文件
在 plugins/QinhCoreLib/scripts/global/ 下新建 hello.js:
// hello.js —— 注意:键名/函数名用英文,注释才用中文
function main(ctx) {
// ctx.player() 可能为 null(不是玩家触发时)
var p = ctx.player();
qcl.logInfo("hello.js 被调用,玩家=" + (p ? p.getName() : "null"));
return true; // 返回 true 表示成功
}第 2 步:重载
/qcl reload第 3 步:在某处引用 global:hello.js(或简写 hello)即可触发。
脚本里永远能拿到两个注入对象:
qcl(API)和ctx(上下文)。GUI 调用时,函数签名固定是function 名(ctx){ ... }。
📦 ctx —— 上下文对象
ctx 代表「这次调用的现场」(谁触发的、带了什么数据)。
| 方法 | 返回 | 说明 |
|---|---|---|
ctx.player() | Player 或 null | 触发的玩家;非玩家触发时为 null,务必判空 |
ctx.pluginName() | 字符串 | 触发来源的插件名 |
ctx.get(key) | 值 | 读取上下文携带的数据 |
ctx.set(key, value) | — | 写入上下文数据(供后续步骤使用) |
ctx.vars() | 变量集合 | 拿到全部上下文变量 |
🛠️ qcl —— API 对象
qcl 是 QCL 导出给脚本用的工具箱。
| 分类 | 方法 | 说明 |
|---|---|---|
| 日志 | qcl.logInfo(msg) / logWarn(msg) / logError(msg) | 往控制台打日志(调试常用) |
| 桥接 | qcl.bridgeStatusNames() | 当前已接入的外部插件桥名列表 |
| 占位符 | qcl.placeholder(text) | 用 PlaceholderAPI 解析文本(含 %...%) |
| 经济·查询 | qcl.economyHas(amount, provider?, currency?) | 玩家钱是否够,返回布尔 |
| 经济·扣款 | qcl.economyWithdraw(amount, provider?, currency?) | 扣钱,返回布尔 |
| 经济·存款 | qcl.economyDeposit(amount, provider?, currency?) | 给钱,返回布尔 |
| 物品·解析 | qcl.itemParse(ref) | 解析物品源引用为物品 |
| 物品·发放 | qcl.itemGive(ref, amount) | 给玩家发物品,返回布尔 |
| 调度·同步 | qcl.runSync(任务) | 把任务丢回主线程执行 |
| 调度·延迟 | qcl.runSyncLater(ticks, 任务) | 延迟 N tick 后在主线程执行 |
| 调度·等待 | qcl.runSyncAndWait(任务) | 在主线程执行并等待结果 |
💰 经济方法的
provider/currency是可选参数,不填用默认经济。物品的ref就是统一物品源引用(见 物品源引用.md)。
🔁 返回值规则(很重要)
脚本函数「返回什么」决定 QCL 认为它成功还是失败:
| 返回值 | 判定 | 备注 |
|---|---|---|
null / undefined / true | ✅ 成功 | 什么都不 return 也算成功 |
false | ❌ 失败 | 失败消息为「脚本返回 false」 |
| 字符串 / 数字 | ✅ 成功带值 | 把值一起带回去 |
{ success: ..., message: ... } | 按 success 判定 | 可自定义失败消息 |
function check(ctx) {
var p = ctx.player();
if (!p) return false;
if (p.getLevel() < 30) {
return { success: false, message: "&c你的等级不足 30!" };
}
return true;
}🖼️ 在 GUI 里挂条件和动作
脚本最常见的用途就是给 GUI 的打开条件和点击动作接逻辑。
用法一:打开条件(view-requirement)
view-requirement:
type: javascript
value: global:example.js:canOpen # 返回 false → 不让打开用法二:点击动作(click-actions)
click-actions:
- type: javascript # 或 run_script
value: global:example.js:onClick🧪 完整示例 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: "&c需要等级 30 才能进入商店" };
}
return true;
}GUI 配置(节选)
view-requirement:
type: javascript
value: global:shop_gate.js:canOpen🧪 完整示例 B:点击购买(扣钱给物)
scripts/global/buy_sword.js
function buy(ctx) {
var p = ctx.player();
if (!p) return { success: false, message: "&c只有玩家能购买" };
// 1) 先检查钱够不够
if (!qcl.economyHas(100)) {
return { success: false, message: "&c金币不足,需要 100" };
}
// 2) 扣钱
if (!qcl.economyWithdraw(100)) {
return { success: false, message: "&c扣款失败" };
}
// 3) 给物品(物品源引用,1 个)
qcl.itemGive("qinhitems-fire_sword", 1);
return { success: true, message: "&a购买成功!" };
}GUI 配置(节选)
click-actions:
- type: javascript
value: global:buy_sword.js:buy顺序很关键:先查 → 再扣 → 再给。任何一步失败就
return失败对象,避免「扣了钱没给物」。
📖 内置示例脚本逐函数讲解
QCL 自带两个示例脚本,可直接照抄改:
scripts/global/example.js
function main(ctx) {
qcl.logInfo("example.js main() 被调用,玩家=" + (ctx.player() ? ctx.player().getName() : "null"));
return true;
}
function canOpen(ctx) {
var player = ctx.player();
if (!player) return false;
return player.getLevel() >= 0; // 永远成立的示例条件
}
function onClick(ctx) {
var player = ctx.player();
if (!player) return false;
qcl.logInfo("onClick: " + player.getName());
return true;
}| 函数 | 作用 | 在 GUI 里怎么引用 |
|---|---|---|
main | 默认入口,打日志后返回成功 | 省略函数名时调它 |
canOpen | 打开条件示例(判空 + 等级判断) | value: global:example.js:canOpen |
onClick | 点击动作示例(打日志) | value: global:example.js:onClick |
scripts/global/qcl_status.js
内含 formatStatus 函数,给 /qcl status detail 命令追加输出行。这是「子插件/系统级」用法的示范,服主一般不用动它。
🔒 沙箱限制
脚本运行在受限沙箱里,允许与禁止清单:
| ✅ 允许 | ❌ 禁止 |
|---|---|
调用导出的 qcl API | 创建线程 |
操作 Bukkit 的 Player 对象 | 文件读写(IO) |
| 普通 JS 逻辑 | 反射任意 Java 类 |
| 网络访问 |
想做沙箱外的事(比如读外部文件)?那已经超出服主脚本范畴,应该改用子插件或专门的开发能力,见 脚本API.md。
🚑 错误码与排查
脚本调用失败时,QCL 会给出错误码:
| 错误码 | 含义 | 怎么修 |
|---|---|---|
SCRIPT_UNAVAILABLE | GraalJS 运行库不可用 | 换 Paper/Purpur,确认 GraalJS 已加载 |
SCRIPT_PARSE_FAILED | 引用格式写错了 | 检查 命名空间:路径:函数名 拼写 |
SCRIPT_NOT_FOUND | 文件不存在 | 确认 .js 文件在对应目录、/qcl reload 过 |
SCRIPT_FUNCTION_MISSING | 函数名不存在 | 确认文件里有这个函数、大小写一致 |
SCRIPT_FAILED | 脚本执行时抛异常 | 开 print-stacktrace 看堆栈 |
🐛 调试技巧
- 开堆栈:把
javascript.debug.print-stacktrace改成true,脚本报错时控制台会打印完整堆栈,一眼看出哪行炸了。 - 加日志:在关键步骤插
qcl.logInfo("到这了:" + 变量),观察执行路径。 - 判空优先:几乎所有 bug 来自
ctx.player()为null没处理——养成if (!p) return ...的习惯。 - 改完必 reload:
/qcl reload才会重扫脚本,否则改了不生效。 - 大小写敏感:命名空间、文件名、函数名全部区分大小写,照抄不要随手改大小写。
➡️ 继续阅读
- 自定义GUI.md —— 把脚本挂到 GUI 的条件与动作上
- 物品源引用.md ——
qcl.itemGive/itemParse用的引用格式 - 配置文件.md ——
javascript.*三项配置的完整说明 - 开发者 / 脚本API.md —— 更深入的脚本能力与 API 细节