feat: integrate remaining game sound effects
Added 5 remaining sound effects across all game modes: 1. ai_turbo (0.12 volume) - Plays when AI enters desperate_catchup mode (>10 units behind), matches line 11941 2. lap_celebration (0.6 volume) - Plays when player completes a lap in survival mode circular track, matches line 12801 3. celebration (default volume) - Plays when: - Player wins practice mode (reaches goal first) - Route completes in sprint mode (with train_whistle) Matches lines 14182, 13543 4. gameOver (default volume) - Plays when AI wins in practice mode (AI reaches goal before player), matches line 14193 5. train_whistle (0.25-0.6 volume) - Plays in sprint mode when: - Streak milestone: streak >= 5 and streak % 3 === 0 (0.4 volume) - High momentum: momentum >= 90 (30% chance, 0.25 volume) - Route completion: train reaches 100% (0.6 volume) Matches lines 13222-13235, 13541 All sound integrations preserve exact timing and volume levels from original Python implementation (web_generator.py). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -32,14 +32,16 @@ export function GameDisplay() {
|
||||
// Check for finish line (player reaches race goal) - only for practice mode
|
||||
useEffect(() => {
|
||||
if (state.correctAnswers >= state.raceGoal && state.isGameActive && state.style === 'practice') {
|
||||
// Play celebration sound (line 14182)
|
||||
playSound('celebration')
|
||||
// End the game
|
||||
dispatch({ type: 'END_RACE' })
|
||||
// Show results after a short delay
|
||||
setTimeout(() => {
|
||||
dispatch({ type: 'SHOW_RESULTS' })
|
||||
}, 1000)
|
||||
}, 1500)
|
||||
}
|
||||
}, [state.correctAnswers, state.raceGoal, state.isGameActive, state.style, dispatch])
|
||||
}, [state.correctAnswers, state.raceGoal, state.isGameActive, state.style, dispatch, playSound])
|
||||
|
||||
// For survival mode (endless circuit), track laps but never end
|
||||
// For sprint mode (steam sprint), end after 60 seconds (will implement later)
|
||||
@@ -87,6 +89,21 @@ export function GameDisplay() {
|
||||
// Boost momentum for sprint mode
|
||||
if (state.style === 'sprint') {
|
||||
boostMomentum()
|
||||
|
||||
// Play train whistle for milestones in sprint mode (line 13222-13235)
|
||||
if (newStreak >= 5 && newStreak % 3 === 0) {
|
||||
// Major milestone - play train whistle
|
||||
setTimeout(() => {
|
||||
playSound('train_whistle', 0.4)
|
||||
}, 200)
|
||||
} else if (state.momentum >= 90) {
|
||||
// High momentum celebration - occasional whistle
|
||||
if (Math.random() < 0.3) {
|
||||
setTimeout(() => {
|
||||
playSound('train_whistle', 0.25)
|
||||
}, 150)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show adaptive feedback
|
||||
|
||||
@@ -6,6 +6,7 @@ import { SpeechBubble } from '../AISystem/SpeechBubble'
|
||||
import { useComplementRace } from '../../context/ComplementRaceContext'
|
||||
import { useGameMode } from '@/contexts/GameModeContext'
|
||||
import { useUserProfile } from '@/contexts/UserProfileContext'
|
||||
import { useSoundEffects } from '../../hooks/useSoundEffects'
|
||||
|
||||
interface CircularTrackProps {
|
||||
playerProgress: number
|
||||
@@ -18,6 +19,7 @@ export function CircularTrack({ playerProgress, playerLap, aiRacers, aiLaps }: C
|
||||
const { state, dispatch } = useComplementRace()
|
||||
const { players } = useGameMode()
|
||||
const { profile } = useUserProfile()
|
||||
const { playSound } = useSoundEffects()
|
||||
const [celebrationCooldown, setCelebrationCooldown] = useState<Set<string>>(new Set())
|
||||
|
||||
// Get the first active player's emoji from UserProfileContext (same as nav bar)
|
||||
@@ -160,6 +162,8 @@ export function CircularTrack({ playerProgress, playerLap, aiRacers, aiLaps }: C
|
||||
const playerCurrentLap = Math.floor(playerProgress / 50)
|
||||
if (playerCurrentLap > playerLap && !celebrationCooldown.has('player')) {
|
||||
dispatch({ type: 'COMPLETE_LAP', racerId: 'player' })
|
||||
// Play celebration sound (line 12801)
|
||||
playSound('lap_celebration', 0.6)
|
||||
setCelebrationCooldown(prev => new Set(prev).add('player'))
|
||||
setTimeout(() => {
|
||||
setCelebrationCooldown(prev => {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useComplementRace } from '../context/ComplementRaceContext'
|
||||
import { getAICommentary, type CommentaryContext } from '../components/AISystem/aiCommentary'
|
||||
import { useSoundEffects } from './useSoundEffects'
|
||||
|
||||
export function useAIRacers() {
|
||||
const { state, dispatch } = useComplementRace()
|
||||
const { playSound } = useSoundEffects()
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.isGameActive) return
|
||||
@@ -32,6 +34,26 @@ export function useAIRacers() {
|
||||
|
||||
dispatch({ type: 'UPDATE_AI_POSITIONS', positions: newPositions })
|
||||
|
||||
// Check for AI win in practice mode (line 14151)
|
||||
if (state.style === 'practice' && state.isGameActive) {
|
||||
const winningAI = state.aiRacers.find((racer, index) => {
|
||||
const updatedPosition = newPositions[index]?.position || racer.position
|
||||
return updatedPosition >= state.raceGoal
|
||||
})
|
||||
|
||||
if (winningAI) {
|
||||
// Play game over sound (line 14193)
|
||||
playSound('gameOver')
|
||||
// End the game
|
||||
dispatch({ type: 'END_RACE' })
|
||||
// Show results after a short delay
|
||||
setTimeout(() => {
|
||||
dispatch({ type: 'SHOW_RESULTS' })
|
||||
}, 1500)
|
||||
return // Exit early to prevent further updates
|
||||
}
|
||||
}
|
||||
|
||||
// Check for commentary triggers after position updates
|
||||
state.aiRacers.forEach(racer => {
|
||||
const updatedPosition = newPositions.find(p => p.id === racer.id)?.position || racer.position
|
||||
@@ -73,6 +95,11 @@ export function useAIRacers() {
|
||||
message,
|
||||
context
|
||||
})
|
||||
|
||||
// Play special turbo sound when AI goes desperate (line 11941)
|
||||
if (context === 'desperate_catchup') {
|
||||
playSound('ai_turbo', 0.12)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useComplementRace } from '../context/ComplementRaceContext'
|
||||
import { generatePassengers, findBoardablePassengers, findDeliverablePassengers } from '../lib/passengerGenerator'
|
||||
import { useSoundEffects } from './useSoundEffects'
|
||||
|
||||
/**
|
||||
* Steam Sprint momentum system
|
||||
@@ -38,6 +39,7 @@ const GAME_DURATION = 60000 // 60 seconds in milliseconds
|
||||
|
||||
export function useSteamJourney() {
|
||||
const { state, dispatch } = useComplementRace()
|
||||
const { playSound } = useSoundEffects()
|
||||
const gameStartTimeRef = useRef<number>(0)
|
||||
const lastUpdateRef = useRef<number>(0)
|
||||
|
||||
@@ -136,6 +138,11 @@ export function useSteamJourney() {
|
||||
|
||||
// Check for route completion (train reaches 100%)
|
||||
if (trainPosition >= 100 && !state.showRouteCelebration) {
|
||||
// Play celebration whistle (line 13541-13543)
|
||||
playSound('train_whistle', 0.6)
|
||||
setTimeout(() => {
|
||||
playSound('celebration', 0.4)
|
||||
}, 800)
|
||||
dispatch({ type: 'COMPLETE_ROUTE' })
|
||||
}
|
||||
}, UPDATE_INTERVAL)
|
||||
|
||||
Reference in New Issue
Block a user