API/GameServer/systems/QuestSystem.js
2026-01-30 10:58:30 -04:00

824 lines
27 KiB
JavaScript

/**
* 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;