# @ferret/combat: API

Melee fights by rules that the clips set: fighters for the player and the AI, weapon sweeps against hit capsules, blocks, parries, stamina, health, reactions, death.

41 exports. Generated from the source (`packages/combat/src`), so it matches the code.

## Classes

### BoneGeometry

*class* · `packages/combat/src/bones.ts:17`

```ts
class BoneGeometry implements BodyGeometry
```

capsules and the weapon's striking part on a body's bones as they are posed now (call after the animations of the frame)

- `constructor(readonly bodies: Record<string, BoneBody>, readonly bodyOf: (f: Fighter) => string = f => f.id)`
- `weapon(f: Fighter): [V3, V3]` — the striking part of the weapon: from its inner end to its outer end
- `capsules(f: Fighter): Capsule[]`

### ClipPoseGeometry

*class* · `packages/combat/src/pose.ts:27`

```ts
class ClipPoseGeometry implements BodyGeometry
```

- `constructor(readonly specs: Record<string, BodySpec>, readonly clips: Record<string, Record<string, ClipJson>>, readonly bodyOf: (f: Fighter) => string = f => f.id)`
- `weapon(f: Fighter): [V3, V3]` — the striking part of the weapon: from its inner end to its outer end
- `capsules(f: Fighter): Capsule[]`

### Duel

*class* · `packages/combat/src/duel.ts:39`

```ts
class Duel
```

- `t: number`
- `phase: 'fight' | 'over'`
- `winner?: string`
- `overFor: number`
- `readonly events: DuelEvent[]`
- `readonly o: Required<Omit<DuelOptions, 'restartAfter' | 'parts'>> & Pick<DuelOptions, 'restartAfter'> & { parts: Record<string, number> }`
- `constructor(readonly fighters: [Fighter, Fighter], readonly geometry: BodyGeometry, o: DuelOptions)`
- `other(f: Fighter): Fighter`
- `step(dt: number, commands: [FighterCommand, FighterCommand]): DuelEvent[]` — one tick: the commands of both (in the order of `fighters`); returns what happened in it
- `scorch(def: Fighter, damage: number): DuelEvent | undefined` — Something burns a fighter (the Dragon's fire): he loses `damage` and stumbles, or falls when it was the last of his health (the other one has won). A fighter who is already reeling, down or past the fight's end is left alone. Returns the event, or nothing when it did not touch him.
- `restart(out?: DuelEvent[])` — back to the start: both in their places, health and stamina full (`out`: a tick's events, when called from `step`)
- `sideOf(def: Fighter, att: Fighter): 'front' | 'back' | 'left' | 'right'` — the side of the defender a blow comes from: by where the attacker stands
- `bodiesOverlap(parts: string[] = ['hips', 'chest', 'head']): number` — how far the two bodies cut into each other: the largest overlap of their trunk and head capsules (0 when apart)

### Fighter

*class* · `packages/combat/src/fighter.ts:72`

```ts
class Fighter
```

- `pos: V2`
- `yaw: number`
- `health: number`
- `stamina: number`
- `state: FighterState`
- `vel: V2` — the world velocity on the ground, and the locomotion clip it shows (free and block states)
- `loco: string`
- `blockSince: number` — when the current block began (for the parry), duel time
- `readonly maxHealth: number`
- `readonly maxStamina: number`
- `constructor(readonly id: string, readonly set: MoveSet, pos: V2, yaw: number)`
- `attack(id: string): AttackDef`
- `timing(clip: string): ClipTiming`
- `windowOf(a: AttackDef): [number, number]` — the hit window of an attack, seconds into its clip
- `get striking(): boolean` — while the weapon can still land a blow
- `get alive(): boolean`
- `get forward(): V2` — the forward direction on the ground (world x, z)
- `get pose(): { clip: string; t: number; additive?: { clip: string; t: number } }` — the clip to show and the time in it; a state whose clip is additive shows the guard with that clip on top
- `enter(kind: FighterStateKind, clip: string, extra: Partial<FighterState> = {})`
- `spend(n: number)`
- `reset(pos: V2, yaw: number)`
- `update(dt: number, cmd: FighterCommand, now: number)` — one tick of the rules; `now` is the duel's time

### FighterAI

*class* · `packages/combat/src/ai.ts:31`

```ts
class FighterAI
```

- `readonly tree: BehaviorTree`
- `constructor(readonly me: Fighter, readonly duel: Duel, readonly level: AiLevel, readonly random: () => number, readonly reach: (f: Fighter, attack: string) => number)`
- `think(dt: number): FighterCommand` — the command for this tick

### FighterBody

*class* · `packages/combat/src/bones.ts:54`

```ts
class FighterBody
```

Moves a body with its fighter: the root where the fighter stands and turned as it faces, and the bones posed by the clips of its state through the animation
backend (`GroupBackend`: the groups are sampled at the times the rules give, never started). A clip that gives way keeps playing while it fades out
(`fade` seconds); an additive clip (a hit reaction of UE's) lies on top of the guard.

- `readonly backend: GroupBackend`
- `constructor(readonly fighter: Fighter, readonly root: BABYLON.TransformNode, readonly groups: Record<string, BABYLON.AnimationGroup>, readonly o: { ground?: number; fade?: number } = {})`
- `snap()` — drop the clips that are fading out: the next `update` shows the fighter's pose alone, at once (for posing a body by hand, as a reach is measured)
- `update(dt: number)` — one frame: `dt` the time since the last (the clips that fade out run on by it)

## Functions

### add

*function* · `packages/combat/src/geometry.ts:10`

```ts
function add: (a: V3, b: V3) => V3
```

### attackReach

*function* · `packages/combat/src/reach.ts:6`

```ts
function attackReach(id: string, set: MoveSet, attack: string, geometry: BodyGeometry, depth = 0.2): number
```

root-to-root distance at which an attack of a move set lands on a body `depth` thick (metres; measured with a fighter standing at the origin facing +z)

### capsuleOverlap

*function* · `packages/combat/src/geometry.ts:39`

```ts
function capsuleOverlap(x: Capsule, y: Capsule): number
```

whether two capsules overlap, and by how much (positive: they cut into each other)

### dot

*function* · `packages/combat/src/geometry.ts:12`

```ts
function dot: (a: V3, b: V3) => number
```

### fromScene

*function* · `packages/combat/src/bones.ts:10`

```ts
function fromScene: (v: Vector3) => V3
```

a scene point in combat's frame

### groupOf

*function* · `packages/combat/src/bones.ts:45`

```ts
function groupOf(groups: Record<string, BABYLON.AnimationGroup>, name: string): BABYLON.AnimationGroup | undefined
```

a clip of a body's bound groups by its own name: the group of that name, or the one that ends in `/<name>` (a clip set bound with a prefix)

### len

*function* · `packages/combat/src/geometry.ts:13`

```ts
function len: (a: V3) => number
```

### lerp3

*function* · `packages/combat/src/geometry.ts:14`

```ts
function lerp3: (a: V3, b: V3, u: number) => V3
```

### scale

*function* · `packages/combat/src/geometry.ts:11`

```ts
function scale: (a: V3, k: number) => V3
```

### seededRandom

*function* · `packages/combat/src/ai.ts:26`

```ts
function seededRandom(seed: number): () => number
```

a small seeded random source (mulberry32): the same seed, the same fight

### segmentSegment

*function* · `packages/combat/src/geometry.ts:17`

```ts
function segmentSegment(p0: V3, p1: V3, q0: V3, q1: V3): { d: number; p: V3; q: V3 }
```

the nearest points of two segments p0-p1 and q0-q1, and their distance

### sub

*function* · `packages/combat/src/geometry.ts:9`

```ts
function sub: (a: V3, b: V3) => V3
```

### sweepHit

*function* · `packages/combat/src/geometry.ts:47`

```ts
function sweepHit(a0: V3, b0: V3, a1: V3, b1: V3, capsules: readonly Capsule[], o: { steps?: number; thickness?: number } = {}): { capsule: Capsule; point: V3; at: number } | null
```

The first contact of a striking segment that moves from (a0, b0) to (a1, b1) with any of the capsules: the moving segment is tested at `steps` moments between
(a fast blade crosses a forearm within a frame), the earliest touching one wins. `thickness` is the half width of the striking part.

### toScene

*function* · `packages/combat/src/bones.ts:12`

```ts
function toScene: (p: V3) => Vector3
```

a combat point in the scene

### toWorld

*function* · `packages/combat/src/pose.ts:22`

```ts
function toWorld(f: Fighter, p: V3): V3
```

a point of a fighter's skeleton frame (glTF: y up, faces +z) in the world: turned by its yaw, standing at its position

## Interfaces

### AiLevel

*interface* · `packages/combat/src/ai.ts:7`

```ts
interface AiLevel
```

- `reaction: number` — seconds before it sees the opponent's attack start
- `parry: number` — chances, once it has seen an attack coming: parry it, block it, step away (the rest: it does not answer)
- `block: number`
- `dodge: number`
- `aggression: number` — attacks it starts per second when in reach and ready, and the chance a light attack is chained / an attack is the heavy one
- `combo: number`
- `heavy: number`
- `tired: number` — below this stamina it backs off and waits
- `counter: number` — the chance it strikes back at once after it has blocked or parried a blow (the attacker is still recovering)

### AttackDef

*interface* · `packages/combat/src/fighter.ts:13`

```ts
interface AttackDef
```

- `id: string`
- `clip: string`
- `damage: number`
- `stamina: number` — stamina it costs to start
- `window?: [number, number]` — when the weapon can hit, seconds into the clip (default: the clip's `hit-start` .. `hit-end` notifies)
- `chain?: string` — the attack a second press chains into, and from when in this one the press is taken
- `chainFrom?: number`
- `reach?: number` — how close the roots must be for it to land, metres (measured from the clip when absent: `attackReach`)
- `track?: number` — the body turns toward the opponent this much of its turn rate while the attack winds up (before the window), none after

### BodyGeometry

*interface* · `packages/combat/src/pose.ts:9`

```ts
interface BodyGeometry
```

where a fighter's weapon strikes and what of it can be hit, in the world (right-handed, y up)

- `weapon(f: Fighter): [V3, V3]` — the striking part of the weapon: from its inner end to its outer end
- `capsules(f: Fighter): Capsule[]`

### BodySpec

*interface* · `packages/combat/src/pose.ts:19`

```ts
interface BodySpec
```

- `rest: Rest`
- `capsules: CapsuleSpec[]`
- `weapon: WeaponSpec`

### BoneBody

*interface* · `packages/combat/src/bones.ts:14`

```ts
interface BoneBody
```

- `nodes: Record<string, BABYLON.TransformNode>`
- `capsules: CapsuleSpec[]`
- `weapon: WeaponSpec`

### Capsule

*interface* · `packages/combat/src/geometry.ts:7`

```ts
interface Capsule
```

a capsule: the segment a-b grown by r (a body part in space)

- `name: string`
- `a: V3`
- `b: V3`
- `r: number`

### CapsuleSpec

*interface* · `packages/combat/src/pose.ts:16`

```ts
interface CapsuleSpec
```

a capsule between two joints of a body (`extend` metres past the second: the crown of the head, the fingertips)

- `name: string`
- `from: string`
- `to: string`
- `extend?: number`
- `radius: number`

### ClipTiming

*interface* · `packages/combat/src/fighter.ts:7`

```ts
interface ClipTiming
```

what a clip says about itself: its length and its named moments (`hit-start`, `hit-end`, `parry-start`, `parry-end`)

- `duration: number`
- `loop?: boolean`
- `notifies?: { name: string; time: number }[]`
- `additive?: boolean` — an additive clip (a hit reaction of UE's): it plays on top of the guard, not instead of it

### DuelOptions

*interface* · `packages/combat/src/duel.ts:17`

```ts
interface DuelOptions
```

- `arena?: number` — the radius of the field (metres, centred on the origin)
- `keepApart?: number` — the roots of the two are kept this far apart (metres)
- `parryWindow?: number` — a block that began this long before the blow is a parry (seconds; default 0.15)
- `blockArc?: number` — the side a block covers: blows from within this angle of the facing (degrees)
- `parts?: Record<string, number>` — how much a part takes of an attack's damage (others: 1)
- `guardReach?: number` — a defender who guards (blocks facing the blow, or is inside his parry window) meets the blade this far in front of his body: the blade stops on the guard, not inside him (metres; default 0.25)
- `blockCost?: number` — stamina a blocked blow costs, per point of the attack's damage (default 0.7)
- `restartAfter?: number` — start again this long after the fight is over (seconds); absent: wait for `restart()`
- `starts: [{ pos: V2; yaw: number }, { pos: V2; yaw: number }]` — where the two start: positions and yaws

### FighterCommand

*interface* · `packages/combat/src/fighter.ts:52`

```ts
interface FighterCommand
```

what drives a fighter each tick: from the player's input or from the AI

- `move?: V2` — where to go, in the fighter's own frame: [right, forward], each -1..1
- `face?: V2` — a point on the ground (world x, z) to face; the body turns to it at its turn rate
- `attack?: boolean` — pressed this tick (an edge, not held)
- `heavy?: boolean`
- `block?: boolean` — held
- `dodge?: boolean | V2` — pressed this tick: a step away; `[right, forward]` picks the way (default straight back)
- `run?: boolean`

### FighterState

*interface* · `packages/combat/src/fighter.ts:68`

```ts
interface FighterState
```

- `kind: FighterStateKind`
- `clip: string`
- `t: number`
- `attack?: AttackDef`
- `landed?: boolean`
- `queued?: boolean`
- `dodgeDir?: V2`

### MoveSet

*interface* · `packages/combat/src/fighter.ts:29`

```ts
interface MoveSet
```

- `clips: Record<string, ClipTiming>`
- `guard: string`
- `walk: { fwd: string; bwd: string; left: string; right: string }`
- `run?: string`
- `attacks: AttackDef[]`
- `light: string` — the first attack of the light chain, and the heavy one (the held attack)
- `heavy: string`
- `block: string`
- `blockImpact: string`
- `parry?: string` — a parry clip (the move a well-timed block becomes); without one the block impact plays
- `hit: { front: string; back: string; left: string; right: string }`
- `stumble: { forward: string; backward: string }`
- `death: { forward: string; backward: string }`
- `dodge: { clip: string; distance: number; time: number; stamina: number }`
- `speed: { walk: number; run: number; back: number; strafe: number; turn: number }` — metres per second; `turn` degrees per second
- `health?: number`
- `stamina?: number`
- `stun?: number` — how long a blow that lands locks the fighter in its reaction (seconds; default the reaction clip's length)

### WeaponSpec

*interface* · `packages/combat/src/pose.ts:18`

```ts
interface WeaponSpec
```

a weapon held in a hand: its grip in the hand's frame (a library socket's `grip`) and the part that strikes, metres along the grip's axis from the grip point

- `bone: string`
- `grip: { point: V3; axis: V3 }`
- `strike: [number, number]`

## Types

### DuelEvent

*type* · `packages/combat/src/duel.ts:8`

```ts
type DuelEvent = | { type: 'hit'; t: number; attacker: string; defender: string; attack: string; part: string; damage: number; point: V3; side: 'front' | 'back' | 'left' | 'right'; clipTime: number; health: number } | { type: 'blocked'; t: number; attacker: string; defender: string; attack: string; point: V3; stamina: number; broken: boolean; clipTime: number } | { type: 'parried'; t: number; attacker: string; defender: string; attack: string; point: V3; clipTime: number } | { type: 'death'; t: number; fighter: string; by: string } | { type: 'scorched'; t: number; defender: string; damage: number; health: number } | { type: 'over'; t: number; winner: string; loser: string } | { type: 'restart'; t: number }
```

### FighterStateKind

*type* · `packages/combat/src/fighter.ts:67`

```ts
type FighterStateKind = 'free' | 'attack' | 'block' | 'blockImpact' | 'parry' | 'react' | 'stagger' | 'dodge' | 'dead'
```

### V2

*type* · `packages/combat/src/fighter.ts:4`

```ts
type V2 = [number, number]
```

### V3

*type* · `packages/combat/src/geometry.ts:4`

```ts
type V3 = [number, number, number]
```

## Constants

### AI_LEVELS

*const* · `packages/combat/src/ai.ts:19`

```ts
const AI_LEVELS: Record<"normal" | "easy" | "hard", AiLevel>
```

### STAMINA_DELAY

*const* · `packages/combat/src/fighter.ts:70`

```ts
const STAMINA_DELAY: 0.6
```

### STAMINA_REGEN

*const* · `packages/combat/src/fighter.ts:70`

```ts
const STAMINA_REGEN: 18
```

