# Story

> Write branching dialogues, quests with tracked objectives and cinematics as project data, and drive them from scripts.

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

Feather's story system covers the three things most narrative games need: branching dialogues,
quests with staged objectives, and cinematics. All of it is project data, authored in the **Story**
window or through `story.*` commands, and saved with the project.

Conditions and actions are short Rhai snippets that use the same functions as
[world scripts](/docs/logic/scripting/). A dialogue choice can check the player's inventory, and a
quest stage can complete when a script value reaches a goal.

## What's included

- **Dialogues:** a node graph of lines and choices, with speakers, conditions, actions, voice-over,
  speaker animations, auto-advance and per-line camera framing.
- **Quests:** ordered stages with objectives, progress toward a goal, completion and failure
  conditions, on-screen tracking, world markers and a journal.
- **Cinematics:** a camera path with timed events — subtitles, animations, moves, sounds, fades and
  more — with letterbox, skipping and a blend back to gameplay.
- Story progress — dialogue choices seen, quest status and stage — is kept in saves.

## Write a dialogue

1. Open **Window → Story** and select the **Dialogues** tab.
2. Create a dialogue. Add nodes to the graph and drag between them to link a line to the next one
   or to a choice's target.
3. For each node, set the **speaker** and the **text**. Add choices for the player, each with
   optional conditions and actions.
4. Use the preview to step through the conversation in the editor.
5. Start the dialogue from a script, for example when the player interacts with a character.

```rust
// A world script: talk to the blacksmith (object 18).
fn on_interact(id) {
    if id == "18" {
        start_dialogue("blacksmith_intro", 18);
    }
}
```

### Dialogue nodes

| Field | Description |
| --- | --- |
| `speaker` | `npc` (the character the dialogue was started with), `player`, an object name or any display name; empty is a narrator line |
| `text` | The line. `{key}` inserts script state and `{npc}` the partner's name. A node with no text is a silent logic step that runs its action and follows the first choice whose condition passes |
| `condition` | Rhai condition; when false the node is skipped and `next` follows |
| `action` | Rhai code run when the node is entered |
| `next` | The node after this one when it has no choices; empty ends the dialogue |
| `choices` | Up to 12 choices |
| `voice` | A sound played with the line |
| `animation` | A clip the speaker plays |
| `camera` | `Free`, `Speaker`, `OverShoulder` or `Wide`; unset uses the dialogue's camera |
| `auto_advance` | Seconds before advancing on its own; 0 waits for the player |

| Choice field | Description |
| --- | --- |
| `text` | What the player picks |
| `next` | Node to go to; empty ends the dialogue |
| `condition` | Rhai condition; when false the choice is hidden |
| `show_disabled`, `hint` | Show a failing choice greyed out with a hint, such as "Needs 50 gold" |
| `action` | Rhai code run when picked |
| `once` | Offer the choice only until it has been picked once |

The dialogue's `camera` defaults to `OverShoulder`. Other options are `Speaker` (close on the
speaker's face), `Wide` (both characters side by side) and `Free` (leave the camera where it is).

## Create a quest

1. In **Window → Story**, select the **Quests** tab and create a quest.
2. Give it a title, description and category, such as "Main" or "Side", and a reward shown in the
   journal.
3. Add stages in order. For each stage, write a short objective and set how it completes.
4. Start the quest with `auto_start`, from a dialogue action, or from a script.

| Quest field | Description |
| --- | --- |
| `category` | Journal group |
| `start` | The first stage; empty uses the first in the list |
| `auto_start` | A Rhai condition that starts the quest when it becomes true |
| `on_complete`, `on_fail` | Rhai code run when the quest ends |
| `reward` | Text shown in the journal |
| `hidden` | Internal quests never show in the journal or tracker |

| Stage field | Description |
| --- | --- |
| `objective` | Short text for the tracker; `{key}` inserts script state |
| `details` | Longer journal entry |
| `complete_when` | Rhai condition that completes the stage |
| `fail_when` | Rhai condition that fails the whole quest |
| `progress`, `goal` | A Rhai number shown as progress toward the goal |
| `on_enter`, `on_complete` | Rhai code run when the stage starts and ends |
| `next` | The next stage; empty completes the quest |
| `target` | Object name the quest marker points at |

Quest conditions are checked every `quest_check_interval` seconds.

## Cinematics

A cinematic is a timed sequence: camera keys plus events. Events can show a subtitle, play an
animation, move, place or show an object, play a sound, fade, shake the camera, set the time of
day, start a dialogue or run Rhai code. A cinematic can follow an `anchor` object, show a
letterbox, hide the HUD, be skippable and blend back to the gameplay camera.

Cinematics are edited on the **Cinematics** tab, and as tracks in the Sequencer. See
[Sequencer](/docs/animation/sequencer/).

## Story settings

| Field | Description | Range | Default |
| --- | --- | --- | --- |
| `text_speed` | Characters revealed per second; 0 shows lines at once | 0–1000 | 45 |
| `dialogue_camera` | Frame the speakers during dialogue | On/off | On |
| `pause_clock_in_dialogue` | Stop the game clock while a dialogue is open | On/off | On |
| `quest_tracker` | Show active objectives on screen | On/off | On |
| `quest_markers` | Draw a marker over the current objective's target | On/off | On |
| `quest_check_interval` | Seconds between quest condition checks | 0.05–10 s | 0.5 s |
| `journal_key` | Opens the quest journal | Key name | J |
| `skip_key` | Skips a skippable cinematic | Key name | Space |
| `advance_keys` | Advance dialogue | Up to 8 keys | Space, Enter, E |
| `letterbox` | Letterbox bar height as a fraction of the screen | 0–0.3 | 0.11 |
| `subtitles` | Show subtitles for cinematic lines | On/off | On |

## Extend it

| Function | Description |
| --- | --- |
| `start_dialogue(id[, npc])` | Starts a dialogue, optionally with an object as the `npc` speaker |
| `end_dialogue()`, `dialogue_active()` | End the open dialogue, or check for one |
| `dialogue_seen(dialogue, node)` | Whether the player has reached a node |
| `start_quest(id)`, `set_quest_stage(id, stage)` | Start a quest or jump to a stage |
| `complete_quest(id)`, `fail_quest(id)` | End a quest |
| `quest_status(id)` | `inactive`, `active`, `completed` or `failed` |
| `quest_stage(id)`, `quest_active(id)` | The current stage, or whether the quest is active |
| `play_cinematic(id)`, `stop_cinematic()`, `cinematic_playing()` | Cinematics |
| `play_sequence(id)` | Plays a Sequencer sequence |
| `open_journal()`, `close_journal()` | The quest journal |

| Hook | Runs when |
| --- | --- |
| `on_dialogue_end(id)` | A dialogue finishes |
| `on_quest(id, status, stage)` | A quest changes status or stage; `status` is `started`, `stage`, `completed` or `failed` |
| `on_cinematic_end(id)` | A cinematic finishes |

```rust
// A world script: play the ending cinematic when the main quest completes.
fn on_quest(id, status, stage) {
    if id == "main" && status == "completed" {
        play_cinematic("ending");
    }
}
```

## Limits

- Up to 512 dialogues with 400 nodes each, and 12 choices per node.
- Up to 256 quests with 64 stages each.
- Up to 128 cinematics with 512 events each.
- Text up to 4,096 characters and Rhai snippets up to 8,192 characters.

## Automate it

```bash
# A two-line dialogue with a choice
feather-cli --connect .feather-session.json command story.set --json '{
  "kind":"dialogue",
  "value":{"id":"blacksmith_intro","title":"Blacksmith","nodes":[
    {"id":"hello","speaker":"npc","text":"Need a blade sharpened?","choices":[
      {"text":"Yes, please.","next":"thanks"},
      {"text":"Not today.","next":""}]},
    {"id":"thanks","speaker":"npc","text":"Come back at dusk."}]}}'

# Show lines instantly and hide quest markers
feather-cli --connect .feather-session.json command story.settings \
  --json '{"text_speed":0,"quest_markers":false}'
```

`story.set` replaces the whole item, so read it with `story.get` first when you change one part.
`story.get` with no arguments summarises every dialogue, quest and cinematic plus the settings.
