Развилка и память выбора¶
Нелинейная история: «выбери сторону», «кого спасти», «как ответить». Выбор должен закрыть текущий квест и повлиять на дальнейшие.
InkQuest строит ветвление без специальных полей. Хватает трёх примитивов: условие optionals, опциональные задачи и scoreboard-теги. Вместе они образуют полноценный state-machine.
Зачем нужен этот паттерн¶
- Нелинейный сюжет без новой системы. Никаких «диалоговых деревьев» — всё стандартными JSON.
- Память выбора переживает релог, рестарт, перезагрузку датапаков. Теги хранятся в данных игрока, не зависят от состояния квеста.
- Статистика «куда пошли игроки». Достаточно посчитать игроков с тем или иным тегом.
Паттерн делится на две части: развилка внутри одного квеста (выбор закрывает квест) и память выбора для следующих квестов (тег открывает или скрывает контент).
Шаг 1. Развилка внутри этапа¶
Этап содержит обязательную задачу с условием optionals и несколько опциональных задач. Игрок выполняет одну опциональную — обязательная закрывается сама, этап считается пройденным.
{
"version": 3,
"variant": 0,
"title": "Разговор у костра",
"description": "Старик ждёт, что я отвечу. Каждое слово — поворот.",
"tasks": {
"speak": {
"title": "Сделать выбор",
"description": "Один ответ закроет разговор.",
"condition": {
"success": {
"type": "optionals",
"status": "success",
"min": 1
}
}
},
"answer_truth": {
"title": "Сказать правду",
"buttons": ["success"],
"on": {
"success": { "tags": ["story.ch1.path_truth"] }
}
},
"answer_lie": {
"title": "Соврать",
"buttons": ["success"],
"on": {
"success": { "tags": ["story.ch1.path_lie"] }
}
},
"stay_silent": {
"title": "Промолчать",
"buttons": ["success"],
"on": {
"success": { "tags": ["story.ch1.path_silence"] }
}
}
},
"stages": [
["speak", "answer_truth", "answer_lie", "stay_silent"]
]
}
Как это работает¶
- Обязательная
speak— это «детектор выбора». Условиеoptionalsсмотрит на optional-задачи активного этапа, кроме самой себя — то есть автоматически на все три варианта.min: 1означает «достаточно одной с успехом». - Опциональные
answer_truth/lie/silence— это сами варианты выбора. У каждой кнопкаsuccessв книге; клик — задача завершается, ставится свой тег. - Как только игрок жмёт любой ответ — на следующем тике условие
speakстановится истинным, она завершается, этап заканчивается, квест уходит вsuccess.
Кнопки
buttons: ["success"]— самый простой способ дать игроку явный выбор. Если выбор должен происходить через действие в мире (нажал на табличку, поговорил с NPC) — замениbuttonsнаcondition.successсpredicate.
Что будет с невыбранными опциональными¶
Останутся в статусе active навсегда (этап уже закрыт, в новый их никто не пустит). Игрок их не увидит ни в HUD, ни в книге — туда попадают только задачи активного этапа. Закрывать их искусственно через /quest complete ... skip не нужно — это штатное поведение системы.
Шаг 2. Проверка развилки¶
- Выдай квест:
/quest give @s story:ch1_fireside. - Открой книгу — увидишь обязательную
speak(без кнопок) и три опциональных с кнопкамиSuccess. - Нажми одну — квест уйдёт в «Завершённые → success».
- Проверь, что тег появился:
/tag @s list— должен быть, например,story.ch1.path_truth.
Шаг 3. Память выбора в следующих квестах¶
Теперь выбор существует только как тег у игрока. Чтобы он повлиял на дальнейший сюжет — используй require.tags в зависимых квестах.
{
"version": 3,
"variant": 0,
"title": "Возвращение к старику",
"description": "Прошло несколько дней. Костёр всё ещё горит.",
"after": [["story:ch1_fireside"]],
"require": {
"tags": ["story.ch1.path_truth"]
},
"tasks": {
"talk_again": { "title": "Поговорить со стариком" }
},
"stages": [["talk_again"]]
}
Этот квест выдастся только тем, кто в первом квесте выбрал ветку правды. Для остальных создавай отдельные квесты-продолжения с другими require.tags. В data/story/quests/ch1_lie_branch.json:
В data/story/quests/ch1_silence_branch.json:
После завершения первого квеста InkQuest посмотрит на теги игрока и выдаст ровно тот вариант продолжения, который ему подходит.
Важный момент про момент проверки¶
require оценивается в момент завершения зависимости из after — то есть в тот тик, когда игрок успешно прошёл предыдущий квест.
Что это значит на практике:
- Если ты ставишь тег в
on.successфинальной задачи (как в шаге 1) — порядок гарантирован: тег появляется ДО проверкиrequireзависимых квестов. ✅ - Если игрок получит тег позже (через ивент мира, через другой квест, через
tag @s add) — новый квест не разблокируется автоматически. Это намеренно: атомарность ветвления.
Практический вывод: всегда ставь теги ветвления внутри хука on.success той задачи, что является последней обязательной в зависимости. Тогда тег попадёт в скорборд в том же тике, что и завершение квеста.
Гигиена тегов¶
Теги — глобальный пул для игрока. Если два разных квеста используют тег secret_choice — они конфликтуют. Префиксуй теги пространством имён, как пути функций:
| Плохо | Хорошо |
|---|---|
truth_chosen |
story.ch1.path_truth |
helped_npc |
npc.merchant.helped |
boss_killed |
world.boss.lich_killed |
Точка как разделитель работает в командах: @a[tag=story.ch1.path_truth]. Никаких особенностей нет, это просто строка.
Расширения¶
Развилка через действие в мире (без кнопок)¶
Если выбор должен быть «органичным» — игрок ударил по табличке, выпил зелье, надел шлем — замени buttons на условие predicate:
"answer_truth": {
"title": "Сказать правду",
"condition": {
"success": { "type": "predicate", "predicate": "story:answered_truth" }
},
"on": {
"success": { "tags": ["story.ch1.path_truth"] }
}
}
data/story/predicates/answered_truth.json — проверка scoreboard-флага игрока, который ставит командный блок у нужной таблички или NPC:
{
"condition": "minecraft:entity_scores",
"entity": "this",
"scores": {
"story.ch1.answered_truth": { "min": 1 }
}
}
Заранее заведи objective: scoreboard objectives add story.ch1.answered_truth dummy. Командный блок у нужной таблички: scoreboard players set @p story.ch1.answered_truth 1.
Множественный выбор (count > 1)¶
Если игрок должен сделать несколько выборов перед закрытием этапа — измени count:
"speak": {
"title": "Сделать три выбора",
"condition": {
"success": { "type": "optionals", "status": "success", "min": 3 }
}
}
В HUD появится прогресс-бар «0/3», «1/3» и т.д.
Развилка с провалом¶
Один из ответов может быть «неправильным» — он провалит квест:
"answer_lie": {
"title": "Соврать",
"buttons": ["failure"],
"on": {
"failure": { "tags": ["story.ch1.path_lie"] }
}
}
Теперь кнопка под «Соврать» — Failure. Клик → задача завершается failure → квест немедленно уйдёт в failure (поскольку условие optionals ловит любой терминальный статус по умолчанию, но если хотим именно провал — добавь "status": "failure" в условии speak). См. Задача с двумя исходами про эту логику.
Статистика выборов¶
Хочешь знать, сколько игроков пошли по каждой ветке? Подсчитай теги:
execute store result score #stats global_path_truth if entity @a[tag=story.ch1.path_truth]
execute store result score #stats global_path_lie if entity @a[tag=story.ch1.path_lie]
execute store result score #stats global_path_silence if entity @a[tag=story.ch1.path_silence]
И выведи на scoreboard sidebar:
«Невыбранное» как явная ветка¶
Если хочешь обработать сценарий «игрок не выбрал вообще» (например, прошло время) — добавь condition.failure к speak:
"speak": {
"condition": {
"success": { "type": "optionals", "status": "success", "min": 1 },
"failure": { "type": "score", "objective": "ch1_timer", "from": 6000, "to": 0 }
},
"on": {
"failure": { "tags": ["story.ch1.path_default"] }
}
}
5 минут на размышление, иначе «по умолчанию». Это уже комбинация с Задача с двумя исходами.
Подводные камни¶
- Теги не сбрасываются автоматически. Если ты перезапускаешь сюжет («новая игра+»), теги старого прохождения остаются. Убирай их явно:
tag @s remove story.ch1.path_truthв стартовой функции. /quest dropне удаляет теги. Они живут отдельно от данных квеста. Если игроку нужно «переиграть выбор» — сначала сними тег, потом перевыдай квест (или сделай его repeatable).- Развилки в InkQuest — не отдельная фича, а композиция. Если что-то не работает — ищи ошибку в формате задач/условий/тегов, а не в системе ветвления.
- Невыбранное — не «потеряно», а «не пройдено». Файлы квеста остаются нетронутыми, опциональные просто остаются с пустым статусом. Это удобно для статистики, но если тебе важно явно «закрыть» их — добавь обязательной задаче хук
on.success, который проставит тегdefaultдля невыбранных путей.
См. также¶
- Условия выполнения задачи — полная спецификация условия
tasks - Зависимости между квестами — как
require.tagsфильтруют доступ к квестам - Цепочка квестов — упрощённый случай ветвления
- Хуки жизненного цикла задачи — про
on.successи теги