Cookbook#
Pick what you want your mod to do, copy the JSON, and change the numbers. Every recipe shows the goal, the code, the result in game and a few variations.
Before you start#
- Recipes that live in a set bonus go in the
thresholdslist of a set. A threshold is one bonus that switches on when you wearpiecesitems of the set. - Recipes that live in a synergy go in
synergiesinequipment.json. A synergy switches on when an item carries one affix of each family listed inrequires. - A listener reacts to an event. Its
actionsays what to do,paramsare the action's settings,chance(0 to 1) makes it random, andcooldown(turns, or"floor") limits how often it fires. Give a listener anid: the game remembers its cooldown under that name, so you can tune numbers later without resetting saved games. - Text keys such as
SYNERGY_BLOODTHIRSTneed a line in all nine files ofpublic/config/locales/. - Set ids and numbers below are examples. Check the real ones in the files, and see How it works if modifier or listener are new words.
Hit harder#
Recipe 1: Heal on a critical hit#
Goal: an item that heals you every time you land a critical hit.
{
"id": "bloodthirst",
"label": "SYNERGY_BLOODTHIRST",
"requires": ["damage", "drain"],
"priority": 5,
"effect": {
"text": "SYNERGY_BLOODTHIRST_EFFECT",
"modifiers": [{ "hook": "hit.drain", "op": "add", "value": 1 }]
},
"special": {
"text": "SYNERGY_BLOODTHIRST_SPECIAL",
"announce": "SYNERGY_BLOODTHIRST_TRIGGERED",
"on": "hit.dealt",
"when": { "critical": true },
"action": "heal",
"params": { "amount": "$power" },
"power": 2
}
}
Every hit drains 1 more life point, and a critical hit also heals 2 (4 on an exceptional-quality item). Healing never goes above your maximum life.
Variations
- Add
"chance": 0.5to the special so only half of the critical hits heal. - Swap
healforrestoreManato refill mana instead.
Recipe 2: Frost that slows longer#
Goal: make every hit slow the enemy one turn more, and wolves three turns in all.
The game already slows an enemy for as many turns as the value hit.frost. You only raise that value. Here it is a three-piece set bonus:
{
"pieces": 3,
"modifiers": [
{ "hook": "hit.frost", "op": "add", "value": 1 },
{ "hook": "hit.frost", "op": "add", "value": 2, "when": { "targetFamily": "wolf" } }
]
}
Each hit slows for 1 extra turn. Against a wolf the slow lasts 1 + 2 = 3 extra turns.
Variations
- Use
"targetBoss": falseinstead of a family to skip bosses. - The
whenhere can look at the target, but not at an element: a hit has no element.
Recipe 3: Thorns#
Goal: enemies that hit you get hurt, and sometimes burn.
Thorns are a plain modifier, valid in any source:
{ "hook": "retaliation.damage", "op": "add", "value": 2 }
For burning thorns, add this listener to a set threshold:
{
"pieces": 3,
"listeners": [
{
"on": "hit.taken",
"when": { "attackerBoss": false },
"action": "burn",
"params": { "at": "attacker", "turns": 2, "power": 2 },
"chance": 0.5,
"cooldown": 2
}
]
}
Each time a creature hits you and you survive, it takes 2 arcane damage. Half of the time, a non-boss attacker also burns for 2 turns, at most once every 2 turns.
Variations
- The burn also reacts to monster spells, because a spell that hurts you raises
hit.takenfirst. - If the thorns damage kills the attacker, the burn does nothing.
React to what happens#
Recipe 4: Mana from hitting a wolf with a spell#
Goal: get mana back when a spell projectile hits a wolf.
{
"pieces": 3,
"listeners": [
{
"id": "wolf-mana",
"on": "spell.hit",
"when": { "targetFamily": "wolf", "kind": "projectile" },
"action": "restoreMana",
"params": { "amount": 2 },
"chance": 0.25,
"cooldown": 5
}
]
}
When a projectile spell reaches a wolf, you have a one in four chance to regain 2 mana, at most every 5 turns. Mana never goes above your maximum.
Variations
kindis how the spell is delivered:projectile,field,networkorself. Drop it to react to all of them.- Life, mana and hunger can be tested on any event. To heal when you are hurt below 30% life, listen to
hit.takenand use"when": { "hpBelow": 30 }.
Recipe 5: A shockwave every third blow#
Goal: every few hits, damage every enemy standing next to you.
{
"pieces": 4,
"listeners": [
{
"id": "third-blow-wave",
"on": "hit.dealt",
"action": "wave",
"params": { "every": 3, "element": "arcane", "amount": 2 }
}
]
}
Every third hit you land sends out a wave. All enemies next to you (within 1.5 cells) take 2 arcane damage.
Variations
- The count belongs to the listener. Keep its
id: a listener without one loses its count in existing saves when you edit its numbers. - The wave is always centred on you. Enemies it kills do not count as kills for other listeners.
Recipe 6: Once per floor#
Goal: the first creature you kill on each floor heals you.
{
"pieces": 3,
"listeners": [
{
"id": "first-kill-heal",
"on": "mob.killed",
"action": "heal",
"params": { "amount": 3 },
"cooldown": "floor"
}
]
}
The first kill on a floor heals 3 life. Other kills do nothing until you enter a floor again. The wait is saved with the game.
Variations
- A number, as in
"cooldown": 20, counts turns, not floors. - The cooldown is only spent when the listener really fires: a failed
chancecosts nothing.
Survive#
Recipe 7: Last breath#
Goal: get up once per floor after a killing blow.
{
"pieces": 4,
"listeners": [
{
"on": "player.dying",
"action": "heal",
"params": { "amount": 8 },
"cooldown": "floor"
}
]
}
The first deadly blow on a floor leaves you with 8 life instead of killing you. The next one on the same floor kills you. On the next floor it works again.
Variations
- Add
"chance": 0.3for a 30% chance to survive. - Damage that lands after the event, such as a second blow in the same turn, still kills.
Recipe 8: Emergency ration#
Goal: get food back when you become very hungry.
{
"pieces": 4,
"listeners": [
{
"on": "hunger.stageChanged",
"when": { "hungerStage": "veryHungry" },
"action": "restoreSatiety",
"params": { "amount": 15 },
"cooldown": "floor"
}
]
}
The moment your hunger drops to the "very hungry" stage (20 or less), you regain 15 satiety, once per floor.
Variations
- The event fires when you cross the line, not on every turn spent below it.
- To waste less food instead, add the modifier
{ "hook": "hunger.cost", "op": "mul", "value": 0.85 }to an effect: you lose 15% less satiety per action.
Build sets and synergies#
Recipe 9: A new set bonus#
Goal: add a three-piece bonus to a set that only has bonuses at two and four pieces.
{
"pieces": 3,
"modifiers": [
{ "hook": "hunger.cost", "op": "mul", "value": 0.9 },
{ "hook": "attack.accuracy", "op": "add", "value": 1, "when": { "target": "lit" } }
],
"listeners": [
{
"on": "floor.entered",
"action": "revealTraps",
"params": { "radius": 6 }
}
]
}
Add it to the thresholds list of the lanterniers set.
With three pieces worn you eat 10% less, hit lit targets more easily, and see traps within 6 cells when you enter a floor. The two-piece bonus still applies as well.
Variations
piecesis a whole number from 1 to the size of the set, and each number can appear once.- A threshold has no
$value: write plain numbers.
Recipe 10: A synergy with a lucky effect#
Goal: a quarter of your arrows cost no ammunition.
A synergy's special can be a modifier with "op": "chance": it applies a change only some of the time.
{
"id": "thrifty-quiver",
"label": "SYNERGY_THRIFTY_QUIVER",
"requires": ["precision", "damage"],
"priority": 3,
"effect": {
"text": "SYNERGY_THRIFTY_QUIVER_EFFECT",
"modifiers": [{ "hook": "attack.critRange", "op": "add", "value": -1 }]
},
"special": {
"text": "SYNERGY_THRIFTY_QUIVER_SPECIAL",
"hook": "ammo.consumed",
"op": "chance",
"value": 0.25,
"then": { "op": "mul", "value": 0 }
}
}
25% of shots use no ammunition. The ordinary effect also lowers the number needed for a critical hit by one step.
Variations
- This kind of special writes nothing in the log: it has no
announce. - The dice roll again on every shot.
Create new content#
Recipe 11: A new affix effect, in JSON only#
Goal: invent a "Siphon" affix that returns mana when your projectile spells hit. It takes three linked pieces of JSON and no code. An affix effect is the behaviour behind a family of affixes; the affixes themselves are the names you see on items.
Step 1. Declare the effect in affixEffects in equipment.json. Its name uses lowercase letters and dashes.
{
"siphon": {
"text": "AFFIX_EFFECT_SIPHON",
"listeners": [
{
"id": "siphon-mana",
"on": "spell.hit",
"when": { "kind": "projectile" },
"action": "restoreMana",
"params": { "amount": "$value" },
"cooldown": 2
}
]
}
}
Step 2. Write an affix of that family in affixes in the same file.
{
"id": "of-the-siphon",
"label": "AFFIX_NAME_OF_THE_SIPHON",
"position": "suffix",
"family": "siphon",
"kinds": ["amulet", "ring"],
"tiers": [[1, 1], [1, 2], [2, 2], [2, 3]],
"weight": 4,
"minLevel": 2,
"group": "siphon"
}
Step 3. Give the effect an engraving reagent in engrave.reagentByFamily in crafting.json. Each effect needs exactly one reagent, and it must exist in the world.
{ "siphon": "pearl" }
Step 4. Write AFFIX_EFFECT_SIPHON (it receives {value}) and AFFIX_NAME_OF_THE_SIPHON in the nine locale files.
Amulets and rings can now drop "of the Siphon". When a projectile spell hits a creature, you regain as much mana as the affix value, at most every 2 turns. The affix can be engraved with pearl, like every affix of its family.
Variations
- For a plain number with no event, use a modifier instead of a listener:
"modifiers": [{ "hook": "mana.max", "op": "add", "value": "$value" }]. An effect can have both. - Add
"perSkill": trueto the effect to tie each affix to one skill. Each affix then carries"skill", and conditions can read"$skill". - An effect without a reagent, or a reagent for a missing effect, stops the game from loading. Only
$valueworks in an affix effect;$powerdoes not. - Rarity comes from the affix:
weight,minLevelandkindsdecide how often it drops.
Combining recipes#
- A number that changes: use a modifier on the right hook.
- Something that happens when something else happens: use a listener on the right event.
- Too strong? Add
cooldown, thenchance. - Only for some enemies: add
targetFamily, ortargetBoss: falseto spare bosses. - Two effects on one event: write two listeners, not one that triggers the other. Every hook, event, condition and action is listed in the catalogue.