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

507 lines
15 KiB
JavaScript

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