<!-- Generated by scripts/build-downloads.mjs; do not edit by hand. -->

# Документация Questwright

Полный комплект документации с https://phoenixtools.dev/ru/docs. Онлайн-версия может быть свежее.

Содержание:

1. Начало работы
2. Quest Studio
3. Написание квестов
4. Цели и прогресс
5. Постановка и камеры
6. Озвучка
7. Лицевая анимация
8. Локализация
9. Интеграция в рантайме
10. Сохранения и мультиплеер
11. Справочник настроек
12. Решение проблем и FAQ

---

# Начало работы

Questwright компилирует квесты, написанные простыми текстовыми файлами, в играбельные сцены
диалогов MetaHuman: с озвучкой, липсинком, постановкой, камерами, целями и журналом. Это
руководство проведёт вас от чистой установки до говорящего NPC.

▶ **Видеоурок:** [Установка и первый озвученный квест](https://youtu.be/IrFoAWTkh_I)

## Требования

| Требование | Значение |
| --- | --- |
| Unreal Engine | 5.6 – 5.8 |
| Запекание липсинка | Плагин MetaHuman + DX12 (опционально, см. ниже) |
| Персонажи | MetaHuman для полной лицевой демонстрации; иначе любой персонаж на скелетном меше |
| Озвучка | Опционально: бесплатный локальный TTS-сервер, ElevenLabs (ваш ключ) или ваши собственные WAV-файлы |
| Тип проекта | C++ или только Blueprint |

Прежде чем начать, стоит понимать две вещи:

- **MetaHuman не обязателен.** Диалоги, квесты, постановка, камеры, журнал и сохранения работают
  с любым персонажем на скелетном меше. Пайплайн MetaHuman отвечает только за запечённый липсинк
  и послойную лицевую анимацию; без него сборка корректно деградирует до нейтрального лица.
- **Ничего не встроено, ничего не «звонит домой».** Синтез голоса и машинный перевод используют
  ваши собственные сервисы и ключи; с вашими WAV-файлами плагин работает полностью офлайн.

## Установка из Fab

1. Откройте страницу Questwright на Fab и добавьте плагин в свою библиотеку.
2. В Epic Games Launcher найдите его в **Library → Fab Library** и нажмите
   **Install to Engine**.
3. Откройте проект, зайдите в **Edit → Plugins**, найдите *Questwright*, поставьте галочку
   **Enabled** и перезапустите редактор.

Для сборок из исходников или CI вместо этого включите плагин в `.uproject`:

```json
{
  "Plugins": [
    { "Name": "Questwright", "Enabled": true }
  ]
}
```

## Настройка проекта в один клик

Откройте **Tools → Quest Studio**. Studio представляет собой единое окно, из которого
управляется всё; остальная документация подробно проводит по нему экскурсию.

![Окно Quest Studio при первом открытии, слева видна библиотека](https://phoenixtools.dev/docs-img/questwright/qw-studio-overview.png)

Нажмите шестерёнку **Global Setup** на панели библиотеки. Она подключает к Blueprint'ам вашего
Player pawn и PlayerController нужные компоненты Questwright: взаимодействие, клиент диалогов,
квестовый HUD. Один клик; операция идемпотентна и обратима.

![Диалог Global Setup со списком Blueprint'ов игрока, которые будут изменены](https://phoenixtools.dev/docs-img/questwright/qw-global-setup.png)

## Назначьте персонажей

Откройте вкладку **Cast** и выберите Blueprint персонажа для каждого NPC-говорящего: перетащите
его или воспользуйтесь пикером. Затем отметьте, какая запись соответствует Player. Колонка проверки состояния
показывает, чего не хватает каждому персонажу; **Fix now** сразу добавляет компоненты (этап
Build сделал бы это и автоматически).

![Вкладка Cast со строками говорящих и колонкой проверки состояния](https://phoenixtools.dev/docs-img/questwright/qw-cast-tab.png)

Для Blueprint'а, только что собранного в MetaHuman, есть необязательный раздел **Fresh MetaHuman
auto-setup**: один раз укажите базовый класс персонажа и Animation Blueprint локомоции, после
чего кнопка **Auto-setup** у каждого говорящего перепривязывает родителя Blueprint'а, назначает
Anim Blueprint на меш Body, выравнивает меш по капсуле и добавляет компоненты диалога. Весь
ручной ритуал выполняется в один клик.

![Раздел Fresh MetaHuman auto-setup на вкладке Cast](https://phoenixtools.dev/docs-img/questwright/qw-cast-autosetup.png)

## Сборка и запуск

Откройте вкладку **Build** и нажмите **RUN**. Пайплайн компилирует квест:

```
parse → validate → voice-over → facial animation → asset generation → bind & stage
```

Живой трекер этапов показывает прогресс; ошибки появляются в предполётном чек-листе над кнопкой
RUN, у каждой есть ссылка **Fix →**, которая переносит на нужную вкладку.

![Вкладка Build: зелёный предполётный чек-лист, трекер этапов и кнопка RUN](https://phoenixtools.dev/docs-img/questwright/qw-build-tab.png)

Нажмите **Play (PIE)** и поговорите с NPC.

![Озвученный диалог MetaHuman с липсинком в PIE, на экране виден выбор реплики](https://phoenixtools.dev/docs-img/questwright/qw-play-result.png)

Весь сгенерированный контент пишется в ваш проект, в `/Game/Questwright/Generated/`.
Собственная папка контента плагина остаётся чистой, а поставляемый демо-квест можно удалить,
ничего не сломав.

## Что дальше

- [Quest Studio](https://phoenixtools.dev/ru/docs/questwright/quest-studio): экскурсия по каждой вкладке.
- [Написание квестов](https://phoenixtools.dev/ru/docs/questwright/writing-quests): формат `.quest.yaml`.
- [Справочник настроек](https://phoenixtools.dev/ru/docs/questwright/settings-reference): объяснение каждой настройки проекта.
- Предпочитаете читать офлайн? Полную документацию можно
  [скачать](https://phoenixtools.dev/ru/docs/questwright/print) одной страницей для печати или Markdown-бандлом.

---

# Quest Studio

Одно закрепляемое окно (**Tools → Quest Studio**) управляет всем пайплайном. Эта страница
проводит по каждой зоне; руководства по ссылкам разбирают стоящие за ними рабочие процессы
подробнее.

![Полное окно Quest Studio: видны библиотека, вкладка квеста и схема постановки](https://phoenixtools.dev/docs-img/questwright/qw-studio-overview.png)

## Библиотека

Левая колонка перечисляет все `*.quest.yaml`, найденные в проекте (по умолчанию в
`Content/Quests/`; файлы появляются автоматически, без шага импорта). У каждого квеста
есть бейдж статуса:

| Бейдж | Значение |
| --- | --- |
| Generated & valid | Ассеты соответствуют актуальной версии файла |
| Changed | Файл изменился после последней сборки; просто запустите RUN ещё раз |
| Validation errors | В файле ошибки; откройте квест, чтобы увидеть находки с переходом к источнику |
| Not built | Ещё ни разу не компилировался |

**+ New Quest** создаёт файл из шаблона. **New Chain** объединяет квесты в сюжетную
цепочку: перетаскивайте квесты в папку цепочки, чтобы задать их порядок. Цепочки хранятся как
чистые данные: перетаскивание записывает `available_when` / `priority` прямо в YAML; никакого
скрытого манифеста, который можно повредить, нет.

![Библиотека с развёрнутой сюжетной цепочкой и бейджами статуса у квестов](https://phoenixtools.dev/docs-img/questwright/qw-library-chains.png)

▶ **Видеоурок:** Библиотека, цепочки и статусы — скоро.

## Вкладка Quest: конструктор

Весь квест редактируется прямо на месте: сцены, реплики, выборы, эмоции, планы, связи шагов,
эффекты, условия, награды и барки, а сведения о цепочке и предусловиях показаны на карточке
квеста. Всё, что пишет конструктор, остаётся обычным YAML: правка файла в текстовом редакторе
всегда эквивалентна, и обе стороны остаются синхронными.

![Вкладка Quest: редактирование сцены с репликами, эмоциями и выборами](https://phoenixtools.dev/docs-img/questwright/qw-quest-tab.png)

▶ **Видеоурок:** Сборка квеста в конструкторе — скоро.

## Схема постановки

В правой зоне находится схема сцены с видом сверху для расстановки участников: перетаскивайте метки,
чтобы позиционировать персонажей, поворачивайте их, добавляйте спавнящихся статистов
(*появляется уже на месте* или *входит в кадр*), ставьте путевые точки для перемещения по ходу
диалога и размещайте **пользовательские камеры** с живым предпросмотром зоны обзора. Поставьте
курсор в реплику диалога, и схема подсветит, кто где стоит в этот момент. Подробности в
[Постановке и камерах](https://phoenixtools.dev/ru/docs/questwright/staging-and-cameras).

![Схема постановки с метками участников, путевой точкой и выбранной камерой с её зоной обзора](https://phoenixtools.dev/docs-img/questwright/qw-staging-canvas.png)

▶ **Видеоурок:** Постановка сцены на схеме — скоро.

## Вкладка Cast

Сопоставляет каждый ключ говорящего с Blueprint'ом персонажа и отмечает Player. Этап привязки
добавляет компоненты Questwright в ваши Blueprint'ы идемпотентно; **Remove Questwright
components** возвращает персонажа в исходное чистое состояние. Раздел **Fresh MetaHuman
auto-setup** превращает Blueprint, только что собранный в MetaHuman, в готового к диалогам
персонажа одним кликом (см. [Начало работы](https://phoenixtools.dev/ru/docs/questwright/getting-started)).

![Вкладка Cast с колонкой проверки состояния и кнопками Fix now](https://phoenixtools.dev/docs-img/questwright/qw-cast-tab.png)

▶ **Видеоурок:** Состав персонажей и проверка состояния — скоро.

## Вкладка Voice

Выберите TTS-провайдера на проект, назначьте голос каждому говорящему, причём **для каждого
языка**, прослушивайте отдельные реплики или синтезируйте их пакетно, либо подложите свои
WAV-файлы. Подробности в [Озвучке](https://phoenixtools.dev/ru/docs/questwright/voice-over).

![Вкладка Voice с назначением голосов по говорящим](https://phoenixtools.dev/docs-img/questwright/qw-voice-tab.png)

▶ **Видеоурок:** Голоса и синтез — скоро.

## Вкладка Localization

Добавляйте языки, смотрите покрытие перевода по квестам и переводите недостающие реплики
машинным переводом со своим ключом Claude или OpenAI (результаты помечаются как «машинный,
требует вычитки»). Подробности в [Локализации](https://phoenixtools.dev/ru/docs/questwright/localization).

![Вкладка Localization с покрытием по языкам](https://phoenixtools.dev/docs-img/questwright/qw-localization-tab.png)

▶ **Видеоурок:** Языки и машинный перевод — скоро.

## Вкладка Build

Предполётный чек-лист (у каждой ошибки есть ссылка **Fix →** на нужную вкладку), чекбоксы языков,
кнопка **RUN**, живой трекер этапов, **Play (PIE)** и **Show Assets**.

![Вкладка Build перед запуском: чек-лист, выбор языков и кнопка RUN](https://phoenixtools.dev/docs-img/questwright/qw-build-tab.png)

Сборки идемпотентны: уже озвученные реплики пропускаются, изменённые синтезируются и
запекаются заново, а ассеты удалённых реплик и шагов вычищаются автоматически.

▶ **Видеоурок:** Сборка: от чек-листа до Play — скоро.

---

# Написание квестов

Квест целиком живёт в одном файле `.quest.yaml`. Файл является **источником истины**: Studio компилирует
его в ассеты, а вкладка Quest служит лишь его представлением; правка вручную и правка в конструкторе
всегда эквивалентны. Эта страница проходит по формату; полный справочник по каждому полю
(написанный так, чтобы заодно служить промптом для ИИ) доступен для
[скачивания](https://phoenixtools.dev/downloads/questwright-authoring-reference.ru.md).

## Где лежат файлы

Кладите квесты в `<Project>/Content/Quests/`, и они появятся в библиотеке Studio автоматически.
Называйте их `<Name>.quest.yaml`, один квест на файл.

## Минимальный скелет

```yaml
quest: Region.MyQuest          # unique id/slug (required)
name: "My Quest"
giver: Bran                    # speaker key of the NPC who gives it
source_culture: en
steps:                         # required, at least 1
  - id: MyQuest_Offer
    scene:
      speakers: [Bran, Player]
      lines:
        - { by: Bran,   id: greet, text: "Hello.", emotion: neutral }
        - { by: Player, id: hi,    text: "Hi." }
      choices:
        - { id: accept, text: "I'll help.", goto: MyQuest_Do }
      on_end: { quest: accept }
  - id: MyQuest_Do
    end: completed
```

## Золотые правила

1. **Стабильные id в нижнем регистре у каждой реплики, выбора и цели.** По id ключуются
   локализация и сгенерированные ассеты голоса и лица; вставка нового контента никогда не
   должна перенумеровывать существующие id.
2. **Используйте только документированные поля, эмоции, планы, виды условий и глаголы
   эффектов.** Валидатор ловит всё, что выходит за рамки контракта, с переходом к источнику по
   каждой находке.
3. **Каждое значение GameplayTag должно быть зарегистрировано** в проекте (нативно в C++ или в
   `DefaultGameplayTags.ini`). Незарегистрированный тег молча не срабатывает; отсюда причина №1
   жалобы «моя цель не засчитывается». См.
   [Цели и прогресс](https://phoenixtools.dev/ru/docs/questwright/objectives-and-progression).

## Шаги

Шаг представляет собой один узел квеста: сцену диалога, отслеживаемую цель или терминальный маркер.

| Поле | Назначение |
| --- | --- |
| `id` | Обязателен, стабилен. Автоматически получает пространство имён `<ShortQuest>_<id>`. |
| `scene` | Сцена диалога (реплики + выборы) |
| `objective` | Отслеживаемая цель: убить / собрать / добраться / поговорить / доставить |
| `next` | Переход к следующему шагу по умолчанию (когда в сцене нет выборов) |
| `end` | Терминальный маркер: `completed`, `declined` или `failed` |
| `require` | Условие входа: например, пустить на шаг сдачи квеста только после выполнения цели |

## Сцены: реплики и выборы

```yaml
scene:
  speakers: [Bran, Mira, Player]   # every speaker key present in this scene
  lines:
    - { by: Bran,   id: greet, text: "Come here.", emotion: worried, shot: cu }
    - { by: Mira,   id: warn,  text: "Don't trust him.", to: Player }
    - { by: Player, id: what,  text: "What's wrong?" }
  choices:
    - { id: help,   text: "I'll help.",      goto: MyQuest_Accept }
    - { id: refuse, text: "Not my problem.", goto: MyQuest_Refuse }
  on_end: { quest: accept }
```

Сцена поддерживает **любое число говорящих**: перечислите все ключи в `speakers` и задайте
`by` у каждой реплики. В рантайме каждый ключ привязывается к актору, уже размещённому на
уровне, либо, если такого нет, к заспавненному по карте говорящих, записанной этапом Cast, и
поставленному рядом с квестодателем. Заспавненные персонажи автоматически убираются по
окончании диалога.

Полезные поля реплики:

- `to:` указывает, кому адресована реплика; управляет кадрированием «через плечо» и поворотом
  головы в сценах с 3+ говорящими.
- `emotion:` принимает одно из значений `neutral`, `happy`, `sad`, `angry`, `afraid`,
  `surprised` (плюс алиасы вроде `worried` → `afraid`). Управляет аддитивным лицевым
  настроением и тоном озвучки.
- `shot:` выбирает план: `auto` (по умолчанию; компилятор кадрирует сцену сам), `wide`, `ots`,
  `cu`, `reaction` или id пользовательской метки камеры (см.
  [Постановку и камеры](https://phoenixtools.dev/ru/docs/questwright/staging-and-cameras)).
- `echo_of:` означает, что реплика повторяет текст выбора; укажите id выбора, чтобы они делили
  один ключ локализации.

Выборы могут проверять и выдавать GameplayTag: `require:` делает выбор доступным, только если
у игрока есть тег(и), `grant:` выдаёт теги при выборе.

## Динамический текст

Текст поддерживает плейсхолдеры `{PlayerName}` и пользовательские `{Token}`, подставляемые в
момент отображения безопасным для локализации способом. О том, как задавать значения токенов
из игры, см. [Интеграцию с рантаймом](https://phoenixtools.dev/ru/docs/questwright/runtime-integration).

## Авторинг с ИИ-ассистентом

Самый быстрый путь — готовый GPT
[Questwright Quest Builder](https://chatgpt.com/g/g-6a43791d7d0081918d2a969c1f7a8577-questwright-quest-builder):
он уже знает формат, так что просто опишите завязку («смотритель маяка просит игрока найти его
пропавшего брата») — и получите готовый `.quest.yaml`.

Предпочитаете другого ассистента? Полный справочник формата намеренно самодостаточен: вставьте
[файл справочника](https://phoenixtools.dev/downloads/questwright-authoring-reference.ru.md) в ChatGPT или Claude вместе с
вашей завязкой и попросите `.quest.yaml`.

В любом случае положите результат в `Content/Quests/`: валидатор поймает любые отклонения от
контракта ещё до того, как они дойдут до сборки.

---

# Цели и прогресс

Здесь описано всё между «принять квест» и «сдать квест»: как засчитываются цели, как условия
ограничивают доступ к контенту, как эффекты меняют состояние и как квесты связываются в цепочки.

## Цели

Отслеживаемая цель живёт внутри шага. Шаг использует **обобщённый id** (`Goal`, `Deliver`),
а конкретный вид цели задаётся в `objective.type`, поэтому вид можно поменять, ничего не
переименовывая:

```yaml
- id: Goal
  objective:
    type: Kill
    target: MyGame.Enemy.Wolf         # GameplayTag; MUST be registered
    count: 3
    location: MyGame.Area.Farm        # optional area gate
    desc: "Kill the wolves near the farm"
  next: Deliver
```

| Тип | Смысл | Как засчитывается |
| --- | --- | --- |
| `Kill` | убить N врагов с тегом | ваш код смерти сообщает событие Kill |
| `Collect` | собрать N предметов с тегом | ваш код подбора сообщает событие Collect |
| `Reach` | добраться до места с тегом | событие Reach или `CompleteObjective` из триггера |
| `TalkTo` | поговорить с кем-то | **автоматически**: начало разговора засчитывает цель |
| `Deliver` | вернуться к квестодателю | выполняется самим актом сдачи квеста |
| `Custom` | что угодно ещё | игра сообщает `Custom` + ваш собственный тег |

Два удобных момента, о которых стоит знать:

- **Целью `TalkTo` служит ключ говорящего, а не тег** (в `target: Mary` стоит тот же ключ, что в
  `speakers:` и на вкладке Cast). Любой разговор с этим NPC засчитывает цель только тому игроку,
  который говорит, и только пока цель активна, поэтому разговор с NPC *до* принятия квеста
  корректно не засчитывает ничего.
- **`Deliver` никогда не блокирует сдачу квеста**: он существует, чтобы в трекере была строка
  «вернуться с докладом», и автоматически отмечается при сдаче квеста.

Засчитывание из вашей игры сводится к одному вызову (Blueprint или C++), серверно-авторитетному
и идемпотентному по `EventSourceId`:

```cpp
Comp->ReportObjectiveEvent(Instigator, EQuestwrightObjectiveEventType::Kill,
                           MatchTags, LocationTags, EventSourceId);
```

Сопоставление тегов **иерархическое**: цель с `target: Enemy.Wolf` засчитывается событием с
тегом `Enemy.Wolf.Alpha`, так что перечислять каждый подтип не нужно.

![Подход к квестодателю: подсказка взаимодействия, разговор автоматически засчитывает цели TalkTo](https://phoenixtools.dev/docs-img/questwright/qw-ingame-interact.png)

## GameplayTags: одно критическое правило

Каждое значение `target`, `location`, `require`/`grant` у вариантов выбора, `tag` в условиях и
`grant_tag` в эффектах представляет собой GameplayTag, который **уже должен быть зарегистрирован** в вашем
проекте, иначе он разрешается в невалидный тег и *молча* игнорируется. Это причина №1 жалобы
«моя цель не засчитывается». Регистрировать теги можно двумя способами:

1. **Нативно в C++** (предпочтительно для тегов, уходящих в релиз): `UE_DEFINE_GAMEPLAY_TAG_COMMENT(...)`.
2. **Project Settings → Project → GameplayTags** (пишет в `Config/DefaultGameplayTags.ini`).

Используйте иерархию через точки (`MyGame.Enemy.Wolf`, `MyGame.Area.Farm`), и иерархическое
сопоставление заработает само собой. Единственное исключение: в цели `TalkTo` указывается ключ
говорящего, а не тег.

## Условия

Условия ограничивают доступность квеста (`available_when`) и вход в шаг (`require`). Атомарные
виды:

| Форма | Смысл |
| --- | --- |
| `{ quest: <QuestId>, state: <state> }` | другой квест находится в состоянии (`available`, `active`, `completed`, `failed`, `declined`) |
| `{ objective: <ObjId>, state: <state> }` | цель `active` / `complete` / `failed` / `inactive` |
| `{ flag: <Name>, equals: <Value> }` | квестовая переменная текущего прохождения равна значению |
| `{ tag: <GameplayTag> }` | у игрока есть gameplay-тег |
| `{ item: <ItemId>, min: <N> }` | в инвентаре ≥ N предметов (через ваш провайдер инвентаря) |
| `{ attribute: <AttrId>, min: <N> }` | характеристика ≥ N (через ваш провайдер статов) |

Составные: `{ all: [...] }` (И), `{ any: [...] }` (ИЛИ), `{ not: <cond> }`.

```yaml
available_when:
  all:
    - { quest: Region.Intro, state: completed }
    - { not: { flag: Betrayed, equals: "true" } }
```

## Эффекты (`on_end:`)

Применяются при выходе из сцены:

```yaml
on_end:
  quest: accept                     # accept | complete | decline | fail | turnin
  set: { RewardTier: Negotiated }   # write flags, read back by flag conditions
  grant_tag: Region.MetBran         # grant gameplay tag(s) to the player
  grant_quest: Region.FollowUp      # auto-accept follow-up quest(s)
```

Сцена сдачи квеста обычно заканчивается на `on_end: { quest: turnin }` плюс `end: completed`.

## Ветвление по выбору игрока

Чтобы квестодатель отреагировал на сделанный ранее выбор, установите флаг в момент выбора, а
затем опишите два шага сдачи: шаг, закрытый флагом, поставьте **первым**, а универсальный
оставьте последним. Маршрутизация квестодателя выбирает первый шаг сдачи, чей `require` проходит:

```yaml
- id: Haggle
  scene: { ... }
  on_end: { quest: accept, set: { RewardTier: Negotiated } }

- id: DeliverHaggled                       # specific branch, goes before the catch-all
  require:
    all:
      - { objective: Goal, state: complete }
      - { flag: RewardTier, equals: Negotiated }
  scene: { ... }
  on_end: { quest: turnin }
  end: completed

- id: Deliver                              # default branch, carries the tracked objective
  require: { objective: Goal, state: complete }
  objective: { type: Deliver, count: 1, desc: "Report back" }
  scene: { ... }
  on_end: { quest: turnin }
  end: completed
```

Демо-квест из поставки использует ровно этот приём (торг ведёт к другой сцене расплаты).

## Цепочки квестов

Цепочка представляет собой просто данные, никакого отдельного ассета:

- у квеста B `available_when: { quest: A, state: completed }` (пререквизит), и/или
- у квеста A `on_end: { grant_quest: B }` (авто-принятие продолжения).

`priority` упорядочивает несколько квестов, предлагаемых одним квестодателем (квест с большим
значением предлагается первым).
UI **New Chain** в Studio записывает эти значения за вас перетаскиванием; ручное написание
эквивалентно. Валидация ловит циклы в цепочках и висячие ссылки на квесты и цели.

## Барки

Фоновые барки (`barks:`) звучат над головой квестодателя по случайному таймеру; барки простоя
(`idle_barks:`) звучат при взаимодействии, когда квестодателю больше нечего предложить. И те и
другие проходят через тот же пайплайн озвучки и лицевой анимации голосом квестодателя:

```yaml
barks:
  interval: [8, 16]
  lines:
    - "Wolves howling again..."
    - { text: "After dark, nobody sets foot in the yard.", weight: 2 }

idle_barks:
  lines:
    - "Rest easy. The farm's safe, thanks to you."
```

## Награды

```yaml
rewards:
  base:
    currency: 20
    xp: 50
    items:
      - { def: SmokedMeat, count: 1 }
```

`currency`, `xp` и `items` записываются при сдаче квеста; сами блага ваша игра выдаёт через шов
провайдера инвентаря (см. [Интеграция в рантайме](https://phoenixtools.dev/ru/docs/questwright/runtime-integration)).

---

# Постановка и камеры

Без какой-либо постановки Questwright просто ставит каждого говорящего лицом к собеседнику и
кадрирует сцену автоматически; многим квестам большего и не нужно. Постановка нужна для
мизансцен с несколькими NPC, спавна статистов, перемещений по ходу диалога и камер,
расставленных вручную.

## Схема постановки

Правая зона Studio представляет собой схему сцены с видом сверху вокруг её **якоря** (размещённого маркера
диалога или трансформа самого квестодателя, если маркер не поставлен). Перетаскивайте метки,
чтобы расставить персонажей, поворачивайте их, добавляйте путевые точки и размещайте камеры.
Поставьте курсор на реплику диалога, и схема подсветит, кто где стоит **в этот момент**:
занятость меток отслеживается пореплично по всей сцене.

![Схема постановки: метки участников, путевая точка и камера с её зоной обзора](https://phoenixtools.dev/docs-img/questwright/qw-staging-canvas.png)

Всё, что редактируется на схеме, записывается в блок `staging:` файла квеста; как и во всём
остальном Studio, ручное написание полностью равнозначно:

```yaml
staging:
  anchor: DialogueMarker_Barn   # a placed BP_QW_DialogueMarker; omit → the giver's transform
  settle_timeout: 4.0           # seconds a walk may take before falling back to teleport
  marks:
    - { id: giver, actor: Bran,   pos: [-100, -30], face: hero, keep: true }
    - { id: hero,  actor: Player, pos: [-100,  60], face: giver }
    - { id: m1,    actor: Mira,   pos: [ 140,  30], face: anchor, appear: arrive }
    - { id: m2,    actor: Mira,   pos: [  40, -80], face: anchor }   # waypoint
```

Ключевые поля метки:

| Поле | Назначение |
| --- | --- |
| `id` | Стабильный идентификатор: `line.stage.to` и камеры ссылаются на метки по id |
| `actor` | Ключ говорящего; опустите для чистой путевой точки |
| `pos` | `[x, y]` в сантиметрах относительно якоря; высота в рантайме прижимается к земле |
| `face` / `yaw` | Автоповорот к якорю / говорящему / метке, либо угол вручную |
| `appear` | `placed` (стоит на месте с начала сцены) или `arrive` (спавнится вне поля зрения и подходит; его первая реплика ждёт прибытия) |
| `keep` | Остаётся ли заспавненный участник после конца диалога (по умолчанию деспавнится) |

Участники, уже находящиеся на уровне (и Player), *перемещаются*; всех остальных плагин
*спавнит* из состава сцены и автоматически убирает.

## Перемещение по ходу диалога

Прикрепите переход `stage:` к любой реплике; реплика подождёт, пока перемещение завершится
(с откатом на телепорт по истечении `settle_timeout`):

```yaml
- { by: Mira, id: step, text: "Let me see the tracks.",
    stage: [ { who: Mira, to: m2, approach: walk } ] }
```

Одна метка вмещает одного участника за раз: валидатор помечает двух человек на одной метке в
один момент как ошибку, а схема показывает конфликт.

## Автоматические камеры

По умолчанию компилятор кадрирует каждую сцену по грамматике планов: устанавливающий общий план
на первой реплике, крупные планы после нескольких реплик подряд от одного говорящего,
кадрирование через плечо по адресату `to:` каждой реплики. Переопределения для отдельной
реплики задаются через `shot:` (`wide`, `ots`, `cu`, `reaction`), а политика на уровне квеста
настраивается:

```yaml
camera:
  cu_run_threshold: 2     # close-up after N consecutive lines from one speaker (default 3)
  establishing: true      # establishing wide on the first line (default true)
  choice_shot: wide       # shot while the player is choosing (default wide)
```

## Собственные метки камер

Для полного режиссёрского контроля размещайте камеры прямо на схеме. У каждой есть живой
предпросмотр зоны обзора, так что вы точно видите, что попадает в кадр:

![Метки собственных камер на схеме постановки с живым предпросмотром зон обзора среди участников и путевых точек](https://phoenixtools.dev/docs-img/questwright/qw-staging-camera.png)

```yaml
staging:
  cameras:
    - { id: cam_quick,  class: dynamic,         pos: [200, 0],    height: 200, look_at: anchor }
    - { id: cam_window, class: BP_MyCam_Voyeur, pos: [-320, 90],  height: 150, look_at: giver, fov: 28 }
    - { id: cam_dutch,  class: BP_MyCam_Dutch,  pos: [140, 200],  height: 120, yaw: -135, pitch: -8 }

# on a line:            shot: cam_window
# while choices unfold: camera: { choice_shot: cam_high }
```

Как всё это устроено:

- **Метка** описывает конкретный экземпляр: стабильный id, позицию относительно якоря и
  направление. Реплики ссылаются на id метки через `shot:`, никогда на класс или координаты.
- Переиспользуемая **оптика** (объектив / DOF / filmback) живёт в классе камеры, то есть в любом
  Blueprint-наследнике `QuestwrightDialogueSceneCamera`. Встроенному `class: dynamic` Blueprint
  не нужен вовсе (оптика по умолчанию; добавьте `fov:` для быстрого нестандартного ракурса).
- `look_at` наводится на якорь, говорящего или метку, и разрешается **в момент смены плана**,
  поэтому камера держит объект в кадре даже после перемещений по ходу диалога.
- Автоматическая грамматика никогда не выбирает собственные метки; они применяются только как
  явные переопределения на реплике (или через `choice_shot`).
- Если класс камеры не загрузился в рантайме, реплика деградирует до запасного общего плана с
  предупреждением; сцена никогда не ломается.

## Заметки о дизайне

Планы сменяются мгновенно (`SetViewTargetWithBlend` с нулевым блендом), а персонажи двигаются
штатной AI-локомоцией движка: без Sequencer и запечённых синематиков, поэтому сцены
продолжают работать, когда меняются ваш уровень, персонажи или анимации. Все ссылки идут на
стабильные идентификаторы (метки, реплики, квесты), а не на координаты или внутренности классов.

---

# Озвучка

Каждая реплика диалога, реплика-барк и фоновый барк могут нести озвучку. Стадия сборки
*resolve-audio* синтезирует (или импортирует) один аудиоассет на реплику и на язык, а лицевая
стадия затем запекает из этого аудио липсинк (см. [Лицевую анимацию](https://phoenixtools.dev/ru/docs/questwright/facial-animation)).

## Провайдеры

Три взаимозаменяемых провайдера, выбираются во вкладках Voice/Build:

| Провайдер | Что это | Стоимость |
| --- | --- | --- |
| **Provided** | Вы кладёте WAV-файлы, названные по id реплик, в `VO_Source/<culture>/` | — |
| **[OmniVoice](https://github.com/debpalash/OmniVoice-Studio)** | Локальный HTTP-сервер TTS; ставится и запускается одним кликом **Setup & Launch** на вкладке Voice | бесплатно, локально |
| **ElevenLabs** | Облачный TTS с вашим API-ключом | ваш аккаунт |

В комплекте ничего не поставляется и ничто никуда не «звонит»: OmniVoice работает на вашей
машине, ElevenLabs использует ваш ключ, а с готовыми WAV-файлами (Provided) плагин работает
полностью офлайн.

## Назначение голосов

Голоса назначаются **на говорящего и на язык** во вкладке Voice: у одного персонажа может быть
английский голос и русский с другим кастингом. Можно прослушать отдельную реплику,
перегенерировать одну реплику или пакетно синтезировать всё недостающее.

![Вкладка Voice: выбор провайдера и назначение голосов по говорящим](https://phoenixtools.dev/docs-img/questwright/qw-voice-tab.png)

Синтез идемпотентен: уже озвученные реплики при пересборке пропускаются, изменённые
синтезируются заново, а аудио удалённых реплик вычищается. Сгенерированные звуковые ассеты
называются по стабильным id реплик (`<Step>_<line>_<Speaker>`); именно поэтому id никогда
нельзя перенумеровывать.

## Настройка OmniVoice

OmniVoice — локальный TTS-сервер с открытым исходным кодом:
[github.com/debpalash/OmniVoice-Studio](https://github.com/debpalash/OmniVoice-Studio).
Склонируйте или скачайте этот репозиторий в любое место на машине — дальше Studio всё сделает
сама.

Укажите в **Repo path** путь к скачанному репозиторию и нажмите **Setup & Launch** (это нужно,
только если вы не запустили сервер сами заранее — для уже работающего сервера достаточно
указать его URL). Кнопка устанавливает зависимости и запускает сервер, обходя подводные камни
консольного окружения, которые ломают запуск вручную; при первом запуске дополнительно
скачиваются ML-модели, так что он может идти несколько минут без вывода. Дождитесь, пока
строка статуса станет зелёной. Если сервер не стартует, посмотрите `_qw_omnivoice.log` рядом с
файлами сервера.

![Секция OmniVoice на вкладке Voice: Repo path указывает на склонированный репозиторий, рядом кнопка Setup & Launch](https://phoenixtools.dev/docs-img/questwright/qw-omnivoice-setup.png)

Когда сервер поднят, нажмите **Fetch voices** — все доступные на сервере голоса подтянутся в
выпадающие списки по говорящим.

![Кнопка Fetch voices получает всех доступных спикеров с работающего сервера](https://phoenixtools.dev/docs-img/questwright/qw-omnivoice-fetch.png)

Нужно больше разнообразия? Откройте список **Add preset** с подобранными голосовыми пресетами
сервера и выберите нужный — после клика он автоматически появится как опция в выпадающих
списках голосов ваших говорящих.

![Список Add preset: выбранный пресет становится доступен как голос для ваших говорящих](https://phoenixtools.dev/docs-img/questwright/qw-omnivoice-presets.png)

## Где живёт конфигурация

Провайдер/модель на культуру и карта голосов по говорящим относятся к настройкам проекта: вкладка
Voice редактирует **Project Settings → Plugins → Questwright Studio** (`BuildCultures`,
`VoiceByCulture`), которые хранятся в `DefaultGame.ini` и потому общие для всей команды. См.
[Справочник настроек](https://phoenixtools.dev/ru/docs/questwright/settings-reference).

**API-ключи никогда не хранятся в файлах проекта.** Ключи живут в пользовательском ini
(игнорируется git) с запасным вариантом через переменную окружения (`QW_ELEVENLABS_KEY`), что
безопасно для команд и CI.

## Ваши собственные записи

Чтобы использовать записанную озвучку, выберите провайдер **Provided** и положите WAV-файлы,
названные по id реплик, в `VO_Source/<culture>/`. Смешивать можно: на время производства
синтезируйте заглушки через OmniVoice, а позже подмените их студийными записями: id не
меняются, а значит не меняется и всё остальное.

---

# Лицевая анимация

Questwright не нуждается в захвате мимики: лицевая анимация **запекается из аудио озвучки**.
Если у реплики есть озвучка, у неё будет липсинк, и неважно, пришло аудио из TTS или из ваших
собственных записей.

## Запекание (на этапе сборки)

Лицевая стадия сборки прогоняет каждую озвученную реплику через MetaHuman Animator и запекает
липсинк-`UAnimSequence` на реплику (`FA_<LineId>`), выводя настроение из значения `emotion:`
реплики. Как и любая стадия, она идемпотентна: неизменённые реплики пропускаются, осиротевшие
лицевые анимации удалённых реплик вычищаются.

Требования этой стадии: включённый плагин **MetaHuman** и **DX12**. Оба проверяются
предполётной проверкой во вкладке Build; там же переключатель лицевой стадии позволяет
пропустить её целиком (быстрые итерационные сборки).

## Наложение слоёв в рантайме

В рантайме плагин собирает лицо послойно **в коде** на любом MetaHuman; Animation Blueprints
вашего персонажа никогда не модифицируются:

```
lip-sync slot → procedural blinking → additive emotion → eye gaze → the character's own RigLogic
```

Поворот головы к текущему говорящему применяется через рантаймовый post-process Anim Blueprint
на теле (у MetaHuman кость головы leader-позируется от тела), поэтому персонажи естественно
смотрят на того, кто говорит, в том числе после перемещений постановки по ходу диалога.

![Два MetaHuman в диалоге: голова слушателя повёрнута к говорящему](https://phoenixtools.dev/docs-img/questwright/qw-ingame-dialogue.png)

## Всегда мягкая деградация

Каждая часть лицевого пайплайна деградирует мягко, а не ломает сборку или сцену:

| Ситуация | Результат |
| --- | --- |
| Нет плагина MetaHuman / нет DX12 | Лицевая стадия пропущена с предупреждением; диалог играет с нейтральным лицом |
| Персонаж не является MetaHuman | Диалог, постановка и камеры работают; запечённого липсинка нет |
| Лицевой ассет не загрузился | Нейтральное лицо для этой реплики, предупреждение в логе |

Классы Anim Blueprint лица и поворота головы заданы мягкими ссылками, которые разрешаются
лениво в рантайме, поэтому плагин нормально загружается в проектах вообще без контента MetaHuman.

## Локализованные лица

Каждый язык получает собственную озвучку **и собственное запекание липсинка**: русская реплика
двигает рот по-русски. В упакованных сборках ассеты по культурам подменяет система локализации
движка (об оговорке для PIE см. [Локализацию](https://phoenixtools.dev/ru/docs/questwright/localization)).

---

# Локализация

Локализация встроена в пайплайн, а не прикручена сбоку: каждый язык получает собственный текст,
озвучку **и** анимацию липсинка. Всё это производит одна и та же сборка.

## Как это устроено

- Квесты пишутся на одном **исходном языке** (`source_culture` квеста, например `en`).
- Переводы хранятся в CSV-сайдкарах `Loc/<Quest>.<culture>.csv`, ключом служат стабильные id реплик.
  Непереведённые ключи откатываются к исходному тексту; исходный файл никогда не перезаписывается.
- Культуры, которые собирает проект, перечислены в **Project Settings → Plugins → Questwright
  Studio → Build Cultures** (вкладка Localization управляет этим списком за вас).

## Вкладка Localization

Добавляйте языки, смотрите покрытие перевода по каждому квесту и переводите недостающие реплики
машинно со своим ключом **Claude или OpenAI**. Машинные результаты помечаются как *«машинный,
требует вычитки»*, так что ручную проверку легко отслеживать. Ключи хранятся у каждого
пользователя локально и никогда не попадают в репозиторий, точно так же, как ключи для озвучки.

![Вкладка Localization с покрытием по языкам и действием машинного перевода](https://phoenixtools.dev/docs-img/questwright/qw-localization-tab.png)

## Сборка языков

На вкладке Build есть чекбокс для каждой культуры. Каждый собранный язык получает:

- собственные String Tables (диалоги, варианты выбора, текст журнала);
- собственную озвучку с голосовым составом для этого языка с вкладки Voice;
- собственное запекание липсинка из аудио этого языка.

Ассеты каждой культуры лежат в `/L10N/<culture>/`, а подменяет их штатная система
локализации движка.

## Единственный нюанс: PIE и упакованная сборка

Unreal подменяет **ассеты** по культуре (аудио, лицевую анимацию) только в
упакованных/приготовленных сборках; в PIE вы всегда слышите голос исходной культуры.
**Текстовая** локализация работает везде, включая PIE. Это поведение движка, а не настройка,
которую можно поправить; проверяйте локализованную озвучку в упакованной сборке.

## Динамический текст и локализация

Плейсхолдеры `{PlayerName}` и собственные `{Token}` переживают перевод: подстановка происходит
в момент отображения через `FText::Format`, поэтому переводчики могут свободно переставлять
токены внутри предложения.

---

# Интеграция в рантайме

Questwright нативен для движка и самодостаточен; поверхность интеграции сознательно небольшая.
Эта страница описывает все точки соприкосновения плагина с кодом вашей игры (C++ или Blueprint).

## Квестовый компонент

Прогресс квеста представляет собой факт о паре **(игрок, квест)**: он живёт в `UQuestwrightQuestComponent`,
который автоматически добавляется каждому PlayerState (и никогда не хранится на NPC). Получить
его можно откуда угодно:

```cpp
UQuestwrightQuestComponent* Comp = UQuestwrightQuestComponent::Get(SomeActor);
UQuestwrightQuestComponent* Local = UQuestwrightQuestComponent::GetLocalPlayer(World);
```

## Засчитывание целей

Когда происходит что-то значимое для квеста (убийство, подбор предмета), сделайте один вызов
(BlueprintCallable, серверно-авторитетный, идемпотентный по `EventSourceId`):

```cpp
Comp->ReportObjectiveEvent(Instigator, Type, MatchTags, LocationTags, EventSourceId);
```

Каждая активная цель, чьи `type` и (иерархически сопоставленные) теги `target`/`location`
подходят под событие, увеличивает счётчик. Если передать убийцу в `Instigator`, засчитается
только этому игроку; если никого не передать, засчитается всем (запасной вариант для общего мира). Для
сюжетных моментов триггер или диалог может вместо этого вызвать:

```cpp
Comp->CompleteObjective(QuestId, ObjectiveId);
```

## UI из коробки

Добавьте `UQuestwrightQuestHUDComponent` на ваш PlayerController, и вы получите экранный трекер
целей, журнал квестов (привяжите клавишу к `ToggleJournal`) и всплывающие уведомления о
состоянии квестов.

![Открытый в игре журнал квестов с трекером и уведомлением о состоянии](https://phoenixtools.dev/docs-img/questwright/qw-ingame-journal.png)

Все виджеты написаны на C++ и заменяются вашими подклассами. Либо пропустите их вовсе и
постройте собственный UI на делегатах, описанных ниже.

## Швы провайдеров

Вместо собственного инвентаря или статов Questwright разговаривает с вашими через интерфейсы
с работающими реализациями по умолчанию:

| Шов | Интерфейс | Где реализуется |
| --- | --- | --- |
| Инвентарь (условия по предметам, награды предметами) | `IQuestwrightInventoryProvider` | Blueprint или C++ |
| Статы / атрибуты (условия по атрибутам) | `IQuestwrightStatProvider` | Blueprint или C++ |
| Бэкенд сохранений | `IQuestwrightSaveProvider` | C++ |
| Синтез голоса | `IQuestwrightVoiceProvider` | C++ (редактор) |
| Приёмник лицевой анимации, smart objects | интерфейсы провайдеров | C++ |

## Состояние квестов как GameplayTags

Вызовите `SetTagTargetActor(Pawn)`, и компонент спроецирует теги `Questwright.Quest.State.*`
на AbilitySystemComponent: состояние квестов становится доступным в требованиях активации
способностей и в любой вашей системе условий на тегах.

## Делегаты

Для собственного UI или игровых реакций:

- `OnQuestStateChanged`: принят / завершён / отклонён / провален;
- `OnObjectivesChanged`: сдвинулся счётчик или завершилась цель;
- `OnJournalDirty`: изменилось что-либо из отображаемого в журнале;
- `OnProgressLoaded`: применён сохранённый прогресс (см.
  [Сохранения и мультиплеер](https://phoenixtools.dev/ru/docs/questwright/saving-and-multiplayer)).

## Токены динамического текста

Плейсхолдеры `{PlayerName}` и собственные `{Token}` в диалогах и тексте журнала подставляются
в момент отображения подсистемой GameInstance:

```cpp
UQuestwrightTextVarsSubsystem* Vars = GameInstance->GetSubsystem<UQuestwrightTextVarsSubsystem>();
Vars->SetPlayerName(TEXT("Ellie"));
Vars->SetTextVar(TEXT("Faction"), TEXT("Rangers"));   // used as {Faction}
```

Оба сеттера помечены как BlueprintCallable. Подстановка использует `FText::Format`, поэтому она безопасна
для локализации.

## Мост «диалог → квест»

Диалоговые сцены меняют состояние квеста через свои эффекты `on_end` (`quest: accept / turnin /
decline / fail`, флаги `set:`, `grant_tag`, `grant_quest`), поэтому склеивающий код между
разговорами и квестами писать не нужно. См.
[Цели и прогресс](https://phoenixtools.dev/ru/docs/questwright/objectives-and-progression).

---

# Сохранения и мультиплеер

Прогресс квестов сохраняется **из коробки**: в одиночной игре вы не пишете ни строчки кода,
а та же модель масштабируется до выделенных серверов через один интерфейс.

## Автосейв / автозагрузка

Квестовый компонент каждого игрока загружает сохранённый прогресс при старте и сохраняет после
изменений (принятие квеста, засчитывание цели, переход на следующий шаг, флаги, завершение).
Сохранения объединяются: серия изменений в одном кадре стоит одну запись. Финальное сохранение
выполняется при выходе, смене карты и логауте, а автозагрузка автоматически восстанавливает
прогресс при переходах между уровнями.

Политика настраивается в **Project Settings → Plugins → Questwright Quests** (подробности в
[справочнике настроек](https://phoenixtools.dev/ru/docs/questwright/settings-reference)):

![Project Settings с опциями сохранения Questwright Quests](https://phoenixtools.dev/docs-img/questwright/qw-settings-quests.png)

Автоматическое сохранение включается для компонентов, принадлежащих **PlayerState** (штатная
схема развёртывания); компоненты на обычных акторах остаются полностью в ручном режиме
`SaveProgress()` / `LoadProgress()`.

## Что сохраняется

Только записи прогресса каждого игрока: состояние, текущий шаг, счётчики целей, флаги,
временные метки. *Контент* квеста живёт в скомпилированных ассетах, а активная сцена диалога
относится к презентации и никогда не сохраняется. Сохранённый прогресс переживает обновления
контента: запись, чей сохранённый шаг больше не существует, откатывается к первому шагу квеста
(с предупреждением в логе), а записи квестов, неизвестных текущей сборке, остаются нетронутыми.

## Три уровня

| Развёртывание | Что нужно сделать |
| --- | --- |
| **Одиночная игра** | Ничего. Один слот `USaveGame` (`Questwright_Quests`). |
| **Кооператив на listen-сервере** | Ничего. Каждый игрок получает отдельный слот по его онлайн-идентификатору (`Questwright_Quests_<id>`). |
| **Выделенный сервер / свой бэкенд** | Реализуйте один C++-интерфейс (ниже). |

Бэкенд подключается через интерфейс `IQuestwrightSaveProvider`:

```cpp
class UMyDbSaveProvider : public UObject, public IQuestwrightSaveProvider
{
    // PlayerKey is the player's UniqueNetId string; use it as your database key.
    virtual bool SaveQuestRecords(const FString& PlayerKey,
                                  const TArray<FQuestwrightQuestRecord>& Records) override;
    virtual bool LoadQuestRecords(const FString& PlayerKey,
                                  TArray<FQuestwrightQuestRecord>& Out) override;
    // Optional non-blocking variant for REST/database fetches:
    // virtual void LoadQuestRecordsAsync(...) override;
};
```

Зарегистрируйте его как `SaveProviderClass` на странице настроек или внедрите экземпляр вызовом
`SetSaveProvider()` при логине. Всё выполняется на стороне сервера; клиенты получают загруженный
прогресс через обычную репликацию.

## Ручное управление

Для игры с точками сохранения: установите `AutoSavePolicy` в *Manual* и вызывайте
`SaveProgress()` / `LoadProgress()` сами. `OnProgressLoaded` срабатывает после применения
сохранённого прогресса.

## Модель мультиплеера

Questwright работает в standalone, на listen-сервере и на выделенном сервере со строгим
серверно-авторитетным разделением:

- **Прогресс индивидуален для каждого игрока**: он хранится в компоненте на PlayerState и
  реплицируется **только владельцу**, поэтому игроки не видят внутренности чужого журнала.
- **Все изменения серверно-авторитетны.** Клиентские вызовы (принятие квеста из UI, сообщение
  о событии) автоматически идут через Server RPC, так что API выглядит одинаково с обеих сторон.
- **Засчитывание убийства** достаётся игроку-инициатору, если ваш код смерти передаёт убийцу;
  иначе засчитывается всем игрокам (фолбэк для общего мира).
- **Презентация никогда не выполняется на сервере.** Камеры, лица, UI диалога и взгляд работают
  только на клиенте-владельце; выделенный сервер не выполняет ничего из этого.
- Компоненты создаются автоматически при логине: присоединившиеся позже игроки получают свои
  без дополнительных действий.

---

# Справочник настроек

Questwright регистрирует три страницы в **Project Settings → Plugins**. Все они действуют на
уровне проекта (`DefaultGame.ini`) и разделяются с командой через систему контроля версий.
Единственное намеренное исключение: **API-ключи здесь никогда не хранятся** (они лежат в
индивидуальном, игнорируемом git ini-файле с фолбэком на переменные окружения).

## Questwright Quests

Сохранение прогресса квестов. Секция конфига:
`[/Script/QuestwrightRuntime.QuestwrightQuestSettings]`.

![Project Settings → Plugins → Questwright Quests](https://phoenixtools.dev/docs-img/questwright/qw-settings-quests.png)

| Настройка | По умолчанию | Что делает |
| --- | --- | --- |
| **Auto Load Progress** (`bAutoLoadProgress`) | Вкл. | Загружает сохранённый прогресс при старте квестового компонента игрока (на стороне сервера). Также восстанавливает прогресс при смене карты, когда PlayerState пересоздаётся. |
| **Auto Save Policy** (`AutoSavePolicy`) | On any change | Когда прогресс автосохраняется. *Manual*: никогда (вы вызываете `SaveProgress()`). *On quest state change*: только переходы жизненного цикла (принятие / завершение / отказ / провал). *On any change*: также переходы шагов, засчитывание целей и флаги. Сохранения объединяются: серия изменений в одном кадре стоит одну запись. |
| **Save On End Play** (`bSaveOnEndPlay`) | Вкл. | Страховочный сброс на диск при завершении работы компонента (выход / смена карты / логаут). Отключите вместе с *Manual* для полностью ручного сохранения. |
| **Save Provider Class** (`SaveProviderClass`) | None | Бэкенд хранилища (C++-класс, реализующий `IQuestwrightSaveProvider`). *None* = встроенный провайдер на слотах `USaveGame`. См. [Сохранения и мультиплеер](https://phoenixtools.dev/ru/docs/questwright/saving-and-multiplayer). |

Автоматическое сохранение включается только для квестовых компонентов, принадлежащих
**PlayerState** (штатная схема развёртывания). Компоненты на обычных акторах остаются полностью
в ручном режиме сохранения/загрузки.

## Questwright Dialogue

Темп диалогов в рантайме. Секция конфига:
`[/Script/QuestwrightRuntime.QuestwrightDialogueSettings]`.

![Project Settings → Plugins → Questwright Dialogue](https://phoenixtools.dev/docs-img/questwright/qw-settings-dialogue.png)

| Настройка | По умолчанию | Что делает |
| --- | --- | --- |
| **Advance Mode** (`AdvanceMode`) | Manual | Как продвигаются реплики. *Manual*: после каждой реплики ждём клика, темп задаёт читатель. *Auto*: реплики проигрываются и продвигаются сами после озвучки; ввода ждут только **выборы** (кинематографичный темп). |
| **Auto Advance Buffer Seconds** (`AutoAdvanceBufferSeconds`) | 0.4 | Режим Auto: дополнительная пауза в секундах после озвучки реплики перед переходом дальше, небольшой вдох между репликами. |
| **Auto Advance No Voice Seconds** (`AutoAdvanceNoVoiceSeconds`) | 2.5 | Режим Auto: сколько остаётся на экране реплика **без** озвучки, чтобы текст успели прочитать. |

Оба значения режима Auto доступны для редактирования только когда *Advance Mode* установлен в
*Auto*.

## Questwright Studio

Культуры локализации и конфигурация озвучки для каждой культуры. Обычно они редактируются через
вкладки Localization и Voice, а не вручную. Секция конфига:
`[/Script/QuestwrightStudio.QuestwrightStudioSettings]`.

![Project Settings → Plugins → Questwright Studio](https://phoenixtools.dev/docs-img/questwright/qw-settings-studio.png)

| Настройка | Что делает |
| --- | --- |
| **Build Cultures** (`BuildCultures`) | Набор культур, которые собирает проект (например `en`, `ru`). Первая запись служит фолбэком исходной культуры, когда квест не объявляет собственную `source_culture`. Пусто = авторинг в одной культуре. |
| **Voice By Culture** (`VoiceByCulture`) | Конфигурация озвучки по культурам; ключом служит код культуры. Каждая запись содержит TTS-**провайдера** (например `OmniVoice`, `ElevenLabs`), **модель** провайдера, подсказку формата **вывода** (например `wav_24000`) и назначения голосов говорящим (ключ говорящего → id голоса у провайдера). |

## Чего *нет* в Project Settings

| Значение | Где живёт |
| --- | --- |
| API-ключи ElevenLabs / Claude / OpenAI | Индивидуальный ini (игнорируется git) + фолбэк на переменные окружения, например `QW_ELEVENLABS_KEY` |
| Контент квестов, цепочки, приоритеты | Сами файлы `.quest.yaml`; скрытого манифеста нет |
| Сгенерированные ассеты | Папка `/Game/Questwright/Generated/` вашего проекта |

---

# Решение проблем и FAQ

## Решение проблем

| Симптом | Причина / решение |
| --- | --- |
| Цель никак не засчитывается | GameplayTag `target`/`location` не зарегистрирован в проекте; см. [Цели и прогрессия](https://phoenixtools.dev/ru/docs/questwright/objectives-and-progression). Проблема номер один. |
| Диалог играет, но липсинка нет | Не включён плагин MetaHuman или нет DX12, поэтому лицевая стадия намеренно деградирует до нейтрального лица. Устраните требование и перезапустите сборку. |
| Кастомная камера не переключается | `shot:` реплики должен ссылаться на объявленный id из `staging.cameras`. Если класс камеры не загружается, сцена откатывается к общему плану с предупреждением. |
| OmniVoice не запускается | Используйте кнопку **Setup & Launch** на вкладке Voice (она обходит подводные камни окружения консоли/venv); проверьте `_qw_omnivoice.log` рядом с сервером. |
| Кнопка RUN неактивна | В предполётном чек-листе есть красные пункты; у каждого есть ярлык **Fix →** к нужной вкладке. |
| Голоса/лица не на том языке в PIE | Это ожидаемо: подмена *ассетов* по культурам работает только в cooked-сборке; локализация текста работает везде. См. [Локализация](https://phoenixtools.dev/ru/docs/questwright/localization). |
| Квест изменён, а ассеты устарели | Библиотека помечает его как «changed»; просто запустите RUN ещё раз. Генерация идемпотентна и подчищает осиротевшие ассеты озвучки/лиц удалённых реплик. |
| Два участника оказываются в одной точке | Две метки (или перемещение посреди диалога) ставят двух персонажей на одну метку в один момент; конфликтующую реплику подсвечивают и валидатор, и схема постановки. |

## FAQ

### Нужны ли MetaHuman?

Нет. Диалоги, квесты, постановка, камеры, журнал и сохранения работают с любым персонажем на
скелетном меше. Пайплайн MetaHuman отвечает только за запечённый липсинк и слоёную лицевую
анимацию; без него сборка изящно деградирует до нейтрального лица.

### Нужно ли писать на C++?

Нет. Настройка выполняется в один клик, цели засчитываются одной BlueprintCallable-нодой, а точки
подключения инвентаря/статов реализуемы в Blueprint. C++ требуется только для кастомного
бэкенда сохранений (`IQuestwrightSaveProvider`).

### Работает ли в мультиплеере / кооперативе?

Да: standalone, listen-сервер и выделенный сервер. Прогресс индивидуален для каждого игрока,
репликация идёт только владельцу, все изменения серверно-авторитетны, а сохранение в кооперативе
работает без кода. См. [Сохранения и мультиплеер](https://phoenixtools.dev/ru/docs/questwright/saving-and-multiplayer).

### Можно ли генерировать квесты процедурно в рантайме?

В рантайме нет: квест остаётся авторским контентом, который компилируется в ассеты Studio (или
commandlet-компилятором в CI). Зато *можно* автоматизировать авторинг: `.quest.yaml` хранится
обычным текстом, поэтому скрипты или ИИ-ассистент могут генерировать файлы квестов, которые вы
затем собираете как обычно.

### Можно ли импортировать диалоги из внешних инструментов (articy, Twine, таблиц)?

Встроенного импортёра нет, но целевой формат открыт: это документированный текстовый файл, и
[справочник авторинга](https://phoenixtools.dev/downloads/questwright-authoring-reference.ru.md) описывает каждое поле.
Конвертация из любого структурированного источника сводится к небольшому скрипту, а вставка
справочника вместе с вашим исходным текстом в ИИ-ассистент тоже отлично работает.

### Можно ли перестилизовать или заменить UI?

Да. Все виджеты (диалог, выборы, журнал, трекер, тосты, барки) написаны на C++ с классами,
наследуемыми в Blueprint, а делегаты `OnQuestStateChanged` / `OnObjectivesChanged` /
`OnJournalDirty` позволяют вместо этого построить полностью свой UI.

### Зависит ли плагин от других платных плагинов или онлайн-сервисов?

Нет. Плагин самодостаточен и использует только средства движка. Синтез голоса и машинный перевод
опциональны и работают через ваши собственные сервисы/ключи; с вашими WAV-файлами он полностью
работает офлайн.

### Какие версии движка поддерживаются?

Unreal Engine 5.6 – 5.8. Слой совместимости проверяется на этапе компиляции, поэтому
несовпадение версий громко падает при сборке, а не молча в рантайме.

### Моя игра стилизованная / не фотореалистичная. Кинематографичная камера обязательна?

Планы задаются для каждой реплики, поэтому вы сами решаете, сколько «кино» получит сцена: от
полностью кастомных меток камер до простых разговоров. UI-виджеты при этом можно перестилизовать
под жанр.

## Всё ещё не получается?

Напишите на [support@phoenixtools.dev](mailto:support@phoenixtools.dev), указав версию движка,
по возможности файл квеста и соответствующие строки Output Log.
