/** * QUEST SYSTEM v2.0 - Mrtva Dolina * Complete quest management with ADHD-friendly dialogue and VFX integration * * Features: * - Main story arc (Zamegljeni Spomini, Anina Sled) * - Town economy quests (NPCs) * - Defense quests (Wall building, Museum) * - Side quests & companion recruitment * - Progress tracking & rewards * - Dialogue system integration * - VFX triggers * * v2.1 UPDATE: Now loads quests from QuestDataLoader (Quest Manifest v2.0 compatible) */ // ❌ DISABLED: ES6 import not supported in browser without module bundler // import QuestDataLoader from '../data/QuestDataLoader.js'; class QuestSystem { constructor(scene) { this.scene = scene; this.quests = new Map(); this.activeQuests = []; this.completedQuests = []; this.questLog = []; this.initializeQuests(); } initializeQuests() { console.log('📜 Loading Quest Manifest v2.0...'); // ❌ DISABLED: QuestDataLoader not available without ES6 modules // Load all quests from QuestDataLoader // const manifestQuests = QuestDataLoader.loadAllQuests(); // manifestQuests.forEach(quest => { // this.registerQuest(quest); // }); // console.log(`✅ Loaded ${manifestQuests.length} quests from manifest!`); // Keep legacy quests for backwards compatibility this.registerLegacyQuests(); } /** * Legacy quests for backwards compatibility */ registerLegacyQuests() { // MAIN STORY QUEST 1: Zamegljeni Spomini this.registerQuest({ id: 'zamegljeni_spomini', title: 'Zamegljeni Spomini', type: 'main_story', priority: 5, description: 'Kai se zbudi brez spominov. Najdi fotografijo.', objectives: [ { id: 'find_photo', text: 'Najdi družinsko fotografijo', type: 'item', item: 'family_photo', required: 1, current: 0 }, { id: 'unlock_capital', text: 'Odkleni Capital City', type: 'flag', flag: 'capital_unlocked', complete: false } ], rewards: { xp: 500, items: ['family_photo'], unlocks: ['capital_city'] }, vfx: { start: 'amnesia_blur', complete: 'flashback' }, dialogue: { start: ["Kje sem? Kaj se mi dogaja?", "Ta fotka... Kdo je ta punca?"], complete: ["Ana! Pomnim se! To je moja sestra!"] }, npc: null, location: 'base_farm' }); // MAIN STORY QUEST 2: Anina Sled this.registerQuest({ id: 'anина_sled', title: 'Anina Sled', type: 'main_story', priority: 5, description: 'Zberi 50 namigov o tem, kje je Ana.', objectives: [ { id: 'collect_clues', text: 'Zberi Aniне namige (0/50)', type: 'collection', item: 'ana_clue', required: 50, current: 0 } ], rewards: { xp: 5000, items: ['final_location'], unlocks: ['ana_encounter'] }, vfx: { onClue: 'amnesia_blur', complete: 'revelation' }, dialogue: { onClue: ["Kai, veš da te imam rada?", "Ne skrbi zame..."], complete: ["NAŠEL SEM JO! Ana, prihajam!"] }, prerequisites: ['zamegljeni_spomini'], npc: null }); // TOWN QUEST: Šivilja this.registerQuest({ id: 'siviljina_prosnja', title: 'Šiviljina Prošnja', type: 'town_economy', priority: 4, description: 'Šivilja rabi 20x Platno.', objectives: [ { id: 'cloth', text: 'Zberi platno (0/20)', type: 'item', item: 'cloth', required: 20, current: 0 } ], rewards: { xp: 200, items: ['enchanted_jacket'], gold: 500 }, dialogue: { start: ["Hej! Rabim 20 platna. Lahko mi pomagaš?"], progress: ["Še {remaining}!"], complete: ["SUPER! Na, vzemi ta jopič!"] }, npc: 'sivilja' }); // TOWN QUEST: Pek this.registerQuest({ id: 'pekov_recept', title: 'Pekov Recept', type: 'town_economy', priority: 4, description: 'Pek rabi 10x Pšenico za kruh.', objectives: [ { id: 'wheat', text: 'Zberi pšenico (0/10)', type: 'item', item: 'wheat', required: 10, current: 0 }, { id: 'restore', text: 'Obnovi pekarno', type: 'building', building: 'bakery', complete: false } ], rewards: { xp: 300, items: ['daily_bread'], unlocks: ['bakery_shop'] }, dialogue: { start: ["Živjo! Rabim 10 pšenice, pa še pekarna je zrušena!"], complete: ["ZAKON! Zdej lahko spet pečem! Kruh na dan ZASTONJ!"] }, npc: 'pek' }); // TOWN QUEST: Tehnik this.registerQuest({ id: 'tehnik_generator', title: 'Tehnikova Naprava', type: 'town_economy', priority: 4, description: 'Tehnik rabi 5x Električne Komponente.', objectives: [ { id: 'components', text: 'Zberi komponente (0/5)', type: 'item', item: 'electric_component', required: 5, current: 0 } ], rewards: { xp: 250, items: ['auto_tiller'], unlocks: ['tech_services'] }, dialogue: { start: ["Yo! Rabim 5 električnih komponent za generator."], complete: ["NICE! Generator dela! Auto-Tiller upgrade za te!"] }, npc: 'tehnik' }); // DEFENSE QUEST: Walls this.registerQuest({ id: 'obzidje', title: 'Obzidje Mrtve Doline', type: 'defense', priority: 5, description: 'Zgradi 20 segmentov zidov.', objectives: [ { id: 'walls', text: 'Zgradi zunanje zidove (0/20)', type: 'building', building: 'wall', required: 20, current: 0 } ], rewards: { xp: 400, items: ['wall_blueprint_2'], unlocks: ['raid_event'] }, vfx: { complete: 'raid_warning' }, dialogue: { start: ["Zombiji prihajajo! Hitr zgradi zidove!"], complete: ["DONE! Zidovi so zgoraj! Priprav se na raiderje!"] }, npc: 'mayor', triggers: ['day_7'] }); // COLLECTION QUEST: Museum this.registerQuest({ id: 'muzej_hrosci', title: 'Muzejski Mejnik', type: 'collection', priority: 3, description: 'Kustos rabi 24 različnih hroščev.', objectives: [ { id: 'bugs', text: 'Doniraj hrošče (0/24)', type: 'collection', collection: 'museum_bugs', required: 24, current: 0 } ], rewards: { xp: 600, items: ['museum_key'], unlocks: ['museum_stage2'] }, dialogue: { start: ["Potrebujem 24 različnih hroščev za muzejsko zbirko."], progress: ["Odlično! Še {remaining}!"], complete: ["FANTASTIČNO! Razširim muzej!"] }, npc: 'kustos' }); // SIDE QUEST: Arborist this.registerQuest({ id: 'arborist_sadike', title: 'Arboristova Pomoč', type: 'side', priority: 2, description: 'Arborist rabi 50x sadike dreves.', objectives: [ { id: 'saplings', text: 'Zberi sadike (0/50)', type: 'item', item: 'tree_sapling', required: 50, current: 0 } ], rewards: { xp: 350, items: ['tree_planter'], unlocks: ['forest_restore'] }, dialogue: { start: ["Rad bi obnovil gozd. Mi lahko pomagaš?"], complete: ["SUPER! Drevo-sadersko orodje je zate!"] }, npc: 'arborist' }); // COMPANION QUEST: Zombie Scout this.registerQuest({ id: 'zombie_scout_join', title: 'Zombi Skavt - Rekrutacija', type: 'companion', priority: 5, description: 'Pridobi zaupanje inteligentnega zombija.', objectives: [ { id: 'feed', text: 'Nahrani zombija (0/3 mesa)', type: 'item_give', item: 'meat', required: 3, current: 0 }, { id: 'trust', text: 'Pridobi zaupanje', type: 'interaction', complete: false } ], rewards: { xp: 1000, unlocks: ['zombie_scout_companion'], companions: ['zombie_scout'] }, vfx: { complete: 'companion_join' }, dialogue: { start: ["*zombie zvoki* ...Meso?"], progress: ["*munch* ...Ti dobr človek."], complete: ["*zavija* Jaz s teb! Jaz pomagat! [JOINED]"] }, npc: 'zombie_scout', location: 'dead_forest' }); // Legacy basic quest chain for backwards compatibility this.registerQuest({ id: 'q1_start', title: 'Survival Basics', type: 'tutorial', priority: 5, description: 'Collect Wood and Stone.', objectives: [ { id: 'wood', text: 'Collect Wood (0/5)', type: 'item', item: 'wood', required: 5, current: 0 }, { id: 'stone', text: 'Collect Stone (0/3)', type: 'item', item: 'stone', required: 3, current: 0 } ], rewards: { gold: 10, xp: 50 }, nextQuest: 'q2_farm', npc: 'villager' }); this.registerQuest({ id: 'q2_farm', title: 'The Farmer', type: 'tutorial', priority: 5, description: 'Plant some seeds.', objectives: [ { id: 'plant', text: 'Plant seeds (0/3)', type: 'action', action: 'plant', required: 3, current: 0 } ], rewards: { gold: 20, items: ['wood:10'] }, prerequisites: ['q1_start'], nextQuest: 'q3_defense', npc: 'villager' }); this.registerQuest({ id: 'q3_defense', title: 'Fortification', type: 'tutorial', priority: 4, description: 'Build a Fence.', objectives: [ { id: 'fence', text: 'Build Fence (0/2)', type: 'action', action: 'build_fence', required: 2, current: 0 } ], rewards: { gold: 50, items: ['sword:1'] }, prerequisites: ['q2_farm'], nextQuest: 'q4_slayer', npc: 'merchant' }); this.registerQuest({ id: 'q4_slayer', title: 'Zombie Slayer', type: 'tutorial', priority: 4, description: 'Kill 3 Zombies.', objectives: [ { id: 'kill', text: 'Kill Zombies (0/3)', type: 'kill', target: 'zombie', required: 3, current: 0 } ], rewards: { gold: 100, items: ['gold:50'] }, prerequisites: ['q3_defense'], npc: 'villager' }); } registerQuest(questData) { // Set defaults questData.isActive = false; questData.isComplete = false; questData.startTime = null; this.quests.set(questData.id, questData); } startQuest(questId) { const quest = this.quests.get(questId); if (!quest) { console.error(`Quest ${questId} not found!`); return false; } // Check prerequisites if (quest.prerequisites) { for (const prereq of quest.prerequisites) { if (!this.isQuestComplete(prereq)) { console.log(`Cannot start ${questId}: Missing ${prereq}`); return false; } } } // Prevent duplicate active if (this.activeQuests.includes(questId)) { return false; } quest.isActive = true; quest.startTime = Date.now(); this.activeQuests.push(questId); this.questLog.push({ questId, startTime: quest.startTime, status: 'active' }); // Show start dialogue if (quest.dialogue && quest.dialogue.start) { this.showDialogue(quest.dialogue.start, quest.npc); // 🔊 VOICEOVER: Play quest start dialogue if (this.scene.voiceoverSystem) { const voiceKey = `quest_${questId}_start`; this.scene.voiceoverSystem.playVoiceover(voiceKey, quest.dialogue.start[0]); } } // Trigger VFX if (quest.vfx && quest.vfx.start) { this.scene.events.emit('vfx:trigger', quest.vfx.start); } // Show notification this.scene.events.emit('show-floating-text', { x: this.scene.player.x, y: this.scene.player.y - 50, text: 'Quest Accepted!', color: '#FFFF00' }); this.scene.events.emit('quest:started', questId); this.updateUI(); return true; } updateObjective(questId, objectiveId, progress) { const quest = this.quests.get(questId); if (!quest || !quest.isActive) return; const objective = quest.objectives.find(obj => obj.id === objectiveId); if (!objective) return; // Update progress if (objective.type === 'item' || objective.type === 'collection') { objective.current = Math.min(progress, objective.required); objective.complete = objective.current >= objective.required; } else if (objective.type === 'action' || objective.type === 'kill') { objective.current = Math.min(progress, objective.required); objective.complete = objective.current >= objective.required; } else if (objective.type === 'flag' || objective.type === 'interaction' || objective.type === 'building') { objective.complete = progress === true; } // Check completion const allComplete = quest.objectives.every(obj => obj.complete); if (allComplete) { this.completeQuest(questId); } else { // Progress dialogue if (quest.dialogue && quest.dialogue.progress) { const remaining = objective.required - objective.current; const msg = quest.dialogue.progress[0].replace('{remaining}', remaining); this.showDialogue([msg], quest.npc); // 🔊 VOICEOVER: Play progress dialogue if (this.scene.voiceoverSystem) { const voiceKey = `quest_${questId}_progress`; this.scene.voiceoverSystem.playVoiceover(voiceKey, msg); } } } this.scene.events.emit('quest:updated', questId, objectiveId, progress); this.updateUI(); } trackAction(actionType, amount = 1) { // Track actions for active quests' objectives for (const questId of this.activeQuests) { const quest = this.quests.get(questId); if (!quest) continue; for (const obj of quest.objectives) { if (obj.complete) continue; if (obj.type === 'action' && obj.action === actionType) { obj.current = Math.min((obj.current || 0) + amount, obj.required); if (obj.current >= obj.required) { obj.complete = true; this.scene.events.emit('show-floating-text', { x: this.scene.player.x, y: this.scene.player.y, text: 'Objective Complete!', color: '#00FF00' }); } } else if (obj.type === 'kill' && obj.target === actionType) { obj.current = Math.min((obj.current || 0) + amount, obj.required); if (obj.current >= obj.required) { obj.complete = true; this.scene.events.emit('show-floating-text', { x: this.scene.player.x, y: this.scene.player.y, text: 'Objective Complete!', color: '#00FF00' }); } } } // Auto-complete check const allDone = quest.objectives.every(o => o.complete); if (allDone) { this.completeQuest(questId); } } this.updateUI(); } completeQuest(questId) { const quest = this.quests.get(questId); if (!quest) return; quest.isActive = false; quest.isComplete = true; this.activeQuests = this.activeQuests.filter(id => id !== questId); this.completedQuests.push(questId); // Grant rewards if (quest.rewards) { if (quest.rewards.xp && this.scene.player && this.scene.player.gainExperience) { this.scene.player.gainExperience(quest.rewards.xp); } if (quest.rewards.gold && this.scene.inventorySystem) { this.scene.inventorySystem.addGold(quest.rewards.gold); } if (quest.rewards.items) { quest.rewards.items.forEach(item => { const [itemName, amt] = item.includes(':') ? item.split(':') : [item, 1]; if (this.scene.inventorySystem) { this.scene.inventorySystem.addItem(itemName, parseInt(amt)); } else if (this.scene.player && this.scene.player.inventory) { this.scene.player.inventory.addItem(itemName, parseInt(amt)); } }); } if (quest.rewards.unlocks) { quest.rewards.unlocks.forEach(unlock => { if (this.scene.gameState && this.scene.gameState.unlock) { this.scene.gameState.unlock(unlock); } }); } if (quest.rewards.companions) { quest.rewards.companions.forEach(companion => { if (this.scene.player && this.scene.player.addCompanion) { this.scene.player.addCompanion(companion); } }); } } // Completion dialogue if (quest.dialogue && quest.dialogue.complete) { this.showDialogue(quest.dialogue.complete, quest.npc); // 🔊 VOICEOVER: Play completion dialogue if (this.scene.voiceoverSystem) { const voiceKey = `quest_${questId}_complete`; this.scene.voiceoverSystem.playVoiceover(voiceKey, quest.dialogue.complete[0]); } } // Completion VFX if (quest.vfx && (quest.vfx.complete || quest.vfx.onComplete)) { this.scene.events.emit('vfx:trigger', quest.vfx.complete || quest.vfx.onComplete); } // Notification this.scene.events.emit('show-floating-text', { x: this.scene.player.x, y: this.scene.player.y - 50, text: 'Quest Complete!', color: '#00FF00' }); console.log(`🏆 Quest Complete: ${quest.title}`); this.scene.events.emit('quest:completed', questId); // Auto-start next quest if defined if (quest.nextQuest) { setTimeout(() => { console.log(`Next quest available: ${quest.nextQuest}`); }, 1000); } this.updateUI(); } showDialogue(lines, npcId) { // Emit dialogue event this.scene.events.emit('dialogue:show', { npc: npcId, lines: lines }); } getActiveQuests() { return this.activeQuests.map(id => this.quests.get(id)); } getAvailableQuest(npcType) { // Find first uncompleted, non-active quest for this NPC for (const [id, quest] of this.quests) { if (quest.isComplete || quest.isActive) continue; if (quest.npc !== npcType) continue; // Check prerequisites if (quest.prerequisites) { const prereqsMet = quest.prerequisites.every(p => this.isQuestComplete(p)); if (!prereqsMet) continue; } return quest; } return null; } getQuestProgress(questId) { const quest = this.quests.get(questId); if (!quest) return null; return { id: questId, title: quest.title, objectives: quest.objectives.map(obj => ({ text: obj.text, current: obj.current || 0, required: obj.required || 1, complete: obj.complete || false })), isActive: quest.isActive, isComplete: quest.isComplete }; } isQuestActive(questId) { const quest = this.quests.get(questId); return quest && quest.isActive; } isQuestComplete(questId) { const quest = this.quests.get(questId); return quest && quest.isComplete; } update(delta) { // Auto-update objectives based on inventory if (!this.scene.inventorySystem) return; for (const questId of this.activeQuests) { const quest = this.quests.get(questId); if (!quest) continue; let changed = false; for (const obj of quest.objectives) { if (obj.complete) continue; if (obj.type === 'item') { const count = this.scene.inventorySystem.getItemCount(obj.item); if (count !== obj.current) { obj.current = count; changed = true; if (obj.current >= obj.required) { obj.complete = true; this.scene.events.emit('show-floating-text', { x: this.scene.player.x, y: this.scene.player.y, text: 'Objective Complete!', color: '#00FF00' }); } } } } if (changed) { const allDone = quest.objectives.every(o => o.complete); if (allDone) { this.completeQuest(questId); } else { this.updateUI(); } } } } updateUI() { const ui = this.scene.scene.get('UIScene'); if (ui && ui.updateQuestTracker) { const active = this.activeQuests.length > 0 ? this.quests.get(this.activeQuests[0]) : null; ui.updateQuestTracker(active); } } destroy() { this.quests.clear(); this.activeQuests = []; this.completedQuests = []; this.questLog = []; } }