/**
* Сценарий quest-bells — полный цикл квеста «Три цветка» (акт 1) через мост.
* 1) Ирвин: elder_first до выбора, вариант по ТЕКСТУ (pickChoiceByText) ->
* флаги met_elder + quest_bells_taken;
* 2) чит-цветы (scene:setVar flowers=3) -> повторный разговор: elder_hand_in
* (квест-стадия ready), флаг quest_bells_done, кат-сцена посадки:
* vars.flowers -> 0, тост «Поляна гудит», поляна зеленеет;
* 3) Мила: trader_first (полотно в сумке, met_mila), затем trader_after
* (эпилог из квест-стадии);
* 4) инвариант interact-used-consistent и контент чисты.
* Запуск: node tools/agent.mjs run tools/checks/quest-bells.mjs
*/
import { withChecks } from '../lib.mjs';
/** Реплика героя в elder_first (выбор по тексту — не по индексу). */
const RING_REPLY = 'Прозвоню дорогу до прудов и вернусь до темноты.';
export default async function ({ pretty }) {
return withChecks(
'quest-bells',
async (t) => {
const { c } = t;
await c.run('Ирвин: elder_first, выбор по тексту, флаги квеста', async () => {
await t.boot();
await t.sleepEnemies();
// NPC в Звенце: тропа на востоке (26,14), затем Ирвин (12,9).
await t.goToArea('zvenets', { x: 26, y: 14 });
await t.ctx.agent.tapTile(12, 9); // Ирвин — клик издалека
const o = await t.ctx.agent.waitFor('s.dialogue?.id === "elder_first"', { timeoutTicks: 900 });
c.expect(o.ok, 'диалог elder_first не открылся', o.snapshot.dialogue);
// Листаем до выбора: advance при выборе активирует курсор — проверяем снапшот.
for (let i = 0; i < 12; i++) {
const s = await t.ctx.agent.snapshot();
if (s.dialogue?.waitingForChoice) break;
if (!s.dialogue) break;
await t.ctx.agent.press('advance'); // догнать печать или следующая реплика
}
const picked = await t.ctx.agent.pickChoiceByText(RING_REPLY);
c.expect(picked, 'вариант по тексту не найден', (await t.ctx.agent.snapshot()).dialogue);
const d = await t.ctx.agent.runDialogue(900);
c.expect(d, 'диалог не завершился после выбора');
const w = await t.ctx.agent.waitFor(
's.flags.includes("met_elder") && s.flags.includes("quest_bells_taken")',
{ timeoutTicks: 300 }
);
c.expect(w.ok, 'граф не поднял флаги знакомства и взятия квеста', w.snapshot.flags);
return null;
});
await c.run('сдача: elder_hand_in, кат-сцена посадки, поляна зеленеет', async () => {
// Чит-цветы: сам сбор (пруды) покрыт клик-роутингом, здесь — сдача.
await t.ctx.agent.command('scene:setVar', { id: 'flowers', value: 3 });
await t.ctx.agent.tapTile(12, 9);
const o = await t.ctx.agent.waitFor('s.dialogue?.id === "elder_hand_in"', { timeoutTicks: 900 });
c.expect(o.ok, 'квест-стадия не дала elder_hand_in', o.snapshot.dialogue);
const d = await t.ctx.agent.runDialogue(900);
c.expect(d, 'диалог сдачи не завершился');
const w = await t.ctx.agent.waitFor(
's.flags.includes("quest_bells_done") && s.vars.flowers === 0',
{ timeoutTicks: 900 }
);
c.expect(w.ok, 'нет флага сдачи или цветы не списаны', {
flags: w.snapshot.flags,
vars: w.snapshot.vars
});
// Кат-сцена посадки: тост и конец (клики блокируются до её конца).
const toast = await t.ctx.agent.waitFor('(s.lastToast?.text ?? "").includes("гудит")', {
timeoutTicks: 900
});
c.expect(toast.ok, 'нет тоста посадки', toast.snapshot.lastToast);
await t.ctx.agent.waitFor('!s.cutscene?.active', { timeoutTicks: 900 });
return null;
});
await c.run('Мила: trader_first (полотно) -> trader_after (эпилог)', async () => {
await t.ctx.agent.tapTile(17, 11);
const o = await t.ctx.agent.waitFor('s.dialogue?.id === "trader_first"', { timeoutTicks: 900 });
c.expect(o.ok, 'первый диалог Милы не открылся', o.snapshot.dialogue);
const d1 = await t.ctx.agent.runDialogue(900);
c.expect(d1, 'первый диалог Милы не завершился');
const inv = await t.ctx.agent.waitFor(
'(s.inventory.find((i) => i.id === "cloth")?.count ?? 0) === 1',
{ timeoutTicks: 300 }
);
c.expect(inv.ok, 'полотно (do[] giveItem) не в сумке', inv.snapshot.inventory);
// Повторный разговор: квест-стадия-эпилог -> trader_after.
await t.ctx.agent.tapTile(17, 11);
const o2 = await t.ctx.agent.waitFor('s.dialogue?.id === "trader_after"', { timeoutTicks: 900 });
c.expect(o2.ok, 'эпилог trader_after не открылся', o2.snapshot.dialogue);
const d2 = await t.ctx.agent.runDialogue(600);
c.expect(d2, 'эпилог не завершился');
return null;
});
await c.run('инварианты чисты после полного квеста', async () => {
await t.expectInvariantsClean('после полного квеста');
return null;
});
},
{ pretty }
);
}