BELOW THE IRON Modding guide

How it works#

Every rule you write is one of two things: a modifier that changes a number, or a listener that reacts to something that happened. This page explains both, then conditions and the places where rules can live.

A modifier changes a number#

While the game plays, it often needs a number: the damage of an attack, the mana cost of a spell, the hunger a step costs. Each such question is a hook, for example attack.damage. The game gives a base number, and your modifiers adjust it.

A modifier has three parts: the hook it answers, an operation (op) and a value.

{ "hook": "attack.damage", "op": "add", "value": 2 }
In game

Every attack of the bearer deals 2 more damage.

The five operations#

All examples start from a base of 10, such as the mana cost of a spell.

{ "hook": "ammo.consumed", "op": "chance", "value": 0.2, "then": { "op": "mul", "value": 0 } }
In game

Each shot has a 20% chance to use no ammunition.

Several modifiers on one number#

The game applies all add first, then all mul, then min, then max. Writing order does not matter. With add 3, add 2 and mul 1.5 on a base of 10, the result is (10 + 3 + 2) × 1.5 = 22.5.

A listener reacts to an event#

An event is something that just happened, such as hit.dealt (you hit something) or floor.entered (you arrived on a new floor). A listener waits for one event and then runs an action, such as healing or poisoning. Unlike a hook, an event asks for no number back.

event            listener                       action
hit.dealt  --->  on: hit.dealt           --->  heal
                 when: critical is true        amount: 2
                 chance: 0.5
{
  "id": "crit-heal",
  "on": "hit.dealt",
  "when": { "critical": true },
  "action": "heal",
  "params": { "amount": 2 },
  "chance": 0.5,
  "cooldown": 10,
  "announce": "SYNERGY_EXAMPLE_TRIGGERED"
}
In game

When you land a critical hit, there is a 50% chance you heal 2 points of life. It cannot happen again for 10 turns, and the message log shows the text behind announce.

The catalogue lists 55 hooks and 50 events you can use, and 33 actions to run.

Conditions: the when field#

A condition says when a modifier or listener applies. It is a small object, and every entry in it must be true.

{ "targetFamily": "wolf", "targetBoss": false }

This is true only against a wolf that is not a boss. There is no "or" and no "not". For "or", write two rules. For "not", use the opposite value, such as critical: false.

The game knows 24 conditions. The catalogue lists them with the values they accept.

The golden rule: the context#

Each question or event comes with a context: the facts the game fills in at that moment, such as the target, the element or whether the hit was critical. A condition reads one of those facts.

For example, critical is provided by hit.dealt only. A listener on hit.taken with { "critical": true } would never run. The same goes for critical: false, because a missing fact is neither true nor false.

{ "hook": "attack.accuracy", "op": "add", "value": 2, "when": { "target": "lit" } }
In game

You are 2 points more accurate against a creature lit by a light spell.

A few conditions read your own state rather than the context: hpBelow, manaBelow and hungerBelow work on every event.

Where rules can live#

A modifier or a listener only exists while its source exists: take the item off and its rules go with it.

SourceFileCan hold
Affix effectequipment.json, affixEffectsModifiers and listeners
Synergyequipment.json, synergiesModifiers and one special effect
On-hit pipelineequipment.json, onHitListeners on hit.dealt
Item set bonusequipment.json, sets.equipmentSetsModifiers and listeners
Enchantmentequipment.json, enchantments.mechanicsModifiers and listeners
Forge catalystresources.json, craftingCatalystsModifiers and listeners
Creature rulecreature-rules.jsonModifiers and listeners
Skillcharacter.json, character.skillsModifiers that grow with the skill

The rules are written from the player's point of view: hit.dealt means the bearer hit something, hit.taken means the bearer was hit. Creatures have their own rules, and a rule on one creature never affects another.

Skills grow their rules#

A skill gives modifiers too, and they grow as the skill rises. Each effect is a modifier with one more field, which says how it grows:

Write exactly one of the two. The game reads the skill with its equipment bonus included. An every works with add, mul and chance: a mul of 0.95 every 20 points gives 0.90 at 40, and a chance of 0.05 gives 10 % at 40. The optional stat names the line of the skills screen that shows the gain, a key of the language files; effects that share a stat add up on one line.

{
  "blades": {
    "effects": [
      { "hook": "attack.damage", "op": "add", "value": 1, "every": 25, "when": { "kind": "melee" }, "stat": "SKILL_STAT_MELEE_DAMAGE" },
      { "hook": "attack.critRange", "op": "add", "value": -1, "from": 50, "when": { "kind": "melee" }, "stat": "SKILL_STAT_MELEE_CRIT" }
    ]
  }
}

This is the start of what Melee gives in the shipped game: one more damage every 25 points, and a critical hit from 19 at 50 points. A skill never changes skill.value, which would feed its own value; the game refuses to start instead.

Advanced#

Advanced: combining order and priority

Priority is a whole number, 0 by default. It does not change the result of the five operations, because adding and multiplying give the same answer in any order. It only changes the order of chance draws, and the order in which listeners run: higher priority goes first. Leave it at 0 unless you depend on a precise sequence of random draws.

Advanced: chance

A chance modifier draws once each time its hook is read. A chance on a listener draws once each time the listener could run, which means its condition is true and its cooldown is over. A draw below the chance succeeds. The then of a chance modifier is an add, mul, min or max, never another chance.

Advanced: cooldowns, identity and memory

cooldown is a number of turns, or "floor" to wait until the next floor. It starts only if the action actually ran. Each listener has a stable key that names its cooldown and memory in the save file. With an id, the key is that id. Without one, the key is built from what the listener does, so changing a parameter resets its cooldown in existing saves. Give an id to any listener you expect to tune.

Some actions keep a small memory between runs, such as the count of a wave. It is stored under the same key.

Advanced: no loops

While an event runs its listeners, any other event is ignored. An action therefore never triggers another event. A creature killed by an action does not raise mob.killed, though it still dies and drops its loot. To chain effects, put several listeners on the same event.

Advanced: determinism

Every random draw comes from the game's seeded generator, saved with the game. The same seed and the same commands give the same draws. Adding, removing or reordering a rule that uses chance changes the sequence, so an existing seed plays out differently. Rules without chance use no randomness. Language never influences the rules: announce is only a text key.

Next#