Skip to content

脚本概览

AchieveMaster 现在这套脚本系统,已经是“统一编译入口 + 统一上下文 + 统一函数注册表”的结构,不再是早期那种靠零散正则去拼配置的实现。

如果你只先记住三件事,后面读文档会快很多:

  • 当前正式主路线是 AST,旧写法只负责兼容归一,不再作为扩展中心。
  • script-js 仍然保留,但它实际跑的是 AviatorScript 5.4.3,不是浏览器 JavaScript。
  • 触发脚本、奖励脚本、内置函数、动作函数、自定义函数、反射调用,最终都围绕 ScriptServiceFunctionRegistryScriptContext 这一组运行时工作。

入口分布

AchieveMaster 里真正会进入脚本系统的入口,主要有下面这些:

位置格式实际执行路线说明
trigger.script / trigger.脚本AST / TQS 风格脚本LegacySyntaxNormalizer -> StatementParser -> AST -> AstScriptExecutor当前触发阶段的正式主入口
trigger.script-js / trigger.jsAviatorScriptJsScriptEngine如果和 trigger.script 同时存在,优先走 script-js
rewards.script / rewards.脚本AST / TQS 风格脚本同上成就解锁后的奖励脚本
rewards.script-js / rewards.jsAviatorScriptJsScriptEngine如果和 rewards.script 同时存在,优先走 script-js
scripts/functions/yml/YML DSL加载后注册为函数适合做声明式包装函数
scripts/functions/tqs/AST / TQS 风格脚本编译成 AST 后注册为函数当前最自然的自定义函数写法
scripts/functions/java/Java 风格 DSL解析后注册为函数兼容已有 Java 风格函数定义
scripts/functions/js/AviatorScript .js / .av由 Aviator 加载并注册适合表达式型函数与桥接逻辑

同一个 script 字段本身还支持三种 YAML 载入方式:

  • 单行字符串
  • 多行 |
  • 列表格式,最终按换行拼接成一段脚本

执行链路

把一段脚本从配置写到服务器真正执行,中间大致会经过这几步:

  1. ConfigManager 从 YAML 里把脚本字段读出来。
  2. 调用方根据入口决定走 AST 还是 script-js
  3. AST 路线先做旧语法归一,再把语句解析成 AST,并进入脚本缓存。
  4. 运行时把 playerachievementevententityitem、成就进度、扩展数据等塞进 ScriptContext
  5. AST 执行得到 ScriptResult,Aviator 执行得到原始返回值并包装进 ScriptResult.returnValue
  6. 最后由“调用这个脚本的人”决定是否消费结果。

这最后一步很关键,因为 AchieveMaster 不是所有脚本入口都会把 ScriptResult 当成正式控制信号处理。

不同入口对结果的消费方式

这是当前最容易踩坑、也最值得先弄清楚的部分:

场景引擎谁消费结果实际含义
触发阶段 trigger.scriptASTTriggerManager会消费 shouldContinue、当前成就进度变化、目标进度变化,并保存数据
触发阶段 trigger.script-jsAviatorScript触发主流程几乎不消费返回值return true/false 不会自动推动或阻止成就;要生效必须直接调动作函数
奖励阶段 rewards.scriptASTAchievementManager 当前不消费 ScriptResult动作型函数会直接执行,但 返回停止脚本设置进度 这类依赖结果回写的语义不会额外回流
奖励阶段 rewards.script-jsAviatorScript同上返回值只保存在执行结果里,不会被奖励主流程继续解释
延迟执行 {} 代码块ASTDelayNode 自己消费延迟块结果延迟块里的当前成就进度 / 目标进度改动会在后续 tick 回写,但它是异步执行,不属于当前调用栈

所以推荐的理解方式是:

  • 触发脚本里,AST 的“当前成就语句”最适合做主流程推进。
  • 奖励脚本里,重点放在直接副作用,例如发消息、执行命令、联动外部插件。
  • script-js 里不要把 return true/false 当成主流程控制手段。

当前脚本系统的四层结构

从职责上看,这套脚本系统可以拆成四层:

1. 编译层

负责把配置里的文本脚本变成真正可执行的程序:

  • AST 走 LegacySyntaxNormalizerStatementParser
  • script-jsJsScriptEngine
  • 编译结果进入 ScriptProgramCache

这也是为什么现在文档必须以 AST 正式语法为准。旧语法能跑,是因为兼容层会把它改写成现在的规范写法,不代表旧语法仍是主设计目标。

2. 上下文层

ScriptContext 负责给脚本提供运行时数据。常见来源包括:

  • 当前玩家
  • 当前成就
  • 当前事件
  • 当前实体 / 物品
  • MythicMobs 数据
  • 任务数据
  • 成就进度对象和局部变量

AST 和 script-js 在少数变量上有语义差异,最典型的是:

  • AST 里 progress 默认是当前进度数值
  • script-jsprogressAchievementProgress 对象

变量解析、别名归一、字段访问、循环变量、延迟执行上下文拷贝等细节,统一看 变量与上下文

3. 函数与动作层

所有内置函数最终都会注册进同一个 FunctionRegistry。这意味着:

  • AST 里的函数调用能用
  • 自定义函数内部能用
  • script-js 也能通过包装层复用
  • 动作型函数和读取型函数只是“用途不同”,底层不是两套系统

当前源码实际暴露的是 238 个唯一函数名,精确结果以服务器运行时 /ach functions 输出为准。

4. 扩展层

这一层负责把脚本系统和外部世界打通:

  • 反射调用
  • 导入类
  • new 构造对象
  • DragonCore / PAPI / MythicMobs / AttributePlus 等集成函数
  • scripts/functions/ 自定义函数目录

AchieveMaster 这次脚本重构并没有砍掉反射能力,只是把它统一收进 AST 运行时,不再走旧解析器那套零散逻辑。

自定义函数的加载顺序

自定义函数并不是“谁后加载谁覆盖前者”。当前源码里的顺序是固定的:

  1. yml
  2. tqs
  3. java
  4. js

注册中心对同名函数采用“先注册保留,后注册跳过”的规则,因此:

  • 先加载的同名函数会保留
  • 后加载的重名函数会跳过
  • 别名冲突也会跳过,不会强行覆盖

如果你准备把一个旧函数逐步迁到 AST,要先确认是否还留着同名的 YML / Java / JS 版本。

什么时候该写脚本,什么时候不该写

不是所有成就都值得上大脚本。

更稳的分工一般是:

  • 简单判定:优先用触发器条件、基础进度配置、现成内置函数
  • 中等复杂度:在 trigger.script 里写 AST 分支、循环、变量和动作
  • 多处复用逻辑:抽成 scripts/functions/
  • 跨插件、跨类深度联动:最后再上反射或特定集成函数

这样做的好处很直接:

  • 成就配置本体更短
  • 复用逻辑不需要复制粘贴
  • 后面排查兼容问题时,能更快判断问题是在触发器、脚本、函数还是外部插件

运行时排查方式

源码能告诉你“理论支持什么”,但你服里真正注册成功了什么,最好用运行时命令直接看:

text
/ach functions
/ach functions 数学
/ach functions 发送消息
/ach triggers
/ach triggers REGION

这些命令适合确认:

  • 某个函数是否真的存在
  • 函数分类、参数、别名、用法是不是和文档一致
  • 当前服务器装的依赖插件有没有让集成功能真正可用

如果还要进一步排查脚本执行过程,再配合:

  • settings.debug: true
  • 控制台里的脚本编译错误 / 警告日志
  • 自定义函数加载日志

迁移建议

如果你是从旧脚本或旧配置迁过来的,建议直接按下面的思路处理:

  • 老配置先让兼容层托住,不要第一步就全服手工大改。
  • 新写脚本一律按 AST 正式语法写,不再继续扩展旧关键字习惯。
  • script-js 只在你明确需要 Aviator 表达式能力时再用。
  • 奖励脚本主要写直接副作用,不要依赖 返回 或“当前成就 AST 进度语句”去驱动奖励主流程。
  • 需要跨插件或反射时,先看有没有内置函数;实在没有,再进 反射与导入类

推荐阅读顺序

  1. 语法与表达式
  2. 变量与上下文
  3. 内置函数
  4. 内置动作
  5. 自定义函数
  6. 反射与导入类
  7. script-js / AviatorScript

最后一个结论

如果你只想知道“现在到底该怎么写”,答案其实很简单:

  • 触发主逻辑优先写 AST
  • 复用逻辑抽自定义函数
  • 外部联动优先内置函数,其次反射
  • script-js 当 Aviator 用,不要当浏览器 JS 用

按这个思路写,基本就不会再掉回旧解释器时代那种“看起来能跑、实际上语义不稳定”的坑里。

TQ Minecraft Server Plugin Docs