Skip to main content
Read this first. 这一页给正在帮用户写 cheart 脚本的 AI 读。 完整 API 签名看左边侧栏的其他页;这里只放 你必须知道但容易错 的东西。

环境

  • 语言:BeanShell 3.0(不是 Java 8 也不是 Groovy,有自己的一套规则)
  • 宿主:Minecraft 1.20.1 Forge 47.3+,Java 17 toolchain
  • 脚本位置:用户用 .scripts folder 打开(路径不要硬编码)
  • 热重载.scripts reload [<name>],重载会保留 enabled / 键位 / 属性值
  • 加载顺序_ 开头 → 含 libs → 其他;先加载的脚本可以 bridge.addPublicMethod 把工具发布给后面的

BeanShell 陷阱(AI 最常写错的)

1. 颜色 / 大十六进制字面量是 long,不是 int

BSH 对 0xFF4CAF50 这种无符号值 > Integer.MAX_VALUE 的十六进制自动判成 long。API 的 color 参数全部是 long,直接传就行:
不要写:
比较两个颜色 bit 模式时走位掩码:((long)intVal) & 0xFFFFFFFFL == hexLong

2. 事件值不要用 (Number) / (int) 强转

BSH 对 Object → 抽象类 cast / Object → primitive cast 支持脆弱:
ScriptEvent 上的 getInt/getNumber/getBool/getString 都在 Java 侧做 instanceof Number 转型,BSH 不参与。

3. 未类型化变量在 if/else 里定义会成块作用域

修:在 if 前先初始化一次:

4. 不能 implements Runnable

BSH 在本客户端不能实现 Java 接口。所有”回调”都用方法名字符串

注解

@Module@Command@EventTarget@OnLoad / @OnUnload / @OnEnable / @OnDisable 直接写就行。注解字段支持 name="..."category="..."defaultEnabled=truekey=71(GLFW keycode)等。

时机硬规则

错了会炸 / 行为不一致,没有补救:

异步 / 线程安全

  • 脚本事件一律在主线程派发(BeanShell interpreter 单线程执行)
  • me.async("method") → 后台线程跑 method;方法里不能碰 MC 状态(碰了会和主线程竞争崩)
  • 后台拿数据,切回主线程用事件或标志位:
  • 绝不要在 tick / render_2dHttpClient.getThread.sleep、文件 IO、大循环(60fps × 1ms delay = 卡顿)
  • bridge 的 shared map 用 ConcurrentHashMap,跨脚本读写安全

事件取消 / 写回

很多事件可以 cancel 或改参数:
字段详见 events-reference

配置持久化(自动)

脚本 module 的这些状态自动存到 cheart 配置、下次启动恢复、.scripts reload 也保留:
  • enabled 状态
  • 键位绑定(key 字段)
  • 所有注册的属性值(slider / boolean / mode / color / list / …)
不自动保存的:脚本里的全局变量(比如 int counter = 0;)—— reload 后回到文件里的初值。需要跨 reload 持久化的状态请塞进属性(vm.registerTextvm.registerSlider 隐藏掉)或者写到 bridge shared map。

常见安全问题

崩溃风险

  • 在非主线程碰 MC:async 回调里直接调 me.getPlayer() / render.* / packet.send → 并发崩
  • 无限循环:BSH 没有超时保护,while (true) 冻死
  • 深度递归:BSH 帧栈小,invokeMethod 递归 > 几百层炸栈
  • 空指针:所有 API get* 方法在目标不存在时返回 null / 0 / false,但脚本自己链式调时要 null check:

服务端检测(防封号)

  • 超 reach 交互useItemOn 距离 > 5 格会被服务端拒绝或记录
    • me.placeBlock 前先检查 pos.distanceTo(player.getPosition()) < 4.5
  • 同 tick 多次发位置包:Blink / TimerHack 类脚本切勿一 tick 发 > 1 个 motion
  • HTTP 请求自己服务器HttpClient.* 请求会暴露 IP(服务器 log 看得到)——不要请求外网泄露玩家数据

自身脚本崩溃不会影响其他脚本

每个事件 handler 都有 try/catch 兜底,单脚本抛异常会被 ScriptLog.error 打出来但不传染。但启动时 @OnLoad 抛异常会标记脚本 FAILED,后续事件不会派发给它。

不要做的事

  1. ❌ 改其他脚本的属性 / 状态(除非通过 bridge)——reload 时序不可控
  2. me.unsafe().invoke(...) 调有副作用的 Forge 内部 API——可能崩服务端或 mod 兼容
  3. ❌ 把密钥 / token 硬编码进脚本——脚本文件用户肉眼可见
  4. bridge.clear()——影响所有脚本的共享数据,慎用
  5. @OnLoad 里发包 / 调 me.getPlayer()——此时玩家可能还没进服务器
  6. ❌ 写文件到 cheart 目录——用 me.unsafe() 配合 java.io.File 写到 <gameDir>/config/yourscript/

顶层对象速查(看详细签名跳详情页)

  • me — 玩家 / 世界 / 动作 / raytrace / 异步
  • moduleManager — 查询 / 开关所有模块
  • inventory — 背包 / 容器 / slot spoof
  • packet — 47 个 serverbound 包工厂 + 发送
  • notify — 右上角通知
  • bridge — 跨脚本共享 / 调用
  • render — 2D 绘制 + world→screen 投影
  • render3d — 3D 世界绘制

包装类速查

可工作的模板

1. 带属性的模块 + tick 事件

2. 2D ESP(render_2d 里画框)

3. 3D 世界文字

4. Scaffold(注意 player_update + moveFix)

5. 跨脚本工具库

6. 异步 HTTP + 主线程消费

7. 自定义 clickgui(registerScreen)

调试流程

脚本没按预期工作,按顺序检查:
  1. .scripts list — 状态 ✗ failed 说明加载时崩了,.scripts info <name> 看错误
  2. .scripts events — 事件订阅数为 0 说明 @EventTarget 没生效(可能写错事件名)
  3. 在可疑方法第一行加 me.log("reached") 看是否进入
  4. 运行时错误通过 ScriptLog 输出到控制台(不是聊天),去看 logs/latest.log
  5. ClassCastException 多半是 (int) / (Number) 硬转,改用 event.getInt 类型方法
  6. InterpreterError: cannot assign <number> to type int = 十六进制太大,用 long 接

最后提醒

  • 代码里少写类型声明,BSH 里 double x = 1.5x = 1.5 都能跑但后者在某些块作用域会出问题——看第 3 条陷阱
  • API 调用失败都是软返回,不抛异常;脚本作者需要自己判空
  • 每次改完脚本文件后 .scripts reload <name> 就生效,不用重启游戏
  • 写之前建议先看目标 API 详情页,上面有更完整的签名 / 参数说明