# Combat

> Turn on the built-in action combat kit — movement, modular guns, melee, grenades, an ability, enemies with limb damage, pickups and a HUD — and tune it as project data.

Source: https://feather-engine.com/docs/gameplay/combat/

Feather includes a complete first-person combat kit. Turning it on gives the player a movement
kit, a grapple, modular guns, melee weapons, grenades and a charged ability, and fills your levels
with enemies that have hitboxes, severable limbs and loot drops. Everything is stored in the
project as `combat` data, so you tune numbers rather than write systems, and you drive missions
from world scripts.

Combat also supports over-the-shoulder shooting when the project's player camera is third-person
with an avatar.

## What's included

- **Player:** health, regenerating armour, a hit grace period and a close-range shove.
- **Movement kit:** run, sprint, slide, dash, coyote-time jumps, field-of-view kick and head bob.
- **Grapple:** a rope to tagged anchors or any static surface, with reel, swing and release boost.
- **Modular guns:** weapons assembled from a **Frame**, **Core**, **Barrel** and **Relic**, plus a
  sidearm.
- **Melee:** light and heavy swings, cleaving, blocking, and tools that dig the ground.
- **Grenades** (blast and gravity) and a charged area **ability**.
- **Enemies:** melee, ranged and charger archetypes, hostile or allied, with hitboxes per bone,
  limbs that sever, weak points and drops.
- **Pickups, feedback and HUD:** ammo, health, armour, grenade and charge pickups; hit markers,
  gore, blood decals and camera shake; a compass, minimap and objective markers.
- **Budgets** that cap enemies, body parts, particles and projectiles in large fights.

## Set up combat

1. Open **Window → Combat**.
2. Select **First-person combat enabled**.
3. Work through the tabs: **Player & keys**, **Movement & grapple**, **Weapons**,
   **Grenades & ability**, **Melee & arms**, **Enemies**, **Feedback & budgets** and **HUD**.
4. Press **Play**. Enemies are spawned by world scripts (see [Extend it](#extend-it)).

![The Combat panel on the Player & keys tab, showing the Hunter health, armour and shove settings](../../../../assets/docs/gameplay/combat.png)

Every edit in the window commits through `combat.set`, the same validated, undoable path agents use.

New projects start with a working set: two frames (`hex_shotgun`, `warden_rifle`), two cores
(`wraith_core`, `ember_core`), two barrels (`scatter_barrel`, `long_barrel`), two relics
(`chain_relic`, `storm_relic`), the `blast` and `gravity` grenades, an `axe`, and the enemies
`swarmer`, `hex_caster` and `armoured_revenant`.

## Player and keys

| Field | Description | Default |
| --- | --- | --- |
| `player.max_health` | Health | 100 |
| `player.max_armor` | Armour | 50 |
| `player.armor_absorb` | Fraction of damage armour absorbs | 0.7 |
| `player.armor_regen_delay` | Seconds without damage before armour regenerates | 4 s |
| `player.armor_regen_rate` | Armour regained per second | 10 |
| `player.hit_grace` | Invulnerability after a hit | 0.25 s |
| `player.melee_damage`, `melee_push`, `melee_range`, `melee_cooldown` | The shove | 25, 9, 2.4 m, 0.7 s |

| Action | Field | Default key |
| --- | --- | --- |
| Fire / aim | `keys.fire`, `keys.aim` | Mouse1 / Mouse2 |
| Sprint and dash | `keys.sprint`, `keys.dash` | ShiftLeft |
| Slide | `keys.slide` | ControlLeft |
| Jump | `keys.jump` | Space |
| Grapple | `keys.grapple` | F |
| Grenade | `keys.grenade` | G |
| Ability | `keys.ability` | Q |
| Reload | `keys.reload` | R |
| Shove | `keys.melee` | V |
| Melee, primary and sidearm slots | `keys.melee_slot`, `ranged_slot`, `secondary_slot` | 1, 2, 3 |

## Movement and grapple

| Field | Default |
| --- | --- |
| `run_speed` / `sprint_speed` | 6.5 / 9.5 m/s |
| `acceleration` | 60 m/s² |
| `air_control` | 0.35 |
| `gravity` | 22 m/s² |
| `jump_speed` | 7.5 m/s |
| `coyote_time` | 0.12 s after leaving a ledge |
| `slide_speed`, `slide_duration`, `slide_cooldown` | 13 m/s, 0.75 s, 0.4 s |
| `slide_height` | Eye drop while sliding, 0.6 m |
| `dash_speed`, `dash_duration`, `dash_cooldown` | 22 m/s, 0.16 s, 1.2 s |
| `fov` / `sprint_fov` | 72° vertical / +8° while sprinting or dashing |
| `head_bob`, `slide_tilt` | 0.035, 4° |

The grapple attaches to objects tagged `grapple` (`anchors: "Tagged"`, the default) or to any static
surface (`"AnySurface"`).

| Field | Description | Default |
| --- | --- | --- |
| `grapple.enabled` | Grapple available | On |
| `range` | Reach | 32 m |
| `aim_cone` | Aim assist angle around the crosshair | 7° |
| `reel_speed`, `pull` | Reel-in speed and pull acceleration | 20, 45 |
| `swing_control` | Steering while attached | 14 |
| `release_distance` | Detaches this close to the anchor | 2.5 m |
| `max_attach_seconds` | Longest attachment | 2.5 s |
| `cooldown` | Between grapples | 1.5 s |
| `release_boost` | Upward boost on release | 4 |

## Weapons

A gun is a loadout of four parts. The **Frame** sets the weapon family and its base stats; the
**Core**, **Barrel** and **Relic** attach to the frame's sockets and change stats with modifiers.

1. In **Weapons**, create or select a part and choose its slot.
2. For a frame, set the `family` (for example `shotgun` or `rifle`), the base stats, and the
   sockets where other parts and the muzzle sit.
3. For other parts, list the families they fit in `fits` (empty fits every frame) and add
   modifiers: `{stat, op, value}` with `op` set to `Add`, `Multiply` or `Set`.
4. Choose a model, offset, rotation and scale for each part, plus fire and reload sounds.
5. Set the starting `loadout` (and, optionally, a `secondary` sidearm on key 3).

Feather applies the frame's base stats, then every part's modifiers in slot order, and clamps the
result to playable ranges.

| Stat | Description | Clamped to |
| --- | --- | --- |
| `damage` | Per pellet or projectile | 0–10,000 |
| `pellets` | Pellets per shot | 1–32 |
| `spread` | Degrees | 0–45 |
| `fire_rate` | Shots per second | 0.1–30 |
| `magazine`, `reserve` | Rounds | 1–500, 0–5000 |
| `reload_time` | Seconds | 0.05–10 |
| `range`, `falloff` | Damage falls to half between falloff and range, metres | 1–500 |
| `impulse` | Push on hit | 0–200 |
| `recoil` | Camera kick per shot, degrees | 0–45 |
| `projectile_speed` | 0 is hitscan; otherwise m/s | 0–500 |
| `homing` | Turn rate toward the nearest enemy, rad/s | 0–50 |
| `limb_multiplier`, `weakpoint_multiplier` | Extra damage to limbs and weak points | 0–20 |
| `stagger` | Stagger per hit | 0–20 |
| `charge_per_hit` | Ability charge per hit | 0–100 |
| `chain_bonus` | Damage bonus per chained kill, up to five stacks | 0–5 |
| `ability_radius` | Ability radius multiplier | 0.1–5 |
| `automatic` | 1 holds to fire | 0 or 1 |
| `explosion_radius`, `explosion_damage` | Projectiles explode on impact (rockets, launchers) | 0–30 m, 0–10,000 |

Props tagged `explosive` chain-explode and props tagged `breakable` shatter when shot. Hitscan shots
into the ground flick the built-in grass aside and clip a tuft while the grass's **Shots clip it**
setting is on (the default); see [Foliage and biomes](/docs/world/foliage-and-biomes/).

## Example: a marksman barrel for the starting rifle

You'll add a barrel that makes the rifle hit harder and more accurately at long range but fire
more slowly, and put it on the player's starting gun. Start from the **War City** example project,
whose starting gun is the `rifle` frame (named AK-47).

1. Open **Window → Combat · movement, weapons, enemies** and select the **Weapons** tab.
2. Under **Parts**, click **+ Barrel**. Set **Id** to `marksman_barrel`, **Name** to
   **Marksman Barrel** and **Fits families (empty = all)** to `rifle`.
3. Under **Modifiers**, click **+ Modifier** four times and set them to:
   - `spread` **×** 0.4
   - `range` **+** 40
   - `damage` **+** 8
   - `fire_rate` **×** 0.6
4. Under **Default loadout**, choose **Marksman Barrel** in the **Barrel** box. The line below
   reads **Assembled: AK-47** with the new stats.
5. Press **Play**. You start with the rifle and its new barrel. Taking a gun from War City's weapon
   crates swaps it for that bare frame, because the example's world script calls
   `set_loadout(frame, "", "", "")`.

### Do the same from the command line

```bash
feather-cli --workspace /tmp/demo --project project.feather.json command combat.upsert \
  --json '{"kind":"weapon_part","value":{"id":"marksman_barrel","name":"Marksman Barrel","slot":"Barrel","fits":["rifle"],"modifiers":[{"stat":"spread","op":"Multiply","value":0.4},{"stat":"range","op":"Add","value":40},{"stat":"damage","op":"Add","value":8},{"stat":"fire_rate","op":"Multiply","value":0.6}]}}' \
  --save project.feather.json

feather-cli --workspace /tmp/demo --project project.feather.json command combat.set \
  --json '{"patch":{"loadout":{"frame":"rifle","barrel":"marksman_barrel"}}}' \
  --save project.feather.json
```

### Check it worked

Read the assembled weapon back:

```bash
feather-cli --workspace /tmp/demo --project project.feather.json command combat.get --json '{}'
```

`loadout.barrel` is `marksman_barrel`, and `assembled_default_weapon.stats` shows `damage` 30
(was 22), `spread` 0.48 (was 1.2), `range` 160 (was 120) and `fire_rate` 5.4 (was 9).

Combat doesn't run in `game.simulate`, so check the feel in Play: shots group tighter and the rifle
fires noticeably slower.

### Common problems

- **"Marksman Barrel does not fit the shotgun frame."** The part's **Fits families** doesn't
  include the frame's **Family**. Add the family, or leave the list empty to fit every frame.
- **"marksman_barrel is not a frame."** Only a **Frame** part can go in the frame slot.
- **A part can't be deleted.** **Delete part** is disabled while the default loadout uses it.

## Melee

A melee weapon takes the melee slot. Tap fire for a light swing, hold it for a heavy swing, and hold
aim to block.

| Field | Description | Axe default |
| --- | --- | --- |
| `light_damage`, `heavy_damage` | Damage per swing | 42, 110 |
| `light_seconds`, `heavy_seconds` | Swing duration | 0.55 s, 0.85 s |
| `heavy_charge` | Hold time for a heavy swing | 0.35 s |
| `hit_window` | Part of the swing that can hit, 0–1 | [0.25, 0.6] |
| `range`, `arc` | Reach and swing width | 2.7 m, 110° |
| `cleave_light`, `cleave_heavy` | Enemies one swing cuts through | 2, 4 |
| `limb_multiplier` | Blades sever limbs readily | 1.8 |
| `block_reduction`, `block_angle` | Damage blocked and the arc it covers | 0.8, 120° |
| `dig_radius`, `dig_depth` | Earth moved per ground hit when destruction is on | 0 m, 0 m |
| `structure_damage` | Multiplier against destructible objects | 1 |

`destruction.setup` adds an entrenching shovel that digs foxholes and trenches. See
[Destruction](/docs/gameplay/destruction/).

The **Melee & arms** tab also configures the first-person arms model, its weapon sockets and
animation clips, and an optional carried lantern.

## Grenades and ability

| Field | Description | Blast default |
| --- | --- | --- |
| `kind` | `Blast`, or `Gravity` (pulls light enemies in, then bursts) | Blast |
| `fuse` | Seconds | 1.4 s |
| `radius`, `damage`, `impulse`, `stagger` | Blast | 6 m, 140, 14, 3 |
| `pull`, `pull_duration` | Gravity grenades only | 0 |
| `throw_speed` | m/s | 19 |
| `self_damage` | Fraction the player takes from their own blast | 0.3 |

The player starts with `grenade_stock` grenades (default 3) and carries up to `grenade_max`
(default 4).

The ability (default name **Shockwave**) fills from hits and kills and releases an area blast:
`charge` 100, `charge_per_kill` 8, `radius` 9 m, `damage` 60, `impulse` 18, `stagger` 4.

## Enemies

Each enemy archetype combines a model, a behaviour, combat numbers, hitboxes, limbs and drops.

| Field | Description |
| --- | --- |
| `behaviour` | `Melee` runs in and strikes; `Ranged` keeps distance and casts telegraphed projectiles; `Charger` adds a readable charge at mid range |
| `team` | `Hostile` hunts the player and allies; `Ally` fights hostiles beside the player and follows them when no hostile is near. The player's shots pass allies by |
| `health`, `armor` | Armour is the fraction of damage ignored, except on weak points |
| `heavy` | Resists gravity pulls and most knockback |
| `walk_speed`, `run_speed`, `radius`, `height` | Movement and body size |
| `attack_*`, `ranged_*`, `charge_*` | Damage, range, wind-up and cooldown per attack type; wind-ups telegraph the strike so it can be dodged |
| `flank` | 0 comes straight in, 1 swings wide so crowds surround |
| `leap_range`, `leap_speed`, `leap_cooldown` | Pounce from a distance band; `[0, 0]` never leaps |
| `vault_speed` | Hop over low obstacles when stuck; 0 never |
| `poise` | Stagger needed to interrupt |
| `gib_overkill` | Overkill damage that blows the body apart |
| `model_variants` | Other looks on the same skeleton; each spawn picks one |
| `clips` | Idle, walk, run, attack, hit, death and other clips, with variants |
| `value` | A number scripts read in `on_enemy_killed` |

### Hitboxes and limbs

A hitbox is a capsule from `bone` to `to_bone` (or `length` metres along the bone) with a `radius`,
and it feeds damage to a `limb`. Default archetypes use a humanoid rig.

| Limb field | Description |
| --- | --- |
| `id` | Limb name, referenced by hitboxes |
| `sever_bone` | Bone whose subtree comes off when the limb is destroyed; empty never severs |
| `health` | Limb health as a fraction of the enemy's health |
| `damage_multiplier` | Damage multiplier for hits on this limb |
| `effect` | `None`, `DisableAttack`, `DisableRanged`, `Limp`, `Crawl` or `Kill` |
| `lethality` | Fraction of a limb hit that also reaches the enemy's health |
| `weakpoint` | Applies the weapon's weak-point multiplier and ignores armour |

Low `lethality` on arms and legs means shooting a limb cripples an enemy rather than killing it.

### Drops

`drops` is a list of `{kind, chance, amount}` where `kind` is `Ammo`, `Health`, `Armor`, `Grenade`
or `Charge`. By default an enemy drops ammo 20% of the time and health 8% of the time. Pickups are
pulled toward the player within `pickups.magnet_radius` (default 3.5 m) and vanish after
`pickups.lifetime` (default 25 s).

To give an enemy authored behaviour, assign it a behaviour tree. See
[AI and navigation](/docs/gameplay/ai-and-navigation/).

## Feedback, budgets and HUD

| Field | Default |
| --- | --- |
| `feedback.shake` | 1 (players can scale it down in Settings) |
| `feedback.hit_markers`, `damage_vignette`, `gore` | On |
| `feedback.head_burst` | On: a killing shot to a severable head bursts it |
| `feedback.blood_decals`, `max_decals`, `decal_seconds` | On, 160, 45 s |
| `feedback.bleed_seconds` | 1.8 s |
| `feedback.flinch` | 14° per hit |
| `budgets.max_enemies` | 60 |
| `budgets.max_body_parts` | 28 |
| `budgets.max_particles` | 700 |
| `budgets.max_projectiles` | 120 |
| `budgets.corpse_seconds`, `part_seconds` | 6 s, 8 s |
| `hud.compass`, `hud.minimap`, `hud.world_markers` | On |
| `hud.minimap_range` | 45 m |

`hud_template` replaces the built-in HUD with your own markup. It can use `{{health}}`,
`{{max_health}}`, `{{health_pct}}`, `{{armor}}`, `{{armor_pct}}`, `{{ammo}}`, `{{reserve}}`,
`{{weapon}}`, `{{grenade}}`, `{{grenades}}`, `{{ability_pct}}`, `{{ability_ready}}`,
`{{grapple_ready}}`, `{{dash_ready}}` and `{{reloading}}`. Leave it empty to use the built-in HUD.

## Damage kinds and statuses

All damage goes through one shared path, **elements**. It has seven damage kinds — `physical`,
`blunt`, `explosive`, `fire`, `electric`, `ice` and `water` — and four statuses: `burning`, `wet`,
`shocked` and `chilled`. Wet targets take half fire damage and double electric damage; chilled
targets move slower and take more from blunt hits. Per-material and per-enemy multipliers (0 makes
a target immune) are set with `elements.set`.

## Extend it

World scripts drive missions with these functions. Enemy IDs are strings.

| Function | Description |
| --- | --- |
| `spawn_enemy(kind, x, y, z[, yaw])` | Spawns an archetype; returns its ID |
| `set_horde(on)` | Enemies spawned from now on sprint |
| `enemies()`, `enemy_count([kind])` | Live enemies as maps `{id, kind, x, y, z, health}`, or a count |
| `damage_enemy(id, amount)`, `kill_enemies()`, `clear_enemies()` | Damage or remove enemies |
| `player_health()`, `player_max_health()`, `player_armor()`, `player_alive()` | Player state |
| `heal_player(n)`, `hurt_player(n)`, `set_player_health(n)`, `give_armor(n)` | Change health and armour |
| `ammo()`, `reserve_ammo()`, `give_ammo(n)` | Ammunition |
| `grenades()`, `grenade_type()`, `give_grenades(n)`, `set_grenade(id)` | Grenades |
| `ability_charge()`, `add_charge(n)` | The ability |
| `loadout()`, `set_loadout(frame, core, barrel, relic)`, `weapon_name()` | The equipped gun |
| `spawn_pickup(kind, amount, x, y, z)` | Drops a pickup |
| `set_marker(key, x, y, z, label)`, `clear_marker(key)` | Objective markers on the compass, minimap and screen |
| `revive_player()`, `refill_player()`, `set_invulnerable(on)`, `set_combat_controls(on)` | Player control |
| `kills()`, `combat_running()` | Run state |
| `deal_damage(target, amount, kind)`, `damage_area(x, y, z, radius, amount, kind)` | Damage through elements |
| `apply_status(target, status, seconds)`, `clear_status`, `has_status`, `statuses` | Statuses |
| `profile_get(key[, default])`, `profile_set(key, value)`, `profile_has(key)` | Unlocks and banked values that persist across runs |

| Hook | Runs when |
| --- | --- |
| `on_enemy_killed(id, kind, x, y, z, value)` | An enemy dies |
| `on_player_hurt(amount)` | The player takes damage |
| `on_player_died()` | The player dies |
| `on_pickup(kind, amount)` | The player collects a pickup |
| `on_damaged(target, amount, kind)` | The player, an enemy or an object takes damage through elements |
| `on_status(target, status)` | A target starts burning, gets wet, shocked or chilled |

```rust
// A simple wave: spawn five swarmers, then a reward when they are all down.
fn start() {
    for i in 0..5 {
        spawn_enemy("swarmer", i * 2.0, 0.0, 30.0);
    }
    set_marker("arena", 0.0, 0.0, 30.0, "Arena");
}

fn on_enemy_killed(id, kind, x, y, z, value) {
    if enemy_count() == 0 {
        clear_marker("arena");
        spawn_pickup("health", 25, x, y, z);
    }
}
```

See [Scripting](/docs/logic/scripting/) for where world scripts live.

## Limits

- Up to 64 entries each for weapon parts, grenades, enemies and melee weapons; IDs are at most 48
  characters.
- A loadout must use a **Frame** in the frame slot, and other parts must fit the frame's family.
- Live enemies are capped by `budgets.max_enemies`.

## Automate it

```bash
# Turn combat on and make sprinting faster
feather-cli --connect .feather-session.json command combat.set \
  --json '{"patch":{"enabled":true,"movement":{"sprint_speed":11}}}'

# Add a barrel that trades spread for range on rifles
feather-cli --connect .feather-session.json command combat.upsert \
  --json '{"kind":"weapon_part","value":{"id":"marksman_barrel","name":"Marksman Barrel","slot":"Barrel","fits":["rifle"],"modifiers":[{"stat":"spread","op":"Multiply","value":0.4},{"stat":"range","op":"Add","value":40}]}}'

# Make swarmers immune to fire
feather-cli --connect .feather-session.json command elements.set \
  --json '{"patch":{"enemies":{"swarmer":{"fire":0}}}}'
```

`combat.get` with no arguments returns every section, the assembled default weapon and the stat
names; pass `section`, or `kind` and `id`, to read one part. `combat.remove` deletes a weapon part,
grenade, enemy or melee weapon.
