This is a mod designed for developers and data pack creators, providing the ability to read custom formulas from JSON files and compute results by passing parameters through the
`/formula <formula> <parameters[]>
command in-game. The mod supports several basic functions, including:
`min(x,y)`, `max(x,y)`, `log(x,y)`, `pow(x,y)`, `floor(x)`, `round(x)`, `ceil(x)`
This mod employs the same parsing principles used by compilers. Loaded formulas are converted into highly optimized immutable objects. On the Java side, calling a formula 3 million times incurs an average overhead of just 29ms (short formulas benefit from super-instruction optimization, taking only about 9ms for 3 million calls; complex nested formulas are expanded via abstract syntax trees, taking around 150ms for 3 million calls — but realistically, who would invoke a complex formula that many times? If you need to repeatedly call something that complex, just write it directly in code). Performance is not a concern.
This mod is optional on the client side. If you only need silent calculations, you do not need to install it on the client. If you need to display values in certain UI interfaces, the client installation is required to provide the formulas. If the server includes mods that deeply integrate this mod's functionality (such as another mod of mine, Tinkers' Armor Extension), then since those mods are required on both sides, this mod becomes a transitive dependency and must also be installed on the client.
For Developers:
- You can conveniently access loaded functions via
FormulaManager.getOrNulland pass parameters usingf.accept(double...)to obtain return values. - You can use
FormulaBuilderto easily construct readable JSON files. Note that the mod's loading path is./data/{namespace}/formula.
For Data Pack Creators:
You can use the /formula command to pass parameters and perform calculations in-game, simplifying operations that would be extremely cumbersome with scoreboards and significantly optimizing data pack performance.
Some Examples:
// The most basic flat formula, with simple input and output
{
"inputs": ["result", "left", "right"],
"formula": "#result = #left + #right * 2"
}
// Chained calls
{
"inputs": ["a", "b", "c"],
"formula": [
"#a = #b + #c",
"#b = #a * #c",
"#a = #b + 1"
]
}
// if-else branching
{
"inputs": ["score", "kills", "deaths"],
"formula": {
"if": "#kills > #deaths",
"formula": "#score = #kills * 3 - #deaths * 2",
"else": "#score = max(0, #kills - #deaths)"
}
}
// if-elseif-else conditional chain
{
"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"
}
}
// Mixed nesting
{
"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"
}
]
}
// Explicitly specifying the output (by default, the result of the last expression is returned)
{
"inputs": ["dummy", "x", "y"],
"caches": ["tmp", "extra"],
"output": "#extra",
"formula": [
"#tmp = #x * #y",
"#dummy = #tmp + #x",
"#extra = #tmp * 2 + #y"
]
}
// Threshold processing
{
"inputs": ["result", "raw", "count"],
"formula": "#result = round(max(0, #raw) / max(1, #count)) + floor(#raw * 0.1) + ceil(#raw * 0.01)"
}
// Abbreviated mode (trading readability for writing speed)
{
"input": 3,
"cache": 2,
"output": "#4",
"formula": [
"#0 = #1 + #2",
"#3 = #0 * #1",
"#4 = #3 + #2"
]
}
Developer Guide:
FormulaBuilder is a streaming API for programmatically generating formula JSON strings in Java code.
It automatically adds a # prefix to named variables.
I. Quick Start
// Named variables — automatic # prefix, "result = x + y" becomes "#result = #x + #y"
String json = FormulaBuilder
.inputs("result", "x", "y")
.flat("result = x + y + 2")
.build();
// Numeric references — # must be written manually
String json = FormulaBuilder
.input(3)
.flat("#0 = #1 + #2 + 2")
.build();
II. Defining Inputs
// Named inputs (recommended) — automatic # prefix
FormulaBuilder.inputs("result", "amount", "tick")
// Numeric input — generates "input": 3, write #0, #1, #2 manually in formula
FormulaBuilder.input(3)
// Choose one; cannot use both
III. Defining Intermediate Variables (caches)
// Named caches — automatic # prefix
.caches("tmp1", "tmp2") // generates "caches": ["tmp1","tmp2"]
// Numeric caches — write #3, #4 manually
.cache(2) // generates "cache": 2
// Can mix: inputs("a","b").cache(1) → valid
// Can skip: if caches/cache not called, JSON will not contain cache field
IV. Output Field
// Specify which variable's value to return; returns last step's result if not specified
.output("result") // generates "output": "#result"
.output("#3") // numeric references require manual #
V. Formula Types
1. flat — Single formula string
.flat("result = x + y + 2")
2. array — Execute multiple formulas sequentially; later formulas can read results of earlier ones
.array(
"a = b + c",
"b = a * 2"
)
3. mixed — Mixed array: strings + conditional objects
mixed(
"tmp = a + b + 2",
cond("tmp == 12").flat("b + result + a")
)
.def("tmp1 = b + y") // Adds default to the last condition in mixed
VI. Conditional Branches
1. chain — Multi-condition chain, matches from top to bottom, stops on first match
.chain(
cond("value >= 100").flat("result = value * 2"),
cond("value >= 50").flat("result = value * 1.5"),
cond("value >= 10").flat("result = value")
)
.def("result = 0") // Default value when no conditions match
2. chainDef — Same as above, but default passed directly as parameter
.chainDef(
new ConditionalEntry[]{ cond("x>0").flat("...") },
"result = 0"
)
3. ifelse — Single condition if/else shorthand
.ifelse(
"kills > deaths", // condition
"score = kills * 3", // if branch
"score = deaths" // else branch
)
// Generated JSON uses if/else shorthand format (not chain array)
VII. ConditionalEntry — Conditional Entry
Created using cond(), supports chaining:
// Instance method (recommended, automatic # prefix)
builder.cond("x >= 10").flat("result = x * 2")
// Static method (no automatic # prefix, for numeric mode)
FormulaBuilder.condRaw("#1 >= 10").flat("#0 = #1 * 2")
ConditionalEntry methods:
.flat("formula") // Single formula
.array("f1", "f2") // Multiple sequential formulas
.chain(cond1, cond2) // Nested condition chain
.chainDef(entries, def) // Nested condition chain with default
.def("f1", "f2") // Adds default value to chain
.ifelse(cond, ifBranch, elseBranch) // Nested if/else
VIII. Building Output
.build() // Returns String — formatted JSON string, for writing to file or data packet
.buildFormula() // Returns JsonFormula — for direct testing/evaluation in code
IX. Automatic # Prefix Rules
After declaring inputs("result","x","y"), the following strings are processed automatically:
| Input | Output |
|---|---|
"result = x + y" |
"#result = #x + #y" |
"max(0, min(1, x))" |
"max(0, min(1, #x))" |
"x >= 10" (cond parameter) |
"#x >= 10" |
The # prefix will NOT be added again in these cases:
"#result = #x + #y"// preserved as-is (# already present)"#0 = #1 + #2"// numeric references preserved as-is
Important Notes:
- Do not name variables after function names (avoid naming variables
min,max,pow,log,round, etc.) - Condition strings (cond parameters) also get automatic # prefix — just use the instance method
cond() - Use the static method
condRaw()to skip automatic # prefix
X. Correspondence with JSON Format
| Builder Method | Generated JSON Structure |
|---|---|
.flat("x") |
"formula": "..." |
.array("a","b") |
"formula": ["...", "..."] |
.chain(c1,c2) |
"formula": {"chain": [...], "default": ...} |
.ifelse(c,a,b) |
"formula": {"if": "...", "formula": ..., "else": ...} |
.def("x") |
Adds "default" field to previous conditional branch |
.output("x") |
"output": "#x" |
.inputs("a","b") |
"inputs": ["a", "b"] |
.input(3) |
"input": 3 |
.caches("a","b") |
"caches": ["a", "b"] |
.cache(2) |
"cache": 2 |
XI. Typical Use Cases
// Basic arithmetic
FormulaBuilder.inputs("r","a","b")
.output("r")
.flat("r = a + b * 2")
.build();
// Sequential calculation with caches
FormulaBuilder.inputs("r","base","mod")
.caches("logval")
.array(
"logval = log(2, base + 1)",
"r = logval * mod"
)
.build();
// Multi-condition branching
FormulaBuilder.inputs("result","val")
.chain(
cond("val >= 100").flat("result = val * 2"),
cond("val >= 50").flat("result = val")
)
.def("result = 0")
.build();
// if/else shorthand
FormulaBuilder.inputs("score","kills","deaths")
.ifelse(
"kills > deaths",
"score = kills * 3 - deaths * 2",
"score = max(0, kills - deaths)"
)
.build();
// Numeric mode (no named variables, all # manual)
FormulaBuilder.input(3).cache(1).output("#3")
.array(
"#0 = #1 + #2",
"#3 = #0 * 2 + #1"
)
.build();
