/** * STORY & QUEST SYSTEM * Complete quest system with story acts, dialogue, cutscenes, and multiple endings */ class StoryQuestSystem { constructor(scene) { this.scene = scene; this.enabled = true; // Quests this.quests = new Map(); this.activeQuests = new Set(); this.completedQuests = new Set(); // Story progress this.currentAct = 1; this.storyFlags = new Set(); // Characters this.characters = new Map(); // Dialogue this.currentDialogue = null; // Endings this.playerChoices = []; this.ending = null; this.loadProgress(); this.init(); console.log('βœ… Story & Quest System initialized'); } init() { this.defineCharacters(); this.defineQuests(); console.log('πŸ“– Story & Quest system ready'); } // ========== CHARACTERS ========== defineCharacters() { this.characters.set('jakob', { name: 'Old Jakob', role: 'Merchant', relationship: 0, dialogues: new Map() }); this.characters.set('lyra', { name: 'Lyra', role: 'Mutant Elf', relationship: 0, dialogues: new Map() }); this.characters.set('grok', { name: 'Grok', role: 'Troll Guardian', relationship: 0, dialogues: new Map() }); this.characters.set('dr_chen', { name: 'Dr. Chen', role: 'Radio Voice', relationship: 0, dialogues: new Map() }); } // ========== QUESTS ========== defineQuests() { // ACT 1: Survival (Day 1-10) this.defineQuest('first_harvest', { name: 'Prvi Pridelek', act: 1, description: 'Harvest 10 wheat to survive', objectives: [ { type: 'harvest', item: 'wheat', amount: 10, current: 0 } ], rewards: { xp: 100, item: 'iron_hoe' }, unlocks: ['safe_haven'] }); this.defineQuest('safe_haven', { name: 'Varno Zatočiőče', act: 1, description: 'Build fence around farm', objectives: [ { type: 'build', item: 'fence', amount: 20, current: 0 } ], rewards: { xp: 150, blueprint: 'reinforced_fence' }, unlocks: ['night_watch'] }); this.defineQuest('night_watch', { name: 'Nočna StraΕΎa', act: 1, description: 'Survive first zombie night', objectives: [ { type: 'survive', nights: 1, current: 0 } ], rewards: { xp: 200, item: 'torch_pack' }, unlocks: ['meet_merchant'] }); this.defineQuest('meet_merchant', { name: 'Meet the Merchant', act: 1, description: 'Find Jakob the trader', objectives: [ { type: 'talk', npc: 'jakob' } ], rewards: { xp: 100, unlocks: 'trading' }, unlocks: ['strange_transmission'] }); // ACT 2: Discovery (Day 11-20) this.defineQuest('strange_transmission', { name: 'Strange Transmission', act: 2, description: 'Find radio in city', objectives: [ { type: 'find', item: 'radio', location: 'city_ruins' } ], rewards: { xp: 300, item: 'radio' }, unlocks: ['first_tame'] }); this.defineQuest('first_tame', { name: 'Prvi Poskus', act: 2, description: 'Tame first zombie', objectives: [ { type: 'tame', creature: 'zombie', amount: 1 } ], rewards: { xp: 250, unlocks: 'zombie_workers' }, unlocks: ['lab_ruins'] }); this.defineQuest('lab_ruins', { name: 'Lab Ruins', act: 2, description: 'Explore abandoned research facility', objectives: [ { type: 'explore', location: 'research_lab' } ], rewards: { xp: 400, item: 'lab_key' }, unlocks: ['mutant_contact'] }); this.defineQuest('mutant_contact', { name: 'Mutant Contact', act: 2, description: 'Meet friendly mutant Lyra', objectives: [ { type: 'talk', npc: 'lyra' } ], rewards: { xp: 300, unlocks: 'mutation_research' }, unlocks: ['lab_notes'] }); // ACT 3: The Truth (Day 21-30) this.defineQuest('lab_notes', { name: 'Lab Notes', act: 3, description: 'Collect 5 research documents', objectives: [ { type: 'collect', item: 'research_document', amount: 5, current: 0 } ], rewards: { xp: 500 }, unlocks: ['patient_zero'] }); this.defineQuest('patient_zero', { name: 'Patient Zero', act: 3, description: 'Find virus source', objectives: [ { type: 'find', item: 'virus_sample', location: 'deep_lab' } ], rewards: { xp: 600 }, unlocks: ['difficult_choice'] }); this.defineQuest('difficult_choice', { name: 'Difficult Choice', act: 3, description: 'Choose faction', objectives: [ { type: 'choice', options: ['human', 'zombie', 'hybrid'] } ], rewards: { xp: 1000 }, unlocks: ['final_confrontation'] }); this.defineQuest('final_confrontation', { name: 'Final Confrontation', act: 3, description: 'Boss battle', objectives: [ { type: 'defeat', boss: 'zombie_king' } ], rewards: { xp: 2000 }, ending: true }); } defineQuest(id, data) { this.quests.set(id, { id, active: false, completed: false, ...data }); } // ========== QUEST MANAGEMENT ========== startQuest(questId) { const quest = this.quests.get(questId); if (!quest || quest.active || quest.completed) return false; quest.active = true; this.activeQuests.add(questId); console.log(`πŸ“œ Quest started: ${quest.name}`); return true; } updateQuestProgress(questId, objectiveIndex, progress) { const quest = this.quests.get(questId); if (!quest || !quest.active) return; const objective = quest.objectives[objectiveIndex]; if (!objective) return; objective.current = progress; // Check if objective complete if (this.isObjectiveComplete(objective)) { console.log(`βœ… Objective complete: ${objective.type}`); // Check if all objectives complete if (quest.objectives.every(obj => this.isObjectiveComplete(obj))) { this.completeQuest(questId); } } } isObjectiveComplete(objective) { switch (objective.type) { case 'harvest': case 'build': case 'collect': return objective.current >= objective.amount; case 'talk': case 'find': case 'explore': case 'defeat': return objective.current === true; case 'survive': return objective.current >= objective.nights; default: return false; } } completeQuest(questId) { const quest = this.quests.get(questId); if (!quest) return; quest.active = false; quest.completed = true; this.activeQuests.delete(questId); this.completedQuests.add(questId); // Grant rewards this.grantQuestRewards(quest); // Unlock next quests if (quest.unlocks) { const unlocks = Array.isArray(quest.unlocks) ? quest.unlocks : [quest.unlocks]; unlocks.forEach(nextQuest => this.startQuest(nextQuest)); } // Check for ending if (quest.ending) { this.triggerEnding(); } console.log(`πŸŽ‰ Quest completed: ${quest.name}!`); this.saveProgress(); } grantQuestRewards(quest) { const rewards = quest.rewards; if (rewards.xp && this.scene.skillTree) { this.scene.skillTree.addXP(rewards.xp); } if (rewards.item && this.scene.inventorySystem) { this.scene.inventorySystem.addItem(rewards.item, 1); } if (rewards.blueprint) { console.log(`πŸ“‹ Unlocked blueprint: ${rewards.blueprint}`); } } // ========== DIALOGUE SYSTEM ========== startDialogue(npcId, dialogueId) { const character = this.characters.get(npcId); if (!character) return false; this.currentDialogue = { npc: npcId, dialogueId, currentNode: 0, choices: [] }; console.log(`πŸ’¬ Dialogue started with ${character.name}`); return true; } selectDialogueChoice(choiceIndex) { if (!this.currentDialogue) return; const choice = this.currentDialogue.choices[choiceIndex]; if (!choice) return; // Track player choice this.playerChoices.push({ npc: this.currentDialogue.npc, choice: choice.text, consequence: choice.consequence }); // Update relationship const character = this.characters.get(this.currentDialogue.npc); if (character && choice.relationshipDelta) { character.relationship += choice.relationshipDelta; } // Apply consequences if (choice.consequence) { this.applyChoiceConsequence(choice.consequence); } console.log(`πŸ’¬ Choice selected: ${choice.text}`); } applyChoiceConsequence(consequence) { // Set story flags if (consequence.flag) { this.storyFlags.add(consequence.flag); } // Unlock quests if (consequence.unlockQuest) { this.startQuest(consequence.unlockQuest); } // Change faction if (consequence.faction) { this.playerChoices.push({ type: 'faction', value: consequence.faction }); } } endDialogue() { this.currentDialogue = null; } // ========== CUTSCENES ========== playCutscene(cutsceneId) { console.log(`🎬 Playing cutscene: ${cutsceneId}`); const cutscenes = { 'arrival': this.cutsceneArrival.bind(this), 'first_zombie': this.cutsceneFirstZombie.bind(this), 'city_discovery': this.cutsceneCityDiscovery.bind(this), 'boss_reveal': this.cutsceneBossReveal.bind(this) }; const cutscene = cutscenes[cutsceneId]; if (cutscene) { cutscene(); } } cutsceneArrival() { console.log('🎬 Arrival cutscene - Farm overview'); } cutsceneFirstZombie() { console.log('🎬 First zombie encounter - Tutorial'); } cutsceneCityDiscovery() { console.log('🎬 City discovery - Ruins pan'); } cutsceneBossReveal() { console.log('🎬 Boss reveal - Zombie King emergence'); } // ========== ENDINGS ========== triggerEnding() { // Determine ending based on player choices const faction = this.getPlayerFaction(); const relationships = this.getRelationships(); if (faction === 'human' && relationships.jakob > 50) { this.ending = 'cure'; this.playCutscene('cure_ending'); } else if (faction === 'zombie') { this.ending = 'zombie_king'; this.playCutscene('zombie_king_ending'); } else if (faction === 'hybrid') { this.ending = 'mutation'; this.playCutscene('mutation_ending'); } else if (relationships.lyra > 70) { this.ending = 'escape'; this.playCutscene('escape_ending'); } else { this.ending = 'farmer'; this.playCutscene('farmer_ending'); } console.log(`🎬 Ending: ${this.ending}`); this.saveProgress(); } getPlayerFaction() { const factionChoice = this.playerChoices.find(c => c.type === 'faction'); return factionChoice ? factionChoice.value : null; } getRelationships() { const relationships = {}; for (const [id, character] of this.characters.entries()) { relationships[id] = character.relationship; } return relationships; } // ========== PERSISTENCE ========== saveProgress() { const data = { currentAct: this.currentAct, activeQuests: Array.from(this.activeQuests), completedQuests: Array.from(this.completedQuests), storyFlags: Array.from(this.storyFlags), playerChoices: this.playerChoices, ending: this.ending, characters: Array.from(this.characters.entries()).map(([id, char]) => ({ id, relationship: char.relationship })) }; localStorage.setItem('novafarma_story', JSON.stringify(data)); } loadProgress() { const saved = localStorage.getItem('novafarma_story'); if (saved) { try { const data = JSON.parse(saved); this.currentAct = data.currentAct || 1; this.activeQuests = new Set(data.activeQuests || []); this.completedQuests = new Set(data.completedQuests || []); this.storyFlags = new Set(data.storyFlags || []); this.playerChoices = data.playerChoices || []; this.ending = data.ending || null; if (data.characters) { data.characters.forEach(char => { const character = this.characters.get(char.id); if (character) { character.relationship = char.relationship; } }); } console.log('βœ… Story progress loaded'); } catch (error) { console.error('Failed to load story progress:', error); } } } destroy() { this.saveProgress(); console.log('πŸ“– Story & Quest System destroyed'); } }