/** * HIPOAudioSystem.js * * COMPLETE AUDIO MANAGER - Demo Ready! * * Features: * - [AI_VOICE] detection in dialogue * - Typewriter effect with character blips * - Farm animal proximity sounds * - Combat SFX integration * - Noir City ambient (HIPODEVIL666CITY) * - Xbox haptic feedback * * Created: Jan 10, 2026 * Author: David "HIPO" Kotnik * Studio: Hipodevil666 Studiosβ„’ */ export default class HIPOAudioSystem { constructor(scene) { this.scene = scene; // Audio references this.aiVoices = {}; this.sfx = {}; this.ambient = {}; this.blips = {}; // Dialogue state this.currentDialogue = null; this.typewriterActive = false; // Farm animal timers this.animalTimers = {}; // Haptic enabled this.hapticEnabled = true; console.log('πŸŽ™οΈ HIPO Audio System initialized!'); } /** * Preload all audio assets */ preloadAssets() { const scene = this.scene; console.log('🎡 Loading HIPO Audio System assets...'); // === AI VOICES (Edge-TTS Generated) === console.log(' πŸ“‚ AI Voices...'); // Gronk (Deep, Gritty Male) for (let i = 1; i <= 8; i++) { scene.load.audio(`gronk_voice_${i}`, `assets/audio/voice/gronk/gronk_phrase_${String(i).padStart(2, '0')}.ogg`); } // Kai (Energetic, Bold - FEMALE!) for (let i = 1; i <= 8; i++) { scene.load.audio(`kai_voice_${i}`, `assets/audio/voice/kai/kai_phrase_${String(i).padStart(2, '0')}.ogg`); } // Ana (Mysterious, Calm Female) for (let i = 1; i <= 8; i++) { scene.load.audio(`ana_voice_${i}`, `assets/audio/voice/ana/ana_phrase_${String(i).padStart(2, '0')}.ogg`); } // === FARMING & NATURE SFX === console.log(' πŸ„ Farming & Nature...'); scene.load.audio('sfx_sheep', 'assets/audio/sfx/farming/sheep.ogg'); scene.load.audio('sfx_pig', 'assets/audio/sfx/farming/pig.ogg'); scene.load.audio('sfx_chicken', 'assets/audio/sfx/farming/chicken.ogg'); scene.load.audio('sfx_horse', 'assets/audio/sfx/farming/horse.ogg'); scene.load.audio('sfx_goat', 'assets/audio/sfx/farming/goat.ogg'); scene.load.audio('sfx_cow', 'assets/audio/sfx/farming/cow.ogg'); // === COMBAT SFX === console.log(' βš”οΈ Combat...'); scene.load.audio('sfx_zombie_hit', 'assets/audio/sfx/combat/zombie_hit.ogg'); scene.load.audio('sfx_zombie_death', 'assets/audio/sfx/combat/zombie_death.ogg'); scene.load.audio('sfx_player_hurt', 'assets/audio/sfx/combat/player_hurt.ogg'); // === AMBIENT === console.log(' πŸŒƒ Ambient...'); scene.load.audio('ambient_noir_city', 'assets/audio/ambient/noir_city_echo.ogg'); scene.load.audio('ambient_farm_wind', 'assets/audio/ambient/wind_loop.ogg'); scene.load.audio('ambient_crickets', 'assets/audio/ambient/crickets_loop.ogg'); // === TYPEWRITER BLIPS (Character-specific) === console.log(' ⌨️ Typewriter blips...'); scene.load.audio('blip_gronk', 'assets/audio/ui/typewriter_low.ogg'); scene.load.audio('blip_kai', 'assets/audio/ui/typewriter_high.ogg'); scene.load.audio('blip_ana', 'assets/audio/ui/typewriter_mid.ogg'); scene.load.audio('blip_npc', 'assets/audio/ui/typewriter_normal.ogg'); console.log('βœ… HIPO Audio System assets queued!'); } /** * Initialize audio after preload */ initialize() { const scene = this.scene; console.log('🎡 Initializing HIPO Audio System...'); // === AI VOICES === this.aiVoices = { gronk: [], kai: [], ana: [] }; for (let i = 1; i <= 8; i++) { this.aiVoices.gronk.push(scene.sound.add(`gronk_voice_${i}`, { volume: 0.8 })); this.aiVoices.kai.push(scene.sound.add(`kai_voice_${i}`, { volume: 0.75 })); this.aiVoices.ana.push(scene.sound.add(`ana_voice_${i}`, { volume: 0.7 })); } // === FARMING & NATURE === this.sfx.farming = { sheep: scene.sound.add('sfx_sheep', { volume: 0.4 }), pig: scene.sound.add('sfx_pig', { volume: 0.4 }), chicken: scene.sound.add('sfx_chicken', { volume: 0.35 }), horse: scene.sound.add('sfx_horse', { volume: 0.5 }), goat: scene.sound.add('sfx_goat', { volume: 0.4 }), cow: scene.sound.add('sfx_cow', { volume: 0.45 }) }; // === COMBAT === this.sfx.combat = { zombieHit: scene.sound.add('sfx_zombie_hit', { volume: 0.6 }), zombieDeath: scene.sound.add('sfx_zombie_death', { volume: 0.7 }), playerHurt: scene.sound.add('sfx_player_hurt', { volume: 0.8 }) }; // === AMBIENT === this.ambient = { noirCity: scene.sound.add('ambient_noir_city', { loop: true, volume: 0.2 // Noir city with echo }), farmWind: scene.sound.add('ambient_farm_wind', { loop: true, volume: 0.2 }), crickets: scene.sound.add('ambient_crickets', { loop: true, volume: 0.25 }) }; // === TYPEWRITER BLIPS === this.blips = { gronk: scene.sound.add('blip_gronk', { volume: 0.15 }), kai: scene.sound.add('blip_kai', { volume: 0.13 }), ana: scene.sound.add('blip_ana', { volume: 0.12 }), npc: scene.sound.add('blip_npc', { volume: 0.1 }) }; console.log('βœ… HIPO Audio System ready!'); } /** * Play dialogue with smart AI/Typewriter detection * * Format: "[AI_VOICE:character:phraseNumber] Text here" * Example: "[AI_VOICE:gronk:2] Pink is best color!" * * OR standard: "Just normal text" (uses typewriter) */ playDialogue(text, character = 'npc', onComplete = null) { // Check for [AI_VOICE] tag const aiVoiceMatch = text.match(/\[AI_VOICE:(\w+):(\d+)\]\s*(.+)/); if (aiVoiceMatch) { // AI VOICE MODE const voiceChar = aiVoiceMatch[1]; // gronk/kai/ana const phraseNum = parseInt(aiVoiceMatch[2]); const displayText = aiVoiceMatch[3]; console.log(`πŸŽ™οΈ AI VOICE: ${voiceChar} phrase ${phraseNum}`); console.log(` Text: "${displayText}"`); // Play AI voice this.playAIVoice(voiceChar, phraseNum, onComplete); // Show text instantly (no typewriter for AI voice) this.showDialogueText(displayText, character, true); // Light haptic for voice this.vibrateLight(); } else { // TYPEWRITER MODE console.log(`⌨️ TYPEWRITER: ${character}`); console.log(` Text: "${text}"`); // Show text with typewriter effect this.showDialogueText(text, character, false, onComplete); } } /** * Play AI voice */ playAIVoice(character, phraseNumber, onComplete = null) { const voices = this.aiVoices[character]; if (!voices || !voices[phraseNumber - 1]) { console.warn(`⚠️ AI Voice not found: ${character} phrase ${phraseNumber}`); return; } const voice = voices[phraseNumber - 1]; voice.play(); if (onComplete) { voice.once('complete', onComplete); } } /** * Show dialogue text (with or without typewriter) */ showDialogueText(text, character, instant = false, onComplete = null) { // This would integrate with your DialogueSystem // For now, just log console.log(` Display: "${text}" (instant: ${instant})`); if (!instant) { // Typewriter effect with character-specific blip this.typewriterEffect(text, character, onComplete); } else { // Instant display if (onComplete) onComplete(); } } /** * Typewriter effect with blips */ typewriterEffect(text, character, onComplete) { this.typewriterActive = true; const blip = this.blips[character] || this.blips.npc; const speed = 50; // ms per character let index = 0; const typeInterval = setInterval(() => { if (index < text.length) { const char = text[index]; // Play blip (skip spaces) if (char !== ' ' && !blip.isPlaying) { blip.play(); } index++; } else { clearInterval(typeInterval); this.typewriterActive = false; if (onComplete) onComplete(); } }, speed); } /** * Start farm animal sounds (proximity-based random) */ startFarmAnimalSounds() { const animals = ['sheep', 'pig', 'chicken', 'horse', 'goat', 'cow']; animals.forEach(animal => { this.animalTimers[animal] = this.scene.time.addEvent({ delay: Phaser.Math.Between(5000, 15000), callback: () => { this.playFarmAnimal(animal); }, loop: true }); }); console.log('πŸ„ Farm animal sounds started!'); } /** * Play farm animal sound (checks proximity to Kai) */ playFarmAnimal(animalType) { const sound = this.sfx.farming[animalType]; if (!sound || sound.isPlaying) return; // Check proximity to player (if player exists) const player = this.scene.player || this.scene.kai; if (player) { // Simplified: just play (proximity would check farm location) // In full game, check distance to farm area const nearFarm = true; // TODO: Implement farm proximity if (nearFarm) { sound.play(); console.log(`πŸ„ ${animalType} sound!`); } } } /** * Stop farm animal sounds */ stopFarmAnimalSounds() { Object.values(this.animalTimers).forEach(timer => { if (timer) timer.remove(); }); this.animalTimers = {}; console.log('πŸ”‡ Farm animals stopped'); } /** * Play combat sound with haptic */ playCombat(type) { const soundMap = { 'hit': 'zombieHit', 'death': 'zombieDeath', 'hurt': 'playerHurt' }; const soundKey = soundMap[type]; const sound = this.sfx.combat[soundKey]; if (!sound) { console.warn(`⚠️ Combat sound not found: ${type}`); return; } sound.play(); // Haptic feedback if (type === 'hurt') { this.vibrateStrong(400); // Strong for player damage } else { this.vibrateStrong(200); // Normal for zombie } console.log(`βš”οΈ Combat: ${type}`); } /** * Play ambient for location */ playAmbient(location) { // Stop all ambient Object.values(this.ambient).forEach(amb => amb.stop()); let ambient = null; switch (location) { case 'city': case 'HIPODEVIL666CITY': ambient = this.ambient.noirCity; console.log('πŸŒƒ Noir City ambient (with echo)'); break; case 'farm': case 'grassland': ambient = this.ambient.farmWind; console.log('🌾 Farm ambient'); break; case 'night': ambient = this.ambient.crickets; console.log('πŸŒ™ Night ambient'); break; } if (ambient) { ambient.play(); } } /** * Stop all ambient */ stopAmbient() { Object.values(this.ambient).forEach(amb => amb.stop()); } /** * Light haptic feedback */ vibrateLight() { this.vibrate(100, 0.3, 0.5); } /** * Strong haptic feedback */ vibrateStrong(duration = 300) { this.vibrate(duration, 0.7, 1.0); } /** * Xbox controller vibration */ vibrate(duration, weak, strong) { if (!this.hapticEnabled) return; const scene = this.scene; if (scene.input.gamepad && scene.input.gamepad.total > 0) { const pad = scene.input.gamepad.getPad(0); if (pad && pad.vibration) { pad.vibration.playEffect('dual-rumble', { startDelay: 0, duration: duration, weakMagnitude: weak, strongMagnitude: strong }); } } } /** * Cleanup */ destroy() { this.stopAmbient(); this.stopFarmAnimalSounds(); console.log('πŸ”‡ HIPO Audio System destroyed'); } }