Перейти к содержанию

Цепочка квестов

Сюжет идёт по порядку: пройди первый квест — откроется второй; пройди второй — откроется третий. Самый частый паттерн в любой РПГ.

В InkQuest это делается одним полем after в JSON зависимого квеста. Никаких хуков, никаких функций — система сама выдаёт следующий квест, как только предыдущий завершится успехом.


Зачем нужен этот паттерн

  • Линейный сюжет. Глава первая → глава вторая → глава третья. Игрок не может пропустить шаг.
  • Контроль доступа к контенту. Сложный квест откроется только тем, кто прошёл подготовку.
  • Автоматическая выдача. Не нужно слежение командными блоками — система сама подхватит момент завершения и выдаст следующий квест.

Шаг 1. Создаём первый квест

Положи data/story/quests/ch1_arrival.json:

{
  "version": 3,
  "variant": 0,
  "title": "Прибытие в город",
  "description": "Долгая дорога позади. У ворот ждёт начальник стражи.",
  "tasks": {
    "talk_to_captain": {
      "title": "Поговорить с капитаном",
      "condition": {
        "success": { "type": "predicate", "predicate": "story:talked_to_captain" }
      }
    }
  },
  "stages": [["talk_to_captain"]]
}

Это обычный квест с одной задачей. Ничего особенного.

data/story/predicates/talked_to_captain.json — предикат, который сработает, когда NPC «капитан» поставит флаг разговора:

{
  "condition": "minecraft:entity_scores",
  "entity": "this",
  "scores": {
    "story.captain_spoken": { "min": 1 }
  }
}

Сам флаг ставит командный блок у NPC: scoreboard players set @p story.captain_spoken 1. Перед первым использованием не забудь завести objective: scoreboard objectives add story.captain_spoken dummy.


Шаг 2. Создаём зависимый квест

Положи data/story/quests/ch1_first_task.json. Главное — поле after:

{
  "version": 3,
  "variant": 0,
  "title": "Первое поручение",
  "description": "Капитан просит проверить заброшенный пост у леса.",
  "after": [["story:ch1_arrival"]],
  "tasks": {
    "visit_outpost": {
      "title": "Дойти до поста"
    }
  },
  "stages": [["visit_outpost"]]
}

Поле after — это список групп. В простейшем случае одна группа с одним квестом: «выдай этот квест, когда story:ch1_arrival завершён успехом».


Шаг 3. Выдаём только первый квест

В стартовой функции мира (#minecraft:load или функция при первом входе игрока):

quest give @s story:ch1_arrival

Второй квест выдавать не нужно. InkQuest сам выдаст его, как только игрок успешно завершит первый.


Проверка

  1. Выдай себе первый квест — он появился в книге?
  2. Выполни его (/quest complete @s story:ch1_arrival task talk_to_captain success) — второй квест появился в книге сам? ✅
  3. Если второй не появился — проверь, что:
  4. Имя в after совпадает с ID файла (story:ch1_arrival, а не story/ch1_arrival).
  5. Первый квест завершился со статусом success (не failure — см. ниже про статусы).

Как именно работают зависимости

after — это список групп, и каждая группа — список квестов:

"after": [
  ["a", "b"],
  ["c"]
]

Читается как «AND внутри группы, OR между группами»:

  • [["a", "b"]] — выдать, если завершены И A, И B.
  • [["a"], ["b"]] — выдать, если завершён A ИЛИ B.
  • [["a", "b"], ["c"]] — выдать, если завершены (A И B), ИЛИ просто C.

В цепочке обычно достаточно простого [["предыдущий_квест"]].

Какой статус «считается»

after срабатывает только когда зависимость завершилась успехом (success). Провал или пропуск не открывают зависимый квест. Если хочешь, чтобы цепочка шла и через провалы — оформи их отдельной веткой (Развилка и память выбора).


Расширения

Ветвление: один квест → две ветки

Если из одного квеста должны открыться два разных дальнейших — заведи две зависимости с одинаковым after, и обе откроются одновременно. В файле data/story/quests/quest_a.json:

"after": [["story:ch1_arrival"]]

И то же самое в data/story/quests/quest_b.json:

"after": [["story:ch1_arrival"]]

После завершения ch1_arrival игроку выдадутся оба quest_a и quest_b. Он сможет проходить их параллельно или в любом порядке.

Слияние: два квеста → один общий

Финальный квест требует, чтобы оба «рукава» цепочки сошлись:

"after": [["story:branch_left_end", "story:branch_right_end"]]

Откроется, только когда оба будут пройдены — типичный финал главы после параллельных подсюжетов.

Условное ветвление по выбору игрока

Если цепочка должна разойтись в зависимости от выбора в предыдущем квесте — комбинируй after с require.tags:

"after": [["story:ch1_arrival"]],
"require": {
  "tags": ["story.chose_dark_path"]
}

Откроется только тем, у кого после ch1_arrival появился тег story.chose_dark_path. Подробно — в Развилка и память выбора.

Пауза перед следующим квестом

Если следующий квест должен открыться не сразу, а после события (наступит ночь, игрок дойдёт до места) — добавь в первый квест задачу-ждун. См. Ожидание события.

Условие за пределами квестов

require.predicate позволяет добавить нативный Minecraft-predicate к условию разблокировки. Например, «открыть квест, только если игрок в Незере»:

"after": [["story:ch1_arrival"]],
"require": {
  "predicate": "story:in_nether"
}

data/story/predicates/in_nether.json:

{
  "condition": "minecraft:entity_properties",
  "entity": "this",
  "predicate": {
    "location": {
      "dimension": "minecraft:the_nether"
    }
  }
}

⚠️ Помни: require проверяется в момент завершения зависимости — то есть прямо в тот тик, когда первый квест завершился. Если предикат сработает позже — квест не откроется автоматически. Подробнее — в Развилка и память выбора, раздел «Важный момент про момент проверки».


Подводные камни

  • after не блокирует команду /quest give. Оператор всегда может выдать зависимый квест вручную, обойдя зависимости. Это нужное поведение: для дебага и QA.
  • Завершённый квест не «разблокирует» цепочку задним числом. Если ты добавил after: [["A"]] к новому квесту, а игрок уже давно завершил A — зависимый квест выдастся при следующем quest reload или сразу при загрузке (потому что система проверит условия после релоада датапаков). Но если новый квест уже был выдан, а в файле появилось новое after к ещё-не-завершённому квесту — нужна повторная выдача.
  • Цепочки могут зацикливаться. A зависит от B, B зависит от A — оба никогда не откроются. Это твоя ответственность; система не валидирует циклы.

См. также