Skip to content

导航:文档首页服主指南 › 脚本入门 相关:自定义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(不会让服务器崩,但脚本逻辑全部失效)。
  • 启动后留意控制台,确认脚本系统正常加载。

📁 脚本放哪

命名空间磁盘目录谁的
globalplugins/QinhCoreLib/scripts/global/服主公共脚本
qinhitemsplugins/QinhItems/scripts/子插件 QinhItems 注册的命名空间
其他由各子插件自行注册子插件

💡 命名空间是一个名字到目录的映射。服主自己写脚本,放进 scripts/global/ 即可,引用时命名空间就是 global(也可省略,见下文)。子插件可以注册自己的命名空间,把脚本目录指向它自己的插件文件夹。


🔗 引用语法详解

脚本引用是一个字符串,三段式:

命名空间:相对路径.js:函数名
说明省略规则
命名空间决定去哪个目录找省略 → 默认 global
相对路径.js相对命名空间目录的文件路径.js 可省略,自动补
函数名调用文件里的哪个函数省略 → 用 config 的 javascript.default-function(默认 main

例子对照

写法等价含义
qinhitems:hooks/craft.js:checkqinhitems 命名空间 → hooks/craft.js → 调 check
global:example.jsglobal → example.js → 调 main(默认函数名)
example.js:onClickglobal(省略命名空间)→ example.js → 调 onClick
exampleglobal → example.js(自动补 .js)→ 调 main

✅ 三段都能省,最短可以只写 脚本名,就会去 scripts/global/脚本名.jsmain


⚙️ 配置项(config.yml)

脚本相关三项,位于主配置:

默认说明
javascript.enabledtrue总开关,关掉后所有脚本调用失效
javascript.default-functionmain引用省略函数名时调用的默认函数
javascript.debug.print-stacktracefalse开启后脚本出错时在控制台打印完整堆栈(排错神器)
yaml
javascript:
  enabled: true
  default-function: main        # 省略函数名时调它
  debug:
    print-stacktrace: false     # 排错时改成 true

🔄 改完 config 或新增脚本后执行 /qcl reload,会重新扫描 scripts 目录。


✍️ 写你的第一个脚本(逐步)

第 1 步:建文件

plugins/QinhCoreLib/scripts/global/ 下新建 hello.js

javascript
// 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()Playernull触发的玩家;非玩家触发时为 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 判定可自定义失败消息
javascript
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)

yaml
view-requirement:
  type: javascript
  value: global:example.js:canOpen   # 返回 false → 不让打开

用法二:点击动作(click-actions)

yaml
click-actions:
  - type: javascript        # 或 run_script
    value: global:example.js:onClick

🧪 完整示例 A:等级门槛开 GUI

scripts/global/shop_gate.js

javascript
function canOpen(ctx) {
    var p = ctx.player();
    if (!p) return false;
    if (p.getLevel() < 30) {
        return { success: false, message: "&c需要等级 30 才能进入商店" };
    }
    return true;
}

GUI 配置(节选)

yaml
view-requirement:
  type: javascript
  value: global:shop_gate.js:canOpen

🧪 完整示例 B:点击购买(扣钱给物)

scripts/global/buy_sword.js

javascript
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 配置(节选)

yaml
click-actions:
  - type: javascript
    value: global:buy_sword.js:buy

顺序很关键:先查 → 再扣 → 再给。任何一步失败就 return 失败对象,避免「扣了钱没给物」。


📖 内置示例脚本逐函数讲解

QCL 自带两个示例脚本,可直接照抄改:

scripts/global/example.js

javascript
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_UNAVAILABLEGraalJS 运行库不可用换 Paper/Purpur,确认 GraalJS 已加载
SCRIPT_PARSE_FAILED引用格式写错了检查 命名空间:路径:函数名 拼写
SCRIPT_NOT_FOUND文件不存在确认 .js 文件在对应目录、/qcl reload
SCRIPT_FUNCTION_MISSING函数名不存在确认文件里有这个函数、大小写一致
SCRIPT_FAILED脚本执行时抛异常print-stacktrace 看堆栈

🐛 调试技巧

  1. 开堆栈:把 javascript.debug.print-stacktrace 改成 true,脚本报错时控制台会打印完整堆栈,一眼看出哪行炸了。
  2. 加日志:在关键步骤插 qcl.logInfo("到这了:" + 变量),观察执行路径。
  3. 判空优先:几乎所有 bug 来自 ctx.player()null 没处理——养成 if (!p) return ... 的习惯。
  4. 改完必 reload/qcl reload 才会重扫脚本,否则改了不生效。
  5. 大小写敏感:命名空间、文件名、函数名全部区分大小写,照抄不要随手改大小写。

➡️ 继续阅读