Skip to content
On this page

服务端通用脚本与 Cron

服务端通用脚本把一段 ChaUI Script 放在页面之外运行。它适合封装跨页面复用的命令、奖励入口、公告和定时任务,也可以由命令、页面、其他插件 API 或 Cron 启动。

通用脚本没有页面上下文:它不能查找或修改组件,不能访问当前页面会话、容器绑定或 原版.* 客户端状态。相对地,它不依赖某张页面保持打开,页面关闭后也不会因此丢失一项已经独立启动的服务端工作。

先分清三种脚本

类型配置位置运行上下文可以操作组件吗典型入口
页面事件脚本页面 yml 的 events.*当前页面和触发组件可以点击、打开、关闭、输入
页面内自定义方法页面 yml 的 methods调用它的当前页面可以methods.refresh() / 方法.刷新()
服务端通用方法plugins/ChaUI/global-scripts.yml一次独立服务端执行,可选目标玩家不可以命令、页面字面量调用、Java API、Cron

如果逻辑必须读取 component("id")、页面 vars原版.设置.*,它属于页面脚本;如果逻辑应脱离页面运行,再定义为服务端通用方法。

第一个可运行方法

打开 plugins/ChaUI/global-scripts.yml,完整写入:

yaml
methods:
  notify_player:
    # 参数按这里的顺序传入。
    parameters:
      - name: message
        type: string
    script: |
      // {player} 会替换为本次目标玩家名。
      // {args.message} 来自上面声明的只读参数。
      consolecmd("tell {player} [ChaUI] {args.message}")

# 暂时不配置定时任务时,可以保留空映射,方便以后添加。
crons: {}

然后执行:

text
/chaui reload
/chaui script Steve notify_player 欢迎回来

Steve 换成当前在线玩家名。执行成功后,玩家会收到 [ChaUI] 欢迎回来,命令发送者会得到一个唯一执行 ID。

这个方法没有周期任务,会在命令返回前同步完成,所以随后用该 ID 停止可能提示“执行不存在”。这是正常结果;需要保持运行并演示停止,请继续下一节。

方法、参数和变量

每个方法包含三个部分:

yaml
methods:
  announce_reward:
    parameters:
      - name: amount
        type: number
      - name: announce
        type: boolean
    script: |
      vars.applied = true
      consolecmd("say {player} 完成任务,奖励数量为 {args.amount}")
      if args.announce {
        consolecmd("tell {player} 奖励记录已生成")
      }
crons: {}

参数只支持三种类型:

类型配置值命令参数示例
字符串string欢迎回来
有限数字number100.5
布尔值booleantruefalse

参数数量和顺序必须与 parameters 完全一致。命令行中每个空格分隔项对应一个参数,因此一个字符串参数最好使用单个词;需要长文案时可在方法脚本中组合固定文字。

方法 ID 最长 64 个 Unicode 字符,方法 ID 与参数名都必须以 Unicode 字母或下划线开头,后续只能包含 Unicode 字母、数字或下划线。不要使用空格、连字符、点号或路径分隔符;同一方法内的参数名不能重复。

args.* 是只读参数

args.amountargs.announce 只能读取,不能赋值。字符串动作中也可以使用 {args.amount} 插值。

vars.* 是本次执行的临时变量

vars.appliedvars.count 可以在本次方法及其周期回调中读写。不同玩家、不同命令和不同 Cron 触发各有独立变量,不会写回 global-scripts.yml,也不会跨执行共享。嵌套调用的每个通用方法也有自己的 args.*vars.*,不会直接继承或共享调用者的临时变量;需要的数据必须作为参数显式传入。

通用方法没有返回值。需要拆分逻辑时,一个方法可以调用另一个方法:

text
global("notify_player", "任务开始")
调用通用方法("notify_player", "任务完成")

方法 ID 必须写成字符串字面量,参数数量和类型会在加载时检查。包含根方法在内,一条调用链最多进入 8 个方法;递归或相互调用超过深度会终止整个根执行,并报告完整方法链。

timer、interval 与执行 ID

只要根执行仍有活动周期任务,执行 ID 就保持有效。下面的方法同时创建一个按 tick 和一个按毫秒运行的计数器:

yaml
methods:
  repeating_state:
    parameters:
      - name: start
        type: number
    script: |
      vars.tickCount = args.start
      vars.millisecondCount = args.start

      // 每 20 tick 执行一次,通常约为 1 秒。
      timer("tick_counter", 20) {
        vars.tickCount = vars.tickCount + 1
      }

      // 每 1000 毫秒执行一次。
      interval("millisecond_counter", 1000) {
        vars.millisecondCount = vars.millisecondCount + 1
      }
crons: {}

重载并启动:

text
/chaui reload
/chaui script Steve repeating_state 0

复制返回的执行 ID,例如 c4a72b67-...,然后停止:

text
/chaui script list
/chaui script stop <执行ID>

script list 只显示当前仍有活动计时任务的根执行。停止根执行会一起取消它的子方法、timerinterval 和其他已登记的延时任务。再次停止同一个 ID 会返回“未找到”,不会影响其他执行。

周期任务还可以在脚本内按名称取消:

text
stopTimer("tick_counter")
cancel("millisecond_counter")

周期块的重要边界

  • timer(name, ticks)interval(name, milliseconds) 都是周期执行,直到取消或根执行结束;
  • 同一执行中同名任务会替换旧任务;
  • timer 间隔为 1..72000 tick,interval1..3600000 毫秒;
  • 单次执行最多同时保留 32 个活动计时任务,最多执行 4096 个脚本节点;
  • repeattimerinterval 内不能再嵌套周期结构;
  • 为避免重复放大高权限操作,周期块与 repeat 块不能执行玩家命令、OP 命令、控制台命令、打开页面等高权限动作,也不能绕过限制间接调用包含这些动作的方法。

因此上面的周期示例只维护执行内状态。需要按固定时间发公告时,应使用本页后面的 Cron,每次触发一个独立、可审计的方法执行。

四种启动入口

1. 管理命令

text
/chaui script <玩家> <方法> [参数...]
/chaui script list
/chaui script stop <执行ID>

目标玩家必须在线。命令会先为目标玩家把每个字符串参数中的 PAPI 更新一次,再按方法声明把参数解析为 stringnumberboolean。成功时总会返回根执行 ID,即使方法随后同步完成。script list 可查看页面、命令、Java API 和 Cron 创建的全部当前活动根执行;完整指令说明见插件指令

2. 页面脚本

页面可以启动一个独立的服务端通用方法:

text
global("notify_player", "按钮已触发")
调用通用方法("announce_reward", 10, true)

页面调用只接受字符串、有限数字、布尔值三类字面量,不能传 vars.amount、组件属性、实时公式或动态方法 ID,null 同样不接受。调用成功后页面脚本不会等待通用方法完成,也不会把根执行 ID 返回到页面变量;管理员可以用 /chaui script list 找到仍在运行的页面执行,其中来源显示为 page:<页面ID>

字符串字面量中的 PlaceholderAPI 占位符可以在服务端调用开始时解析一次,但页面变量不是 PAPI 占位符,不能借此传入。

3. Java API

java
import com.github.ginirohikocha.chaui.api.ChaUIAPI;
import com.github.ginirohikocha.chaui.api.GlobalScriptExecutionInfo;

import java.util.List;

String executionId = ChaUIAPI.executeGlobalMethod(
        player, "reward", 10, true);

List<GlobalScriptExecutionInfo> active = ChaUIAPI.activeGlobalScripts();
boolean stopped = ChaUIAPI.stopGlobalScript(executionId);

executeGlobalMethodstopGlobalScript 必须在 Bukkit 主线程调用。传入玩家时,该玩家必须在线;参数数量和 Java 类型按声明严格校验:stringStringnumber 传有限的 NumberbooleanBoolean,不会把任意对象或字符串自动转换成另一种类型。executeGlobalMethod 返回根执行 ID,stopGlobalScript 只在找到仍活动的执行时返回 true

activeGlobalScripts() 返回当前活动根执行的只读快照。每项 GlobalScriptExecutionInfo 包含 executionId、根 methodIdsourcesourceIdplayerIdplayerName;控制台执行的两个玩家字段为 nullsourceId 在 API 与命令来源中是根方法 ID,在页面来源中是真实页面 ID,在 Cron 来源中是 Cron 配置 ID。

插件可以为纯控制台方法传入 null 玩家,但此时玩家命令、打开页面、{player} 和玩家 PAPI 都没有可用上下文。API 表格见API 方法表

4. Cron

Cron 在设定的日期和时间自动启动通用方法。每次触发都是新的根执行,内部拥有独立执行 ID、参数快照和临时变量。只要 Cron 执行仍有活动计时任务,它就会出现在 /chaui script list 中,来源显示为 cron:<Cron ID>;复制该执行 ID 后即可用 /chaui script stop 强制停止。

PAPI:只在调用开始时更新一次

安装并启用 PlaceholderAPI 后,命令、Java API、页面字面量调用和 online_players Cron 都会在根方法启动时解析字符串参数中的 PlaceholderAPI 一次:

text
/chaui script Steve notify_player 在线人数=%server_online%

如果启动时 %server_online%12,方法得到的 args.message 就是 在线人数=12。即使方法的 timer 或 interval 运行期间在线人数变成 15,回调仍使用启动时的快照,不会像页面实时 PAPI 文本一样刷新。未安装或未启用 PlaceholderAPI 时,玩家目标的字符串会保留原始 %...% 文本,不会凭空解析。

嵌套通用方法也只接收调用者已经得到的值,不会再次解析一遍。target: console 没有玩家上下文,不能使用 %player_name% 等玩家占位符。

Cron 表达式

Cron 是一张“什么时候执行”的时间表。ChaUI 支持 5 段和 6 段数字表达式:

段数顺序示例含义
5 段分 时 日 月 星期*/5 * * * *每 5 分钟
5 段分 时 日 月 星期0 12 * * *每天 12:00
6 段秒 分 时 日 月 星期*/10 * * * * *每 10 秒
6 段秒 分 时 日 月 星期0 0 12 * * *每天 12:00:00

字段范围:秒 0..59、分 0..59、时 0..23、日 1..31、月 1..12、星期 0..7,其中 07 都表示星期日。

每一段支持:

  • *:任意值;
  • */5:按步长;
  • 1,15,30:多个值;
  • 9-17:连续范围;
  • 0-50/10:范围内按步长。

不支持月份英文名、星期英文名、?LW# 或第 7 段年份。日期和星期同时受限时,匹配其中任一条件即可触发;不想产生歧义时,把其中一项写 *

Cron 配置字段

yaml
crons:
  online_notice:
    enabled: true
    schedule: "*/5 * * * *"
    timezone: "Asia/Shanghai"
    target: online_players
    method: notify_player
    args:
      - "欢迎 %player_name%"
字段类型说明
enabled布尔值缺省为 true;设为 false 时保留配置但不调度
schedule字符串5 段或 6 段 Cron 表达式
timezone字符串IANA 时区,例如 Asia/Shanghai;省略时使用服务器时区
target枚举online_playersconsole
method字符串已通过校验的通用方法 ID
args列表按方法参数顺序提供,数量和 YAML 值类型必须匹配

online_players

触发时为每位在线玩家创建独立执行。每名玩家分别解析一次字符串 PAPI,某名玩家解析或执行失败不会阻止其他在线玩家。

同一个 Cron 对同一名玩家的上一执行仍在运行时,本次只跳过该玩家;不会阻止该 Cron 为其他玩家执行。

console

每次触发只创建一个没有玩家上下文的执行。适合 consolecmd 公告和不依赖玩家的服务端动作,不能使用玩家命令或打开玩家页面。控制台执行不会解析任何 PlaceholderAPI 占位符;参数中只要出现 %...%,本次执行就会被明确拒绝,而不是把未解析文本继续传给方法。

一份完整可复制的配置

下面的文件同时包含手动通知、可停止周期执行,以及在线玩家和控制台两种 Cron。可直接保存为 plugins/ChaUI/global-scripts.yml

yaml
methods:
  notify_player:
    parameters:
      - name: message
        type: string
    script: |
      // 目标玩家收到一次消息。
      consolecmd("tell {player} [ChaUI] {args.message}")

  console_announcement:
    parameters:
      - name: message
        type: string
    script: |
      // 不依赖玩家,可供 console Cron 使用。
      consolecmd("say [ChaUI] {args.message}")

  repeating_state:
    parameters:
      - name: start
        type: number
    script: |
      // 用于演示执行 ID 和强制停止;周期块只维护本次执行变量。
      vars.tickCount = args.start
      vars.millisecondCount = args.start
      timer("tick_counter", 100) {
        vars.tickCount = vars.tickCount + 1
      }
      interval("millisecond_counter", 10000) {
        vars.millisecondCount = vars.millisecondCount + 1
      }

crons:
  online_notice:
    enabled: true
    schedule: "*/5 * * * *"
    timezone: "Asia/Shanghai"
    target: online_players
    method: notify_player
    args:
      - "欢迎 %player_name%"

  noon_console_announcement:
    enabled: true
    schedule: "0 0 12 * * *"
    timezone: "Asia/Shanghai"
    target: console
    method: console_announcement
    args:
      - "每日中午公告"

  ten_second_example:
    # 这是 6 段表达式示例;默认关闭,避免测试服刷屏。
    enabled: false
    schedule: "*/10 * * * * *"
    timezone: "Asia/Shanghai"
    target: console
    method: console_announcement
    args:
      - "十秒表达式测试"

建议按以下顺序验收:

text
/chaui reload
/chaui script Steve notify_player 欢迎回来
/chaui script Steve repeating_state 0
/chaui script stop <repeating_state 返回的执行ID>

随后把 ten_second_example.enabled 临时改为 true,reload 后观察 6 段表达式;确认后立即关闭,避免持续公告。

Cron 生命周期与并发规则

  • 服务器停机期间错过的时间不会在启动后补跑;
  • 同一 Cron 的同一目标仍有活动根执行时,跳过这一次触发;
  • online_players 的每名玩家是独立目标,一人失败或重叠不影响其他人;
  • 玩家离线时,属于该玩家的通用脚本执行及周期任务停止;
  • 插件禁用时,全部通用脚本执行和 Cron 调度停止;
  • /chaui reload 只替换后续方法目录与 Cron 时间表,已经启动的执行继续使用启动时的方法快照,直到完成或被停止;
  • 配置中单个方法或 Cron 无效时,只跳过对应条目并记录原因;整个 YAML 无法读取时,继续使用上一份有效配置。

允许的动作与边界

通用方法可以使用条件、数学、args.*vars.*、有界 repeat、timer / interval、方法互调,以及以下一次性动作:

  • cmd / 指令:目标玩家执行命令;
  • opcmd / 管理员指令:以临时管理员身份执行;
  • consolecmd / 控制台指令:控制台执行;
  • open / 打开:为目标玩家打开已加载页面;
  • openSub / 打开子页面:为目标玩家打开子页面;
  • global / 调用通用方法:调用另一个通用方法。

它不能使用 component(...)、页面变量、页面生命周期、容器槽位、原版.* 设置或客户端本地菜单动作。方法没有目标玩家时,玩家相关动作不会获得一个虚构玩家上下文。

排错

现象原因修正
reload 后提示方法被跳过参数列表、脚本语法、动作名或调用链不合法从日志中的 methods.<id> 首个原因修正;其他合法方法仍可加载
命令提示参数数量不对命令 token 数与 parameters 不一致按声明顺序逐个传入;长字符串改为单 token 或固定脚本文案
数字或布尔参数失败number 不是有限数字,或布尔值不是 true/false检查参数类型,不依赖自动字符串转换以外的猜测
页面调用被拒绝使用了页面变量、公式、null 或动态方法 ID页面入口只传字符串、有限数字、布尔值三类字面量
周期块中的命令导致方法失效重复结构禁止高权限动作用 Cron 每次启动一次独立方法;周期块只维护受限执行状态
console Cron 的 PAPI 参数失败控制台目标不解析任何 %...% 占位符改为 online_players,或删除全部 PAPI 占位符
Cron 不触发段数、数字范围、时区或 enabled 错误使用 5/6 段数字表达式和合法 IANA 时区
停止返回未找到ID 已完成、已停止、拼写错误或属于旧记录只停止仍有周期任务的当前执行 ID
方法互调突然终止包含根方法在内超过 8 层缩短调用链并移除递归环
到点没有重复启动同一 Cron 的同一目标上次执行仍活动停止旧 ID,或让方法在下次触发前自然结束
reload 后旧执行行为没变活动执行保留启动时方法快照停止旧 ID,再启动新执行;这是防止运行中脚本被半途改写的正常行为

页面脚本的计时器、组件操作和生命周期仍请阅读循环与计时器生命周期与自定义方法