Skip to content

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.

  • 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.
  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).

The Combat panel on the Player & keys tab, showing the Hunter health, armour and shove settings

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.

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
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

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.

Example: a marksman barrel for the starting rifle

Section titled “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, "", "", "").
Terminal window
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

Read the assembled weapon back:

Terminal window
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.

  • “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.

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.

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

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.

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

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 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.

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.

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.

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
// 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 for where world scripts live.

  • 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.
Terminal window
# 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.