Files
SoundGame/web/harness/sessions/space.mjs
T

107 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 《盲航》新手会话剧本:首局完整演练 D1-D4(第二颗故意超时,听当场重教)→
* 正式对局躲过保护期(20s/45s 的加速/护盾引入都照做)→ 每颗陨石都撑到 loud
* 警报档(撞击前 500ms:警报 + dir_* 方向呼喊同时响,round3 G7)才拨开 →
* 保护期结束后故意漏两颗(第 1 次听归因+向导「同向滑动就能拨开」,第 2 次听
* gd_last_life「最后一次机会」——round2 F2.2:受击经济透明)→ 之后全部躲开,
* 保护期结束后再撑 20 秒以上收工。
*/
export function describe(game) {
const DIR = { left: '左边', right: '右边', up: '上面', down: '下面' };
switch (game.phase) {
case 'ask': return '问我直接出发还是重听演练:单击出发,上滑重听';
case 'drill': return game.drillOb
? `演练:${DIR[game.drillOb.dir]}有陨石在逼近,${DIR[game.drillOb.dir]}滑把它拨开`
: '演练旁白在播,先听';
case 'playing': {
const n = game.obstacles.size;
const protect = performance.now() < game.protectionUntil;
return n
? `对局中:${n} 颗陨石在逼近,听方位同向滑开${protect ? '(保护期,撞了也不掉血)' : ''}`
: `对局中:引擎声平稳,等下一颗${protect ? '(保护期)' : ''}`;
}
case 'feature': return game.featureKind === 'boost' ? '暂停了刷怪教新功能:单击试试加速' : '暂停了刷怪教新功能:双击展开护盾';
case 'menu': return '暂停菜单:单击切选项,双击确认,下滑恢复';
case 'over': return `坠毁了,存活数据播报中。单击再飞一次,下滑回大厅`;
default: return '开场,稍等';
}
}
export async function session(ctx, mock) {
const { game } = ctx;
// 演练 D1-D4:逼近音一响就同向拨开(第二颗故意不出手,听当场重教)。
//(刷怪链是自驱动的定时器链——禁止裸 waitIdle,等待一律挂条件)
for (let i = 0; i < 4; i++) {
await ctx.waitCond(() => game.phase === 'drill' && game.drillIdx === i && !!game.drillOb);
ctx.status(`演练 ${i + 1}/4:${{ left: '左', right: '右', up: '上', down: '下' }[game.drillOb.dir]}侧有陨石`);
if (i === 1) {
ctx.note('这颗犹豫了,没拨开——听当场重教');
await ctx.waitCond(() => game.phase === 'drill' && !game.drillOb); // 超时 → drill_retry
await ctx.waitCond(() => game.phase === 'drill' && game.drillIdx === i && !!game.drillOb);
ctx.note('重教听完,这回马上同向滑');
await ctx.gesture(game.drillOb.dir);
continue;
}
ctx.note('逼近音一响,同向滑开');
await ctx.gesture(game.drillOb.dir);
}
ctx.status('演练做完了,要出发了');
// 正式对局:保护期 60s + 结束后再撑 20s 以上收工。
// 每颗陨石都撑到 loud 警报档(撞击前 500ms:第二段警报 + 「左!/右!…」方向
// 呼喊同时响,round3 G7)才同向拨开——仍在 1.5s 判定窗口内,稳。呼喊与警报
// 同一刻先落,随即滑开(=500ms 阈值恰在 loud 警报定时器触发点上)。
//(刷怪链是自驱动的定时器链——禁止裸 waitIdle,等待一律挂条件)
await ctx.waitCond(() => game.phase === 'playing');
ctx.status('出发。引擎声起来了,保护期里先练手');
const dodged = new Set();
const victims = []; // 保护期结束后故意不躲的(前两颗:听归因 + gd_last_life)
let eaten = 0;
while ((game.phase === 'playing' || game.phase === 'feature')
&& performance.now() < game.protectionUntil + 21000) {
if (game.phase === 'feature') {
ctx.status('游戏停下来教新功能');
// 引入句听完就照做(不裸 waitIdle——feature 态之后的刷怪链是自驱动定时器,
// 裸等会把整局对局连本带利烧穿到坠毁)
await ctx.waitCond(() => !ctx.voiceBusy());
ctx.note(game.featureKind === 'boost' ? '照它说的单击加速' : '照它说的双击开护盾');
await ctx.gesture(game.featureKind === 'boost' ? 'tap' : 'doubletap');
continue;
}
// 被撞的那颗消失 → 归因链听完了,继续
const eatenIdx = victims.findIndex((v) => !game.obstacles.has(v));
if (eatenIdx >= 0) {
victims.splice(eatenIdx, 1);
ctx.note(eaten === 2 && victims.length === 0
? '第二次被撞:先听方位归因,又听到「这是最后一次机会,撑住」——这就是全部家底了'
: '被撞了。先听到方位归因,又听到「同向滑动就能拨开」');
continue;
}
// 撑到 loud 警报档(撞击前 0.5s,第二段警报 + dir 方向呼喊同窗)再出手
//(=500ms 恰在 loud 警报定时器触发点:呼喊先落,同一拍跟着滑开)
const target = [...game.obstacles].find((o) => !dodged.has(o)
&& o.impactAt - performance.now() <= 500);
if (target) {
if (eaten < 2 && performance.now() >= game.protectionUntil) {
eaten++;
victims.push(target);
dodged.add(target); // 标记 = 故意不躲
ctx.status(eaten === 1
? '保护期结束了,这颗我故意没动'
: '又有一颗,也故意不躲——想听听第二次被撞会说什么');
continue;
}
dodged.add(target);
ctx.note('听到第二段警报和方向呼喊,同向滑开');
await ctx.gesture(target.dir);
continue;
}
await ctx.waitCond(() => game.phase !== 'playing'
|| victims.some((v) => !game.obstacles.has(v))
|| [...game.obstacles].some((o) => !dodged.has(o) && o.impactAt - performance.now() <= 500));
}
const aliveSec = Math.round((performance.now() - game.runStart) / 1000);
ctx.status(`保护期结束后撑了 ${aliveSec - 60} 秒、挨了两下还活着;每回撞击前半秒都能听到方向呼喊,先到这`);
}