/**
* Смоук перехода луга -> Серые пруды: герой идёт кликами в северо-западный
* угол карты, где триггер (2,2). Запуск: node tools/smoke-ponds.mjs [url] [скриншот]
*/
import puppeteer from 'puppeteer-core';
const url = process.argv[2] ?? 'http://localhost:5199/';
const shot = process.argv[3] ?? '/tmp/rpg_ponds.png';
const browser = await puppeteer.launch({
executablePath: '/usr/bin/chromium',
headless: true,
// autoplay-policy: в headless ctx.resume() без флага может не резолвиться,
// и навигация, завязанная на звук, зависает.
args: ['--no-sandbox', '--enable-unsafe-swiftshader', '--use-angle=swiftshader',
'--autoplay-policy=no-user-gesture-required', '--window-size=960,540']
});
const page = await browser.newPage();
await page.setViewport({ width: 960, height: 540 });
page.on('console', (msg) => console.log(`[консоль] ${msg.text()}`));
page.on('pageerror', (err) => console.log(`[ошибка страницы] ${err.message}\n${err.stack ?? ''}`));
// Ждём маяк BootScene: без него клики уходят «в загрузку».
const booted = new Promise((resolve) => {
page.on('console', (msg) => {
if (msg.text().includes('[boot] ассеты загружены')) resolve();
});
});
await page.goto(url, { waitUntil: 'networkidle2', timeout: 20000 });
await booted;
await new Promise((r) => setTimeout(r, 1500)); // fade перехода в меню
// «Новая игра» (первый пункт в свежем профиле; канвас 480x270 растянут x2)
await page.mouse.click(480, 283);
await new Promise((r) => setTimeout(r, 2500));
// Шагаем к переходу (2,2): в изометрии он строго «вверх экрана» от старта
// (герой идёт по диагонали tx=ty, экранная X неизменна). Экран->мир зависит
// от камеры, поэтому точку клика вычисляем в странице: на 2 тайла выше ног
// героя в сторону триггера. Каждый клик подтягивает героя к (2,2).
async function stepClick() {
return page.evaluate(() => {
const g = window.__game;
const sc = g.scenes.current;
const tile = sc.player.currentTile();
const canvas = document.querySelector('canvas');
const rect = canvas.getBoundingClientRect();
const wr = g.renderer.worldRoot.position;
const halfH = 8; // DEFAULT_ISO: tileH 16
const wx = 0; // диагональ tx=ty — экранная X равна 0
const wy = (tile.x + tile.y) * halfH + halfH - 32; // на 2 тайла выше ног
return {
x: rect.left + ((wx + wr.x) / 480) * rect.width,
y: rect.top + ((wy + wr.y) / 270) * rect.height
};
}).then((p) => page.mouse.click(p.x, p.y));
}
for (let i = 0; i < 18; i++) {
const near = await page.evaluate(() => {
const sc = window.__game.scenes.current;
return sc && Math.max(sc.player.currentTile().x, sc.player.currentTile().y) <= 5;
});
if (near) {
// Триггер уже на экране — кликаем точно в его мировую точку.
const p = await page.evaluate(() => {
const g = window.__game;
const rect = document.querySelector('canvas').getBoundingClientRect();
const wr = g.renderer.worldRoot.position;
const wy = 4 * 8 + 8; // центр ромба (2,2)
return { x: rect.left + (wr.x / 480) * rect.width,
y: rect.top + ((wy + wr.y) / 270) * rect.height };
});
await page.mouse.click(p.x, p.y);
} else {
await stepClick();
}
await new Promise((r) => setTimeout(r, 700));
}
await page.screenshot({ path: shot });
console.log(`Скриншот: ${shot}`);
await browser.close();