Magic#
This page lets you add new spells to the game: a syllable, a spell definition, a damage element, a visual combination, a reaction, a named formula or a grimoire. Spell definitions live in public/config/magic.json. Book models and drops use items.json and loot.json. None of this needs code.
How a spell is built#
The player types a formula: a short chain of syllables. The game turns that chain into a spell in four steps. Take KA TOR, the fireball.
KA TOR what the player types
| |
v v
grammar essence first, then form, then modifiers
|
v
definitions ka -> "fire" tor -> "blast"
|
v
actions + statuses fire damage, burning for 2 turns, burst of radius 2
- A syllable is one word of the spell language. It has a role:
essence(the element),form(the shape) ormodifier(a twist such as duration). - The grammar says how many syllables a formula may have and in which order the roles come.
KA TORworks.TOR KAis refused, because a form cannot come before its essence. - A definition says what a syllable does: its actions, its statuses and the traits that reactions read.
- An action is one thing a spell does when it hits: damage, healing, a status, a light or a push.
The first damage in the essence decides the spell element. For ka it is fire.
Add a syllable#
Goal: let the player type a new syllable, ICE, that casts water magic.
The game ships with a water definition, but no syllable points to it. Add one under magic.syllables:
{ "ice": { "role": "essence", "name": "SYLLABLE_ICE", "definition": "water", "signature": { "glyph": "❄", "color": "#9dcbd1", "rhythm": "hold", "stroke": "dashed", "timbre": { "wave": "triangle", "pitch": 440 } } } }
The syllabary lists a new essence with a snowflake. ICE casts frost damage and freezes the target for 3 turns.
Variations:
- Add
"power": 2to the syllable to set its power yourself. Without it, the syllable takes the power of its reagent. - Write the name
SYLLABLE_ICEin every file ofpublic/config/locales/, or the game cannot show it.
Add a spell definition#
Goal: replace what air does with a lightning storm that pushes enemies back further each time it repeats.
{ "storm": { "name": "MAGIC_SYSTEM_AIR", "role": "CONTENT_ELECTRICITY_SPREADS_THROUGH_LINKS", "traits": ["electric"], "visual": "lightning", "actions": [{ "kind": "damage", "element": "lightning", "factor": 1 }, { "kind": "push", "extra": 0 }], "geometry": { "chainRadius": 3 }, "transforms": [{ "kind": "scale-with-repeats", "action": "push", "param": "extra", "perRepeat": 1 }] } }
A syllable that points to storm deals lightning damage, which jumps to creatures within 3 cells. It also pushes the target, one more cell for each repeat.
The parts of a definition:
traitsare labels that reactions read (see Reactions below).visualis the name of the effect on screen. Combinations use it.actionsare what the spell does. The five kinds aredamage,heal,status,lightandpush. Bounds for each are in the catalogue.transformschange an action afterwards. The list of transform kinds is in the catalogue too.
Every syllable needs a reagent: the item that is spent when it is cast. A reagent has an id (lowercase letters only), a name, a symbol, a color, an effect that names a definition, and a power above 0:
{ "id": "stormseed", "name": "WORLD_SPIRIT_ASH", "symbol": "✧", "color": "#9dcbd1", "effect": "air", "power": 9 }
Add a damage element#
Goal: add a new element, holy, then make an item deal it.
Declare it in magic.elements. The visual block is optional. Anything you leave out gets a neutral grey look.
{ "id": "holy", "label": "ELEMENT_HOLY", "visual": { "shape": "light", "color": "#ffd700", "missile": true } }
Write ELEMENT_HOLY in every file of public/config/locales/. Now a modifier can add holy damage to every hit. Put it in an affix effect:
{ "hook": "hit.element", "op": "add", "value": 2, "when": { "element": "holy" } }
Each hit with the item deals 2 extra holy damage, shown in the holy colour. A spell can deal it too: use "element": "holy" in a damage action.
Variations:
- A hit strikes each element once. Two
hit.elementmodifiers on the same element add up inside that one strike. - Keep the six base elements (
physical,fire,frost,lightning,arcane,poison). Fire burns webs and frost slows, and that behaviour is attached to them. - Set
"conducts": falseon an element to stop its damage from spreading through linked creatures.
Add an effect combination#
Goal: when a spell holds both fire and a binding effect, add a flame layer to its animation.
{ "id": "scorch", "requires": ["fire", "bind"], "visual": { "shape": "flame", "color": "#ff8d38", "glyph": "♨" } }
A spell with fire and binding (a web, a tangle) gets an extra flame layer, and fields it leaves behind show the ♨ glyph. Damage, statuses and power do not change.
requires lists two to four effect names. Use the visual name of each effect: water is frost, air is lightning, earth and anchors are bind, life is heal. A combination only draws a layer if its visual names a shape. Without one, it only supplies its glyph.
Reactions#
A reaction rewrites a spell when its syllables carry certain traits together. The game ships with eight. For example, heat plus propulsive makes a fireball.
Goal: when heat meets a wave, drop the damage and set the target on fire instead.
{ "id": "kindling-wave", "requires": ["heat", "resonant"], "priority": 80, "name": "WORLD_KINDLING_WAVE", "description": "WORLD_KINDLING_WAVE_BURNS_WITHOUT_BLOW", "remove": ["damage"], "actions": [{ "kind": "status", "status": "burning", "turns": 2 }] }
Casting KA RUM (fire plus wave) no longer hurts anyone directly. It burns everything it touches for 2 turns.
- A reaction applies only if all its
requirestraits are present. - Reactions run by rising
priority. Each one removes the action kinds inremove, then adds its ownactions. - Add
"syllabic": trueto apply it to formulas only.
Statuses#
A status is a lasting condition on a creature. A status action names one and a number of turns, from 1 to 100:
{ "kind": "status", "status": "regeneration", "turns": 3, "factor": 1 }
The target regains health every turn for 3 turns. Each turn it heals the spell power times factor.
There are four statuses: burning (damage each turn), regeneration, frozen (cannot move) and slowed. Only regeneration takes a factor. Adding a new status needs code.
Named formulas, favourites and grimoires#
A named formula is a formula the game knows by name. Its id is its syllables joined with +, and the same id is used by known formulas, favourites and grimoires.
Goal: give KA TOR a name.
{ "ka+tor": { "syllables": ["ka", "tor"], "name": "MAGIC_FORMULA_FIREBALL" } }
The player sees "Fireball" in their formula list. If the id does not match the syllables, or the grammar refuses the chain, the game fails to load and names the faulty path.
Favourites. The first few favourites are quick slots, bound to F1 to F4 by default. Set how many (1 to 12):
{ "quickSlots": 4 }
Grimoires. A grimoire distribution belongs to loot.json. Its grimoire reference names a model in items.json, under grimoires. The model owns the name and icon. It teaches count formulas the player does not know yet, picked from your named formulas. depthWeights makes it more common deeper: the weight is weight until the first level, then the weight of the last level reached.
{ "type": "grimoire", "grimoire": "grimoire", "weight": 1, "count": 1, "depthWeights": [{ "minLevel": 1, "weight": 3 }, { "minLevel": 6, "weight": 5 }] }
Grimoires drop in the floors listed, and reading one teaches a new formula. From level 6, it drops more often.
More settings#
These are rarely needed. Most live in magic.json; push settings belong to combat.json. Reagents, including their name, symbol and effect reference, belong to resources.json.
magic.effectNames gives names to the shared effects light, heal and bind. Each entry chooses exactly one definition reference or one label key. An element keeps priority over this alias. An unknown definition or a missing translation is refused.
Advanced: push, light, resistance, cost and sounds
- Push.
combat.jsonsets the default push distance and its cap. Thepushaction adds itsextraon top, up to the cap:{ "distance": 1, "maxDistance": 2 }. - Light anchors. A
lightaction has ananchor:casterlights you,targetlights the creature hit,groundleaves a light on the floor. Example:{ "kind": "light", "anchor": "target", "radius": 4 }. - Transformations. A transform changes an action after the spell is planned.
convert-actionturns healing into regeneration;spread-over-turnsspreads a hit over several turns. - Magic resistance.
variantMagicResistancesincreature-rules.jsonsets how much spell damage a creature variant absorbs, from 0 to 1:{ "ash": 0.5 }. Weapons and traps are not reduced. - Shape sounds.
magic.shapeSoundsgives each drawn shape a launch sound and an impact sound, as file paths from the site root. - Syllable signatures. The
signatureonly changes how a syllable looks and sounds. Addvoicewith a.mp3,.oggor.wavpath to use a recorded voice. - Mana cost.
magic.manaCostisbase + perPower x power + perSyllable x syllables:{ "base": 0, "perPower": 0.6, "perSyllable": 1 }.
- Terrain
How elements burn webs and wake cells.
- Troubleshooting
Validator errors and how to read them.