428 lines
11 KiB
JavaScript
428 lines
11 KiB
JavaScript
/**
|
|
* QuestSystemExpanded.js
|
|
* =======================
|
|
* KRVAVA ŽETEV - Enhanced Quest System for Main Campaign
|
|
*
|
|
* Extends the original QuestSystem to support:
|
|
* - Act-based quest structure
|
|
* - Location-based objectives
|
|
* - Dialogue integration
|
|
* - Event triggers
|
|
* - Quest chains
|
|
* - Bond strength rewards
|
|
*
|
|
* @author NovaFarma Team
|
|
* @date 2025-12-23
|
|
*/
|
|
|
|
class QuestSystemExpanded {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
|
|
// Load Act 1 Quest Data
|
|
this.questDB = typeof Act1QuestData !== 'undefined' ? Act1QuestData : {};
|
|
|
|
// Quest state
|
|
this.activeQuests = new Map(); // questId -> quest instance
|
|
this.completedQuests = new Set();
|
|
this.questHistory = [];
|
|
|
|
// Current main quest
|
|
this.mainQuest = null;
|
|
|
|
// UI reference
|
|
this.questUI = null;
|
|
|
|
console.log('📜 QuestSystemExpanded initialized');
|
|
console.log(`📚 Loaded ${Object.keys(this.questDB).length} quests`);
|
|
}
|
|
|
|
/**
|
|
* Start a quest
|
|
*/
|
|
startQuest(questId) {
|
|
if (this.completedQuests.has(questId)) {
|
|
console.log(`⚠️ Quest already completed: ${questId}`);
|
|
return false;
|
|
}
|
|
|
|
if (this.activeQuests.has(questId)) {
|
|
console.log(`⚠️ Quest already active: ${questId}`);
|
|
return false;
|
|
}
|
|
|
|
const questTemplate = this.questDB[questId];
|
|
if (!questTemplate) {
|
|
console.error(`❌ Quest not found: ${questId}`);
|
|
return false;
|
|
}
|
|
|
|
// Create quest instance (deep copy)
|
|
const quest = JSON.parse(JSON.stringify(questTemplate));
|
|
quest.startTime = Date.now();
|
|
quest.status = 'active';
|
|
|
|
// Add to active quests
|
|
this.activeQuests.set(questId, quest);
|
|
|
|
// If main quest, set as current
|
|
if (quest.isMainQuest) {
|
|
this.mainQuest = quest;
|
|
}
|
|
|
|
// Show start dialogue
|
|
if (quest.startDialogue) {
|
|
this.showQuestDialogue(quest.startDialogue);
|
|
}
|
|
|
|
// Trigger any start events
|
|
if (quest.triggerEvent) {
|
|
this.triggerQuestEvent(quest.triggerEvent);
|
|
}
|
|
|
|
// Notification
|
|
this.showQuestNotification({
|
|
title: 'New Quest!',
|
|
questTitle: quest.title,
|
|
description: quest.description,
|
|
icon: quest.isMainQuest ? '📖' : '📝'
|
|
});
|
|
|
|
// Update UI
|
|
this.updateUI();
|
|
|
|
console.log(`📜 Quest Started: ${quest.title} (${questId})`);
|
|
|
|
// Emit event
|
|
this.scene.events.emit('questStarted', { quest });
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Complete a quest objective
|
|
*/
|
|
completeObjective(questId, objectiveId) {
|
|
const quest = this.activeQuests.get(questId);
|
|
if (!quest) {
|
|
console.warn(`Quest not active: ${questId}`);
|
|
return false;
|
|
}
|
|
|
|
const objective = quest.objectives.find(obj => obj.id === objectiveId);
|
|
if (!objective) {
|
|
console.error(`Objective not found: ${objectiveId}`);
|
|
return false;
|
|
}
|
|
|
|
if (objective.completed) {
|
|
console.log(`Objective already complete: ${objectiveId}`);
|
|
return false;
|
|
}
|
|
|
|
objective.completed = true;
|
|
objective.completedTime = Date.now();
|
|
|
|
console.log(`✅ Objective Complete: ${objective.description}`);
|
|
|
|
// Show notification
|
|
this.showObjectiveNotification(objective.description);
|
|
|
|
// Check if all objectives complete
|
|
const allComplete = quest.objectives.every(obj => obj.completed);
|
|
if (allComplete) {
|
|
this.completeQuest(questId);
|
|
} else {
|
|
// Update UI
|
|
this.updateUI();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Update objective progress
|
|
*/
|
|
updateObjectiveProgress(questId, objectiveId, amount) {
|
|
const quest = this.activeQuests.get(questId);
|
|
if (!quest) return false;
|
|
|
|
const objective = quest.objectives.find(obj => obj.id === objectiveId);
|
|
if (!objective) return false;
|
|
|
|
objective.current = Math.min(objective.required, objective.current + amount);
|
|
|
|
// Check if objective complete
|
|
if (objective.current >= objective.required && !objective.completed) {
|
|
this.completeObjective(questId, objectiveId);
|
|
} else {
|
|
this.updateUI();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Complete a quest
|
|
*/
|
|
completeQuest(questId) {
|
|
const quest = this.activeQuests.get(questId);
|
|
if (!quest) {
|
|
console.error(`Quest not active: ${questId}`);
|
|
return false;
|
|
}
|
|
|
|
// Mark as completed
|
|
quest.status = 'completed';
|
|
quest.completeTime = Date.now();
|
|
|
|
// Show completion dialogue
|
|
if (quest.completeDialogue) {
|
|
this.showQuestDialogue(quest.completeDialogue);
|
|
}
|
|
|
|
// Grant rewards
|
|
if (quest.rewards) {
|
|
this.grantRewards(quest.rewards);
|
|
}
|
|
|
|
// Move to completed
|
|
this.activeQuests.delete(questId);
|
|
this.completedQuests.add(questId);
|
|
this.questHistory.push(quest);
|
|
|
|
// Notification
|
|
this.showQuestNotification({
|
|
title: 'Quest Complete!',
|
|
questTitle: quest.title,
|
|
description: 'Rewards granted',
|
|
icon: '🏆',
|
|
color: '#FFD700'
|
|
});
|
|
|
|
// Start next quest if exists
|
|
if (quest.nextQuest) {
|
|
setTimeout(() => {
|
|
this.startQuest(quest.nextQuest);
|
|
}, 2000); // 2 second delay
|
|
}
|
|
|
|
// If main quest, clear it
|
|
if (quest.isMainQuest) {
|
|
this.mainQuest = null;
|
|
}
|
|
|
|
console.log(`🏆 Quest Complete: ${quest.title}`);
|
|
|
|
// Emit event
|
|
this.scene.events.emit('questCompleted', { quest });
|
|
|
|
// Update UI
|
|
this.updateUI();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Grant quest rewards
|
|
*/
|
|
grantRewards(rewards) {
|
|
// XP
|
|
if (rewards.xp && this.scene.player) {
|
|
this.scene.player.addXP?.(rewards.xp);
|
|
console.log(`💫 Gained ${rewards.xp} XP`);
|
|
}
|
|
|
|
// Items
|
|
if (rewards.items && this.scene.inventorySystem) {
|
|
rewards.items.forEach(item => {
|
|
this.scene.inventorySystem.addItem(item.id, item.amount);
|
|
console.log(`📦 Received ${item.amount}x ${item.id}`);
|
|
});
|
|
}
|
|
|
|
// Bond Strength
|
|
if (rewards.bondStrength && this.scene.twinBondSystem) {
|
|
this.scene.twinBondSystem.changeBondStrength(rewards.bondStrength);
|
|
console.log(`💞 Bond Strength ${rewards.bondStrength > 0 ? '+' : ''}${rewards.bondStrength}`);
|
|
}
|
|
|
|
// Unlocks
|
|
if (rewards.unlocks) {
|
|
rewards.unlocks.forEach(unlock => {
|
|
this.unlockFeature(unlock);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unlock feature/system
|
|
*/
|
|
unlockFeature(featureId) {
|
|
console.log(`🔓 Unlocked: ${featureId}`);
|
|
|
|
switch (featureId) {
|
|
case 'twin_bond_ui':
|
|
if (this.scene.twinBondSystem) {
|
|
this.scene.twinBondSystem.createBondUI();
|
|
}
|
|
break;
|
|
|
|
case 'zombie_commands':
|
|
// Already available through ZombieSystem
|
|
break;
|
|
|
|
case 'grave_crafting':
|
|
if (this.scene.recipeSystem) {
|
|
this.scene.recipeSystem.unlockRecipe('grave');
|
|
}
|
|
break;
|
|
|
|
case 'act_2':
|
|
console.log('🎬 ACT 2 UNLOCKED!');
|
|
// TODO: Transition to Act 2
|
|
break;
|
|
}
|
|
|
|
this.scene.events.emit('featureUnlocked', { featureId });
|
|
}
|
|
|
|
/**
|
|
* Trigger quest event
|
|
*/
|
|
triggerQuestEvent(event) {
|
|
if (event.type === 'twin_bond_message' && this.scene.twinBondSystem) {
|
|
setTimeout(() => {
|
|
this.scene.twinBondSystem.showTelepathicMessage(
|
|
event.message,
|
|
event.emotion
|
|
);
|
|
}, event.delay || 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show quest dialogue
|
|
*/
|
|
showQuestDialogue(dialogue) {
|
|
// Simple text display for now
|
|
// TODO: Integrate with DialogueSystem
|
|
console.log(`💬 ${dialogue.speaker}: ${dialogue.text}`);
|
|
}
|
|
|
|
/**
|
|
* Show quest notification
|
|
*/
|
|
showQuestNotification(notification) {
|
|
const ui = this.scene.scene.get('UIScene');
|
|
if (ui && ui.showNotification) {
|
|
ui.showNotification(notification);
|
|
} else {
|
|
// Fallback
|
|
console.log(`📢 ${notification.icon} ${notification.title}: ${notification.questTitle}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show objective notification
|
|
*/
|
|
showObjectiveNotification(description) {
|
|
const ui = this.scene.scene.get('UIScene');
|
|
if (ui && ui.showFloatingText) {
|
|
ui.showFloatingText({
|
|
x: this.scene.player?.x || 400,
|
|
y: this.scene.player?.y - 50 || 300,
|
|
text: `✓ ${description}`,
|
|
color: '#00FF00'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check location objective
|
|
*/
|
|
checkLocationObjective(questId, objectiveId, playerX, playerY) {
|
|
const quest = this.activeQuests.get(questId);
|
|
if (!quest) return false;
|
|
|
|
const objective = quest.objectives.find(obj => obj.id === objectiveId);
|
|
if (!objective || objective.type !== 'location') return false;
|
|
|
|
const distance = Phaser.Math.Distance.Between(
|
|
playerX, playerY,
|
|
objective.target.x, objective.target.y
|
|
);
|
|
|
|
if (distance <= objective.target.radius) {
|
|
this.completeObjective(questId, objectiveId);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Update (check objectives)
|
|
*/
|
|
update(delta) {
|
|
// Check location objectives for active quests
|
|
if (this.scene.player) {
|
|
const playerX = this.scene.player.x;
|
|
const playerY = this.scene.player.y;
|
|
|
|
this.activeQuests.forEach((quest, questId) => {
|
|
quest.objectives.forEach(objective => {
|
|
if (objective.completed) return;
|
|
|
|
if (objective.type === 'location') {
|
|
this.checkLocationObjective(questId, objective.id, playerX, playerY);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update UI
|
|
*/
|
|
updateUI() {
|
|
const ui = this.scene.scene.get('UIScene');
|
|
if (ui && ui.updateQuestTracker) {
|
|
ui.updateQuestTracker(this.mainQuest);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Getters
|
|
*/
|
|
isQuestActive(questId) {
|
|
return this.activeQuests.has(questId);
|
|
}
|
|
|
|
isQuestComplete(questId) {
|
|
return this.completedQuests.has(questId);
|
|
}
|
|
|
|
getActiveQuests() {
|
|
return Array.from(this.activeQuests.values());
|
|
}
|
|
|
|
getMainQuest() {
|
|
return this.mainQuest;
|
|
}
|
|
|
|
getQuestProgress(questId) {
|
|
const quest = this.activeQuests.get(questId);
|
|
if (!quest) return null;
|
|
|
|
const total = quest.objectives.length;
|
|
const completed = quest.objectives.filter(obj => obj.completed).length;
|
|
|
|
return {
|
|
total,
|
|
completed,
|
|
percentage: Math.round((completed / total) * 100)
|
|
};
|
|
}
|
|
}
|