Files
novafarma/EMERGENCY_SYSTEMS_RECOVERY/VoiceoverSystem.js
2026-01-16 02:43:46 +01:00

354 lines
11 KiB
JavaScript

/**
* 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();
}
}