This repository has been archived on 2026-05-04. You can view files and clone it, but cannot push or open issues or pull requests.
Galaxy-Strike-Online/Client/js/systems/QuestSystem.js
2026-01-30 10:58:30 -04:00

2194 lines
89 KiB
JavaScript

/**
* Galaxy Strike Online - Quest System
* Manages hand-crafted and procedural quests
*/
class QuestSystem {
constructor(gameEngine) {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('QuestSystem.constructor', {
gameEngineProvided: !!gameEngine
});
this.game = gameEngine;
// Server time synchronization
this.serverTimeOffset = 0; // Difference between server and client time
this.lastServerTimeSync = 0;
// Quest types
this.questTypes = {
main: 'Main Story',
daily: 'Daily',
weekly: 'Weekly',
completed: 'Completed',
failed: 'Failed Quests'
};
// Quest status
this.questStatus = {
available: 'available',
active: 'active',
completed: 'completed',
failed: 'failed'
};
if (debugLogger) debugLogger.logStep('Quest system configuration initialized', {
questTypes: Object.keys(this.questTypes),
questStatus: Object.keys(this.questStatus)
});
// Main story quests - populated by server
this.mainQuests = [];
// Daily quests - populated by server
this.dailyQuests = [];
this.weeklyQuests = [];
this.activeQuests = [];
this.completedQuests = [];
this.failedQuests = [];
// Quest tracking arrays
this.selectedDailyQuests = [];
this.selectedWeeklyQuests = [];
this.completedDailyQuests = [];
this.completedWeeklyQuests = [];
// Procedural quest templates (server-driven)
this.proceduralTemplates = {};
// Quest generation settings
this.maxProceduralQuests = 3;
this.proceduralQuestRefresh = 30 * 60 * 1000; // 30 minutes
// Initialize stats
this.stats = {
questsCompleted: 0,
dailyQuestsCompleted: 0,
weeklyQuestsCompleted: 0,
totalRewardsEarned: { credits: 0, experience: 0, gems: 0 },
lastDailyReset: this.getServerTime(),
lastWeeklyReset: this.getServerTime()
};
console.log('[QUEST SYSTEM] Client quest system initialized - waiting for server data');
if (debugLogger) debugLogger.endStep('QuestSystem.constructor', {
mainQuestsCount: this.mainQuests.length,
dailyQuestsCount: this.dailyQuests.length,
weeklyQuestsCount: this.weeklyQuests.length,
maxProceduralQuests: this.maxProceduralQuests,
proceduralQuestRefresh: this.proceduralQuestRefresh,
initialStats: this.stats,
dailyQuestsInitialized: this.dailyQuests.length,
weeklyQuestsInitialized: this.weeklyQuests.length
});
// Start countdown timers for server-driven quests
console.log('[QUEST SYSTEM] Starting countdown timers');
this.startDailyCountdown();
this.startWeeklyCountdown();
// Initialize daily quests with safety check
try {
if (this.allDailyQuests && Array.isArray(this.allDailyQuests)) {
console.log('[QUEST SYSTEM] Initializing daily quests...');
this.randomizeDailyQuests();
} else {
console.warn('[QUEST SYSTEM] allDailyQuests not properly initialized, skipping daily quest initialization');
this.dailyQuests = [];
this.selectedDailyQuests = [];
}
} catch (error) {
console.error('[QUEST SYSTEM] Error initializing daily quests:', error);
// Fallback to empty arrays to prevent crash
this.dailyQuests = [];
this.selectedDailyQuests = [];
this.activeQuests = [];
}
// Initialize weekly quests with safety check
try {
if (this.allWeeklyQuests && Array.isArray(this.allWeeklyQuests)) {
console.log('[QUEST SYSTEM] Initializing weekly quests...');
this.randomizeWeeklyQuests();
} else {
console.warn('[QUEST SYSTEM] allWeeklyQuests not properly initialized, skipping weekly quest initialization');
this.weeklyQuests = [];
this.selectedWeeklyQuests = [];
}
} catch (error) {
console.error('[QUEST SYSTEM] Error initializing weekly quests:', error);
// Fallback to empty arrays to prevent crash
this.weeklyQuests = [];
this.selectedWeeklyQuests = [];
this.activeQuests = [];
}
// Procedural quest templates
this.proceduralTemplates = {
bounty: {
name: 'Bounty Hunt',
description: 'Hunt down dangerous targets in the galaxy',
objectives: [
{ id: 'defeat_targets', description: 'Defeat {target} targets', target: 5, current: 0, type: 'combat' }
],
rewards: { credits: 300, experience: 75 }
},
exploration: {
name: 'Exploration Mission',
description: 'Explore uncharted regions of space',
objectives: [
{ id: 'explore_areas', description: 'Explore {target} areas', target: 3, current: 0, type: 'exploration' }
],
rewards: { credits: 250, experience: 60 }
},
collection: {
name: 'Resource Collection',
description: 'Collect valuable resources',
objectives: [
{ id: 'collect_resources', description: 'Collect {target} resources', target: 100, current: 0, type: 'collection' }
],
rewards: { credits: 200, experience: 50 }
},
escort: {
name: 'Escort Mission',
description: 'Escort valuable cargo through dangerous space',
objectives: [
{ id: 'escort_complete', description: 'Complete escort mission', target: 1, current: 0, type: 'escort' }
],
rewards: { credits: 400, experience: 100 }
}
};
// Quest generation settings
this.maxProceduralQuests = 3;
this.proceduralQuestRefresh = 30 * 60 * 1000; // 30 minutes
// Initialize stats
this.stats = {
questsCompleted: 0,
questsFailed: 0,
dailyQuestsCompleted: 0,
weeklyQuestsCompleted: 0,
totalRewardsEarned: { credits: 0, experience: 0, gems: 0 },
lastDailyReset: this.getServerTime(),
lastWeeklyReset: this.getServerTime()
};
// Initialize daily quests
this.randomizeDailyQuests();
// Initialize weekly quests
this.randomizeWeeklyQuests();
if (debugLogger) debugLogger.endStep('QuestSystem.constructor', {
mainQuestsCount: this.mainQuests.length,
allDailyQuestsCount: this.allDailyQuests.length,
allWeeklyQuestsCount: this.allWeeklyQuests.length,
maxProceduralQuests: this.maxProceduralQuests,
proceduralQuestRefresh: this.proceduralQuestRefresh,
initialStats: this.stats,
dailyQuestsInitialized: this.dailyQuests.length,
weeklyQuestsInitialized: this.weeklyQuests.length
});
}
// Server time synchronization methods
getServerTime() {
// In multiplayer mode, use UTC time as server time
if (window.smartSaveManager?.isMultiplayer) {
// Get current UTC timestamp
const utcTime = Date.now();
console.log('[QUEST SYSTEM] Using UTC time as server time:', utcTime);
console.log('[QUEST SYSTEM] Local time:', Date.now());
return utcTime;
}
// Fallback to client time in singleplayer
return Date.now();
}
getServerDate() {
// Create a date that displays in UTC
const timestamp = this.getServerTime();
const utcDate = new Date(timestamp);
// Force UTC display by using UTC methods
return {
getTime: () => timestamp,
getDay: () => utcDate.getUTCDay(),
getHours: () => utcDate.getUTCHours(),
getMinutes: () => utcDate.getUTCMinutes(),
getSeconds: () => utcDate.getUTCSeconds(),
getDate: () => utcDate.getUTCDate(),
getFullYear: () => utcDate.getUTCFullYear(),
getMonth: () => utcDate.getUTCMonth(),
toString: () => utcDate.toUTCString(),
valueOf: () => timestamp
};
}
async initialize() {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('QuestSystem.initialize', {
mainQuestsCount: this.mainQuests.length,
activeQuestsCount: this.activeQuests.length,
dailyQuestsCount: this.dailyQuests.length
});
// Initialize main quests
if (debugLogger) debugLogger.logStep('Initializing main quests');
this.initializeMainQuests();
// Check for daily reset
if (debugLogger) debugLogger.logStep('Checking for daily reset');
this.checkDailyReset();
// Check for weekly reset
if (debugLogger) debugLogger.logStep('Checking for weekly reset');
this.checkWeeklyReset();
// Start daily countdown timer
if (debugLogger) debugLogger.logStep('Starting daily countdown timer');
this.startDailyCountdown();
// Start weekly countdown timer
if (debugLogger) debugLogger.logStep('Starting weekly countdown timer');
this.startWeeklyCountdown();
// Update UI
this.updateQuestList();
if (debugLogger) debugLogger.endStep('QuestSystem.initialize', {
mainQuestsInitialized: true,
dailyResetChecked: true,
weeklyResetChecked: true,
countdownStarted: true,
weeklyQuestsInitialized: true,
uiUpdated: true
});
}
initializeMainQuests() {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('QuestSystem.initializeMainQuests', {
mainQuestsCount: this.mainQuests.length
});
// Set all quests to locked initially, then unlock based on requirements
this.mainQuests.forEach(quest => {
quest.status = 'locked';
});
if (debugLogger) debugLogger.logStep('All main quests set to locked status');
// Unlock first quest
if (this.mainQuests.length > 0) {
const firstQuest = this.mainQuests[0];
firstQuest.status = 'available';
if (debugLogger) debugLogger.logStep('First quest unlocked', {
questId: firstQuest.id,
questName: firstQuest.name
});
}
// Check quest availability based on requirements
if (debugLogger) debugLogger.logStep('Checking quest availability based on requirements');
this.checkQuestAvailability();
if (debugLogger) debugLogger.endStep('QuestSystem.initializeMainQuests', {
questsProcessed: this.mainQuests.length,
availableQuests: this.mainQuests.filter(q => q.status === 'available').length,
lockedQuests: this.mainQuests.filter(q => q.status === 'locked').length
});
}
checkQuestAvailability() {
const debugLogger = window.debugLogger;
const player = this.game.systems.player;
if (debugLogger) debugLogger.startStep('QuestSystem.checkQuestAvailability', {
playerLevel: player.stats.level,
mainQuestsCount: this.mainQuests.length
});
const availabilityChanges = [];
this.mainQuests.forEach(quest => {
const oldStatus = quest.status;
const requirementsMet = this.checkQuestRequirements(quest);
if (quest.status === 'locked' && requirementsMet) {
quest.status = 'available';
availabilityChanges.push({
questId: quest.id,
questName: quest.name,
oldStatus: oldStatus,
newStatus: 'available',
reason: 'Requirements met'
});
this.game.showNotification(`New quest available: ${quest.name}`, 'info', 5000);
} else if (quest.status === 'available' && !requirementsMet) {
// Hide quests that were available but no longer meet requirements
quest.status = 'locked';
availabilityChanges.push({
questId: quest.id,
questName: quest.name,
oldStatus: oldStatus,
newStatus: 'locked',
reason: 'Requirements no longer met'
});
}
});
if (debugLogger) debugLogger.endStep('QuestSystem.checkQuestAvailability', {
availabilityChanges: availabilityChanges,
questsProcessed: this.mainQuests.length,
availableQuests: this.mainQuests.filter(q => q.status === 'available').length,
lockedQuests: this.mainQuests.filter(q => q.status === 'locked').length
});
}
// Quest management
startQuest(questId) {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('QuestSystem.startQuest', {
questId: questId,
currentActiveQuests: this.activeQuests.length
});
const quest = this.findQuest(questId);
if (!quest) {
console.log('Quest not found:', questId);
if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
success: false,
reason: 'Quest not found',
questId: questId
});
return false;
}
console.log('Attempting to start quest:', questId, 'status:', quest.status);
if (debugLogger) debugLogger.logStep('Quest found', {
questId: quest.id,
questName: quest.name,
questType: quest.type,
currentStatus: quest.status,
requirements: quest.requirements
});
if (quest.status !== 'available') {
console.log('Quest not available, status:', quest.status);
this.game.showNotification('Quest is not available', 'error', 3000);
if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
success: false,
reason: 'Quest not available',
questId: questId,
questName: quest.name,
currentStatus: quest.status
});
return false;
}
// Check requirements
const requirementsMet = this.checkQuestRequirements(quest);
console.log('Requirements met:', requirementsMet, 'for quest:', questId);
if (debugLogger) debugLogger.logStep('Requirements check', {
requirements: quest.requirements,
requirementsMet: requirementsMet
});
if (!requirementsMet) {
this.game.showNotification('Requirements not met', 'error', 3000);
if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
success: false,
reason: 'Requirements not met',
questId: questId,
questName: quest.name,
requirements: quest.requirements
});
return false;
}
console.log('Before status change - quest status:', quest.status);
quest.status = 'active';
console.log('After status change - quest status:', quest.status);
// Also update the quest in the main quests array to ensure consistency
const mainQuest = this.mainQuests.find(q => q.id === questId);
if (mainQuest) {
mainQuest.status = 'active';
console.log('Updated mainQuest status to:', mainQuest.status);
if (debugLogger) debugLogger.logStep('Main quest status updated', {
questId: mainQuest.id,
newStatus: mainQuest.status
});
}
this.activeQuests.push(quest);
// Check initial progress for existing player stats
if (debugLogger) debugLogger.logStep('Checking initial quest progress');
this.checkInitialQuestProgress(quest);
// Update the UI to reflect the status change
this.updateQuestList();
this.game.showNotification(`Quest started: ${quest.name}`, 'success', 3000);
if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
success: true,
questId: questId,
questName: quest.name,
questType: quest.type,
objectives: quest.objectives.map(obj => ({
id: obj.id,
description: obj.description,
target: obj.target,
current: obj.current
})),
activeQuestsCount: this.activeQuests.length
});
return true;
}
completeQuest(questId) {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('QuestSystem.completeQuest', {
questId: questId,
currentActiveQuests: this.activeQuests.length,
completedQuestsCount: this.completedQuests.length
});
const quest = this.findQuest(questId);
if (!quest) {
if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
success: false,
reason: 'Quest not found',
questId: questId
});
return false;
}
if (debugLogger) debugLogger.logStep('Quest found for completion', {
questId: quest.id,
questName: quest.name,
questType: quest.type,
currentStatus: quest.status,
objectives: quest.objectives.map(obj => ({
id: obj.id,
description: obj.description,
target: obj.target,
current: obj.current,
completed: obj.current >= obj.target
}))
});
if (quest.status !== 'active') {
this.game.showNotification('Quest is not active', 'error', 3000);
if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
success: false,
reason: 'Quest not active',
questId: questId,
questName: quest.name,
currentStatus: quest.status
});
return false;
}
// Check if all objectives are completed
const allObjectivesComplete = quest.objectives.every(obj => obj.current >= obj.target);
if (!allObjectivesComplete) {
this.game.showNotification('Not all objectives completed', 'warning', 3000);
if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
success: false,
reason: 'Not all objectives completed',
questId: questId,
questName: quest.name,
objectives: quest.objectives.map(obj => ({
id: obj.id,
current: obj.current,
target: obj.target,
completed: obj.current >= obj.target
}))
});
return false;
}
// Complete quest
quest.status = 'completed';
quest.completedAt = this.getServerTime();
this.completedQuests.push(quest);
if (debugLogger) debugLogger.logStep('Quest marked as completed', {
questId: quest.id,
questName: quest.name,
completedAt: quest.completedAt,
rewards: quest.rewards
});
// Save completed daily quests to history
if (quest.type === 'daily') {
const questCopy = { ...quest, completedAt: this.getServerTime() };
this.completedDailyQuests.push(questCopy);
if (debugLogger) debugLogger.logStep('Daily quest added to history', {
questId: quest.id,
questName: quest.name,
completedDailyQuestsCount: this.completedDailyQuests.length
});
}
// Save completed weekly quests to history
if (quest.type === 'weekly') {
const questCopy = { ...quest, completedAt: this.getServerTime() };
this.completedWeeklyQuests.push(questCopy);
if (debugLogger) debugLogger.logStep('Weekly quest added to history', {
questId: quest.id,
questName: quest.name,
completedWeeklyQuestsCount: this.completedWeeklyQuests.length
});
}
// Remove from active quests
const activeIndex = this.activeQuests.findIndex(q => q.id === questId);
if (activeIndex !== -1) {
this.activeQuests.splice(activeIndex, 1);
if (debugLogger) debugLogger.logStep('Quest removed from active list', {
questId: questId,
removedIndex: activeIndex,
remainingActiveQuests: this.activeQuests.length
});
}
// Give rewards
if (debugLogger) debugLogger.logStep('Giving quest rewards');
this.giveQuestRewards(quest);
// Update statistics
const oldStats = { ...this.stats };
this.stats.questsCompleted++;
if (quest.type === 'daily') {
this.stats.dailyQuestsCompleted++;
}
if (quest.type === 'weekly') {
this.stats.weeklyQuestsCompleted++;
}
if (debugLogger) debugLogger.logStep('Quest statistics updated', {
oldStats: oldStats,
newStats: this.stats,
questsCompletedIncrement: this.stats.questsCompleted - oldStats.questsCompleted,
dailyQuestsCompletedIncrement: this.stats.dailyQuestsCompleted - oldStats.dailyQuestsCompleted,
weeklyQuestsCompletedIncrement: this.stats.weeklyQuestsCompleted - oldStats.weeklyQuestsCompleted
});
// Unlock next quest if it's a main quest
if (quest.nextQuest) {
if (debugLogger) debugLogger.logStep('Unlocking next quest', {
nextQuestId: quest.nextQuest
});
this.unlockNextQuest(quest.nextQuest);
}
// Check for other quests that might now be available
if (debugLogger) debugLogger.logStep('Checking for newly available quests');
this.checkQuestAvailability();
this.game.showNotification(`Quest completed: ${quest.name}!`, 'success', 5000);
if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
success: true,
questId: questId,
questName: quest.name,
questType: quest.type,
rewardsGiven: quest.rewards,
nextQuestUnlocked: quest.nextQuest || null,
finalActiveQuestsCount: this.activeQuests.length,
completedQuestsCount: this.completedQuests.length
});
return true;
}
giveQuestRewards(quest) {
const debugLogger = window.debugLogger;
if (quest.rewards.credits) {
this.game.systems.economy.addCredits(quest.rewards.credits, 'quest');
this.stats.totalRewardsEarned.credits += quest.rewards.credits;
rewardsGiven.credits = quest.rewards.credits;
if (debugLogger) debugLogger.logStep('Credits reward given', {
amount: quest.rewards.credits,
oldCredits: oldPlayerStats.credits,
newCredits: this.game.systems.economy.credits
});
}
if (quest.rewards.experience) {
this.game.systems.player.addExperience(quest.rewards.experience);
this.stats.totalRewardsEarned.experience += quest.rewards.experience;
rewardsGiven.experience = quest.rewards.experience;
if (debugLogger) debugLogger.logStep('Experience reward given', {
amount: quest.rewards.experience,
oldExperience: oldPlayerStats.experience,
newExperience: this.game.systems.player.stats.experience,
oldLevel: oldPlayerStats.level,
newLevel: this.game.systems.player.stats.level,
leveledUp: this.game.systems.player.stats.level > oldPlayerStats.level
});
}
if (quest.rewards.gems) {
this.game.systems.economy.addGems(quest.rewards.gems, 'quest');
this.stats.totalRewardsEarned.gems += quest.rewards.gems;
rewardsGiven.gems = quest.rewards.gems;
if (debugLogger) debugLogger.logStep('Gems reward given', {
amount: quest.rewards.gems,
oldGems: oldPlayerStats.gems,
newGems: this.game.systems.economy.gems
});
}
if (quest.rewards.item) {
const item = this.game.systems.inventory.generateItem('weapon', 'legendary');
this.game.systems.inventory.addItem(item);
rewardsGiven.item = item;
if (debugLogger) debugLogger.logStep('Item reward given', {
itemGenerated: item,
itemType: item.type,
itemRarity: item.rarity,
itemName: item.name
});
}
if (debugLogger) debugLogger.endStep('QuestSystem.giveQuestRewards', {
questId: quest.id,
questName: quest.name,
rewardsGiven: rewardsGiven,
playerChanges: {
credits: { old: oldPlayerStats.credits, new: this.game.systems.economy.credits },
gems: { old: oldPlayerStats.gems, new: this.game.systems.economy.gems },
experience: { old: oldPlayerStats.experience, new: this.game.systems.player.stats.experience },
level: { old: oldPlayerStats.level, new: this.game.systems.player.stats.level }
},
totalRewardsEarnedUpdated: this.stats.totalRewardsEarned
});
}
// Objective progress
updateObjectiveProgress(type, amount, context = {}) {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('QuestSystem.updateObjectiveProgress', {
type: type,
amount: amount,
context: context,
activeQuestsCount: this.activeQuests.length
});
const progressUpdates = [];
// Update all active quests
this.activeQuests.forEach(quest => {
const questProgress = [];
quest.objectives.forEach(objective => {
if (objective.type === type && objective.current < objective.target) {
const oldProgress = objective.current;
objective.current = Math.min(objective.current + amount, objective.target);
const actualProgress = objective.current - oldProgress;
if (actualProgress > 0) {
questProgress.push({
objectiveId: objective.id,
description: objective.description,
oldProgress: oldProgress,
newProgress: objective.current,
target: objective.target,
progressMade: actualProgress,
completed: objective.current >= objective.target
});
}
}
});
if (questProgress.length > 0) {
progressUpdates.push({
questId: quest.id,
questName: quest.name,
questType: quest.type,
objectivesUpdated: questProgress
});
// Check if quest is completed
if (quest.objectives.every(obj => obj.current >= obj.target)) {
this.game.showNotification(`Quest ready to turn in: ${quest.name}`, 'success', 4000);
if (debugLogger) debugLogger.logStep('Quest completed via progress update', {
questId: quest.id,
questName: quest.name,
allObjectivesCompleted: true
});
}
}
});
if (debugLogger) debugLogger.endStep('QuestSystem.updateObjectiveProgress', {
type: type,
amount: amount,
progressUpdates: progressUpdates,
questsUpdated: progressUpdates.length
});
}
// Event listeners for quest progress
onDungeonCompleted() {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.log('QuestSystem.onDungeonCompleted triggered', {
activeQuestsCount: this.activeQuests.length
});
this.updateObjectiveProgress('dungeon', 1);
}
onEnemyDefeated() {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.log('QuestSystem.onEnemyDefeated triggered', {
activeQuestsCount: this.activeQuests.length
});
this.updateObjectiveProgress('combat', 1);
}
onLevelUp(newLevel) {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.log('QuestSystem.onLevelUp triggered', {
newLevel: newLevel,
activeQuestsCount: this.activeQuests.length
});
this.updateObjectiveProgress('level', 1);
}
onItemCrafted() {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.log('QuestSystem.onItemCrafted triggered', {
activeQuestsCount: this.activeQuests.length
});
this.updateObjectiveProgress('crafting', 1);
}
// Procedural quest generation (deprecated - replaced by weekly quests)
/*
generateProceduralQuests() {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('QuestSystem.generateProceduralQuests', {
currentActiveQuests: this.activeQuests.length,
maxProceduralQuests: this.maxProceduralQuests,
proceduralTemplatesCount: Object.keys(this.proceduralTemplates).length,
proceduralQuestRefresh: this.proceduralQuestRefresh
});
// Clear existing procedural quests
const oldProceduralQuests = this.activeQuests.filter(q => q.type === 'procedural');
this.activeQuests = this.activeQuests.filter(q => q.type !== 'procedural');
if (debugLogger) debugLogger.logStep('Cleared existing procedural quests', {
oldProceduralQuestsCount: oldProceduralQuests.length,
oldProceduralQuests: oldProceduralQuests.map(q => ({ id: q.id, name: q.name }))
});
// Generate new procedural quests
const templates = Object.keys(this.proceduralTemplates);
const questCount = Math.min(this.maxProceduralQuests, templates.length);
const generatedQuests = [];
for (let i = 0; i < questCount; i++) {
const templateKey = templates[Math.floor(Math.random() * templates.length)];
const template = this.proceduralTemplates[templateKey];
const quest = {
id: `procedural_${Date.now()}_${i}`,
name: template.name,
description: template.description.replace('{target}', template.objectives[0].target),
type: 'procedural',
status: 'available',
objectives: template.objectives.map(obj => ({ ...obj })),
rewards: { ...template.rewards },
expiresAt: Date.now() + this.proceduralQuestRefresh
};
this.activeQuests.push(quest);
generatedQuests.push(quest);
if (debugLogger) debugLogger.logStep('Procedural quest generated', {
templateKey: templateKey,
questId: quest.id,
questName: quest.name,
questDescription: quest.description,
objectives: quest.objectives,
rewards: quest.rewards,
expiresAt: quest.expiresAt
});
}
// Schedule next refresh
setTimeout(() => this.generateProceduralQuests(), this.proceduralQuestRefresh);
if (debugLogger) debugLogger.endStep('QuestSystem.generateProceduralQuests', {
oldProceduralQuestsRemoved: oldProceduralQuests.length,
newProceduralQuestsGenerated: generatedQuests.length,
generatedQuests: generatedQuests.map(q => ({ id: q.id, name: q.name, type: q.type })),
nextRefreshScheduled: true,
refreshInterval: this.proceduralQuestRefresh
});
}
*/
randomizeDailyQuests() {
const debugLogger = window.debugLogger;
console.log('[QUEST SYSTEM] randomizeDailyQuests called');
// Safety check for allDailyQuests
if (!this.allDailyQuests || !Array.isArray(this.allDailyQuests)) {
console.error('[QUEST SYSTEM] allDailyQuests is not available or not an array');
return;
}
console.log(`[QUEST SYSTEM] allDailyQuests count: ${this.allDailyQuests.length}`);
if (debugLogger) debugLogger.startStep('QuestSystem.randomizeDailyQuests', {
allDailyQuestsCount: this.allDailyQuests.length,
currentDailyQuestsCount: this.dailyQuests.length,
currentSelectedDailyQuestsCount: this.selectedDailyQuests.length
});
// Clear current daily quests
this.dailyQuests = [];
this.selectedDailyQuests = [];
if (debugLogger) debugLogger.logStep('Cleared current daily quests');
// Select 3 random quests from allDailyQuests
const shuffled = [...this.allDailyQuests].sort(() => Math.random() - 0.5);
this.selectedDailyQuests = shuffled.slice(0, 3);
console.log(`[QUEST SYSTEM] Selected ${this.selectedDailyQuests.length} random daily quests`);
if (debugLogger) debugLogger.logStep('Selected random daily quests', {
selectedQuests: this.selectedDailyQuests.map(q => ({
id: q.id,
name: q.name,
difficulty: q.difficulty
}))
});
// Create deep copies for active quests and automatically start them
this.selectedDailyQuests.forEach(questTemplate => {
try {
const quest = {
...questTemplate,
id: `${questTemplate.id}_${Date.now()}`,
status: 'active', // Auto-start daily quests
objectives: questTemplate.objectives.map(obj => ({ ...obj, current: 0 }))
};
this.dailyQuests.push(quest);
this.activeQuests.push(quest); // Add to active quests for progress tracking
console.log(`[QUEST SYSTEM] Created daily quest: ${quest.id}, name: ${quest.name}, status: ${quest.status}`);
} catch (error) {
console.error('[QUEST SYSTEM] Error creating daily quest from template:', error);
}
});
if (debugLogger) debugLogger.endStep('QuestSystem.randomizeDailyQuests', {
dailyQuestsCreated: this.dailyQuests.length,
activeQuestsCount: this.activeQuests.length,
selectedDailyQuestsCount: this.selectedDailyQuests.length
});
}
// Daily reset
reset() {
const debugLogger = window.debugLogger;
const oldState = {
activeQuestsCount: this.activeQuests.length,
completedQuestsCount: this.completedQuests.length,
dailyQuestsCount: this.dailyQuests.length,
selectedDailyQuestsCount: this.selectedDailyQuests.length
};
if (debugLogger) debugLogger.startStep('QuestSystem.reset', {
oldState: oldState
});
this.activeQuests = [];
this.completedQuests = [];
this.dailyQuests = [];
this.selectedDailyQuests = [];
this.lastDailyReset = this.getServerTime();
// Reset main quest statuses
this.mainQuests.forEach(quest => {
quest.status = quest.id === 'tutorial_complete' ? 'available' : 'locked';
});
if (debugLogger) debugLogger.endStep('QuestSystem.reset', {
oldState: oldState,
newState: {
activeQuestsCount: this.activeQuests.length,
completedQuestsCount: this.completedQuests.length,
dailyQuestsCount: this.dailyQuests.length,
selectedDailyQuestsCount: this.selectedDailyQuests.length,
mainQuestsReset: true
}
});
}
clear() {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('QuestSystem.clear');
this.reset();
if (debugLogger) debugLogger.endStep('QuestSystem.clear', {
resetCompleted: true
});
}
checkDailyReset() {
const debugLogger = window.debugLogger;
const now = this.getServerTime();
const lastReset = this.lastDailyReset;
const daysSinceReset = Math.floor((now - lastReset) / (24 * 60 * 60 * 1000));
if (debugLogger) debugLogger.startStep('QuestSystem.checkDailyReset', {
now: now,
lastReset: lastReset,
daysSinceReset: daysSinceReset,
threshold: 1
});
if (daysSinceReset >= 1) {
if (debugLogger) debugLogger.logStep('Daily reset triggered', {
daysSinceReset: daysSinceReset
});
this.resetDailyQuests();
this.stats.lastDailyReset = now;
this.stats.dailyQuestsCompleted = 0;
this.game.showNotification('Daily quests refreshed!', 'success', 4000);
if (debugLogger) debugLogger.logStep('Daily reset completed', {
newLastDailyReset: this.stats.lastDailyReset,
dailyQuestsCompletedReset: this.stats.dailyQuestsCompleted
});
}
// Remove only COMPLETED daily quests from active list, not all daily quests
const oldActiveQuestsCount = this.activeQuests.length;
this.activeQuests = this.activeQuests.filter(q => q.type !== 'daily' || q.status !== 'completed');
const removedDailyQuests = oldActiveQuestsCount - this.activeQuests.length;
if (debugLogger) debugLogger.endStep('QuestSystem.checkDailyReset', {
resetTriggered: daysSinceReset >= 1,
removedDailyQuests: removedDailyQuests,
finalActiveQuestsCount: this.activeQuests.length
});
}
resetDailyQuests() {
// Remove old daily quests from active list first
this.activeQuests = this.activeQuests.filter(q => q.type !== 'daily');
// Generate new random daily quests
this.randomizeDailyQuests();
}
startDailyCountdown() {
console.log('[QUEST SYSTEM] Starting daily countdown timer');
// Update countdown immediately
this.updateDailyCountdown();
// Only start timer if in multiplayer mode or game is actively running
const shouldStartTimer = window.smartSaveManager?.isMultiplayer || this.game?.isRunning;
if (shouldStartTimer) {
// Update every second
this.dailyCountdownInterval = setInterval(() => {
this.updateDailyCountdown();
}, 1000);
console.log('[QUEST SYSTEM] Daily countdown timer started with interval:', this.dailyCountdownInterval);
} else {
console.log('[QUEST SYSTEM] Skipping daily countdown timer - not in multiplayer mode');
}
}
updateDailyCountdown() {
// Always update countdown so it's ready when user switches to daily tab
const now = this.getServerDate();
const tomorrow = new Date();
tomorrow.setUTCDate(now.getDate() + 1);
tomorrow.setUTCHours(0, 0, 0, 0); // Set to midnight UTC
const timeUntilReset = tomorrow - now;
const hours = Math.floor(timeUntilReset / (1000 * 60 * 60));
const minutes = Math.floor((timeUntilReset % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((timeUntilReset % (1000 * 60)) / 1000);
// Update countdown display
const countdownElement = document.getElementById('dailyCountdown');
if (countdownElement) {
countdownElement.textContent = `Daily quests reset in: ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
console.log('[QUEST SYSTEM] Daily countdown updated:', countdownElement.textContent);
} else {
console.log('[QUEST SYSTEM] Daily countdown element not found');
}
}
// Weekly quest management functions
randomizeWeeklyQuests() {
const debugLogger = window.debugLogger;
console.log('[QUEST SYSTEM] randomizeWeeklyQuests called');
// Safety check for allWeeklyQuests
if (!this.allWeeklyQuests || !Array.isArray(this.allWeeklyQuests)) {
console.error('[QUEST SYSTEM] allWeeklyQuests not properly initialized');
this.weeklyQuests = [];
this.selectedWeeklyQuests = [];
return;
}
console.log(`[QUEST SYSTEM] allWeeklyQuests count: ${this.allWeeklyQuests.length}`);
if (debugLogger) debugLogger.startStep('QuestSystem.randomizeWeeklyQuests', {
allWeeklyQuestsCount: this.allWeeklyQuests.length,
currentWeeklyQuestsCount: this.weeklyQuests.length,
currentSelectedWeeklyQuestsCount: this.selectedWeeklyQuests.length
});
// Clear existing weekly quests from active quests
this.activeQuests = this.activeQuests.filter(q => q.type !== 'weekly');
// Select 5 random weekly quests
const shuffled = [...this.allWeeklyQuests].sort(() => Math.random() - 0.5);
this.selectedWeeklyQuests = shuffled.slice(0, 5);
this.weeklyQuests = this.selectedWeeklyQuests.map(quest => ({ ...quest }));
// Add weekly quests to active quests
this.weeklyQuests.forEach(quest => {
if (quest.status === 'available') {
this.activeQuests.push(quest);
}
});
console.log(`[QUEST SYSTEM] Created ${this.weeklyQuests.length} weekly quests`);
if (debugLogger) debugLogger.endStep('QuestSystem.randomizeWeeklyQuests', {
weeklyQuestsCreated: this.weeklyQuests.length,
activeQuestsCount: this.activeQuests.length,
selectedWeeklyQuestsCount: this.selectedWeeklyQuests.length
});
}
checkWeeklyReset() {
const debugLogger = window.debugLogger;
const now = this.getServerTime();
const lastReset = this.lastWeeklyReset || 0;
// Calculate if we need to reset based on Saturday midnight (server time)
const currentDateTime = this.getServerDate();
const dayOfWeek = currentDateTime.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
const currentHour = currentDateTime.getHours();
const currentMinute = currentDateTime.getMinutes();
const currentSecond = currentDateTime.getSeconds();
let shouldReset = false;
let resetTime;
if (dayOfWeek === 6) { // Today is Saturday
resetTime = new Date(currentDateTime);
resetTime.setHours(0, 0, 0, 0); // Set to midnight
// Reset at exactly midnight on Saturday
if (currentHour === 0 && currentMinute === 0 && currentSecond === 0 && lastReset < resetTime.getTime()) {
shouldReset = true;
}
} else {
// Calculate last Saturday's reset time (midnight)
const daysSinceLastSaturday = (dayOfWeek + 1) % 7 || 7;
const lastSaturday = new Date(currentDateTime);
lastSaturday.setDate(currentDateTime.getDate() - daysSinceLastSaturday);
lastSaturday.setHours(0, 0, 0, 0);
resetTime = lastSaturday;
if (lastReset < resetTime.getTime()) {
shouldReset = true;
}
}
if (debugLogger) debugLogger.startStep('QuestSystem.checkWeeklyReset', {
now: now,
lastReset: lastReset,
currentDayOfWeek: dayOfWeek,
currentHour: currentHour,
currentMinute: currentMinute,
resetTime: resetTime ? resetTime.getTime() : null,
shouldReset: shouldReset
});
if (shouldReset) {
console.log('[QUEST SYSTEM] Weekly reset triggered');
// Reset weekly quests
this.lastWeeklyReset = now;
// Remove completed weekly quests from active quests
const oldActiveQuestsCount = this.activeQuests.length;
this.activeQuests = this.activeQuests.filter(q => q.type !== 'weekly' || q.status !== 'completed');
const removedWeeklyQuests = oldActiveQuestsCount - this.activeQuests.length;
if (debugLogger) debugLogger.endStep('QuestSystem.checkWeeklyReset', {
resetTriggered: true,
removedWeeklyQuests: removedWeeklyQuests,
finalActiveQuestsCount: this.activeQuests.length
});
// Clear weekly quest history and generate new ones
this.completedWeeklyQuests = [];
this.activeQuests = this.activeQuests.filter(q => q.type !== 'weekly');
// Generate new random weekly quests
this.randomizeWeeklyQuests();
// Update UI to show new weekly quests
this.updateQuestList();
// Show notification to player
this.game.showNotification('Weekly quests have been reset! New quests available.', 'info', 5000);
}
}
startWeeklyCountdown() {
console.log('[QUEST SYSTEM] Starting weekly countdown timer');
// Update countdown immediately
this.updateWeeklyCountdown();
// Only start timer if in multiplayer mode or game is actively running
const shouldStartTimer = window.smartSaveManager?.isMultiplayer || this.game?.isRunning;
if (shouldStartTimer) {
// Update every minute (weekly changes less frequently)
this.weeklyCountdownInterval = setInterval(() => {
this.updateWeeklyCountdown();
// Check for weekly reset every minute
this.checkWeeklyReset();
}, 60000); // 1 minute
console.log('[QUEST SYSTEM] Weekly countdown timer started with interval:', this.weeklyCountdownInterval);
} else {
console.log('[QUEST SYSTEM] Skipping weekly countdown timer - not in multiplayer mode');
}
}
updateWeeklyCountdown() {
const now = this.getServerDate();
const dayOfWeek = now.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
const currentHour = now.getHours();
const currentMinute = now.getMinutes();
const currentSecond = now.getSeconds();
console.log('[QUEST SYSTEM] Weekly countdown debug:', {
now: now.toString(),
dayOfWeek: dayOfWeek,
dayName: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][dayOfWeek],
currentHour: currentHour,
currentMinute: currentMinute,
currentSecond: currentSecond
});
// Calculate days until Saturday midnight (00:00:00)
let daysUntilSaturday;
if (dayOfWeek === 6) { // Today is Saturday
// Check if it's before midnight
if (now.getHours() < 0 || (now.getHours() === 0 && now.getMinutes() === 0 && now.getSeconds() === 0)) {
daysUntilSaturday = 0;
} else {
// It's after midnight on Saturday, next reset is next Saturday
daysUntilSaturday = 7;
}
} else {
// Simple calculation: days until Saturday = 6 - current day
// Sunday (0) -> 6 days, Monday (1) -> 5 days, ..., Friday (5) -> 1 day
daysUntilSaturday = 6 - dayOfWeek;
if (daysUntilSaturday <= 0) daysUntilSaturday += 7; // Ensure positive
}
console.log('[QUEST SYSTEM] Days until Saturday:', daysUntilSaturday);
// Create the target reset time (Saturday midnight UTC)
const nextSaturday = new Date();
nextSaturday.setUTCDate(now.getDate() + daysUntilSaturday);
nextSaturday.setUTCHours(0, 0, 0, 0); // Set to midnight UTC
console.log('[QUEST SYSTEM] Next Saturday reset time:', nextSaturday.toUTCString());
let timeUntilReset = nextSaturday.getTime() - now.getTime();
// Ensure timeUntilReset is positive (handle edge cases)
if (timeUntilReset <= 0) {
console.log('[QUEST SYSTEM] Time until reset is negative or zero, adding 7 days');
nextSaturday.setUTCDate(nextSaturday.getDate() + 7);
timeUntilReset = nextSaturday.getTime() - now.getTime();
}
console.log('[QUEST SYSTEM] Time until reset (ms):', timeUntilReset);
// Convert to days, hours, minutes, seconds
const totalSeconds = Math.floor(timeUntilReset / 1000);
const days = Math.floor(totalSeconds / (24 * 60 * 60));
const hours = Math.floor((totalSeconds % (24 * 60 * 60)) / (60 * 60));
const minutes = Math.floor((totalSeconds % (60 * 60)) / 60);
const seconds = totalSeconds % 60;
console.log('[QUEST SYSTEM] Time breakdown:', {
timeUntilReset: timeUntilReset,
totalSeconds: totalSeconds,
days: days,
hours: hours,
minutes: minutes,
seconds: seconds,
daysUntilSaturday: daysUntilSaturday,
nextSaturdayDate: nextSaturday.toString(),
currentDate: now.toString()
});
// Update countdown display
const countdownElement = document.getElementById('weeklyCountdown');
if (countdownElement) {
// Use the calculated days from timeUntilReset, not daysUntilSaturday
if (days > 0) {
countdownElement.textContent = `Weekly quests reset in: ${days}d ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
} else if (hours > 0) {
countdownElement.textContent = `Weekly quests reset in: ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
} else {
countdownElement.textContent = `Weekly quests reset in: ${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
console.log('[QUEST SYSTEM] Weekly countdown updated:', countdownElement.textContent, `(${days}d ${hours}h ${minutes}m ${seconds}s)`);
} else {
console.log('[QUEST SYSTEM] Weekly countdown element not found');
}
}
// ... (rest of the code remains the same)
// Quest requirements and unlocking
checkQuestRequirements(quest) {
console.log('Checking requirements for quest:', quest.id, quest.requirements);
if (!quest.requirements) {
console.log('No requirements, returning true');
return true;
}
const player = this.game.systems.player;
const dungeonSystem = this.game.systems.dungeonSystem;
if (quest.requirements.level) {
console.log('Level requirement:', quest.requirements.level, 'Player level:', player.stats.level);
if (player.stats.level < quest.requirements.level) {
console.log('Level requirement not met');
return false;
}
}
if (quest.requirements.quest) {
const requiredQuest = this.findQuest(quest.requirements.quest);
console.log('Required quest:', quest.requirements.quest, 'Found:', requiredQuest ? requiredQuest.id : 'null', 'Status:', requiredQuest ? requiredQuest.status : 'null');
if (!requiredQuest || requiredQuest.status !== 'completed') {
console.log('Quest requirement not met');
return false;
}
}
if (quest.requirements.guild) {
// Check if player is in a guild
if (!player.stats.guildJoined) {
return false;
}
}
if (quest.requirements.dungeons) {
const dungeonsCleared = player.stats.dungeonsCleared || 0;
if (dungeonsCleared < quest.requirements.dungeons) {
return false;
}
}
if (quest.requirements.upgrades) {
// Check ship upgrade requirements
const upgradesCompleted = this.checkUpgradeProgress(quest);
if (upgradesCompleted < quest.requirements.upgrades) {
return false;
}
}
return true;
}
checkUpgradeProgress(quest) {
const player = this.game.systems.player;
let upgradesCompleted = 0;
quest.objectives.forEach(objective => {
if (objective.type === 'upgrade') {
if (player.stats[`upgraded_${objective.id}`]) {
upgradesCompleted++;
}
}
});
return upgradesCompleted;
}
getMaxSkillLevel(player) {
// Get the highest skill level from player
if (!player.skills) return 0;
let maxLevel = 0;
Object.values(player.skills).forEach(skill => {
if (skill.level > maxLevel) {
maxLevel = skill.level;
}
});
return maxLevel;
}
unlockNextQuest(nextQuestId) {
const nextQuest = this.findQuest(nextQuestId);
if (nextQuest && nextQuest.status === 'locked') {
nextQuest.status = 'available';
this.game.showNotification(`New quest available: ${nextQuest.name}`, 'info', 4000);
}
}
findQuest(questId) {
// Search in all quest arrays including failed quests with safety checks
const allQuests = [
...(this.mainQuests || []),
...(this.dailyQuests || []),
...(this.activeQuests || []),
...(this.completedQuests || []),
...(this.failedQuests || [])
];
return allQuests.find(q => q.id === questId);
}
getQuestsByType(type) {
console.log(`[QUEST SYSTEM] getQuestsByType called with type: ${type}`);
switch (type) {
case 'main':
const mainQuests = (this.mainQuests || []).filter(q => q.status === 'available' || q.status === 'active');
console.log(`[QUEST SYSTEM] Main quests found: ${mainQuests.length}`);
return mainQuests;
case 'daily':
// Ensure daily quests are initialized
if (!this.dailyQuests || this.dailyQuests.length === 0) {
console.log('[QUEST SYSTEM] Daily quests not initialized, forcing initialization');
this.randomizeDailyQuests();
}
const dailyQuests = (this.dailyQuests || []).filter(q => q.status !== 'failed');
console.log(`[QUEST SYSTEM] Daily quests found: ${dailyQuests.length}`);
console.log(`[QUEST SYSTEM] All daily quests array:`, this.dailyQuests);
dailyQuests.forEach(quest => {
console.log(`[QUEST SYSTEM] Daily quest: ${quest.id}, status: ${quest.status}, name: ${quest.name}`);
});
return dailyQuests;
case 'weekly':
const weeklyQuests = (this.weeklyQuests || []).filter(q => q.status !== 'failed');
console.log(`[QUEST SYSTEM] Weekly quests found: ${weeklyQuests.length}`);
return weeklyQuests;
case 'completed':
return this.getCompletedQuests();
case 'failed':
return (this.failedQuests || []);
default:
console.log(`[QUEST SYSTEM] Unknown quest type: ${type}`);
return [];
}
}
getCompletedQuests() {
const completed = [];
// Add completed main quests
if (this.mainQuests) {
completed.push(...this.mainQuests.filter(q => q.status === 'completed'));
}
// Add completed daily quests (from history)
if (this.completedDailyQuests) {
completed.push(...this.completedDailyQuests);
}
// Add completed weekly quests
if (this.weeklyQuests) {
completed.push(...this.weeklyQuests.filter(q => q.status === 'completed'));
}
// Sort by completion time (most recent first)
return completed.sort((a, b) => (b.completedAt || 0) - (a.completedAt || 0));
}
// Load quests from server data
loadServerQuests(serverQuestData) {
console.log('[QUEST SYSTEM] Loading server quest data:', serverQuestData);
if (!serverQuestData) {
console.log('[QUEST SYSTEM] No server quest data provided');
return;
}
// Clear existing quests
this.mainQuests = [];
this.dailyQuests = [];
this.weeklyQuests = [];
this.activeQuests = [];
this.completedQuests = [];
// Load quests from server data
if (serverQuestData.mainQuests && Array.isArray(serverQuestData.mainQuests)) {
this.mainQuests = serverQuestData.mainQuests;
console.log('[QUEST SYSTEM] Loaded', this.mainQuests.length, 'main quests');
}
if (serverQuestData.dailyQuests && Array.isArray(serverQuestData.dailyQuests)) {
this.dailyQuests = serverQuestData.dailyQuests;
console.log('[QUEST SYSTEM] Loaded', this.dailyQuests.length, 'daily quests');
}
if (serverQuestData.activeQuests && Array.isArray(serverQuestData.activeQuests)) {
this.activeQuests = serverQuestData.activeQuests;
console.log('[QUEST SYSTEM] Loaded', this.activeQuests.length, 'active quests');
}
if (serverQuestData.completedQuests && Array.isArray(serverQuestData.completedQuests)) {
this.completedQuests = serverQuestData.completedQuests;
console.log('[QUEST SYSTEM] Loaded', this.completedQuests.length, 'completed quests');
}
console.log('[QUEST SYSTEM] Server quest data loaded successfully');
console.log('[QUEST SYSTEM] Total quests loaded:', {
main: this.mainQuests.length,
daily: this.dailyQuests.length,
active: this.activeQuests.length,
completed: this.completedQuests.length
});
}
// UI updates
updateUI() {
console.log('[QUEST SYSTEM] updateUI called');
console.log('[QUEST SYSTEM] Game available:', !!this.game);
console.log('[QUEST SYSTEM] Multiplayer mode:', window.smartSaveManager?.isMultiplayer);
this.updateQuestList();
this.updateQuestStats();
}
updateQuestList() {
console.log('[QUEST SYSTEM] updateQuestList called');
const questListElement = document.getElementById('questList');
if (!questListElement) {
console.log('[QUEST SYSTEM] questListElement not found');
return;
}
const activeType = document.querySelector('.quest-tab-btn.active')?.dataset.type || 'main';
console.log(`[QUEST SYSTEM] Active quest tab type: ${activeType}`);
const quests = this.getQuestsByType(activeType);
console.log(`[QUEST SYSTEM] Getting quests for type: ${activeType}, found: ${quests.length} quests`);
console.log('[QUEST SYSTEM] First few quests:', quests.slice(0, 3));
questListElement.innerHTML = '';
if (quests.length === 0) {
console.log(`[QUEST SYSTEM] No quests found for type: ${activeType}`);
questListElement.innerHTML = '<p class="no-quests">No quests available</p>';
return;
}
console.log(`[QUEST SYSTEM] Rendering ${quests.length} quests for type: ${activeType}`);
quests.forEach(quest => {
console.log(`[QUEST SYSTEM] Rendering quest: ${quest.id}, status: ${quest.status}`);
const questElement = document.createElement('div');
questElement.className = `quest-item ${quest.status}`;
// Add difficulty indicator for daily quests
const difficultyIndicator = quest.type === 'daily' && quest.difficulty ?
`<div class="quest-difficulty difficulty-${quest.difficulty}">
${'★'.repeat(quest.difficulty)}
</div>` : '';
// Handle completed quests differently
const isCompleted = quest.status === 'completed';
const progressPercent = isCompleted ? 100 : (quest.objectives.length > 0 ?
(quest.objectives.reduce((sum, obj) => sum + (obj.current / obj.target), 0) / quest.objectives.length) * 100 : 0);
const objectivesHtml = isCompleted ?
'<div class="all-objectives-completed">✓ All objectives completed</div>' :
quest.objectives.map(obj => `
<div class="quest-objective">
<span class="objective-description">${obj.description}</span>
<span class="objective-progress">${obj.current}/${obj.target}</span>
<div class="progress-bar">
<div class="progress-fill" style="width: ${(obj.current / obj.target) * 100}%"></div>
</div>
</div>
`).join('');
const rewardsHtml = Object.entries(quest.rewards)
.filter(([key]) => key !== 'item')
.map(([key, value]) => {
const icon = key === 'credits' ? 'fa-coins' : key === 'experience' ? 'fa-star' : 'fa-gem';
return `<div class="quest-reward"><i class="fas ${icon}"></i> ${value}</div>`;
}).join('');
// Add completion time for completed quests
const completionTime = isCompleted && quest.completedAt ?
`<div class="completion-time">Completed: ${new Date(quest.completedAt).toLocaleDateString()}</div>` : '';
questElement.innerHTML = `
<div class="quest-header">
<div class="quest-title">${quest.name}</div>
<div class="quest-header-info">
${difficultyIndicator}
<div class="quest-rewards">
${rewardsHtml}
</div>
</div>
</div>
<div class="quest-description">${quest.description}</div>
<div class="quest-objectives">
${objectivesHtml}
</div>
${completionTime}
<div class="quest-progress">
<div class="progress-bar">
<div class="progress-fill" style="width: ${progressPercent}%"></div>
</div>
<span>${isCompleted ? 'Completed' : Math.round(progressPercent) + '% Complete'}</span>
</div>
<div class="quest-actions">
${quest.status === 'available' ?
`<button class="btn btn-primary" onclick="if(window.game && window.game.systems) window.game.systems.questSystem.startQuest('${quest.id}')">Start Quest</button>` :
quest.status === 'active' ?
`<div class="active-quest-actions">
<button class="btn btn-warning" onclick="if(window.game && window.game.systems) window.game.systems.questSystem.cancelQuest('${quest.id}')">
Cancel Quest
</button>
<button class="btn btn-success ${progressPercent < 100 ? 'disabled' : ''}"
onclick="if(window.game && window.game.systems) window.game.systems.questSystem.completeQuest('${quest.id}')"
${progressPercent < 100 ? 'disabled' : ''}>
Turn In
</button>
</div>` :
quest.status === 'completed' ?
'<span class="completed-badge">Completed</span>' :
quest.status === 'failed' ?
`<div class="failed-quest-actions">
<span class="failed-reason">Failed: ${quest.failureReason || 'Cancelled'}</span>
<button class="btn btn-secondary" onclick="if(window.game && window.game.systems) window.game.systems.questSystem.retryQuest('${quest.id}')">
Retry Quest
</button>
</div>` :
'<span class="quest-badge">Available</span>'
}
</div>
`;
questListElement.appendChild(questElement);
});
}
updateQuestStats() {
// Update quest statistics if elements exist
const questsCompletedElement = document.getElementById('questsCompleted');
if (questsCompletedElement) {
questsCompletedElement.textContent = this.stats.questsCompleted;
}
}
// Quest cancellation
cancelQuest(questId) {
const quest = this.findQuest(questId);
if (!quest) {
this.game.showNotification('Quest not found!', 'error', 3000);
return;
}
if (quest.status !== 'active') {
this.game.showNotification('Only active quests can be cancelled!', 'warning', 3000);
return;
}
if (!confirm(`Are you sure you want to cancel "${quest.name}"? This quest will be marked as failed.`)) {
return;
}
// Move quest to failed
quest.status = 'failed';
quest.failedAt = Date.now();
quest.failureReason = 'Cancelled by player';
// Remove from active quests
this.activeQuests = this.activeQuests.filter(q => q.id !== questId);
// Add to failed quests
this.failedQuests.push({...quest});
// Apply failure penalty
this.applyQuestFailurePenalty(quest);
this.game.showNotification(`Quest "${quest.name}" cancelled and marked as failed.`, 'warning', 4000);
this.switchToFailedTab();
this.updateQuestList();
}
// Quest failure handling
failQuest(questId, reason = 'Time expired') {
const quest = this.findQuest(questId);
if (!quest) return;
if (quest.status !== 'active') return;
quest.status = 'failed';
quest.failedAt = Date.now();
quest.failureReason = reason;
// Remove from active quests
this.activeQuests = this.activeQuests.filter(q => q.id !== questId);
// Add to failed quests
this.failedQuests.push({...quest});
// Apply failure penalty
this.applyQuestFailurePenalty(quest);
this.game.showNotification(`Quest "${quest.name}" failed: ${reason}`, 'error', 4000);
this.switchToFailedTab();
this.updateQuestList();
}
applyQuestFailurePenalty(quest) {
// Apply failure penalties
const player = this.game.systems.player;
// Reduce reputation
if (player.stats.reputation) {
player.stats.reputation = Math.max(0, player.stats.reputation - 10);
}
// Apply other penalties based on quest type
switch (quest.type) {
case 'daily':
this.stats.dailyQuestsCompleted = Math.max(0, this.stats.dailyQuestsCompleted - 1);
break;
}
// Only update player UI if in multiplayer mode or game is actively running
if (this.game.shouldUpdateGUI()) {
player.updateUI();
}
}
// Retry failed quest
retryQuest(questId) {
const failedQuestIndex = this.failedQuests.findIndex(q => q.id === questId);
if (failedQuestIndex === -1) {
this.game.showNotification('Failed quest not found!', 'error', 3000);
return;
}
const quest = this.failedQuests[failedQuestIndex];
// Check if retry is allowed (24 hour cooldown)
const timeSinceFailure = Date.now() - quest.failedAt;
const retryCooldown = 24 * 60 * 60 * 1000; // 24 hours
if (timeSinceFailure < retryCooldown) {
const remainingTime = Math.ceil((retryCooldown - timeSinceFailure) / (60 * 60 * 1000));
this.game.showNotification(`Can retry this quest in ${remainingTime} hours.`, 'warning', 3000);
return;
}
// Reset quest progress
quest.objectives.forEach(obj => {
obj.current = 0;
});
quest.status = 'available';
quest.failedAt = null;
quest.failureReason = null;
// Remove from failed quests
this.failedQuests.splice(failedQuestIndex, 1);
this.game.showNotification(`Quest "${quest.name}" is now available to retry.`, 'success', 3000);
this.updateQuestList();
}
// Switch to failed quests tab
switchToFailedTab() {
// Remove active class from all quest tab buttons
document.querySelectorAll('.quest-tab-btn').forEach(btn => {
btn.classList.remove('active');
});
// Add active class to failed quests tab
const failedTabBtn = document.querySelector('[data-type="failed"]');
if (failedTabBtn) {
failedTabBtn.classList.add('active');
}
// Update the quest list to show failed quests
this.updateQuestList();
}
// Quest progress tracking
updateQuestProgress(objectiveType, amount = 1) {
// Update progress for all active quests with this objective type
[...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
if (quest.status === 'active') {
quest.objectives.forEach(objective => {
if (objective.type === objectiveType || objective.id.includes(objectiveType)) {
const oldValue = objective.current;
objective.current = Math.min(objective.current + amount, objective.target);
if (objective.current !== oldValue) {
this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
}
}
});
}
});
this.updateQuestList();
this.checkQuestCompletion();
}
updateLevelProgress(newLevel) {
// Update progress for level-based objectives
[...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
if (quest.status === 'active') {
quest.objectives.forEach(objective => {
if (objective.type === 'level' || objective.id.includes('level')) {
const oldValue = objective.current;
objective.current = Math.min(newLevel, objective.target);
if (objective.current !== oldValue) {
this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
}
}
});
}
});
this.updateQuestList();
this.checkQuestCompletion();
}
updateDungeonProgress() {
// Update progress for dungeon-based objectives
[...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
if (quest.status === 'active') {
quest.objectives.forEach(objective => {
if (objective.type === 'dungeon' || objective.id.includes('dungeon') || objective.id.includes('clear_dungeon')) {
const oldValue = objective.current;
objective.current = Math.min(objective.current + 1, objective.target);
if (objective.current !== oldValue) {
this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
}
}
});
}
});
this.updateQuestList();
this.checkQuestCompletion();
}
updateTutorialDungeonProgress() {
// Update progress for tutorial dungeon objectives
[...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
if (quest.status === 'active') {
quest.objectives.forEach(objective => {
if (objective.type === 'tutorial_dungeon' || objective.id.includes('tutorial_dungeon')) {
const oldValue = objective.current;
objective.current = 1; // Tutorial dungeon is completed
if (objective.current !== oldValue) {
this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
}
}
});
}
});
this.updateQuestList();
this.checkQuestCompletion();
}
updateUpgradeProgress(upgradeType) {
// Update progress for upgrade objectives
[...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
if (quest.status === 'active') {
quest.objectives.forEach(objective => {
if (objective.type === 'upgrade' && (objective.id === upgradeType || objective.id.includes(upgradeType))) {
const oldValue = objective.current;
objective.current = 1; // Upgrade completed
if (objective.current !== oldValue) {
this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
}
}
});
}
});
this.updateQuestList();
this.checkQuestCompletion();
this.checkQuestAvailability();
}
updateGuildProgress(action, amount = 1) {
// Update progress for guild objectives
[...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
if (quest.status === 'active') {
quest.objectives.forEach(objective => {
if (action === 'join' && (objective.type === 'guild' || objective.id.includes('guild'))) {
const oldValue = objective.current;
objective.current = 1; // Guild joined
if (objective.current !== oldValue) {
this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
}
}
if (action === 'contribute' && (objective.type === 'contribution' || objective.id.includes('contribution'))) {
const oldValue = objective.current;
objective.current = Math.min(objective.current + amount, objective.target);
if (objective.current !== oldValue) {
this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
}
}
});
}
});
this.updateQuestList();
this.checkQuestCompletion();
this.checkQuestAvailability();
}
updateSkillProgress() {
// Update progress for skill objectives
[...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
if (quest.status === 'active') {
quest.objectives.forEach(objective => {
if (objective.type === 'skill' || objective.id.includes('skill')) {
const player = this.game.systems.player;
const maxSkillLevel = this.getMaxSkillLevel(player);
const oldValue = objective.current;
objective.current = Math.min(maxSkillLevel, objective.target);
if (objective.current !== oldValue) {
this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
}
}
});
}
});
this.updateQuestList();
this.checkQuestCompletion();
this.checkQuestAvailability();
}
checkInitialQuestProgress(quest) {
const player = this.game.systems.player;
const dungeonSystem = this.game.systems.dungeonSystem;
console.log(`[QUEST SYSTEM] Checking initial progress for quest: ${quest.id}`);
console.log(`[QUEST SYSTEM] Player stats:`, player.stats);
quest.objectives.forEach(objective => {
console.log(`[QUEST SYSTEM] Processing objective: ${objective.id}, type: ${objective.type}`);
// Check level-based objectives
if (objective.type === 'level' || objective.id.includes('level')) {
objective.current = Math.min(player.stats.level, objective.target);
console.log(`[QUEST SYSTEM] Level objective ${objective.id}: ${objective.current}/${objective.target}`);
}
// Check dungeon-based objectives
if (objective.type === 'dungeon' || objective.id.includes('dungeon') || objective.id.includes('clear_dungeon')) {
// Check any dungeon completion - use multiple sources
const playerDungeons = player.stats.dungeonsCleared || 0;
const dungeonSystemStats = dungeonSystem ? dungeonSystem.stats.dungeonsCompleted || 0 : 0;
const dungeonsCleared = Math.max(playerDungeons, dungeonSystemStats);
objective.current = Math.min(dungeonsCleared, objective.target);
// Debug logging
console.log(`Dungeon objective check: Player=${playerDungeons}, DungeonSystem=${dungeonSystemStats}, Final=${dungeonsCleared}`);
}
// Check tutorial dungeon objective
if (objective.type === 'tutorial_dungeon' || objective.id.includes('tutorial_dungeon')) {
// Check if tutorial dungeon has been completed
const tutorialCompleted = player.stats.tutorialDungeonCompleted || false;
objective.current = tutorialCompleted ? 1 : 0;
console.log(`[QUEST SYSTEM] Tutorial dungeon objective ${objective.id}: ${objective.current}/${objective.target}, completed: ${tutorialCompleted}`);
}
// Check upgrade objectives
if (objective.type === 'upgrade' || objective.id.includes('upgrade')) {
const upgradeCompleted = player.stats[`upgraded_${objective.id}`] || false;
objective.current = upgradeCompleted ? 1 : 0;
}
// Check guild objectives
if (objective.type === 'guild' || objective.id.includes('guild')) {
const guildJoined = player.stats.guildJoined || false;
objective.current = guildJoined ? 1 : 0;
}
// Check contribution objectives
if (objective.type === 'contribution' || objective.id.includes('contribution')) {
const contribution = player.stats.guildContribution || 0;
objective.current = Math.min(contribution, objective.target);
}
// Check skill objectives
if (objective.type === 'skill' || objective.id.includes('skill')) {
const maxSkillLevel = this.getMaxSkillLevel(player);
objective.current = Math.min(maxSkillLevel, objective.target);
}
// Check other objective types
if (objective.type === 'collection' || objective.id.includes('collect')) {
// Check inventory for collected items
const inventory = this.game.systems.inventory;
if (inventory && inventory.items) {
const totalCollected = Object.values(inventory.items).reduce((sum, item) => sum + item.quantity, 0);
objective.current = Math.min(totalCollected, objective.target);
}
}
});
// Check if quest is already complete
const allComplete = quest.objectives.every(obj => obj.current >= obj.target);
if (allComplete) {
quest.status = 'completed';
this.completeQuest(quest.id);
}
}
checkQuestCompletion() {
// Check if any active quests are complete
this.activeQuests.forEach(quest => {
if (quest.status === 'active') {
const allComplete = quest.objectives.every(obj => obj.current >= obj.target);
if (allComplete) {
quest.status = 'completed';
this.completeQuest(quest.id);
}
}
});
}
// Save/Load
save() {
const debugLogger = window.debugLogger;
// if (debugLogger) debugLogger.startStep('QuestSystem.save', {
// mainQuestsCount: this.mainQuests.length,
// dailyQuestsCount: this.dailyQuests.length,
// activeQuestsCount: this.activeQuests.length,
// completedQuestsCount: this.completedQuests.length,
// currentStats: this.stats
// });
const saveData = {
mainQuests: this.mainQuests,
dailyQuests: this.dailyQuests,
activeQuests: this.activeQuests,
completedQuests: this.completedQuests,
stats: this.stats
};
// if (debugLogger) debugLogger.endStep('QuestSystem.save', {
// saveDataSize: JSON.stringify(saveData).length,
// mainQuestsSaved: this.mainQuests.length,
// dailyQuestsSaved: this.dailyQuests.length,
// activeQuestsSaved: this.activeQuests.length,
// completedQuestsSaved: this.completedQuests.length,
// statsSaved: this.stats,
// saveData: saveData
// });
return saveData;
}
load(data) {
const debugLogger = window.debugLogger;
const oldState = {
mainQuestsCount: this.mainQuests.length,
dailyQuestsCount: this.dailyQuests.length,
activeQuestsCount: this.activeQuests.length,
completedQuestsCount: this.completedQuests.length,
stats: this.stats
};
if (debugLogger) debugLogger.startStep('QuestSystem.load', {
oldState: oldState,
loadData: data
});
try {
if (data.mainQuests) {
this.mainQuests = data.mainQuests;
if (debugLogger) debugLogger.logStep('Loaded main quests', {
mainQuestsLoaded: this.mainQuests.length,
mainQuests: this.mainQuests.map(q => ({
id: q.id,
name: q.name,
status: q.status
}))
});
}
if (data.dailyQuests) {
this.dailyQuests = data.dailyQuests;
if (debugLogger) debugLogger.logStep('Loaded daily quests', {
dailyQuestsLoaded: this.dailyQuests.length,
dailyQuests: this.dailyQuests.map(q => ({
id: q.id,
name: q.name,
status: q.status
}))
});
}
if (data.activeQuests) {
this.activeQuests = data.activeQuests;
if (debugLogger) debugLogger.logStep('Loaded active quests', {
activeQuestsLoaded: this.activeQuests.length,
activeQuests: this.activeQuests.map(q => ({
id: q.id,
name: q.name,
type: q.type,
status: q.status
}))
});
}
if (data.completedQuests) {
this.completedQuests = data.completedQuests;
if (debugLogger) debugLogger.logStep('Loaded completed quests', {
completedQuestsLoaded: this.completedQuests.length,
completedQuests: this.completedQuests.map(q => ({
id: q.id,
name: q.name,
type: q.type,
completedAt: q.completedAt
}))
});
}
if (data.stats) {
const oldStats = { ...this.stats };
this.stats = { ...this.stats, ...data.stats };
if (debugLogger) debugLogger.logStep('Loaded quest statistics', {
oldStats: oldStats,
newStats: this.stats,
statsMerged: true
});
}
// Check for daily reset after loading
if (debugLogger) debugLogger.logStep('Checking daily reset after load');
this.checkDailyReset();
if (debugLogger) debugLogger.endStep('QuestSystem.load', {
success: true,
oldState: oldState,
newState: {
mainQuestsCount: this.mainQuests.length,
dailyQuestsCount: this.dailyQuests.length,
activeQuestsCount: this.activeQuests.length,
completedQuestsCount: this.completedQuests.length,
stats: this.stats
},
dailyResetChecked: true
});
} catch (error) {
if (debugLogger) debugLogger.errorEvent('QuestSystem.load', error, {
oldState: oldState,
loadData: data,
error: error.message
});
throw error;
}
}
}