/** * SMETAR DIALOGUE SYSTEM * Interakcija z Zombijem Skavtom */ export const SmetarDialogues = { // Glavni intro dialog ko Kai pride s Zombijem intro_with_zombie: { trigger: 'player_near_smetar_with_zombie_scout', participants: ['smetar', 'zombie_scout'], lines: [ { speaker: 'smetar', text: "Ej, Kai! Kaj mi spet furaš tega svojega s klobukom? Glej ga, nahrbtnik ima čisto svinjski od tistih vaših ruševin. Šutni ga sem, da mi malo pomaga s temi grafiti na obzidju!", emotion: 'gruff', animation: 'portrait_bounce', soundLayers: ['broom_scraping', 'wind_ambient'], typewriterSpeed: 40 // ms per character }, { speaker: 'zombie_scout', text: "*Tiho godrnjanje, rožljanje nahrbtnika* M-m-možgaaaaani... pa metla?", emotion: 'confused_hungry', animation: 'portrait_bounce_slow', soundLayers: ['zombie_grumble', 'backpack_rattle', 'wind_ambient'], typewriterSpeed: 50 }, { speaker: 'smetar', text: "Ja, metla! Če boš dobro pometal, ti mogoče dam kakšen star košček spomina, ki sem ga izbrskal iz smeti za tvojega šefa.", emotion: 'encouraging', animation: 'portrait_bounce', soundLayers: ['broom_scraping', 'wind_ambient'], typewriterSpeed: 40 } ], outcomes: { accept: 'quest_sanitation_zombie_help', decline: 'smetar_disappointed' } }, // Quest assignment quest_assignment: { speaker: 'smetar', text: "Pofafaj tole metlo pa mi pomagaj, al pa mi šutni tiste tri zombije, da naredimo mal reda v tej luknji!", emotion: 'determined', animation: 'portrait_bounce', soundLayers: ['broom_tap', 'wind_ambient'], typewriterSpeed: 40 }, // Ko Kai posodi zombije zombie_loan_accepted: { speaker: 'smetar', text: "Čist fajn! Dej, ti trije, pole sem! *postavi zombije v vrsto* Vi boste čistili ta grafit, vi tisto smejo, pa vi tam pa tisti razbit zabojnik!", emotion: 'commanding', animation: 'portrait_bounce_strong', soundLayers: ['zombie_lineup', 'broom_distribution'], typewriterSpeed: 45, vfx: 'zombie_worker_spawn' }, // Ko je delo končano work_complete: { speaker: 'smetar', text: "Heh! Dobro delo, fantje! Kai, tvojim zombijem bi lahko dal za jest, pa tukaj je tisto za tebe...", emotion: 'satisfied', animation: 'portrait_bounce', soundLayers: ['success_jingle', 'wind_ambient'], typewriterSpeed: 40, reward: { type: 'rare_gift', item: 'family_memory_fragment', vfx: 'amnesia_blur_trigger' } }, // Ko najde redko darilo v smeteh rare_gift_found: { speaker: 'smetar', text: "Ej Kai, poglej kaj sem najdu v teh ruševinah - stara družinska slika. Tvoja je, ne? Zgleda da... Kai? Kai! Hej, kam greš?!", emotion: 'surprised', animation: 'portrait_bounce', soundLayers: ['paper_rustle', 'wind_ambient'], typewriterSpeed: 35, trigger_after: 'amnesia_blur_flashback' }, // Ko mesto ni čisto city_dirty_nag: { speaker: 'smetar', text: "Kai, no! Spet je mesto polno smeti! Kje so tvoji zombi? Rabim pomoč!", emotion: 'frustrated', animation: 'portrait_shake', soundLayers: ['broom_angry_tap'], typewriterSpeed: 40 }, // Ko je mesto 100% čisto city_perfect: { speaker: 'smetar', text: "KONČNO! Prvo mesto v tem post-apokaliptičnem sranju, ki je res čisto! Bravo, Kai. Evo, to si res zaslužil.", emotion: 'proud', animation: 'portrait_bounce_celebration', soundLayers: ['fanfare', 'wind_ambient'], typewriterSpeed: 40, reward: { type: 'legendary_gift', item: 'kai_childhood_photo', vfx: 'amnesia_blur_major' } }, // Casual banter casual_greeting: [ { text: "Ej, pometal sem že 200 ruševin. Tvoj zombi? Kaj, 5?", emotion: 'teasing', animation: 'portrait_bounce' }, { text: "Vejpam že od preden so zombiji začeli hodit. Ta dim jih odbija, verjameš?", emotion: 'casual', animation: 'portrait_bounce' }, { text: "Ko sem bil mlajši, sem bil bolj punk od tebe. Dreadi do tal!", emotion: 'nostalgic', animation: 'portrait_bounce' } ] }; /** * DIALOGUE ANIMATION CONTROLLER */ export class SmetarDialogueController { constructor(scene) { this.scene = scene; this.cinematicVoice = scene.cinematicVoice; // CinematicVoiceSystem this.currentDialogue = null; } /** * Play dialogue sequence with animations */ async playDialogue(dialogueKey) { const dialogue = SmetarDialogues[dialogueKey]; if (!dialogue) return; this.currentDialogue = dialogue; // Single line dialogue if (dialogue.speaker) { await this.playLine(dialogue); } // Multi-line sequence else if (dialogue.lines) { for (const line of dialogue.lines) { await this.playLine(line); await this.wait(500); // Brief pause between lines } } // Handle outcomes/rewards if (dialogue.reward) { this.awardReward(dialogue.reward); } if (dialogue.trigger_after) { this.scene.events.emit(dialogue.trigger_after); } } /** * Play single dialogue line with full effects */ async playLine(line) { const speaker = line.speaker; // Show portrait with bounce animation this.showPortrait(speaker, line.animation); // Start sound layers this.startSoundLayers(line.soundLayers); // Create dialogue box const dialogueBox = this.createDialogueBox(speaker, line.text); // Typewriter effect with voice sync await this.typewriterEffect(dialogueBox, line.text, line.typewriterSpeed); // Voice synthesis if (this.cinematicVoice) { await this.cinematicVoice.speak( line.text, speaker, line.emotion, { typewriterElement: dialogueBox.textElement, ambient: line.soundLayers.includes('wind_ambient') ? 'wind_ruins' : null } ); } // VFX if specified if (line.vfx) { this.scene.events.emit('vfx:trigger', line.vfx); } // Wait for player to dismiss await this.waitForDismiss(); // Hide dialogue this.hideDialogue(dialogueBox); this.hidePortrait(speaker); } /** * Portrait bounce animation (Style 32) */ showPortrait(speaker, animationType) { const portrait = this.scene.add.sprite(150, 500, `portrait_${speaker}`); portrait.setDepth(100); portrait.setAlpha(0); // Fade in this.scene.tweens.add({ targets: portrait, alpha: 1, duration: 300, ease: 'Sine.easeIn' }); // Bounce animation if (animationType === 'portrait_bounce') { this.scene.tweens.add({ targets: portrait, y: '+=10', duration: 600, yoyo: true, repeat: -1, ease: 'Sine.easeInOut' }); } else if (animationType === 'portrait_bounce_slow') { this.scene.tweens.add({ targets: portrait, y: '+=15', duration: 1000, yoyo: true, repeat: -1, ease: 'Sine.easeInOut' }); } this.currentPortrait = portrait; } hidePortrait(speaker) { if (this.currentPortrait) { this.scene.tweens.add({ targets: this.currentPortrait, alpha: 0, duration: 300, onComplete: () => this.currentPortrait.destroy() }); } } /** * Start ambient sound layers */ startSoundLayers(layers) { if (!layers) return; layers.forEach(soundKey => { if (this.scene.sound && this.scene.sound.get(soundKey)) { this.scene.sound.play(soundKey, { volume: 0.3, loop: soundKey.includes('ambient') }); } }); } /** * Typewriter effect */ async typewriterEffect(dialogueBox, text, speed) { const textElement = dialogueBox.textElement; textElement.setText(''); for (let i = 0; i < text.length; i++) { textElement.setText(text.substring(0, i + 1)); await this.wait(speed); } } createDialogueBox(speaker, text) { // Placeholder - real implementation would create Phaser graphics return { textElement: this.scene.add.text(200, 550, '', { fontSize: '18px', color: '#FFFFFF', wordWrap: { width: 600 } }).setDepth(101) }; } hideDialogue(dialogueBox) { if (dialogueBox && dialogueBox.textElement) { dialogueBox.textElement.destroy(); } } awardReward(reward) { if (reward.type === 'rare_gift' || reward.type === 'legendary_gift') { this.scene.zombieEconomy.awardRareGift(reward.item); } if (reward.vfx) { this.scene.events.emit('vfx:trigger', reward.vfx); } } wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async waitForDismiss() { // Wait for spacebar or click return new Promise(resolve => { const handler = () => { this.scene.input.keyboard.off('keydown-SPACE', handler); resolve(); }; this.scene.input.keyboard.on('keydown-SPACE', handler); }); } }