508 lines
16 KiB
JavaScript
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;
|