proposal on future modular attack system
This commit is contained in:
parent
e7f98d1228
commit
ff4db86c96
383
ABILITY_SCHEMA.md
Normal file
383
ABILITY_SCHEMA.md
Normal file
@ -0,0 +1,383 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user