Game-Server/Galaxy-Strike-Online-main/Client/js/systems/DungeonSystem.js

833 lines
31 KiB
JavaScript

/**
* Galaxy Strike Online - Client Dungeon System
* Server-driven dungeon management client
*/
// Create global function for dungeon start that's more reliable
window.startDungeon = function(dungeonId) {
console.log('[DUNGEON SYSTEM] startDungeon called with:', dungeonId);
console.log('[DUNGEON SYSTEM] Game available:', !!window.game);
console.log('[DUNGEON SYSTEM] Game systems available:', !!(window.game && window.game.systems));
console.log('[DUNGEON SYSTEM] Dungeon system available:', !!(window.game && window.game.systems && window.game.systems.dungeonSystem));
if (window.game && window.game.systems && window.game.systems.dungeonSystem) {
return window.game.systems.dungeonSystem.startDungeon(dungeonId);
}
console.warn('[DUNGEON SYSTEM] Game systems not available for dungeon start');
};
// Create global function for process encounter that's more reliable
window.processEncounter = function() {
console.log('[DUNGEON SYSTEM] processEncounter called');
if (window.game && window.game.systems && window.game.systems.dungeonSystem) {
return window.game.systems.dungeonSystem.processEncounter();
}
console.warn('[DUNGEON SYSTEM] Game systems not available for process encounter');
};
// Create global function for dungeon toggle that's more reliable
window.toggleDungeonSection = function(sectionId) {
// Try to use the dungeon system if available
if (window.game && window.game.systems && window.game.systems.dungeonSystem) {
return window.game.systems.dungeonSystem.toggleDungeonSection(sectionId);
}
// Fallback: Direct DOM manipulation
const section = document.getElementById(sectionId);
const indicator = document.getElementById(`${sectionId}-indicator`);
if (!section || !indicator) {
console.warn('[DUNGEON SYSTEM] Section or indicator not found:', sectionId);
return;
}
const isCollapsed = section.classList.contains('collapsed');
if (isCollapsed) {
// Expand
section.classList.remove('collapsed');
indicator.classList.remove('fa-chevron-right');
indicator.classList.add('fa-chevron-down');
} else {
// Collapse
section.classList.add('collapsed');
indicator.classList.remove('fa-chevron-down');
indicator.classList.add('fa-chevron-right');
}
// Save the state in the dungeon system if available
if (window.game && window.game.systems && window.game.systems.dungeonSystem) {
window.game.systems.dungeonSystem.collapseStates.set(sectionId, !isCollapsed);
}
console.log(`[DUNGEON SYSTEM] Toggled section ${sectionId}: ${isCollapsed ? 'expanded' : 'collapsed'}`);
};
class DungeonSystem {
constructor(gameEngine) {
this.game = gameEngine;
// Current dungeon state (runtime only)
this.currentDungeon = null;
this.currentRoom = null;
this.dungeonProgress = 0;
this.isExploring = false;
// Debouncing to prevent multiple rapid clicks
this.lastProcessTime = 0;
this.processCooldown = 1000; // 1 second cooldown
// Prevent duplicate event processing
this.lastEncounterData = null;
this.lastNextRoomData = null;
// Store collapse states to preserve them during regeneration
this.collapseStates = new Map();
// Track last generation to prevent unnecessary regenerations
this.lastGenerationTime = 0;
this.generationThrottle = 500; // 500ms throttle
// Server dungeons data
this.serverDungeons = null;
this.roomTypes = {};
this.enemyTemplates = {};
console.log('[DUNGEON SYSTEM] Client DungeonSystem initialized - server-driven mode');
// Set up socket event listeners
this.setupSocketListeners();
}
/**
* Set up Socket.IO event listeners for dungeon data
*/
setupSocketListeners() {
if (!this.game.socket) {
console.warn('[DUNGEON SYSTEM] No socket available for event listeners');
return;
}
// Listen for dungeon data response
this.game.socket.on('dungeons_data', (data) => {
console.log('[DUNGEON SYSTEM] Received dungeons data:', data);
this.serverDungeons = data.dungeons || data;
console.log('[DUNGEON SYSTEM] Loaded grouped dungeons from server:', Object.keys(this.serverDungeons));
// Update UI when data is loaded
this.forceGenerateDungeonList();
});
// Listen for room types response
this.game.socket.on('room_types_data', (data) => {
console.log('[DUNGEON SYSTEM] Received room types data:', data);
this.roomTypes = data;
console.log(`[DUNGEON SYSTEM] Loaded ${Object.keys(this.roomTypes).length} room types from server`);
// Update UI when room data is loaded
this.forceGenerateDungeonList();
});
// Listen for enemy templates response
this.game.socket.on('enemy_templates_data', (data) => {
console.log('[DUNGEON SYSTEM] Received enemy templates data:', data);
this.enemyTemplates = data;
console.log(`[DUNGEON SYSTEM] Loaded ${Object.keys(this.enemyTemplates).length} enemy templates from server`);
// Update UI when enemy data is loaded
this.forceGenerateDungeonList();
});
// Listen for dungeon start response
this.game.socket.on('dungeon_started', (data) => {
console.log('[DUNGEON SYSTEM] Dungeon started:', data);
// Handle error responses
if (data.success === false) {
console.error('[DUNGEON SYSTEM] Failed to start dungeon:', data.error);
if (this.game && this.game.showNotification) {
this.game.showNotification(data.error, 'error', 5000);
}
return;
}
// Clear any existing dungeon state first
if (this.currentDungeon) {
console.warn('[DUNGEON SYSTEM] Clearing existing dungeon state before starting new one');
this.currentDungeon = null;
this.currentRoom = null;
this.isExploring = false;
this.dungeonProgress = 0;
}
this.currentDungeon = data.instance;
this.isExploring = true;
this.dungeonProgress = 0;
console.log('[DUNGEON SYSTEM] About to update UI - State:', {
currentDungeon: !!this.currentDungeon,
isExploring: this.isExploring,
dungeonProgress: this.dungeonProgress,
gameUIManager: !!this.game.systems.ui,
instanceId: this.currentDungeon?.id
});
// Update UI to show dungeon exploration
this.updateUI();
// Show notification to player
if (this.game && this.game.showNotification) {
this.game.showNotification(`Entered ${data.instance.dungeonId} dungeon!`, 'success', 3000);
}
});
// Listen for encounter response
this.game.socket.on('encounter_data', (data) => {
// Skip duplicate events
if (this.lastEncounterData &&
this.lastEncounterData.encounterIndex === data.encounterIndex &&
this.lastEncounterData.encounter?.name === data.encounter?.name) {
console.log('[DUNGEON SYSTEM] Skipping duplicate encounter data');
return;
}
this.lastEncounterData = data;
console.log('[DUNGEON SYSTEM] Encounter received:', data);
console.log('[DUNGEON SYSTEM] Current state before update:', {
currentDungeonId: this.currentDungeon?.id,
currentProgress: this.dungeonProgress,
newEncounterIndex: data.encounterIndex,
encounterType: data.encounter?.type,
encounterName: data.encounter?.name
});
this.currentRoom = data.encounter;
this.dungeonProgress = data.encounterIndex; // Use server data, not local increment
// Update UI to show the new encounter
this.updateUI();
});
// Listen for encounter completion (auto-combat)
this.game.socket.on('encounter_completed', (data) => {
console.log('[DUNGEON SYSTEM] Encounter completed:', data);
if (data.success) {
// Check if dungeon is complete
if (data.isComplete) {
console.log('[DUNGEON SYSTEM] Dungeon completed!');
// Clear all dungeon state
this.currentDungeon = null;
this.currentRoom = null;
this.dungeonProgress = 0;
this.isExploring = false;
this.lastEncounterData = null;
this.lastNextRoomData = null;
// Show completion notification
if (this.game && this.game.showNotification) {
this.game.showNotification('Dungeon completed! 🎉', 'success', 5000);
}
// Force UI to show dungeon list
setTimeout(() => {
this.updateUI();
}, 1000);
} else {
this.currentRoom = data.nextEncounter;
this.dungeonProgress = data.encounterIndex;
// Show rewards notification
if (data.rewards && (data.rewards.credits > 0 || data.rewards.experience > 0)) {
const rewardText = [];
if (data.rewards.credits > 0) rewardText.push(`${data.rewards.credits} credits`);
if (data.rewards.experience > 0) rewardText.push(`${data.rewards.experience} exp`);
if (this.game && this.game.showNotification) {
this.game.showNotification(`Combat complete! Gained: ${rewardText.join(', ')}`, 'success', 3000);
}
}
}
// Update UI to show the new state
this.updateUI();
} else {
console.error('[DUNGEON SYSTEM] Error completing encounter:', data.error);
}
});
// Listen for next room response
this.game.socket.on('next_room_data', (data) => {
// Skip duplicate events
if (this.lastNextRoomData &&
this.lastNextRoomData.encounterIndex === data.encounterIndex &&
this.lastNextRoomData.encounter?.name === data.encounter?.name) {
console.log('[DUNGEON SYSTEM] Skipping duplicate next room data');
return;
}
this.lastNextRoomData = data;
console.log('[DUNGEON SYSTEM] Next room received:', data);
console.log('[DUNGEON SYSTEM] Current state before update:', {
currentDungeonId: this.currentDungeon?.id,
currentProgress: this.dungeonProgress,
newEncounterIndex: data.encounterIndex,
encounterType: data.encounter?.type,
encounterName: data.encounter?.name,
isComplete: data.isComplete
});
if (data.success) {
this.currentRoom = data.encounter;
this.dungeonProgress = data.encounterIndex;
// Update UI to show the new room
this.updateUI();
} else {
console.error('[DUNGEON SYSTEM] Error moving to next room:', data.error);
}
});
// Listen for dungeon completion response
this.game.socket.on('dungeon_completed', (data) => {
console.log('[DUNGEON SYSTEM] Dungeon completed:', data);
// Reset dungeon state
this.currentDungeon = null;
this.currentRoom = null;
this.isExploring = false;
this.dungeonProgress = 0;
});
// Listen for dungeon status response
this.game.socket.on('dungeon_status', (data) => {
console.log('[DUNGEON SYSTEM] Dungeon status received:', data);
});
console.log('[DUNGEON SYSTEM] Socket event listeners set up');
}
/**
* Load dungeon data from server using Socket.IO packets
*/
async loadServerData() {
try {
console.log('[DUNGEON SYSTEM] Loading dungeon data from server via packets...');
if (!this.game.socket) {
console.error('[DUNGEON SYSTEM] No socket connection available');
return;
}
// Request dungeons from server
this.game.socket.emit('get_dungeons');
// Request room types from server
this.game.socket.emit('get_room_types');
// Request enemy templates from server
this.game.socket.emit('get_enemy_templates');
console.log('[DUNGEON SYSTEM] Server data requests sent via packets');
} catch (error) {
console.error('[DUNGEON SYSTEM] Error loading server data:', error);
}
}
/**
* Get all available dungeons
*/
getAllDungeons() {
return this.serverDungeons;
}
/**
* Get dungeons by difficulty
*/
getDungeonsByDifficulty(difficulty) {
return this.serverDungeons.filter(dungeon => dungeon.difficulty === difficulty);
}
/**
* Get specific dungeon by ID
*/
getDungeon(dungeonId) {
return this.serverDungeons.find(dungeon => dungeon.id === dungeonId);
}
/**
* Get room type by ID
*/
getRoomType(roomTypeId) {
return this.roomTypes[roomTypeId];
}
/**
* Get enemy template by ID
*/
getEnemyTemplate(enemyId) {
return this.enemyTemplates[enemyId];
}
/**
* Start exploring a dungeon using Socket.IO packets
*/
async startDungeon(dungeonId) {
try {
console.log(`[DUNGEON SYSTEM] Starting dungeon: ${dungeonId}`);
if (!this.game.socket) {
console.error('[DUNGEON SYSTEM] No socket connection available');
return null;
}
// Send packet to start dungeon
this.game.socket.emit('start_dungeon', {
dungeonId: dungeonId,
userId: this.game.systems.player?.id || 'anonymous'
});
console.log('[DUNGEON SYSTEM] Dungeon start packet sent');
return true;
} catch (error) {
console.error('[DUNGEON SYSTEM] Error starting dungeon:', error);
return null;
}
}
/**
* Process encounter in current dungeon room
*/
async processEncounter() {
// Debounce to prevent multiple rapid clicks
const now = Date.now();
if (now - this.lastProcessTime < this.processCooldown) {
console.log('[DUNGEON SYSTEM] Process throttled, please wait...');
return null;
}
this.lastProcessTime = now;
try {
// Safety check - make sure we have an active dungeon
if (!this.currentDungeon) {
console.error('[DUNGEON SYSTEM] No active dungeon to process encounter for');
return null;
}
console.log(`[DUNGEON SYSTEM] Processing encounter for dungeon: ${this.currentDungeon.id}`);
if (!this.game.socket) {
console.error('[DUNGEON SYSTEM] No socket connection available');
return null;
}
// Send packet to process encounter
this.game.socket.emit('process_encounter', {
instanceId: this.currentDungeon.id,
userId: this.game.systems.player?.id || 'anonymous'
});
console.log('[DUNGEON SYSTEM] Encounter process packet sent');
return true;
} catch (error) {
console.error('[DUNGEON SYSTEM] Error processing encounter:', error);
return null;
}
}
/**
* Complete current dungeon using Socket.IO packets
*/
async completeDungeon() {
if (!this.currentDungeon || !this.isExploring) {
console.warn('[DUNGEON SYSTEM] No active dungeon to complete');
return null;
}
try {
console.log(`[DUNGEON SYSTEM] Completing dungeon: ${this.currentDungeon.id}`);
if (!this.game.socket) {
console.error('[DUNGEON SYSTEM] No socket connection available');
return null;
}
// Send packet to complete dungeon
this.game.socket.emit('complete_dungeon', {
instanceId: this.currentDungeon.id,
userId: this.game.systems.player?.id || 'anonymous'
});
console.log('[DUNGEON SYSTEM] Dungeon completion packet sent');
return true;
} catch (error) {
console.error('[DUNGEON SYSTEM] Error completing dungeon:', error);
return null;
}
}
/**
* Get player's current dungeon status using Socket.IO packets
*/
async getDungeonStatus() {
try {
if (!this.game.socket) {
console.error('[DUNGEON SYSTEM] No socket connection available');
return null;
}
// Send packet to get dungeon status
this.game.socket.emit('get_dungeon_status', {
userId: this.game.systems.player?.id || 'anonymous'
});
console.log('[DUNGEON SYSTEM] Dungeon status request packet sent');
return true;
} catch (error) {
console.error('[DUNGEON SYSTEM] Error getting dungeon status:', error);
}
return null;
}
/**
* Force generate dungeon list (bypasses throttle)
*/
forceGenerateDungeonList() {
this.lastGenerationTime = 0; // Reset throttle
this.generateDungeonList();
}
/**
* Generate dungeon list UI using server data
*/
generateDungeonList() {
const now = Date.now();
// Throttle generation to prevent excessive calls
if (now - this.lastGenerationTime < this.generationThrottle) {
return; // Silently skip instead of logging
}
this.lastGenerationTime = now;
// console.log('[DUNGEON SYSTEM] Generating dungeon list UI');
const dungeonListElement = document.getElementById('dungeonList');
if (!dungeonListElement) {
console.error('[DUNGEON SYSTEM] Dungeon list element not found');
return;
}
// Clear existing content
dungeonListElement.innerHTML = '';
if (!this.serverDungeons || Object.keys(this.serverDungeons).length === 0) {
dungeonListElement.innerHTML = '<p>Loading dungeons from server...</p>';
return;
}
// Generate HTML for each difficulty category
let html = '';
Object.entries(this.serverDungeons).forEach(([difficulty, dungeons]) => {
if (!dungeons || dungeons.length === 0) return;
const difficultyClass = difficulty === 'tutorial' ? 'tutorial' : difficulty;
const difficultyTitle = difficulty === 'tutorial' ? 'Tutorial Dungeons' :
difficulty.charAt(0).toUpperCase() + difficulty.slice(1) + ' Dungeons';
const difficultyIcon = this.getDifficultyIcon(difficulty);
const sectionId = `dungeon-section-${difficulty}`;
// Add collapsible difficulty header
html += `
<div class="dungeon-section">
<div class="difficulty-header ${difficultyClass} collapsible" onclick="toggleDungeonSection('${sectionId}')">
<div class="header-content">
<i class="${difficultyIcon}"></i>
<span>${difficultyTitle}</span>
<span class="dungeon-count">(${dungeons.length})</span>
</div>
<div class="collapse-indicator">
<i class="fas fa-chevron-down" id="${sectionId}-indicator"></i>
</div>
</div>
<div class="dungeon-content" id="${sectionId}">
`;
dungeons.forEach(dungeon => {
const canEnter = this.canEnterDungeon(dungeon);
const statusClass = canEnter ? 'available' : 'locked';
const energyCost = dungeon.energyCost || 0;
const healthType = dungeon.healthType || 'player';
const healthIcon = healthType === 'ship' ? '🚀' : '👤';
// Each dungeon in its own individual container using proper CSS classes
html += `
<div class="dungeon-item ${statusClass}" data-dungeon-id="${dungeon.id}">
<div class="dungeon-name">${dungeon.name}</div>
<div class="dungeon-difficulty ${difficulty}">
<i class="${difficultyIcon}"></i> ${difficulty} - ${energyCost} Energy
</div>
<div class="dungeon-description">${dungeon.description}</div>
<div class="health-type">${healthIcon}</div>
<div class="dungeon-enemies">
<strong>Enemies:</strong>
<div class="enemy-list">
${this.generateEnemyList(dungeon.enemyTypes || [])}
</div>
</div>
<button class="dungeon-btn" ${!canEnter ? 'disabled' : ''}
onclick="startDungeon('${dungeon.id}')">
${canEnter ? 'Enter Dungeon' : 'Locked'}
</button>
</div>
`;
});
// Close the section
html += `
</div>
</div>
`;
});
dungeonListElement.innerHTML = html;
// Initialize default collapse states
this.initializeDungeonSections();
}
/**
* Initialize dungeon sections with saved collapse states
*/
initializeDungeonSections() {
// Default states: tutorial and easy expanded, others collapsed
const defaultStates = {
'dungeon-section-tutorial': false, // expanded
'dungeon-section-easy': false, // expanded
'dungeon-section-medium': true, // collapsed
'dungeon-section-hard': true, // collapsed
'dungeon-section-extreme': true // collapsed
};
Object.entries(defaultStates).forEach(([sectionId, defaultCollapsed]) => {
const section = document.getElementById(sectionId);
const indicator = document.getElementById(`${sectionId}-indicator`);
if (section && indicator) {
// Use saved state if available, otherwise use default
const shouldCollapse = this.collapseStates.has(sectionId) ?
this.collapseStates.get(sectionId) : defaultCollapsed;
if (shouldCollapse) {
section.classList.add('collapsed');
indicator.classList.remove('fa-chevron-down');
indicator.classList.add('fa-chevron-right');
}
}
});
}
/**
* Toggle dungeon section collapse/expand
*/
toggleDungeonSection(sectionId) {
// Check if game and systems are available
if (!window.game || !window.game.systems || !window.game.systems.dungeonSystem) {
console.warn('[DUNGEON SYSTEM] Game systems not available for toggle');
return;
}
const section = document.getElementById(sectionId);
const indicator = document.getElementById(`${sectionId}-indicator`);
if (!section || !indicator) return;
const isCollapsed = section.classList.contains('collapsed');
if (isCollapsed) {
// Expand
section.classList.remove('collapsed');
indicator.classList.remove('fa-chevron-right');
indicator.classList.add('fa-chevron-down');
} else {
// Collapse
section.classList.add('collapsed');
indicator.classList.remove('fa-chevron-down');
indicator.classList.add('fa-chevron-right');
}
// Save the state
this.collapseStates.set(sectionId, !isCollapsed);
console.log(`[DUNGEON SYSTEM] Toggled section ${sectionId}: ${isCollapsed ? 'expanded' : 'collapsed'}`);
}
/**
* Get difficulty icon for dungeon
*/
getDifficultyIcon(difficulty) {
const icons = {
tutorial: 'fas fa-graduation-cap',
easy: 'fas fa-smile',
medium: 'fas fa-meh',
hard: 'fas fa-frown',
extreme: 'fas fa-skull'
};
return icons[difficulty] || 'fas fa-question';
}
/**
* Generate enemy list HTML for dungeon
*/
generateEnemyList(enemyTypes) {
if (!enemyTypes || enemyTypes.length === 0) {
return '<span class="no-enemies">No enemies</span>';
}
let html = '';
enemyTypes.forEach(enemyType => {
const enemy = this.getEnemyTemplate(enemyType);
if (enemy) {
html += `
<div class="enemy-item">
<span class="enemy-name">${enemy.name}</span>
<div class="enemy-stats">
<span class="health">❤️ ${enemy.health}</span>
<span class="attack">⚔️ ${enemy.attack}</span>
<span class="defense">🛡️ ${enemy.defense}</span>
</div>
</div>
`;
}
});
return html;
}
/**
* Check if player can enter dungeon
*/
canEnterDungeon(dungeon) {
if (!this.game.systems.player) {
return false;
}
const playerLevel = this.game.systems.player.stats?.level || 1;
const minLevel = dungeon.minLevel || 1;
const maxLevel = dungeon.maxLevel || 999;
const energyCost = dungeon.energyCost || 0;
const playerEnergy = this.game.systems.player.attributes?.energy || 0;
return playerLevel >= minLevel &&
playerLevel <= maxLevel &&
playerEnergy >= energyCost;
}
/**
* Exit current dungeon
*/
exitDungeon() {
console.log('[DUNGEON SYSTEM] Exiting dungeon');
if (this.currentDungeon) {
// Send exit packet to server
if (this.game.socket) {
this.game.socket.emit('exit_dungeon', {
instanceId: this.currentDungeon.id,
userId: this.game.systems.player?.id || 'anonymous'
});
}
}
// Reset local state
this.currentDungeon = null;
this.currentRoom = null;
this.isExploring = false;
this.dungeonProgress = 0;
// Update UI to show dungeon list
this.updateUI();
// Show notification
if (this.game && this.game.showNotification) {
this.game.showNotification('Exited dungeon', 'info', 2000);
}
}
/**
* Move to next room (for rooms without enemies)
*/
moveToNextRoom() {
console.log('[DUNGEON SYSTEM] Moving to next room');
if (!this.currentDungeon) {
console.warn('[DUNGEON SYSTEM] No active dungeon to continue');
return;
}
// Request next room from server
if (this.game.socket) {
this.game.socket.emit('next_room', {
instanceId: this.currentDungeon.id,
userId: this.game.systems.player?.id || 'anonymous'
});
}
}
/**
* Update UI with current dungeon information
*/
updateUI() {
if (this.game.systems.ui) {
this.game.systems.ui.updateDungeonUI({
currentDungeon: this.currentDungeon,
currentRoom: this.currentRoom,
progress: this.dungeonProgress,
isExploring: this.isExploring
});
} else {
console.warn('[DUNGEON SYSTEM] UI manager not available in game.systems.ui');
}
}
/**
* Initialize system and load server data
*/
async initialize() {
console.log('[DUNGEON SYSTEM] Initializing client dungeon system...');
// Set up socket listeners if not already done
if (!this.game.socket) {
console.warn('[DUNGEON SYSTEM] Socket not available during initialization, will retry...');
// Retry after a short delay
setTimeout(() => {
if (this.game.socket) {
this.setupSocketListeners();
this.loadServerData();
} else {
console.error('[DUNGEON SYSTEM] Socket still not available after retry');
}
}, 1000);
return;
}
this.setupSocketListeners();
await this.loadServerData();
}
}
// Export DungeonSystem to global scope
if (typeof window !== 'undefined') {
window.DungeonSystem = DungeonSystem;
}
// Export for use in GameEngine
if (typeof module !== 'undefined' && module.exports) {
module.exports = DungeonSystem;
}