Game-Server/ABILITY_SCHEMA.md
2026-04-18 18:58:19 -03:00

384 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.<namespace>.<name>.<field>`
| 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.