round2: 看门狗礼仪F1(不重播/单句升级/idle-skip) + 分档归因F2 + 交接count-in/answer F3 + 教学看表现F4-F5 + 警报去叠F6 + 回绕F7 + 开场瘦身F8 + 10句新语音 + 测试全绿(6playtest零改动+guide52断言+资产终检)
This commit is contained in:
+33
-9
@@ -137,6 +137,8 @@ export class RpgGame extends Game {
|
||||
this._ambId = null;
|
||||
// playSec 计时
|
||||
this._segStart = null;
|
||||
// F7:回绕提示去重(同一次菜单打开内每句最多 1 次)
|
||||
this._wrapShown = new Set();
|
||||
this._newRun();
|
||||
}
|
||||
|
||||
@@ -257,6 +259,7 @@ export class RpgGame extends Game {
|
||||
this.scene = id;
|
||||
this.optIdx = 0;
|
||||
this.phase = 'explore';
|
||||
this._wrapShown = new Set(); // F7:场景选项列表重新"打开",回绕提示重新可用
|
||||
this._switchSceneAudio(id, opts.fadeMs || 800);
|
||||
this.audio.play('sfx/step', { volume: 0.8 });
|
||||
|
||||
@@ -307,11 +310,22 @@ export class RpgGame extends Game {
|
||||
return false;
|
||||
}
|
||||
|
||||
_speakOption() {
|
||||
_speakOption(onComplete) {
|
||||
if (this.phase !== 'explore') return;
|
||||
const opts = SCENES[this.scene].options;
|
||||
if (!opts.length) return;
|
||||
this.audio.play(`voice/opt_${opts[this.optIdx]}`);
|
||||
this.audio.play(`voice/opt_${opts[this.optIdx]}`, { onComplete });
|
||||
}
|
||||
|
||||
/**
|
||||
* F7(round2):循环列表切回首项/末项的回绕提示(P1「不报回绕」)。full 档
|
||||
* queue 跟在本项报名之后;同一次菜单打开内每句最多 1 次(_wrapShown),别每滑必播。
|
||||
*/
|
||||
_wrapCue(voiceId) {
|
||||
if (this.guide.level() !== 'full') return;
|
||||
if (this._wrapShown.has(voiceId)) return;
|
||||
this._wrapShown.add(voiceId);
|
||||
this.audio.play(`voice/${voiceId}`, { queue: true });
|
||||
}
|
||||
|
||||
_onExploreGesture(g) {
|
||||
@@ -320,7 +334,10 @@ export class RpgGame extends Game {
|
||||
if (g === 'left' || g === 'right') {
|
||||
const n = opts.length;
|
||||
this.optIdx = (this.optIdx + (g === 'right' ? 1 : n - 1)) % n;
|
||||
this._speakOption();
|
||||
const wrap = n >= 2 && ((g === 'right' && this.optIdx === 0) || (g === 'left' && this.optIdx === n - 1))
|
||||
? (this.optIdx === 0 ? 'gd_wrap_first' : 'gd_wrap_last') : null;
|
||||
// 回绕提示排在本项报名播完之后;被下一次滑动打断则自然作废(不迟到误导)
|
||||
this._speakOption(wrap ? () => this._wrapCue(wrap) : undefined);
|
||||
} else if (g === 'tap') {
|
||||
// voice 播放中单击 = 跳过本句(12 文档 §1:场景 desc/NPC 对白/事件/状态链可跳),
|
||||
// 语音链跳下一句;无 voice 时单击 = 确认选项(原语义)。
|
||||
@@ -605,13 +622,14 @@ export class RpgGame extends Game {
|
||||
const b = this._battle;
|
||||
if (!b) return;
|
||||
b.state = 'PLAYER_SELECT';
|
||||
this._wrapShown = new Set(); // F7:指令循环重新"打开",回绕提示重新可用
|
||||
// 玩家回合的听觉标点(11 文档 §5):turn_player(≤0.4s)→ 留 200ms+ 间隙 → 指令名
|
||||
this.audio.play('sfx/turn_player');
|
||||
// 向导层静默看门狗(schemes §2):探索/常规战斗本无卡关计时,PLAYER_SELECT
|
||||
// 挂机永远静默等——60s 无输入 → nudge + gd_stall(一级即止,自动停止)
|
||||
// 向导层静默看门狗(round2 F1.3:60s→30s,等待默认低频但不再干晾一分钟):
|
||||
// PLAYER_SELECT 挂机永远静默等——30s 无输入 → nudge + gd_stall(一级即止)
|
||||
this.guide.watchdog({
|
||||
arm: true,
|
||||
at: [60000],
|
||||
at: [30000],
|
||||
say: ['gd_stall'],
|
||||
cond: () => this.phase === 'battle' && this._battle === b && b.state === 'PLAYER_SELECT',
|
||||
});
|
||||
@@ -666,7 +684,11 @@ export class RpgGame extends Game {
|
||||
if (b.state === 'PLAYER_SELECT') {
|
||||
if (g === 'left' || g === 'right') {
|
||||
b.cmdIdx = (b.cmdIdx + (g === 'right' ? 1 : CMDS.length - 1)) % CMDS.length;
|
||||
this.audio.play(`voice/cmd_${CMDS[b.cmdIdx]}`);
|
||||
// F7:指令循环回绕提示(探索菜单同款,full 档,跟在本项报名之后)
|
||||
const wrap = (g === 'right' && b.cmdIdx === 0) || (g === 'left' && b.cmdIdx === CMDS.length - 1)
|
||||
? (b.cmdIdx === 0 ? 'gd_wrap_first' : 'gd_wrap_last') : null;
|
||||
this.audio.play(`voice/cmd_${CMDS[b.cmdIdx]}`,
|
||||
wrap ? { onComplete: () => this._wrapCue(wrap) } : undefined);
|
||||
// 教学 T0:切换 2 次(听到指令名切换)即过关
|
||||
if (b.teachStage === 'menu' && ++b.teachSwipes >= 2) this._teachAdvance('attack');
|
||||
} else if (g === 'tap') {
|
||||
@@ -830,10 +852,12 @@ export class RpgGame extends Game {
|
||||
p.hp -= dmg;
|
||||
if (b.enc.teach) p.hp = Math.max(1, p.hp); // 教学战 HP 锁底 1(必不死)
|
||||
console.log(`[RpgGame] 敌方攻击 dmg=${dmg} 格挡=${b.parry} 玩家HP=${p.hp}/${this.maxHp}`);
|
||||
// 教学 T2:格挡成功或受击均可通过 → 进入防御教学 T3(11 文档 §2)
|
||||
// 教学 T2:格挡成功或受击均可通过 → 进入防御教学 T3(11 文档 §2)。
|
||||
// F4(round2):格挡**成功**通过时先插 tut_b2b「挡得漂亮。」确认表现,
|
||||
// 消除 P1/P4「明明挡住了还说来不及格挡」的歧义(失败路径 tut_b4 不变)。
|
||||
if (b.teachStage === 'parry') {
|
||||
b.teachStage = 'defend';
|
||||
const ids = b.parry ? ['voice/tut_b6'] : ['voice/tut_b4', 'voice/tut_b6'];
|
||||
const ids = b.parry ? ['voice/tut_b2b', 'voice/tut_b6'] : ['voice/tut_b4', 'voice/tut_b6'];
|
||||
this._speakGuarded(ids, () => this._playerSelect(), 10000);
|
||||
this._armTeachWatchdog('defend');
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user