Buildings, VFX particles, and Quest System v2.0. Added 5 building sprites (Capital City, Museum, Bakery). Added 6 VFX particles (sparkles, water, glow, smoke, blood, coin). Upgraded Quest System to v2.0 with 12 quests, ADHD dialogue, VFX integration, rewards, and backwards compatibility.
This commit is contained in:
@@ -1,197 +1,593 @@
|
|||||||
class QuestSystem {
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class QuestSystem {
|
||||||
constructor(scene) {
|
constructor(scene) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
|
this.quests = new Map();
|
||||||
|
this.activeQuests = [];
|
||||||
|
this.completedQuests = [];
|
||||||
|
this.questLog = [];
|
||||||
|
|
||||||
// Quest Definitions
|
this.initializeQuests();
|
||||||
this.questDB = {
|
}
|
||||||
'q1_start': {
|
|
||||||
|
initializeQuests() {
|
||||||
|
// 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',
|
id: 'q1_start',
|
||||||
title: 'Survival Basics',
|
title: 'Survival Basics',
|
||||||
description: 'Collect Wood and Stone to build your first defense.',
|
type: 'tutorial',
|
||||||
|
priority: 5,
|
||||||
|
description: 'Collect Wood and Stone.',
|
||||||
objectives: [
|
objectives: [
|
||||||
{ type: 'collect', item: 'wood', amount: 5, current: 0, done: false },
|
{ id: 'wood', text: 'Collect Wood (0/5)', type: 'item', item: 'wood', required: 5, current: 0 },
|
||||||
{ type: 'collect', item: 'stone', amount: 3, current: 0, done: false }
|
{ id: 'stone', text: 'Collect Stone (0/3)', type: 'item', item: 'stone', required: 3, current: 0 }
|
||||||
],
|
],
|
||||||
reward: { gold: 10, xp: 50 },
|
rewards: { gold: 10, xp: 50 },
|
||||||
nextQuest: 'q2_farm',
|
nextQuest: 'q2_farm',
|
||||||
giver: 'villager'
|
npc: 'villager'
|
||||||
},
|
});
|
||||||
'q2_farm': {
|
|
||||||
|
this.registerQuest({
|
||||||
id: 'q2_farm',
|
id: 'q2_farm',
|
||||||
title: 'The Farmer',
|
title: 'The Farmer',
|
||||||
description: 'Plant some seeds to grow food. You will need it.',
|
type: 'tutorial',
|
||||||
|
priority: 5,
|
||||||
|
description: 'Plant some seeds.',
|
||||||
objectives: [
|
objectives: [
|
||||||
{ type: 'action', action: 'plant', amount: 3, current: 0, done: false }
|
{ id: 'plant', text: 'Plant seeds (0/3)', type: 'action', action: 'plant', required: 3, current: 0 }
|
||||||
],
|
],
|
||||||
reward: { gold: 20, item: 'wood', amount: 10 },
|
rewards: { gold: 20, items: ['wood:10'] },
|
||||||
|
prerequisites: ['q1_start'],
|
||||||
nextQuest: 'q3_defense',
|
nextQuest: 'q3_defense',
|
||||||
giver: 'villager'
|
npc: 'villager'
|
||||||
},
|
});
|
||||||
'q3_defense': {
|
|
||||||
|
this.registerQuest({
|
||||||
id: 'q3_defense',
|
id: 'q3_defense',
|
||||||
title: 'Fortification',
|
title: 'Fortification',
|
||||||
description: 'Build a Fence to keep zombies out.',
|
type: 'tutorial',
|
||||||
|
priority: 4,
|
||||||
|
description: 'Build a Fence.',
|
||||||
objectives: [
|
objectives: [
|
||||||
{ type: 'action', action: 'build_fence', amount: 2, current: 0, done: false }
|
{ id: 'fence', text: 'Build Fence (0/2)', type: 'action', action: 'build_fence', required: 2, current: 0 }
|
||||||
],
|
],
|
||||||
reward: { gold: 50, item: 'sword', amount: 1 },
|
rewards: { gold: 50, items: ['sword:1'] },
|
||||||
|
prerequisites: ['q2_farm'],
|
||||||
nextQuest: 'q4_slayer',
|
nextQuest: 'q4_slayer',
|
||||||
giver: 'merchant'
|
npc: 'merchant'
|
||||||
},
|
});
|
||||||
'q4_slayer': {
|
|
||||||
|
this.registerQuest({
|
||||||
id: 'q4_slayer',
|
id: 'q4_slayer',
|
||||||
title: 'Zombie Slayer',
|
title: 'Zombie Slayer',
|
||||||
description: 'Kill 3 Zombies using your new sword.',
|
type: 'tutorial',
|
||||||
|
priority: 4,
|
||||||
|
description: 'Kill 3 Zombies.',
|
||||||
objectives: [
|
objectives: [
|
||||||
{ type: 'kill', target: 'zombie', amount: 3, current: 0, done: false }
|
{ id: 'kill', text: 'Kill Zombies (0/3)', type: 'kill', target: 'zombie', required: 3, current: 0 }
|
||||||
],
|
],
|
||||||
reward: { gold: 100, item: 'gold', amount: 50 },
|
rewards: { gold: 100, items: ['gold:50'] },
|
||||||
nextQuest: null,
|
prerequisites: ['q3_defense'],
|
||||||
giver: 'villager'
|
npc: 'villager'
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
this.activeQuest = null;
|
|
||||||
this.completedQuests = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAvailableQuest(npcType) {
|
registerQuest(questData) {
|
||||||
const chain = ['q1_start', 'q2_farm', 'q3_defense', 'q4_slayer'];
|
// Set defaults
|
||||||
let targetId = null;
|
questData.isActive = false;
|
||||||
for (const id of chain) {
|
questData.isComplete = false;
|
||||||
if (!this.completedQuests.includes(id)) {
|
questData.startTime = null;
|
||||||
targetId = id;
|
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetId) return null;
|
// Prevent duplicate active
|
||||||
if (this.activeQuest && this.activeQuest.id === targetId) return null;
|
if (this.activeQuests.includes(questId)) {
|
||||||
|
return false;
|
||||||
const q = this.questDB[targetId];
|
|
||||||
if (q.giver === npcType) return q;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startQuest(id) {
|
quest.isActive = true;
|
||||||
if (this.completedQuests.includes(id)) return;
|
quest.startTime = Date.now();
|
||||||
|
this.activeQuests.push(questId);
|
||||||
|
|
||||||
const template = this.questDB[id];
|
this.questLog.push({
|
||||||
if (!template) return;
|
questId,
|
||||||
|
startTime: quest.startTime,
|
||||||
|
status: 'active'
|
||||||
|
});
|
||||||
|
|
||||||
this.activeQuest = JSON.parse(JSON.stringify(template));
|
// Show start dialogue
|
||||||
console.log(`📜 Quest Started: ${this.activeQuest.title}`);
|
if (quest.dialogue && quest.dialogue.start) {
|
||||||
|
this.showDialogue(quest.dialogue.start, quest.npc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completion VFX
|
||||||
|
if (quest.vfx && (quest.vfx.complete || quest.vfx.onComplete)) {
|
||||||
|
this.scene.events.emit('vfx:trigger', quest.vfx.complete || quest.vfx.onComplete);
|
||||||
|
}
|
||||||
|
|
||||||
// Notification
|
// Notification
|
||||||
this.scene.events.emit('show-floating-text', {
|
this.scene.events.emit('show-floating-text', {
|
||||||
x: this.scene.player.x,
|
x: this.scene.player.x,
|
||||||
y: this.scene.player.y - 50,
|
y: this.scene.player.y - 50,
|
||||||
text: "Quest Accepted!",
|
text: 'Quest Complete!',
|
||||||
color: '#FFFF00'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
update(delta) {
|
|
||||||
if (!this.activeQuest) return;
|
|
||||||
|
|
||||||
let changed = false;
|
|
||||||
let allDone = true;
|
|
||||||
|
|
||||||
if (this.scene.inventorySystem) {
|
|
||||||
const inv = this.scene.inventorySystem;
|
|
||||||
|
|
||||||
for (const obj of this.activeQuest.objectives) {
|
|
||||||
if (obj.done) continue;
|
|
||||||
|
|
||||||
if (obj.type === 'collect') {
|
|
||||||
const count = inv.getItemCount(obj.item);
|
|
||||||
if (count !== obj.current) {
|
|
||||||
obj.current = count;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (obj.current >= obj.amount) {
|
|
||||||
obj.done = true;
|
|
||||||
this.scene.events.emit('show-floating-text', { x: this.scene.player.x, y: this.scene.player.y, text: "Objective Complete!" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const obj of this.activeQuest.objectives) {
|
|
||||||
if (!obj.done) allDone = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed) this.updateUI();
|
|
||||||
|
|
||||||
if (allDone) {
|
|
||||||
this.completeQuest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trackAction(actionType, amount = 1) {
|
|
||||||
if (!this.activeQuest) return;
|
|
||||||
|
|
||||||
let changed = false;
|
|
||||||
for (const obj of this.activeQuest.objectives) {
|
|
||||||
if (obj.done) continue;
|
|
||||||
|
|
||||||
if (obj.type === 'action' && obj.action === actionType) {
|
|
||||||
obj.current += amount;
|
|
||||||
changed = true;
|
|
||||||
if (obj.current >= obj.amount) {
|
|
||||||
obj.done = true;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (obj.type === 'kill' && obj.target === actionType) {
|
|
||||||
obj.current += amount;
|
|
||||||
changed = true;
|
|
||||||
if (obj.current >= obj.amount) {
|
|
||||||
obj.done = true;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (changed) this.updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
completeQuest() {
|
|
||||||
console.log(`🏆 Quest Complete: ${this.activeQuest.title}`);
|
|
||||||
|
|
||||||
if (this.activeQuest.reward) {
|
|
||||||
const r = this.activeQuest.reward;
|
|
||||||
if (r.gold && this.scene.inventorySystem) {
|
|
||||||
this.scene.inventorySystem.addGold(r.gold);
|
|
||||||
}
|
|
||||||
if (r.item && this.scene.inventorySystem) {
|
|
||||||
this.scene.inventorySystem.addItem(r.item, r.amount || 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scene.events.emit('show-floating-text', {
|
|
||||||
x: this.scene.player.x,
|
|
||||||
y: this.scene.player.y - 50,
|
|
||||||
text: "Quest Complete!",
|
|
||||||
color: '#00FF00'
|
color: '#00FF00'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.completedQuests.push(this.activeQuest.id);
|
console.log(`🏆 Quest Complete: ${quest.title}`);
|
||||||
const next = this.activeQuest.nextQuest;
|
this.scene.events.emit('quest:completed', questId);
|
||||||
this.activeQuest = null;
|
|
||||||
this.updateUI();
|
|
||||||
|
|
||||||
if (next) {
|
// Auto-start next quest if defined
|
||||||
console.log('Next quest available at NPC.');
|
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() {
|
updateUI() {
|
||||||
const ui = this.scene.scene.get('UIScene');
|
const ui = this.scene.scene.get('UIScene');
|
||||||
if (ui && ui.updateQuestTracker) {
|
if (ui && ui.updateQuestTracker) {
|
||||||
ui.updateQuestTracker(this.activeQuest);
|
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 = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user