计算器指令
计算器是 OpenModsLib 唯一直接面向玩家的功能。通过客户端的 =* 指令可以即时求值数学表达式、定义变量与函数、切换数值后端,并从磁盘脚本批量执行。整个指令树的构建入口是 CommandCalcFactory(src/main/java/openmods/calc/command/CommandCalcFactory.java:35)。
引擎内部机制见计算器引擎,后端差异见类型后端,其余 OpenModsLib 指令见指令总览。
基本信息
| 属性 | 值 |
|---|---|
| 指令前缀 | =(由 CommandCalc 构造函数强制拼接 "=" + name) |
| 根组件构建 | CommandCalcFactory 的 root 字段(CommandCalcFactory.java:39-251) |
| 指令注册 | src/main/java/openmods/proxy/OpenClientProxy.java:101-105 |
| 开关 | LibConfig.enableCalculatorCommands(默认 true) |
| 脚本目录 | <游戏目录>/scripts(OpenClientProxy.java:99) |
| 侧限制 | 仅客户端,服务端无此指令 |
| 默认模式 | INFIX(CalcState.java:269) |
功能
根组件的完整子命令表
CommandCalcFactory.root(CommandCalcFactory.java:39)是一个 MapCommandComponent,含 6 个子命令:
| 根子命令 | 定义行 | 是否注册为顶层指令 | 组件类型 | 说明 |
|---|---|---|---|---|
config |
:40 |
是 → /=config |
MapCommandComponent |
管理计算器实例(8 个嵌套子命令) |
execute |
:163 |
是 → /=execute |
TerminalCommandComponent |
执行脚本文件 |
let |
:201 |
是 → /=let |
TerminalCommandComponent |
绑定全局变量 |
fun |
:211 |
是 → /=fun |
TerminalCommandComponent |
定义全局函数 |
eval |
:229 |
是 → /=eval(别名 /=) |
TerminalCommandComponent |
求值表达式 |
echo |
:245 |
否 | TerminalCommandComponent |
原样输出文本 |
echo 是本条目最需要注意的一处不对称:它在根组件中已完整实现(CommandCalcFactory.java:245-250,直接 sender.addChatMessage(new ChatComponentText(args.getTail()))),但 OpenClientProxy.java:101-105 只注册了 config/eval/fun/let/execute 五条,没有为 echo 注册顶层 ICommand。因此 /echo 不可直接输入,但 /=execute <脚本> 执行脚本时每行都经由 root.execute()(CommandCalcFactory.java executeScript 方法),echo 在脚本内完全可用。
CommandCalc 构造函数 this.name = "=" + name(src/main/java/openmods/calc/command/CommandCalc.java:27)强制给所有顶层指令加 = 前缀。=eval 注册时额外传入别名 "="(OpenClientProxy.java:102),得到 /=eval + 别名 /=。
/=config — 计算器实例管理
config 是唯一的嵌套 MapCommandComponent(CommandCalcFactory.java:40-161),含 8 个终端子命令:
| 子命令 | 定义行 | 参数 | 实际行为 | 非法输入的处理 |
|---|---|---|---|---|
new |
:43 |
<类型> |
新建计算器,类型取自 CalculatorType(5 选 1) |
抛 openmodslib.command.calc_invalid_type,并列出全部合法类型 |
load |
:65 |
<名字> |
切换到已命名计算器 | 抛 openmodslib.command.calc_invalid_name |
store |
:81 |
<名字> |
给当前计算器命名 | — |
pop |
:88 |
无 | 压栈(见下方命名缺陷) | — |
push |
:95 |
无 | 出栈(见下方命名缺陷) | 栈下溢抛 openmodslib.command.calc_stack_underflow |
set |
:106 |
<键> <值> |
设置计算器属性 | 键不存在抛 calc_invalid_key,其它异常抛 calc_cant_set |
get |
:130 |
<键> |
读取计算器属性 | 键不存在抛 calc_invalid_key |
mode |
:149 |
<模式> |
切换 PREFIX/INFIX/POSTFIX |
抛 openmodslib.command.calc_invalid_mode,列出全部合法模式 |
两个栈指令回报的栈大小分别使用 openmodslib.command.calc_after_push / calc_after_pop 语言键。
已核实缺陷:/=config push 与 /=config pop 的指令名与实际行为互换。
指令 pop(CommandCalcFactory.java:88)调用的是 state.pushCalculator()(:90)并回报 calc_after_push;指令 push(:95)调用的是 state.popCalculator()(:97)并回报 calc_after_pop。
CalcState 侧的实现本身正确且符合直觉(src/main/java/openmods/calc/command/CalcState.java):
public int pushCalculator() { // :294
calculatorStack.push(active); // :295 确实压栈
return calculatorStack.size();
}
public int popCalculator() { // :299
setActiveCalculator(calculatorStack.pop()); // :300 确实出栈
return calculatorStack.size();
}
即方法名与行为正确,指令名与所调用的方法相反。玩家输入 /=config pop 实际执行压栈,输出提示 “Pushed calculator, stack size: N”;StackUnderflowException 也只会在 /=config push(真正出栈)时抛出。需按行为而非名称理解。
/=eval — 表达式求值
用法 /=eval <表达式>。行为由当前模式(ExprType.hasSingleResult)决定:
- 单值模式(
PREFIX/INFIX):state.compileExecuteAndPrint(sender, expr),直接以文本形式回报结果 - 栈模式(
POSTFIX):state.compileAndExecute(sender, expr),不返回值,改为回报栈大小(openmodslib.command.calc_stack_size)
表达式解析失败时抛 openmodslib.command.calc_syntax_error_path,运行失败时抛 openmodslib.command.calc_runtime_error_path。
/=let 与 /=fun — 定义符号
| 指令 | 用法 | 行为 | 回报键 |
|---|---|---|---|
/=let |
=let <名字> <初始化表达式> |
state.compileAndSetGlobalSymbol,把结果绑为全局符号 |
openmodslib.command.calc_set |
/=fun |
=fun <名字> <参数个数> <函数体> |
state.compileAndDefineGlobalFunction,定义全局函数 |
openmodslib.command.calc_function_defined |
/=fun 的参数个数必须能解析为整数,否则抛 openmodslib.command.calc_invalid_number。
/=execute — 脚本执行
用法 /=execute <路径>,路径相对于 <游戏目录>/scripts。执行流程:
- 目录逃逸防护:
checkIsParent(scriptDir, scriptFile)逐级上溯getParentFile()比对规范路径,若目标不在scripts目录内则抛openmodslib.command.calc_not_child(CommandCalcFactorycheckIsParent方法)。这是明确的路径穿越防护。 - 文件存在性检查:非文件抛
openmodslib.command.calc_not_file - 逐行执行:
executeScript用BufferedReader逐行读取,每行经WhitespaceSplitters.fromString拆词后交给root.execute(sender, args),行尾#起始的注释行为空串,不产生副作用 - 回报:输出
openmodslib.command.calc_executed_count(已执行行数)
Tab 补全会扫描 scripts 下的文件与目录,目录补全时追加 /(CommandCalcFactory execute 分支的 getTabCompletions)。
计算器栈
CalcState(src/main/java/openmods/calc/command/CalcState.java:41)维护:
- 活动计算器
active(:265,初始为CalculatorType.DOUBLE) - 计算器栈 ——
pushCalculator(:294)/popCalculator(:299)/restorePreviousCalculator(:284) - 命名表 ——
nameCalculator(:304)/loadCalculator(:312)/getCalculatorsNames(:308) - 表达式模式
exprType(:269,默认INFIX)
popCalculator 在栈空时抛 StackUnderflowException,由指令层转为 openmodslib.command.calc_stack_underflow。
游戏内上下文符号
所有后端都由 SenderHolder.addPrinter 注入 p(打印栈值,零返回)与 print(取值,:78,101)。数值类后端额外注入发送者方块坐标:
| 符号 | 含义 | 注入位置 |
|---|---|---|
_x / _y / _z |
指令发送者的方块 X / Y / Z 坐标 | CalcState.java:128,136,144(DOUBLE)、:161,169,177(FRACTION)、:194,202,210(BIGINT) |
player |
玩家对象 | CalcState.java:229(MULTI 专有) |
坐标读取经 SenderHolder.getX/getY/getZ(CalcState.java:61,66,71),对非玩家发送者会因 Preconditions.checkNotNull 抛异常。
数值
| 数值名 | 值 |
|---|---|
| 根组件子命令数 | 6(config execute let fun eval echo) |
注册为顶层 ICommand 的 |
5(缺 echo) |
config 嵌套子命令数 |
8 |
| 计算器后端类型数 | 5(DOUBLE FRACTION BIGINT MULTI BOOL) |
| 表达式模式数 | 3 |
| Tab 补全覆盖的子命令 | config.new(后端名)、config.load/config.store(已命名计算器)、config.set/config.get(属性名)、execute(脚本文件与目录) |
交互
| 触发 | 行为 |
|---|---|
按下 = 键(无其它 GUI 打开时) |
CalcKey 打开聊天框并预填 = (src/main/java/openmods/calc/command/CalcKey.java) |
直接输入 /=<表达式> |
走 =eval 别名 |
| 聊天框未打开时按任意鼠标键 | 同样触发 CalcKey(CalcKey.onMouseInput) |
| 表达式求值出错 | 红色聊天消息(CommandCalc.processCommand 对 NestedCommandException 设 EnumChatFormatting.RED) |
CalcKey 注册的 KeyBinding 名称为 openmodslib.key.calc(显示名 “Open Calculator”),分类 openmodslib.key.category(显示名 “OpenMods”),默认键位为 Keyboard.KEY_EQUALS,可在选项视频设置中改绑。
错误处理
CommandCalc.processCommand(src/main/java/openmods/calc/command/CommandCalc.java:70)分三层处理:
NestedCommandException—— 推入命令名后转红色聊天消息- 其它
Exception—— 沿getCause()链收集全部消息,用', caused by '连接后抛CommandException("openmodslib.command.calc_error", ...) - 参数未消费完 —— 抛
openmodslib.command.calc_extra_args并回报剩余部分