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) {
|
||||
this.scene = scene;
|
||||
|
||||
// Quest Definitions
|
||||
this.questDB = {
|
||||
'q1_start': {
|
||||
id: 'q1_start',
|
||||
title: 'Survival Basics',
|
||||
description: 'Collect Wood and Stone to build your first defense.',
|
||||
objectives: [
|
||||
{ type: 'collect', item: 'wood', amount: 5, current: 0, done: false },
|
||||
{ type: 'collect', item: 'stone', amount: 3, current: 0, done: false }
|
||||
],
|
||||
reward: { gold: 10, xp: 50 },
|
||||
nextQuest: 'q2_farm',
|
||||
giver: 'villager'
|
||||
},
|
||||
'q2_farm': {
|
||||
id: 'q2_farm',
|
||||
title: 'The Farmer',
|
||||
description: 'Plant some seeds to grow food. You will need it.',
|
||||
objectives: [
|
||||
{ type: 'action', action: 'plant', amount: 3, current: 0, done: false }
|
||||
],
|
||||
reward: { gold: 20, item: 'wood', amount: 10 },
|
||||
nextQuest: 'q3_defense',
|
||||
giver: 'villager'
|
||||
},
|
||||
'q3_defense': {
|
||||
id: 'q3_defense',
|
||||
title: 'Fortification',
|
||||
description: 'Build a Fence to keep zombies out.',
|
||||
objectives: [
|
||||
{ type: 'action', action: 'build_fence', amount: 2, current: 0, done: false }
|
||||
],
|
||||
reward: { gold: 50, item: 'sword', amount: 1 },
|
||||
nextQuest: 'q4_slayer',
|
||||
giver: 'merchant'
|
||||
},
|
||||
'q4_slayer': {
|
||||
id: 'q4_slayer',
|
||||
title: 'Zombie Slayer',
|
||||
description: 'Kill 3 Zombies using your new sword.',
|
||||
objectives: [
|
||||
{ type: 'kill', target: 'zombie', amount: 3, current: 0, done: false }
|
||||
],
|
||||
reward: { gold: 100, item: 'gold', amount: 50 },
|
||||
nextQuest: null,
|
||||
giver: 'villager'
|
||||
}
|
||||
};
|
||||
|
||||
this.activeQuest = null;
|
||||
this.quests = new Map();
|
||||
this.activeQuests = [];
|
||||
this.completedQuests = [];
|
||||
this.questLog = [];
|
||||
|
||||
this.initializeQuests();
|
||||
}
|
||||
|
||||
getAvailableQuest(npcType) {
|
||||
const chain = ['q1_start', 'q2_farm', 'q3_defense', 'q4_slayer'];
|
||||
let targetId = null;
|
||||
for (const id of chain) {
|
||||
if (!this.completedQuests.includes(id)) {
|
||||
targetId = id;
|
||||
break;
|
||||
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',
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetId) return null;
|
||||
if (this.activeQuest && this.activeQuest.id === targetId) return null;
|
||||
// Prevent duplicate active
|
||||
if (this.activeQuests.includes(questId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const q = this.questDB[targetId];
|
||||
if (q.giver === npcType) return q;
|
||||
quest.isActive = true;
|
||||
quest.startTime = Date.now();
|
||||
this.activeQuests.push(questId);
|
||||
|
||||
return null;
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
startQuest(id) {
|
||||
if (this.completedQuests.includes(id)) return;
|
||||
updateObjective(questId, objectiveId, progress) {
|
||||
const quest = this.quests.get(questId);
|
||||
if (!quest || !quest.isActive) return;
|
||||
|
||||
const template = this.questDB[id];
|
||||
if (!template) return;
|
||||
const objective = quest.objectives.find(obj => obj.id === objectiveId);
|
||||
if (!objective) return;
|
||||
|
||||
this.activeQuest = JSON.parse(JSON.stringify(template));
|
||||
console.log(`📜 Quest Started: ${this.activeQuest.title}`);
|
||||
// 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();
|
||||
}
|
||||
|
||||
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
|
||||
this.scene.events.emit('show-floating-text', {
|
||||
x: this.scene.player.x,
|
||||
y: this.scene.player.y - 50,
|
||||
text: "Quest Accepted!",
|
||||
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!",
|
||||
text: 'Quest Complete!',
|
||||
color: '#00FF00'
|
||||
});
|
||||
|
||||
this.completedQuests.push(this.activeQuest.id);
|
||||
const next = this.activeQuest.nextQuest;
|
||||
this.activeQuest = null;
|
||||
this.updateUI();
|
||||
console.log(`🏆 Quest Complete: ${quest.title}`);
|
||||
this.scene.events.emit('quest:completed', questId);
|
||||
|
||||
if (next) {
|
||||
console.log('Next quest available at NPC.');
|
||||
// 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) {
|
||||
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