
公式加载器
为模组和资源包用户加载JSON到公式中。
查看大图一款面向开发者和数据包作者的模组。它可以从 JSON 文件中读取自定义公式,并通过游戏内指令或 Java 方法传入参数进行求值:
/formula <namespace:name> <parameters...>
支持的操作
算术运算符: +, -, *, /, % (向下取整余数:a - floor(a / b) * b)
双参数公式: min(x, y), max(x, y), pow(x, y), log(x, y) (以 x 为底 y 的对数), div(x, y) (向负无穷大方向的整数除法)
单参数公式: round(x), floor(x), ceil(x), abs(x), exp(x), trunc(x) (向零取整)
三角函数: sin(x), cos(x), tan(x), asin(x), acos(x), atan(x)
特殊函数 - rem(x, y): 返回余数 x - floor(x / y) * y,同时将商 floor(x / y) 写回 x。请注意,如果编写 #a = rem(#a, #b),赋值操作 #a = ... 会用返回值(余数)覆盖 rem 刚刚存入 #a 的商,因此 #a 最终保存的是余数,而非商。
单目负号无需括号即可使用:-3、-x、-min(a, b)、1 + -x 均可正常工作。
性能
公式在加载时被预编译为基于栈的字节码虚拟机,并进行了优化。短公式(4个或更少操作)受益于循环展开和超指令合并(例如,变量-变量二元运算成为单个操作码)。编译时会应用常量折叠。在典型桌面 JVM 上的基准测试:
- 简单算术:每次求值约 9 纳秒
- 链式条件(3 个分支):每次求值约 15 纳秒
- 顺序步骤(3 步):每次求值约 14 纳秒
- 三角运算带舍入:每次求值约 40 纳秒
实际上,即使是 300 万次调用也只需不到 120 毫秒。性能无需担忧。
客户端安装
此模组在客户端是可选的。如果只需要服务端计算,则无需安装在客户端。如果需要在 UI 元素中显示数值,则需安装在客户端以提供本地公式。当另一个需要双端安装的模组(例如 Tinkers' Armor Extension)深度集成本模组时,本模组成为了其传递性依赖,因此也必须在客户端安装。
开发者指南
- 通过
FormulaManager.getOrNull(ResourceLocation)访问已加载的公式,并使用formula.accept(double...)进行求值。 - 使用
FormulaBuilder以编程方式构建公式 JSON。公式在启动时从data/<namespace>/formula/目录加载。
数据包创建者指南
使用 /formula 指令在游戏中传入参数并求值。这取代了原本需要数十条指令的基于计分板的计算,一次调用即可完成,显著提高了数据包的性能和可读性。
JSON 格式参考
简单公式
{
"inputs": ["result", "left", "right"],
"formula": "#result = #left + #right * 2"
}
顺序步骤
{
"inputs": ["a", "b", "c"],
"formula": [
"#a = #b + #c",
"#b = #a * #c",
"#a = #b + 1"
]
}
If/Else 分支
{
"inputs": ["score", "kills", "deaths"],
"formula": {
"if": "#kills > #deaths",
"formula": "#score = #kills * 3 - #deaths * 2",
"else": "#score = max(0, #kills - #deaths)"
}
}
条件链 (if / else-if / else)
{
"inputs": ["result", "value"],
"formula": {
"chain": [
{"condition": "#value >= 100", "formula": "#result = #value * 2"},
{"condition": "#value >= 50", "formula": "#result = #value * 1.5"},
{"condition": "#value >= 10", "formula": "#result = #value"}
],
"default": "#result = 0"
}
}
混合嵌套 (顺序 + 条件)
{
"inputs": ["result", "base", "modifier"],
"caches": ["scaled"],
"formula": [
"#scaled = log(2, #base + 1)",
{
"chain": [
{"condition": "#modifier > 10", "formula": "#result = #scaled * #modifier * 2"},
{"condition": "#modifier > 0", "formula": "#result = #scaled * #modifier"}
],
"default": "#result = #scaled"
}
]
}
显式输出槽
默认返回最后一步的结果。使用 "output" 可以指定其他变量:
{
"inputs": ["dummy", "x", "y"],
"caches": ["tmp", "extra"],
"output": "#extra",
"formula": [
"#tmp = #x * #y",
"#dummy = #tmp + #x",
"#extra = #tmp * 2 + #y"
]
}
数值模式 (缩写形式)
{
"input": 3,
"cache": 2,
"output": "#4",
"formula": [
"#0 = #1 + #2",
"#3 = #0 * #1",
"#4 = #3 + #2"
]
}
FormulaBuilder — 开发者指南
FormulaBuilder 是一个流畅的 API,用于在 Java 中生成公式 JSON。它会自动为命名变量添加 # 前缀。
I. 快速入门
// 命名变量 — 自动添加 # 前缀。"result = x + y" 会变成 "#result = #x + #y"
String json = FormulaBuilder
.inputs("result", "x", "y")
.flat("result = x + y + 2")
.build();
// 数值模式 — 手动编写 #
String json = FormulaBuilder
.input(3)
.flat("#0 = #1 + #2 + 2")
.build();
II. 定义输入
// 命名输入 (推荐) — 自动添加 # 前缀
FormulaBuilder.inputs("result", "amount", "tick")
// 数值输入 — 生成 "input": 3;手动编写 #0, #1, #2
FormulaBuilder.input(3)
III. 定义缓存 (中间变量)
// 命名缓存 — 自动添加 # 前缀
.caches("tmp1", "tmp2") // 生成 "caches": ["tmp1","tmp2"]
// 数值缓存 — 手动编写 #3, #4
.cache(2) // 生成 "cache": 2
IV. 输出字段
.output("#result") // 指定要返回的变量
// 如果省略,则返回最后一步的结果。
V. 公式形态
flat — 单个公式字符串。返回 FormulaBuilder。
.flat("result = x + y + 2")
array — 顺序步骤。每个步骤可以读取之前步骤的结果。返回 FormulaBuilder。
.array(
"a = b + c",
"b = a * 2"
)
ifelse — 简单的 if/else 简写。返回 FormulaBuilder。
.ifelse(
"kills > deaths", // 条件
"score = kills * 3", // if-true 分支
"score = deaths" // else 分支
)
mixed() — 进入一个 MixedBlock<P>。条目按顺序执行,可以包含 flat 公式、内联条件和嵌套块。调用 .exit() 返回父上下文(P 类型参数)。
b.inputs("a", "b").mixed()
.flat("a = b + 1") // 纯公式
.cond("b > 10", "a = b * 2") // 内联条件简写
.exit() // 返回 FormulaBuilder
.build();
在 MixedBlock 内部,可以使用以下方法:
.flat(String)— 单个公式.array(String...)— 顺序公式.cond(String condition, String formula)— 内联条件简写.cond(String condition)— 进入一个CondBlock<MixedBlock<P>>以处理复杂主体.ifelse(String cond, String ifTrue, String ifFalse)— if/else 简写.chain()— 进入一个ChainBlock<MixedBlock<P>>.mixed()— 进入一个嵌套的MixedBlock<MixedBlock<P>>
cond(String) — 进入一个 CondBlock<P>,代表一个 {"condition":..., "formula":...} 条目。支持 .flat(), .array(), .mixed()。.exit() 返回父对象。
b.inputs("a", "b").mixed()
.cond("b > 10").mixed() // 通过嵌套 mixed 实现复杂主体
.flat("a = b * 2")
.flat("a = a + 1")
.exit() // 退出嵌套 mixed
.exit() // 退出 CondBlock,返回 MixedBlock
.exit()
.build();
chain() — 进入一个 ChainBlock<P>。条件从上到下测试;第一个匹配的被执行。如果都不匹配,则执行 def 分支。.exit() 返回父对象。
b.inputs("result", "value").chain()
.cond("value >= 100", "result = value * 2") // 内联: cond(c, f)
.cond("value >= 50").mixed() // 复杂主体的 CondBlock
.flat("result = value * 1.5")
.exit()
.def("result = 0") // 默认分支
.exit()
.build();
在 ChainBlock 内部,可以使用以下方法:
.cond(String condition, String formula)— 内联条件简写.cond(String condition)— 进入一个CondBlock<ChainBlock<P>>.def(String formula)— 默认分支的单个公式.defArray(String... formulas)— 默认分支的顺序公式.defMixed()— 进入一个MixedBlock<ChainBlock<P>>作为默认分支
VI. 嵌套块总结
| 入口点 | 返回类型 | 可用方法 | 退出返回 |
|---|---|---|---|
.mixed() |
MixedBlock<P> |
.flat() .array() .cond() .ifelse() .chain() .mixed() |
P |
.cond(String) |
CondBlock<P> |
.flat() .array() .mixed() |
P |
.chain() |
ChainBlock<P> |
.cond() .def() .defArray() .defMixed() |
P |
参数化的 P 类型在编译时追踪父上下文 — .exit() 总能在不进行类型转换的情况下返回正确的父类型。
VII. 构建输出
.build() // 返回 String — 格式化的 JSON,用于写入文件
.buildFormula() // 返回 JsonFormula — 用于代码中直接求值
VIII. 自动添加 # 前缀规则
声明 inputs("result","x","y") 后,以下字符串会被自动处理:
| 输入 | 输出 |
|---|---|
"result = x + y" |
"#result = #x + #y" |
"max(0, min(1, x))" |
"max(0, min(1, #x))" |
"x >= 10" (条件) |
"#x >= 10" |
当 # 前缀已存在时,不会再次添加:"#result = #x + #y" 保持不变,数字引用如 "#0 = #1 + #2" 也不受影响。
重要提示: 不要使用公式名称来命名变量(避免使用 min、max、pow、log 等作为变量标识符)。
IX. JSON 格式对应关系
| 构建器方法 | 生成的 JSON |
|---|---|
.flat("x") |
"formula": "..." |
.array("a","b") |
"formula": ["...", "..."] |
.chain() ... .exit() |
"formula": {"chain": [...], "default": ...} |
.ifelse(c,a,b) |
"formula": {"if": "...", "formula": ..., "else": ...} |
.output("x") |
"output": "#x" |
.inputs("a","b") |
"inputs": ["a", "b"] |
.input(3) |
"input": 3 |
.caches("a","b") |
"caches": ["a", "b"] |
.cache(2) |
"cache": 2 |
X. 典型用例
// 基本算术
FormulaBuilder.inputs("r", "a", "b")
.flat("r = a + b * 2")
.build();
// 带缓存的顺序计算
FormulaBuilder.inputs("r", "base", "mod")
.caches("logval")
.array(
"logval = log(2, base + 1)",
"r = logval * mod"
)
.build();
// 多条件链
FormulaBuilder.inputs("result", "val").chain()
.cond("val >= 100", "result = val * 2")
.cond("val >= 50", "result = val")
.def("result = 0")
.exit().build();
// If/else 简写
FormulaBuilder.inputs("score", "kills", "deaths")
.ifelse(
"kills > deaths",
"score = kills * 3 - deaths * 2",
"score = max(0, kills - deaths)"
)
.build();
// 数值模式 (无命名变量)
FormulaBuilder.input(3).cache(1).output("#3")
.array(
"#0 = #1 + #2",
"#3 = #0 * 2 + #1"
)
.build();
// 带条件和链的混合块
FormulaBuilder.inputs("a", "b", "c").mixed()
.flat("a = b + c")
.cond("b > 10", "a = a * 2")
.chain()
.cond("a > 100", "a = 100")
.def("a = a")
.exit()
.exit().build();
正在加载版本记录…


正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。