/** * PrologueScene.js * ================ * KRVAVA ŽETEV - Prologue Cutscene * * Story: * Player (Kai) and twin sister (Ana) were scientists studying zombie virus * During attack, both got infected with "Alfa" strain (hybrid virus) * Ana was kidnapped by mysterious forces * Kai wakes up alone, searching for his sister * * Features: * - Cinematic dialogue system * - Character portraits * - Background transitions * - Skip function (ESC) * - Auto-advance option * * @author NovaFarma Team * @date 2025-12-23 */ class PrologueScene extends Phaser.Scene { constructor() { super({ key: 'PrologueScene' }); this.currentDialogueIndex = 0; this.dialogueData = []; this.canAdvance = true; this.autoAdvance = false; this.autoAdvanceDelay = 3000; // 3 seconds } preload() { // Try to load JSON, but we'll use fallback if it doesn't exist this.load.on('loaderror', (file) => { console.warn(`⚠️ Failed to load: ${file.key}`); }); this.load.json('prologue_data', 'assets/dialogue/prologue.json'); // Load character portraits this.load.image('kai_portrait', 'reference_images/kai_master_style33.png'); this.load.image('ana_portrait', 'reference_images/ana_master_style33.png'); // Load prologue voiceover audio files (Slovenian // Voice-over files (only 1-12 exist!) for (let num = 1; num <= 12; num++) { const paddedNum = num.toString().padStart(2, '0'); this.load.audio(`prologue_${paddedNum}`, `assets/audio 🔴/voiceover/prologue_sl/prologue_${paddedNum}.wav`); } // NPC Portraits - prevent green squares! this.load.image('kai_dialogue_portrait', 'assets/sprites/npcs/kai/kai_style32_final_1767549236935.png'); this.load.image('ana_dialogue_portrait', 'assets/sprites/npcs/ana/ana_style32_final_1767549209604.png'); this.load.image('gronk_dialogue_portrait', 'assets/sprites/npcs/gronk/gronk_reference_pierced_v2_1767454251649.png'); console.log('🎨 Loading NPC dialogue portraits...'); console.log('🎤 Loading 12 Slovenian prologue voiceover files...'); console.log('🖼️ Loading character portraits...'); } create() { const width = this.cameras.main.width; const height = this.cameras.main.height; console.log('🎬 Starting Prologue...'); // Track current audio to stop it when advancing this.currentVoice = null; // Black background this.add.rectangle(0, 0, width, height, 0x000000).setOrigin(0); // Initialize dialogue data from JSON OR fallback this.dialogueData = this.cache.json.get('prologue_data'); if (!this.dialogueData) { console.warn('⚠️ Using fallback hardcoded dialogue!'); // CORRECT STORY: 14-year-old twins, family sacrifice, Troll King kidnapping this.dialogueData = [ { id: 'prologue_01', speaker: "NARRATOR", text: "Leto 2084. Zombi virus je uničil svet.", background: "ruins", bgColor: 0x1a1a2e, portrait: null }, { id: 'prologue_02', speaker: "KAI", text: "Moje ime je Kai Marković. Star sem štirinajst let.", background: "lab", bgColor: 0x2d1b00, portrait: "kai_neutral" }, { id: 'prologue_03', speaker: "KAI", text: "Moja dvojčica Ana in jaz, sva zelo povezana. Nezlomljiv vez.", background: "lab", bgColor: 0x2d4a1e, portrait: "kai_neutral" }, { id: 'prologue_04', speaker: "KAI", text: "Naša starša sta bila znanstvenika. Raziskovala sta virusne mutacije.", background: "lab", bgColor: 0x1a1a2e, portrait: "kai_neutral" }, { id: 'prologue_05', speaker: "NARRATOR", text: "Tretji dan izbruha. Horda zombijev napada družinsko hišo.", background: "lab_chaos", bgColor: 0x660000, portrait: null, shake: true, flash: true }, { id: 'prologue_06', speaker: "NARRATOR", text: "Starša se žrtvujeta, da rešita dvojčka. Zadnji besede: 'Beži, Kai! Zaščiti Ano!'", background: "lab_chaos", bgColor: 0x3d0000, portrait: null, shake: true }, { id: 'prologue_07', speaker: "NARRATOR", text: "Iz senc se pojavi Orjaški Troll Kralj. Poslal ga je zlobni doktor Krnić.", background: "lab_alarm", bgColor: 0x2d0000, portrait: null, shake: true }, { id: 'prologue_08', speaker: "ANA", text: "KAI! REŠI ME! KAIII!", background: "lab_chaos", bgColor: 0x3d0000, portrait: "ana_pain", shake: true }, { id: 'prologue_09', speaker: "KAI", text: "ANA! NE! VRNITE MI JO!", background: "lab_chaos", bgColor: 0x3d0000, portrait: "kai_shocked", shake: true }, { id: 'prologue_10', speaker: "NARRATOR", text: "Kai se spremeni v Alfa Hibrida. Vijolične oči. Moč nadzora nad zombiji.", background: "ruins", bgColor: 0x4a1a4a, portrait: null, flash: true }, { id: 'prologue_11', speaker: "NARRATOR", text: "Šest mesecev kasneje. Kai se zbudi na majhni kmetiji. Ana je izginila.", background: "farm", bgColor: 0x2d4a1e, portrait: null }, { id: 'prologue_12', speaker: "KAI", text: "Moram jo najti. Ne glede na to, kaj bo potrebno. Ana, prihajam!", background: "farm", bgColor: 0x2d4a1e, portrait: "kai_determined" } ]; } // Create UI elements this.createDialogueUI(width, height); // Skip instructions const skipText = this.add.text(width - 20, 20, 'Pritisni ESC za preskok', { fontSize: '16px', fontFamily: 'Georgia, serif', color: '#888888' }); skipText.setOrigin(1, 0); // Auto-advance toggle const autoText = this.add.text(width - 20, 50, 'Pritisni PRESLEDNICA za samodejno nadaljevanje', { fontSize: '14px', fontFamily: 'Georgia, serif', color: '#666666' }); autoText.setOrigin(1, 0); // Input handlers this.input.keyboard.on('keydown-ESC', () => { this.skipPrologue(); }); this.input.keyboard.on('keydown-SPACE', () => { this.autoAdvance = !this.autoAdvance; autoText.setColor(this.autoAdvance ? '#00FF00' : '#666666'); }); this.input.keyboard.on('keydown-ENTER', () => { this.advanceDialogue(); }); this.input.on('pointerdown', () => { this.advanceDialogue(); }); // Start first dialogue this.showDialogue(0); } createDialogueUI(width, height) { const dialogueBoxHeight = 180; const dialogueY = height - dialogueBoxHeight; // Dialogue box background this.dialogueBg = this.add.rectangle( width / 2, dialogueY + dialogueBoxHeight / 2, width - 40, dialogueBoxHeight - 20, 0x2d1b00, 0.95 ); this.dialogueBg.setStrokeStyle(3, 0xd4a574); // Speaker name this.speakerText = this.add.text(50, dialogueY + 20, '', { fontSize: '22px', fontFamily: 'Georgia, serif', color: '#FFD700', fontStyle: 'bold', stroke: '#000000', strokeThickness: 3 }); // Dialogue text this.dialogueText = this.add.text(50, dialogueY + 55, '', { fontSize: '18px', fontFamily: 'Georgia, serif', color: '#f4e4c1', wordWrap: { width: width - 120 }, lineSpacing: 8 }); // Continue indicator this.continueIndicator = this.add.text(width - 60, height - 40, '▼', { fontSize: '24px', color: '#FFD700' }); this.continueIndicator.setOrigin(0.5); // Pulse animation this.tweens.add({ targets: this.continueIndicator, alpha: 0.3, duration: 800, yoyo: true, repeat: -1 }); // Portrait this.portraitBg = this.add.rectangle(width - 150, dialogueY + 90, 120, 120, 0x4a3520, 0.9); this.portraitBg.setStrokeStyle(2, 0xd4a574); // Portrait image sprite (instead of emoji text) this.portraitImage = this.add.sprite(width - 150, dialogueY + 90, 'kai_portrait'); this.portraitImage.setOrigin(0.5); this.portraitImage.setDisplaySize(100, 100); // Fit in portrait box this.portraitImage.setVisible(false); // Background sprite (will be created per dialogue) this.backgroundSprite = null; } showDialogue(index) { if (index >= this.dialogueData.length) { this.completePrologue(); return; } const dialogue = this.dialogueData[index]; this.currentDialogueIndex = index; // Stop previous audio if playing if (this.currentVoice) { this.currentVoice.stop(); this.currentVoice = null; } // Play new audio if (dialogue.id) { try { // Check if audio exists in cache (it might not if load failed/missing) if (this.cache.audio.exists(dialogue.id)) { this.currentVoice = this.sound.add(dialogue.id); this.currentVoice.play({ volume: 1.0 }); } else { console.warn(`🔊 Audio missing for ${dialogue.id}`); } } catch (err) { console.error('Audio play error:', err); } } // Update background this.updateBackground(dialogue.background, dialogue.bgColor); // Apply effects if (dialogue.shake) { this.cameras.main.shake(500, 0.01); } if (dialogue.flash) { this.cameras.main.flash(1000, 255, 255, 255); } // Update speaker this.speakerText.setText(dialogue.speaker); // Typewriter effect for text this.typewriterEffect(dialogue.text); // Update portrait this.updatePortrait(dialogue.portrait); // Auto-advance if enabled if (this.autoAdvance && index < this.dialogueData.length - 1) { // Wait for audio to finish OR standard delay? // Ideally wait for audio duration, but fallback to delay let delay = this.autoAdvanceDelay; if (this.currentVoice && this.currentVoice.duration) { // Add a small buffer after speech ends delay = (this.currentVoice.duration * 1000) + 1000; } this.time.delayedCall(delay, () => { // Check if user hasn't already advanced manually if (this.currentDialogueIndex === index) { this.advanceDialogue(); } }); } } typewriterEffect(text) { let displayText = ''; console.log('Typewriter started for:', text); // Debug let charIndex = 0; this.dialogueText.setText(''); // Safety check for empty text if (!text) { this.canAdvance = true; return; } const timer = this.time.addEvent({ delay: 30, //CharactersperSeconds callback: () => { if (charIndex < text.length) { displayText += text[charIndex]; this.dialogueText.setText(displayText); charIndex++; } else { timer.remove(); this.canAdvance = true; } }, loop: true }); this.canAdvance = false; } updateBackground(bgKey, bgColor) { // Simple colored background for now // TODO: Replace with actual background images const width = this.cameras.main.width; const height = this.cameras.main.height; if (this.backgroundSprite) { this.backgroundSprite.destroy(); } this.backgroundSprite = this.add.rectangle(0, 0, width, height, bgColor); this.backgroundSprite.setOrigin(0); this.backgroundSprite.setDepth(-1); // Add atmosphere text let atmosphereText = ''; switch (bgKey) { case 'lab': atmosphereText = '🔬 Nova Lab - Research Wing'; break; case 'lab_alarm': atmosphereText = '⚠️ BREACH ALARM ⚠️'; break; case 'lab_chaos': atmosphereText = '💥 CHAOS'; break; case 'ruins': atmosphereText = '🏚️ Laboratory Ruins'; break; case 'zombies': atmosphereText = '🧟 First Encounter'; break; case 'farm': atmosphereText = '🌾 Abandoned Farm - New Beginning'; break; } if (atmosphereText) { const atmoText = this.add.text(width / 2, 40, atmosphereText, { fontSize: '20px', fontFamily: 'Georgia, serif', color: '#888888', fontStyle: 'italic' }); atmoText.setOrigin(0.5); atmoText.setAlpha(0.6); atmoText.setDepth(10); } } updatePortrait(portraitKey) { if (!portraitKey) { this.portraitBg.setVisible(false); this.portraitImage.setVisible(false); return; } this.portraitBg.setVisible(true); this.portraitImage.setVisible(true); // Map portrait keys to actual character images // For now, all Kai variants use kai_portrait, all Ana use ana_portrait if (portraitKey.startsWith('kai_')) { this.portraitImage.setTexture('kai_portrait'); } else if (portraitKey.startsWith('ana_')) { this.portraitImage.setTexture('ana_portrait'); } else { // Default fallback this.portraitImage.setVisible(false); } } advanceDialogue() { if (!this.canAdvance) { // Skip typewriter effect const dialogue = this.dialogueData[this.currentDialogueIndex]; this.dialogueText.setText(dialogue.text); this.canAdvance = true; return; } // Stop current audio before advancing if (this.currentVoice) { this.currentVoice.stop(); } this.showDialogue(this.currentDialogueIndex + 1); } skipPrologue() { console.log('⏭️ Skipping prologue...'); if (this.currentVoice) { this.currentVoice.stop(); } this.scene.start('GameScene'); } completePrologue() { console.log('✅ Prologue complete!'); if (this.currentVoice) { this.currentVoice.stop(); } // Fade out this.cameras.main.fadeOut(2000, 0, 0, 0); this.time.delayedCall(2000, () => { this.scene.start('GameScene'); }); } }