feat: speech overflow fix, 60+ voice profiles, post-fight audio cleanup
- Change speak() default to cancelPrevious=true so voices don't queue endlessly - Add stopAllAudio() export and wire into FightViewer unmount + post-fight - Flush speech queue at fight end with delayed cancel for clean cutoff - Expand from 30 to 60+ voice profiles (robots, accents, game, characters) - Add speech bubbles showing bot responses during rounds - Wire announceFinishHim/Fatality/FlawlessVictory/Devastating to not cancel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
d7e2ed8b9d
commit
c87bff01a8
@@ -4,9 +4,9 @@ import { createFightScene, type FightSceneController } from '../game/FightScene'
|
||||
import {
|
||||
fanfareRound, fanfareFight, announce, announceDeep, announceFast,
|
||||
announceDeepIntro, announceRandomHype, announceRoundHype,
|
||||
announceFinishHim, announceFatality, announceFlawlessVictory,
|
||||
announceFinishHim, announceFlawlessVictory,
|
||||
sfxCrowdCheer, sfxCrowdGasp, sfxCrowdOoh, sfxApplause, sfxDrumRoll,
|
||||
setMusicIntensity,
|
||||
setMusicIntensity, stopAllAudio,
|
||||
} from '../game/sounds'
|
||||
|
||||
interface Round {
|
||||
@@ -38,6 +38,7 @@ interface FightData {
|
||||
}
|
||||
|
||||
const props = defineProps<{ fight: FightData; autoplay?: boolean }>()
|
||||
const emit = defineEmits<{ 'replay-done': [] }>()
|
||||
|
||||
const canvasRef = ref<HTMLCanvasElement>()
|
||||
const logEl = ref<HTMLElement>()
|
||||
@@ -65,8 +66,7 @@ const logItems = ref<{ type: string; round: number; text: string; color: string
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.autoplay) {
|
||||
// Fresh fight — start replay immediately instead of showing static result
|
||||
await initScene()
|
||||
// Fresh fight — replay() will call initScene(), no need to double-init
|
||||
await nextTick()
|
||||
replay()
|
||||
} else {
|
||||
@@ -85,6 +85,7 @@ function mapHp(hp: number, winnerId: string | null, botId: string | undefined):
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
stopAllAudio()
|
||||
if (scene) { scene.destroy(); scene = null }
|
||||
})
|
||||
|
||||
@@ -240,17 +241,30 @@ async function replay() {
|
||||
const aWon = round.winnerId === props.fight.botA!.id
|
||||
const bWon = round.winnerId === props.fight.botB!.id
|
||||
|
||||
await scene!.playRound({
|
||||
round: round.roundNumber,
|
||||
challengeType: round.challengeType,
|
||||
winnerId: round.winnerId,
|
||||
botAId: props.fight.botA!.id,
|
||||
botBId: props.fight.botB!.id,
|
||||
narration: round.narration || '',
|
||||
isCritical,
|
||||
botAScore: round.botAScore || 0,
|
||||
botBScore: round.botBScore || 0,
|
||||
})
|
||||
try {
|
||||
await scene!.playRound({
|
||||
round: round.roundNumber,
|
||||
challengeType: round.challengeType,
|
||||
winnerId: round.winnerId,
|
||||
botAId: props.fight.botA!.id,
|
||||
botBId: props.fight.botB!.id,
|
||||
narration: round.narration || '',
|
||||
isCritical,
|
||||
botAScore: round.botAScore || 0,
|
||||
botBScore: round.botBScore || 0,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(`[FightViewer] playRound ${round.roundNumber} error:`, err)
|
||||
}
|
||||
|
||||
// Speech bubbles showing bot responses
|
||||
if (scene) {
|
||||
if (round.botAResponse) scene.showSpeechBubble('a', round.botAResponse.slice(0, 100), 2.8)
|
||||
// Stagger bot B slightly so they don't pop in at the exact same time
|
||||
setTimeout(() => {
|
||||
if (scene && round.botBResponse) scene.showSpeechBubble('b', round.botBResponse.slice(0, 100), 2.5)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// Hit text overlay
|
||||
const hitWords = isCritical
|
||||
@@ -323,6 +337,8 @@ async function replay() {
|
||||
displayHpB.value = mapHp(props.fight.botBHp, props.fight.winnerId, props.fight.botB?.id)
|
||||
|
||||
scene?.stopMusic()
|
||||
// Flush any queued speech from mid-fight so only final lines play
|
||||
if (typeof speechSynthesis !== 'undefined') speechSynthesis.cancel()
|
||||
|
||||
// Always end with a dramatic death/KO sequence
|
||||
if (scene) {
|
||||
@@ -370,7 +386,13 @@ async function replay() {
|
||||
}
|
||||
}
|
||||
|
||||
// Kill any remaining queued speech — fight is over
|
||||
setTimeout(() => {
|
||||
if (typeof speechSynthesis !== 'undefined') speechSynthesis.cancel()
|
||||
}, 3000)
|
||||
|
||||
isReplaying.value = false
|
||||
emit('replay-done')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user