/** * Galaxy Strike Online - Game Engine * Core game loop and state management */ class GameEngine extends EventTarget { constructor() { // Must call super() first since we extend EventTarget super(); // Basic game state this.isRunning = false; this.isPaused = false; this.gameTime = 0; this.lastSaveTime = 0; this.autoSaveInterval = 5000; // 5 seconds this.gameLogicInterval = 1000; // 1 second for game updates // Game systems this.systems = {}; // Save slot configuration this.saveSlotInfo = { slot: 1, useFileSystem: true }; // Game state this.state = { paused: false, currentTab: 'dashboard', notifications: [] }; // Event listeners this.eventListeners = new Map(); // Initialize immediately this.init(); } setMultiplayerMode(isMultiplayer, socket = null, serverData = null, currentUser = null) { const debugLogger = window.debugLogger; console.log('[GAME ENGINE] Setting multiplayer mode:', isMultiplayer); console.log('[GAME ENGINE] Previous mode was:', this.isMultiplayer); if (debugLogger) debugLogger.logStep('setMultiplayerMode', { isMultiplayer, previousMode: this.isMultiplayer }); // CRITICAL: Once set to multiplayer, never allow fallback to singleplayer if (this.isMultiplayer && !isMultiplayer) { console.warn('[GAME ENGINE] ATTEMPTED FALLBACK TO SINGLEPLAYER - BLOCKING!'); console.log('[GAME ENGINE] Preserving multiplayer mode'); return; // Don't allow fallback to singleplayer } this.isMultiplayer = isMultiplayer; this.socket = socket; this.serverData = serverData; this.currentUser = currentUser; // Store multiplayer settings for systems that need them this.multiplayerConfig = { isMultiplayer, socket, serverData, currentUser }; console.log('[GAME ENGINE] Multiplayer mode configured:', { isMultiplayer, hasSocket: !!socket, hasServerData: !!serverData, hasCurrentUser: !!currentUser }); } // Get random integer between min and max (inclusive) getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } // Get random float between min and max getRandomFloat(min, max) { return Math.random() * (max - min) + min; } async init() { console.log('[GAME ENGINE] Initializing game engine'); const logger = window.logger; const debugLogger = window.debugLogger; if (logger) await logger.info('Initializing game engine'); if (debugLogger) await debugLogger.startStep('gameEngineInit'); try { // In multiplayer mode, use simplified initialization to avoid hanging if (this.isMultiplayer) { console.log('[GAME ENGINE] Using simplified multiplayer initialization'); try { await this.initializeMultiplayerSystems(); console.log('[GAME ENGINE] Multiplayer initialization complete - skipping event listeners'); } catch (multiplayerError) { console.error('[GAME ENGINE] Multiplayer systems initialization failed:', multiplayerError); // Don't fall back to singleplayer - keep multiplayer mode but with minimal systems console.log('[GAME ENGINE] Continuing with minimal multiplayer systems'); // Create essential systems only this.systems.player = new Player(this); this.systems.inventory = new Inventory(this); this.systems.economy = new Economy(this); this.systems.itemSystem = new ItemSystem(this); this.systems.idleSystem = new IdleSystem(this); } } else { // Full initialization for singleplayer await this.initializeSystemsForLoad(); if (debugLogger) await debugLogger.logStep('Systems initialized, setting up event listeners'); // Set up event listeners (only in singleplayer to avoid conflicts) await this.setupEventListeners(); } if (debugLogger) await debugLogger.endStep('gameEngineInit', { systemsInitialized: Object.keys(this.systems).length, eventListeners: this.eventListeners.size }); } catch (error) { console.error('Failed to initialize game:', error); if (logger) await logger.errorEvent(error, 'Game Engine Initialization'); if (debugLogger) await debugLogger.errorEvent(error, 'Game Engine Initialization'); } } // Simplified multiplayer-only system initialization to avoid hanging async initializeMultiplayerSystems() { console.log('[GAME ENGINE] Initializing multiplayer systems'); try { // Initialize texture manager first this.systems.textureManager = new TextureManager(this); // Create essential systems immediately console.log('[GAME ENGINE] Creating essential systems'); this.systems.player = new Player(this); this.systems.inventory = new Inventory(this); this.systems.economy = new Economy(this); this.systems.ui = new UIManager(this); this.systems.idleSystem = new IdleSystem(this); this.systems.itemSystem = new ItemSystem(this); console.log('[GAME ENGINE] Essential systems created successfully'); // Initialize ItemSystem ONCE and emit event when ready console.log('[GAME ENGINE] Initializing ItemSystem (single initialization)'); this.systems.itemSystem.initialize().then(() => { console.log('[GAME ENGINE] ItemSystem fully initialized, emitting ready event'); this.emit('itemSystemReady'); }).catch(error => { console.error('[GAME ENGINE] ItemSystem initialization failed:', error); // Still emit event so Economy can fallback gracefully this.emit('itemSystemReady'); }); // Initialize Economy (without ItemSystem - it will wait for the event) console.log('[GAME ENGINE] Initializing Economy system'); this.systems.economy.initialize().catch(error => { console.error('[GAME ENGINE] Economy initialization failed:', error); }); // Create additional systems asynchronously to avoid blocking setTimeout(() => { console.log('[GAME ENGINE] Creating additional multiplayer systems asynchronously'); if (typeof SkillSystem !== 'undefined') { this.systems.skillSystem = new SkillSystem(this); } if (typeof DungeonSystem !== 'undefined') { this.systems.dungeonSystem = new DungeonSystem(this); // Initialize server-driven dungeon system this.systems.dungeonSystem.initialize().then(() => { console.log('[GAME ENGINE] DungeonSystem initialized with server data'); }).catch(error => { console.error('[GAME ENGINE] Failed to initialize DungeonSystem:', error); }); } if (typeof QuestSystem !== 'undefined') { this.systems.questSystem = new QuestSystem(this); console.log('[GAME ENGINE] QuestSystem created'); } if (typeof CraftingSystem !== 'undefined') { this.systems.crafting = new CraftingSystem(this); } if (typeof BaseSystem !== 'undefined') { this.systems.base = new BaseSystem(this); } console.log('[GAME ENGINE] All multiplayer systems created asynchronously'); }, 100); // Create after 100ms delay } catch (error) { console.error('[GAME ENGINE] Error in multiplayer systems initialization:', error); // Don't re-throw - allow game to continue with basic systems console.log('[GAME ENGINE] Continuing with available systems'); } } async initializeSystemsForLoad() { const logger = window.logger; const debugLogger = window.debugLogger; if (debugLogger) await debugLogger.startStep('initializeSystemsForLoad'); if (logger) { await logger.timeAsync('Game Systems Initialization for Load', async () => { await logger.info('Initializing game systems for loading'); if (debugLogger) await debugLogger.logStep('Initializing TextureManager'); // Initialize texture manager first this.systems.textureManager = new TextureManager(this); if (logger) await logger.systemEvent('TextureManager', 'Initialized'); if (debugLogger) await debugLogger.logStep('TextureManager initialized'); if (debugLogger) await debugLogger.logStep('Creating Player system (without initialization)'); // Create systems but don't initialize with default data this.systems.player = new Player(this); if (logger) await logger.systemEvent('Player', 'Created'); if (debugLogger) await debugLogger.logStep('Player system created'); if (debugLogger) await debugLogger.logStep('Creating Inventory system (without initialization)'); this.systems.inventory = new Inventory(this); if (logger) await logger.systemEvent('Inventory', 'Created'); if (debugLogger) await debugLogger.logStep('Inventory system created'); if (debugLogger) await debugLogger.logStep('Creating Economy system (without initialization)'); this.systems.economy = new Economy(this); if (logger) await logger.systemEvent('Economy', 'Created'); if (debugLogger) await debugLogger.logStep('Economy system created'); // In multiplayer mode, skip singleplayer systems if (!this.isMultiplayer) { if (debugLogger) await debugLogger.logStep('Creating IdleSystem'); this.systems.idleSystem = new IdleSystem(this); if (logger) await logger.systemEvent('IdleSystem', 'Created'); if (debugLogger) await debugLogger.logStep('IdleSystem created'); if (debugLogger) await debugLogger.logStep('Creating ItemSystem'); this.systems.itemSystem = new ItemSystem(this); if (logger) await logger.systemEvent('ItemSystem', 'Created'); if (debugLogger) await debugLogger.logStep('ItemSystem created'); } else { console.log('[GAME ENGINE] Multiplayer mode - skipping singleplayer systems (IdleSystem, ItemSystem)'); } if (debugLogger) await debugLogger.logStep('Creating UIManager'); if (typeof UIManager !== 'undefined') { console.log('[GAME ENGINE] UIManager class found, creating real UIManager'); this.systems.ui = new UIManager(this); // Expose UIManager globally for button onclick handlers window.uiManager = this.systems.ui; window.game.systems.ui = this.systems.ui; if (logger) await logger.systemEvent('UIManager', 'Created'); if (debugLogger) await debugLogger.logStep('UIManager created and exposed'); } else { console.error('[GAME ENGINE] UIManager class not found - this should not happen!'); if (debugLogger) await debugLogger.error('UIManager class not found - this should not happen!'); } if (debugLogger) await debugLogger.endStep('initializeSystemsForLoad', { systemsCreated: Object.keys(this.systems).length }); }); } } async startGame(continueGame = false) { const logger = window.logger; const debugLogger = window.debugLogger; console.log('[GAME ENGINE] startGame called with continueGame =', continueGame); if (logger) await logger.info('Starting game', { continueGame }); if (debugLogger) await debugLogger.startStep('startGame', { continueGame }); try { if (continueGame) { console.log('[GAME ENGINE] Loading existing save data...'); if (debugLogger) await debugLogger.logStep('Loading existing save data'); await this.loadGame(); console.log('[GAME ENGINE] Save data loaded'); } else { console.log('[GAME ENGINE] Creating new game...'); if (debugLogger) await debugLogger.logStep('Creating new game'); await this.newGame(); console.log('[GAME ENGINE] New game created'); } // Start game loop this.start(); console.log('[GAME ENGINE] Game loop started'); if (debugLogger) await debugLogger.logStep('Game loop started'); const loadingStatus = document.getElementById('loadingStatus'); if (loadingStatus) { console.log('[GAME ENGINE] Hiding loading status text'); if (debugLogger) await debugLogger.logStep('Hiding loading status text'); loadingStatus.classList.add('hidden'); } const gameInterface = document.getElementById('gameInterface'); if (gameInterface) { console.log('[GAME ENGINE] Showing game interface'); if (debugLogger) await debugLogger.logStep('Showing game interface'); gameInterface.classList.remove('hidden'); } else { console.warn('[GAME ENGINE] gameInterface element not found'); if (debugLogger) await debugLogger.warn('gameInterface element not found'); } if (logger) await logger.info('Game started successfully'); if (debugLogger) await debugLogger.endStep('startGame', { continueGame, isRunning: this.isRunning, gameTime: this.gameTime }); } catch (error) { console.error('[GAME ENGINE] Failed to start game:', error); if (logger) await logger.errorEvent(error, 'Game Start'); if (debugLogger) await debugLogger.errorEvent(error, 'Game Start'); } } start() { const debugLogger = window.debugLogger; if (this.isRunning) { if (debugLogger) debugLogger.log('GameEngine.start() called but game is already running', { isRunning: this.isRunning, gameTime: this.gameTime }); return; } if (debugLogger) debugLogger.logStep('Starting game engine', { gameLogicInterval: this.gameLogicInterval }); this.isRunning = true; this.lastUpdate = Date.now(); // Start game logic timer (completely independent of frame rate) console.log('[GAME ENGINE] Starting game logic timer with interval:', this.gameLogicInterval); this.gameLogicTimer = setInterval(() => { this.updateGameLogic(); }, this.gameLogicInterval); // Start auto-save this.startAutoSave(); console.log('[GAME ENGINE] Game engine started'); if (debugLogger) debugLogger.logStep('Game engine started successfully', { gameLogicInterval: this.gameLogicInterval, autoSaveInterval: this.autoSaveInterval }); } updateGameLogic() { const debugLogger = window.debugLogger; // Use fixed 1-second interval for all updates const fixedDelta = 1000; // 1 second in milliseconds if (this.state.paused) { if (debugLogger) debugLogger.logStep('Game logic update called but game is paused', { gameTime: this.gameTime, fixedDelta: fixedDelta }); return; } this.gameTime += fixedDelta; // Update player play time with fixed delta if (this.systems.player && this.systems.player.updatePlayTime) { this.systems.player.updatePlayTime(fixedDelta); } // Update all systems with fixed delta for (const [name, system] of Object.entries(this.systems)) { if (system && typeof system.update === 'function') { try { system.update(fixedDelta); } catch (error) { console.error(`[GAME ENGINE] Error updating ${name} system:`, error); if (debugLogger) debugLogger.errorEvent(error, `Update ${name} system`); } } } // Update UI displays (money, gems, energy) after system updates if (this.systems && this.systems.ui) { try { this.systems.ui.updateUI(); } catch (error) { console.error('[GAME ENGINE] Error updating UI:', error); } } // Emit game updated event this.emit('gameUpdated', { gameTime: this.gameTime }); } startAutoSave() { const debugLogger = window.debugLogger; // Load saved interval or use default const savedInterval = localStorage.getItem('autoSaveInterval'); this.autoSaveInterval = savedInterval ? parseInt(savedInterval) : 5; console.log(`[GAME ENGINE] Starting auto-save with ${this.autoSaveInterval} minute interval`); // Clear any existing timer this.stopAutoSave(); // Set up new timer this.autoSaveTimer = setInterval(async () => { console.log('[GAME ENGINE] Auto-save timer triggered - isRunning:', this.isRunning, 'paused:', this.state.paused); if (this.isRunning && !this.state.paused) { console.log('[GAME ENGINE] Auto-saving game...'); try { // In multiplayer mode, save to server if (window.smartSaveManager?.isMultiplayer) { console.log('[GAME ENGINE] Auto-saving to server...'); if (this.socket) { this.socket.emit('saveGameData', { timestamp: Date.now(), gameTime: this.gameTime }); } else { console.warn('[GAME ENGINE] No socket available for server save'); } } else { // Singleplayer mode - local save (not implemented yet) console.log('[GAME ENGINE] Local auto-save not implemented'); } this.showNotification('Game auto-saved', 'info', 2000); console.log('[GAME ENGINE] Auto-save completed successfully'); } catch (error) { console.error('[GAME ENGINE] Auto-save failed:', error); if (debugLogger) await debugLogger.errorEvent(error, 'Auto-save'); } } else { console.log('[GAME ENGINE] Auto-save skipped - game not running or paused'); } }, this.autoSaveInterval * 60 * 1000); // Convert minutes to milliseconds } stopAutoSave() { if (this.autoSaveTimer) { console.log('[GAME ENGINE] Stopping auto-save timer'); clearInterval(this.autoSaveTimer); this.autoSaveTimer = null; } } // Notification system async showNotification(message, type = 'info', duration = 3000) { const logger = window.logger; if (logger) await logger.playerAction('Notification', { message, type, duration }); const notification = { id: Date.now(), message, type, duration, timestamp: Date.now() }; this.state.notifications.push(notification); // Auto-remove notification after duration setTimeout(() => { this.removeNotification(notification.id); }, duration); // Update UI if (this.systems.ui) { // UI updates handled by individual systems } } removeNotification(id) { this.state.notifications = this.state.notifications.filter(notification => notification.id !== id); } // Event system on(event, callback) { if (!this.eventListeners.has(event)) { this.eventListeners.set(event, []); } this.eventListeners.get(event).push(callback); } emit(event, data) { if (this.eventListeners.has(event)) { this.eventListeners.get(event).forEach(callback => { try { callback(data); } catch (error) { console.error(`[GAME ENGINE] Error in event listener for ${event}:`, error); } }); } // Also dispatch as DOM event for UIManager this.dispatchEvent(new CustomEvent(event, { detail: data })); } // Utility methods formatNumber(num) { if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B'; if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M'; if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K'; return num.toString(); } formatTime(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = Math.floor(seconds % 60); if (hours > 0) { return `${hours}h ${minutes}m ${secs}s`; } else if (minutes > 0) { return `${minutes}m ${secs}s`; } else { return `${secs}s`; } } getPerformanceStats() { const debugLogger = window.debugLogger; const stats = { gameTime: this.gameTime, isRunning: this.isRunning, lastUpdate: this.lastUpdate, memory: null }; // Add memory info if available if (window.performance && window.performance.memory) { stats.memory = { used: window.performance.memory.usedJSHeapSize, total: window.performance.memory.totalJSHeapSize, limit: window.performance.memory.jsHeapSizeLimit }; } return stats; } // Load server player data (transforms server format to client format) async loadServerPlayerData(playerData) { console.log('[GAME ENGINE] Loading server player data with format transformation'); console.log('[GAME ENGINE] Original server data structure:', playerData); // Transform server data format to client format const transformedData = { ...playerData, // Transform quests from server format to client format quests: playerData.quests ? { mainQuests: playerData.quests.main || [], dailyQuests: playerData.quests.daily || [], weeklyQuests: playerData.quests.weekly || [], tutorialQuests: playerData.quests.tutorial || [], activeQuests: playerData.quests.active || [], completedQuests: playerData.quests.completed || [] } : { mainQuests: [], dailyQuests: [], weeklyQuests: [], tutorialQuests: [], activeQuests: [], completedQuests: [] } }; // DEBUG: Log quest data transformation console.log('[GAME ENGINE] Quest data transformation:', { serverQuests: playerData.quests, transformedQuests: transformedData.quests, mainQuestsCount: transformedData.quests.mainQuests.length, dailyQuestsCount: transformedData.quests.dailyQuests.length, weeklyQuestsCount: transformedData.quests.weeklyQuests.length, tutorialQuestsCount: transformedData.quests.tutorialQuests.length }); // Use crafting data from server or initialize empty transformedData.crafting = playerData.crafting || { skill: 1, experience: 0, knownRecipes: [], completedDungeons: [], currentInstance: null, dungeonProgress: {} }; return transformedData; } async loadPlayerData(playerData) { console.log('[GAME ENGINE] Loading server player data'); console.log('[GAME ENGINE] Full playerData structure:', playerData); console.log('[GAME ENGINE] PlayerData keys:', Object.keys(playerData)); try { // Apply basic player stats if (playerData.stats && this.systems && this.systems.player) { console.log('[GAME ENGINE] Found player stats and player system, applying...'); console.log('[GAME ENGINE] Server playerData.stats:', playerData.stats); console.log('[GAME ENGINE] Server playerData keys:', Object.keys(playerData)); // Check for playTime in different possible locations const possiblePlayTimeFields = [ playerData.stats?.playTime, playerData.playTime, playerData.totalPlayTime, playerData.stats?.totalPlayTime ]; console.log('[GAME ENGINE] Possible playTime fields found:', possiblePlayTimeFields); // Preserve existing playTime if server doesn't provide it const existingPlayTime = this.systems.player.stats.playTime || 0; console.log('[GAME ENGINE] Preserving existing playTime:', existingPlayTime); this.systems.player.load(playerData.stats); console.log('[GAME ENGINE] Applied player stats:', playerData.stats); // Restore playTime if it was lost if (!this.systems.player.stats.playTime || this.systems.player.stats.playTime === 0) { this.systems.player.stats.playTime = existingPlayTime; console.log('[GAME ENGINE] Restored playTime to:', existingPlayTime); } console.log('[GAME ENGINE] Final playTime after load:', this.systems.player.stats.playTime); // Apply credits from server data to economy system if (playerData.stats.credits !== undefined && this.systems.economy) { this.systems.economy.credits = playerData.stats.credits; console.log('[GAME ENGINE] Applied credits from server:', playerData.stats.credits); } // Apply gems from server data to economy system if (playerData.stats.gems !== undefined && this.systems.economy) { this.systems.economy.gems = playerData.stats.gems; console.log('[GAME ENGINE] Applied gems from server:', playerData.stats.gems); } // Force manual sync to ensure economy is updated if (this.systems.economy && this.systems.economy.syncWithServerData) { console.log('[GAME ENGINE] Forcing manual economy sync'); this.systems.economy.syncWithServerData(playerData); } // Request fresh economy data from server to ensure sync if (this.systems.economy && this.systems.economy.requestEconomyData) { setTimeout(() => { this.systems.economy.requestEconomyData(); }, 1000); // Delay to ensure socket is ready } // Apply energy from server data to player attributes if (playerData.stats.currentEnergy !== undefined && this.systems.player.attributes) { this.systems.player.attributes.currentEnergy = playerData.stats.currentEnergy; console.log('[GAME ENGINE] Applied current energy from server:', playerData.stats.currentEnergy); } if (playerData.stats.maxEnergy !== undefined && this.systems.player.attributes) { this.systems.player.attributes.maxEnergy = playerData.stats.maxEnergy; console.log('[GAME ENGINE] Applied max energy from server:', playerData.stats.maxEnergy); } // Ensure player has minimum energy for dungeon access if (this.systems.player.attributes) { // Check if energy is missing or too low if (!this.systems.player.attributes.energy || this.systems.player.attributes.energy < 10) { const oldEnergy = this.systems.player.attributes.energy; this.systems.player.attributes.energy = 100; this.systems.player.attributes.maxEnergy = Math.max(this.systems.player.attributes.maxEnergy || 0, 100); console.log('[GAME ENGINE] Set minimum energy for dungeon access:', oldEnergy, '->', this.systems.player.attributes.energy); } // Also ensure currentEnergy is set if it exists if (this.systems.player.attributes.currentEnergy !== undefined) { if (this.systems.player.attributes.currentEnergy < 10) { this.systems.player.attributes.currentEnergy = 100; console.log('[GAME ENGINE] Set minimum currentEnergy for dungeon access'); } } } console.log('[GAME ENGINE] Final player stats after application:', this.systems.player.stats); } else { console.log('[GAME ENGINE] Missing player stats or player system'); console.log('[GAME ENGINE] - playerData.stats:', !!playerData.stats); console.log('[GAME ENGINE] - this.systems:', !!this.systems); console.log('[GAME ENGINE] - this.systems.player:', !!this.systems?.player); } // Apply inventory if (playerData.inventory && this.systems && this.systems.inventory) { this.systems.inventory.load(playerData.inventory); console.log('[GAME ENGINE] Applied inventory'); } // REMOVED: QuestSystem should be server-driven only // Quest data will be handled by server-side systems only // Show notification if (this.showNotification) { this.showNotification(`Welcome back! Level ${playerData.stats?.level || 1}`, 'success', 3000); } console.log('[GAME ENGINE] Server player data loaded successfully'); // Trigger UI update to refresh all tabs with new data if (this.systems && this.systems.ui) { this.systems.ui.updateUI(); console.log('[GAME ENGINE] Triggered UI update after server data load'); } } catch (error) { console.error('[GAME ENGINE] Error loading server player data:', error); if (this.showNotification) { this.showNotification('Failed to load server data!', 'error', 3000); } } } } // Global game instance let game = null; // Export GameEngine to global scope if (typeof window !== 'undefined') { window.GameEngine = GameEngine; }