RPG Script (Stats)
RPG Script (.rpgs) is a compact language for defining base and calculated stats.
It compiles into the same JSON format the app uses internally.
Scope (current): stats + formulas only (StatFormulaComponent trees).
Not supported yet: authoring effects/mechanics via RPG Script.
File structure
A .rpgs file contains a list of stat declarations:
basedeclarations define default values.calcdeclarations define a formula (an expression) that compiles into a formula JSON tree.
Declarations
Base stat
Syntax:
base <type> <id>(<meta args>) = <literal>;
Example:
base integer level(name="Level") = 1;
base string character_name(name="Name") = "";
base bool is_npc(name="NPC?") = false;
Important: base defaults are literal-only (numbers, strings, bool, null, list literals).
Types
Supported type forms:
stringintegerdecimalboolresource<resource_type>(e.g:resource<spell>)- Arrays:
T[](e.g.integer[],resource<spell>[])
Examples:
base resource<spell> favorite_spell(name="Favorite Spell") = null;
base resource<spell>[] prepared_spells(name="Prepared Spells") = [];
Comments
- Line comments:
// ... - Block comments:
/* ... */
Expressions
Calculated stat expressions compile into StatFormulaComponent JSON.
Literals
"text"123,12.34true,falsenull- List literals:
[1, 2, 3],["a", "b"],[]
Identifiers
An identifier is treated as a stat reference:
strength + proficiency_bonus
Compiles as
{
"type": "add",
"components": {
"type": "list",
"components": [
{
"type": "stat",
"stat": "strength"
},
{
"type": "stat",
"stat": "proficiency_bonus"
},
]
}
}
As with regular RPG JSON, Identifiers may include
.and?inside the name. Names starting with$are reserved for lambda params / special variables
Operator precedence
From lowest → highest:
if ... then ... else ...||&&??==,!=<,<=,>,>=+,-*,/,%- unary
!, unary- - grouping
(...), calls, literals
Special expressions
Null coalescing: ??
a ?? b
Compiles into:
{
"type": "defaultIfNull",
"components": <a>,
"default_components": <b>
}
If/then/else
if condition then x else y
This is lowered to a single when component with clauses, including a final true fallback.
When expression
when {
cond1 -> value1,
cond2 -> value2,
else -> fallback
}
This compiles to:
{
"type": "when",
"clauses": [
{ "condition": <cond1>, "components": <value1> },
{ "condition": <cond2>, "components": <value2> },
{ "condition": { "type":"constant","value_type":"bool","value":true }, "components": <fallback> }
]
}
Function / component calls
A call compiles to a component JSON object:
contains(haystack=spells, needle="Fireball")
Compiles to:
{ "type": "contains", "haystack": <...>, "needle": <...> }
Positional first argument sugar
The first argument may be passed without a name — it becomes components = <expr>:
add( [1, 2, 3] ) // sugar for add(components=[1,2,3])
After the first argument, all arguments must be named.
Lambdas (map / reduce / filter / sortBy / findFirst / flatMap)
Lambdas are only valid as arguments to supported components.
Lambda forms: (x, y) => expr, (z) => expr2
Operator behavior notes
+ chooses between add and concat
- If any operand is a
listliteral,+becomesconcat. - If any operand is a
stringliteral,+becomesconcat. - If all operands are
numericliterals,+becomesadd. - Otherwise (stats / calls / mixed), it defaults to numeric semantics (
add) and the evaluator decides validity.
- becomes n-ary subtract
a - b - c
→ subtract(list(a,b,c))
*, &&, || are flattened to n-ary
a * b * c -> multiply(list(a,b,c))
a && b && c -> and(list(a,b,c))
a || b || c -> or(list(a,b,c))
Practical examples
Proficiency bonus (rounded up)
calc integer proficiency_bonus(name="Proficiency Bonus", abbreviation="PB") =
1 + divide(components=[level, 4], rounding_method="up");
Conditional stat (if → when)
calc integer speed =
if has_armor then 25 else 30;
Defaulting null
calc string title = nickname ?? character_name;
Complex example
calc integer classes_hp_mods(name = "Classes HP mods") =
max(
[
0,
(level * max(
map(
classes,
mapper = ($class) => add(
map(
($class.class.selected_archetype?.actual_hp_modifiers ?? $class.class.actual_hp_modifiers),
mapper = ($mapValue) => metaStat(meta_stat = concat([$mapValue, "_modifier"])),
),
),
),
)),
],
);