Play result audio after finishing cutscene ends
This commit is contained in:
@@ -62,6 +62,7 @@ export function createFinishCutscene(onDismiss: () => void, appearances?: [Fight
|
||||
|
||||
reducedMotion.addEventListener('change', advance);
|
||||
return {
|
||||
get active() { return !!dialog; },
|
||||
update(state: LocalMatchState) {
|
||||
const reveal = state.reveal;
|
||||
if (activeMatch && activeMatch !== state.matchId) close(true);
|
||||
|
||||
@@ -130,7 +130,7 @@ export function createMatchView(container: HTMLElement, onAction: (action: Actio
|
||||
const f = s.finished;
|
||||
el('.finish-reason').hidden = f.reason === 'hit';
|
||||
const outcome = s.aborted ? 'aborted' : f.winnerIndex === null ? 'draw' : f.winnerIndex === s.selfIndex ? 'win' : 'loss';
|
||||
if (outcome !== 'aborted') sound.play(outcome, `${s.matchId}:result`);
|
||||
if (outcome !== 'aborted' && !cutscene.active) sound.play(outcome, `${s.matchId}:result`);
|
||||
container.dataset.outcome = outcome;
|
||||
arena.dataset.result = outcome;
|
||||
el('.result-banner h2').textContent = ({ win: '승리!', loss: '패배', draw: '무승부', aborted: '대전 중단' })[outcome];
|
||||
|
||||
@@ -341,6 +341,28 @@ test('global cutscene speed scales stages and CSS timing while respecting result
|
||||
}
|
||||
});
|
||||
|
||||
test('result sound waits for all three cutscene stages', t => {
|
||||
const { container, state } = domHarness(t, false);
|
||||
const started = state.clockNow, cues: string[] = [];
|
||||
t.mock.timers.enable({ apis: ['Date', 'setTimeout', 'setInterval'], now: started });
|
||||
t.mock.method(performance, 'now', () => Date.now() - started);
|
||||
t.mock.method(sound, 'play', (id: string) => { if (!cues.includes(id)) cues.push(id); });
|
||||
state.matchId = 'audio-stage-order'; state.phase = 'finished';
|
||||
state.reveal = { matchId: state.matchId, turnId: 1, actions: ['blast', 'charge'], energyBefore: [1, 1], energy: [0, 1], hitIndex: 1, winnerIndex: 0,
|
||||
revealedAt: started, revealEndsAt: started + 1200 };
|
||||
state.finished = { matchId: state.matchId, winnerIndex: 0, reason: 'hit', stats: emptyPlayerStats(), revealEndsAt: started + 1200, saved: true };
|
||||
const view = createMatchView(container as unknown as HTMLElement, async () => true, () => {}, () => {});
|
||||
try {
|
||||
view.update(state);
|
||||
t.mock.timers.tick(1100); t.mock.timers.tick(100);
|
||||
assert.deepEqual(cues, ['cutscene1', 'cutscene2']);
|
||||
t.mock.timers.tick(1200);
|
||||
assert.deepEqual(cues, ['cutscene1', 'cutscene2', 'cutscene3']);
|
||||
t.mock.timers.tick(1200);
|
||||
assert.deepEqual(cues, ['cutscene1', 'cutscene2', 'cutscene3', 'win']);
|
||||
} finally { view.destroy(); }
|
||||
});
|
||||
|
||||
for (const reducedMotion of [false, true]) test(`cutscene timer automatically opens results once (reduced motion: ${reducedMotion})`, t => {
|
||||
const { state } = domHarness(t, reducedMotion);
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
@@ -404,3 +426,4 @@ test('a dismissed finisher stays closed when the opponent disconnects after the
|
||||
assert.equal(dismissals, 1); assert.equal(appended.length, 1);
|
||||
} finally { cutscene.destroy(); }
|
||||
});
|
||||
import { sound } from '../src/client/sound.js';
|
||||
|
||||
Reference in New Issue
Block a user