Creatures#
This page shows how to change what a creature is and how it defends itself, using two files: the profile in creatures.json and the rules in creature-rules.json.
Worked example: the golem#
A profile is the entry of one creature family in creatures.json. A definition is a named bundle of modifiers and listeners in creature-rules.json. A profile points to definitions by name. The shipped golem shows the whole chain.
Goal: make the golem a heavy, armoured creature that never dodges.
First the profile, from creatures.json. Only the fields that matter here are shown, so this snippet is partial.
{
"golem": {
"trait": "armored",
"armorWeight": "heavy",
"dodge": 0,
"ruleIds": ["armored-hide"]
}
}
Then the definition, in creature-rules.json. Again this is an extract, not a whole file.
{
"definitions": {
"armored-hide": {
"modifiers": [{ "hook": "defense.armorClass", "op": "add", "value": 0 }]
}
},
"traits": { "armored": ["armored-hide"] }
}
The modifier on its own is a normal modifier, so you can check it like any other:
[
{ "hook": "defense.armorClass", "op": "add", "value": 0 }
]
Every attack against the golem is checked against its armour class and its dodge. The value 0 changes nothing, so armored-hide is a template: replace the 0 with 2 and golems become harder to hit.
dodgeis the raw dodge chance in percentage points, from 0 to 100. A single creature can carry its own value instead.armorWeightislight,mediumorheavy. It scales the dodge. A profile without a weight counts as light.- The golem reaches
armored-hidetwice, throughruleIdsand through its trait. The rule is still granted only once.
Which file holds what#
| File | Section | What it holds |
|---|---|---|
creatures.json | one entry per family | Glyph, health and attack numbers, trait, ability, dodge, armorWeight, ruleIds. |
creature-rules.json | definitions | Named bundles of modifiers and listeners, and nothing else. |
creature-rules.json | traits, abilities, states | Lists of definition names, looked up by trait, ability kind or state. |
creature-rules.json | elites, variants, variantMagicResistances | Elite affixes, variant names and their magic resistance. |
creature-rules.json | hazards, monsterGrowth | What creatures leave on the floor, and how health and damage grow with level. |
Any other top-level key in creature-rules.json is refused when the game loads.
Traits#
A trait is a label on a profile ("trait": "armored"). The traits section maps each trait to a list of definitions, so every creature with that trait gets them. Use a trait when many families share a behaviour.
Abilities#
An ability is the special power of a family, such as fury or shockwave. The abilities section maps an ability kind to definitions. The shipped fury entry points to fury-recovery: a creature that just used its power is easier to hit.
[{ "hook": "defense.armorClass", "op": "add", "value": -2, "when": { "selfRecovering": true } }]
While the creature is recovering from its power, its armour class drops by 2. The recovery lasts one turn.
States#
A state is a temporary condition of the creature. The states section accepts four keys: lit, slowed, bound and poisoned. A definition under a state is active only while the creature is in it. Being lit or poisoned gives no defence bonus by itself: only the definitions you write do.
{
"definitions": {
"lit-glare": {
"modifiers": [{ "hook": "defense.armorClass", "op": "add", "value": 1 }]
}
},
"states": { "lit": ["lit-glare"] }
}
This extract is for reading: the game checks the states key when it loads. The key bound covers both frozen and entangled creatures.
Elites#
An elite is a creature that rolled extra affixes when its floor was generated. The elites section sets how many it gets (count, 0 to 10), the odds that a creature is elite (chance) and the pool of affixes.
Goal: elites get two affixes from this pool, with a 3% chance per floor of depth, capped at 25%.
{ "count": 2, "chance": { "base": 0, "perDepth": 0.03, "max": 0.25 }, "affixes": { "elite-evasive": { "name": "CREATURE_AFFIX_EVASIVE", "value": { "min": 2, "max": 4 } }, "elite-hardened": { "name": "CREATURE_AFFIX_HARDENED", "stackable": true } } }
A creature is elite with a chance of base plus perDepth for each floor below the first, never above max. elite-evasive rolls a value from 2 to 4 once and keeps it in the save. elite-hardened can be rolled twice, and each copy counts.
The definition behind elite-evasive reads the rolled number with $value:
[{ "hook": "defense.dodge", "op": "add", "value": "$value" }]
Variants and magic resistance#
A variant is a named version of a family, such as frost or runic. variants lists the names. variantMagicResistances gives a variant a fraction from 0 to 1 of spell damage it absorbs. A variant not listed there has no resistance.
Goal: make cursed creatures resist a quarter of spell damage, on top of the shipped values.
{
"creatureRules": {
"variantMagicResistances": { "ash": 0.5, "frost": 0.5, "runic": 0.5, "cursed": 0.25 }
}
}
A cursed creature takes 25% less damage from spells. The key must be a name from variants, or the game refuses to start.
The snippet shows only the part of creature-rules.json to change; the other sections stay as they are.
Another example: skeletons that dodge arrows#
Goal: skeletons dodge arrows better, and nothing else.
{ "hook": "defense.dodge", "op": "add", "value": 20, "when": { "kind": "ranged", "resource": "arrows" } }
Put this modifier in a new definition, skeleton-arrow-dodge, then add its name to the ruleIds of the skeleton profile. A creature is its own subject, so you do not need to filter by family.
Arrows the player shoots at a skeleton can be dodged. Bolts, melee and spells are not affected. The 20 points then go through the armour weight and the dodge cap, so a light skeleton dodges 20% of arrows and a medium one 10%.
- Only attacks that roll a die can be dodged. Area effects, traps, poison and burning cannot.
- Drop
resourceto cover every ranged attack, including shots fired by creatures.
Hearing and terrain#
Each profile has a hearingRadius. How far a noise carries is that radius times the noise intensity, capped at 12 in the shipped settings. The distance in a straight line must be within that reach.
Terrain then reduces the reach. Each cell between the noise and the listener multiplies it by its dampensSound factor divided by 100. A wall gives 0.05, a closed door 0.40, so two closed doors give 0.16. A cell without that component lets everything through, and opening a door restores it. See Terrain.
Advanced: activation order
A creature's rules are collected in this order: the profile's ruleIds, then its trait, then its ability kind, then its states in the order of the states keys. Elite affixes come last, in the order they were rolled. A definition reached by several paths is granted only once, at its first occurrence.
Advanced: cooldowns and memory per creature
A listener's cooldowns and stored values belong to one creature. Two creatures with the same rule never share them. Losing a trait does not reset the counters of a definition the creature still has, and entering a floor resets only that creature's per-floor cooldowns.
Advanced: opening doors
The optional opensDoors field, true or false, lets a family open ordinary doors. Opening uses the creature's whole action and makes the normal door noise.
What the validator refuses#
The game stops at the first error and names the file and the key. The usual causes:
- a
ruleIds, trait, ability or state entry that names an unknown definition; - a
dodgeoutside 0 to 100, or anarmorWeightthat is notlight,mediumorheavy; - a listener using an action that only works for the player, such as
gainResource,giveGoldorrestoreSatiety; $valuein a definition outside the elite pool;- a state key other than the four above.
The exact messages and fixes are in Troubleshooting.