Newer
Older
rpg / 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 { startDevServer, openGame, Checks } from '../agent-lib.mjs';

export default async function ({ pretty }) {
    const c = new Checks('interact');
    const server = await startDevServer();
    let ctx;
    let entryTile = null; // тайл, с которого герой кликнул дверь (цель kind 'return')
    try {
        await c.run('дверь: дом Ирвина -> интерьер', async () => {
            ctx = await openGame({ url: server.url, newGame: true });
            await ctx.agent.command('scene:sleepAll');
            // Добираемся до Звенца пешком (луга -> тропа (26,14)).
            const w0 = await ctx.agent.walkTo(26, 14, { timeoutTicks: 3000 });
            c.expect(w0, 'walkTo(26,14) не дошёл');
            const wz = await ctx.agent.waitFor('s.area === "zvenets"', { timeoutTicks: 600 });
            c.expect(wz.ok, 'не в Звенце после перехода', { area: wz.snapshot.area });
            await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
            // Подводим героя к дому (тайл дома (7,6)) и кликаем по нему.
            const wh = await ctx.agent.walkTo(8, 6, { timeoutTicks: 2000 });
            c.expect(wh, 'не дошёл до дома Ирвина');
            entryTile = (await ctx.agent.snapshot()).hero.tile;
            await ctx.agent.tapTile(7, 6);
            const w = await ctx.agent.waitFor('s.area === "house_elder"', { timeoutTicks: 1200 });
            c.expect(w.ok, 'клик по дому не открыл интерьер', { area: w.snapshot.area });
            await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
            const s = await 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 ctx.agent.snapshot();
            const had = before.inventory.find((i) => i.id === 'cloth')?.count ?? 0;
            // Клик по сундуку (3,3) — герой подходит и открывает.
            await ctx.agent.tapTile(3, 3);
            const w = await 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 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 ctx.agent.tapTile(3, 3);
            await ctx.agent.step(120);
            const s2 = await ctx.agent.snapshot();
            c.expect((s2.lastToast?.tick ?? -1) === t0, 'повторное взаимодействие не молчит', s2.lastToast);
            return null;
        });
        await c.run('очаг: реакция меняется с флагом', async () => {
            await ctx.agent.tapTile(8, 0);
            await ctx.agent.step(120);
            const s1 = await ctx.agent.snapshot();
            const text1 = s1.lastToast?.text ?? '';
            c.expect(text1.includes('щепки'), 'реакция очага до флага не та', text1);
            await ctx.agent.command('scene:setFlag', { flag: 'quest_bells_done' });
            await ctx.agent.tapTile(8, 0);
            await ctx.agent.step(120);
            const s2 = await ctx.agent.snapshot();
            c.expect(
                (s2.lastToast?.text ?? '').includes('ровнее'),
                'реакция очага после флага не та',
                s2.lastToast
            );
            return null;
        });
        await c.run('записка: флаг read_note', async () => {
            await ctx.agent.tapTile(10, 4);
            await ctx.agent.step(120);
            const s = await 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 ctx.agent.tapTile(5, 8);
            const w = await ctx.agent.waitFor('s.area === "zvenets"', { timeoutTicks: 1200 });
            c.expect(w.ok, 'проём не вернул в Звенец', { area: w.snapshot.area });
            await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
            const s = await 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 ctx.agent.tapTile(21, 9);
            const w = await ctx.agent.waitFor('s.area === "shop"', { timeoutTicks: 1200 });
            c.expect(w.ok, 'лавка не открылась', { area: w.snapshot.area });
            await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
            await ctx.agent.tapTile(5, 3);
            await ctx.agent.step(120);
            const s1 = await ctx.agent.snapshot();
            const text1 = s1.lastToast?.text ?? '';
            c.expect(text1.includes('соль'), 'реакция прилавка до флага не та', text1);
            await ctx.agent.tapTile(5, 3);
            await ctx.agent.step(120);
            const s2 = await ctx.agent.snapshot();
            c.expect(
                (s2.lastToast?.text ?? '').includes('цветы'),
                'прилавок не изменил реакцию после флага',
                s2.lastToast
            );
            return null;
        });
        await c.run('снапшот: интерактивные объекты области', async () => {
            const s = await 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;
        });
    } finally {
        await ctx?.browser?.close();
        server.stop();
    }
    return c.finish({ pretty }).ok ? 0 : 1;
}