/** * 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 = '

Loading dungeons from server...

'; 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 += `
${difficultyTitle} (${dungeons.length})
`; 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 += `
${dungeon.name}
${difficulty} - ${energyCost} Energy
${dungeon.description}
${healthIcon}
Enemies:
${this.generateEnemyList(dungeon.enemyTypes || [])}
`; }); // Close the section html += `
`; }); 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 'No enemies'; } let html = ''; enemyTypes.forEach(enemyType => { const enemy = this.getEnemyTemplate(enemyType); if (enemy) { html += `
${enemy.name}
❤️ ${enemy.health} ⚔️ ${enemy.attack} 🛡️ ${enemy.defense}
`; } }); 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; }