Honey Village: six model builds + hub frontend, Dockerized for Dokploy

This commit is contained in:
2026-07-15 10:50:12 -04:00
commit 67253bded5
523 changed files with 58879 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
import puppeteer from 'puppeteer-core';
import { spawn } from 'node:child_process';
import net from 'node:net';
const preview = spawn('npx', ['vite','preview','--host','127.0.0.1','--port','4815','--strictPort'], { stdio:'ignore' });
process.on('exit', () => { try{preview.kill()}catch{} });
for (let i=0;i<50;i++){ const ok = await new Promise(r=>{const s=net.connect(4815,'127.0.0.1');s.once('connect',()=>{s.end();r(true)});s.once('error',()=>r(false))}); if(ok)break; await new Promise(r=>setTimeout(r,300)); }
const browser = await puppeteer.launch({ executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', headless: true, args: ['--autoplay-policy=no-user-gesture-required','--mute-audio'] });
const page = await browser.newPage();
await page.setViewport({ width: 960, height: 560 });
page.on('pageerror', e => console.log('[pageerror]', (e.stack||e.message).slice(0,600)));
await page.goto('http://127.0.0.1:4815/', { waitUntil: 'networkidle2' });
await page.waitForFunction('window.__game && window.__director', { timeout: 20000 });
await page.waitForFunction(() => window.__game.scene.isActive('TitleScene'), { timeout: 20000 });
await page.evaluate(() => { document.querySelector('#game input[type="text"]').closest('div').querySelector('button').click(); });
await page.waitForFunction(() => window.__game.scene.isActive('HouseScene'), { timeout: 15000 });
const sleep = (ms) => new Promise(r=>setTimeout(r,ms));
const tap = async (code, hold=90) => { await page.keyboard.down(code); await sleep(hold); await page.keyboard.up(code); await sleep(30); };
await sleep(400);
// 事件日志钩子
await page.evaluate(() => {
const d = window.__director;
window.__log = [];
const orig = d.emit.bind(d);
d.emit = (ev, ...a) => { window.__log.push(`${Date.now()%100000} ${ev} ${JSON.stringify(a).slice(0,80)}`); return orig(ev, ...a); };
d.setDay(2);
d.taskProgress['letters'] = 3;
});
const SCENE = { bakery: 'BakeryScene', shop: 'ShopScene', clinic: 'ClinicScene' };
async function startScene(k) {
await page.evaluate((k) => {
const sm = window.__game.scene;
for (const sc of sm.getScenes(true)) { const key = sc.scene.key; if (key !== 'UIScene' && key !== k) sm.stop(key); }
sm.start(k, { spawn: 'default' });
}, k);
await page.waitForFunction((k) => window.__game.scene.isActive(k), { timeout: 9000 }, k);
await sleep(400);
}
async function deliver(mapKey, npcId) {
await startScene(SCENE[mapKey]);
await page.evaluate((npcId) => {
const k = Object.keys(window.__game.scene.scenes.map ? {} : {});
const s = window.__game.scene.getScenes(true).find(sc => sc.actors && sc.actors.some(a => a.def.id === npcId));
const a = s.actors.find(a => a.def.id === npcId);
s.player.setPosition(a.sprite.x, a.sprite.y + 8); s.player.setVelocity(0,0);
}, npcId);
await sleep(150);
await tap('KeyE');
await sleep(400);
// drain 最多 8 次
for (let i = 0; i < 8; i++) {
const open = await page.evaluate(() => { const u = window.__game.scene.getScene('UIScene'); return !!(u && u.payload); });
if (!open) break;
await tap('KeyE');
await sleep(200);
}
const st = await page.evaluate(() => ({
lettersTo: window.__director.taskSetProgress['lettersTo'] ? [...window.__director.taskSetProgress['lettersTo']] : null,
clues: [...window.__director.clues], tasks: [...window.__director.tasksDone],
dlg: window.__director.dialogueActive,
}));
console.log(`after ${npcId}:`, JSON.stringify(st));
}
await deliver('bakery', 'yuanyuan');
await deliver('shop', 'mimi');
await deliver('clinic', 'fuyisheng');
await sleep(800);
console.log('--- event log ---');
for (const l of await page.evaluate(() => window.__log)) console.log(l);
process.exit(0);