# Задания — `$.quest`

Задание — обычный объект: кто выдаёт, после чего открывается, что сделать, что
за это дают. Порт из audm-neko (`game/quests/*.gd`), где задание было
`.tres`-ресурсом; здесь его можно описать кодом или пачкой из JSON.

```js
$.ready(() => {
    $.quest.define({
        id: 'relay', title: 'Выключить ретранслятор', kind: 'story',
        giver: 'kek', after: ['prologue'], order: 10,
        objectives: [{ kind: 'object', object_id: 'relay_a', count: 2 }],
        reward: { money: 500, items: { medkit: 1 }, trust: 2 },
        flags: { accept: ['relay_started'], done: ['relay_off'] },
    });

    $.quest.status('relay');     // locked | available | active | ready | done
    $.quest.accept('relay');
    $.quest.onObject('RaidPgt', 'relay_a');   // рейд сообщает событие
    $.quest.turnIn('relay');                  // награда и флаги
});
```

---

## 1. Виды целей

| `kind` | Что считается | Поля |
|---|---|---|
| `fetch` | принести предметы | `item`, `count` |
| `kill` | убить одичалых | `count`, `map` |
| `haul` | эвакуироваться с рюкзаком дороже | `count` — порог в рублях |
| `extract` | эвакуироваться | `count`, `map` |
| `spare` | пощадить сдавшихся | `count`, `map` |
| `object` | выключить объект | `count`, `object_id`, `map` |

`map` — фильтр по карте (часть пути сцены): цель считается только на ней. У
`haul` `count` — **порог стоимости рюкзака**, а не число повторов: цель
выполняется один раз, когда рейд зачтён.

## 2. События рейда

Рейд зовёт их сам, когда что-то случилось; каждый помечает выполненные цели и
**возвращает список готовых к сдаче** заданий.

```js
$.quest.onKill('RaidPgt');                 // +1 к kill-целям этой карты
$.quest.onExtract('RaidPgt', 1500);        // +1 к extract; haul — по рюкзаку
$.quest.onSpare('RaidPgt', 2);             // +2 к spare-целям
$.quest.onObject('RaidPgt', 'relay_a');    // выключен объект
$.quest.onFetch('medkit', 2);              // принесено предметов
```

## 3. Правила открытия

| Поле | Смысл |
|---|---|
| `after` | открывается, когда сданы **все** перечисленные |
| `after_any` | достаточно **любого** из перечисленных (развилка) |
| `excludes` | задание закрыто, если любое из перечисленных взято или сдано |
| `requires_flags` | нужны флаги (`$.story.flags`) |
| `requires_scenes` | нужны просмотренные сцены |
| `merciful_branch` | `[флаг если хватает, флаг если не хватает]` — ветка выбирается при взятии |

Главное правило оригинала перенесено дословно: **взятое задание не пропадает из
журнала**, даже если ветка-близнец сдана раньше (правило QA — работу можно
довести). Поэтому `status` для взятого задания возвращает `active` или `ready`,
но никогда `locked`.

## 4. Пространство имён

| Вызов | Возвращает |
|---|---|
| `$.quest.define(def)` | описание задания |
| `$.quest.loadFile(path \| массив)` | загрузить пачку заданий (`{ quests: [...] }` тоже) и вернуть число |
| `$.quest.def(id)` / `definitions()` | описание / все описания по `order` |
| `$.quest.status(id)` | `locked` / `available` / `active` / `ready` / `done` |
| `$.quest.accept(id)` / `cancel(id)` / `turnIn(id)` | взять / отменить / сдать |
| `$.quest.isReady(id)` | все цели выполнены |
| `$.quest.progress(id, index, value?)` | прочитать или поставить прогресс цели |
| `$.quest.line(id)` | строка журнала: «Выключить: 2 — 1/2» |
| `$.quest.forGiver(giver)` | доступные, взятые и готовые задания выдающего |
| `$.quest.save()` / `load(data)` | снимок и восстановление состояния |
| `$.quest.reset()` | новая игра |

`turnIn` возвращает `{ ok, money, items, trust, flags }` — награду отдаёт игра
(деньги в кошелёк, предметы в инвентарь).

## 5. Сейв

```js
const data = $.quest.save();          // { relay: { status, progress } }
$.save.set('quests', data);           // положить в свой сейв
// при загрузке:
$.quest.load($.save.get('quests'));
```

Флаги сдач живут в `$.story.flags`, поэтому сохраняются вместе с флагами сценок.

## 6. Ограничения

* **награду выдаёт игра**: движок не знает ни кошелька, ни инвентаря — он
  возвращает описание награды;
* **`requires_scenes` полагается на внешний список**: его ведёт игра (или
  `$.story`), движок только проверяет;
* **нет собственного журнала интерфейса**: `line()` и `forGiver()` дают данные,
  вёрстка — за игрой;
* **взаимоисключение одностороннее**: `excludes` закрывает задание по чужому
  статусу, но обратного правила нет — при необходимости перечисляйте взаимно;
* **`merciful_threshold` по умолчанию 4** и настраивается через `createBook`.

## 7. Проверка

```bash
# ядро заданий: статусы, условия, события, награда, сейв (без движка)
build/_deps/quickjs-build/qjs tests/js/quest_test.mjs
```
