# Ability JSON Schema Documentation > This document explains the structure of ability definition files used in the system. > It is intended for **wiki contributors** and **tool developers** alike. --- ## Top-Level Structure Every ability file follows this wrapper: ```json { "abilities": { "id": "namespace:category/name", "displayName": "translation.key.for.name", "description": "translation.key.for.description", "math": [ ...nodes ] } } ``` | Field | Type | Required | Description | |---------------|--------|----------|-------------| | `id` | string | ✅ | Unique ID in `namespace:category/name` format | | `displayName` | string | ✅ | i18n translation key for the display name | | `description` | string | ✅ | i18n translation key for the tooltip/description | | `math` | array | ✅ | Ordered list of effect nodes (see below) | --- ## The `id` Format IDs follow the pattern: `namespace:category/name` | Part | Example | Notes | |-------------|---------------|-------| | `namespace` | `original` | The mod or source that owns this ability | | `category` | `fire` | The element, school, or grouping | | `name` | `fireball` | The specific ability | **Examples:** - `original:fire/fireball` - `original:ice/frost_bolt` - `mymod:arcane/void_lance` --- ## Translation Keys `displayName` and `description` are **not raw text** — they are keys looked up in a translation file. **Convention:** `ability...` | Field | Example Key | |---------------|--------------------------------------| | `displayName` | `ability.original.fireball.name` | | `description` | `ability.original.fireball.description` | --- ## Math Nodes The `math` array is the heart of an ability. Each node is an object with at minimum: ```json { "id": "unique_node_id", "type": "node_type", ...fields } ``` | Field | Type | Required | Description | |--------|--------|----------|-------------| | `id` | string | ✅ | Unique name for this node within the ability | | `type` | string | ✅ | Determines what this node does (see types below) | --- ## Node Types --- ### `base_value` The foundational damage or healing number, before any modifiers. ```json { "id": "base_damage", "type": "base_value", "amount": 50, "scaling": { "stat": "spell_power", "multiplier": 1.5 } } ``` | Field | Type | Required | Description | |--------------------|--------|----------|-------------| | `amount` | number | ✅ | The flat base value | | `scaling.stat` | string | ✅ | Which player stat to scale from | | `scaling.multiplier` | number | ✅ | How much of that stat to add (`stat × multiplier`) | > **Formula:** `final = amount + (player[stat] × multiplier)` --- ### `range` How far the ability can reach and how it travels. ```json { "id": "cast_range", "type": "range", "min": 0, "max": 30, "unit": "meters", "rangeType": "projectile" } ``` | Field | Type | Required | Description | |-------------|--------|----------|-------------| | `min` | number | ✅ | Minimum range (use `> 0` for dead zones) | | `max` | number | ✅ | Maximum range (`null` = unlimited) | | `unit` | string | ✅ | `"meters"` | | `rangeType` | string | ✅ | `projectile`, `hitscan`, `melee`, `aura` | --- ### `area_of_effect` Defines the shape and spread of the ability's impact zone. ```json { "id": "explosion", "type": "area_of_effect", "shape": "sphere", "radius": 5, "unit": "meters", "falloff": "linear" } ``` | Field | Type | Required | Description | |-----------|--------|----------|-------------| | `shape` | string | ✅ | `sphere`, `cone`, `cylinder`, `line` | | `radius` | number | ✅ | Size of the AoE | | `unit` | string | ✅ | `"meters"` | | `falloff` | string | ✅ | `none`, `linear`, `quadratic` — how damage drops off at edges | --- ### `damage` Instant damage dealt. Supports **multiple damage types** in one node via `sources`. ```json { "id": "instant_damage", "type": "damage", "sources": [ { "damageType": "fire", "base_value": 50, "scaling": { "stat": "spell_power", "multiplier": 1.5 } }, { "damageType": "physical", "base_value": 15, "scaling": { "stat": "strength", "multiplier": 0.5 } } ] } ``` | Field | Type | Required | Description | |--------------------------|--------|----------|-------------| | `sources` | array | ✅ | One entry per damage type | | `sources[].damageType` | string | ✅ | e.g. `fire`, `physical`, `lightning`, `poison` | | `sources[].base_value` | number | ✅ | Flat damage for this type | | `sources[].scaling` | object | ✅ | Same `stat` / `multiplier` structure as `base_value` | > **Note:** Multiple sources are applied **independently** — each scales off its own stat. --- ### `damage_over_time` Repeating damage applied in ticks after the initial hit. ```json { "id": "burn_dot", "type": "damage_over_time", "damageType": "fire", "damage_per_tick": 10, "tick_interval_seconds": 1, "duration_seconds": 5, "scaling": { "stat": "spell_power", "multiplier": 0.3 }, "stacks": false } ``` | Field | Type | Required | Description | |-------------------------|---------|----------|-------------| | `damageType` | string | ✅ | Damage type per tick | | `damage_per_tick` | number | ✅ | Flat damage each tick | | `tick_interval_seconds` | number | ✅ | Seconds between ticks | | `duration_seconds` | number | ✅ | Total duration | | `scaling` | object | ✅ | Same `stat` / `multiplier` structure | | `stacks` | boolean | ✅ | `true` = re-applying adds a new stack; `false` = resets timer | > **Total ticks:** `duration_seconds / tick_interval_seconds` --- ### `condition` A status effect or triggered reaction applied to the target. ```json { "id": "ignite_debuff", "type": "condition", "chance": 0.75, "duration_seconds": 5, "effect": "reduce_fire_resistance", "magnitude": -20 } ``` ```json { "id": "explosion_knockback", "type": "condition", "chance": 1.0, "effect": "knockback", "force": 8, "direction": "away_from_origin" } ``` | Field | Type | Required | Description | |--------------------|--------|----------|-------------| | `chance` | number | ✅ | Probability `0.0`–`1.0` (`1.0` = always) | | `effect` | string | ✅ | The condition to apply — mapped by the engine | | `duration_seconds` | number | ❌ | How long the condition lasts (omit for instant effects) | | `magnitude` | number | ❌ | Numeric modifier for stat-changing effects | | `force` | number | ❌ | For displacement effects like knockback | | `direction` | string | ❌ | `away_from_origin`, `toward_origin`, `up` | > Multiple `condition` nodes are rolled **independently** per hit. --- ### `meta` Gameplay configuration — cooldowns, costs, and tags. ```json { "id": "fireball_meta", "type": "meta", "cooldown_seconds": 12, "mana_cost": 80, "cast_time_seconds": 1.5, "tags": ["fire", "aoe", "projectile", "dot"] } ``` | Field | Type | Required | Description | |----------------------|----------|----------|-------------| | `cooldown_seconds` | number | ✅ | Recharge time after use | | `mana_cost` | number | ✅ | Resource cost to cast | | `cast_time_seconds` | number | ✅ | Time before the ability fires (`0` = instant) | | `tags` | string[] | ✅ | Used for filtering, synergies, and resistances | --- ## Complete Example — Fireball ```json { "abilities": { "id": "original:fire/fireball", "displayName": "ability.original.fireball.name", "description": "ability.original.fireball.description", "math": [ { "id": "base_damage", "type": "base_value", "amount": 50, "scaling": { "stat": "spell_power", "multiplier": 1.5 } }, { "id": "cast_range", "type": "range", "min": 0, "max": 30, "unit": "meters", "rangeType": "projectile" }, { "id": "explosion", "type": "area_of_effect", "shape": "sphere", "radius": 5, "unit": "meters", "falloff": "linear" }, { "id": "instant_damage", "type": "damage", "sources": [ { "damageType": "fire", "base_value": 50, "scaling": { "stat": "spell_power", "multiplier": 1.5 } }, { "damageType": "physical", "base_value": 15, "scaling": { "stat": "strength", "multiplier": 0.5 } } ] }, { "id": "burn_dot", "type": "damage_over_time", "damageType": "fire", "damage_per_tick": 10, "tick_interval_seconds": 1, "duration_seconds": 5, "scaling": { "stat": "spell_power", "multiplier": 0.3 }, "stacks": false }, { "id": "ignite_debuff", "type": "condition", "chance": 0.75, "duration_seconds": 5, "effect": "reduce_fire_resistance", "magnitude": -20 }, { "id": "explosion_knockback", "type": "condition", "chance": 1.0, "effect": "knockback", "force": 8, "direction": "away_from_origin" }, { "id": "fireball_meta", "type": "meta", "cooldown_seconds": 12, "mana_cost": 80, "cast_time_seconds": 1.5, "tags": ["fire", "aoe", "projectile", "dot"] } ] } } ``` --- ## Quick Reference Card | Node Type | Purpose | Key Fields | |-------------------|-------------------------------------|------------| | `base_value` | Core damage number + stat scaling | `amount`, `scaling` | | `range` | How far & how the ability travels | `min`, `max`, `rangeType` | | `area_of_effect` | Shape & spread of impact zone | `shape`, `radius`, `falloff` | | `damage` | Instant hit damage (multi-type ok) | `sources[]` | | `damage_over_time`| Tick damage over time | `damage_per_tick`, `tick_interval_seconds`, `duration_seconds`, `stacks` | | `condition` | Status effects & reactions | `chance`, `effect`, `duration_seconds` | | `meta` | Cooldown, cost, cast time, tags | `cooldown_seconds`, `mana_cost`, `cast_time_seconds`, `tags` | --- ## Rules & Conventions 1. Every ability **must** have at least one `meta` node. 2. `id` values must be **unique within the same ability file** — use descriptive names. 3. `displayName` and `description` must be **translation keys**, never raw text. 4. The `math` array is processed **top to bottom** — order matters for dependent effects. 5. `damage` nodes should come **after** any `area_of_effect` nodes that modify their area. 6. A `damage_over_time` node is **separate** from `damage` — both can coexist freely. 7. Multiple `condition` nodes are each rolled independently. 8. `tags` on the `meta` node are used by the engine for resistance checks, talent synergies, and UI filtering.