API/GameServer/systems/DungeonSystem.js
2026-01-24 21:39:56 -04:00

508 lines
16 KiB
JavaScript

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