/** * Galaxy Strike Online - Server Dungeon System * Manages dungeon instances, encounters, and rewards */ class DungeonSystem { constructor() { this.dungeons = new Map(); this.instances = new Map(); // instanceId -> dungeon instance this.playerInstances = new Map(); // userId -> instanceId this.initializeDungeons(); } initializeDungeons() { // Tutorial Dungeon this.addDungeon('tutorial_caverns', { name: 'Tutorial Caverns', description: 'Learn the basics of dungeon crawling', difficulty: 'easy', minLevel: 1, maxLevel: 5, maxPlayers: 4, estimatedTime: 15, // minutes encounters: [ { type: 'combat', name: 'Cave Bats', enemies: ['bat', 'bat', 'bat'], difficulty: 1 }, { type: 'combat', name: 'Cave Spider', enemies: ['spider'], difficulty: 2 }, { type: 'boss', name: 'Cave Guardian', enemies: ['guardian'], difficulty: 3 } ], rewards: { experience: { min: 50, max: 100 }, credits: { min: 100, max: 250 }, items: ['health_kit', 'basic_weapon'] }, requirements: { level: 1 } }); // Combat Dungeon this.addDungeon('abandoned_mines', { name: 'Abandoned Mines', description: 'Dangerous mines filled with hostile creatures', difficulty: 'medium', minLevel: 5, maxLevel: 15, maxPlayers: 4, estimatedTime: 30, encounters: [ { type: 'combat', name: 'Mine Guards', enemies: ['guard', 'guard'], difficulty: 3 }, { type: 'trap', name: 'Explosive Trap', difficulty: 4 }, { type: 'combat', name: 'Mine Foreman', enemies: ['foreman'], difficulty: 5 }, { type: 'treasure', name: 'Hidden Cache', difficulty: 3 }, { type: 'boss', name: 'Mine Overlord', enemies: ['overlord'], difficulty: 6 } ], rewards: { experience: { min: 200, max: 400 }, credits: { min: 500, max: 1000 }, items: ['rare_weapon', 'armor_piece'] }, requirements: { level: 5, item: 'torch' } }); // Puzzle Dungeon this.addDungeon('ancient_library', { name: 'Ancient Library', description: 'Solve puzzles to uncover ancient knowledge', difficulty: 'medium', minLevel: 8, maxLevel: 20, maxPlayers: 2, estimatedTime: 45, encounters: [ { type: 'puzzle', name: 'Riddle Door', difficulty: 4 }, { type: 'combat', name: 'Library Guardians', enemies: ['guardian', 'guardian'], difficulty: 4 }, { type: 'puzzle', name: 'Magic Lock', difficulty: 5 }, { type: 'treasure', name: 'Forbidden Tome', difficulty: 5 }, { type: 'boss', name: 'Librarian Specter', enemies: ['specter'], difficulty: 6 } ], rewards: { experience: { min: 300, max: 600 }, credits: { min: 750, max: 1500 }, items: ['spell_book', 'knowledge_scroll'] }, requirements: { level: 8, skill: 'ancient_knowledge' } }); // Raid Dungeon this.addDungeon('dragon_lair', { name: 'Dragon Lair', description: 'Face the ultimate dragon challenge', difficulty: 'hard', minLevel: 15, maxLevel: 30, maxPlayers: 8, estimatedTime: 60, encounters: [ { type: 'combat', name: 'Dragon Whelps', enemies: ['whelp', 'whelp', 'whelp', 'whelp'], difficulty: 7 }, { type: 'combat', name: 'Dragon Guard', enemies: ['dragon_guard', 'dragon_guard'], difficulty: 8 }, { type: 'trap', name: 'Dragon Breath Trap', difficulty: 9 }, { type: 'combat', name: 'Dragon Elite', enemies: ['dragon_elite'], difficulty: 10 }, { type: 'boss', name: 'Ancient Dragon', enemies: ['ancient_dragon'], difficulty: 12 } ], rewards: { experience: { min: 1000, max: 2000 }, credits: { min: 2500, max: 5000 }, items: ['dragon_scale', 'legendary_weapon', 'dragon_egg'] }, requirements: { level: 15, guild: true } }); } addDungeon(id, dungeon) { this.dungeons.set(id, { id, ...dungeon, createdAt: new Date().toISOString() }); } getDungeon(id) { return this.dungeons.get(id); } getAllDungeons() { return Array.from(this.dungeons.values()); } getDungeonsByDifficulty(difficulty) { return Array.from(this.dungeons.values()).filter(dungeon => dungeon.difficulty === difficulty); } createInstance(dungeonId, creatorId, playerIds = []) { const dungeon = this.getDungeon(dungeonId); if (!dungeon) { throw new Error('Dungeon not found'); } // Validate requirements if (playerIds.length > dungeon.maxPlayers) { throw new Error('Too many players for this dungeon'); } const instanceId = `instance_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const instance = { id: instanceId, dungeonId, creatorId, players: new Set([creatorId, ...playerIds]), currentEncounter: 0, status: 'active', startedAt: new Date().toISOString(), completedAt: null, progress: {}, rewards: null, deaths: new Map() // userId -> death count }; // Initialize progress tracking dungeon.encounters.forEach((encounter, index) => { instance.progress[index] = { completed: false, attempts: 0, deaths: 0 }; }); this.instances.set(instanceId, instance); // Track player instances for (const playerId of instance.players) { this.playerInstances.set(playerId, instanceId); } return instance; } getInstance(instanceId) { return this.instances.get(instanceId); } getPlayerInstance(userId) { const instanceId = this.playerInstances.get(userId); return instanceId ? this.getInstance(instanceId) : null; } joinInstance(instanceId, userId) { const instance = this.getInstance(instanceId); if (!instance) { throw new Error('Instance not found'); } if (instance.status !== 'active') { throw new Error('Instance is not active'); } const dungeon = this.getDungeon(instance.dungeonId); if (instance.players.size >= dungeon.maxPlayers) { throw new Error('Instance is full'); } // Remove from existing instance if any const existingInstanceId = this.playerInstances.get(userId); if (existingInstanceId) { this.leaveInstance(userId); } // Add to new instance instance.players.add(userId); this.playerInstances.set(userId, instanceId); return instance; } leaveInstance(userId) { const instanceId = this.playerInstances.get(userId); if (!instanceId) { return null; } const instance = this.getInstance(instanceId); if (instance) { instance.players.delete(userId); // If instance is empty, clean it up if (instance.players.size === 0) { this.instances.delete(instanceId); } } this.playerInstances.delete(userId); return instance; } startEncounter(instanceId, userId) { const instance = this.getInstance(instanceId); if (!instance) { throw new Error('Instance not found'); } if (!instance.players.has(userId)) { throw new Error('Player not in instance'); } const dungeon = this.getDungeon(instance.dungeonId); const encounter = dungeon.encounters[instance.currentEncounter]; if (!encounter) { throw new Error('No more encounters'); } return { encounter, encounterIndex: instance.currentEncounter, instance }; } completeEncounter(instanceId, userId, result) { const instance = this.getInstance(instanceId); if (!instance) { throw new Error('Instance not found'); } const dungeon = this.getDungeon(instance.dungeonId); const encounter = dungeon.encounters[instance.currentEncounter]; if (!encounter) { throw new Error('No encounter to complete'); } const progress = instance.progress[instance.currentEncounter]; progress.completed = true; progress.attempts++; if (result.deaths) { progress.deaths += result.deaths; instance.deaths.set(userId, (instance.deaths.get(userId) || 0) + result.deaths); } // Move to next encounter instance.currentEncounter++; // Check if dungeon is complete if (instance.currentEncounter >= dungeon.encounters.length) { return this.completeDungeon(instanceId); } return { success: true, nextEncounter: instance.currentEncounter < dungeon.encounters.length ? dungeon.encounters[instance.currentEncounter] : null, instance }; } completeDungeon(instanceId) { const instance = this.getInstance(instanceId); if (!instance) { throw new Error('Instance not found'); } const dungeon = this.getDungeon(instance.dungeonId); // Calculate rewards const rewards = this.calculateRewards(dungeon, instance); // Mark as completed instance.status = 'completed'; instance.completedAt = new Date().toISOString(); instance.rewards = rewards; // Remove players from instance tracking for (const playerId of instance.players) { this.playerInstances.delete(playerId); } return { success: true, dungeon, rewards, instance }; } calculateRewards(dungeon, instance) { const rewards = { experience: 0, credits: 0, items: [] }; // Calculate base rewards const expRange = dungeon.rewards.experience; const creditRange = dungeon.rewards.credits; rewards.experience = Math.floor(Math.random() * (expRange.max - expRange.min + 1)) + expRange.min; rewards.credits = Math.floor(Math.random() * (creditRange.max - creditRange.min + 1)) + creditRange.min; // Add death penalty const totalDeaths = Array.from(instance.deaths.values()).reduce((sum, deaths) => sum + deaths, 0); const deathPenalty = Math.floor(totalDeaths * 0.1); // 10% penalty per death rewards.experience = Math.max(0, rewards.experience - deathPenalty); rewards.credits = Math.max(0, rewards.credits - (deathPenalty * 2)); // Add items (chance based on performance) const itemChance = Math.max(0.1, 0.5 - (totalDeaths * 0.1)); // Lower chance with more deaths if (Math.random() < itemChance) { const itemPool = dungeon.rewards.items; if (itemPool.length > 0) { rewards.items.push(itemPool[Math.floor(Math.random() * itemPool.length)]); } } return rewards; } getAvailableDungeons(playerLevel, playerCount = 1) { return Array.from(this.dungeons.values()).filter(dungeon => dungeon.minLevel <= playerLevel && dungeon.maxLevel >= playerLevel && dungeon.maxPlayers >= playerCount ); } getDungeonStatistics(userId) { const instances = Array.from(this.instances.values()).filter(instance => instance.players.has(userId) ); const completed = instances.filter(instance => instance.status === 'completed'); const active = instances.filter(instance => instance.status === 'active'); return { totalDungeons: instances.length, completedDungeons: completed.length, activeDungeons: active.length, totalDeaths: Array.from(this.playerInstances.values()).length, recentCompletions: completed.slice(-5).map(instance => ({ dungeonId: instance.dungeonId, completedAt: instance.completedAt, rewards: instance.rewards })) }; } cleanupExpiredInstances() { const now = Date.now(); const expiredInstances = []; for (const [instanceId, instance] of this.instances) { const age = now - new Date(instance.startedAt).getTime(); const maxAge = 2 * 60 * 60 * 1000; // 2 hours if (age > maxAge && instance.status === 'active') { expiredInstances.push(instanceId); } } // Clean up expired instances for (const instanceId of expiredInstances) { const instance = this.instances.get(instanceId); if (instance) { for (const playerId of instance.players) { this.playerInstances.delete(playerId); } this.instances.delete(instanceId); } } return expiredInstances.length; } } module.exports = DungeonSystem;