/** * Galaxy Strike Online - Server Quest System * Manages quests, progress tracking, and rewards */ class QuestSystem { constructor() { this.quests = new Map(); this.playerQuests = new Map(); // userId -> quest data this.initializeQuests(); } initializeQuests() { // Tutorial quests this.addQuest('tutorial_first_battle', { name: 'First Battle', description: 'Complete your first battle to learn the basics', type: 'tutorial', difficulty: 'easy', requirements: { battlesWon: 1 }, rewards: { experience: 100, credits: 500, items: ['health_kit', 'energy_pack'] }, prerequisites: [], repeatable: false, timeLimit: null }); // Main Story quests this.addQuest('main_story_beginning', { name: 'The Beginning', description: 'Start your journey as a space pilot', type: 'main', difficulty: 'easy', requirements: { level: 1, battlesWon: 1 }, rewards: { experience: 200, credits: 1000, skillPoints: 2 }, prerequisites: [], repeatable: false, timeLimit: null }); this.addQuest('main_story_first_dungeon', { name: 'First Dungeon', description: 'Complete your first dungeon run', type: 'main', difficulty: 'medium', requirements: { level: 5, dungeonsCleared: 1 }, rewards: { experience: 500, credits: 2000, items: ['rare_weapon'] }, prerequisites: ['main_story_beginning'], repeatable: false, timeLimit: null }); this.addQuest('main_story_space_exploration', { name: 'Space Explorer', description: 'Explore 10 different sectors', type: 'main', difficulty: 'medium', requirements: { level: 10, sectorsExplored: 10 }, rewards: { experience: 1000, credits: 5000, items: ['explorer_badge'] }, prerequisites: ['main_story_first_dungeon'], repeatable: false, timeLimit: null }); // Daily quests this.addQuest('daily_battles', { name: 'Daily Battles', description: 'Win 5 battles today', type: 'daily', difficulty: 'easy', requirements: { battlesWon: 5 }, rewards: { experience: 150, credits: 750, gems: 5 }, prerequisites: [], repeatable: true, timeLimit: 24 * 60 * 60 * 1000 // 24 hours }); this.addQuest('daily_exploration', { name: 'Daily Exploration', description: 'Explore 3 new sectors today', type: 'daily', difficulty: 'easy', requirements: { sectorsExplored: 3 }, rewards: { experience: 100, credits: 500, gems: 3 }, prerequisites: [], repeatable: true, timeLimit: 24 * 60 * 60 * 1000 // 24 hours }); this.addQuest('daily_resources', { name: 'Daily Resource Collection', description: 'Collect 1000 resources today', type: 'daily', difficulty: 'medium', requirements: { resourcesCollected: 1000 }, rewards: { experience: 200, credits: 1000, gems: 8 }, prerequisites: [], repeatable: true, timeLimit: 24 * 60 * 60 * 1000 // 24 hours }); // Weekly quests this.addQuest('weekly_champion', { name: 'Weekly Champion', description: 'Win 50 battles this week', type: 'weekly', difficulty: 'hard', requirements: { battlesWon: 50 }, rewards: { experience: 2000, credits: 10000, gems: 50, items: ['champion_title'] }, prerequisites: [], repeatable: true, timeLimit: 7 * 24 * 60 * 60 * 1000 // 7 days }); this.addQuest('weekly_dungeon_master', { name: 'Weekly Dungeon Master', description: 'Complete 10 dungeons this week', type: 'weekly', difficulty: 'hard', requirements: { dungeonsCleared: 10 }, rewards: { experience: 3000, credits: 15000, gems: 75, items: ['dungeon_master_cape'] }, prerequisites: [], repeatable: true, timeLimit: 7 * 24 * 60 * 60 * 1000 // 7 days }); this.addQuest('weekly_wealth_collector', { name: 'Weekly Wealth Collector', description: 'Earn 10000 credits this week', type: 'weekly', difficulty: 'medium', requirements: { creditsEarned: 10000 }, rewards: { experience: 1500, credits: 5000, gems: 25 }, prerequisites: [], repeatable: true, timeLimit: 7 * 24 * 60 * 60 * 1000 // 7 days }); // Combat quests this.addQuest('warrior_path', { name: 'Warrior Path', description: 'Win 10 battles to prove your combat skills', type: 'combat', difficulty: 'medium', requirements: { battlesWon: 10, enemiesDefeated: 50 }, rewards: { experience: 500, credits: 2000, items: ['basic_armor'] }, prerequisites: ['tutorial_first_battle'], repeatable: false, timeLimit: null }); // Crafting quests this.addQuest('novice_crafter', { name: 'Novice Crafter', description: 'Craft 5 items to learn the basics of crafting', type: 'crafting', difficulty: 'easy', requirements: { itemsCrafted: 5 }, rewards: { experience: 200, credits: 1000, craftingExperience: 100 }, prerequisites: [], repeatable: false, timeLimit: null }); // Exploration quests this.addQuest('explorer_spirit', { name: 'Explorer Spirit', description: 'Discover 5 different locations in the galaxy', type: 'exploration', difficulty: 'medium', requirements: { locationsDiscovered: 5 }, rewards: { experience: 300, credits: 1500, items: ['navigation_device'] }, prerequisites: ['tutorial_first_battle'], repeatable: false, timeLimit: null }); // Daily quests this.addQuest('daily_bounty', { name: 'Daily Bounty', description: 'Complete 3 battles today', type: 'daily', difficulty: 'easy', requirements: { battlesWon: 3 }, rewards: { experience: 150, credits: 750 }, prerequisites: [], repeatable: true, timeLimit: 86400000 // 24 hours }); // Weekly quests this.addQuest('weekly_champion', { name: 'Weekly Champion', description: 'Win 20 battles this week', type: 'weekly', difficulty: 'hard', requirements: { battlesWon: 20 }, rewards: { experience: 1000, credits: 5000, items: ['rare_weapon_part'] }, prerequisites: [], repeatable: true, timeLimit: 604800000 // 7 days }); } addQuest(id, quest) { this.quests.set(id, { id, ...quest, createdAt: new Date().toISOString() }); } getQuest(id) { return this.quests.get(id); } getAllQuests() { return Array.from(this.quests.values()); } getQuestsByType(type) { return Array.from(this.quests.values()).filter(quest => quest.type === type); } getQuestsByDifficulty(difficulty) { return Array.from(this.quests.values()).filter(quest => quest.difficulty === difficulty); } initializePlayerData(userId) { if (!this.playerQuests.has(userId)) { const playerData = { activeQuests: new Map(), completedQuests: new Map(), questHistory: [], totalQuestsCompleted: 0, dailyQuestsCompleted: 0, weeklyQuestsCompleted: 0 }; // Assign starting quests to new players this.assignStartingQuests(userId, playerData); this.playerQuests.set(userId, playerData); } return this.playerQuests.get(userId); } assignStartingQuests(userId, playerData) { console.log(`[QUEST SYSTEM] Assigning starting quests to player ${userId}`); // Assign main story quests const mainStoryQuests = ['main_story_beginning', 'main_story_first_dungeon']; mainStoryQuests.forEach(questId => { const quest = this.quests.get(questId); if (quest && !playerData.activeQuests.has(questId) && !playerData.completedQuests.has(questId)) { playerData.activeQuests.set(questId, { ...quest, progress: 0, startedAt: Date.now() }); console.log(`[QUEST SYSTEM] Assigned main story quest: ${quest.name}`); } }); // Assign daily quests this.generateDailyQuests(userId, playerData); // Assign weekly quests this.generateWeeklyQuests(userId, playerData); console.log(`[QUEST SYSTEM] Player now has ${playerData.activeQuests.size} active quests`); } generateDailyQuests(userId, playerData) { console.log(`[QUEST SYSTEM] Generating daily quests for player ${userId}`); const dailyQuestTemplates = ['daily_battles', 'daily_exploration', 'daily_resources']; dailyQuestTemplates.forEach(questId => { const quest = this.quests.get(questId); if (quest) { playerData.activeQuests.set(questId, { ...quest, progress: 0, startedAt: Date.now(), type: 'daily', resetTime: Date.now() + (24 * 60 * 60 * 1000) // 24 hours from now }); console.log(`[QUEST SYSTEM] Assigned daily quest: ${quest.name}`); } }); } generateWeeklyQuests(userId, playerData) { console.log(`[QUEST SYSTEM] Generating weekly quests for player ${userId}`); const weeklyQuestTemplates = ['weekly_champion', 'weekly_dungeon_master', 'weekly_wealth_collector']; weeklyQuestTemplates.forEach(questId => { const quest = this.quests.get(questId); if (quest) { playerData.activeQuests.set(questId, { ...quest, progress: 0, startedAt: Date.now(), type: 'weekly', resetTime: Date.now() + (7 * 24 * 60 * 60 * 1000) // 7 days from now }); console.log(`[QUEST SYSTEM] Assigned weekly quest: ${quest.name}`); } }); } getPlayerData(userId) { return this.playerQuests.get(userId) || this.initializePlayerData(userId); } getAvailableQuests(userId) { const playerData = this.getPlayerData(userId); const availableQuests = []; for (const [questId, quest] of this.quests) { // Skip if already active or completed (for non-repeatable quests) if (playerData.activeQuests.has(questId)) continue; if (playerData.completedQuests.has(questId) && !quest.repeatable) continue; // Check prerequisites const prerequisitesMet = quest.prerequisites.every(prereqId => playerData.completedQuests.has(prereqId) ); if (prerequisitesMet) { availableQuests.push({ ...quest, status: playerData.activeQuests.has(questId) ? 'active' : 'available' }); } } return availableQuests; } startQuest(userId, questId) { const quest = this.getQuest(questId); if (!quest) { throw new Error('Quest not found'); } const playerData = this.getPlayerData(userId); // Check if quest is already active if (playerData.activeQuests.has(questId)) { throw new Error('Quest already active'); } // Check if quest is completed and not repeatable if (playerData.completedQuests.has(questId) && !quest.repeatable) { throw new Error('Quest already completed'); } // Check prerequisites const prerequisitesMet = quest.prerequisites.every(prereqId => playerData.completedQuests.has(prereqId) ); if (!prerequisitesMet) { throw new Error('Prerequisites not met'); } // Start the quest const questProgress = { questId, startedAt: new Date().toISOString(), progress: {}, completed: false, expiredAt: quest.timeLimit ? new Date(Date.now() + quest.timeLimit).toISOString() : null }; // Initialize progress tracking for (const requirement of Object.keys(quest.requirements)) { questProgress.progress[requirement] = 0; } playerData.activeQuests.set(questId, questProgress); return { success: true, quest, progress: questProgress }; } updateQuestProgress(userId, questId, progressUpdates) { const playerData = this.getPlayerData(userId); const activeQuest = playerData.activeQuests.get(questId); if (!activeQuest) { throw new Error('Quest not active'); } // Check if quest is expired if (activeQuest.expiredAt && new Date() > new Date(activeQuest.expiredAt)) { playerData.activeQuests.delete(questId); throw new Error('Quest expired'); } const quest = this.getQuest(questId); let updated = false; // Update progress for (const [requirement, amount] of Object.entries(progressUpdates)) { if (quest.requirements[requirement] !== undefined) { activeQuest.progress[requirement] = Math.min( activeQuest.progress[requirement] + amount, quest.requirements[requirement] ); updated = true; } } // Check if quest is completed const isCompleted = Object.entries(quest.requirements).every( ([requirement, requiredAmount]) => activeQuest.progress[requirement] >= requiredAmount ); if (isCompleted && !activeQuest.completed) { activeQuest.completed = true; activeQuest.completedAt = new Date().toISOString(); return { success: true, questCompleted: true, quest, progress: activeQuest.progress }; } return { success: true, questCompleted: false, quest, progress: activeQuest.progress, updated }; } completeQuest(userId, questId) { const playerData = this.getPlayerData(userId); const activeQuest = playerData.activeQuests.get(questId); if (!activeQuest) { throw new Error('Quest not active'); } if (!activeQuest.completed) { throw new Error('Quest requirements not met'); } const quest = this.getQuest(questId); // Move to completed quests playerData.completedQuests.set(questId, { ...activeQuest, quest, completedAt: new Date().toISOString() }); // Remove from active quests playerData.activeQuests.delete(questId); // Update statistics playerData.totalQuestsCompleted += 1; if (quest.type === 'daily') { playerData.dailyQuestsCompleted += 1; } else if (quest.type === 'weekly') { playerData.weeklyQuestsCompleted += 1; } // Add to history playerData.questHistory.push({ questId, questName: quest.name, completedAt: new Date().toISOString(), rewards: quest.rewards }); // Keep only last 100 quest records if (playerData.questHistory.length > 100) { playerData.questHistory = playerData.questHistory.slice(-100); } return { success: true, quest, rewards: quest.rewards, statistics: { totalCompleted: playerData.totalQuestsCompleted, dailyCompleted: playerData.dailyQuestsCompleted, weeklyCompleted: playerData.weeklyQuestsCompleted } }; } getPlayerActiveQuests(userId) { const playerData = this.getPlayerData(userId); const activeQuests = []; for (const [questId, progress] of playerData.activeQuests) { const quest = this.getQuest(questId); activeQuests.push({ ...quest, progress: progress.progress, startedAt: progress.startedAt, expiredAt: progress.expiredAt }); } return activeQuests; } getPlayerCompletedQuests(userId) { const playerData = this.getPlayerData(userId); const completedQuests = []; for (const [questId, completion] of playerData.completedQuests) { completedQuests.push({ ...completion.quest, completedAt: completion.completedAt, progress: completion.progress }); } return completedQuests; } getQuestStatistics(userId) { const playerData = this.getPlayerData(userId); const availableQuests = this.getAvailableQuests(userId); return { activeQuests: playerData.activeQuests.size, completedQuests: playerData.completedQuests.size, availableQuests: availableQuests.length, totalCompleted: playerData.totalQuestsCompleted, dailyCompleted: playerData.dailyQuestsCompleted, weeklyCompleted: playerData.weeklyQuestsCompleted, recentHistory: playerData.questHistory.slice(-10) }; } resetDailyQuests(userId) { const playerData = this.getPlayerData(userId); // Remove completed daily quests for (const [questId, quest] of this.quests) { if (quest.type === 'daily') { playerData.completedQuests.delete(questId); playerData.activeQuests.delete(questId); } } playerData.dailyQuestsCompleted = 0; return { success: true, message: 'Daily quests reset' }; } getPlayerQuests(userId) { console.log('[QUEST SYSTEM] Getting quests for user:', userId); // Get or create player data let playerData = this.playerQuests.get(userId); if (!playerData) { playerData = { activeQuests: new Map(), completedQuests: new Map(), failedQuests: new Map() }; this.playerQuests.set(userId, playerData); } // Prepare quest data for client const questData = { mainQuests: [], dailyQuests: [], weeklyQuests: [], activeQuests: [], completedQuests: [], failedQuests: [] }; // Add main quests for (const [questId, quest] of this.quests) { if (quest.type === 'main') { const playerQuest = playerData.activeQuests.get(questId) || playerData.completedQuests.get(questId) || playerData.failedQuests.get(questId); const status = playerQuest ? playerQuest.status : 'available'; questData.mainQuests.push({ ...quest, status: status, objectives: quest.objectives.map(obj => ({ ...obj, current: playerQuest && playerQuest.objectives && playerQuest.objectives[obj.id] ? playerQuest.objectives[obj.id].current || 0 : 0 })) }); } } // Add daily quests for (const [questId, quest] of this.quests) { if (quest.type === 'daily') { const playerQuest = playerData.activeQuests.get(questId) || playerData.completedQuests.get(questId) || playerData.failedQuests.get(questId); const status = playerQuest ? playerQuest.status : 'available'; questData.dailyQuests.push({ ...quest, status: status, objectives: quest.objectives.map(obj => ({ ...obj, current: playerQuest && playerQuest.objectives && playerQuest.objectives[obj.id] ? playerQuest.objectives[obj.id].current || 0 : 0 })) }); } } // Add weekly quests for (const [questId, quest] of this.quests) { if (quest.type === 'weekly') { const playerQuest = playerData.activeQuests.get(questId) || playerData.completedQuests.get(questId) || playerData.failedQuests.get(questId); const status = playerQuest ? playerQuest.status : 'available'; questData.weeklyQuests.push({ ...quest, status: status, objectives: quest.objectives.map(obj => ({ ...obj, current: playerQuest && playerQuest.objectives && playerQuest.objectives[obj.id] ? playerQuest.objectives[obj.id].current || 0 : 0 })) }); } } // Add active quests (for compatibility) questData.activeQuests = Array.from(playerData.activeQuests.values()); questData.completedQuests = Array.from(playerData.completedQuests.values()); questData.failedQuests = Array.from(playerData.failedQuests.values()); console.log('[QUEST SYSTEM] Returning quest data:', { mainQuests: questData.mainQuests.length, dailyQuests: questData.dailyQuests.length, weeklyQuests: questData.weeklyQuests.length, activeQuests: questData.activeQuests.length, completedQuests: questData.completedQuests.length }); return questData; } completeQuest(userId, questId, rewards = null) { console.log('[QUEST SYSTEM] Completing quest:', questId, 'for user:', userId); const playerData = this.playerQuests.get(userId); if (!playerData) { console.log('[QUEST SYSTEM] Player data not found for user:', userId); return { success: false, error: 'Player not found' }; } const quest = this.quests.get(questId); if (!quest) { console.log('[QUEST SYSTEM] Quest not found:', questId); return { success: false, error: 'Quest not found' }; } // Move quest from active to completed const activeQuest = playerData.activeQuests.get(questId); if (activeQuest) { // Mark all objectives as completed const completedQuest = { ...activeQuest, status: 'completed', completedAt: Date.now(), objectives: quest.objectives.map(obj => ({ ...obj, current: obj.target })) }; playerData.activeQuests.delete(questId); playerData.completedQuests.set(questId, completedQuest); console.log('[QUEST SYSTEM] Quest completed successfully:', questId); return { success: true, quest: completedQuest }; } else { // Quest might not be active, try to complete it anyway const completedQuest = { ...quest, status: 'completed', completedAt: Date.now(), objectives: quest.objectives.map(obj => ({ ...obj, current: obj.target })) }; playerData.completedQuests.set(questId, completedQuest); console.log('[QUEST SYSTEM] Quest force-completed:', questId); return { success: true, quest: completedQuest }; } } resetWeeklyQuests(userId) { const playerData = this.getPlayerData(userId); // Remove completed weekly quests for (const [questId, quest] of this.quests) { if (quest.type === 'weekly') { playerData.completedQuests.delete(questId); playerData.activeQuests.delete(questId); } } playerData.weeklyQuestsCompleted = 0; return { success: true, message: 'Weekly quests reset' }; } } module.exports = QuestSystem;