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

635 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 = [];
}
}