/** * VoiceoverSystem.js * ================== * KRVAVA ŽETEV - Ana's Voice & Flashback Audio * * Features: * - Ana's voice recordings for clues * - Flashback narration * - Emotional voiceovers * - Audio positioning (3D sound) * - Volume based on bond strength * * @author NovaFarma Team * @date 2025-12-25 */ class VoiceoverSystem { constructor(scene) { this.scene = scene; // Audio state this.currentVoiceover = null; this.voiceoverQueue = []; this.isPlaying = false; // Audio library (paths to audio files) this.anaVoiceovers = {}; this.flashbackVoiceovers = {}; console.log('🎙️ VoiceoverSystem initialized'); this.registerVoiceovers(); } /** * Register all voiceover audio files */ registerVoiceovers() { // ANA'S CLUE VOICEOVERS (15 messages) this.anaVoiceovers = { msg_01: { file: 'assets/audio/voiceover/ana_msg_01.mp3', text: "Kai, if you find this... I'm sorry. They took me. Stay safe.", duration: 5000, emotion: 'scared' }, msg_02: { file: 'assets/audio/voiceover/ana_msg_02.mp3', text: "I'm marking my trail with red flowers. Follow them west.", duration: 4000, emotion: 'determined' }, msg_03: { file: 'assets/audio/voiceover/ana_msg_03.mp3', text: "These zombies... they're intelligent. I saw it in their eyes.", duration: 5000, emotion: 'curious' }, msg_06: { file: 'assets/audio/voiceover/ana_msg_06.mp3', text: "Kai... I can feel you searching for me. Our bond is so strong.", duration: 6000, emotion: 'hopeful' }, msg_09: { file: 'assets/audio/voiceover/ana_msg_09.mp3', text: "I found research on a cure! There's still hope for everyone!", duration: 5000, emotion: 'excited' }, msg_10: { file: 'assets/audio/voiceover/ana_msg_10.mp3', text: "I dream of having a family someday. Children playing in safe fields...", duration: 6000, emotion: 'wistful' }, msg_14: { file: 'assets/audio/voiceover/ana_msg_14.mp3', text: "Don't come for me, Kai. It's too dangerous. Please, save yourself!", duration: 5000, emotion: 'desperate' }, msg_15: { file: 'assets/audio/voiceover/ana_msg_15.mp3', text: "I know you won't listen... you never do. I love you, brother.", duration: 6000, emotion: 'loving' }, // SPECIAL: Ana's Journal Entry ana_journal_01: { file: 'assets/audio/voiceover/ana_journal_01.mp3', text: "Day 147 in captivity. Dr. Krnić is forcing me to work on something terrible. A super virus using Chernobyl radiation. If it releases... everyone dies. I have to find a way to stop him.", duration: 15000, emotion: 'terrified' }, // SPECIAL: Final Video Message ana_final_message: { file: 'assets/audio/voiceover/ana_final_message.mp3', text: "Kai... I'm in Dr. Krnić's facility. Level 3, Vault 7. If you're watching this... don't come. It's a trap. But I know you will anyway. *laughs sadly* Be ready for the Troll King. I love you. Twin bond forever.", duration: 20000, emotion: 'resigned' } }; // FLASHBACK NARRATION this.flashbackVoiceovers = { twin_bond_discovery: { file: 'assets/audio/voiceover/flashback_twin_bond.mp3', text: "Dr. Chen: 'These twins have something special. A connection I've never seen before. They'll always find each other, no matter what.'", duration: 10000, emotion: 'wonder' }, mother_death: { file: 'assets/audio/voiceover/flashback_mother.mp3', text: "Mama Elena: 'Kai... protect Ana. Always. Twin bond is your strength. Together... you can survive anything. I love you both...'", duration: 15000, emotion: 'heartbreaking' }, kidnapping: { file: 'assets/audio/voiceover/flashback_kidnapping.mp3', text: "Ana's scream: 'KAIII! HELP! Please! KAIIIII!' *Kai's voice, desperate* 'ANA! NO! ANA!'", duration: 8000, emotion: 'traumatic' } }; console.log(`✅ Registered ${Object.keys(this.anaVoiceovers).length} Ana voiceovers`); console.log(`✅ Registered ${Object.keys(this.flashbackVoiceovers).length} flashback voiceovers`); } /** * Play Ana's voiceover for a clue */ playAnaVoiceover(clueId, onComplete = null) { const voiceover = this.anaVoiceovers[clueId]; if (!voiceover) { console.warn(`⚠️ No voiceover for clue: ${clueId}`); return false; } console.log(`🎙️ Playing Ana's voice: ${clueId}`); // Queue if already playing if (this.isPlaying) { this.voiceoverQueue.push({ type: 'ana', id: clueId, onComplete }); console.log(`⏳ Voiceover queued (${this.voiceoverQueue.length} in queue)`); return true; } this.isPlaying = true; // Show subtitle text this.showSubtitle(voiceover.text, voiceover.duration); // Play audio (if file exists) if (this.scene.sound.get(voiceover.file)) { this.currentVoiceover = this.scene.sound.play(voiceover.file, { volume: this.getVolumeBasedOnBondStrength() }); // Cleanup when done this.currentVoiceover.once('complete', () => { this.onVoiceoverComplete(onComplete); }); } else { // Fallback if audio file doesn't exist console.warn(`⚠️ Audio file not found: ${voiceover.file}`); // Simulate duration setTimeout(() => { this.onVoiceoverComplete(onComplete); }, voiceover.duration); } // Trigger Twin Bond moment if (this.scene.twinBondUI) { this.scene.twinBondUI.triggerBondMoment('medium'); } return true; } /** * Play flashback narration */ playFlashbackVoiceover(flashbackId, onComplete = null) { const voiceover = this.flashbackVoiceovers[flashbackId]; if (!voiceover) { console.warn(`⚠️ No voiceover for flashback: ${flashbackId}`); return false; } console.log(`🎙️ Playing flashback: ${flashbackId}`); this.isPlaying = true; // Show subtitle this.showSubtitle(voiceover.text, voiceover.duration, '#FFD700'); // Gold color for flashbacks // Play audio if (this.scene.sound.get(voiceover.file)) { this.currentVoiceover = this.scene.sound.play(voiceover.file, { volume: 0.8 }); this.currentVoiceover.once('complete', () => { this.onVoiceoverComplete(onComplete); }); } else { console.warn(`⚠️ Audio file not found: ${voiceover.file}`); setTimeout(() => { this.onVoiceoverComplete(onComplete); }, voiceover.duration); } return true; } /** * Show subtitle text on screen */ showSubtitle(text, duration, color = '#9370DB') { const ui = this.scene.scene.get('UIScene') || this.scene; // Create subtitle container const subtitleBg = ui.add.rectangle( ui.cameras.main.width / 2, ui.cameras.main.height - 100, ui.cameras.main.width - 200, 80, 0x000000, 0.7 ); subtitleBg.setScrollFactor(0); subtitleBg.setDepth(2000); const subtitleText = ui.add.text( ui.cameras.main.width / 2, ui.cameras.main.height - 100, text, { fontSize: '20px', fontFamily: 'Arial', color: color, align: 'center', wordWrap: { width: ui.cameras.main.width - 220 } } ); subtitleText.setOrigin(0.5); subtitleText.setScrollFactor(0); subtitleText.setDepth(2001); // Fade in subtitleBg.setAlpha(0); subtitleText.setAlpha(0); ui.tweens.add({ targets: [subtitleBg, subtitleText], alpha: 1, duration: 300 }); // Fade out and destroy setTimeout(() => { ui.tweens.add({ targets: [subtitleBg, subtitleText], alpha: 0, duration: 500, onComplete: () => { subtitleBg.destroy(); subtitleText.destroy(); } }); }, duration - 500); } /** * Get volume based on twin bond strength */ getVolumeBasedOnBondStrength() { if (!this.scene.twinBondUI) { return 0.7; // Default volume } const bondStrength = this.scene.twinBondUI.getBondStrength(); // Higher bond = louder voice const volume = 0.4 + (bondStrength / 100) * 0.6; // 0.4 to 1.0 return volume; } /** * Voiceover completion handler */ onVoiceoverComplete(callback) { this.isPlaying = false; this.currentVoiceover = null; // Call completion callback if (callback) { callback(); } // Play next in queue if (this.voiceoverQueue.length > 0) { const next = this.voiceoverQueue.shift(); setTimeout(() => { if (next.type === 'ana') { this.playAnaVoiceover(next.id, next.onComplete); } else if (next.type === 'flashback') { this.playFlashbackVoiceover(next.id, next.onComplete); } }, 500); // Small delay between voiceovers } } /** * Stop current voiceover */ stopVoiceover() { if (this.currentVoiceover) { this.currentVoiceover.stop(); this.currentVoiceover = null; } this.isPlaying = false; } /** * Clear queue */ clearQueue() { this.voiceoverQueue = []; } /** * Check if voiceover is playing */ isVoiceoverPlaying() { return this.isPlaying; } /** * Cleanup */ destroy() { this.stopVoiceover(); this.clearQueue(); } }