Newer
Older
rpg / apps / game / tools / checks / interact.mjs
/**
 * Сценарий interact — интерьеры и интерактивные объекты.
 * 1) дверь: клик по дому Ирвина -> интерьер house_elder;
 * 2) сундук (once): предмет в сумке, used:<id>, повтор молчит;
 * 3) очаг: реакция зависит от флага quest_bells_done;
 * 4) записка (once): флаг read_note;
 * 5) выход через проём: step kind 'return' -> Звенец, герой на тайле входа;
 * 6) лавка Милы: прилавок меняет реакцию с флагом.
 * Запуск: node tools/agent.mjs run tools/checks/interact.mjs
 */
import { withChecks } from '../lib.mjs';

export default async function ({ pretty }) {
    // Тайл, с которого герой кликнул дверь (цель kind 'return').
    let entryTile = null;
    return withChecks(
        'interact',
        async (t) => {
            const { c } = t;
            await c.run('дверь: дом Ирвина -> интерьер', async () => {
                await t.boot();
                await t.sleepEnemies();
                // Добираемся до Звенца пешком (луга -> тропа (26,14)).
                await t.goToArea('zvenets', { x: 26, y: 14 });
                // Подводим героя к дому (тайл дома (7,6)) и кликаем по нему.
                const wh = await t.ctx.agent.walkTo(8, 6, { timeoutTicks: 2000 });
                c.expect(wh, 'не дошёл до дома Ирвина');
                entryTile = (await t.ctx.agent.snapshot()).hero.tile;
                await t.ctx.agent.tapTile(7, 6);
                const w = await t.ctx.agent.waitFor('s.area === "house_elder"', { timeoutTicks: 1200 });
                c.expect(w.ok, 'клик по дому не открыл интерьер', { area: w.snapshot.area });
                await t.ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
                const s = await t.ctx.agent.snapshot();
                c.expect(s.hero.tile.x === 5 && s.hero.tile.y === 7, 'герой не у проёма (5,7)', s.hero.tile);
                return null;
            });
            await c.run('сундук: предмет + used, повтор молчит', async () => {
                const before = await t.ctx.agent.snapshot();
                const had = before.inventory.find((i) => i.id === 'cloth')?.count ?? 0;
                // Клик по сундуку (3,3) — герой подходит и открывает.
                await t.ctx.agent.tapTile(3, 3);
                const w = await t.ctx.agent.waitFor(
                    `(s.inventory.find((i) => i.id === 'cloth')?.count ?? 0) === ${had + 1}`,
                    { timeoutTicks: 1200 }
                );
                c.expect(w.ok, 'сундук не дал полотно', { inventory: w.snapshot.inventory });
                const s = await t.ctx.agent.snapshot();
                const chest = (s.interactables ?? []).find((o) => o.id === 'chest_elder');
                c.expect(chest?.used === true, 'сундук не помечен used', chest);
                c.expect((s.flags ?? []).includes('used:chest_elder'), 'нет флага used:chest_elder');
                // Повтор: тик последнего тоста не меняется.
                const t0 = s.lastToast?.tick ?? -1;
                await t.ctx.agent.tapTile(3, 3);
                await t.ctx.agent.step(120);
                const s2 = await t.ctx.agent.snapshot();
                c.expect((s2.lastToast?.tick ?? -1) === t0, 'повторное взаимодействие не молчит', s2.lastToast);
                return null;
            });
            await c.run('очаг: реакция меняется с флагом', async () => {
                await t.ctx.agent.tapTile(8, 0);
                await t.ctx.agent.step(120);
                const s1 = await t.ctx.agent.snapshot();
                const text1 = s1.lastToast?.text ?? '';
                c.expect(text1.includes('щепки'), 'реакция очага до флага не та', text1);
                await t.ctx.agent.command('scene:setFlag', { flag: 'quest_bells_done' });
                await t.ctx.agent.tapTile(8, 0);
                await t.ctx.agent.step(120);
                const s2 = await t.ctx.agent.snapshot();
                c.expect(
                    (s2.lastToast?.text ?? '').includes('ровнее'),
                    'реакция очага после флага не та',
                    s2.lastToast
                );
                return null;
            });
            await c.run('записка: флаг read_note', async () => {
                await t.ctx.agent.tapTile(10, 4);
                await t.ctx.agent.step(120);
                const s = await t.ctx.agent.snapshot();
                c.expect((s.flags ?? []).includes('read_note'), 'записка не подняла read_note', s.flags);
                return null;
            });
            await c.run('выход через проём: возврат в Звенец', async () => {
                // Клик по тайлу проёма (5,8) — герой доходит, step-переход kind 'return'.
                await t.ctx.agent.tapTile(5, 8);
                const w = await t.ctx.agent.waitFor('s.area === "zvenets"', { timeoutTicks: 1200 });
                c.expect(w.ok, 'проём не вернул в Звенец', { area: w.snapshot.area });
                await t.ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
                const s = await t.ctx.agent.snapshot();
                c.expect(
                    s.hero.tile.x === entryTile.x && s.hero.tile.y === entryTile.y,
                    'герой не на тайле, откуда кликнул дверь',
                    { entry: entryTile, after: s.hero.tile }
                );
                return null;
            });
            await c.run('лавка Милы: прилавок меняет реакцию', async () => {
                // Клик по лавке (21,9) — интерьер; прилавок (5,3).
                await t.ctx.agent.tapTile(21, 9);
                const w = await t.ctx.agent.waitFor('s.area === "shop"', { timeoutTicks: 1200 });
                c.expect(w.ok, 'лавка не открылась', { area: w.snapshot.area });
                await t.ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
                await t.ctx.agent.tapTile(5, 3);
                await t.ctx.agent.step(120);
                const s1 = await t.ctx.agent.snapshot();
                const text1 = s1.lastToast?.text ?? '';
                c.expect(text1.includes('соль'), 'реакция прилавка до флага не та', text1);
                await t.ctx.agent.tapTile(5, 3);
                await t.ctx.agent.step(120);
                const s2 = await t.ctx.agent.snapshot();
                c.expect(
                    (s2.lastToast?.text ?? '').includes('цветы'),
                    'прилавок не изменил реакцию после флага',
                    s2.lastToast
                );
                return null;
            });
            await c.run('снапшот: интерактивные объекты области', async () => {
                const s = await t.ctx.agent.snapshot();
                const list = s.interactables ?? [];
                c.expect(list.some((o) => o.id === 'counter_shop'), 'прилавка нет в снапшоте', list);
                c.expect(list.every((o) => typeof o.used === 'boolean'), 'объект без поля used', list);
                return null;
            });
        },
        { pretty }
    );
}