From b793b85e25d79d7ba38d3e28de7e14c03c4733d6 Mon Sep 17 00:00:00 2001 From: Robert MacRae Date: Mon, 26 Jan 2026 17:16:42 -0400 Subject: [PATCH] almost a full rework of the client with server data-driven calls --- Client/index.html | 650 ++++++- Client/js/GameInitializer.js | 546 +++++- Client/js/SaveSystemIntegration.js | 325 ++++ Client/js/SmartSaveManager.js | 228 +++ Client/js/core/DebugLogger.js | 32 +- Client/js/core/Economy.js | 2484 +++++---------------------- Client/js/core/GameEngine.js | 1582 ++++------------- Client/js/core/Inventory.js | 74 +- Client/js/core/Logger.js | 2 - Client/js/core/Player.js | 71 +- Client/js/main.js | 8 +- Client/js/systems/BaseSystem.js | 37 +- Client/js/systems/CraftingSystem.js | 8 +- Client/js/systems/DungeonSystem.js | 2323 +++++-------------------- Client/js/systems/IdleSystem.js | 29 +- Client/js/systems/ItemSystem.js | 383 +++++ Client/js/systems/QuestSystem.js | 196 ++- Client/js/systems/SkillSystem.js | 16 +- Client/js/ui/LiveMainMenu.js | 524 ++---- Client/js/ui/UIManager.js | 497 +++++- Client/styles/main.css | 33 + 21 files changed, 4156 insertions(+), 5892 deletions(-) create mode 100644 Client/js/SaveSystemIntegration.js create mode 100644 Client/js/SmartSaveManager.js create mode 100644 Client/js/systems/ItemSystem.js diff --git a/Client/index.html b/Client/index.html index 5322b26..ac08ce0 100644 --- a/Client/index.html +++ b/Client/index.html @@ -10,8 +10,6 @@ - - @@ -220,8 +218,14 @@

GSO

- Commander - Lv. 1 +
+ Commander + - Rookie Pilot +
+
+ + Lv. 1 +
@@ -261,35 +265,35 @@
-
+
+ +
+

Player Stats

+
+
+ Level + 1 +
+
+ Experience + 0 / 100 +
+
+ Skill Points + 0 +
+
+ Total XP Earned + 0 +
+
+ Quests Completed + 0 +
+
+ Last Login + Never +
Total Kills 0 @@ -331,7 +363,7 @@
Play Time - 0h 0m + 0m 0s
@@ -366,9 +398,9 @@
- - - + + +
@@ -379,10 +411,10 @@
- - - - + + + +
@@ -515,11 +547,11 @@
- - - - - + + + + +
Daily quests reset in: 00:00:00
Weekly quests reset in: 0d 00:00
@@ -600,10 +632,10 @@
- - - - + + + +
@@ -616,13 +648,13 @@
- - - + + + - - - + + +
@@ -672,12 +704,558 @@ + + + + + + +
diff --git a/Client/js/GameInitializer.js b/Client/js/GameInitializer.js index 2e88c07..1dbf404 100644 --- a/Client/js/GameInitializer.js +++ b/Client/js/GameInitializer.js @@ -15,12 +15,16 @@ class GameInitializer { this.socket = null; this.apiBaseUrl = 'https://api.korvarix.com/api'; // API Server this.gameServerUrl = 'https://dev.gameserver.galaxystrike.online'; // Game Server for Socket.IO (local dev server) + + console.log('[GAME INITIALIZER] Constructor - gameServerUrl set to:', this.gameServerUrl); } updateServerUrls(apiUrl, gameUrl) { - console.log('[GAME INITIALIZER] Updating server URLs:', { apiUrl, gameUrl }); + console.log('[GAME INITIALIZER] Updating server URLs:', { apiUrl: apiUrl, gameUrl: gameUrl }); + console.log('[GAME INITIALIZER] Previous gameServerUrl:', this.gameServerUrl); this.apiBaseUrl = apiUrl; this.gameServerUrl = gameUrl; + console.log('[GAME INITIALIZER] New gameServerUrl:', this.gameServerUrl); } initializeMultiplayer(server, serverData, authToken, currentUser) { @@ -33,8 +37,10 @@ class GameInitializer { // Initialize Socket.IO connection this.initializeSocketConnection(); - // Initialize game systems with multiplayer support - this.initializeGameSystems(); + // Set SmartSaveManager to multiplayer mode + if (window.smartSaveManager) { + window.smartSaveManager.setMultiplayerMode(true, this); + } // Update UI for multiplayer mode this.updateUIForMultiplayerMode(); @@ -49,6 +55,7 @@ class GameInitializer { } console.log('[GAME INITIALIZER] Initializing Socket.IO connection'); + console.log('[GAME INITIALIZER] Using gameServerUrl:', this.gameServerUrl); // Check if we're in local mode and should use mock socket if (this.gameServerUrl.includes('localhost') && window.localServerManager && window.localServerManager.localServer) { @@ -63,14 +70,21 @@ class GameInitializer { return; } - // Connect to the game server (different from API server) - this.socket = io(this.gameServerUrl, { + // FORCE THE URL - Override any undefined issues + const FORCED_URL = 'https://dev.gameserver.galaxystrike.online'; + console.log('[GAME INITIALIZER] FORCING URL to:', FORCED_URL); + console.log('[GAME INITIALIZER] Original this.gameServerUrl:', this.gameServerUrl); + + // Connect to the game server with FORCED URL + this.socket = io(FORCED_URL, { auth: { token: this.authToken, serverId: this.serverData.id } }); + console.log('[GAME INITIALIZER] Socket.IO connection initiated to FORCED URL:', FORCED_URL); + // Socket event handlers this.socket.on('connect', () => { console.log('[GAME INITIALIZER] Connected to server'); @@ -111,9 +125,69 @@ class GameInitializer { console.log('[GAME INITIALIZER] Chat message:', data); this.onChatMessage(data); }); + + // Server data events + this.socket.on('authenticated', (data) => { + console.log('[GAME INITIALIZER] Authentication response:', data); + this.onAuthenticated(data); + }); + + this.socket.on('gameDataLoaded', (data) => { + console.log('[GAME INITIALIZER] Game data loaded:', data); + this.onGameDataLoaded(data); + }); + + this.socket.on('updatePlayerStats', (data) => { + console.log('[GAME INITIALIZER] Player stats updated on server:', data); + this.onGameDataSaved(data); + }); + + this.socket.on('gameDataSaved', (data) => { + console.log('[GAME INITIALIZER] Game data saved:', data); + this.onGameDataSaved(data); + }); + + // Idle rewards events + this.socket.on('offlineRewardsClaimed', (data) => { + console.log('[GAME INITIALIZER] Offline rewards claimed:', data); + this.onOfflineRewardsClaimed(data); + }); + + this.socket.on('onlineIdleRewards', (data) => { + console.log('[GAME INITIALIZER] Online idle rewards received:', data); + this.onOnlineIdleRewards(data); + }); + + // PlayTime events + this.socket.on('playTimeUpdated', (data) => { + console.log('[GAME INITIALIZER] PlayTime updated from server:', data); + this.onPlayTimeUpdated(data); + }); + + // Shop purchase events + this.socket.on('purchaseCompleted', (data) => { + console.log('[GAME INITIALIZER] Purchase completed:', data); + this.onPurchaseCompleted(data); + }); + + // Item system events + this.socket.on('shopItemsReceived', (data) => { + console.log('[GAME INITIALIZER] Shop items received:', data); + this.onShopItemsReceived(data); + }); + + this.socket.on('itemDetailsReceived', (data) => { + console.log('[GAME INITIALIZER] Item details received:', data); + this.onItemDetailsReceived(data); + }); } onSocketConnected() { + // Expose socket globally for systems that need it + if (window.game) { + window.game.socket = this.socket; + } + // Join the server room this.socket.emit('joinServer', { serverId: this.serverData.id, @@ -121,13 +195,48 @@ class GameInitializer { username: this.currentUser.username }); + // Authenticate with server to get player data + this.authenticateWithServer(); + // Show connected status this.showConnectionStatus('Connected', 'success'); } onSocketDisconnected() { + console.log('[GAME INITIALIZER] Socket disconnected - switching to singleplayer mode'); + // Show disconnected status this.showConnectionStatus('Disconnected', 'error'); + + // Switch SmartSaveManager back to singleplayer mode + if (window.smartSaveManager) { + window.smartSaveManager.setMultiplayerMode(false); + console.log('[GAME INITIALIZER] SmartSaveManager set to singleplayer mode'); + } + + // Clear socket reference + this.socket = null; + console.log('[GAME INITIALIZER] Socket reference cleared'); + } + + // Force disconnect from multiplayer server + forceDisconnect() { + console.log('[GAME INITIALIZER] Force disconnect called'); + + if (this.socket) { + console.log('[GAME INITIALIZER] Disconnecting socket...'); + this.socket.disconnect(); + this.socket = null; + } + + // Switch to singleplayer mode + if (window.smartSaveManager) { + window.smartSaveManager.setMultiplayerMode(false); + console.log('[GAME INITIALIZER] Force switched to singleplayer mode'); + } + + this.showConnectionStatus('Disconnected', 'error'); + console.log('[GAME INITIALIZER] Force disconnect completed'); } onPlayerJoined(data) { @@ -156,6 +265,147 @@ class GameInitializer { } } + onAuthenticated(data) { + console.log('[GAME INITIALIZER] Authentication successful:', data); + + if (data.success && data.playerData) { + // Store server player data from authentication (this is our primary source) + this.serverPlayerData = data.playerData; + this.currentUser = data.user; + + // CRITICAL: Force multiplayer mode and prevent fallback + if (window.smartSaveManager) { + console.log('[GAME INITIALIZER] FORCING multiplayer mode after authentication'); + window.smartSaveManager.setMultiplayerMode(true, this); + } + + // ItemSystem is now initialized by GameEngine - no need to initialize here + console.log('[GAME INITIALIZER] ItemSystem initialization handled by GameEngine'); + + console.log('[GAME INITIALIZER] Using authentication data as primary source:', this.serverPlayerData); + + // NOW create GameEngine AFTER authentication is successful + if (!window.game) { + console.log('[GAME INITIALIZER] Creating GameEngine after successful authentication'); + this.createGameEngineForMultiplayer(); + } + + // Set SmartSaveManager to multiplayer mode + if (window.smartSaveManager) { + window.smartSaveManager.setMultiplayerMode(true, this); + console.log('[GAME INITIALIZER] SmartSaveManager set to multiplayer mode'); + + // Apply authentication data immediately (this will be stored for later) + window.smartSaveManager.applyServerDataToGame(data.playerData); + } + + // NOTE: Don't apply to GameEngine here - it doesn't exist yet! + // The data will be applied in createGameEngineForMultiplayer() after the game is created. + + this.showNotification(`Welcome back! Level ${data.playerData.stats?.level || 1}`, 'success'); + } else { + this.showNotification(data.error || 'Authentication failed', 'error'); + } + } + + createGameEngineForMultiplayer() { + console.log('[GAME INITIALIZER] Creating GameEngine for multiplayer mode'); + console.log('[GAME INITIALIZER] Server player data available:', !!this.serverPlayerData); + + try { + // Create GameEngine instance + window.game = new GameEngine(); + + // CRITICAL: Set multiplayer mode BEFORE initializing systems + console.log('[GAME INITIALIZER] Setting multiplayer mode BEFORE initialization'); + window.game.setMultiplayerMode(true, this.socket, this.serverData, this.currentUser); + + // NOTE: Don't apply server data immediately - wait for full initialization + console.log('[GAME INITIALIZER] Server data ready, will apply after GameEngine initialization'); + console.log('[GAME INITIALIZER] - this.serverPlayerData:', !!this.serverPlayerData); + console.log('[GAME INITIALIZER] - window.game:', !!window.game); + console.log('[GAME INITIALIZER] - window.game.loadServerPlayerData:', !!window.game?.loadServerPlayerData); + + // Initialize the game engine + console.log('[GAME INITIALIZER] About to call window.game.init()'); + const initPromise = window.game.init(); + console.log('[GAME INITIALIZER] GameEngine.init() returned:', typeof initPromise, initPromise); + + // Apply server data and refresh UI after initialization is complete + initPromise.then(() => { + console.log('[GAME INITIALIZER] GameEngine initialized successfully for multiplayer'); + + // Apply server data immediately after initialization + if (this.serverPlayerData && window.game.loadServerPlayerData) { + console.log('[GAME INITIALIZER] Applying server player data to GameEngine:', this.serverPlayerData); + window.game.loadServerPlayerData(this.serverPlayerData); + console.log('[GAME INITIALIZER] Server player data applied to GameEngine'); + + // Force UI refresh + if (window.game.systems && window.game.systems.ui && window.game.systems.ui.forceRefreshAllUI) { + console.log('[GAME INITIALIZER] Forcing UI refresh after data application'); + window.game.systems.ui.forceRefreshAllUI(); + } else { + console.warn('[GAME INITIALIZER] UI refresh not available - systems:', !!window.game.systems, 'ui:', !!window.game.systems?.ui, 'forceRefreshAllUI:', !!window.game.systems?.ui?.forceRefreshAllUI); + } + } else { + console.warn('[GAME INITIALIZER] No server player data or loadServerPlayerData method available'); + console.log('[GAME INITIALIZER] - this.serverPlayerData:', !!this.serverPlayerData); + console.log('[GAME INITIALIZER] - window.game.loadServerPlayerData:', !!window.game?.loadServerPlayerData); + } + + // Start the game + if (window.game.start) { + window.game.start(); + } + + }).catch((error) => { + console.error('[GAME INITIALIZER] GameEngine init failed:', error); + console.error('[GAME INITIALIZER] Error details:', error.stack); + this.showNotification('Failed to initialize game engine', 'error'); + }); + + } catch (error) { + console.error('[GAME INITIALIZER] Error creating GameEngine:', error); + this.showNotification('Error creating game engine', 'error'); + } + } + + onGameDataLoaded(data) { + console.log('[GAME INITIALIZER] Server game data loaded:', data); + console.log('[GAME INITIALIZER] Data success:', data.success); + console.log('[GAME INITIALIZER] Data content:', data.data); + console.log('[GAME INITIALIZER] Data keys:', data.data ? Object.keys(data.data) : 'No data object'); + + // Only process if we don't already have good data from authentication + if (data.success && data.data && Object.keys(data.data).length > 0 && !this.serverPlayerData) { + console.log('[GAME INITIALIZER] Using gameDataLoaded as primary source (no auth data available)'); + this.serverPlayerData = data.data; + + // Apply server data to game if game is running + if (window.game && window.game.loadServerPlayerData) { + window.game.loadServerPlayerData(data.data); + + // Force UI refresh when server data is applied + if (window.game.systems && window.game.systems.ui && window.game.systems.ui.forceRefreshAllUI) { + window.game.systems.ui.forceRefreshAllUI(); + } + } + } else { + console.log('[GAME INITIALIZER] Ignoring gameDataLoaded - already have data from authentication or data is empty'); + } + } + + onGameDataSaved(data) { + console.log('[GAME INITIALIZER] Server game data saved:', data); + + if (data.success) { + this.showNotification('Game saved to server!', 'success'); + } else { + this.showNotification(data.error || 'Failed to save to server', 'error'); + } + } + onForceDisconnect(data) { // Handle forced disconnection from server console.warn('[GAME INITIALIZER] Force disconnected by server:', data); @@ -211,7 +461,56 @@ class GameInitializer { // Configure game for multiplayer mode console.log('[GAME INITIALIZER] Configuring for multiplayer mode'); - window.game.setMultiplayerMode(true, this.socket, this.serverData, this.currentUser); + // Note: setMultiplayerMode already called in createGameEngineForMultiplayer() before initialization + window.game.gameInitializer = this; // Store reference for server polling + + // DISABLE game logic in multiplayer - server is authoritative + if (window.game) { + console.log('[GAME INITIALIZER] Disabling client game logic - server is authoritative'); + + // Override game logic methods to do nothing in multiplayer + const originalUpdateGameLogic = window.game.updateGameLogic; + window.game.updateGameLogic = function() { + // In multiplayer mode, client does NO game logic + // Server is authoritative for ALL game data including credits + // Client is display-only + }; + + const originalStart = window.game.start; + window.game.start = function() { + console.log('[GAME ENGINE] Multiplayer mode - client does not run game logic, server is authoritative'); + console.log('[GAME ENGINE] GameInitializer reference:', !!this.gameInitializer); + console.log('[GAME ENGINE] Socket reference:', !!(this.gameInitializer && this.gameInitializer.socket)); + console.log('[GAME ENGINE] Game mode:', this.gameInitializer ? this.gameInitializer.gameMode : 'no gameInitializer'); + + this.isRunning = true; + + // NO game logic timer - client is display-only + // Server handles all game logic including credit generation + + // Start server data polling for UI updates + if (this.gameInitializer && this.gameInitializer.socket) { + console.log('[GAME ENGINE] Starting server data polling for UI updates'); + this.serverPollTimer = setInterval(() => { + console.log('[GAME ENGINE] Polling server for data...'); + this.gameInitializer.loadGameDataFromServer(); + }, 5000); // Request updates every 5 seconds + } else { + console.warn('[GAME ENGINE] Cannot start server polling - no gameInitializer or socket'); + } + + // Only start UI updates that use server data (every second) + if (this.systems.ui) { + console.log('[GAME ENGINE] Starting multiplayer UI updates'); + this.uiUpdateTimer = setInterval(() => { + if (this.systems && this.systems.ui && this.systems.ui.updateUI) { + console.log('[GAME ENGINE] Updating multiplayer UI with server data'); + this.systems.ui.updateUI(); + } + }, 1000); + } + }; + } // Game is already set up with save data, just start the game loop if (window.game.start) { @@ -230,8 +529,24 @@ class GameInitializer { updateUIForMultiplayerMode() { // Update UI elements to show multiplayer mode const playerName = document.getElementById('playerName'); - if (playerName && this.currentUser) { - playerName.textContent = this.currentUser.username; + const playerTitle = document.getElementById('playerTitle'); + const playerUsername = document.getElementById('playerUsername'); + + if (this.currentUser) { + // Set the player name (rank/title) + if (playerName) { + playerName.textContent = 'Commander'; + } + + // Set the player title + if (playerTitle) { + playerTitle.textContent = '- Rookie Pilot'; + } + + // Set the username next to the level + if (playerUsername) { + playerUsername.textContent = this.currentUser.username + ' '; + } } // Show multiplayer-specific UI elements @@ -340,10 +655,211 @@ class GameInitializer { } } + // Method to save game data to server (SECURE: only send specific updates) + saveGameDataToServer(gameData) { + if (this.socket && this.gameMode === 'multiplayer') { + console.log('[GAME INITIALIZER] Sending secure game updates to server'); + + // Only send specific, validated updates - not entire game state + const secureUpdates = { + playerStats: { + credits: gameData.player?.credits || 0, + level: gameData.player?.level || 1, + experience: gameData.player?.experience || 0, + playTime: gameData.player?.playTime || 0 + }, + timestamp: Date.now() + }; + + // Send only the specific updates for server validation + this.socket.emit('updatePlayerStats', secureUpdates); + } + } + + // Method to load game data from server + loadGameDataFromServer() { + if (this.socket && this.gameMode === 'multiplayer') { + console.log('[GAME INITIALIZER] Loading game data from server'); + console.log('[GAME INITIALIZER] Socket available:', !!this.socket); + console.log('[GAME INITIALIZER] Game mode:', this.gameMode); + console.log('[GAME INITIALIZER] Current user:', this.currentUser); + console.log('[GAME INITIALIZER] Emitting loadGameData event with username'); + + // Get username from current user or fallback to stored user + let username = 'anonymous'; // default fallback + if (this.currentUser?.username) { + username = this.currentUser.username; + } else { + // Try to get from localStorage as fallback + const storedUser = localStorage.getItem('currentUser'); + if (storedUser) { + try { + const user = JSON.parse(storedUser); + username = user.username || 'anonymous'; + } catch (e) { + console.warn('[GAME INITIALIZER] Failed to parse stored user, using default'); + } + } + } + + console.log('[GAME INITIALIZER] Using username for data load:', username); + + // Send the username to load the correct player data + this.socket.emit('loadGameData', { + username: username + }); + } else { + console.log('[GAME INITIALIZER] Cannot load game data - socket or multiplayer mode not available'); + console.log('[GAME INITIALIZER] Socket available:', !!this.socket); + console.log('[GAME INITIALIZER] Game mode:', this.gameMode); + } + } + + // Method to authenticate with server + authenticateWithServer() { + console.log('[GAME INITIALIZER] authenticateWithServer called'); + console.log('[GAME INITIALIZER] Socket available:', !!this.socket); + console.log('[GAME INITIALIZER] Game mode:', this.gameMode); + console.log('[GAME INITIALIZER] Current user:', this.currentUser); + + if (this.socket && this.currentUser) { + console.log('[GAME INITIALIZER] Sending authentication to server'); + this.socket.emit('authenticate', { + userId: this.currentUser.userId, + username: this.currentUser.username + }); + } else { + console.warn('[GAME INITIALIZER] Cannot authenticate - missing socket or user data'); + if (!this.socket) { + console.warn('[GAME INITIALIZER] Socket is null/undefined'); + } + if (!this.currentUser) { + console.warn('[GAME INITIALIZER] Current user is null/undefined'); + } + } + } + + onOfflineRewardsClaimed(data) { + if (data.success) { + if (data.rewards.credits > 0 || data.rewards.experience > 0) { + // Apply rewards to player + if (window.game && window.game.systems) { + if (data.rewards.credits > 0) { + window.game.systems.economy.addCredits(data.rewards.credits, 'offline'); + } + if (data.rewards.experience > 0) { + window.game.systems.player.addExperience(data.rewards.experience); + } + + // Show success message + let message = 'Offline rewards claimed!\n'; + if (data.rewards.credits > 0) message += `+${data.rewards.credits} credits\n`; + if (data.rewards.experience > 0) message += `+${data.rewards.experience} experience\n`; + + window.game.showNotification(message, 'success', 5000); + } + } else { + window.game.showNotification('No offline rewards available', 'info', 3000); + } + } else { + window.game.showNotification(`Failed to claim offline rewards: ${data.error}`, 'error', 5000); + } + } + + onOnlineIdleRewards(data) { + if (window.game && window.game.systems) { + // Update player balance with online idle rewards + if (data.credits > 0) { + // The server already updated the balance, just show notification + window.game.showNotification(`+${data.credits} credits (online idle)`, 'success', 2000); + } + } + } + + onPlayTimeUpdated(data) { + console.log('[GAME INITIALIZER] PlayTime updated from server:', data); + + if (window.game && window.game.systems && window.game.systems.player) { + const player = window.game.systems.player; + + // Update playTime from server + player.stats.playTime = data.playTime; + + console.log('[GAME INITIALIZER] Updated local playTime to:', data.playTime, 'ms'); + console.log('[GAME INITIALIZER] PlayTime in hours:', data.playTime / (1000 * 60 * 60), 'hours'); + + // Update UI + player.updateUI(); + } + } + + onPurchaseCompleted(data) { + if (data.success) { + // Update local player data with server response + if (window.game && window.game.systems && window.game.systems.economy) { + const economy = window.game.systems.economy; + + // Update currency balance + if (data.currency === 'credits') { + economy.credits = data.newBalance; + } else if (data.currency === 'gems') { + economy.gems = data.newBalance; + } + + // Request fresh economy data from server to ensure sync + if (economy.requestEconomyData) { + setTimeout(() => { + economy.requestEconomyData(); + }, 500); + } + + // Update UI + economy.updateUI(); + + // Show success message + window.game.showNotification(`Purchased ${data.item.name}!`, 'success', 3000); + } + } else { + // Show error message + window.game.showNotification(`Purchase failed: ${data.error}`, 'error', 5000); + } + } + + onShopItemsReceived(data) { + if (data.success && window.game && window.game.systems && window.game.systems.itemSystem) { + // Update ItemSystem with server data + window.game.systems.itemSystem.processServerItems(data.items); + console.log('[GAME INITIALIZER] ItemSystem updated with server shop items'); + + // Update Economy shop UI + if (window.game.systems.economy) { + window.game.systems.economy.updateShopUI(); + console.log('[GAME INITIALIZER] Economy shop UI updated'); + } + } else { + console.warn('[GAME INITIALIZER] Failed to receive shop items:', data); + } + } + + onItemDetailsReceived(data) { + // This is handled by the ItemSystem directly + // Just log for debugging + if (data.success) { + console.log('[GAME INITIALIZER] Item details received for:', data.item.name); + } else { + console.warn('[GAME INITIALIZER] Failed to receive item details:', data); + } + } + // Cleanup method cleanup() { console.log('[GAME INITIALIZER] Cleaning up'); + // Reset SmartSaveManager to singleplayer mode + if (window.smartSaveManager) { + window.smartSaveManager.setMultiplayerMode(false); + } + if (this.socket) { this.socket.disconnect(); this.socket = null; @@ -353,12 +869,26 @@ class GameInitializer { this.serverData = null; this.authToken = null; this.currentUser = null; + this.serverPlayerData = null; } } // Create global instance window.gameInitializer = new GameInitializer(); +// Make force disconnect available globally for testing +window.forceDisconnectMultiplayer = function() { + if (window.gameInitializer && window.gameInitializer.forceDisconnect) { + window.gameInitializer.forceDisconnect(); + } else { + console.log('[GAME INITIALIZER] GameInitializer not available for force disconnect'); + } +}; + +// Debug: Log the global instance immediately +console.log('[GAME INITIALIZER] Global instance created:', window.gameInitializer); +console.log('[GAME INITIALIZER] Global instance gameServerUrl:', window.gameInitializer.gameServerUrl); + // Export for use in other scripts if (typeof module !== 'undefined' && module.exports) { module.exports = GameInitializer; diff --git a/Client/js/SaveSystemIntegration.js b/Client/js/SaveSystemIntegration.js new file mode 100644 index 0000000..85f5e2e --- /dev/null +++ b/Client/js/SaveSystemIntegration.js @@ -0,0 +1,325 @@ +/** + * Save System Integration + * Integrates SmartSaveManager with existing game systems + */ + +console.log('[SAVE INTEGRATION] Save system integration loading'); + +// Override the game's save method to use SmartSaveManager +function integrateWithGameEngine() { + if (window.game && window.game.save) { + // Store original save method + const originalSave = window.game.save; + + // Override game save method + window.game.save = async function() { + // console.log('[SAVE INTEGRATION] Game save called'); + + if (window.smartSaveManager) { + await window.smartSaveManager.save(); + } else { + // Fallback to original save if SmartSaveManager not available + return await originalSave.call(this); + } + }; + + console.log('[SAVE INTEGRATION] Game save method overridden'); + } +} + +// Override the game's load method to use SmartSaveManager +function integrateLoadSystem() { + if (window.game && window.game.load) { + // Store original load method + const originalLoad = window.game.load; + + // Override load method + window.game.load = async function(saveData = null) { + console.log('[SAVE INTEGRATION] Game load called, using SmartSaveManager'); + + try { + let dataToLoad = saveData; + + // If no data provided, use SmartSaveManager + if (!dataToLoad && window.smartSaveManager) { + dataToLoad = await window.smartSaveManager.loadPlayerData(); + } + + // Load the data + if (dataToLoad) { + if (this.loadPlayerData) { + this.loadPlayerData(dataToLoad); + } else { + // Fallback to original load + return await originalLoad.call(this, dataToLoad); + } + + console.log('[SAVE INTEGRATION] Game data loaded successfully'); + return true; + } else { + console.log('[SAVE INTEGRATION] No save data found, starting fresh'); + return false; + } + } catch (error) { + console.error('[SAVE INTEGRATION] Load error:', error); + return false; + } + }; + + console.log('[SAVE INTEGRATION] Game load method overridden'); + } +} + +// Add server data loading method to game +function addServerDataSupport() { + if (window.game) { + // Store pending server data for later application + window.game.pendingServerData = null; + + window.game.loadServerPlayerData = function(serverData) { + console.log('[SAVE INTEGRATION] Loading server player data into game'); + console.log('[SAVE INTEGRATION] Server data received:', serverData); + console.log('[SAVE INTEGRATION] Server data type:', typeof serverData); + console.log('[SAVE INTEGRATION] Server data keys:', serverData ? Object.keys(serverData) : 'No data'); + console.log('[SAVE INTEGRATION] Game systems available:', this.systems ? Object.keys(this.systems) : 'No systems'); + + // Store server data for later if systems aren't ready + if (!this.systems || Object.keys(this.systems).length === 0) { + console.log('[SAVE INTEGRATION] Game systems not ready, storing data for later'); + this.pendingServerData = serverData; + return; + } + + console.log('[SAVE INTEGRATION] Game systems ready, applying server data now'); + + try { + // Apply player stats + if (serverData.stats && this.systems && this.systems.player) { + console.log('[SAVE INTEGRATION] Applying player stats:', serverData.stats); + console.log('[SAVE INTEGRATION] Player system methods:', Object.getOwnPropertyNames(Object.getPrototypeOf(this.systems.player))); + + // Check if load method exists + if (typeof this.systems.player.load === 'function') { + this.systems.player.load(serverData.stats); + console.log('[SAVE INTEGRATION] Player stats applied successfully'); + console.log('[SAVE INTEGRATION] Updated player stats:', this.systems.player.stats); + } else { + console.warn('[SAVE INTEGRATION] Player system has no load method, trying direct assignment'); + // Direct assignment as fallback + if (this.systems.player.stats) { + Object.assign(this.systems.player.stats, serverData.stats); + console.log('[SAVE INTEGRATION] Player stats assigned directly:', this.systems.player.stats); + } + } + } else { + console.warn('[SAVE INTEGRATION] No player system or stats in server data'); + console.log('[SAVE INTEGRATION] Has serverData.stats:', !!serverData?.stats); + console.log('[SAVE INTEGRATION] Has systems.player:', !!(this.systems?.player)); + } + + // Apply inventory + if (serverData.inventory && this.systems && this.systems.inventory) { + console.log('[SAVE INTEGRATION] Applying player inventory:', serverData.inventory); + if (typeof this.systems.inventory.load === 'function') { + this.systems.inventory.load(serverData.inventory); + } else { + console.warn('[SAVE INTEGRATION] Inventory system has no load method'); + } + } + + // Apply ship data + if (serverData.ship && this.systems && this.systems.ship) { + console.log('[SAVE INTEGRATION] Applying player ship:', serverData.ship); + if (typeof this.systems.ship.load === 'function') { + this.systems.ship.load(serverData.ship); + } else { + console.warn('[SAVE INTEGRATION] Ship system has no load method'); + } + } + + // Apply base data + if (serverData.base && this.systems && this.systems.base) { + console.log('[SAVE INTEGRATION] Applying player base:', serverData.base); + if (typeof this.systems.base.load === 'function') { + this.systems.base.load(serverData.base); + } else { + console.warn('[SAVE INTEGRATION] Base system has no load method'); + } + } + + // Show notification + if (this.showNotification) { + this.showNotification(`Welcome back! Level ${serverData.stats?.level || 1}`, 'success', 3000); + } + + console.log('[SAVE INTEGRATION] Server player data application completed'); + + // Force UI update + if (this.systems && this.systems.ui && this.systems.ui.updateUI) { + this.systems.ui.updateUI(); + console.log('[SAVE INTEGRATION] Server player data application completed'); + } + + // Apply pending server data if any exists + if (this.pendingServerData) { + console.log('[SAVE INTEGRATION] Applying pending server data'); + this.loadServerPlayerData(this.pendingServerData); + this.pendingServerData = null; + } + } catch (error) { + console.error('[SAVE INTEGRATION] Error applying server player data:', error); + if (this.showNotification) { + this.showNotification('Failed to load server data!', 'error', 3000); + } + } + }; + + // Method to check and apply pending server data + window.game.checkAndApplyPendingServerData = function() { + if (this.pendingServerData && this.systems && Object.keys(this.systems).length > 0) { + console.log('[SAVE INTEGRATION] Systems ready, applying pending server data'); + this.loadServerPlayerData(this.pendingServerData); + this.pendingServerData = null; + } + }; + + // Fallback loadPlayerData method if GameEngine doesn't have it + if (!window.game.loadPlayerData) { + window.game.loadPlayerData = window.game.loadServerPlayerData; + } + + console.log('[SAVE INTEGRATION] Server data support added to game'); + } +} + +// Add save mode switching to UI +function addSaveModeUI() { + // Add save mode indicator to UI + const createSaveModeIndicator = () => { + const indicator = document.createElement('div'); + indicator.id = 'save-mode-indicator'; + indicator.style.cssText = ` + position: fixed; + bottom: 10px; + right: 10px; + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 8px 12px; + border-radius: 4px; + font-size: 12px; + z-index: 10000; + display: none; + `; + document.body.appendChild(indicator); + return indicator; + }; + + const updateSaveModeIndicator = () => { + const indicator = document.getElementById('save-mode-indicator') || createSaveModeIndicator(); + + if (window.smartSaveManager) { + const info = window.smartSaveManager.getSaveInfo(); + indicator.textContent = `Save: ${info.saveLocation}`; + indicator.style.display = 'block'; + + // Color code based on mode + indicator.style.background = info.isMultiplayer ? 'rgba(0, 100, 200, 0.8)' : 'rgba(0, 150, 0, 0.8)'; + } + }; + + // Update indicator when mode changes + if (window.smartSaveManager) { + const originalSetMultiplayerMode = window.smartSaveManager.setMultiplayerMode; + window.smartSaveManager.setMultiplayerMode = function(...args) { + originalSetMultiplayerMode.apply(this, args); + updateSaveModeIndicator(); + }; + } + + // Initial update + setTimeout(updateSaveModeIndicator, 1000); +} + +// Debug function to check data flow +function debugDataFlow() { + console.log('[DEBUG] === DATA FLOW DEBUG ==='); + + // Check GameInitializer + if (window.gameInitializer) { + console.log('[DEBUG] GameInitializer exists:', !!window.gameInitializer); + console.log('[DEBUG] GameInitializer serverPlayerData:', window.gameInitializer.serverPlayerData); + console.log('[DEBUG] GameInitializer gameMode:', window.gameInitializer.gameMode); + } else { + console.log('[DEBUG] GameInitializer NOT found'); + } + + // Check game systems + if (window.game) { + console.log('[DEBUG] Game exists:', !!window.game); + console.log('[DEBUG] Game systems:', window.game.systems ? Object.keys(window.game.systems) : 'No systems'); + + if (window.game.systems && window.game.systems.player) { + console.log('[DEBUG] Player system exists:', !!window.game.systems.player); + console.log('[DEBUG] Player stats:', window.game.systems.player.stats); + console.log('[DEBUG] Player credits:', window.game.systems.player.stats?.credits); + } + } else { + console.log('[DEBUG] Game NOT found'); + } + + // Check SmartSaveManager + if (window.smartSaveManager) { + console.log('[DEBUG] SmartSaveManager exists:', !!window.smartSaveManager); + console.log('[DEBUG] SmartSaveManager mode:', window.smartSaveManager.isMultiplayer ? 'multiplayer' : 'singleplayer'); + } else { + console.log('[DEBUG] SmartSaveManager NOT found'); + } + + console.log('[DEBUG] === END DEBUG ==='); +} + +// Debug function available for manual testing +window.debugDataFlow = debugDataFlow; + +// Enhanced debug function for connection testing +window.debugConnectionState = function() { + console.log('=== CONNECTION STATE DEBUG ==='); + console.log('GameInitializer exists:', !!window.gameInitializer); + console.log('GameInitializer socket connected:', !!window.gameInitializer?.socket?.connected); + console.log('GameInitializer gameMode:', window.gameInitializer?.gameMode); + console.log('GameInitializer serverPlayerData:', !!window.gameInitializer?.serverPlayerData); + console.log('SmartSaveManager exists:', !!window.smartSaveManager); + console.log('SmartSaveManager mode:', window.smartSaveManager?.isMultiplayer ? 'multiplayer' : 'singleplayer'); + console.log('Game exists:', !!window.game); + console.log('Game isRunning:', window.game?.isRunning); + console.log('=== END CONNECTION DEBUG ==='); +}; + +// Initialize integration when DOM is ready +function initializeIntegration() { + console.log('[SAVE INTEGRATION] Initializing save system integration'); + + // Wait for game to be ready + const checkGameReady = () => { + if (window.game) { + integrateWithGameEngine(); + integrateLoadSystem(); + addServerDataSupport(); + addSaveModeUI(); + console.log('[SAVE INTEGRATION] Integration complete'); + } else { + setTimeout(checkGameReady, 500); + } + }; + + checkGameReady(); +} + +// Auto-initialize +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeIntegration); +} else { + initializeIntegration(); +} + +console.log('[SAVE INTEGRATION] Save system integration loaded'); diff --git a/Client/js/SmartSaveManager.js b/Client/js/SmartSaveManager.js new file mode 100644 index 0000000..91234e1 --- /dev/null +++ b/Client/js/SmartSaveManager.js @@ -0,0 +1,228 @@ +/** + * Smart Save Manager + * Intelligently handles save data for both singleplayer and multiplayer modes + */ + +class SmartSaveManager { + constructor() { + this.isMultiplayer = false; + this.serverPlayerData = null; + this.localSaveData = null; + this.gameInitializer = null; + + console.log('[SMART SAVE] SmartSaveManager initialized'); + } + + setMultiplayerMode(isMultiplayer, gameInitializer = null) { + const oldMode = this.isMultiplayer; + this.isMultiplayer = isMultiplayer; + this.gameInitializer = gameInitializer; + + console.log(`[SMART SAVE] Mode change: ${oldMode ? 'multiplayer' : 'singleplayer'} -> ${isMultiplayer ? 'multiplayer' : 'singleplayer'}`); + console.log(`[SMART SAVE] Set to ${isMultiplayer ? 'multiplayer' : 'singleplayer'} mode`); + + if (isMultiplayer && gameInitializer) { + // Load server data when switching to multiplayer + this.loadServerData(); + } + } + + // Load player data (intelligently chooses source) + async loadPlayerData() { + if (this.isMultiplayer) { + return await this.loadServerData(); + } else { + return await this.loadLocalData(); + } + } + + // Save player data (intelligently chooses destination) + async savePlayerData(gameData) { + if (this.isMultiplayer) { + return await this.saveServerData(gameData); + } else { + return await this.saveLocalData(gameData); + } + } + + // Load server data + async loadServerData() { + try { + if (!this.gameInitializer || !this.gameInitializer.socket) { + // Don't warn during initialization - this is expected before socket is ready + // console.warn('[SMART SAVE] No multiplayer connection available'); + return null; + } + + console.log('[SMART SAVE] Loading server player data'); + + // Request data from server + this.gameInitializer.loadGameDataFromServer(); + + // Return cached server data if available + return this.serverPlayerData; + } catch (error) { + console.error('[SMART SAVE] Error loading server data:', error); + return null; + } + } + + // Save server data (DISABLED - client should not send data to server) + async saveServerData(gameData) { + console.warn('[SMART SAVE] Client save disabled - server is authoritative'); + return true; // Pretend it worked to avoid client errors + } + + // Load local data + async loadLocalData() { + try { + console.log('[SMART SAVE] Loading local save data'); + + // Use existing local save system + if (window.mainMenu && window.mainMenu.loadGame) { + const saveData = await window.mainMenu.loadGame(1); // Load slot 1 + this.localSaveData = saveData; + return saveData; + } + + // Fallback to localStorage + const saveKey = 'gso_save_slot_1'; + const saveData = localStorage.getItem(saveKey); + + if (saveData) { + const parsed = JSON.parse(saveData); + this.localSaveData = parsed; + return parsed; + } + + return null; + } catch (error) { + console.error('[SMART SAVE] Error loading local data:', error); + return null; + } + } + + // Save local data + async saveLocalData(gameData) { + try { + // Don't save locally when in multiplayer mode + if (this.isMultiplayer) { + console.log('[SMART SAVE] Skipping local save - in multiplayer mode'); + return true; + } + + console.log('[SMART SAVE] Saving locally'); + + // Use existing local save system + if (window.mainMenu && window.mainMenu.saveGame) { + await window.mainMenu.saveGame(1, gameData); // Save to slot 1 + this.localSaveData = gameData; + return true; + } + + // Fallback to localStorage + const saveKey = 'gso_save_slot_1'; + localStorage.setItem(saveKey, JSON.stringify(gameData)); + this.localSaveData = gameData; + + return true; + } catch (error) { + console.error('[SMART SAVE] Error saving local data:', error); + return false; + } + } + + // Apply server data to game + applyServerDataToGame(serverData) { + console.log('[SMART SAVE] Applying server data to game'); + + this.serverPlayerData = serverData; + + // Apply to game if game is running (try both methods) + if (window.game) { + console.log('[SMART SAVE] Game is available, checking for data loading methods'); + console.log('[SMART SAVE] - loadPlayerData:', !!window.game.loadPlayerData); + console.log('[SMART SAVE] - loadServerPlayerData:', !!window.game.loadServerPlayerData); + + if (window.game.loadServerPlayerData) { + console.log('[SMART SAVE] Using loadServerPlayerData method'); + window.game.loadServerPlayerData(serverData); + console.log('[SMART SAVE] Server data applied to game, forcing UI refresh'); + + // Force UI refresh after applying server data + if (window.game.systems && window.game.systems.ui && window.game.systems.ui.forceRefreshAllUI) { + console.log('[SMART SAVE] Forcing UI refresh after server data application'); + window.game.systems.ui.forceRefreshAllUI(); + } else { + console.warn('[SMART SAVE] UI refresh not available after server data application - systems:', !!window.game.systems, 'ui:', !!window.game.systems?.ui, 'forceRefreshAllUI:', !!window.game.systems?.ui?.forceRefreshAllUI); + } + } else if (window.game.loadPlayerData) { + console.log('[SMART SAVE] Using loadPlayerData method'); + window.game.loadPlayerData(serverData); + console.log('[SMART SAVE] Server data applied to game, forcing UI refresh'); + + // Force UI refresh after applying server data + if (window.game.systems && window.game.systems.ui && window.game.systems.ui.forceRefreshAllUI) { + console.log('[SMART SAVE] Forcing UI refresh after server data application'); + window.game.systems.ui.forceRefreshAllUI(); + } else { + console.warn('[SMART SAVE] UI refresh not available after server data application - systems:', !!window.game.systems, 'ui:', !!window.game.systems?.ui, 'forceRefreshAllUI:', !!window.game.systems?.ui?.forceRefreshAllUI); + + // Try delayed UI refresh since UIManager might not be ready yet + console.log('[SMART SAVE] Attempting delayed UI refresh...'); + setTimeout(() => { + if (window.game.systems && window.game.systems.ui && window.game.systems.ui.forceRefreshAllUI) { + console.log('[SMART SAVE] Delayed UI refresh successful'); + window.game.systems.ui.forceRefreshAllUI(); + } else { + console.warn('[SMART SAVE] Delayed UI refresh also failed - systems:', !!window.game.systems, 'ui:', !!window.game.systems?.ui, 'forceRefreshAllUI:', !!window.game.systems?.ui?.forceRefreshAllUI); + } + }, 2000); // Wait 2 seconds for systems to initialize + } + } else { + console.warn('[SMART SAVE] No data loading method available on game object'); + } + } else { + console.warn('[SMART SAVE] Game not available for data application'); + } + + // Store for game engine + if (window.gameInitializer) { + window.gameInitializer.serverPlayerData = serverData; + } + } + + // Get current save source info + getSaveInfo() { + return { + isMultiplayer: this.isMultiplayer, + hasServerData: !!this.serverPlayerData, + hasLocalData: !!this.localSaveData, + saveLocation: this.isMultiplayer ? 'Server Database' : 'Local Storage' + }; + } + + // Sync data between local and server (for migration) + async syncData(direction = 'toServer') { + if (direction === 'toServer') { + // Upload local data to server + const localData = await this.loadLocalData(); + if (localData) { + await this.saveServerData(localData); + console.log('[SMART SAVE] Synced local data to server'); + } + } else { + // Download server data to local + const serverData = await this.loadServerData(); + if (serverData) { + await this.saveLocalData(serverData); + console.log('[SMART SAVE] Synced server data to local'); + } + } + } +} + +// Create global instance +window.smartSaveManager = new SmartSaveManager(); + +console.log('[SMART SAVE] SmartSaveManager loaded and available globally'); diff --git a/Client/js/core/DebugLogger.js b/Client/js/core/DebugLogger.js index ad59a1f..947f156 100644 --- a/Client/js/core/DebugLogger.js +++ b/Client/js/core/DebugLogger.js @@ -5,7 +5,9 @@ class DebugLogger { constructor() { - this.debugEnabled = true; // Always enabled + // Completely disable debug logging to prevent console flooding + this.debugEnabled = false; + this.startTime = performance.now(); this.stepTimers = new Map(); this.debugLogs = []; // Store logs in memory @@ -15,10 +17,15 @@ class DebugLogger { this.logger = window.logger || null; // Log initialization - this.log('=== DEBUG SESSION STARTED ==='); + if (this.debugEnabled) { + this.log('=== DEBUG SESSION STARTED ==='); + } } async log(message, data = null) { + // Skip logging if debug is disabled + if (!this.debugEnabled) return; + const timestamp = new Date().toISOString(); const stackTrace = new Error().stack; @@ -55,13 +62,13 @@ class DebugLogger { this.debugLogs.shift(); } - // Always log to console - console.log(`[DEBUG] ${message}`, data || ''); + // Skip console logging to prevent flooding + // console.log(`[DEBUG] ${message}`, data || ''); - // Log performance data to console - if (performanceData.memory) { - console.log(`[PERF] ${performanceData.elapsed} | Memory: ${performanceData.memory.used}/${performanceData.memory.total}`); - } + // Skip performance logging to prevent flooding + // if (performanceData.memory) { + // console.log(`[PERF] ${performanceData.elapsed} | Memory: ${performanceData.memory.used}/${performanceData.memory.total}`); + // } // Use existing logger if available if (this.logger) { @@ -79,6 +86,9 @@ class DebugLogger { } async startStep(stepName) { + // Skip logging if debug is disabled + if (!this.debugEnabled) return; + this.stepTimers.set(stepName, performance.now()); await this.log(`STEP START: ${stepName}`, { type: 'step_start', @@ -88,6 +98,9 @@ class DebugLogger { } async endStep(stepName, data = null) { + // Skip logging if debug is disabled + if (!this.debugEnabled) return; + const startTime = this.stepTimers.get(stepName); const duration = startTime ? (performance.now() - startTime).toFixed(2) : 'N/A'; @@ -101,6 +114,9 @@ class DebugLogger { } async logStep(stepName, data = null) { + // Skip logging if debug is disabled + if (!this.debugEnabled) return; + await this.log(`STEP: ${stepName}`, { type: 'step', step: stepName, diff --git a/Client/js/core/Economy.js b/Client/js/core/Economy.js index ecdea9c..d9a3f52 100644 --- a/Client/js/core/Economy.js +++ b/Client/js/core/Economy.js @@ -1,21 +1,24 @@ /** * Galaxy Strike Online - Economy System - * Manages credits, gems, transactions, and shop + * Manages player currency, transactions, and shop functionality + * Now uses server-side ItemSystem for all item data */ class Economy { constructor(gameEngine) { - const debugLogger = window.debugLogger; - if (debugLogger) debugLogger.log('Economy constructor called'); - this.game = gameEngine; - // Currency - this.credits = 1000; - this.gems = 10; - this.premiumCurrency = 0; // For future premium features + // Preserve existing economy data if available (prevents wipe during re-initialization) + const existingEconomy = window.game?.systems?.economy; + const preservedCredits = existingEconomy?.credits || 0; + const preservedGems = existingEconomy?.gems || 0; - // Transaction history + // Player resources + this.credits = preservedCredits; + this.gems = preservedGems; + this.premiumCurrency = 0; + + // Transaction tracking this.transactionHistory = []; this.transactions = []; // Add missing transactions array @@ -25,1324 +28,125 @@ class Economy { // Shop categories this.shopCategories = { ships: 'Ships', - weapons: 'Weapons', - armors: 'Armors', - materials: 'Materials', + weapons: 'Weapons', + modules: 'Modules', cosmetics: 'Cosmetics', - // upgrades: 'Upgrades', // Temporarily disabled consumables: 'Consumables', - buildings: 'Buildings' + materials: 'Materials' }; - // Random shop system + // Random shop system - now uses server ItemSystem this.randomShopItems = {}; // Current random items per category this.shopRefreshInterval = null; // Timer for 2-hour refresh this.shopHeartbeatInterval = null; // Timer for live countdown updates this.lastShopRefresh = null; // Timestamp of last refresh this.SHOP_REFRESH_INTERVAL = 2 * 60 * 60 * 1000; // 2 hours in milliseconds - this.MAX_ITEMS_PER_CATEGORY = 6; + this.MAX_ITEMS_PER_CATEGORY = 8; this.categoryPurchaseLimits = {}; // Track purchases per category per refresh - // Shop items - this.shopItems = { - ships: [ - // Starter Cruiser Variants - { - id: 'starter_cruiser_common', - name: 'Starter Cruiser', - type: 'ship', - rarity: 'common', - price: 5000, - currency: 'credits', - description: 'Reliable starter cruiser for new pilots', - texture: 'assets/textures/ships/starter_cruiser.png', - stats: { attack: 15, speed: 10, defense: 12, hull: 100 } - }, - { - id: 'starter_cruiser_uncommon', - name: 'Starter Cruiser II', - type: 'ship', - rarity: 'uncommon', - price: 12000, - currency: 'credits', - description: 'Upgraded starter cruiser with enhanced systems', - texture: 'assets/textures/ships/starter_cruiser.png', - stats: { attack: 18, speed: 12, defense: 15, hull: 120 } - }, - { - id: 'starter_cruiser_rare', - name: 'Starter Cruiser III', - type: 'ship', - rarity: 'rare', - price: 25000, - currency: 'credits', - description: 'Elite starter cruiser with advanced weaponry', - texture: 'assets/textures/ships/starter_cruiser.png', - stats: { attack: 22, speed: 14, defense: 18, hull: 145 } - }, - { - id: 'starter_cruiser_epic', - name: 'Starter Cruiser IV', - type: 'ship', - rarity: 'epic', - price: 45000, - currency: 'credits', - description: 'Master starter cruiser with elite modifications', - texture: 'assets/textures/ships/starter_cruiser.png', - stats: { attack: 26, speed: 16, defense: 22, hull: 175 } - }, - { - id: 'starter_cruiser_legendary', - name: 'Starter Cruiser V', - type: 'ship', - rarity: 'legendary', - price: 75000, - currency: 'credits', - description: 'Legendary starter cruiser with unparalleled performance', - texture: 'assets/textures/ships/starter_cruiser.png', - stats: { attack: 32, speed: 18, defense: 28, hull: 210 } - }, - - // Light Destroyer Variants - { - id: 'light_destroyer_common', - name: 'Light Destroyer', - type: 'ship', - rarity: 'common', - price: 12000, - currency: 'credits', - description: 'Fast and maneuverable light destroyer', - texture: 'assets/textures/ships/light_destroyer.png', - stats: { attack: 18, speed: 18, defense: 10, hull: 80 } - }, - { - id: 'light_destroyer_uncommon', - name: 'Light Destroyer II', - type: 'ship', - rarity: 'uncommon', - price: 28000, - currency: 'credits', - description: 'Enhanced light destroyer with improved speed', - texture: 'assets/textures/ships/light_destroyer.png', - stats: { attack: 22, speed: 22, defense: 12, hull: 95 } - }, - { - id: 'light_destroyer_rare', - name: 'Light Destroyer III', - type: 'ship', - rarity: 'rare', - price: 55000, - currency: 'credits', - description: 'Elite light destroyer with superior agility', - texture: 'assets/textures/ships/light_destroyer.png', - stats: { attack: 26, speed: 26, defense: 15, hull: 115 } - }, - { - id: 'light_destroyer_epic', - name: 'Light Destroyer IV', - type: 'ship', - rarity: 'epic', - price: 95000, - currency: 'credits', - description: 'Master light destroyer with exceptional speed', - texture: 'assets/textures/ships/light_destroyer.png', - stats: { attack: 30, speed: 30, defense: 18, hull: 140 } - }, - { - id: 'light_destroyer_legendary', - name: 'Light Destroyer V', - type: 'ship', - rarity: 'legendary', - price: 150000, - currency: 'credits', - description: 'Legendary light destroyer with unmatched velocity', - texture: 'assets/textures/ships/light_destroyer.png', - stats: { attack: 36, speed: 36, defense: 22, hull: 170 } - }, - - // Heavy Destroyer Variants - { - id: 'heavy_destroyer_common', - name: 'Heavy Destroyer', - type: 'ship', - rarity: 'common', - price: 25000, - currency: 'credits', - description: 'Powerful heavy destroyer with strong weapons', - texture: 'assets/textures/ships/heavy_destroyer.png', - stats: { attack: 25, speed: 12, defense: 18, hull: 120 } - }, - { - id: 'heavy_destroyer_uncommon', - name: 'Heavy Destroyer II', - type: 'ship', - rarity: 'uncommon', - price: 55000, - currency: 'credits', - description: 'Upgraded heavy destroyer with enhanced firepower', - texture: 'assets/textures/ships/heavy_destroyer.png', - stats: { attack: 30, speed: 14, defense: 22, hull: 145 } - }, - { - id: 'heavy_destroyer_rare', - name: 'Heavy Destroyer III', - type: 'ship', - rarity: 'rare', - price: 110000, - currency: 'credits', - description: 'Elite heavy destroyer with devastating weapons', - texture: 'assets/textures/ships/heavy_destroyer.png', - stats: { attack: 35, speed: 16, defense: 26, hull: 175 } - }, - { - id: 'heavy_destroyer_epic', - name: 'Heavy Destroyer IV', - type: 'ship', - rarity: 'epic', - price: 190000, - currency: 'credits', - description: 'Master heavy destroyer with overwhelming power', - texture: 'assets/textures/ships/heavy_destroyer.png', - stats: { attack: 40, speed: 18, defense: 30, hull: 210 } - }, - { - id: 'heavy_destroyer_legendary', - name: 'Heavy Destroyer V', - type: 'ship', - rarity: 'legendary', - price: 300000, - currency: 'credits', - description: 'Legendary heavy destroyer with ultimate destruction', - texture: 'assets/textures/ships/heavy_destroyer.png', - stats: { attack: 48, speed: 20, defense: 36, hull: 255 } - }, - - // Heavy Cruiser Variants - { - id: 'heavy_cruiser_common', - name: 'Heavy Cruiser', - type: 'ship', - rarity: 'common', - price: 45000, - currency: 'credits', - description: 'Massive heavy cruiser with excellent defense', - texture: 'assets/textures/ships/heavy_cruiser.png', - stats: { attack: 22, speed: 8, defense: 25, hull: 150 } - }, - { - id: 'heavy_cruiser_uncommon', - name: 'Heavy Cruiser II', - type: 'ship', - rarity: 'uncommon', - price: 95000, - currency: 'credits', - description: 'Enhanced heavy cruiser with superior armor', - texture: 'assets/textures/ships/heavy_cruiser.png', - stats: { attack: 26, speed: 9, defense: 30, hull: 180 } - }, - { - id: 'heavy_cruiser_rare', - name: 'Heavy Cruiser III', - type: 'ship', - rarity: 'rare', - price: 190000, - currency: 'credits', - description: 'Elite heavy cruiser with fortress-like defense', - texture: 'assets/textures/ships/heavy_cruiser.png', - stats: { attack: 30, speed: 10, defense: 36, hull: 220 } - }, - { - id: 'heavy_cruiser_epic', - name: 'Heavy Cruiser IV', - type: 'ship', - rarity: 'epic', - price: 320000, - currency: 'credits', - description: 'Master heavy cruiser with impenetrable armor', - texture: 'assets/textures/ships/heavy_cruiser.png', - stats: { attack: 34, speed: 11, defense: 42, hull: 265 } - }, - { - id: 'heavy_cruiser_legendary', - name: 'Heavy Cruiser V', - type: 'ship', - rarity: 'legendary', - price: 500000, - currency: 'credits', - description: 'Legendary heavy cruiser with ultimate defense', - texture: 'assets/textures/ships/heavy_cruiser.png', - stats: { attack: 40, speed: 12, defense: 50, hull: 320 } - } - ], - weapons: [ - // Starter Blaster Variants - { - id: 'starter_blaster_common', - name: 'Starter Blaster', - type: 'weapon', - rarity: 'common', - price: 2000, - currency: 'credits', - description: 'Basic blaster for new pilots', - texture: 'assets/textures/weapons/starter_blaster.png', - stats: { damage: 10, fireRate: 2, range: 5, energy: 5 } - }, - { - id: 'starter_blaster_uncommon', - name: 'Starter Blaster II', - type: 'weapon', - rarity: 'uncommon', - price: 5000, - currency: 'credits', - description: 'Enhanced starter blaster with better fire rate', - texture: 'assets/textures/weapons/starter_blaster.png', - stats: { damage: 12, fireRate: 2.5, range: 5.5, energy: 6 } - }, - { - id: 'starter_blaster_rare', - name: 'Starter Blaster III', - type: 'weapon', - rarity: 'rare', - price: 12000, - currency: 'credits', - description: 'Elite starter blaster with improved damage', - texture: 'assets/textures/weapons/starter_blaster.png', - stats: { damage: 15, fireRate: 3, range: 6, energy: 7 } - }, - { - id: 'starter_blaster_epic', - name: 'Starter Blaster IV', - type: 'weapon', - rarity: 'epic', - price: 25000, - currency: 'credits', - description: 'Master starter blaster with superior performance', - texture: 'assets/textures/weapons/starter_blaster.png', - stats: { damage: 18, fireRate: 3.5, range: 6.5, energy: 8 } - }, - { - id: 'starter_blaster_legendary', - name: 'Starter Blaster V', - type: 'weapon', - rarity: 'legendary', - price: 50000, - currency: 'credits', - description: 'Legendary starter blaster with ultimate power', - texture: 'assets/textures/weapons/starter_blaster.png', - stats: { damage: 22, fireRate: 4, range: 7, energy: 10 } - }, - - // Laser Pistol Variants - { - id: 'laser_pistol_common', - name: 'Laser Pistol', - type: 'weapon', - rarity: 'common', - price: 3000, - currency: 'credits', - description: 'Compact laser pistol for close combat', - texture: 'assets/textures/weapons/laser_pistol.png', - stats: { damage: 8, fireRate: 3, range: 4, energy: 3 } - }, - { - id: 'laser_pistol_uncommon', - name: 'Laser Pistol II', - type: 'weapon', - rarity: 'uncommon', - price: 7500, - currency: 'credits', - description: 'Enhanced laser pistol with better accuracy', - texture: 'assets/textures/weapons/laser_pistol.png', - stats: { damage: 10, fireRate: 3.5, range: 4.5, energy: 4 } - }, - { - id: 'laser_pistol_rare', - name: 'Laser Pistol III', - type: 'weapon', - rarity: 'rare', - price: 18000, - currency: 'credits', - description: 'Elite laser pistol with piercing shots', - texture: 'assets/textures/weapons/laser_pistol.png', - stats: { damage: 13, fireRate: 4, range: 5, energy: 5 } - }, - { - id: 'laser_pistol_epic', - name: 'Laser Pistol IV', - type: 'weapon', - rarity: 'epic', - price: 35000, - currency: 'credits', - description: 'Master laser pistol with rapid fire capability', - texture: 'assets/textures/weapons/laser_pistol.png', - stats: { damage: 16, fireRate: 4.5, range: 5.5, energy: 6 } - }, - { - id: 'laser_pistol_legendary', - name: 'Laser Pistol V', - type: 'weapon', - rarity: 'legendary', - price: 70000, - currency: 'credits', - description: 'Legendary laser pistol with devastating power', - texture: 'assets/textures/weapons/laser_pistol.png', - stats: { damage: 20, fireRate: 5, range: 6, energy: 8 } - }, - - // Laser Sniper Rifle Variants - { - id: 'laser_sniper_rifle_common', - name: 'Laser Sniper Rifle', - type: 'weapon', - rarity: 'common', - price: 8000, - currency: 'credits', - description: 'Long-range laser sniper rifle for precision attacks', - texture: 'assets/textures/weapons/laser_sniper_rifle.png', - stats: { damage: 25, fireRate: 1, range: 10, energy: 8 } - }, - { - id: 'laser_sniper_rifle_uncommon', - name: 'Laser Sniper Rifle II', - type: 'weapon', - rarity: 'uncommon', - price: 20000, - currency: 'credits', - description: 'Enhanced laser sniper rifle with better penetration', - texture: 'assets/textures/weapons/laser_sniper_rifle.png', - stats: { damage: 30, fireRate: 1.2, range: 11, energy: 9 } - }, - { - id: 'laser_sniper_rifle_rare', - name: 'Laser Sniper Rifle III', - type: 'weapon', - rarity: 'rare', - price: 50000, - currency: 'credits', - description: 'Elite laser sniper rifle with deadly accuracy', - texture: 'assets/textures/weapons/laser_sniper_rifle.png', - stats: { damage: 36, fireRate: 1.4, range: 12, energy: 10 } - }, - { - id: 'laser_sniper_rifle_epic', - name: 'Laser Sniper Rifle IV', - type: 'weapon', - rarity: 'epic', - price: 100000, - currency: 'credits', - description: 'Master laser sniper rifle with extreme range', - texture: 'assets/textures/weapons/laser_sniper_rifle.png', - stats: { damage: 42, fireRate: 1.6, range: 13, energy: 12 } - }, - { - id: 'laser_sniper_rifle_legendary', - name: 'Laser Sniper Rifle V', - type: 'weapon', - rarity: 'legendary', - price: 200000, - currency: 'credits', - description: 'Legendary laser sniper rifle with unparalleled precision', - texture: 'assets/textures/weapons/laser_sniper_rifle.png', - stats: { damage: 50, fireRate: 2, range: 15, energy: 15 } - } - ], - armors: [ - // Basic Armor Variants - { - id: 'basic_armor_common', - name: 'Basic Armor', - type: 'armor', - rarity: 'common', - price: 1500, - currency: 'credits', - description: 'Light protection for beginners', - texture: 'assets/textures/armors/basic_armor.png', - stats: { defense: 5, durability: 20, weight: 2, energyShield: 0 } - }, - { - id: 'basic_armor_uncommon', - name: 'Basic Armor II', - type: 'armor', - rarity: 'uncommon', - price: 4000, - currency: 'credits', - description: 'Improved basic armor with better durability', - texture: 'assets/textures/armors/basic_armor.png', - stats: { defense: 7, durability: 25, weight: 2.2, energyShield: 2 } - }, - { - id: 'basic_armor_rare', - name: 'Basic Armor III', - type: 'armor', - rarity: 'rare', - price: 10000, - currency: 'credits', - description: 'Elite basic armor with energy shielding', - texture: 'assets/textures/armors/basic_armor.png', - stats: { defense: 10, durability: 30, weight: 2.5, energyShield: 5 } - }, - { - id: 'basic_armor_epic', - name: 'Basic Armor IV', - type: 'armor', - rarity: 'epic', - price: 20000, - currency: 'credits', - description: 'Master basic armor with advanced protection', - texture: 'assets/textures/armors/basic_armor.png', - stats: { defense: 13, durability: 35, weight: 2.8, energyShield: 8 } - }, - { - id: 'basic_armor_legendary', - name: 'Basic Armor V', - type: 'armor', - rarity: 'legendary', - price: 40000, - currency: 'credits', - description: 'Legendary basic armor with ultimate defense', - texture: 'assets/textures/armors/basic_armor.png', - stats: { defense: 17, durability: 40, weight: 3, energyShield: 12 } - }, - - // Medium Armor Variants - { - id: 'medium_armor_common', - name: 'Medium Armor', - type: 'armor', - rarity: 'common', - price: 5000, - currency: 'credits', - description: 'Balanced armor for general combat', - texture: 'assets/textures/armors/medium_armor.png', - stats: { defense: 12, durability: 40, weight: 5, energyShield: 3 } - }, - { - id: 'medium_armor_uncommon', - name: 'Medium Armor II', - type: 'armor', - rarity: 'uncommon', - price: 12000, - currency: 'credits', - description: 'Enhanced medium armor with better shielding', - texture: 'assets/textures/armors/medium_armor.png', - stats: { defense: 15, durability: 50, weight: 5.5, energyShield: 6 } - }, - { - id: 'medium_armor_rare', - name: 'Medium Armor III', - type: 'armor', - rarity: 'rare', - price: 30000, - currency: 'credits', - description: 'Elite medium armor with advanced systems', - texture: 'assets/textures/armors/medium_armor.png', - stats: { defense: 19, durability: 60, weight: 6, energyShield: 10 } - }, - { - id: 'medium_armor_epic', - name: 'Medium Armor IV', - type: 'armor', - rarity: 'epic', - price: 60000, - currency: 'credits', - description: 'Master medium armor with superior protection', - texture: 'assets/textures/armors/medium_armor.png', - stats: { defense: 23, durability: 70, weight: 6.5, energyShield: 15 } - }, - { - id: 'medium_armor_legendary', - name: 'Medium Armor V', - type: 'armor', - rarity: 'legendary', - price: 100000, - currency: 'credits', - description: 'Legendary medium armor with ultimate defense', - texture: 'assets/textures/armors/medium_armor.png', - stats: { defense: 28, durability: 80, weight: 7, energyShield: 20 } - }, - - // Heavy Armor Variants - { - id: 'heavy_armor_common', - name: 'Heavy Armor', - type: 'armor', - rarity: 'common', - price: 10000, - currency: 'credits', - description: 'Maximum protection with increased weight', - texture: 'assets/textures/armors/heavy_armor.png', - stats: { defense: 20, durability: 60, weight: 8, energyShield: 5 } - }, - { - id: 'heavy_armor_uncommon', - name: 'Heavy Armor II', - type: 'armor', - rarity: 'uncommon', - price: 25000, - currency: 'credits', - description: 'Upgraded heavy armor with better energy shielding', - texture: 'assets/textures/armors/heavy_armor.png', - stats: { defense: 25, durability: 75, weight: 9, energyShield: 10 } - }, - { - id: 'heavy_armor_rare', - name: 'Heavy Armor III', - type: 'armor', - rarity: 'rare', - price: 60000, - currency: 'credits', - description: 'Elite heavy armor with advanced protection systems', - texture: 'assets/textures/armors/heavy_armor.png', - stats: { defense: 31, durability: 90, weight: 10, energyShield: 16 } - }, - { - id: 'heavy_armor_epic', - name: 'Heavy Armor IV', - type: 'armor', - rarity: 'epic', - price: 120000, - currency: 'credits', - description: 'Master heavy armor with superior defense capabilities', - texture: 'assets/textures/armors/heavy_armor.png', - stats: { defense: 37, durability: 105, weight: 11, energyShield: 23 } - }, - { - id: 'heavy_armor_legendary', - name: 'Heavy Armor V', - type: 'armor', - rarity: 'legendary', - price: 200000, - currency: 'credits', - description: 'Legendary heavy armor with ultimate protection', - texture: 'assets/textures/armors/heavy_armor.png', - stats: { defense: 45, durability: 120, weight: 12, energyShield: 30 } - } - ], - cosmetics: [ - { - id: 'blue_paint', - name: 'Blue Paint Job', - type: 'cosmetic', - rarity: 'common', - price: 100, - currency: 'gems', - description: 'Custom blue paint for your ship' - }, - { - id: 'golden_trim', - name: 'Golden Trim', - type: 'cosmetic', - rarity: 'rare', - price: 500, - currency: 'gems', - description: 'Luxurious golden trim for your ship' - } - ], - consumables: [ - { - id: 'health_kit', - name: 'Health Kit', - type: 'consumable', - rarity: 'common', - price: 15, - currency: 'credits', - description: 'Restores 50 health points', - texture: 'assets/textures/items/health_pack.png', - effect: { heal: 50 } - }, - { - id: 'mega_health_kit', - name: 'Mega Health Kit', - type: 'consumable', - rarity: 'uncommon', - price: 50, - currency: 'credits', - description: 'Restores full health', - texture: 'assets/textures/items/mega_health_pack.png', - effect: { heal: 999 } - } - ], - buildings: [ - { - id: 'command_center', - name: 'Command Center', - type: 'building', - rarity: 'uncommon', - price: 50000, - currency: 'credits', - description: 'Central command facility for base operations and coordination', - texture: 'assets/textures/base/command_center.png', - stats: { command: 10, efficiency: 15, capacity: 20 } - }, - { - id: 'mining_facility', - name: 'Mining Facility', - type: 'building', - rarity: 'common', - price: 25000, - currency: 'credits', - description: 'Automated mining facility for resource extraction and processing', - texture: 'assets/textures/base/mining_facility.png', - stats: { production: 12, efficiency: 8, storage: 15 } - } - ], - materials: [ - { - id: 'iron_ore', - name: 'Iron Ore', - type: 'material', - rarity: 'common', - price: 10, - currency: 'credits', - description: 'Basic metal ore used for crafting weapons and armor', - texture: 'assets/textures/items/iron_ore.png', - stackable: true - }, - { - id: 'copper_ore', - name: 'Copper Ore', - type: 'material', - rarity: 'common', - price: 8, - currency: 'credits', - description: 'Conductive metal ore used for wiring and electronics', - texture: 'assets/textures/items/copper_ore.png', - stackable: true - }, - { - id: 'tin_bar', - name: 'Tin Bar', - type: 'material', - rarity: 'common', - price: 12, - currency: 'credits', - description: 'Refined tin bar used for alloys and soldering', - texture: 'assets/textures/items/tin_bar.png', - stackable: true - }, - { - id: 'copper_wire', - name: 'Copper Wire', - type: 'material', - rarity: 'common', - price: 8, - currency: 'credits', - description: 'Conductive wiring for electronic components', - texture: 'assets/textures/items/copper_wire.png', - stackable: true - }, - { - id: 'energy_crystal', - name: 'Energy Crystal', - type: 'material', - rarity: 'uncommon', - price: 25, - currency: 'credits', - description: 'Crystallized energy source for power systems', - texture: 'assets/textures/items/energy_crystal.png', - stackable: true - }, - { - id: 'leather', - name: 'Leather', - type: 'material', - rarity: 'common', - price: 12, - currency: 'credits', - description: 'Durable material for crafting armor and accessories', - texture: 'assets/textures/items/leather.png', - stackable: true - }, - { - id: 'herbs', - name: 'Herbs', - type: 'material', - rarity: 'common', - price: 5, - currency: 'credits', - description: 'Medicinal herbs used for healing items', - texture: 'assets/textures/items/herbs.png', - stackable: true - }, - { - id: 'bandages', - name: 'Bandages', - type: 'material', - rarity: 'common', - price: 3, - currency: 'credits', - description: 'Medical bandages used for crafting healing items', - texture: 'assets/textures/items/bandages.png', - stackable: true - }, - { - id: 'steel_plate', - name: 'Steel Plate', - type: 'material', - rarity: 'uncommon', - price: 30, - currency: 'credits', - description: 'Reinforced steel plates used for advanced armor and ship components', - texture: 'assets/textures/items/stell_plate.png', - stackable: true - }, - { - id: 'advanced_component', - name: 'Advanced Component', - type: 'material', - rarity: 'rare', - price: 75, - currency: 'credits', - description: 'High-tech component used for advanced equipment and upgrades', - texture: 'assets/textures/items/advanced_component.png', - stackable: true - }, - { - id: 'advanced_components', - name: 'Advanced Components Set', - type: 'material', - rarity: 'epic', - price: 150, - currency: 'credits', - description: 'Complete set of advanced components for high-end manufacturing', - texture: 'assets/textures/items/advanced_components.png', - stackable: true - }, - { - id: 'basic_circuitboard', - name: 'Basic Circuit Board', - type: 'material', - rarity: 'common', - price: 20, - currency: 'credits', - description: 'Basic electronic circuit board for simple devices', - texture: 'assets/textures/items/basic_circuitboard.png', - stackable: true - }, - { - id: 'common_circuitboard', - name: 'Common Circuit Board', - type: 'material', - rarity: 'common', - price: 35, - currency: 'credits', - description: 'Standard circuit board for most electronic equipment', - texture: 'assets/textures/items/common_circuitboard.png', - stackable: true - }, - { - id: 'advanced_circuitboard', - name: 'Advanced Circuit Board', - type: 'material', - rarity: 'rare', - price: 100, - currency: 'credits', - description: 'High-performance circuit board for advanced systems', - texture: 'assets/textures/items/advanced_circuitboard.png', - stackable: true - }, - { - id: 'battery', - name: 'Battery', - type: 'material', - rarity: 'uncommon', - price: 35, - currency: 'credits', - description: 'Power batteries used for energy-based equipment', - texture: 'assets/textures/items/battery.png', - stackable: true - }, - { - id: 'advanced_components', - name: 'Advanced Components', - type: 'material', - rarity: 'rare', - price: 150, - currency: 'credits', - description: 'Sophisticated electronic components for advanced ship systems', - stackable: true - } - ] - }; + // Shop items - now loaded from server ItemSystem + this.shopItems = null; // Will be populated by ItemSystem in multiplayer // Owned cosmetics this.ownedCosmetics = []; - if (debugLogger) debugLogger.log('Economy constructor completed', { - initialCredits: this.credits, - initialGems: this.gems, - shopCategoriesCount: Object.keys(this.shopCategories).length, - totalShopItems: Object.values(this.shopItems).reduce((total, category) => total + category.length, 0) + console.log('[ECONOMY] Economy system initialized with server-side ItemSystem'); + console.log('[ECONOMY] Preserved values - Credits:', this.credits, 'Gems:', this.gems); + + // Set up socket listeners for economy sync + this.setupSocketListeners(); + + // Request fresh economy data after a short delay to ensure sync + if (window.smartSaveManager?.isMultiplayer) { + setTimeout(() => { + this.requestEconomyData(); + }, 1000); + } + } + + /** + * Set up socket listeners for economy data synchronization + */ + setupSocketListeners() { + if (!this.game.socket) { + console.warn('[ECONOMY] No socket available for economy sync'); + return; + } + + // Listen for economy data updates from server + this.game.socket.on('economy_data', (data) => { + console.log('[ECONOMY] Received economy data from server:', data); + this.credits = data.credits || 0; + this.gems = data.gems || 0; + + // Update UI immediately + if (this.game.ui) { + this.game.ui.updatePlayerStats(); + } + + console.log('[ECONOMY] Economy synced - Credits:', this.credits, 'Gems:', this.gems); }); } + /** + * Request economy data from server + */ + requestEconomyData() { + if (this.game.socket) { + console.log('[ECONOMY] Requesting economy data from server'); + this.game.socket.emit('get_economy_data'); + } else { + console.warn('[ECONOMY] Cannot request economy data - no socket available'); + } + } + async initialize() { - const debugLogger = window.debugLogger; - console.log('[ECONOMY] Economy system initializing'); - if (debugLogger) await debugLogger.startStep('economyInitialize'); + console.log('[ECONOMY] Initializing economy system'); - try { - if (debugLogger) await debugLogger.logStep('Economy initialization started', { - currentCredits: this.credits, - currentGems: this.gems, - transactionHistoryLength: this.transactionHistory.length - }); - - // Setup shop purchase event listeners - this.setupShopEventListeners(); - - // Initialize random shop system + // In multiplayer mode, wait for ItemSystem to be ready (handled by event listener) + this.game.on('itemSystemReady', () => { + console.log('[ECONOMY] ItemSystem is ready, updating shop UI'); + this.updateShopUI(); + }); + if (window.smartSaveManager?.isMultiplayer) { + console.log('[ECONOMY] Multiplayer mode - waiting for ItemSystem to be ready'); + // ItemSystem initialization removed - wait for event instead + } else { + console.log('[ECONOMY] Singleplayer mode - using local shop data'); + // Initialize random shop for singleplayer this.initializeRandomShop(); - - console.log('[ECONOMY] Economy system initialization completed'); - if (debugLogger) await debugLogger.endStep('economyInitialize'); - } catch (error) { - console.error('[ECONOMY] Error during initialization:', error); - if (debugLogger) await debugLogger.errorEvent(error, 'Economy Initialize'); } + + console.log('[ECONOMY] Economy system initialized'); } - initializeRandomShop() { - const debugLogger = window.debugLogger; - console.log('[ECONOMY] Initializing random shop system'); - - // Shop data is now loaded through the main save/load system in load() - // Only initialize if no shop data exists (new game) - if (!this.lastShopRefresh || Object.keys(this.randomShopItems).length === 0) { - console.log('[ECONOMY] No shop data found, generating new shop'); - this.refreshRandomShop(); - } else { - console.log('[ECONOMY] Shop data already loaded from save'); - // Start the refresh timer for ongoing updates - this.startShopRefreshTimer(); - } - - if (debugLogger) debugLogger.logStep('Random shop system initialized', { - hasShopData: Object.keys(this.randomShopItems).length > 0, - lastRefresh: this.lastShopRefresh - }); - } - - refreshRandomShop() { - const debugLogger = window.debugLogger; - console.log('[ECONOMY] Refreshing random shop items'); - - const categories = Object.keys(this.shopCategories); - this.randomShopItems = {}; - - categories.forEach(category => { - const categoryItems = this.shopItems[category] || []; - if (categoryItems.length === 0) return; - - // Group items by their base ID (remove tier suffixes) - const baseItemGroups = {}; - categoryItems.forEach(item => { - // Extract base ID by removing tier suffixes (common, uncommon, rare, epic, legendary) - const baseId = item.id.replace(/_(common|uncommon|rare|epic|legendary)$/, ''); - if (!baseItemGroups[baseId]) { - baseItemGroups[baseId] = []; - } - baseItemGroups[baseId].push(item); - }); - - // Get all unique base items - const uniqueBaseItems = Object.keys(baseItemGroups); - - // Randomly select up to MAX_ITEMS_PER_CATEGORY base items - const shuffledBaseItems = [...uniqueBaseItems].sort(() => Math.random() - 0.5); - const selectedBaseItems = shuffledBaseItems.slice(0, Math.min(this.MAX_ITEMS_PER_CATEGORY, shuffledBaseItems.length)); - - // For each selected base item, randomly pick one tier variant - this.randomShopItems[category] = selectedBaseItems.map(baseId => { - const variants = baseItemGroups[baseId]; - const selectedVariant = variants[Math.floor(Math.random() * variants.length)]; - - return { - ...selectedVariant, - id: `${selectedVariant.id}_random_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, - originalId: selectedVariant.id, - baseId: baseId, - price: Math.round(selectedVariant.price * (0.8 + Math.random() * 0.4)), // ±20% price variation - isRandomItem: true, - refreshTimestamp: Date.now() - }; - }); - - // Reset purchase limits for this category - this.categoryPurchaseLimits[category] = 0; - - if (debugLogger) debugLogger.logStep('Random shop category refreshed', { - category: category, - totalBaseItems: uniqueBaseItems.length, - baseItemsSelected: selectedBaseItems.length, - itemsGenerated: this.randomShopItems[category].length, - selectedVariants: this.randomShopItems[category].map(item => ({ - baseId: item.baseId, - variantId: item.originalId, - rarity: item.rarity, - name: item.name - })), - purchaseLimitReset: true - }); - }); - - this.lastShopRefresh = Date.now(); - this.saveRandomShopData(); - this.startShopRefreshTimer(); - - // Update UI if shop tab is active - this.updateShopUI(); - - this.game.showNotification('Shop inventory refreshed!', 'info', 3000); - - if (debugLogger) debugLogger.logStep('Random shop refresh completed', { - categoriesRefreshed: categories.length, - totalItemsGenerated: Object.values(this.randomShopItems).flat().length, - nextRefresh: new Date(this.lastShopRefresh + this.SHOP_REFRESH_INTERVAL) - }); - } - - startShopRefreshTimer() { - // Clear existing timers - if (this.shopRefreshInterval) { - clearInterval(this.shopRefreshInterval); - } - if (this.shopHeartbeatInterval) { - clearInterval(this.shopHeartbeatInterval); - } - - // Set up heartbeat timer for live countdown updates (every second) - this.shopHeartbeatInterval = setInterval(() => { - this.updateShopCountdown(); - }, 1000); - - // Set up refresh timer (every 2 hours) - this.shopRefreshInterval = setInterval(() => { - this.refreshRandomShop(); - }, this.SHOP_REFRESH_INTERVAL); - - console.log('[ECONOMY] Shop refresh timers started'); - } - - updateShopCountdown() { - // Only update if shop tab is active and using random shop - const shopTab = document.getElementById('shop-tab'); - if (!shopTab || shopTab.style.display === 'none') { - return; - } - - const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships'; - if (!this.randomShopItems[activeCategory]) { - return; - } - - // Find and update the countdown element - const countdownElement = document.querySelector('.refresh-countdown'); - if (countdownElement) { - countdownElement.innerHTML = ` - - Next refresh in: ${this.getShopRefreshCountdown()} - `; - } - } - - stopShopRefreshTimer() { - if (this.shopRefreshInterval) { - clearInterval(this.shopRefreshInterval); - this.shopRefreshInterval = null; - } - if (this.shopHeartbeatInterval) { - clearInterval(this.shopHeartbeatInterval); - this.shopHeartbeatInterval = null; - } - console.log('[ECONOMY] Shop refresh timers stopped'); - } - - loadRandomShopData() { - try { - const savedData = localStorage.getItem('gso_random_shop'); - if (savedData) { - const data = JSON.parse(savedData); - this.randomShopItems = data.randomShopItems || {}; - this.categoryPurchaseLimits = data.categoryPurchaseLimits || {}; - this.lastShopRefresh = data.lastShopRefresh || null; - console.log('[ECONOMY] Random shop data loaded from localStorage'); - } - } catch (error) { - console.error('[ECONOMY] Error loading random shop data:', error); - this.randomShopItems = {}; - this.categoryPurchaseLimits = {}; - this.lastShopRefresh = null; - } - } - saveRandomShopData() { - try { - const data = { - randomShopItems: this.randomShopItems, - categoryPurchaseLimits: this.categoryPurchaseLimits, - lastShopRefresh: this.lastShopRefresh - }; - localStorage.setItem('gso_random_shop', JSON.stringify(data)); - console.log('[ECONOMY] Random shop data saved to localStorage'); - } catch (error) { - console.error('[ECONOMY] Error saving random shop data:', error); - } - } - - shouldRefreshShop() { - // Check if enough time has passed since the last actual refresh - if (!this.lastShopRefresh) { - return true; // No previous refresh, need to refresh - } - - const now = Date.now(); - const timeSinceRefresh = now - this.lastShopRefresh; - return timeSinceRefresh >= this.SHOP_REFRESH_INTERVAL; - } - - getShopRefreshCountdown() { - const schedule = this.getRefreshSchedule(); - const now = Date.now(); - - // Calculate time until next server refresh - const timeUntilRefresh = schedule.nextRefresh.getTime() - now; - - if (timeUntilRefresh <= 0) { - return 'Refreshing...'; - } - - return this.formatTimeRemaining(timeUntilRefresh); - } - - getNextRefreshTime() { - // Calculate the next refresh time based on server time - const now = Date.now(); - - if (!this.lastShopRefresh) { - // If no last refresh, next refresh is now + interval - return new Date(now + this.SHOP_REFRESH_INTERVAL); - } - - // Calculate next refresh time - const timeSinceRefresh = now - this.lastShopRefresh; - const intervalsPassed = Math.floor(timeSinceRefresh / this.SHOP_REFRESH_INTERVAL); - const nextRefreshTime = this.lastShopRefresh + ((intervalsPassed + 1) * this.SHOP_REFRESH_INTERVAL); - - return new Date(nextRefreshTime); - } - - getRefreshSchedule() { - // Generate a consistent refresh schedule based on server time - const now = new Date(); - const currentHour = now.getHours(); - - // Refresh at even hours: 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22 - let nextRefreshHour; - - if (currentHour % 2 === 0 && now.getMinutes() === 0 && now.getSeconds() === 0) { - // Exactly on an even hour at :00:00, next refresh is in 2 hours - nextRefreshHour = currentHour + 2; - } else { - // Find the next even hour - nextRefreshHour = currentHour + (2 - (currentHour % 2)); - } - - // Handle midnight wrap-around - if (nextRefreshHour >= 24) { - nextRefreshHour = nextRefreshHour % 24; - } - - const nextRefresh = new Date(now); - nextRefresh.setHours(nextRefreshHour, 0, 0, 0); - - // If the calculated time is in the past (shouldn't happen but just in case), add 2 hours - if (nextRefresh <= now) { - nextRefresh.setHours(nextRefresh.getHours() + 2); - } - - console.log('[ECONOMY] Current time:', now.toLocaleTimeString()); - console.log('[ECONOMY] Next refresh at:', nextRefresh.toLocaleTimeString()); - - return { - nextRefresh: nextRefresh, - interval: 2 * 60 * 60 * 1000, // 2 hours - schedule: 'Every 2 hours at even hours (2,4,6,8,10,12,14,16,18,20,22,00)' - }; - } - - formatTimeRemaining(timeUntilRefresh) { - const hours = Math.floor(timeUntilRefresh / (1000 * 60 * 60)); - const minutes = Math.floor((timeUntilRefresh % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((timeUntilRefresh % (1000 * 60)) / 1000); - - if (hours > 0) { - return `${hours}h ${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; - } else { - return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; - } - } - - getItemCategory(item) { - // Determine category based on item type - switch (item.type) { - case 'ship': return 'ships'; - case 'weapon': return 'weapons'; - case 'armor': return 'armors'; - case 'material': return 'materials'; - case 'cosmetic': return 'cosmetics'; - case 'upgrade': return 'upgrades'; - case 'consumable': return 'consumables'; - case 'building': return 'buildings'; - default: - // For random items, check if we can find them in shop categories - for (const [category, items] of Object.entries(this.shopItems)) { - if (items.some(shopItem => shopItem.id === item.originalId || shopItem.id === item.id)) { - return category; - } - } - return 'unknown'; - } - } - - setupShopEventListeners() { - const debugLogger = window.debugLogger; - console.log('[ECONOMY] Setting up shop event listeners'); - - // Remove existing listeners to prevent duplicates - const shopItemsElement = document.getElementById('shopItems'); - if (shopItemsElement) { - // Clone the element to remove all event listeners - const newElement = shopItemsElement.cloneNode(true); - shopItemsElement.parentNode.replaceChild(newElement, shopItemsElement); - - // Setup shop purchase button event delegation on the new element - newElement.addEventListener('click', (event) => { - const purchaseBtn = event.target.closest('.shop-item-purchase-btn'); - if (purchaseBtn && !purchaseBtn.disabled) { - const itemId = purchaseBtn.getAttribute('data-item-id'); - console.log(`[ECONOMY] Shop purchase button clicked for item: ${itemId}`); - - if (itemId) { - this.purchaseItem(itemId, 1); - } else { - console.error('[ECONOMY] No item ID found on purchase button'); - } - } - }); - - console.log('[ECONOMY] Shop event listeners setup completed'); - } else { - console.error('[ECONOMY] Shop items element not found'); - } - } - - // Currency management - addCredits(amount, source = 'unknown') { - const oldCredits = this.credits; - this.credits += amount; - this.addTransaction('credits', amount, source); - - console.log(`[ECONOMY] Added ${amount} credits from ${source}. New total: ${this.credits}`); - - // Update UI to show new credit amount - if (this.game.systems.ui && this.game.shouldUpdateGUI()) { - this.game.systems.ui.updateUI(); - } - - this.game.showNotification(`+${this.game.formatNumber(amount)} credits`, 'success', 2000); - } - - removeCredits(amount) { - const oldCredits = this.credits; - - if (this.credits < amount) { - this.game.showNotification('Not enough credits!', 'error', 3000); - return false; - } - - this.credits -= amount; - this.addTransaction('credits', -amount, 'purchase'); - - console.log(`[ECONOMY] Removed ${amount} credits. New total: ${this.credits}`); - - // Update UI to show new credit amount - if (this.game.systems.ui && this.game.shouldUpdateGUI()) { - this.game.systems.ui.updateUI(); - } - - return true; - } - - addGems(amount, source = 'unknown') { - const oldGems = this.gems; - this.gems += amount; - this.addTransaction('gems', amount, source); - - console.log(`[ECONOMY] Added ${amount} gems from ${source}. New total: ${this.gems}`); - - // Update UI to show new gem amount - if (this.game.systems.ui && this.game.shouldUpdateGUI()) { - this.game.systems.ui.updateUI(); - } - - this.game.showNotification(`+${this.game.formatNumber(amount)} gems`, 'success', 2000); - } - - removeGems(amount) { - const oldGems = this.gems; - - if (this.gems < amount) { - this.game.showNotification('Not enough gems!', 'error', 3000); - return false; - } - - this.gems -= amount; - this.addTransaction('gems', -amount, 'purchase'); - - console.log(`[ECONOMY] Removed ${amount} gems. New total: ${this.gems}`); - - // Update UI to show new gem amount - if (this.game.systems.ui && this.game.shouldUpdateGUI()) { - this.game.systems.ui.updateUI(); - } - - return true; - } - - // Transaction tracking - addTransaction(currency, amount, source) { - const debugLogger = window.debugLogger; - const transaction = { - id: Date.now() + Math.random().toString(36).substr(2, 9), - currency: currency, - amount: amount, - source: source, - timestamp: Date.now() - }; - - this.transactionHistory.unshift(transaction); - - // Keep only last 100 transactions - const oldLength = this.transactionHistory.length; - if (this.transactionHistory.length > 100) { - this.transactionHistory = this.transactionHistory.slice(0, 100); - } - - if (debugLogger) debugLogger.logStep('Transaction recorded', { - transactionId: transaction.id, - currency: currency, - amount: amount, - source: source, - timestamp: transaction.timestamp, - historyLength: this.transactionHistory.length, - wasTrimmed: oldLength > 100, - removedCount: oldLength > 100 ? oldLength - 100 : 0 - }); - } - - // Shop functionality + // Shop functionality - now uses ItemSystem in multiplayer purchaseItem(itemId, quantity = 1) { const debugLogger = window.debugLogger; + + // In multiplayer mode, send request to server + if (window.smartSaveManager?.isMultiplayer) { + if (debugLogger) debugLogger.logStep('Sending purchase request to server', { + itemId: itemId, + quantity: quantity + }); + + // Send purchase request to server + if (window.game && window.game.socket) { + window.game.socket.emit('purchaseItem', { + itemId: itemId, + quantity: quantity + }); + + // Show loading message + this.game.showNotification('Processing purchase...', 'info', 2000); + } else { + this.game.showNotification('Not connected to server', 'error', 3000); + } + return; + } + + // Singleplayer mode - use local logic const item = this.findShopItem(itemId); if (!item) { @@ -1423,25 +227,8 @@ class Economy { return false; } - // Random shop items don't have purchase limits - unlimited purchases allowed - if (item.isRandomItem) { - const category = this.getItemCategory(item); - - if (debugLogger) debugLogger.logStep('Random shop purchase completed', { - itemId: itemId, - itemName: item.name, - category: category - }); - } - - // Update inventory UI to show the new item - if (this.game.systems.ui && this.game.systems.ui.updateInventory) { - this.game.systems.ui.updateInventory(); - } else if (this.game.systems.ui && this.game.systems.ui.updateUI) { - this.game.systems.ui.updateUI(); - } else { - console.warn('No UI update method available after purchase'); - } + // Show success message + this.game.showNotification(`Purchased ${item.name}!`, 'success', 3000); if (debugLogger) debugLogger.logStep('Item purchase completed successfully', { itemId: itemId, @@ -1463,27 +250,42 @@ class Economy { findShopItem(itemId) { const debugLogger = window.debugLogger; - for (const category of Object.values(this.shopItems)) { - const item = category.find(i => i.id === itemId); - if (item) { - if (debugLogger) debugLogger.logStep('Shop item found', { - itemId: itemId, - itemName: item.name, - itemType: item.type, - itemPrice: item.price, - itemCurrency: item.currency - }); - return item; - } - } + console.log('[ECONOMY] Looking for shop item:', itemId); + console.log('[ECONOMY] Multiplayer mode:', window.smartSaveManager?.isMultiplayer); + console.log('[ECONOMY] ItemSystem available:', !!(this.game.systems.itemSystem)); - if (debugLogger) debugLogger.logStep('Shop item not found', { - itemId: itemId, - availableCategories: Object.keys(this.shopItems) - }); - return null; + // In multiplayer mode, use ItemSystem (required) + if (window.smartSaveManager?.isMultiplayer) { + // Check if ItemSystem is ready before using it + if (!this.game.systems.itemSystem || !this.game.systems.itemSystem.itemCatalog) { + console.log('[ECONOMY] ItemSystem not ready yet, cannot find shop item'); + if (debugLogger) debugLogger.logStep('Shop item lookup failed - ItemSystem not ready', { + itemId: itemId, + multiplayer: true + }); + return null; + } + + // Search in ItemSystem catalog + const item = this.game.systems.itemSystem.itemCatalog.get(itemId); + if (item) { + console.log('[ECONOMY] Found item in ItemSystem:', item.name); + return item; + } else { + console.log('[ECONOMY] Item not found in ItemSystem:', itemId); + return null; + } + } else { + // Singleplayer mode - search in local random shop + for (const categoryItems of Object.values(this.randomShopItems)) { + const item = categoryItems.find(item => item.id === itemId); + if (item) return item; + } + return null; + } } + // Purchase methods purchaseShip(ship) { const debugLogger = window.debugLogger; const player = this.game.systems.player; @@ -1491,75 +293,36 @@ class Economy { const oldShipClass = player.ship.class; const oldAttributes = { ...player.attributes }; - if (debugLogger) debugLogger.logStep('Ship purchase processing', { - shipId: ship.id, - shipName: ship.name, - shipType: ship.type, - shipStats: ship.stats, - oldShipName: oldShipName, - oldShipClass: oldShipClass - }); + // Update player ship + player.ship = { + name: ship.name, + class: ship.id, + texture: ship.texture, + stats: ship.stats || {} + }; - // Add ship to player's ship collection - // Add ship to base gallery - if (this.game.systems.baseSystem) { - this.game.systems.baseSystem.addShipToGallery(ship); - if (debugLogger) debugLogger.logStep('Ship added to base gallery'); + // Update player attributes + if (ship.stats) { + player.attributes = { ...player.attributes, ...ship.stats }; } - // Replace current ship (no upgrade functionality) - player.ship.name = ship.name; - player.ship.class = ship.type; - player.ship.level = 1; // Reset level for new ship - player.ship.texture = ship.texture; // Copy texture for image display - - // Set ship-specific stats (not just attributes) - if (ship.stats.health) { - player.ship.maxHealth = ship.stats.health; - player.ship.health = ship.stats.health; + // Add to owned ships + if (!player.ownedShips) { + player.ownedShips = []; } - if (ship.stats.attack) { - player.ship.attack = ship.stats.attack; - } - if (ship.stats.defense) { - player.ship.defense = ship.stats.defense; - } - if (ship.stats.speed) { - player.ship.speed = ship.stats.speed; - } - if (ship.stats.criticalChance) { - player.ship.criticalChance = ship.stats.criticalChance; - } - if (ship.stats.criticalDamage) { - player.ship.criticalDamage = ship.stats.criticalDamage; - } - - // Also update player attributes for compatibility - for (const [stat, value] of Object.entries(ship.stats)) { - if (player.attributes[stat] !== undefined) { - player.attributes[stat] = value; // Replace stats instead of adding - } - } - - player.updateUI(); - - // Also update ShipSystem display - if (this.game.systems.ship && this.game.systems.ship.updateCurrentShipDisplay) { - this.game.systems.ship.updateCurrentShipDisplay(); + if (!player.ownedShips.includes(ship.id)) { + player.ownedShips.push(ship.id); } if (debugLogger) debugLogger.logStep('Ship purchase completed', { shipId: ship.id, + shipName: ship.name, oldShipName: oldShipName, - newShipName: player.ship.name, oldShipClass: oldShipClass, - newShipClass: player.ship.class, - attributeChanges: Object.entries(ship.stats).map(([stat, value]) => ({ - stat: stat, - oldValue: oldAttributes[stat], - newValue: player.attributes[stat], - change: value - })) + newShipName: ship.name, + newShipClass: ship.id, + oldAttributes: oldAttributes, + newAttributes: player.attributes }); } @@ -1583,380 +346,277 @@ class Economy { purchaseConsumable(consumable, quantity) { const debugLogger = window.debugLogger; const inventory = this.game.systems.inventory; - const oldInventorySize = inventory ? inventory.items.length : 0; - if (debugLogger) debugLogger.logStep('Consumable purchase processing', { - consumableId: consumable.id, - consumableName: consumable.name, + // Create item object for inventory + const item = { + id: consumable.id, + name: consumable.name, + type: consumable.type, + rarity: consumable.rarity, quantity: quantity, - effect: consumable.effect, - oldInventorySize: oldInventorySize - }); - - // Add to inventory - for (let i = 0; i < quantity; i++) { - const item = { - id: `${consumable.id}_${Date.now()}_${i}`, - name: consumable.name, - type: 'consumable', - rarity: consumable.rarity, - quantity: 1, - description: consumable.description, - consumable: true, - effect: consumable.effect - }; - - inventory.addItem(item); - } - - if (debugLogger) debugLogger.logStep('Consumable purchase completed', { - consumableId: consumable.id, - quantity: quantity, - oldInventorySize: oldInventorySize, - newInventorySize: inventory ? inventory.items.length : 0, - itemsAdded: quantity - }); - } - - purchaseEquipment(equipment, quantity = 1) { - const inventory = this.game.systems.inventory; - const oldInventorySize = inventory ? inventory.items.length : 0; - - // Add to inventory - for (let i = 0; i < quantity; i++) { - const item = { - id: `${equipment.id}_${Date.now()}_${i}`, - name: equipment.name, - type: equipment.type, - rarity: equipment.rarity, - quantity: 1, - description: equipment.description, - texture: equipment.texture, - stats: equipment.stats || {}, - equipable: true - }; - - inventory.addItem(item); - } - } - - purchaseMaterial(material, quantity = 1) { - const inventory = this.game.systems.inventory; - const debugLogger = window.debugLogger; - - if (!inventory) { - console.error('[ECONOMY] Inventory system not available for material purchase'); - return false; - } + description: consumable.description, + texture: consumable.texture, + stats: consumable.stats || {}, + acquired: new Date().toISOString() + }; try { const oldInventorySize = inventory.items.length; - console.log(`[DEBUG] Inventory state before purchase: ${oldInventorySize} items`); + inventory.addItem(item); - // Create item object for inventory - const item = { - id: material.id, - name: material.name, - type: 'material', - rarity: material.rarity || 'common', - quantity: quantity, - description: material.description || `A ${material.name} material`, - stackable: true, - stats: {}, - equipable: false, - slot: null - }; - - console.log(`[DEBUG] Adding item to inventory: ${item.name}`); - const added = inventory.addItem(item); - - console.log(`[DEBUG] addItem() returned: ${added}, new inventory size: ${inventory.items.length}`); - - if (debugLogger) debugLogger.logStep('Material purchase completed', { - materialId: material.id, - materialName: material.name, + if (debugLogger) debugLogger.logStep('Consumable purchase completed', { + itemId: consumable.id, + itemName: consumable.name, quantity: quantity, oldInventorySize: oldInventorySize, - newInventorySize: inventory.items.length, - addedSuccessfully: added + newInventorySize: inventory.items.length }); - - if (!added) { - console.error('Failed to add material to inventory'); - return false; - } else { - // Update inventory UI to show the new material - if (this.game.systems.ui && this.game.systems.ui.updateInventory) { - this.game.systems.ui.updateInventory(); - } else if (this.game.systems.ui && this.game.systems.ui.updateUI) { - this.game.systems.ui.updateUI(); - } else { - console.warn('No UI update method available after material purchase'); - } - console.log('[DEBUG] Material purchase completed and UI update called'); - return true; - } } catch (error) { - console.error('Exception in purchaseMaterial:', error); - if (debugLogger) debugLogger.errorEvent('Purchase Material Exception', error, { - materialId: material.id, - materialName: material.name - }); - return false; + console.error('[ECONOMY] Error adding consumable to inventory:', error); + this.game.showNotification('Failed to add item to inventory', 'error', 3000); } } - // Reward generation - generateRewards(difficulty = 'normal', source = 'dungeon') { + purchaseMaterial(material, quantity) { const debugLogger = window.debugLogger; + const inventory = this.game.systems.inventory; - if (debugLogger) debugLogger.startStep('generateRewards', { - difficulty: difficulty, - source: source - }); - - const baseRewards = { - easy: { credits: [50, 150], gems: [0, 1], experience: [20, 50] }, - normal: { credits: [100, 300], gems: [1, 3], experience: [50, 100] }, - hard: { credits: [200, 500], gems: [2, 5], experience: [100, 200] }, - extreme: { credits: [500, 1000], gems: [5, 10], experience: [200, 400] } + // Create item object for inventory + const item = { + id: material.id, + name: material.name, + type: material.type, + rarity: material.rarity, + quantity: quantity, + description: material.description, + texture: material.texture, + stackable: material.stackable || true, + acquired: new Date().toISOString() }; - const rewards = baseRewards[difficulty] || baseRewards.normal; - const generatedRewards = {}; - - // Generate credits - generatedRewards.credits = Math.floor( - Math.random() * (rewards.credits[1] - rewards.credits[0] + 1) + rewards.credits[0] - ); - - // Generate gems - generatedRewards.gems = Math.floor( - Math.random() * (rewards.gems[1] - rewards.gems[0] + 1) + rewards.gems[0] - ); - - // Generate experience - generatedRewards.experience = Math.floor( - Math.random() * (rewards.experience[1] - rewards.experience[0] + 1) + rewards.experience[0] - ); - - // Bonus for premium currency (rare) - if (Math.random() < 0.05) { // 5% chance - generatedRewards.premiumCurrency = 1; - } - - if (debugLogger) debugLogger.endStep('generateRewards', { - difficulty: difficulty, - source: source, - generatedRewards: generatedRewards, - rewardRanges: rewards - }); - - return generatedRewards; - } - - giveRewards(rewards, source = 'unknown') { - const debugLogger = window.debugLogger; - const player = this.game.systems.player; - const oldCredits = this.credits; - const oldGems = this.gems; - const oldExperience = player.stats.experience; - const oldLevel = player.stats.level; - - if (debugLogger) debugLogger.startStep('giveRewards', { - source: source, - rewards: rewards, - oldPlayerState: { - credits: oldCredits, - gems: oldGems, - experience: oldExperience, - level: oldLevel - } - }); - - let totalRewards = []; - - // Add credits - if (rewards.credits > 0) { - this.addCredits(rewards.credits, source); - totalRewards.push(`${rewards.credits} credits`); - } - - // Add gems - if (rewards.gems > 0) { - this.addGems(rewards.gems, source); - totalRewards.push(`${rewards.gems} gems`); - } - - // Add premium currency - if (rewards.premiumCurrency > 0) { - this.addPremiumCurrency(rewards.premiumCurrency, source); - totalRewards.push(`${rewards.premiumCurrency} premium currency`); - } - - // Add experience - if (rewards.experience > 0) { - player.addExperience(rewards.experience); - totalRewards.push(`${rewards.experience} experience`); - } - - // Add materials - if (rewards.materials && rewards.materials.length > 0) { - const inventory = this.game.systems.inventory; - alert(`[DUNGEON DEBUG] Adding ${rewards.materials.length} materials to inventory\nInventory available: ${!!inventory}\nMaterials: ${JSON.stringify(rewards.materials, null, 2)}`); + try { + const oldInventorySize = inventory.items.length; + inventory.addItem(item); - for (const material of rewards.materials) { - // Create proper item object like in purchaseMaterial - const itemObject = { - id: material.id, - name: material.id.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()), // Convert ID to readable name - type: 'material', - rarity: 'common', - quantity: material.quantity, - description: `A ${material.id.replace('_', ' ')} material`, - stackable: true, - stats: {}, - equipable: false, - slot: null - }; - - alert(`[DUNGEON DEBUG] Fixed! Adding material as full item object:\n${JSON.stringify(itemObject, null, 2)}`); - inventory.addItem(itemObject); - totalRewards.push(`${material.quantity}x ${material.id}`); - } + if (debugLogger) debugLogger.logStep('Material purchase completed', { + itemId: material.id, + itemName: material.name, + quantity: quantity, + oldInventorySize: oldInventorySize, + newInventorySize: inventory.items.length + }); + } catch (error) { + console.error('[ECONOMY] Error adding material to inventory:', error); + this.game.showNotification('Failed to add item to inventory', 'error', 3000); } - - // Add items - if (rewards.items && rewards.items.length > 0) { - const inventory = this.game.systems.inventory; - for (const item of rewards.items) { - inventory.addItem(item.id, item.quantity || 1); - totalRewards.push(`${item.quantity || 1}x ${item.id}`); - } - } - - // Show reward notification - if (totalRewards.length > 0) { - const rewardText = totalRewards.join(', '); - this.game.showNotification(`Rewards: ${rewardText}`, 'success', 3000); - } - - if (debugLogger) debugLogger.endStep('giveRewards', { - source: source, - rewardsGiven: rewards, - totalRewardsText: totalRewards.join(', '), - currencyChanges: { - credits: { old: oldCredits, new: this.credits, change: this.credits - oldCredits }, - gems: { old: oldGems, new: this.gems, change: this.gems - oldGems } - }, - playerChanges: { - experience: { old: oldExperience, new: player.stats.experience, change: player.stats.experience - oldExperience }, - level: { old: oldLevel, new: player.stats.level, leveledUp: player.stats.level > oldLevel } - } - }); } - // Daily rewards and bonuses - claimDailyReward() { - const debugLogger = window.debugLogger; - const lastClaim = localStorage.getItem('lastDailyReward'); - const today = new Date().toDateString(); + // Currency management + addCredits(amount, source = 'unknown') { + const oldCredits = this.credits; + this.credits += amount; - if (debugLogger) debugLogger.startStep('claimDailyReward', { - lastClaim: lastClaim, - today: today, - alreadyClaimed: lastClaim === today + // Add transaction + this.addTransaction({ + type: 'credit', + amount: amount, + source: source, + balance: this.credits, + timestamp: new Date().toISOString() }); - if (lastClaim === today) { - this.game.showNotification('Daily reward already claimed!', 'warning', 3000); - if (debugLogger) debugLogger.endStep('claimDailyReward', { - success: false, - reason: 'Already claimed today' - }); - return false; + console.log(`[ECONOMY] Added ${amount} credits from ${source}. New balance: ${this.credits}`); + this.updateUI(); + + return this.credits - oldCredits; + } + + addGems(amount, source = 'unknown') { + const oldGems = this.gems; + this.gems += amount; + + // Add transaction + this.addTransaction({ + type: 'gem', + amount: amount, + source: source, + balance: this.gems, + timestamp: new Date().toISOString() + }); + + console.log(`[ECONOMY] Added ${amount} gems from ${source}. New balance: ${this.gems}`); + this.updateUI(); + + return this.gems - oldGems; + } + + canAfford(cost, currency = 'credits') { + if (currency === 'credits') { + return this.credits >= cost; + } else if (currency === 'gems') { + return this.gems >= cost; + } else if (currency === 'premium') { + return this.premiumCurrency >= cost; } + return false; + } + + // Transaction management + addTransaction(transaction) { + this.transactions.push(transaction); + this.transactionHistory.push(transaction); - // Calculate reward based on consecutive days - const consecutiveDays = parseInt(localStorage.getItem('consecutiveDailyRewards') || '0') + 1; - const baseReward = 100; - const bonusMultiplier = Math.min(consecutiveDays * 0.1, 2); // Max 2x bonus - const credits = Math.floor(baseReward * (1 + bonusMultiplier)); - const gems = Math.min(Math.floor(consecutiveDays / 7), 5); // 1 gem per week, max 5 - - if (debugLogger) debugLogger.logStep('Daily reward calculation', { - consecutiveDays: consecutiveDays, - baseReward: baseReward, - bonusMultiplier: bonusMultiplier, - creditsReward: credits, - gemsReward: gems - }); - - // Give rewards - this.addCredits(credits, 'daily_reward'); - this.addGems(gems, 'daily_reward'); - - // Update daily reward tracking - localStorage.setItem('lastDailyReward', today); - localStorage.setItem('consecutiveDailyRewards', consecutiveDays.toString()); - - // Show notification - const rewardText = `${credits} credits${gems > 0 ? ` and ${gems} gems` : ''}`; - this.game.showNotification(`Daily reward claimed: ${rewardText}! (${consecutiveDays} day streak)`, 'success', 4000); - - if (debugLogger) debugLogger.endStep('claimDailyReward', { - success: true, - consecutiveDays: consecutiveDays, - creditsReward: credits, - gemsReward: gems, - rewardText: rewardText, - newLastClaim: today, - newConsecutiveDays: consecutiveDays - }); - - return true; + // Keep only last 100 transactions in memory + if (this.transactions.length > 100) { + this.transactions = this.transactions.slice(-100); + } } // UI updates updateUI() { - const debugLogger = window.debugLogger; - - if (debugLogger) debugLogger.startStep('updateUI', { - currentCredits: this.credits, - currentGems: this.gems, - currentPremiumCurrency: this.premiumCurrency - }); - - // Update resource displays - const creditsElement = document.getElementById('credits'); - if (creditsElement) { - creditsElement.textContent = this.game.formatNumber(this.credits); + // Update resource display + if (this.game.systems.ui) { + this.game.systems.ui.updateResourceDisplay(); } - const gemsElement = document.getElementById('gems'); - if (gemsElement) { - gemsElement.textContent = this.game.formatNumber(this.gems); - } - - const premiumElement = document.getElementById('premiumCurrency'); - if (premiumElement) { - premiumElement.textContent = this.game.formatNumber(this.premiumCurrency); - } - - // Update shop UI when shop tab is active + // Update shop UI if open this.updateShopUI(); - - if (debugLogger) debugLogger.endStep('updateUI', { - creditsUpdated: !!creditsElement, - gemsUpdated: !!gemsElement, - premiumUpdated: !!premiumElement, - shopUIUpdated: true - }); } - // Reset economy (for new game) - reset() { + updateShopUI() { const debugLogger = window.debugLogger; + + console.log('[ECONOMY] updateShopUI called'); + console.log('[ECONOMY] Multiplayer mode:', window.smartSaveManager?.isMultiplayer); + console.log('[ECONOMY] ItemSystem available:', !!(this.game.systems.itemSystem)); + console.log('[ECONOMY] ItemSystem catalog:', !!(this.game.systems.itemSystem?.itemCatalog)); + + if (window.smartSaveManager?.isMultiplayer) { + // Check if ItemSystem is ready before using it + if (!this.game.systems.itemSystem || !this.game.systems.itemSystem.itemCatalog) { + console.log('[ECONOMY] ItemSystem not ready yet, skipping shop update'); + return; + } + + // Safe to use ItemSystem now + const items = Array.from(this.game.systems.itemSystem.shopItems || []); + console.log('[ECONOMY] Rendering shop with', items.length, 'items from ItemSystem'); + console.log('[ECONOMY] First few items:', items.slice(0, 3)); + this.renderShopItems(items); + } else { + // Singleplayer mode - use local shop data + console.log('[ECONOMY] Singleplayer mode - using local shop data'); + const items = Object.values(this.randomShopItems).flat(); + console.log('[ECONOMY] Rendering shop with', items.length, 'local items'); + this.renderShopItems(items); + } + } + + renderShopItems(items) { + const shopItemsElement = document.getElementById('shopItems'); + if (!shopItemsElement) return; + + const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships'; + console.log('[ECONOMY] Active shop category:', activeCategory); + console.log('[ECONOMY] All items types:', items.map(item => ({id: item.id, type: item.type, name: item.name}))); + console.log('[ECONOMY] Unique item types:', [...new Set(items.map(item => item.type))]); + + // Map category names to item types (handle plural/singular mismatches) + const categoryTypeMap = { + 'ships': 'ship', + 'weapons': 'weapon', + 'armors': 'armor', + 'cosmetics': 'cosmetic', + 'consumables': 'consumable', + 'materials': 'material', + 'keys': 'key' + }; + + const targetItemType = categoryTypeMap[activeCategory] || activeCategory; + console.log('[ECONOMY] Mapped category', activeCategory, 'to item type', targetItemType); + + const categoryItems = items.filter(item => item.type === targetItemType); + console.log('[ECONOMY] Filtered items for category', activeCategory, '(type:', targetItemType, ') :', categoryItems.length, 'items'); + + if (categoryItems.length === 0) { + shopItemsElement.innerHTML = '

No items available in this category

'; + return; + } + + shopItemsElement.innerHTML = categoryItems.map(item => { + const canAfford = this.canAfford(item.price, item.currency); + const isOwned = item.type === 'cosmetic' && this.ownedCosmetics.includes(item.id); + + return ` +
+
+
+

${item.name}

+ ${item.rarity} +
+
+

${item.description}

+
+ ${this.formatPrice(item)} +
+
+ +
+
+ `; + }).join(''); + } + + formatPrice(item) { + if (!item.price) return 'Free'; + + const currency = item.currency || 'credits'; + const price = this.game.formatNumber(item.price); + + return `${price} ${currency}`; + } + + // Save/Load functionality + save() { + return { + credits: this.credits, + gems: this.gems, + premiumCurrency: this.premiumCurrency, + transactions: this.transactions, + ownedCosmetics: this.ownedCosmetics, + shopData: { + randomShopItems: this.randomShopItems, + categoryPurchaseLimits: this.categoryPurchaseLimits, + lastShopRefresh: this.lastShopRefresh + } + }; + } + + load(data) { + if (data.credits !== undefined) this.credits = data.credits; + if (data.gems !== undefined) this.gems = data.gems; + if (data.premiumCurrency !== undefined) this.premiumCurrency = data.premiumCurrency; + if (data.transactions) this.transactions = data.transactions; + if (data.ownedCosmetics) this.ownedCosmetics = data.ownedCosmetics; + + // Load shop data + if (data.shopData) { + this.randomShopItems = data.shopData.randomShopItems || {}; + this.categoryPurchaseLimits = data.shopData.categoryPurchaseLimits || {}; + this.lastShopRefresh = data.shopData.lastShopRefresh || null; + } + + this.updateUI(); + } + + // Reset functionality + reset() { const oldState = { credits: this.credits, gems: this.gems, @@ -1965,10 +625,6 @@ class Economy { ownedCosmeticsCount: this.ownedCosmetics.length }; - if (debugLogger) debugLogger.startStep('reset', { - oldState: oldState - }); - this.credits = 1000; this.gems = 10; this.premiumCurrency = 0; @@ -1977,23 +633,13 @@ class Economy { // Reset daily rewards localStorage.removeItem('lastDailyReward'); - localStorage.removeItem('consecutiveDailyRewards'); - if (debugLogger) debugLogger.endStep('reset', { - oldState: oldState, - newState: { - credits: this.credits, - gems: this.gems, - premiumCurrency: this.premiumCurrency, - transactionCount: this.transactions.length, - ownedCosmeticsCount: this.ownedCosmetics.length - } - }); + this.updateUI(); + + return oldState; } - // Clear all data clear() { - const debugLogger = window.debugLogger; const oldState = { credits: this.credits, gems: this.gems, @@ -2002,334 +648,40 @@ class Economy { ownedCosmeticsCount: this.ownedCosmetics.length }; - if (debugLogger) debugLogger.startStep('clear', { - oldState: oldState - }); - this.credits = 0; this.gems = 0; this.premiumCurrency = 0; this.transactions = []; this.ownedCosmetics = []; - if (debugLogger) debugLogger.endStep('clear', { - oldState: oldState, - newState: { - credits: this.credits, - gems: this.gems, - premiumCurrency: this.premiumCurrency, - transactionCount: this.transactions.length, - ownedCosmeticsCount: this.ownedCosmetics.length - } - }); + this.updateUI(); + + return oldState; } - updateShopUI() { - const debugLogger = window.debugLogger; - const shopItemsElement = document.getElementById('shopItems'); - - if (!shopItemsElement) { - if (debugLogger) debugLogger.log('updateShopUI: Shop items element not found'); - return; - } - - const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships'; - - // Always use random shop items when the system is available - // If no random items exist for this category, trigger a refresh - if (!this.randomShopItems[activeCategory] || this.randomShopItems[activeCategory].length === 0) { - console.log(`[ECONOMY] No random items for ${activeCategory}, triggering refresh`); - this.refreshRandomShop(); - } - - const items = this.randomShopItems[activeCategory] || []; - const isRandomShop = true; // Always random shop when system is available - - if (debugLogger) debugLogger.startStep('updateShopUI', { - activeCategory: activeCategory, - itemCount: items.length, - isRandomShop: isRandomShop, - currentCredits: this.credits, - currentGems: this.gems, - currentPremiumCurrency: this.premiumCurrency - }); - - shopItemsElement.innerHTML = ''; - - // Add shop refresh info if using random shop - if (isRandomShop) { - const refreshInfo = document.createElement('div'); - refreshInfo.className = 'shop-refresh-info'; - refreshInfo.innerHTML = ` -
-
- - Next refresh in: ${this.getShopRefreshCountdown()} -
-
- `; - shopItemsElement.appendChild(refreshInfo); - } - - items.forEach(item => { - const itemElement = document.createElement('div'); - itemElement.className = 'shop-item'; - - const canAfford = this.canAfford(item); - const isOwned = item.type === 'cosmetic' && this.ownedCosmetics.includes(item.id); - - itemElement.innerHTML = ` -
- ${item.texture ? - `
- ${item.name} -
` : ''} -
-
${item.name}
-
${item.description}
- ${item.stats ? ` -
- ${Object.entries(item.stats).map(([stat, value]) => - `
${stat}: ${value}
` - ).join('')} -
- ` : ''} -
${this.formatPrice(item)}
-
${item.rarity}
-
-
- - `; - - shopItemsElement.appendChild(itemElement); - - if (debugLogger) debugLogger.logStep('Shop item rendered', { - itemId: item.id, - itemName: item.name, - itemCategory: activeCategory, - itemPrice: item.price, - itemCurrency: item.currency, - canAfford: canAfford, - isOwned: isOwned - }); - }); - - // Re-setup event listeners since we just recreated all the buttons - this.setupShopEventListeners(); - - if (debugLogger) debugLogger.endStep('updateShopUI', { - activeCategory: activeCategory, - itemsRendered: items.length, - shopUIUpdated: true - }); + // Initialize random shop for singleplayer (minimal implementation) + initializeRandomShop() { + console.log('[ECONOMY] Random shop not available in singleplayer mode'); + this.randomShopItems = {}; } - // Testing and utility methods - testPurchase(itemId) { - const debugLogger = window.debugLogger; - - if (debugLogger) debugLogger.startStep('testPurchase', { - itemId: itemId - }); - - const item = this.findShopItem(itemId); - if (!item) { - if (debugLogger) debugLogger.endStep('testPurchase', { - success: false, - reason: 'Item not found', - itemId: itemId - }); - return false; - } - - const result = this.purchaseItem(itemId); - - if (debugLogger) debugLogger.endStep('testPurchase', { - success: result, - itemId: itemId, - itemName: item.name, - itemCategory: item.type - }); - - return result; - } - - formatItemStats(item) { - const debugLogger = window.debugLogger; - - if (debugLogger) debugLogger.startStep('formatItemStats', { - itemId: item.id, - itemType: item.type - }); - - let stats = []; - - if (item.stats) { - for (const [stat, value] of Object.entries(item.stats)) { - stats.push(`${stat}: +${value}`); - } - } - - if (item.effect) { - if (item.effect.attackMultiplier) { - stats.push(`Attack: x${item.effect.attackMultiplier}`); - } - if (item.effect.defense) { - stats.push(`Defense: +${item.effect.defense}`); - } - if (item.effect.healing) { - stats.push(`Healing: +${item.effect.healing}`); - } - if (item.effect.energyRestore) { - stats.push(`Energy: +${item.effect.energyRestore}`); - } - } - - const formattedStats = stats.length > 0 ? stats.join(', ') : 'No special stats'; - - if (debugLogger) debugLogger.endStep('formatItemStats', { - itemId: item.id, - formattedStats: formattedStats, - statCount: stats.length - }); - - return formattedStats; - } - - // Save and load - save() { - const debugLogger = window.debugLogger; - - // if (debugLogger) debugLogger.startStep('save', { - // currentCredits: this.credits, - // currentGems: this.gems, - // currentPremiumCurrency: this.premiumCurrency, - // transactionCount: this.transactions.length, - // ownedCosmeticsCount: this.ownedCosmetics.length - // }); - - const saveData = { - credits: this.credits, - gems: this.gems, - premiumCurrency: this.premiumCurrency, - transactions: this.transactions, - ownedCosmetics: this.ownedCosmetics, - // Include shop data with timestamp for proper refresh calculation - shopData: { - randomShopItems: this.randomShopItems, - categoryPurchaseLimits: this.categoryPurchaseLimits, - lastShopRefresh: this.lastShopRefresh, - saveTimestamp: Date.now() // When this save was created - } - }; - - // if (debugLogger) debugLogger.endStep('save', { - // saveDataSize: JSON.stringify(saveData).length, - // economyState: saveData - // }); - - return saveData; - } - - load(data) { - const debugLogger = window.debugLogger; - const oldState = { + // Get system statistics + getStats() { + return { credits: this.credits, gems: this.gems, premiumCurrency: this.premiumCurrency, transactionCount: this.transactions.length, - ownedCosmeticsCount: this.ownedCosmetics.length + ownedCosmeticsCount: this.ownedCosmetics.length, + shopItemsCount: this.game.systems.itemSystem && this.game.systems.itemSystem.itemCatalog ? + this.game.systems.itemSystem.getStats().totalItems : 0 }; - - // if (debugLogger) debugLogger.startStep('load', { - // oldState: oldState, - // loadData: data - // }); - - try { - this.credits = data.credits || 0; - this.gems = data.gems || 0; - this.premiumCurrency = data.premiumCurrency || 0; - this.transactions = data.transactions || []; - this.ownedCosmetics = data.ownedCosmetics || []; - - // Load shop data and calculate if refresh is needed - if (data.shopData) { - console.log('[ECONOMY] Loading shop data from save'); - - // Restore shop data - this.randomShopItems = data.shopData.randomShopItems || {}; - this.categoryPurchaseLimits = data.shopData.categoryPurchaseLimits || {}; - this.lastShopRefresh = data.shopData.lastShopRefresh || null; - - // Calculate if shop should have refreshed while game was closed - const saveTimestamp = data.shopData.saveTimestamp || Date.now(); - const currentTime = Date.now(); - const timeSinceSave = currentTime - saveTimestamp; - - if (this.lastShopRefresh) { - const timeSinceLastRefresh = currentTime - this.lastShopRefresh; - const totalIntervalsPassed = Math.floor(timeSinceLastRefresh / this.SHOP_REFRESH_INTERVAL); - - if (totalIntervalsPassed > 0) { - console.log(`[ECONOMY] Shop missed ${totalIntervalsPassed} refresh(es) while game was closed, refreshing now`); - this.refreshRandomShop(); - } else { - console.log('[ECONOMY] Shop items still valid, no refresh needed'); - } - } else { - console.log('[ECONOMY] No previous shop refresh, generating new shop'); - this.refreshRandomShop(); - } - } else { - console.log('[ECONOMY] No shop data in save, initializing new shop'); - this.refreshRandomShop(); - } - - if (debugLogger) debugLogger.endStep('load', { - success: true, - oldState: oldState, - newState: { - credits: this.credits, - gems: this.gems, - premiumCurrency: this.premiumCurrency, - transactionCount: this.transactions.length, - ownedCosmeticsCount: this.ownedCosmetics.length - } - }); - } catch (error) { - if (debugLogger) debugLogger.errorEvent('load', error, { - oldState: oldState, - error: error.message - }); - throw error; - } - } - - // Utility methods for shop - canAfford(item) { - if (item.currency === 'credits') { - return this.credits >= item.price; - } else if (item.currency === 'gems') { - return this.gems >= item.price; - } else if (item.currency === 'premium') { - return this.premiumCurrency >= item.price; - } - return false; - } - - formatPrice(item) { - const currencySymbols = { - 'credits': '₵', - 'gems': '💎', - 'premium': '⭐' - }; - - const symbol = currencySymbols[item.currency] || item.currency; - return `${symbol}${this.game.formatNumber(item.price)}`; } } + +// Export for use in other modules +if (typeof module !== 'undefined' && module.exports) { + module.exports = Economy; +} else { + window.Economy = Economy; +} diff --git a/Client/js/core/GameEngine.js b/Client/js/core/GameEngine.js index 6534af9..713040a 100644 --- a/Client/js/core/GameEngine.js +++ b/Client/js/core/GameEngine.js @@ -5,66 +5,53 @@ class GameEngine extends EventTarget { constructor() { - super(); // Call EventTarget constructor first - - console.log('🛑 DEBUG STOP 1: GameEngine constructor starting'); - const debugLogger = window.debugLogger; - if (debugLogger) debugLogger.log('GameEngine constructor called', { - autoSaveInterval: 5, - timestamp: new Date().toISOString() - }); + // Must call super() first since we extend EventTarget + super(); + // Basic game state this.isRunning = false; - this.lastUpdate = 0; + this.isPaused = false; this.gameTime = 0; + this.lastSaveTime = 0; + this.autoSaveInterval = 5000; // 5 seconds + this.gameLogicInterval = 1000; // 1 second for game updates - // Auto-save settings - this.autoSaveInterval = 1; // Default 1 minute - this.autoSaveTimer = null; - this.lastAutoSave = 0; + // Game systems + this.systems = {}; - // Save slot information + // Save slot configuration this.saveSlotInfo = { - slot: 1, // Default save slot - useFileSystem: !!window.electronAPI // Use file system if Electron API is available + slot: 1, + useFileSystem: true }; - console.log('🛑 DEBUG STOP 2: Save slot info initialized:', this.saveSlotInfo); - - // GUI update settings - this.guiUpdateInterval = 1000; // Update GUI once per second (1000ms) - this.lastGUIUpdate = 0; - - // Game logic settings (independent of frame rate) - this.gameLogicInterval = 1000; // Update game logic every 1 second - this.gameLogicTimer = null; - // Game state this.state = { paused: false, currentTab: 'dashboard', - notifications: [], - loading: true + notifications: [] }; - console.log('🛑 DEBUG STOP 3: Game state initialized:', this.state); - - // Systems - this.systems = {}; - // Event listeners this.eventListeners = new Map(); - console.log('🛑 DEBUG STOP 4: About to call this.init()'); + // Initialize immediately this.init(); - console.log('🛑 DEBUG STOP 5: GameEngine constructor completed'); } setMultiplayerMode(isMultiplayer, socket = null, serverData = null, currentUser = null) { const debugLogger = window.debugLogger; console.log('[GAME ENGINE] Setting multiplayer mode:', isMultiplayer); - if (debugLogger) debugLogger.logStep('setMultiplayerMode', { 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; @@ -106,17 +93,33 @@ class GameEngine extends EventTarget { if (debugLogger) await debugLogger.startStep('gameEngineInit'); try { - // Initialize core systems but don't setup new game data - await this.initializeSystemsForLoad(); + // 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.logStep('Systems initialized, setting up event listeners'); - - // Set up event listeners - await this.setupEventListeners(); - - if (debugLogger) await debugLogger.logStep('Event listeners setup complete'); - - if (logger) await logger.info('Game engine initialization completed'); if (debugLogger) await debugLogger.endStep('gameEngineInit', { systemsInitialized: Object.keys(this.systems).length, eventListeners: this.eventListeners.size @@ -129,84 +132,72 @@ class GameEngine extends EventTarget { } } - 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 }); + // Simplified multiplayer-only system initialization to avoid hanging + async initializeMultiplayerSystems() { + console.log('[GAME ENGINE] Initializing multiplayer systems'); 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'); - } + // Initialize texture manager first + this.systems.textureManager = new TextureManager(this); - // Start game loop - // CRITICAL: Ensure UIManager is initialized before starting game - if (this.systems.ui) { - console.log('[GAME ENGINE] Final UIManager initialization check...'); - try { - await this.systems.ui.initialize(); - console.log('[GAME ENGINE] UIManager initialized successfully'); - } catch (error) { - console.error('[GAME ENGINE] UIManager initialization failed:', error); - } - } + // 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); - this.start(); - console.log('[GAME ENGINE] Game loop started'); - if (debugLogger) await debugLogger.logStep('Game loop started'); + console.log('[GAME ENGINE] Essential systems created successfully'); - 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'); - - // CRITICAL: Initialize UIManager after showing interface - console.log('[GAME ENGINE] Initializing UIManager after showing interface...'); - if (this.systems.ui) { - try { - await this.systems.ui.initialize(); - console.log('[GAME ENGINE] UIManager initialized successfully (post-interface)'); - } catch (error) { - console.error('[GAME ENGINE] UIManager initialization failed:', error); - } - } else { - console.log('[GAME ENGINE] UIManager not found when trying to initialize after interface'); - } - } 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 + // 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); + }); + } + // REMOVED: QuestSystem should be server-driven only + 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] Failed to start game:', error); - if (logger) await logger.errorEvent(error, 'Game Start'); - if (debugLogger) await debugLogger.errorEvent(error, 'Game Start'); + 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'); } } @@ -242,35 +233,20 @@ class GameEngine extends EventTarget { if (logger) await logger.systemEvent('Economy', 'Created'); if (debugLogger) await debugLogger.logStep('Economy system created'); - 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 DungeonSystem'); - this.systems.dungeonSystem = new DungeonSystem(this); - if (logger) await logger.systemEvent('DungeonSystem', 'Created'); - if (debugLogger) await debugLogger.logStep('DungeonSystem created'); - - if (debugLogger) await debugLogger.logStep('Creating SkillSystem'); - this.systems.skillSystem = new SkillSystem(this); - if (logger) await logger.systemEvent('SkillSystem', 'Created'); - if (debugLogger) await debugLogger.logStep('SkillSystem created'); - - if (debugLogger) await debugLogger.logStep('Creating BaseSystem'); - this.systems.baseSystem = new BaseSystem(this); - if (logger) await logger.systemEvent('BaseSystem', 'Created'); - if (debugLogger) await debugLogger.logStep('BaseSystem created'); - - if (debugLogger) await debugLogger.logStep('Creating QuestSystem'); - this.systems.questSystem = new QuestSystem(this); - if (logger) await logger.systemEvent('QuestSystem', 'Created'); - if (debugLogger) await debugLogger.logStep('QuestSystem created'); - - if (debugLogger) await debugLogger.logStep('Creating ShipSystem'); - this.systems.shipSystem = new ShipSystem(this); - if (logger) await logger.systemEvent('ShipSystem', 'Created'); - if (debugLogger) await debugLogger.logStep('ShipSystem 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') { @@ -284,23 +260,7 @@ class GameEngine extends EventTarget { } 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!'); - // Create minimal UI system to prevent crashes (should never be needed based on logs) - this.systems.ui = { - showNotification: (message, type, duration) => { - console.log(`[UI MINIMAL] ${type}: ${message}`); - }, - updateUI: () => { - console.log('[UI MINIMAL] updateUI called'); - }, - switchTab: (tabName) => { - console.log('[UI MINIMAL] switchTab called'); - } - }; } - if (debugLogger) await debugLogger.logStep('Creating CraftingSystem'); - this.systems.crafting = new CraftingSystem(this); - if (logger) await logger.systemEvent('CraftingSystem', 'Created'); - if (debugLogger) await debugLogger.logStep('CraftingSystem created'); if (debugLogger) await debugLogger.endStep('initializeSystemsForLoad', { systemsCreated: Object.keys(this.systems).length @@ -309,388 +269,63 @@ class GameEngine extends EventTarget { } } - async initializeSystems() { + async startGame(continueGame = false) { const logger = window.logger; const debugLogger = window.debugLogger; - if (debugLogger) await debugLogger.startStep('initializeSystems'); + console.log('[GAME ENGINE] startGame called with continueGame =', continueGame); + if (logger) await logger.info('Starting game', { continueGame }); + if (debugLogger) await debugLogger.startStep('startGame', { continueGame }); - if (logger) { - await logger.timeAsync('Game Systems Initialization', async () => { - await logger.info('Initializing game systems'); - - 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('Initializing Player system'); - // Initialize in dependency order - this.systems.player = new Player(this); - if (logger) await logger.systemEvent('Player', 'Initialized'); - if (debugLogger) await debugLogger.logStep('Player system initialized', { - playerLevel: this.systems.player.stats.level, - playerExperience: this.systems.player.stats.experience - }); - - if (debugLogger) await debugLogger.logStep('Initializing Inventory system'); - this.systems.inventory = new Inventory(this); - if (logger) await logger.systemEvent('Inventory', 'Initialized'); - if (debugLogger) await debugLogger.logStep('Inventory system initialized', { - inventorySize: this.systems.inventory.items.length - }); - - if (debugLogger) await debugLogger.logStep('Initializing Economy system'); - this.systems.economy = new Economy(this); - if (logger) await logger.systemEvent('Economy', 'Initialized'); - if (debugLogger) await debugLogger.logStep('Economy system initialized', { - credits: this.systems.economy.credits, - gems: this.systems.economy.gems - }); - - if (debugLogger) await debugLogger.logStep('Initializing IdleSystem'); - this.systems.idleSystem = new IdleSystem(this); - if (logger) await logger.systemEvent('IdleSystem', 'Initialized'); - if (debugLogger) await debugLogger.logStep('IdleSystem initialized'); - - if (debugLogger) await debugLogger.logStep('Initializing DungeonSystem'); - this.systems.dungeonSystem = new DungeonSystem(this); - if (logger) await logger.systemEvent('DungeonSystem', 'Initialized'); - if (debugLogger) await debugLogger.logStep('DungeonSystem initialized'); - - if (debugLogger) await debugLogger.logStep('Initializing SkillSystem'); - this.systems.skillSystem = new SkillSystem(this); - if (logger) await logger.systemEvent('SkillSystem', 'Initialized'); - if (debugLogger) await debugLogger.logStep('SkillSystem initialized'); - - if (debugLogger) await debugLogger.logStep('Initializing BaseSystem'); - this.systems.baseSystem = new BaseSystem(this); - if (logger) await logger.systemEvent('BaseSystem', 'Initialized'); - if (debugLogger) await debugLogger.logStep('BaseSystem initialized'); - - if (debugLogger) await debugLogger.logStep('Initializing QuestSystem'); - this.systems.questSystem = new QuestSystem(this); - await this.systems.questSystem.initialize(); - if (logger) await logger.systemEvent('QuestSystem', 'Initialized'); - if (debugLogger) await debugLogger.logStep('QuestSystem initialized'); - - if (debugLogger) await debugLogger.logStep('Initializing ShipSystem'); - this.systems.ship = new ShipSystem(this); - if (logger) await logger.systemEvent('ShipSystem', 'Initialized'); - if (debugLogger) await debugLogger.logStep('ShipSystem initialized'); - - if (debugLogger) await debugLogger.logStep('UIManager already initialized in initializeSystemsForLoad'); - - if (debugLogger) await debugLogger.logStep('Initializing CraftingSystem'); - this.systems.crafting = new CraftingSystem(this); - if (logger) await logger.systemEvent('CraftingSystem', 'Initialized'); - if (debugLogger) await debugLogger.logStep('CraftingSystem initialized'); - - if (logger) await logger.info('All game systems initialized'); - if (debugLogger) await debugLogger.logStep('All systems created, running individual initializations'); - - if (debugLogger) await debugLogger.logStep('Running individual system initializations'); - for (const [name, system] of Object.entries(this.systems)) { - if (system.initialize) { - if (debugLogger) await debugLogger.logStep(`Initializing ${name} system`); - await system.initialize(); - if (debugLogger) await debugLogger.logStep(`${name} system initialization complete`); - } else { - if (debugLogger) await debugLogger.logStep(`${name} system has no initialize method`); - } - } - - if (debugLogger) await debugLogger.endStep('initializeSystems', { - totalSystems: Object.keys(this.systems).length, - systemsList: Object.keys(this.systems) - }); - }); - } else { - // Fallback without timing - if (debugLogger) await debugLogger.logStep('Logger not available, using fallback initialization'); - - // Initialize texture manager first - this.systems.textureManager = new TextureManager(this); - - // Initialize in dependency order - this.systems.player = new Player(this); - this.systems.inventory = new Inventory(this); - this.systems.economy = new Economy(this); - this.systems.idleSystem = new IdleSystem(this); - this.systems.dungeonSystem = new DungeonSystem(this); - this.systems.skillSystem = new SkillSystem(this); - this.systems.baseSystem = new BaseSystem(this); - this.systems.questSystem = new QuestSystem(this); - // UIManager already initialized in initializeSystemsForLoad - this.systems.crafting = new CraftingSystem(this); - - for (const [name, system] of Object.entries(this.systems)) { - if (system.initialize) { - await system.initialize(); - } + 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'); } - if (debugLogger) await debugLogger.endStep('initializeSystems', { - fallbackMode: true, - totalSystems: Object.keys(this.systems).length - }); - } - } - - async setupEventListeners() { - const debugLogger = window.debugLogger; - - if (debugLogger) await debugLogger.startStep('setupEventListeners'); - - // Window events - if (debugLogger) await debugLogger.logStep('Setting up window event listeners'); - window.addEventListener('beforeunload', () => { - if (debugLogger) debugLogger.logStep('beforeunload event triggered - saving game'); - this.save(); - }); - - window.addEventListener('visibilitychange', () => { - // if (debugLogger) debugLogger.logStep('visibilitychange event triggered', { - // hidden: document.hidden, - // visibilityState: document.visibilityState - // }); - if (document.hidden) { - // if (debugLogger) debugLogger.logStep('Document hidden - saving game'); - this.save(); - } - }); - - // Keyboard shortcuts - if (debugLogger) await debugLogger.logStep('Setting up keyboard shortcuts'); - document.addEventListener('keydown', (event) => { - if (debugLogger) debugLogger.logStep('Key pressed', { - key: event.key, - code: event.code, - ctrlKey: event.ctrlKey, - altKey: event.altKey, - shiftKey: event.shiftKey - }); + // Start game loop + this.start(); + console.log('[GAME ENGINE] Game loop started'); + if (debugLogger) await debugLogger.logStep('Game loop started'); - // Ctrl+S for manual save - if (event.ctrlKey && event.key === 's') { - event.preventDefault(); - if (debugLogger) debugLogger.logStep('Manual save shortcut triggered (Ctrl+S)'); - this.save(); + 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'); } - }); - - // Game loop events - if (debugLogger) await debugLogger.logStep('Setting up game loop events'); - this.addEventListener('gameStarted', () => { - if (debugLogger) debugLogger.logStep('Game started event received'); - }); - - this.addEventListener('gameStopped', () => { - if (debugLogger) debugLogger.logStep('Game stopped event received'); - }); - - this.addEventListener('gameSaved', () => { - // if (debugLogger) debugLogger.logStep('Game saved event received'); - }); - - this.addEventListener('gameLoaded', () => { - if (debugLogger) debugLogger.logStep('Game loaded event received'); - }); - - // Setup UI event listeners - if (debugLogger) await debugLogger.logStep('Setting up UI event listeners'); - if (this.systems.ui && typeof this.systems.ui.setupEventListeners === 'function' && !this.uiEventListenersSetup) { - this.systems.ui.setupEventListeners(); - this.uiEventListenersSetup = true; // Prevent duplicate setup - if (debugLogger) await debugLogger.logStep('UI event listeners setup complete'); - } else if (this.uiEventListenersSetup) { - if (debugLogger) await debugLogger.logStep('UI event listeners already setup, skipping'); - } - - if (debugLogger) await debugLogger.endStep('setupEventListeners', { - windowEvents: 2, - keyboardShortcuts: 2, - gameLoopEvents: 4 - }); - } - - start() { - const debugLogger = window.debugLogger; - - if (this.isRunning) { - if (debugLogger) debugLogger.log('GameEngine.start() called but game is already running', { + + 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 }); - return; - } - - if (debugLogger) debugLogger.logStep('Starting game engine', { - gameLogicInterval: this.gameLogicInterval - }); - - this.isRunning = true; - this.lastUpdate = performance.now(); - - if (debugLogger) debugLogger.logStep('Game engine started, beginning game loop'); - - this.gameLoop(); - - // Loading screen is now handled by startGame() method - // Don't duplicate the hiding logic here - - if (debugLogger) debugLogger.logStep('Game loop initiated'); - } - - async stop() { - const debugLogger = window.debugLogger; - - if (debugLogger) await debugLogger.startStep('stopGame'); - - console.log('[GAME ENGINE] Stopping game and saving...'); - if (debugLogger) await debugLogger.logStep('Stopping game engine'); - - if (!this.isRunning) { - if (debugLogger) debugLogger.logStep('Game already stopped, ignoring stop request'); - return; - } - - this.isRunning = false; - - // Clear game logic timer - if (this.gameLogicTimer) { - clearInterval(this.gameLogicTimer); - this.gameLogicTimer = null; - } - - // Clear auto-save timer - if (this.autoSaveTimer) { - clearInterval(this.autoSaveTimer); - this.autoSaveTimer = null; - } - - console.log('[GAME ENGINE] Game stopped and saved successfully'); - - if (debugLogger) await debugLogger.endStep('stopGame', { - gameTime: this.gameTime, - isRunning: this.isRunning - }); - } - - 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`); - if (debugLogger) debugLogger.logStep('Starting auto-save system', { - interval: this.autoSaveInterval, - intervalMs: this.autoSaveInterval * 60 * 1000, - savedInterval: savedInterval, - isRunning: this.isRunning - }); - - // Clear any existing timer - this.stopAutoSave(); - - // Set up new timer - this.autoSaveTimer = setInterval(async () => { - if (debugLogger) debugLogger.logStep('Auto-save timer triggered', { - isRunning: this.isRunning, - paused: this.state.paused, - gameTime: this.gameTime, - lastAutoSave: this.lastAutoSave - }); - 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...'); - if (debugLogger) debugLogger.logStep('Auto-saving game'); - - try { - await this.save(); - this.showNotification('Game auto-saved', 'info', 2000); - console.log('[GAME ENGINE] Auto-save completed successfully'); - - this.lastAutoSave = Date.now(); - if (debugLogger) debugLogger.logStep('Auto-save completed successfully', { - lastAutoSave: this.lastAutoSave - }); - - } 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'); - if (debugLogger) debugLogger.logStep('Auto-save skipped', { - reason: this.isRunning ? 'paused' : 'not running', - isRunning: this.isRunning, - paused: this.state.paused - }); - } - }, this.autoSaveInterval * 60 * 1000); // Convert minutes to milliseconds - - this.lastAutoSave = Date.now(); - if (debugLogger) debugLogger.logStep('Auto-save timer started', { - timerId: this.autoSaveTimer, - lastAutoSave: this.lastAutoSave - }); - } - - stopAutoSave() { - const debugLogger = window.debugLogger; - - if (this.autoSaveTimer) { - console.log('[GAME ENGINE] Stopping auto-save timer'); - if (debugLogger) debugLogger.logStep('Stopping auto-save timer', { - timerId: this.autoSaveTimer, - wasRunning: true - }); - - clearInterval(this.autoSaveTimer); - this.autoSaveTimer = null; - - if (debugLogger) debugLogger.logStep('Auto-save timer stopped'); - } else { - if (debugLogger) debugLogger.logStep('stopAutoSave called but no timer was active', { - timerId: this.autoSaveTimer, - wasRunning: false - }); + } 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'); } } - updateAutoSaveInterval(newInterval) { - const debugLogger = window.debugLogger; - - console.log(`[GAME ENGINE] Updating auto-save interval to ${newInterval} minutes`); - if (debugLogger) debugLogger.logStep('Updating auto-save interval', { - oldInterval: this.autoSaveInterval, - newInterval: newInterval, - isRunning: this.isRunning - }); - - this.autoSaveInterval = newInterval; - - // Save to localStorage - localStorage.setItem('autoSaveInterval', newInterval.toString()); - - // Restart auto-save with new interval if game is running - if (this.isRunning) { - if (debugLogger) debugLogger.logStep('Restarting auto-save with new interval'); - this.startAutoSave(); - } - - if (debugLogger) debugLogger.logStep('Auto-save interval updated successfully', { - currentInterval: this.autoSaveInterval, - savedToStorage: true - }); - } - start() { const debugLogger = window.debugLogger; @@ -731,8 +366,6 @@ class GameEngine extends EventTarget { // Use fixed 1-second interval for all updates const fixedDelta = 1000; // 1 second in milliseconds - console.log('[GAME ENGINE] Game logic update called - adding', fixedDelta, 'ms to playtime'); - if (this.state.paused) { if (debugLogger) debugLogger.logStep('Game logic update called but game is paused', { gameTime: this.gameTime, @@ -743,27 +376,9 @@ class GameEngine extends EventTarget { this.gameTime += fixedDelta; - console.log('[GAME ENGINE] Total game time now:', this.gameTime, 'ms'); - - // Emit UI update event instead of direct call - this.emitUIUpdateEvent('full'); - - // Log update performance every 300 frames (approximately 5 seconds) - if (debugLogger && this.gameTime % 15000 === 0) { // Every 15 seconds of game time - debugLogger.logStep('Game logic update cycle', { - gameTime: this.gameTime, - fixedDelta: fixedDelta, - isRunning: this.isRunning, - paused: this.state.paused, - currentTab: this.state.currentTab - }); - } - // Update player play time with fixed delta if (this.systems.player && this.systems.player.updatePlayTime) { - console.log('[GAME ENGINE] Before update - player playTime:', this.systems.player.stats.playTime); this.systems.player.updatePlayTime(fixedDelta); - console.log('[GAME ENGINE] After update - player playTime:', this.systems.player.stats.playTime); } // Update all systems with fixed delta @@ -780,79 +395,57 @@ class GameEngine extends EventTarget { // Update UI displays (money, gems, energy) after system updates if (this.systems && this.systems.ui) { - console.log('[GAME ENGINE] Updating UI displays'); try { this.systems.ui.updateUI(); - console.log('[GAME ENGINE] UI update completed'); } catch (error) { console.error('[GAME ENGINE] Error updating UI:', error); } } + + // Emit game updated event + this.emit('gameUpdated', { gameTime: this.gameTime }); } - update(deltaTime) { - // Legacy method - game logic now handled by updateGameLogic() - // This method kept for compatibility but doesn't process game systems - } - - shouldUpdateGUI() { - const currentTime = Date.now(); - if (currentTime - this.lastGUIUpdate >= this.guiUpdateInterval) { - this.lastGUIUpdate = currentTime; - return true; - } - return false; - } - - updateGUI() { + startAutoSave() { const debugLogger = window.debugLogger; - console.log('[GAME ENGINE] Updating GUI'); + // Load saved interval or use default + const savedInterval = localStorage.getItem('autoSaveInterval'); + this.autoSaveInterval = savedInterval ? parseInt(savedInterval) : 5; - // Update UI displays (money, gems, energy) after system updates - if (this.systems && this.systems.ui) { - console.log('[GAME ENGINE] Updating UI displays'); - try { - this.systems.ui.updateUI(); - console.log('[GAME ENGINE] UI update completed'); - } catch (error) { - console.error('[GAME ENGINE] Error updating UI:', error); - } - } - } - - // 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 => { + 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 { - callback(data); + await this.save(); + this.showNotification('Game auto-saved', 'info', 2000); + console.log('[GAME ENGINE] Auto-save completed successfully'); + } catch (error) { - console.error(`[GAME ENGINE] Error in event listener for ${event}:`, error); + console.error('[GAME ENGINE] Auto-save failed:', error); + if (debugLogger) await debugLogger.errorEvent(error, 'Auto-save'); } - }); - } - - // Also dispatch as DOM event for UIManager - this.dispatchEvent(new CustomEvent(event, { detail: data })); + } else { + console.log('[GAME ENGINE] Auto-save skipped - game not running or paused'); + } + }, this.autoSaveInterval * 60 * 1000); // Convert minutes to milliseconds } - emitUIUpdateEvent(type, data = {}) { - const eventData = { - type: type, - timestamp: Date.now(), - ...data - }; - - console.log('[GAME ENGINE] Emitting UI update event:', eventData); - this.emit('uiUpdate', eventData); + stopAutoSave() { + if (this.autoSaveTimer) { + console.log('[GAME ENGINE] Stopping auto-save timer'); + clearInterval(this.autoSaveTimer); + this.autoSaveTimer = null; + } } // Notification system @@ -885,613 +478,27 @@ class GameEngine extends EventTarget { this.state.notifications = this.state.notifications.filter(notification => notification.id !== id); } - processNotifications() { - const now = Date.now(); - this.state.notifications = this.state.notifications.filter( - notification => now - notification.timestamp < notification.duration - ); + // Event system + on(event, callback) { + if (!this.eventListeners.has(event)) { + this.eventListeners.set(event, []); + } + this.eventListeners.get(event).push(callback); } - // Save/Load system - async save() { - const logger = window.logger; - const debugLogger = window.debugLogger; - - console.log('[GAME ENGINE] Save method called'); - if (logger) await logger.info('Saving game'); - // if (debugLogger) await debugLogger.startStep('saveGame'); - - try { - // if (debugLogger) await debugLogger.logStep('Collecting save data from systems'); - console.log('[GAME ENGINE] Collecting save data from systems...'); - - const saveData = { - player: this.systems.player.save(), - inventory: this.systems.inventory.save(), - economy: this.systems.economy.save(), - idleSystem: this.systems.idleSystem.save(), - dungeonSystem: this.systems.dungeonSystem.save(), - skillSystem: this.systems.skillSystem.save(), - baseSystem: this.systems.baseSystem.save(), - questSystem: this.systems.questSystem.save(), - gameTime: this.gameTime, - lastSave: Date.now() - }; - - // if (debugLogger) await debugLogger.logStep('Save data collected', { - // playerLevel: saveData.player?.stats?.level, - // gameTime: this.gameTime, - // saveDataSize: JSON.stringify(saveData).length, - // }); - - // console.log('[GAME ENGINE] Save data collected, player level:', saveData.player?.stats?.level); - // console.log('[GAME ENGINE] Save slot info:', this.saveSlotInfo); - - // Check if we're in local mode and should use localStorage - const isLocalMode = window.liveMainMenu && window.liveMainMenu.isLocalMode; - - if (isLocalMode) { - console.log('[GAME ENGINE] Using localStorage for local mode save'); - if (debugLogger) await debugLogger.logStep('Saving via localStorage for local mode'); - + emit(event, data) { + if (this.eventListeners.has(event)) { + this.eventListeners.get(event).forEach(callback => { try { - const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`; - const saveString = JSON.stringify(saveData); - - // Save to localStorage - localStorage.setItem(saveKey, saveString); - - console.log('[GAME ENGINE] Game saved successfully to localStorage'); - console.log('[GAME ENGINE] Save key:', saveKey); - console.log('[GAME ENGINE] Save data size:', saveString.length, 'characters'); - console.log('[GAME ENGINE] Current localStorage keys:', Object.keys(localStorage)); - - // Verify save exists - const verifySave = localStorage.getItem(saveKey); - console.log('[GAME ENGINE] Save verification:', !!verifySave ? 'SUCCESS' : 'FAILED'); - - if (debugLogger) await debugLogger.logStep('Game saved successfully to localStorage', { - saveKey, - saveDataSize: saveString.length - }); - - // Emit save event - this.emit('gameSaved', { slot: this.saveSlotInfo.slot, saveData }); - // if (debugLogger) debugLogger.logStep('Game saved event emitted'); - + callback(data); } catch (error) { - console.error('[GAME ENGINE] Failed to save to localStorage:', error); - if (debugLogger) await debugLogger.errorEvent(error, 'LocalStorage Save'); - throw error; + console.error(`[GAME ENGINE] Error in event listener for ${event}:`, error); } - } else { - // Use file system if available, otherwise localStorage (original logic) - if (this.saveSlotInfo && this.saveSlotInfo.useFileSystem) { - if (window.electronAPI) { - // console.log('[GAME ENGINE] Using Electron API to save game'); - // if (debugLogger) await debugLogger.logStep('Saving via Electron API', { - // slot: this.saveSlotInfo.slot, - // useFileSystem: true - // }); - - try { - // Save through Electron API - const result = await window.electronAPI.saveGame(this.saveSlotInfo.slot, saveData); - if (result.success) { - // console.log('[GAME ENGINE] Game saved successfully via Electron API'); - if (debugLogger) await debugLogger.logStep('Game saved successfully via Electron API', { - slot: this.saveSlotInfo.slot, - result: result - }); - - // Emit save event - this.emit('gameSaved', { slot: this.saveSlotInfo.slot, saveData }); - // if (debugLogger) debugLogger.logStep('Game saved event emitted'); - - } else { - console.error('[GAME ENGINE] Failed to save via Electron API:', result.error); - if (debugLogger) await debugLogger.errorEvent(new Error(result.error), 'Electron API Save'); - - // Fallback to localStorage - if (debugLogger) await debugLogger.logStep('Falling back to localStorage'); - const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`; - localStorage.setItem(saveKey, JSON.stringify(saveData)); - console.log('[GAME ENGINE] Fallback: Game saved to localStorage with key:', saveKey); - if (debugLogger) await debugLogger.logStep('Game saved to localStorage fallback', { - saveKey, - dataSize: JSON.stringify(saveData).length - }); - } - } catch (error) { - console.error('[GAME ENGINE] Electron API save error:', error); - if (debugLogger) await debugLogger.errorEvent(error, 'Electron API Save'); - - // Fallback to localStorage - if (debugLogger) await debugLogger.logStep('Falling back to localStorage due to error'); - const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`; - localStorage.setItem(saveKey, JSON.stringify(saveData)); - console.log('[GAME ENGINE] Error fallback: Game saved to localStorage with key:', saveKey); - } - } else { - console.warn('[GAME ENGINE] Electron API not available, using localStorage'); - if (debugLogger) await debugLogger.warn('Electron API not available, using localStorage'); - - const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`; - localStorage.setItem(saveKey, JSON.stringify(saveData)); - console.log('[GAME ENGINE] Game saved to localStorage with key:', saveKey); - if (debugLogger) await debugLogger.logStep('Game saved to localStorage', { - saveKey, - dataSize: JSON.stringify(saveData).length - }); - } - } else { - // LocalStorage fallback - if (debugLogger) await debugLogger.logStep('Using localStorage save'); - const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`; - localStorage.setItem(saveKey, JSON.stringify(saveData)); - console.log('[GAME ENGINE] Game saved to localStorage with key:', saveKey); - if (debugLogger) await debugLogger.logStep('Game saved to localStorage', { - saveKey, - dataSize: JSON.stringify(saveData).length - }); - } - } - - if (logger) await logger.info('Game saved successfully'); - // if (debugLogger) await debugLogger.endStep('saveGame', { - // gameTime: this.gameTime, - // saveSlot: this.saveSlotInfo?.slot, - // success: true - // }); - - } catch (error) { - console.error('[GAME ENGINE] Save failed:', error); - if (logger) await logger.errorEvent(error, 'Game Save'); - if (debugLogger) await debugLogger.errorEvent(error, 'Game Save'); - throw error; + }); } - } - - async loadGame() { - const logger = window.logger; - const debugLogger = window.debugLogger; - console.log('[GAME ENGINE] Load method called'); - if (logger) await logger.info('Loading game'); - if (debugLogger) await debugLogger.startStep('loadGame'); - - try { - if (debugLogger) await debugLogger.logStep('Loading save data', { - saveSlot: this.saveSlotInfo?.slot, - useFileSystem: this.saveSlotInfo?.useFileSystem - }); - - let saveData; - - // Use file system if available, otherwise localStorage - if (this.saveSlotInfo && this.saveSlotInfo.useFileSystem && window.electronAPI) { - console.log('[GAME ENGINE] Loading via Electron API'); - if (debugLogger) await debugLogger.logStep('Loading via Electron API'); - - const result = await window.electronAPI.loadGame(this.saveSlotInfo.slot); - if (result.success) { - saveData = result.data; - console.log('[GAME ENGINE] Game loaded successfully via Electron API'); - if (debugLogger) await debugLogger.logStep('Game loaded via Electron API', { - slot: this.saveSlotInfo.slot, - dataSize: JSON.stringify(saveData).length - }); - } else { - console.error('[GAME ENGINE] Failed to load via Electron API:', result.error); - if (debugLogger) await debugLogger.errorEvent(new Error(result.error), 'Electron API Load'); - throw new Error(result.error); - } - } else { - // LocalStorage fallback - console.log('[GAME ENGINE] Loading from localStorage'); - if (debugLogger) await debugLogger.logStep('Loading from localStorage'); - - const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`; - const saveString = localStorage.getItem(saveKey); - - if (saveString) { - try { - saveData = JSON.parse(saveString); - console.log('[GAME ENGINE] Game loaded from localStorage'); - if (debugLogger) await debugLogger.logStep('Game loaded from localStorage', { - saveKey, - dataSize: saveString.length - }); - } catch (parseError) { - console.error('[GAME ENGINE] Failed to parse save data:', parseError); - if (debugLogger) await debugLogger.errorEvent(parseError, 'Parse Save Data'); - throw new Error('Corrupted save data'); - } - } else { - console.warn('[GAME ENGINE] No save data found in localStorage'); - if (debugLogger) await debugLogger.warn('No save data found in localStorage', { saveKey }); - throw new Error('No save data found'); - } - } - - if (debugLogger) await debugLogger.logStep('Applying save data to systems'); - - // Apply save data to systems - if (saveData.player && this.systems.player) { - console.log('[GAME ENGINE] Loading player data...'); - this.systems.player.load(saveData.player); - if (debugLogger) await debugLogger.logStep('Player data loaded', { - level: saveData.player?.stats?.level, - experience: saveData.player?.stats?.experience - }); - } - - if (saveData.inventory && this.systems.inventory) { - console.log('[GAME ENGINE] Loading inventory data...'); - this.systems.inventory.load(saveData.inventory); - if (debugLogger) await debugLogger.logStep('Inventory data loaded', { - itemCount: saveData.inventory.items?.length || 0 - }); - } - - if (saveData.economy && this.systems.economy) { - console.log('[GAME ENGINE] Loading economy data...'); - this.systems.economy.load(saveData.economy); - if (debugLogger) await debugLogger.logStep('Economy data loaded', { - credits: saveData.economy.credits, - gems: saveData.economy.gems - }); - } - - if (saveData.questSystem && this.systems.questSystem) { - console.log('[GAME ENGINE] Loading quest data...'); - try { - this.systems.questSystem.load(saveData.questSystem); - if (debugLogger) await debugLogger.logStep('Quest data loaded', { - mainQuestsCount: saveData.questSystem.mainQuests?.length || 0, - dailyQuestsCount: saveData.questSystem.dailyQuests?.length || 0 - }); - console.log('[GAME ENGINE] Quest data loaded successfully'); - } catch (questError) { - console.error('[GAME ENGINE] Failed to load quest data:', questError); - if (debugLogger) await debugLogger.errorEvent(questError, 'Quest data load failed'); - // Continue with other systems instead of throwing - } - } - - if (saveData.baseSystem && this.systems.baseSystem) { - console.log('[GAME ENGINE] Loading base data...'); - try { - this.systems.baseSystem.load(saveData.baseSystem); - if (debugLogger) await debugLogger.logStep('Base data loaded', { - baseLevel: saveData.baseSystem.base?.level || 1, - roomsCount: saveData.baseSystem.base?.rooms?.length || 0 - }); - console.log('[GAME ENGINE] Base data loaded successfully'); - } catch (baseError) { - console.error('[GAME ENGINE] Failed to load base data:', baseError); - if (debugLogger) await debugLogger.errorEvent(baseError, 'Base data load failed'); - } - } - - if (saveData.skillSystem && this.systems.skillSystem) { - console.log('[GAME ENGINE] Loading skill data...'); - try { - this.systems.skillSystem.load(saveData.skillSystem); - if (debugLogger) await debugLogger.logStep('Skill data loaded'); - console.log('[GAME ENGINE] Skill data loaded successfully'); - } catch (skillError) { - console.error('[GAME ENGINE] Failed to load skill data:', skillError); - if (debugLogger) await debugLogger.errorEvent(skillError, 'Skill data load failed'); - } - } - - // CRITICAL: Initialize UIManager after loading save data - console.log('[GAME ENGINE] About to initialize UIManager - checking systems...'); - console.log('[GAME ENGINE] Available systems:', Object.keys(this.systems)); - console.log('[GAME ENGINE] UIManager exists:', !!this.systems.ui); - - if (this.systems.ui) { - console.log('[GAME ENGINE] Initializing UIManager after save load...'); - if (debugLogger) await debugLogger.logStep('Initializing UIManager after save load'); - try { - await this.systems.ui.initialize(); - console.log('[GAME ENGINE] UIManager initialized successfully'); - if (debugLogger) await debugLogger.logStep('UIManager initialized successfully'); - } catch (uiError) { - console.error('[GAME ENGINE] UIManager initialization failed:', uiError); - if (debugLogger) await debugLogger.errorEvent(uiError, 'UIManager initialization failed'); - } - } else { - console.log('[GAME ENGINE] UIManager not found in systems!'); - } - - console.log('[GAME ENGINE] UIManager initialization section completed'); - - // Initialize DungeonSystem after UIManager is ready - if (this.systems.dungeonSystem) { - console.log('[GAME ENGINE] Initializing DungeonSystem...'); - if (debugLogger) await debugLogger.logStep('Initializing DungeonSystem after save load'); - try { - await this.systems.dungeonSystem.initialize(); - console.log('[GAME ENGINE] DungeonSystem initialized successfully'); - if (debugLogger) await debugLogger.logStep('DungeonSystem initialized successfully'); - } catch (dungeonError) { - console.error('[GAME ENGINE] DungeonSystem initialization failed:', dungeonError); - if (debugLogger) await debugLogger.errorEvent(dungeonError, 'DungeonSystem initialization failed'); - } - } else { - console.log('[GAME ENGINE] DungeonSystem not found in systems!'); - } - - // Restore game time - if (saveData.gameTime !== undefined) { - this.gameTime = saveData.gameTime; - console.log('[GAME ENGINE] Game time restored:', this.gameTime); - } - - console.log('[GAME ENGINE] loadGame method completed successfully'); - - } catch (error) { - console.error('[GAME ENGINE] Failed to load game:', error); - if (logger) await logger.errorEvent(error, 'Game Load'); - if (debugLogger) await debugLogger.errorEvent(error, 'loadGame'); - - if (debugLogger) await debugLogger.endStep('loadGame', { - success: false, - error: error.message - }); - - throw error; - } - } - - async load() { - const logger = window.logger; - const debugLogger = window.debugLogger; - - console.log('[GAME ENGINE] Load method called'); - if (logger) await logger.info('Loading game'); - if (debugLogger) await debugLogger.startStep('loadGame'); - - try { - if (debugLogger) await debugLogger.logStep('Loading save data', { - saveSlot: this.saveSlotInfo?.slot, - useFileSystem: this.saveSlotInfo?.useFileSystem - }); - - let saveData; - - // Use file system if available, otherwise localStorage - if (this.saveSlotInfo && this.saveSlotInfo.useFileSystem && window.electronAPI) { - console.log('[GAME ENGINE] Loading via Electron API'); - if (debugLogger) await debugLogger.logStep('Loading via Electron API'); - - const result = await window.electronAPI.loadGame(this.saveSlotInfo.slot); - if (result.success) { - saveData = result.data; - console.log('[GAME ENGINE] Game loaded successfully via Electron API'); - if (debugLogger) await debugLogger.logStep('Game loaded via Electron API', { - slot: this.saveSlotInfo.slot, - dataSize: JSON.stringify(saveData).length - }); - } else { - console.error('[GAME ENGINE] Failed to load via Electron API:', result.error); - if (debugLogger) await debugLogger.errorEvent(new Error(result.error), 'Electron API Load'); - throw new Error(result.error); - } - } else { - // LocalStorage fallback - console.log('[GAME ENGINE] Loading from localStorage'); - if (debugLogger) await debugLogger.logStep('Loading from localStorage'); - - const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`; - const saveString = localStorage.getItem(saveKey); - - if (saveString) { - try { - saveData = JSON.parse(saveString); - console.log('[GAME ENGINE] Game loaded from localStorage'); - if (debugLogger) await debugLogger.logStep('Game loaded from localStorage', { - saveKey, - dataSize: saveString.length - }); - } catch (parseError) { - console.error('[GAME ENGINE] Failed to parse save data:', parseError); - if (debugLogger) await debugLogger.errorEvent(parseError, 'Parse Save Data'); - throw new Error('Corrupted save data'); - } - } else { - console.warn('[GAME ENGINE] No save data found in localStorage'); - if (debugLogger) await debugLogger.warn('No save data found in localStorage', { saveKey }); - throw new Error('No save data found'); - } - } - - // if (debugLogger) await debugLogger.logStep('Applying save data to systems'); - - // Apply save data to systems - if (saveData.player && this.systems.player) { - console.log('[GAME ENGINE] Loading player data...'); - this.systems.player.load(saveData.player); - if (debugLogger) await debugLogger.logStep('Player data loaded', { - level: saveData.player?.stats?.level, - experience: saveData.player?.stats?.experience - }); - } - - if (saveData.inventory && this.systems.inventory) { - console.log('[GAME ENGINE] Loading inventory data...'); - this.systems.inventory.load(saveData.inventory); - if (debugLogger) await debugLogger.logStep('Inventory data loaded', { - itemCount: saveData.inventory.items?.length || 0 - }); - } - - if (saveData.economy && this.systems.economy) { - console.log('[GAME ENGINE] Loading economy data...'); - this.systems.economy.load(saveData.economy); - if (debugLogger) await debugLogger.logStep('Economy data loaded', { - credits: saveData.economy.credits, - gems: saveData.economy.gems - }); - } - - if (saveData.gameTime) { - this.gameTime = saveData.gameTime; - if (debugLogger) await debugLogger.logStep('Game time restored', { - gameTime: this.gameTime - }); - } - - // Emit load event - this.emit('gameLoaded', { saveData }); - if (debugLogger) debugLogger.logStep('Game loaded event emitted'); - - if (logger) await logger.info('Game loaded successfully'); - if (debugLogger) await debugLogger.endStep('loadGame', { - gameTime: this.gameTime, - saveSlot: this.saveSlotInfo?.slot, - success: true - }); - - } catch (error) { - console.error('[GAME ENGINE] Load failed:', error); - if (logger) await logger.errorEvent(error, 'Game Load'); - if (debugLogger) await debugLogger.errorEvent(error, 'Game Load'); - throw error; - } - } - - async newGame() { - const logger = window.logger; - const debugLogger = window.debugLogger; - - console.log('[GAME ENGINE] Starting new game initialization'); - if (logger) await logger.info('Starting new game'); - if (debugLogger) await debugLogger.startStep('newGame'); - - try { - // For new games, we need to properly initialize systems with default data - if (debugLogger) await debugLogger.logStep('Initializing systems for new game'); - - // Initialize inventory with starting items - if (this.systems.inventory) { - console.log('[GAME ENGINE] Initializing inventory with starting items'); - if (debugLogger) await debugLogger.logStep('Initializing inventory with starting items'); - await this.systems.inventory.initialize(); - } - - // Initialize DungeonSystem for new game - if (this.systems.dungeonSystem) { - console.log('[GAME ENGINE] Initializing DungeonSystem for new game...'); - if (debugLogger) await debugLogger.logStep('Initializing DungeonSystem for new game'); - try { - await this.systems.dungeonSystem.initialize(); - console.log('[GAME ENGINE] DungeonSystem initialized successfully for new game'); - if (debugLogger) await debugLogger.logStep('DungeonSystem initialized successfully for new game'); - } catch (dungeonError) { - console.error('[GAME ENGINE] DungeonSystem initialization failed for new game:', dungeonError); - if (debugLogger) await debugLogger.errorEvent(dungeonError, 'DungeonSystem initialization failed for new game'); - } - } else { - console.log('[GAME ENGINE] DungeonSystem not found in systems!'); - } - - if (this.systems.player) { - console.log('[GAME ENGINE] Resetting player to initial state'); - if (debugLogger) await debugLogger.logStep('Resetting player to initial state'); - this.systems.player.resetToLevel1(); - console.log('[GAME ENGINE] Player reset completed'); - if (debugLogger) await debugLogger.logStep('Player reset completed', { - level: this.systems.player.stats.level, - experience: this.systems.player.stats.experience, - playTime: this.systems.player.stats.playTime - }); - - console.log('[GAME ENGINE] Setting up new player'); - if (debugLogger) await debugLogger.logStep('Setting up new player'); - this.systems.player.setupNewPlayer(); - console.log('[GAME ENGINE] Player setup completed'); - if (debugLogger) await debugLogger.logStep('Player setup completed', { - level: this.systems.player.stats.level, - experience: this.systems.player.stats.experience - }); - } else { - console.error('[GAME ENGINE] Player system not available'); - if (debugLogger) await debugLogger.error('Player system not available'); - } - - if (debugLogger) await debugLogger.logStep('Resetting economy system'); - console.log('[GAME ENGINE] Step 2: Resetting economy system'); - - if (this.systems.economy) { - console.log('[GAME ENGINE] Calling economy.reset()'); - if (debugLogger) await debugLogger.logStep('Calling economy.reset()'); - this.systems.economy.reset(); - console.log('[GAME ENGINE] Economy reset completed'); - if (debugLogger) await debugLogger.logStep('Economy reset completed', { - credits: this.systems.economy.credits, - gems: this.systems.economy.gems - }); - } else { - console.error('[GAME ENGINE] Economy system not available'); - if (debugLogger) await debugLogger.error('Economy system not available'); - } - - // Skip inventory reset - initialize() already handles proper setup - if (debugLogger) await debugLogger.logStep('Skipping inventory reset - already initialized'); - console.log('[GAME ENGINE] Skipping inventory reset - already initialized with starting items'); - - if (this.systems.inventory) { - if (debugLogger) await debugLogger.logStep('Inventory already initialized', { - itemCount: this.systems.inventory.items.length - }); - } else { - console.error('[GAME ENGINE] Inventory system not available'); - if (debugLogger) await debugLogger.error('Inventory system not available'); - } - - if (debugLogger) await debugLogger.logStep('Resetting quest system'); - console.log('[GAME ENGINE] Step 4: Resetting quest system'); - - if (this.systems.questSystem) { - console.log('[GAME ENGINE] Calling questSystem.reset()'); - if (debugLogger) await debugLogger.logStep('Calling questSystem.reset()'); - this.systems.questSystem.reset(); - console.log('[GAME ENGINE] Quest system reset completed'); - if (debugLogger) await debugLogger.logStep('Quest system reset completed'); - - // Activate the tutorial quest for new games - console.log('[GAME ENGINE] Activating tutorial quest for new game'); - if (debugLogger) await debugLogger.logStep('Activating tutorial quest'); - this.systems.questSystem.startQuest('tutorial_complete'); - console.log('[GAME ENGINE] Tutorial quest activated'); - if (debugLogger) await debugLogger.logStep('Tutorial quest activated'); - } else { - console.error('[GAME ENGINE] Quest system not available'); - if (debugLogger) await debugLogger.error('Quest system not available'); - } - - if (debugLogger) await debugLogger.logStep('Resetting game time'); - console.log('[GAME ENGINE] Step 5: Resetting game time'); - this.gameTime = 0; - console.log('[GAME ENGINE] Game time reset to 0'); - if (debugLogger) await debugLogger.logStep('Game time reset', { gameTime: this.gameTime }); - - if (logger) await logger.info('New game initialized successfully'); - if (debugLogger) await debugLogger.endStep('newGame', { - gameTime: this.gameTime, - playerLevel: this.systems.player?.stats.level, - success: true - }); - - } catch (error) { - console.error('[GAME ENGINE] Failed to initialize new game:', error); - if (logger) await logger.errorEvent(error, 'New Game Initialization'); - if (debugLogger) await debugLogger.errorEvent(error, 'New Game Initialization'); - throw error; - } + // Also dispatch as DOM event for UIManager + this.dispatchEvent(new CustomEvent(event, { detail: data })); } // Utility methods @@ -1516,14 +523,6 @@ class GameEngine extends EventTarget { } } - toggleDebugConsole() { - const debugLogger = window.debugLogger; - // if (debugLogger) debugLogger.logStep('Toggle debug console requested'); - - // Implementation would go here - // console.log('[GAME ENGINE] Debug console toggle requested'); - } - getPerformanceStats() { const debugLogger = window.debugLogger; @@ -1543,10 +542,163 @@ class GameEngine extends EventTarget { }; } - // if (debugLogger) debugLogger.logStep('Performance stats requested', stats); - 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); + } + + // 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); + } + + 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 diff --git a/Client/js/core/Inventory.js b/Client/js/core/Inventory.js index 7804cfa..9a4dfe3 100644 --- a/Client/js/core/Inventory.js +++ b/Client/js/core/Inventory.js @@ -95,72 +95,24 @@ class Inventory { maxSlots: this.maxSlots }); - const startingItems = [ - { - id: 'starter_blaster_common', - name: 'Common Blaster', - type: 'weapon', - rarity: 'common', - quantity: 1, - stats: { attack: 5, criticalChance: 0.02 }, - description: 'A reliable basic blaster for new pilots', - equipable: true, - slot: 'weapon' - }, - { - id: 'basic_armor_common', - name: 'Basic Armor', - type: 'armor', - rarity: 'common', - quantity: 1, - stats: { defense: 3 }, - description: 'Light armor providing basic protection', - equipable: true, - slot: 'armor' - } - ]; - - if (debugLogger) debugLogger.logStep('Adding starting items', { - startingItemCount: startingItems.length, - startingItems: startingItems.map(item => ({ - id: item.id, - name: item.name, - type: item.type, - quantity: item.quantity - })) - }); - - startingItems.forEach(item => { - console.log(`[DEBUG] Adding starting item: ${item.name}`); - const result = this.addItem(item); - console.log(`[DEBUG] Starting item add result: ${result}, inventory size: ${this.items.length}`); - }); - - // Equip starter items - console.log('[INVENTORY] Equipping starter items'); - if (debugLogger) debugLogger.logStep('Equipping starter items'); - - // Equip starter blaster - const blasterItem = this.items.find(item => item.id === 'starter_blaster'); - if (blasterItem) { - console.log('[INVENTORY] Equipping starter blaster'); - this.equipItem(blasterItem.id); + // In multiplayer mode, starting items should come from server + if (window.smartSaveManager?.isMultiplayer) { + console.log('[INVENTORY] Multiplayer mode - starting items will be provided by server'); + if (debugLogger) debugLogger.logStep('Skipping starting items in multiplayer mode'); + if (debugLogger) debugLogger.endStep('Inventory.addStartingItems', { + finalItemCount: this.items.length, + itemsAdded: 0 + }); + return; } - // Equip basic armor - const armorItem = this.items.find(item => item.id === 'basic_armor'); - if (armorItem) { - console.log('[INVENTORY] Equipping basic armor'); - this.equipItem(armorItem.id); - } - - // Auto-stack starting items - if (debugLogger) debugLogger.logStep('Auto-stacking starting items'); - this.autoStackItems(); + // Singleplayer mode - no hardcoded starting items available + console.log('[INVENTORY] Singleplayer mode - no hardcoded starting items available'); + if (debugLogger) debugLogger.logStep('No starting items available in singleplayer mode'); if (debugLogger) debugLogger.endStep('Inventory.addStartingItems', { finalItemCount: this.items.length, - itemsAdded: startingItems.length + itemsAdded: 0 }); } diff --git a/Client/js/core/Logger.js b/Client/js/core/Logger.js index 26b2221..e715594 100644 --- a/Client/js/core/Logger.js +++ b/Client/js/core/Logger.js @@ -213,12 +213,10 @@ class Logger { async info(message, data = null) { await this.log('info', message, data); - console.info(`[INFO] ${message}`, data || ''); } async debug(message, data = null) { await this.log('debug', message, data); - console.debug(`[DEBUG] ${message}`, data || ''); } async gameEvent(eventType, details) { diff --git a/Client/js/core/Player.js b/Client/js/core/Player.js index c244c42..17056e4 100644 --- a/Client/js/core/Player.js +++ b/Client/js/core/Player.js @@ -537,9 +537,9 @@ class Player { upgrades: [] }; - console.log('=== DEBUG: Character Reset ==='); - console.log('Player health reset to:', this.attributes.health, '/', this.attributes.maxHealth); - console.log('Ship health reset to:', this.ship.health, '/', this.ship.maxHealth); + // console.log('=== DEBUG: Character Reset ==='); + // console.log('Player health reset to:', this.attributes.health, '/', this.attributes.maxHealth); + // console.log('Ship health reset to:', this.ship.health, '/', this.ship.maxHealth); // Reset skills this.skills = {}; @@ -692,16 +692,43 @@ class Player { } updatePlayTime(deltaTime) { + // DISABLED: Reduce console spam for quest debugging + /* console.log('[PLAYER] updatePlayTime called with deltaTime:', deltaTime, 'ms'); + console.log('[PLAYER] Game state check:', { + hasGame: !!this.game, + isRunning: this.game?.isRunning, + isPaused: this.game?.state?.paused, + isHidden: document.hidden + }); + */ + + // Only update playtime when game is actively running and not paused + if (!this.game || !this.game.isRunning || this.game.state.paused) { + // console.log('[PLAYER] Skipping playtime update - game not running or paused'); + return; + } + + // Also check if tab is visible (don't count time when tab is in background) + if (document.hidden) { + // console.log('[PLAYER] Skipping playtime update - tab hidden'); + return; + } + + // DISABLED: Reduce console spam for quest debugging + /* console.log('[PLAYER] Before update - playTime:', this.stats.playTime, 'ms'); + */ // Use real computer time delta this.stats.playTime += deltaTime; + // DISABLED: Reduce console spam for quest debugging + /* console.log('[PLAYER] After update - playTime:', this.stats.playTime, 'ms'); console.log('[PLAYER] PlayTime in seconds:', this.stats.playTime / 1000, 'seconds'); console.log('[PLAYER] PlayTime in minutes:', this.stats.playTime / 60000, 'minutes'); - console.log('[PLAYER] PlayTime in hours:', this.stats.playTime / 3600000, 'hours'); + */ } // UI updates @@ -717,18 +744,25 @@ class Player { // Update player info const playerNameElement = document.getElementById('playerName'); + const playerTitleElement = document.getElementById('playerTitle'); const playerLevelElement = document.getElementById('playerLevel'); if (playerNameElement) { - playerNameElement.textContent = `${this.info.name} - ${this.info.title}`; + playerNameElement.textContent = this.info.name; + } + + if (playerTitleElement) { + playerTitleElement.textContent = ` - ${this.info.title}`; } if (playerLevelElement) { playerLevelElement.textContent = `Lv. ${this.stats.level}`; } - // Update health and energy - if (this.game && this.game.systems && this.game.systems.ui) { + // Update health and energy only if in multiplayer mode or game is actively running + const shouldUpdateUI = window.smartSaveManager?.isMultiplayer || this.game?.isRunning; + + if (shouldUpdateUI && this.game && this.game.systems && this.game.systems.ui) { this.game.systems.ui.updateResourceDisplay(); } @@ -765,6 +799,7 @@ class Player { if (debugLogger) debugLogger.logStep('Player UI update completed', { elementsUpdated: { playerName: !!playerNameElement, + playerTitle: !!playerTitleElement, playerLevel: !!playerLevelElement, totalKills: !!totalKillsElement, dungeonsCleared: !!dungeonsClearedElement, @@ -817,9 +852,29 @@ class Player { try { if (data.stats) { console.log('[PLAYER] Loading stats:', data.stats); + console.log('[PLAYER] Current playTime before load:', this.stats.playTime); + console.log('[PLAYER] Server playTime:', data.stats.playTime); + const oldStats = { ...this.stats }; - this.stats = { ...this.stats, ...data.stats }; + + // Preserve playTime if server doesn't provide it or provides 0 + const existingPlayTime = this.stats.playTime || 0; + const serverPlayTime = data.stats.playTime || 0; + + // Use server playTime if it's greater than existing, otherwise preserve existing + const preservedPlayTime = serverPlayTime > existingPlayTime ? serverPlayTime : existingPlayTime; + + console.log('[PLAYER] Preserving playTime:', preservedPlayTime, '(existing:', existingPlayTime, ', server:', serverPlayTime, ')'); + + // Merge stats but preserve playTime + this.stats = { + ...this.stats, + ...data.stats, + playTime: preservedPlayTime // Force preserve playTime + }; + console.log('[PLAYER] Level after stats load:', this.stats.level); + console.log('[PLAYER] PlayTime after stats load:', this.stats.playTime); if (debugLogger) debugLogger.logStep('Player stats loaded', { oldLevel: oldStats.level, diff --git a/Client/js/main.js b/Client/js/main.js index ec83f21..e9275fa 100644 --- a/Client/js/main.js +++ b/Client/js/main.js @@ -54,8 +54,9 @@ document.addEventListener('DOMContentLoaded', async () => { window.debugLogger.startStep('domLoad'); window.debugLogger.logStep('DOM loaded, starting initialization'); - // Auto-start local server for singleplayer mode - console.log('[MAIN] Checking local server status...'); + // Auto-start local server for singleplayer mode (DISABLED to prevent auto-start after multiplayer disconnect) + console.log('[MAIN] Skipping local server auto-start to prevent conflicts with multiplayer mode'); + /* if (window.localServerManager) { try { const serverResult = await window.localServerManager.autoStartIfSingleplayer(); @@ -78,6 +79,7 @@ document.addEventListener('DOMContentLoaded', async () => { } else { console.warn('[MAIN] LocalServerManager not available'); } + */ // Title bar is already initialized, just log it console.log('[MAIN] DOM loaded - title bar should already be working'); @@ -368,7 +370,7 @@ if (window.performance && window.performance.memory) { setInterval(() => { if (window.game && window.game.isRunning) { const stats = window.game.getPerformanceStats(); - if (stats.memory && stats.memory.used / stats.memory.total > 0.9) { + if (stats.memory && stats.memory.used / stats.memory.limit > 0.8) { console.warn('High memory usage detected:', stats.memory); } } diff --git a/Client/js/systems/BaseSystem.js b/Client/js/systems/BaseSystem.js index 391e9f3..b5acea3 100644 --- a/Client/js/systems/BaseSystem.js +++ b/Client/js/systems/BaseSystem.js @@ -1309,7 +1309,11 @@ class BaseSystem { }); } - player.updateUI(); + // Only update player UI if in multiplayer mode or game is actively running + if (this.game.shouldUpdateGUI()) { + player.updateUI(); + } + this.updateShipGallery(); // Also update ShipSystem display @@ -1592,10 +1596,12 @@ class BaseSystem { // Apply benefits this.applyStarbaseBenefits(newStarbase); - // Update UI - economy.updateUI(); - this.updateStarbaseList(); - this.updateStarbasePurchaseList(); + // Only update UI if in multiplayer mode or game is actively running + if (this.game.shouldUpdateGUI()) { + economy.updateUI(); + this.updateStarbaseList(); + this.updateStarbasePurchaseList(); + } this.game.showNotification(`Purchased ${starbaseTemplate.name}!`, 'success', 4000); } @@ -1650,11 +1656,18 @@ class BaseSystem { } startMiningProduction(starbase) { - // Set up passive credit generation - if (!this.miningInterval) { + // Only start mining if in multiplayer mode or game is actively running + const shouldStartMining = window.smartSaveManager?.isMultiplayer || this.game?.isRunning; + + if (shouldStartMining && !this.miningInterval) { + // Set up passive credit generation this.miningInterval = setInterval(() => { this.game.systems.economy.addCredits(500, 'mining_outpost'); }, 3600000); // 1 hour + + console.log('[BASE SYSTEM] Mining production started'); + } else if (!shouldStartMining) { + console.log('[BASE SYSTEM] Skipping mining production - not in multiplayer mode'); } } @@ -2057,10 +2070,12 @@ showRoomBuildMenu(x, y) { console.log('[DEBUG] Player ship updated:', player.ship); console.log('[DEBUG] Player attributes updated:', player.attributes); - // Update UI displays - player.updateUI(); - if (this.game.systems.ship && this.game.systems.ship.updateCurrentShipDisplay) { - this.game.systems.ship.updateCurrentShipDisplay(); + // Only update UI displays if in multiplayer mode or game is actively running + if (this.game.shouldUpdateGUI()) { + player.updateUI(); + if (this.game.systems.ship && this.game.systems.ship.updateCurrentShipDisplay) { + this.game.systems.ship.updateCurrentShipDisplay(); + } } } } diff --git a/Client/js/systems/CraftingSystem.js b/Client/js/systems/CraftingSystem.js index 0e4843a..a21618e 100644 --- a/Client/js/systems/CraftingSystem.js +++ b/Client/js/systems/CraftingSystem.js @@ -641,7 +641,13 @@ class CraftingSystem extends BaseSystem { switchCategory(category) { this.currentCategory = category; this.selectedRecipe = null; - this.updateUI(); + + // Update UI only if in multiplayer mode or game is actively running + const shouldUpdateUI = window.smartSaveManager?.isMultiplayer || this.game?.isRunning; + + if (shouldUpdateUI) { + this.updateUI(); + } } } diff --git a/Client/js/systems/DungeonSystem.js b/Client/js/systems/DungeonSystem.js index 2bce1dc..6f4bddd 100644 --- a/Client/js/systems/DungeonSystem.js +++ b/Client/js/systems/DungeonSystem.js @@ -1,1977 +1,462 @@ /** - * Galaxy Strike Online - Dungeon System - * Manages procedural dungeon generation and exploration + * Galaxy Strike Online - Client Dungeon System + * Server-driven dungeon management client */ class DungeonSystem { constructor(gameEngine) { this.game = gameEngine; - // Dungeon configuration - this.dungeonTypes = { - tutorial: { - name: 'Tutorial Dungeon', - description: 'Learn the basics of dungeon exploration in this guided tutorial', - difficulty: 'tutorial', - enemyTypes: ['training_drone', 'practice_target'], - rewardMultiplier: 0.5, - oneTimeOnly: true, - healthType: 'player', // Ground mission - energyCost: 0 - }, - alien_ruins: { - name: 'Alien Ruins', - description: 'Ancient alien structures filled with mysterious technology', - difficulty: 'medium', - enemyTypes: ['alien_guardian', 'ancient_drone', 'crystal_golem'], - rewardMultiplier: 1.2, - healthType: 'player', // Ground mission - energyCost: 20 - }, - pirate_lair: { - name: 'Pirate Lair', - description: 'Dangerous pirate hideouts with valuable loot', - difficulty: 'easy', - enemyTypes: ['space_pirate', 'pirate_captain', 'defense_turret'], - rewardMultiplier: 1.0, - healthType: 'ship', // Space mission - energyCost: 15 - }, - corrupted_vault: { - name: 'Corrupted AI Vault', - description: ' malfunctioning AI facilities with corrupted security', - difficulty: 'hard', - enemyTypes: ['security_drone', 'corrupted_ai', 'virus_program'], - rewardMultiplier: 1.5, - healthType: 'ship', // Space mission - energyCost: 25 - }, - asteroid_mine: { - name: 'Asteroid Mine', - description: 'Abandoned mining facilities in asteroid fields', - difficulty: 'easy', - enemyTypes: ['mining_drone', 'rock_creature', 'explosive_asteroid'], - rewardMultiplier: 0.8, - healthType: 'ship', // Space mission - energyCost: 10 - }, - nebula_anomaly: { - name: 'Nebula Anomaly', - description: 'Strange energy anomalies in deep space', - difficulty: 'extreme', - enemyTypes: ['energy_being', 'phase_shifter', 'quantum_entity'], - rewardMultiplier: 2.0, - healthType: 'ship', // Space mission - energyCost: 30 - }, - - // NEW DUNGEONS - Space Theme - space_station: { - name: 'Abandoned Space Station', - description: 'A derelict space station floating in the void', - difficulty: 'medium', - enemyTypes: ['maintenance_drone', 'security_android', 'station_ai'], - rewardMultiplier: 1.3, - healthType: 'player', - energyCost: 22 - }, - comet_core: { - name: 'Comet Core', - description: 'The frozen heart of a passing comet', - difficulty: 'hard', - enemyTypes: ['ice_elemental', 'frost_wyrm', 'crystal_guardian'], - rewardMultiplier: 1.6, - healthType: 'ship', - energyCost: 28 - }, - black_hole_perimeter: { - name: 'Black Hole Perimeter', - description: 'Dangerous space near a black hole event horizon', - difficulty: 'extreme', - enemyTypes: ['gravity_wraith', 'void_stalker', 'singularity_spawn'], - rewardMultiplier: 2.5, - healthType: 'ship', - energyCost: 35 - }, - star_forge: { - name: 'Star Forge', - description: 'Ancient facility that harnesses stellar energy', - difficulty: 'hard', - enemyTypes: ['plasma_elemental', 'solar_guardian', 'fusion_core'], - rewardMultiplier: 1.8, - healthType: 'ship', - energyCost: 30 - }, - debris_field: { - name: 'Ship Debris Field', - description: 'Graveyard of destroyed spacecraft', - difficulty: 'easy', - enemyTypes: ['scrap_golem', 'hull_breacher', 'salage_drone'], - rewardMultiplier: 0.9, - healthType: 'ship', - energyCost: 12 - }, - - // NEW DUNGEONS - Planet Theme - jungle_temple: { - name: 'Jungle Temple', - description: 'Overgrown temple hidden in dense alien jungle', - difficulty: 'medium', - enemyTypes: ['plant_beast', 'tribal_warrior', 'jungle_spirit'], - rewardMultiplier: 1.4, - healthType: 'player', - energyCost: 25 - }, - desert_pyramid: { - name: 'Desert Pyramid', - description: 'Ancient pyramid buried under endless sand dunes', - difficulty: 'hard', - enemyTypes: ['sand_worm', 'mummy_guardian', 'heat_elemental'], - rewardMultiplier: 1.7, - healthType: 'player', - energyCost: 28 - }, - volcanic_caverns: { - name: 'Volcanic Caverns', - description: 'Molten caverns deep within an active volcano', - difficulty: 'hard', - enemyTypes: ['lava_elemental', 'fire_demon', 'magma_beast'], - rewardMultiplier: 1.6, - healthType: 'player', - energyCost: 26 - }, - arctic_research: { - name: 'Arctic Research Base', - description: 'Frozen research facility with failed experiments', - difficulty: 'medium', - enemyTypes: ['cryo_mutant', 'frost_android', 'ice_wraith'], - rewardMultiplier: 1.5, - healthType: 'player', - energyCost: 24 - }, - swamp_lair: { - name: 'Swamp Lair', - description: 'Murky swamp inhabited by strange creatures', - difficulty: 'easy', - enemyTypes: ['swamp_beast', 'toxic_spitter', 'mud_golem'], - rewardMultiplier: 1.1, - healthType: 'player', - energyCost: 18 - }, - - // NEW DUNGEONS - Technology Theme - cyber_realm: { - name: 'Cyber Realm', - description: 'Virtual reality space corrupted by malware', - difficulty: 'hard', - enemyTypes: ['glitch_wraith', 'firewall_guardian', 'data_vampire'], - rewardMultiplier: 1.9, - healthType: 'player', - energyCost: 32 - }, - robot_factory: { - name: 'Robot Factory', - description: 'Automated factory producing hostile machines', - difficulty: 'medium', - enemyTypes: ['assembly_drone', 'welder_bot', 'factory_overseer'], - rewardMultiplier: 1.4, - healthType: 'player', - energyCost: 23 - }, - quantum_lab: { - name: 'Quantum Laboratory', - description: 'Research facility experimenting with quantum physics', - difficulty: 'extreme', - enemyTypes: ['quantum_phantom', 'particle_accelerator', 'reality_bender'], - rewardMultiplier: 2.3, - healthType: 'player', - energyCost: 38 - }, - server_farm: { - name: 'Server Farm', - description: 'Massive data center with rogue security programs', - difficulty: 'medium', - enemyTypes: ['sentinel_program', 'data_corruptor', 'system_guardian'], - rewardMultiplier: 1.3, - healthType: 'player', - energyCost: 21 - }, - - // NEW DUNGEONS - Biome/Elemental Theme - crystal_caves: { - name: 'Crystal Caves', - description: 'Caves filled with energy-infused crystals', - difficulty: 'medium', - enemyTypes: ['crystal_golem', 'shard_elemental', 'resonance_beast'], - rewardMultiplier: 1.4, - healthType: 'player', - energyCost: 22 - }, - toxic_wastes: { - name: 'Toxic Wastes', - description: 'Polluted wasteland filled with mutated creatures', - difficulty: 'hard', - enemyTypes: ['mutant_horror', 'toxic_slime', 'radiation_beast'], - rewardMultiplier: 1.7, - healthType: 'player', - energyCost: 27 - }, - shadow_realm: { - name: 'Shadow Realm', - description: 'Dark dimension inhabited by shadow creatures', - difficulty: 'extreme', - enemyTypes: ['shadow_demon', 'nightmare_stalker', 'void_walker'], - rewardMultiplier: 2.2, - healthType: 'player', - energyCost: 36 - }, - time_anomaly: { - name: 'Time Anomaly', - description: 'Area where time flows unpredictably', - difficulty: 'extreme', - enemyTypes: ['temporal_paradox', 'future_soldier', 'past_guardian'], - rewardMultiplier: 2.4, - healthType: 'player', - energyCost: 40 - }, - - // NEW DUNGEONS - Military/War Theme - war_zone: { - name: 'Active War Zone', - description: 'Battlefield with ongoing combat operations', - difficulty: 'hard', - enemyTypes: ['enemy_soldier', 'combat_drone', 'field_commander'], - rewardMultiplier: 1.8, - healthType: 'player', - energyCost: 29 - }, - military_base: { - name: 'Abandoned Military Base', - description: 'Former military installation with automated defenses', - difficulty: 'medium', - enemyTypes: ['turret_system', 'combat_android', 'base_commander'], - rewardMultiplier: 1.5, - healthType: 'player', - energyCost: 24 - }, - weapons_testing: { - name: 'Weapons Testing Facility', - description: 'Secret facility testing advanced weaponry', - difficulty: 'hard', - enemyTypes: ['weapon_drone', 'test_subject', 'chief_scientist'], - rewardMultiplier: 1.9, - healthType: 'player', - energyCost: 31 - }, - - // NEW DUNGEONS - Special/Unique Theme - dream_scape: { - name: 'Dream Scape', - description: 'Surreal landscape shaped by collective dreams', - difficulty: 'medium', - enemyTypes: ['nightmare_creature', 'dream_guardian', 'subconscious_demon'], - rewardMultiplier: 1.6, - healthType: 'player', - energyCost: 26 - }, - memory_palace: { - name: 'Memory Palace', - description: 'Mental realm storing forgotten memories', - difficulty: 'hard', - enemyTypes: ['memory_fragment', 'forgetfulness_demon', 'nostalgia_spirit'], - rewardMultiplier: 1.7, - healthType: 'player', - energyCost: 28 - }, - dimension_rift: { - name: 'Dimension Rift', - description: 'Tear between dimensions with interdimensional invaders', - difficulty: 'extreme', - enemyTypes: ['rift_demon', 'dimensional_hunter', 'reality_tear'], - rewardMultiplier: 2.6, - healthType: 'player', - energyCost: 42 - } - }; - - // Current dungeon state + // Current dungeon state (runtime only) this.currentDungeon = null; this.currentRoom = null; this.dungeonProgress = 0; this.isExploring = false; - // Dungeon templates - this.roomTypes = { - entrance: { name: 'Entrance', enemies: 0, rewards: false }, - corridor: { name: 'Corridor', enemies: 1, rewards: false }, - chamber: { name: 'Chamber', enemies: 2, rewards: true }, - treasure: { name: 'Treasure Room', enemies: 0, rewards: true }, - boss: { name: 'Boss Room', enemies: 1, rewards: true, isBoss: true }, - exit: { name: 'Exit', enemies: 0, rewards: false } - }; + // Server data loaded from server + this.serverDungeons = []; + this.roomTypes = {}; + this.enemyTemplates = {}; - // Enemy templates - this.enemyTemplates = { - // Original enemies - training_drone: { - name: 'Training Drone', - health: 10, - attack: 5, - defense: 2, - experience: 5, - credits: 3 - }, - practice_target: { - name: 'Practice Target', - health: 5, - attack: 0, - defense: 0, - experience: 2, - credits: 1 - }, - alien_guardian: { - name: 'Alien Guardian', - health: 50, - attack: 8, - defense: 5, - experience: 25, - credits: 15 - }, - ancient_drone: { - name: 'Ancient Drone', - health: 30, - attack: 12, - defense: 2, - experience: 20, - credits: 10 - }, - crystal_golem: { - name: 'Crystal Golem', - health: 80, - attack: 6, - defense: 10, - experience: 35, - credits: 25 - }, - space_pirate: { - name: 'Space Pirate', - health: 25, - attack: 10, - defense: 3, - experience: 15, - credits: 12 - }, - pirate_captain: { - name: 'Pirate Captain', - health: 40, - attack: 15, - defense: 6, - experience: 30, - credits: 20 - }, - defense_turret: { - name: 'Defense Turret', - health: 20, - attack: 18, - defense: 8, - experience: 18, - credits: 8 - }, - security_drone: { - name: 'Security Drone', - health: 35, - attack: 14, - defense: 4, - experience: 22, - credits: 15 - }, - corrupted_ai: { - name: 'Corrupted AI', - health: 60, - attack: 20, - defense: 2, - experience: 40, - credits: 30 - }, - virus_program: { - name: 'Virus Program', - health: 15, - attack: 25, - defense: 1, - experience: 20, - credits: 12 - }, - mining_drone: { - name: 'Mining Drone', - health: 20, - attack: 8, - defense: 3, - experience: 12, - credits: 8 - }, - rock_creature: { - name: 'Rock Creature', - health: 45, - attack: 6, - defense: 12, - experience: 25, - credits: 15 - }, - explosive_asteroid: { - name: 'Explosive Asteroid', - health: 10, - attack: 30, - defense: 0, - experience: 15, - credits: 5 - }, - energy_being: { - name: 'Energy Being', - health: 55, - attack: 22, - defense: 3, - experience: 45, - credits: 35 - }, - phase_shifter: { - name: 'Phase Shifter', - health: 30, - attack: 28, - defense: 1, - experience: 35, - credits: 25 - }, - quantum_entity: { - name: 'Quantum Entity', - health: 70, - attack: 35, - defense: 5, - experience: 60, - credits: 50 - }, + 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.generateDungeonList(); + }); + + // 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 room types from server'); + }); + + // 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.generateDungeonList(); + }); + + // Listen for dungeon start response + this.game.socket.on('dungeon_started', (data) => { + console.log('[DUNGEON SYSTEM] Dungeon started:', data); + this.currentDungeon = data.instance; + this.isExploring = true; + this.dungeonProgress = 0; + }); + + // Listen for encounter response + this.game.socket.on('encounter_data', (data) => { + console.log('[DUNGEON SYSTEM] Encounter received:', data); + this.currentRoom = data.encounter; + this.dungeonProgress++; + }); + + // 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...'); - // NEW ENEMIES - Space Theme - maintenance_drone: { - name: 'Maintenance Drone', - health: 28, - attack: 11, - defense: 4, - experience: 18, - credits: 12 - }, - security_android: { - name: 'Security Android', - health: 42, - attack: 16, - defense: 7, - experience: 28, - credits: 20 - }, - station_ai: { - name: 'Station AI', - health: 65, - attack: 24, - defense: 3, - experience: 45, - credits: 32 - }, - ice_elemental: { - name: 'Ice Elemental', - health: 38, - attack: 18, - defense: 8, - experience: 32, - credits: 24 - }, - frost_wyrm: { - name: 'Frost Wyrm', - health: 72, - attack: 26, - defense: 6, - experience: 52, - credits: 38 - }, - gravity_wraith: { - name: 'Gravity Wraith', - health: 85, - attack: 32, - defense: 4, - experience: 68, - credits: 48 - }, - void_stalker: { - name: 'Void Stalker', - health: 78, - attack: 38, - defense: 5, - experience: 72, - credits: 52 - }, - singularity_spawn: { - name: 'Singularity Spawn', - health: 95, - attack: 42, - defense: 8, - experience: 85, - credits: 65 - }, - plasma_elemental: { - name: 'Plasma Elemental', - health: 58, - attack: 28, - defense: 4, - experience: 48, - credits: 35 - }, - solar_guardian: { - name: 'Solar Guardian', - health: 82, - attack: 34, - defense: 7, - experience: 65, - credits: 48 - }, - fusion_core: { - name: 'Fusion Core', - health: 68, - attack: 30, - defense: 12, - experience: 58, - credits: 42 - }, - scrap_golem: { - name: 'Scrap Golem', - health: 35, - attack: 14, - defense: 9, - experience: 22, - credits: 16 - }, - hull_breacher: { - name: 'Hull Breacher', - health: 32, - attack: 20, - defense: 3, - experience: 26, - credits: 18 - }, - salage_drone: { - name: 'Salvage Drone', - health: 22, - attack: 12, - defense: 5, - experience: 16, - credits: 11 - }, - - // NEW ENEMIES - Planet Theme - plant_beast: { - name: 'Plant Beast', - health: 48, - attack: 15, - defense: 8, - experience: 35, - credits: 26 - }, - tribal_warrior: { - name: 'Tribal Warrior', - health: 38, - attack: 18, - defense: 6, - experience: 28, - credits: 20 - }, - jungle_spirit: { - name: 'Jungle Spirit', - health: 55, - attack: 22, - defense: 4, - experience: 42, - credits: 30 - }, - sand_worm: { - name: 'Sand Worm', - health: 75, - attack: 28, - defense: 9, - experience: 58, - credits: 42 - }, - mummy_guardian: { - name: 'Mummy Guardian', - health: 62, - attack: 24, - defense: 7, - experience: 48, - credits: 35 - }, - heat_elemental: { - name: 'Heat Elemental', - health: 52, - attack: 26, - defense: 3, - experience: 45, - credits: 32 - }, - lava_elemental: { - name: 'Lava Elemental', - health: 68, - attack: 30, - defense: 5, - experience: 55, - credits: 40 - }, - fire_demon: { - name: 'Fire Demon', - health: 72, - attack: 32, - defense: 6, - experience: 62, - credits: 45 - }, - magma_beast: { - name: 'Magma Beast', - health: 85, - attack: 28, - defense: 12, - experience: 68, - credits: 50 - }, - cryo_mutant: { - name: 'Cryo Mutant', - health: 45, - attack: 20, - defense: 7, - experience: 38, - credits: 28 - }, - frost_android: { - name: 'Frost Android', - health: 52, - attack: 22, - defense: 8, - experience: 42, - credits: 30 - }, - ice_wraith: { - name: 'Ice Wraith', - health: 58, - attack: 25, - defense: 4, - experience: 48, - credits: 35 - }, - swamp_beast: { - name: 'Swamp Beast', - health: 35, - attack: 16, - defense: 9, - experience: 24, - credits: 18 - }, - toxic_spitter: { - name: 'Toxic Spitter', - health: 28, - attack: 19, - defense: 3, - experience: 22, - credits: 16 - }, - mud_golem: { - name: 'Mud Golem', - health: 42, - attack: 12, - defense: 11, - experience: 26, - credits: 19 - }, - - // NEW ENEMIES - Technology Theme - glitch_wraith: { - name: 'Glitch Wraith', - health: 48, - attack: 26, - defense: 2, - experience: 45, - credits: 32 - }, - firewall_guardian: { - name: 'Firewall Guardian', - health: 65, - attack: 22, - defense: 8, - experience: 52, - credits: 38 - }, - data_vampire: { - name: 'Data Vampire', - health: 38, - attack: 30, - defense: 3, - experience: 35, - credits: 26 - }, - assembly_drone: { - name: 'Assembly Drone', - health: 32, - attack: 15, - defense: 6, - experience: 24, - credits: 17 - }, - welder_bot: { - name: 'Welder Bot', - health: 28, - attack: 20, - defense: 4, - experience: 22, - credits: 15 - }, - factory_overseer: { - name: 'Factory Overseer', - health: 58, - attack: 24, - defense: 7, - experience: 46, - credits: 33 - }, - quantum_phantom: { - name: 'Quantum Phantom', - health: 78, - attack: 35, - defense: 4, - experience: 68, - credits: 50 - }, - particle_accelerator: { - name: 'Particle Accelerator', - health: 92, - attack: 40, - defense: 6, - experience: 85, - credits: 62 - }, - reality_bender: { - name: 'Reality Bender', - health: 88, - attack: 45, - defense: 3, - experience: 92, - credits: 68 - }, - sentinel_program: { - name: 'Sentinel Program', - health: 42, - attack: 21, - defense: 8, - experience: 32, - credits: 24 - }, - data_corruptor: { - name: 'Data Corruptor', - health: 35, - attack: 25, - defense: 3, - experience: 28, - credits: 20 - }, - system_guardian: { - name: 'System Guardian', - health: 55, - attack: 23, - defense: 9, - experience: 42, - credits: 30 - }, - - // NEW ENEMIES - Biome/Elemental Theme - shard_elemental: { - name: 'Shard Elemental', - health: 45, - attack: 19, - defense: 10, - experience: 38, - credits: 28 - }, - resonance_beast: { - name: 'Resonance Beast', - health: 52, - attack: 22, - defense: 6, - experience: 42, - credits: 30 - }, - mutant_horror: { - name: 'Mutant Horror', - health: 68, - attack: 28, - defense: 5, - experience: 58, - credits: 42 - }, - toxic_slime: { - name: 'Toxic Slime', - health: 42, - attack: 18, - defense: 8, - experience: 32, - credits: 24 - }, - radiation_beast: { - name: 'Radiation Beast', - health: 75, - attack: 30, - defense: 4, - experience: 65, - credits: 48 - }, - shadow_demon: { - name: 'Shadow Demon', - health: 72, - attack: 34, - defense: 3, - experience: 68, - credits: 50 - }, - nightmare_stalker: { - name: 'Nightmare Stalker', - health: 85, - attack: 38, - defense: 5, - experience: 78, - credits: 58 - }, - void_walker: { - name: 'Void Walker', - health: 92, - attack: 42, - defense: 7, - experience: 88, - credits: 65 - }, - temporal_paradox: { - name: 'Temporal Paradox', - health: 78, - attack: 40, - defense: 4, - experience: 75, - credits: 55 - }, - future_soldier: { - name: 'Future Soldier', - health: 65, - attack: 32, - defense: 9, - experience: 58, - credits: 42 - }, - past_guardian: { - name: 'Past Guardian', - health: 70, - attack: 28, - defense: 12, - experience: 62, - credits: 45 - }, - - // NEW ENEMIES - Military/War Theme - enemy_soldier: { - name: 'Enemy Soldier', - health: 45, - attack: 20, - defense: 7, - experience: 35, - credits: 26 - }, - combat_drone: { - name: 'Combat Drone', - health: 38, - attack: 22, - defense: 5, - experience: 30, - credits: 22 - }, - field_commander: { - name: 'Field Commander', - health: 62, - attack: 28, - defense: 9, - experience: 52, - credits: 38 - }, - turret_system: { - name: 'Turret System', - health: 48, - attack: 26, - defense: 10, - experience: 38, - credits: 28 - }, - combat_android: { - name: 'Combat Android', - health: 55, - attack: 24, - defense: 8, - experience: 45, - credits: 33 - }, - base_commander: { - name: 'Base Commander', - health: 72, - attack: 30, - defense: 11, - experience: 62, - credits: 45 - }, - weapon_drone: { - name: 'Weapon Drone', - health: 42, - attack: 28, - defense: 4, - experience: 38, - credits: 28 - }, - test_subject: { - name: 'Test Subject', - health: 58, - attack: 25, - defense: 6, - experience: 48, - credits: 35 - }, - chief_scientist: { - name: 'Chief Scientist', - health: 35, - attack: 32, - defense: 3, - experience: 42, - credits: 30 - }, - - // NEW ENEMIES - Special/Unique Theme - nightmare_creature: { - name: 'Nightmare Creature', - health: 62, - attack: 28, - defense: 5, - experience: 55, - credits: 40 - }, - dream_guardian: { - name: 'Dream Guardian', - health: 68, - attack: 30, - defense: 8, - experience: 58, - credits: 42 - }, - subconscious_demon: { - name: 'Subconscious Demon', - health: 75, - attack: 34, - defense: 4, - experience: 68, - credits: 50 - }, - memory_fragment: { - name: 'Memory Fragment', - health: 48, - attack: 26, - defense: 6, - experience: 45, - credits: 33 - }, - forgetfulness_demon: { - name: 'Forgetfulness Demon', - health: 55, - attack: 30, - defense: 3, - experience: 48, - credits: 35 - }, - nostalgia_spirit: { - name: 'Nostalgia Spirit', - health: 52, - attack: 24, - defense: 9, - experience: 42, - credits: 30 - }, - rift_demon: { - name: 'Rift Demon', - health: 88, - attack: 44, - defense: 5, - experience: 92, - credits: 68 - }, - dimensional_hunter: { - name: 'Dimensional Hunter', - health: 95, - attack: 48, - defense: 8, - experience: 105, - credits: 78 - }, - reality_tear: { - name: 'Reality Tear', - health: 102, - attack: 52, - defense: 3, - experience: 115, - credits: 85 + 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.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 current room encounter using Socket.IO packets + */ + async processEncounter() { + if (!this.currentDungeon || !this.isExploring) { + console.warn('[DUNGEON SYSTEM] No active dungeon to process'); + return null; + } - // Statistics - this.stats = { - dungeonsAttempted: 0, - dungeonsCompleted: 0, - totalEnemiesDefeated: 0, - bestTime: Infinity, - totalLootEarned: 0 - }; + try { + 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.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; + } } - async initialize() { - this.generateDungeonList(); + /** + * 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.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.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; + } + + /** + * Generate dungeon list UI using server data + */ generateDungeonList() { + console.log('[DUNGEON SYSTEM] Generating dungeon list UI with server data...'); const dungeonListElement = document.getElementById('dungeonList'); if (!dungeonListElement) { - console.error('Dungeon list element not found!'); + console.error('[DUNGEON SYSTEM] Dungeon list element not found'); return; } - // Clear existing list + // Clear existing content dungeonListElement.innerHTML = ''; - const questSystem = this.game.systems.questSystem; - const firstStepsQuest = questSystem ? questSystem.findQuest('tutorial_complete') : null; - const showTutorialDungeon = firstStepsQuest && firstStepsQuest.status === 'active'; + if (!this.serverDungeons || Object.keys(this.serverDungeons).length === 0) { + dungeonListElement.innerHTML = '

Loading dungeons from server...

'; + return; + } - Object.entries(this.dungeonTypes).forEach(([key, dungeon]) => { - // Skip tutorial dungeon unless First Steps quest is active - if (key === 'tutorial' && !showTutorialDungeon) { - return; - } + // Generate HTML for each difficulty category + let html = ''; + + Object.entries(this.serverDungeons).forEach(([difficulty, dungeons]) => { + if (!dungeons || dungeons.length === 0) return; - const dungeonElement = document.createElement('div'); - dungeonElement.className = 'dungeon-item'; - dungeonElement.dataset.dungeonType = key; + 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); - // Check if tutorial dungeon is completed - const isCompleted = key === 'tutorial' && this.game.systems.player.stats.tutorialDungeonCompleted; - const statusClass = isCompleted ? 'completed' : ''; - const statusText = isCompleted ? 'COMPLETED' : ''; - - dungeonElement.innerHTML = ` -
${dungeon.name}
-
${dungeon.difficulty.toUpperCase()}
- ${statusText ? `
${statusText}
` : ''} -
${dungeon.description}
-
Rewards: ${dungeon.rewardMultiplier}x
-
Energy Cost: ${dungeon.energyCost || this.getEnergyCost(key)}
+ // Add difficulty header + html += ` +

+ + ${difficultyTitle} +

`; - dungeonElement.addEventListener('click', () => { - if (isCompleted) { - this.game.showNotification('Tutorial dungeon has already been completed!', 'warning', 3000); - } else { - this.selectDungeon(key); - } - }); - - dungeonListElement.appendChild(dungeonElement); - }); - - } - - selectDungeon(type) { - - // Check energy cost - const energyCost = this.getEnergyCost(type); - const player = this.game.systems.player; - - - if (!player.useEnergy(energyCost)) { - this.game.showNotification(`Not enough energy! Need ${energyCost} energy`, 'error', 3000); - return; - } - - - // Ensure we're on the Dungeons tab so the user can see the dungeon - if (this.game.ui && this.game.ui.currentTab !== 'dungeons') { - this.game.ui.switchTab('dungeons'); - } - - // Remove previous selection - document.querySelectorAll('.dungeon-item').forEach(item => { - item.classList.remove('selected'); - }); - - // Add selection to clicked dungeon - const selectedElement = document.querySelector(`[data-dungeon-type="${type}"]`); - if (selectedElement) { - selectedElement.classList.add('selected'); - } - - - // Generate dungeon - this.generateDungeon(type); - this.displayDungeon(); - - - this.game.showNotification(`Entered dungeon! -${energyCost} energy`, 'info', 3000); - } - - getEnergyCost(type) { - const costs = { - tutorial: 0, - alien_ruins: 20, - pirate_lair: 15, - corrupted_vault: 25, - asteroid_mine: 10, - nebula_anomaly: 30 - }; - return costs[type] || 15; - } - - calculateDungeonRewards(type, difficulty) { - const dungeonTemplate = this.dungeonTypes[type]; - const baseRewards = { - credits: 50, - experience: 25, - items: [] - }; - - // Apply difficulty multiplier - const difficultyMultipliers = { - tutorial: 0.5, - easy: 1.0, - medium: 1.5, - hard: 2.0 - }; - - const multiplier = difficultyMultipliers[difficulty] || 1.0; - const rewardMultiplier = dungeonTemplate.rewardMultiplier || 1.0; - - baseRewards.credits = Math.floor(baseRewards.credits * multiplier * rewardMultiplier); - baseRewards.experience = Math.floor(baseRewards.experience * multiplier * rewardMultiplier); - - return baseRewards; - } - - generateDungeon(type) { - - const dungeonTemplate = this.dungeonTypes[type]; - - // Check if tutorial dungeon has already been completed - if (type === 'tutorial' && this.game.systems.player.stats.tutorialDungeonCompleted) { - this.game.showNotification('Tutorial dungeon has already been completed!', 'warning', 3000); - return; - } - - - this.currentDungeon = { - type: type, - name: dungeonTemplate.name, - description: dungeonTemplate.description, - difficulty: dungeonTemplate.difficulty, - healthType: dungeonTemplate.healthType, - enemyTypes: dungeonTemplate.enemyTypes, - rewardMultiplier: dungeonTemplate.rewardMultiplier, - rooms: [], - currentRoomIndex: 0, - startTime: Date.now(), - completed: false - }; - - - // Generate rooms - ensure minimum of 3 rooms (entrance, at least 1 middle, boss) - const roomCount = Math.max(3, this.game.getRandomInt(5, 8)); - this.currentDungeon.rooms = this.generateRoomLayout(roomCount, dungeonTemplate); - - // Set current room - this.currentRoom = this.currentDungeon.rooms[0]; - this.dungeonProgress = 0; - - - // Show dungeon view and hide list - this.showDungeonView(); - - } - - generateRoomLayout(roomCount, dungeonTemplate) { - const rooms = []; - - - // Always start with entrance - rooms.push(this.createRoom('entrance', dungeonTemplate)); - - // Generate middle rooms (subtract 2 for entrance and boss room) - const middleRoomCount = roomCount - 2; - - for (let i = 0; i < middleRoomCount; i++) { - const roomType = this.getRandomRoomType(); - const room = this.createRoom(roomType, dungeonTemplate); - rooms.push(room); - } - - // Always end with boss room (exit is handled by completing the boss room) - rooms.push(this.createRoom('boss', dungeonTemplate)); - - - // Verify room accessibility - for (let i = 0; i < rooms.length; i++) { - } - - return rooms; - } - - getRandomRoomType() { - const weights = { - corridor: 40, - chamber: 30, - treasure: 0, // Temporarily disabled - entrance: 5, - boss: 5, - exit: 0 - }; - - const totalWeight = Object.values(weights).reduce((sum, weight) => sum + weight, 0); - let random = Math.random() * totalWeight; - - for (const [type, weight] of Object.entries(weights)) { - random -= weight; - if (random <= 0) { - return type; - } - } - - return 'corridor'; - } - - createRoom(roomType, dungeonTemplate) { - const template = this.roomTypes[roomType]; - const room = { - type: roomType, - name: template.name, - enemies: [], - rewards: null, - explored: false, - completed: false - }; - - // Generate enemies - if (template.enemies > 0) { - for (let i = 0; i < template.enemies; i++) { - const enemyType = dungeonTemplate.enemyTypes[ - Math.floor(Math.random() * dungeonTemplate.enemyTypes.length) - ]; - room.enemies.push(this.createEnemy(enemyType, template.isBoss)); - } - } - - // Generate rewards - if (template.rewards) { - room.rewards = this.generateRoomRewards(dungeonTemplate.difficulty, template.isBoss); - } - - return room; - } - - createEnemy(enemyType, isBoss = false) { - const template = this.enemyTemplates[enemyType]; - const bossMultiplier = isBoss ? 2.5 : 1.0; - - return { - id: Date.now() + Math.random().toString(36).substr(2, 9), - type: enemyType, - name: isBoss ? `Boss ${template.name}` : template.name, - health: Math.floor(template.health * bossMultiplier), - maxHealth: Math.floor(template.health * bossMultiplier), - attack: Math.floor(template.attack * bossMultiplier), - defense: Math.floor(template.defense * bossMultiplier), - experience: Math.floor(template.experience * bossMultiplier), - credits: Math.floor(template.credits * bossMultiplier), - isBoss: isBoss - }; - } - - generateRoomRewards(difficulty, isBoss = false) { - const baseRewards = this.game.systems.economy.generateRewards(difficulty, 'dungeon'); - const multiplier = isBoss ? 2.0 : 1.0; - - const rewards = { - credits: Math.floor(baseRewards.credits * multiplier), - experience: Math.floor(baseRewards.experience * multiplier), - materials: [] - }; - - // Add crafting materials based on chance and difficulty - const materialChance = isBoss ? 0.9 : 0.4; - if (Math.random() < materialChance) { - const materialCount = isBoss ? this.game.getRandomInt(3, 6) : this.game.getRandomInt(1, 3); - - // Define material pools with weights for better balance - const materialPools = { - tutorial: [ - { id: 'iron_ore', weight: 40 }, - { id: 'copper_wire', weight: 30 }, - { id: 'herbs', weight: 20 }, - { id: 'bandages', weight: 10 } - ], - easy: [ - { id: 'iron_ore', weight: 30 }, - { id: 'copper_wire', weight: 25 }, - { id: 'energy_crystal', weight: 15 }, - { id: 'leather', weight: 15 }, - { id: 'herbs', weight: 10 }, - { id: 'bandages', weight: 5 } - ], - medium: [ - { id: 'iron_ore', weight: 25 }, - { id: 'steel_plate', weight: 20 }, - { id: 'energy_crystal', weight: 20 }, - { id: 'copper_wire', weight: 15 }, - { id: 'rare_metal', weight: 5 }, - { id: 'bandages', weight: 15 } - ], - hard: [ - { id: 'steel_plate', weight: 30 }, - { id: 'energy_crystal', weight: 25 }, - { id: 'rare_metal', weight: 15 }, - { id: 'battery', weight: 20 }, - { id: 'bandages', weight: 10 } - ], - extreme: [ - { id: 'rare_metal', weight: 30 }, - { id: 'energy_crystal', weight: 25 }, - { id: 'battery', weight: 25 }, - { id: 'advanced_components', weight: 20 } - ] - }; - - const pool = materialPools[difficulty] || materialPools.easy; - - // Helper function to get weighted random material - function getWeightedRandomMaterial(materialPool) { - const totalWeight = materialPool.reduce((sum, mat) => sum + mat.weight, 0); - let random = Math.random() * totalWeight; + 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' ? '🚀' : '👤'; - for (const material of materialPool) { - random -= material.weight; - if (random <= 0) { - return material.id; - } - } - return materialPool[0].id; // Fallback - } - - for (let i = 0; i < materialCount; i++) { - const material = getWeightedRandomMaterial(pool); - const quantity = this.game.getRandomInt(1, isBoss ? 3 : 2); - - rewards.materials.push({ - id: material, - quantity: quantity - }); - } - } - - return rewards; - } - - displayDungeon() { - - const dungeonViewElement = document.getElementById('dungeonView'); - - if (!dungeonViewElement) { - const allElements = document.querySelectorAll('[id*="dungeon"]'); - return; - } - - if (!this.currentDungeon) { - return; - } - - const room = this.currentRoom; - if (room) { - // Room exists, continue processing - } - - const progress = Math.floor((this.currentDungeon.currentRoomIndex / this.currentDungeon.rooms.length) * 100); - - - let content = ` -
-
-

${this.currentDungeon.name}

-
-
-
+ // Each dungeon in its own individual container using proper CSS classes + html += ` +
+
${dungeon.name}
+
+ ${difficulty} - ${energyCost} Energy
- Room ${this.currentDungeon.currentRoomIndex + 1} / ${this.currentDungeon.rooms.length} -
-
- -
- ${this.getHealthDisplay()} -
- -
-

${room.name}

- ${this.getRoomDescription(room)} -
- -
- ${this.getRoomContent(room)} -
- -
- ${this.getRoomActions(room)} -
-
- `; - - - dungeonViewElement.innerHTML = content; - - - // Only add event listeners for buttons that don't use inline onclick handlers - const completeDungeonBtn = dungeonViewElement.querySelector('button[onclick*="completeDungeon"]'); - if (completeDungeonBtn) { - completeDungeonBtn.addEventListener('click', (e) => { - e.preventDefault(); - this.completeDungeon(); - }); - } - - } - - getHealthDisplay() { - const player = this.game.systems.player; - const healthType = this.currentDungeon.healthType; - - - if (healthType === 'ship') { - const shipHealth = player.ship.health || 0; - const shipMaxHealth = player.ship.maxHealth || 1000; - const healthPercent = Math.round((shipHealth / shipMaxHealth) * 100); - return ` -
-
Ship Health
-
-
-
- ${shipHealth} / ${shipMaxHealth} -
- `; - } else { - const playerHealth = player.attributes.health || 0; - const playerMaxHealth = player.attributes.maxHealth || 100; - const healthPercent = Math.round((playerHealth / playerMaxHealth) * 100); - return ` -
-
Player Health
-
-
-
- ${playerHealth} / ${playerMaxHealth} -
- `; - } - } - - getRoomDescription(room) { - const descriptions = { - entrance: 'You enter the dungeon. The air is thick with anticipation.', - corridor: 'A narrow corridor stretches ahead. You can hear distant sounds.', - chamber: 'You enter a large chamber. The echoes of past battles linger here.', - treasure: 'Golden light glimmers from piles of treasure in this room.', - boss: 'A massive presence fills the room. This is the guardian of this dungeon.', - exit: 'You can see the exit. Freedom awaits!' - }; - - return `

${descriptions[room.type] || 'You continue exploring...'}

`; - } - - getRoomContent(room) { - if (room.completed) { - return '

This room has been cleared.

'; - } - - if (room.enemies.length > 0) { - const enemiesHtml = room.enemies.map(enemy => ` -
-
${enemy.name}
-
-
-
+
${dungeon.description}
+
${healthIcon}
+
+ Enemies: +
+ ${this.generateEnemyList(dungeon.enemyTypes || [])} +
- ${enemy.health} / ${enemy.maxHealth} +
-
ATK: ${enemy.attack} | DEF: ${enemy.defense}
-
- `).join(''); - - return `
${enemiesHtml}
`; - } - - if (room.rewards && !room.completed) { - return '

Treasure awaits!

'; - } - - return '

The room is empty and quiet.

'; - } - - getRoomActions(room) { - - if (room.completed) { - if (this.currentDungeon.currentRoomIndex < this.currentDungeon.rooms.length - 1) { - return ''; - } else { - return ''; - } - } - - if (room.enemies.length > 0) { - const buttonHtml = ''; - return buttonHtml; - } - - if (room.rewards) { - return ''; - } - - // Check if this is the final room - if (this.currentDungeon.currentRoomIndex >= this.currentDungeon.rooms.length - 1) { - return ''; - } - - return ''; - } - async startCombat() { - - if (this.isExploring) { - return; - } - - this.isExploring = true; - const room = this.currentRoom; - const enemies = room.enemies.filter(e => e.health > 0); - - - if (enemies.length === 0) { - this.completeRoom(); - return; - } - - // Simulate combat - await this.simulateCombat(enemies); - } - - async simulateCombat(enemies) { - - try { - const player = this.game.systems.player; - - const healthType = this.currentDungeon.healthType; - - let combatLog = []; - - // Handle case where there are no enemies - if (enemies.length === 0) { - combatLog.push('Room was empty - no enemies found'); - this.completeRoom(); - return; - } - - for (const [index, enemy] of enemies.entries()) { - - // Player attacks - const playerDamage = player.calculateDamage(this.currentDungeon.difficulty); - - const actualDamage = Math.max(1, playerDamage.damage - enemy.defense); - enemy.health = Math.max(0, enemy.health - actualDamage); - - combatLog.push(`You dealt ${actualDamage} damage to ${enemy.name}${playerDamage.isCritical ? ' (CRITICAL!)' : ''}`); - - // Update UI after player attack to show enemy health change - this.displayDungeon(); - // Also update global player UI to ensure health bars are updated - if (player.updateUI) { - player.updateUI(); - } - - // Small delay to make the combat visible - await new Promise(resolve => setTimeout(resolve, 500)); - - // Enemy attacks if still alive - if (enemy.health > 0) { - let enemyDamage, damageTarget; - - if (healthType === 'ship') { - // Space missions: higher damage, target ship health - enemyDamage = Math.max(1, Math.floor(enemy.attack * 1.5) - player.ship.defense); - damageTarget = 'ship'; - this.applyShipDamage(enemyDamage); - combatLog.push(`${enemy.name} dealt ${enemyDamage} damage to your ship`); - } else { - // Ground missions: normal damage, target player health - enemyDamage = Math.max(1, enemy.attack - player.attributes.defense); - damageTarget = 'player'; - player.takeDamage(enemyDamage); - combatLog.push(`${enemy.name} dealt ${enemyDamage} damage to you`); - } - - // Update UI after enemy attack to show player health change - this.displayDungeon(); - // Also update global player UI to ensure health bars are updated - if (player.updateUI) { - player.updateUI(); - } - - // Small delay to make the combat visible - await new Promise(resolve => setTimeout(resolve, 500)); - - // Check for destruction - if (healthType === 'ship' && player.ship.health <= 0) { - this.handleShipDestruction(); - } else if (healthType === 'player' && player.attributes.health <= 0) { - this.handlePlayerDefeat(); - } - } else { - // Enemy defeated - player.addExperience(enemy.experience); - this.game.systems.economy.addCredits(enemy.credits, 'dungeon'); - player.incrementKills(); - this.stats.totalEnemiesDefeated++; - - // Update quest progress for combat objectives - if (this.game.systems.questSystem) { - this.game.systems.questSystem.onEnemyDefeated(); - } - - combatLog.push(`${enemy.name} defeated! +${enemy.experience} XP, +${enemy.credits} credits`); - - // Update UI after enemy defeat - this.displayDungeon(); - // Also update global player UI to ensure health bars are updated - if (player.updateUI) { - player.updateUI(); - } - - // Small delay to make the combat visible - await new Promise(resolve => setTimeout(resolve, 500)); - } - } - - // Show combat results - this.game.showNotification('Combat completed!', 'success', 3000); - combatLog.forEach(message => this.game.showNotification(message, 'info', 2000)); - - // Check if all enemies defeated - const remainingEnemies = this.currentRoom.enemies.filter(e => e.health > 0); - if (remainingEnemies.length === 0) { - this.completeRoom(); - } - - this.isExploring = false; - this.displayDungeon(); - } catch (error) { - console.error('[DUNGEON] Error in simulateCombat:', error); - console.error('[DUNGEON] Error stack:', error.stack); - this.isExploring = false; - } - } - - // Health system methods - applyShipDamage(amount) { - const player = this.game.systems.player; - player.ship.health = Math.max(0, player.ship.health - amount); - player.updateUI(); - - // Check for ship destruction - if (player.ship.health <= 0) { - this.handleShipDestruction(); - } - } - - handleShipDestruction() { - const player = this.game.systems.player; - - // Show destruction message - this.game.showNotification('Your ship has been destroyed!', 'error', 5000); - this.game.showNotification('Dungeon failed - all progress lost.', 'warning', 4000); - - // Remove the current ship from inventory - if (this.game.systems.inventory) { - this.game.systems.inventory.removeItem(player.ship); - } - - // Reset to a basic ship if available in inventory - const availableShips = this.game.systems.inventory.getItemsByType('ship'); - if (availableShips.length > 0) { - player.ship = availableShips[0]; - this.game.showNotification(`Switched to ${player.ship.name}`, 'info', 3000); - } else { - // No ships available - create a basic one - player.ship = { - name: 'Basic Fighter', - health: 1000, - maxHealth: 1000, - defense: 5, - attack: 10, - type: 'ship' - }; - this.game.showNotification('No ships available - using emergency fighter', 'warning', 3000); - } - - // Apply dungeon failure penalty - this.applyDungeonFailurePenalty(); - - // Exit dungeon - this.exitDungeon(); - } - - applyDungeonFailurePenalty() { - const player = this.game.systems.player; - - // Lose some credits as penalty - const creditPenalty = Math.floor(this.game.systems.economy.credits * 0.1); // 10% credit loss - this.game.systems.economy.removeCredits(creditPenalty); - - // Lose some experience - const expPenalty = Math.floor(player.experience * 0.05); // 5% exp loss - player.experience = Math.max(0, player.experience - expPenalty); - - // Update failure statistics - this.stats.dungeonsFailed++; - - this.game.showNotification(`Dungeon penalty: -${creditPenalty} credits, -${expPenalty} XP`, 'warning', 4000); - } - - handlePlayerDefeat() { - this.game.showNotification('You have been defeated!', 'error', 5000); - this.game.showNotification('You\'ve been forced to retreat from the dungeon.', 'warning', 4000); - - // Heal player to prevent death loop - const player = this.game.systems.player; - player.attributes.health = Math.floor(player.attributes.maxHealth * 0.5); - - // Exit dungeon - this.exitDungeon(); - } - - exitDungeon() { - this.currentDungeon = null; - this.currentRoom = null; - this.dungeonProgress = 0; - this.isExploring = false; - - // Return to main view - this.generateDungeonList(); - } - - claimRewards() { - const room = this.currentRoom; - if (!room.rewards || room.completed) return; - - // Give all rewards including materials - this.game.systems.economy.giveRewards(room.rewards, 'dungeon'); - - room.rewards = null; // Remove rewards after claiming - - this.game.showNotification('Rewards claimed!', 'success', 3000); - - // Use the proper completeRoom method to ensure consistent behavior - this.completeRoom(); - } - - completeRoom() { - const room = this.currentRoom; - room.completed = true; - room.explored = true; - - // Reset exploring flag when completing a room - this.isExploring = false; - - - // Auto-claim rewards if available - if (room.rewards) { - // Give credits and experience - this.game.systems.economy.giveRewards(room.rewards, 'dungeon'); - - // Add crafting materials to inventory - if (room.rewards.materials && room.rewards.materials.length > 0) { - let materialText = ''; - room.rewards.materials.forEach(material => { - this.game.systems.inventory.addItem(material.id, material.quantity); - materialText += `${material.quantity}x ${material.id}, `; - }); - - materialText = materialText.slice(0, -2); - - this.game.showNotification(`Materials found: ${materialText}`, 'success', 4000); - } - - room.rewards = null; - } - - this.game.showNotification(`${room.name} completed!`, 'success', 3000); - - // Check if this is a boss room - if so, automatically complete the dungeon - if (room.type === 'boss') { - setTimeout(() => { - this.completeDungeon(); - }, 2000); // Small delay to show the completion message first - } else { - this.displayDungeon(); - } - - } - - nextRoom() { - - // Log all rooms for debugging - this.currentDungeon.rooms.forEach((room, index) => { + `; + }); }); - if (this.currentDungeon.currentRoomIndex >= this.currentDungeon.rooms.length - 1) { - return; - } - - const oldIndex = this.currentDungeon.currentRoomIndex; - this.currentDungeon.currentRoomIndex++; - this.currentRoom = this.currentDungeon.rooms[this.currentDungeon.currentRoomIndex]; - - - this.displayDungeon(); + dungeonListElement.innerHTML = html; + console.log('[DUNGEON SYSTEM] Dungeon list UI generated successfully'); } - completeDungeon() { - if (!this.currentDungeon) { - return; + /** + * 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'; } - const completionTime = Date.now() - this.currentDungeon.startTime; - const player = this.game.systems.player; - const economy = this.game.systems.economy; - - // Generate proper rewards including materials - const baseRewards = this.generateRoomRewards(this.currentDungeon.difficulty, false); - - // Apply time bonus - const timeBonus = Math.floor(completionTime < 300000 ? 50 : 0); // Bonus for completing in under 5 minutes - baseRewards.credits += timeBonus; - baseRewards.experience += Math.floor(timeBonus / 2); - - // Give rewards - economy.giveRewards(baseRewards, 'dungeon'); - player.addExperience(baseRewards.experience); - - // Update statistics - this.stats.dungeonsCompleted++; - this.stats.totalTimeInDungeons += completionTime; - - // Update player dungeons cleared stat - player.stats.dungeonsCleared++; - - // Update quest progress for dungeon objectives - if (this.game.systems.questSystem) { - // Check if this is a tutorial dungeon - if (this.currentDungeon.type === 'tutorial') { - // Mark tutorial dungeon as completed in player stats - player.stats.tutorialDungeonCompleted = true; - // Update tutorial dungeon quest progress - this.game.systems.questSystem.updateTutorialDungeonProgress(); - } else { - // Update regular dungeon quest progress using the standard method - this.game.systems.questSystem.onDungeonCompleted(); + let html = ''; + enemyTypes.forEach(enemyType => { + const enemy = this.getEnemyTemplate(enemyType); + if (enemy) { + html += ` +
+ ${enemy.name} +
+ ❤️ ${enemy.health} + ⚔️ ${enemy.attack} + 🛡️ ${enemy.defense} +
+
+ `; } - } + }); - // Show completion message - this.game.showNotification(`Dungeon completed! Time: ${this.game.formatTime(completionTime)}`, 'success', 5000); - if (timeBonus > 0) { - this.game.showNotification(`Time bonus: +${timeBonus} credits!`, 'info', 3000); - } - - // Reset dungeon state - this.currentDungeon = null; - this.currentRoom = null; - this.isExploring = false; - - // Return to dungeon list view - this.showDungeonList(); - this.generateDungeonList(); - - // Force UI refresh - this.updateUI(); + return html; } - showDungeonList() { - const dungeonListElement = document.getElementById('dungeonList'); - const dungeonViewElement = document.getElementById('dungeonView'); - - if (dungeonListElement) { - dungeonListElement.style.display = 'flex'; + /** + * Check if player can enter dungeon + */ + canEnterDungeon(dungeon) { + if (!this.game.player) { + console.log('[DUNGEON SYSTEM] No player data available'); + return false; } - if (dungeonViewElement) { - dungeonViewElement.style.display = 'none'; - // Clear the dungeon view content to prevent frozen display - dungeonViewElement.innerHTML = ''; - } + const playerLevel = this.game.player.stats?.level || 1; + const minLevel = dungeon.minLevel || 1; + const maxLevel = dungeon.maxLevel || 999; + const energyCost = dungeon.energyCost || 0; + const playerEnergy = this.game.player.stats?.energy || 0; + + console.log(`[DUNGEON SYSTEM] Dungeon check for ${dungeon.name}:`, { + playerLevel, + minLevel, + maxLevel, + playerEnergy, + energyCost, + canEnter: playerLevel >= minLevel && playerLevel <= maxLevel && playerEnergy >= energyCost + }); + + return playerLevel >= minLevel && + playerLevel <= maxLevel && + playerEnergy >= energyCost; } - showDungeonView() { - - const dungeonListElement = document.getElementById('dungeonList'); - const dungeonViewElement = document.getElementById('dungeonView'); - - - if (dungeonListElement) { - dungeonListElement.style.display = 'none'; - } - - if (dungeonViewElement) { - dungeonViewElement.style.display = 'flex'; - } - - - // Display the current dungeon - this.displayDungeon(); - - } - - // UI updates + /** + * Update UI with current dungeon information + */ updateUI() { - // Update dungeon statistics if elements exist - const dungeonsClearedElement = document.getElementById('dungeonsCleared'); - if (dungeonsClearedElement) { - dungeonsClearedElement.textContent = this.stats.dungeonsCompleted; + if (this.game.uiManager) { + this.game.uiManager.updateDungeonUI({ + currentDungeon: this.currentDungeon, + currentRoom: this.currentRoom, + progress: this.dungeonProgress, + isExploring: this.isExploring + }); } } - // Save/Load - save() { - return { - stats: this.stats, - currentDungeon: this.currentDungeon, - currentRoom: this.currentRoom, - dungeonProgress: this.dungeonProgress - }; - } - - load(data) { - if (data.stats) this.stats = { ...this.stats, ...data.stats }; - if (data.currentDungeon) this.currentDungeon = data.currentDungeon; - if (data.currentRoom) this.currentRoom = data.currentRoom; - if (data.dungeonProgress !== undefined) this.dungeonProgress = data.dungeonProgress; + /** + * 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); + } else { + this.setupSocketListeners(); + await this.loadServerData(); + } + + console.log('[DUNGEON SYSTEM] Client dungeon system initialization complete'); } } + +// Export for use in GameEngine +if (typeof module !== 'undefined' && module.exports) { + module.exports = DungeonSystem; +} diff --git a/Client/js/systems/IdleSystem.js b/Client/js/systems/IdleSystem.js index 0eec3bd..f15973d 100644 --- a/Client/js/systems/IdleSystem.js +++ b/Client/js/systems/IdleSystem.js @@ -12,11 +12,18 @@ class IdleSystem { this.lastActiveTime = Date.now(); this.accumulatedTime = 0; // Track time for resource generation - // Idle production rates + // Idle production rates (online rates) this.productionRates = { - credits: 10, // credits per second (increased for better gameplay) - experience: 1, // experience per second (increased for better progression) - energy: 0.5 // energy regeneration per second + credits: 0.1, // 1 credit every 10 seconds (0.1 per second) + experience: 0, // no auto experience - only from dungeons + energy: 1/300 // 1 energy every 5 minutes (1/300 per second) + }; + + // Offline rates (different from online rates) + this.offlineProductionRates = { + credits: 1/60, // 1 credit every 1 minute (1/60 per second) + experience: 0, // no experience offline - only from dungeons + energy: 1/300 // 1 energy every 5 minutes (same as online) }; // Offline rewards @@ -144,6 +151,20 @@ class IdleSystem { } claimOfflineRewards() { + // In multiplayer mode, use server communication + if (window.smartSaveManager?.isMultiplayer) { + this.game.showNotification('Claiming offline rewards from server...', 'info', 2000); + + // Send request to server + if (window.game && window.game.socket) { + window.game.socket.emit('claimOfflineRewards', {}); + } else { + this.game.showNotification('Not connected to server', 'error', 3000); + } + return; + } + + // Singleplayer mode - use local logic if (this.offlineRewards.credits === 0 && this.offlineRewards.experience === 0 && this.offlineRewards.items.length === 0) { diff --git a/Client/js/systems/ItemSystem.js b/Client/js/systems/ItemSystem.js new file mode 100644 index 0000000..7caaf30 --- /dev/null +++ b/Client/js/systems/ItemSystem.js @@ -0,0 +1,383 @@ +/** + * Galaxy Strike Online - Client Item System + * Dynamically loads and manages items from the GameServer + */ + +class ItemSystem { + constructor(gameEngine) { + this.game = gameEngine; + + // Item storage + this.itemCatalog = new Map(); // itemId -> item data + this.shopItems = []; // Array of shop items + this.lastUpdated = null; + + // Loading state + this.isLoading = false; + this.loadPromise = null; + + // Event listeners + this.eventListeners = new Map(); + } + + /** + * Initialize the item system and load data from server + */ + async initialize() { + console.log('[ITEM SYSTEM] Initializing client item system'); + + if (this.loadPromise) { + return this.loadPromise; + } + + this.loadPromise = this.loadFromServer(); + return this.loadPromise; + } + + /** + * Load all items from the GameServer + */ + async loadFromServer() { + if (this.isLoading) { + console.log('[ITEM SYSTEM] Already loading items from server'); + return this.loadPromise; + } + + this.isLoading = true; + + try { + console.log('[ITEM SYSTEM] Loading items from GameServer - Multiplayer Mode'); + console.log('[ITEM SYSTEM] Socket connection status:', !!window.game?.socket); + + if (!window.game || !window.game.socket) { + throw new Error('Not connected to server - multiplayer mode requires server connection'); + } + + // Load shop items from server + const shopItems = await this.fetchShopItems(); + + console.log('[ITEM SYSTEM] Received', shopItems.length, 'items from server'); + + // Process and store items + this.processServerItems(shopItems); + + this.lastUpdated = Date.now(); + console.log(`[ITEM SYSTEM] Successfully loaded ${this.itemCatalog.size} items from server`); + console.log('[ITEM SYSTEM] Item categories loaded:', Object.keys(this.itemCatalog).length); + + // Emit loaded event + this.emit('itemsLoaded', { + itemCount: this.itemCatalog.size, + shopItemCount: this.shopItems.length, + timestamp: this.lastUpdated + }); + + return true; + + } catch (error) { + console.error('[ITEM SYSTEM] Failed to load items from server:', error); + console.error('[ITEM SYSTEM] Error details:', { + message: error.message, + stack: error.stack, + socketConnected: !!window.game?.socket, + socketId: window.game?.socket?.id + }); + + // No fallback - emit error event + this.emit('itemsLoadError', error); + return false; + + } finally { + this.isLoading = false; + } + } + + /** + * Fetch shop items from the GameServer + */ + async fetchShopItems() { + console.log('[ITEM SYSTEM] Starting fetchShopItems'); + + if (!window.game || !window.game.socket) { + console.error('[ITEM SYSTEM] No socket connection available'); + throw new Error('Not connected to server'); + } + + console.log('[ITEM SYSTEM] Socket ID:', window.game.socket.id); + console.log('[ITEM SYSTEM] Socket connected:', window.game.socket.connected); + + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + console.error('[ITEM SYSTEM] Server request timeout after 10 seconds'); + window.game.socket.off('shopItemsReceived', handleResponse); + reject(new Error('Server request timeout')); + }, 10000); + + // Request shop items from server + console.log('[ITEM SYSTEM] Emitting getShopItems request'); + window.game.socket.emit('getShopItems', {}); + + // Listen for response + const handleResponse = (data) => { + console.log('[ITEM SYSTEM] Received shopItemsReceived response:', data); + clearTimeout(timeout); + window.game.socket.off('shopItemsReceived', handleResponse); + + if (data.success) { + console.log('[ITEM SYSTEM] Successfully received', data.items?.length || 0, 'items'); + console.log('[ITEM SYSTEM] Response timestamp:', data.timestamp); + resolve(data.items || []); + } else { + console.error('[ITEM SYSTEM] Server returned error:', data.error); + reject(new Error(data.error || 'Failed to load shop items')); + } + }; + + console.log('[ITEM SYSTEM] Setting up shopItemsReceived listener'); + window.game.socket.on('shopItemsReceived', handleResponse); + }); + } + + /** + * Fetch specific item details from server + */ + async fetchItemDetails(itemId) { + if (!window.game || !window.game.socket) { + throw new Error('Not connected to server'); + } + + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error('Server request timeout')); + }, 5000); + + // Request item details from server + window.game.socket.emit('getItemDetails', { itemId }); + + // Listen for response + const handleResponse = (data) => { + clearTimeout(timeout); + window.game.socket.off('itemDetailsReceived', handleResponse); + + if (data.success) { + // Cache the item + this.itemCatalog.set(itemId, data.item); + resolve(data.item); + } else { + reject(new Error(data.error || 'Item not found')); + } + }; + + window.game.socket.on('itemDetailsReceived', handleResponse); + }); + } + + /** + * Process items received from server + */ + processServerItems(items) { + console.log('[ITEM SYSTEM] Processing', items.length, 'items from server'); + console.log('[ITEM SYSTEM] Sample items:', items.slice(0, 3)); + + this.itemCatalog.clear(); + this.shopItems = []; + + for (const item of items) { + // Store in catalog + this.itemCatalog.set(item.id, item); + + // Add to shop items if available for shop + if (item.categories && item.categories.includes('shop')) { + this.shopItems.push(item); + } + + console.log('[ITEM SYSTEM] Added item:', { + id: item.id, + name: item.name, + type: item.type, + rarity: item.rarity, + price: item.price, + categories: item.categories + }); + } + + console.log('[ITEM SYSTEM] Processing complete - Catalog:', this.itemCatalog.size, 'Shop items:', this.shopItems.length); + console.log('[ITEM SYSTEM] Shop items by type:', this.shopItems.reduce((acc, item) => { + acc[item.type] = (acc[item.type] || 0) + 1; + return acc; + }, {})); + } + + /** + * Get item by ID + */ + getItem(itemId) { + // Return from cache if available + if (this.itemCatalog.has(itemId)) { + return this.itemCatalog.get(itemId); + } + + // Try to fetch from server if not cached + if (window.game && window.game.socket) { + this.fetchItemDetails(itemId).catch(error => { + console.warn(`[ITEM SYSTEM] Failed to fetch item ${itemId}:`, error); + }); + } + + return null; + } + + /** + * Get all shop items + */ + getShopItems() { + return [...this.shopItems]; + } + + /** + * Get items by category + */ + getItemsByCategory(category) { + return Array.from(this.itemCatalog.values()).filter(item => + item.type === category || (item.categories && item.categories.includes(category)) + ); + } + + /** + * Get items by type + */ + getItemsByType(type) { + return Array.from(this.itemCatalog.values()).filter(item => item.type === type); + } + + /** + * Get items by rarity + */ + getItemsByRarity(rarity) { + return Array.from(this.itemCatalog.values()).filter(item => item.rarity === rarity); + } + + /** + * Check if player can use item based on requirements + */ + canPlayerUseItem(item, playerLevel = null) { + if (!item.requirements) return true; + + // Get player level if not provided + if (playerLevel === null && window.game && window.game.systems && window.game.systems.player) { + playerLevel = window.game.systems.player.level; + } + + // Check level requirement + if (item.requirements.level && playerLevel < item.requirements.level) { + return false; + } + + // Add other requirement checks here + + return true; + } + + /** + * Get filtered shop items for current player + */ + getAvailableShopItems() { + return this.shopItems.filter(item => this.canPlayerUseItem(item)); + } + + /** + * Format item price for display + */ + formatPrice(item) { + if (!item.price) return 'Free'; + + const currency = item.currency || 'credits'; + const price = this.game.formatNumber(item.price); + + return `${price} ${currency}`; + } + + /** + * Get item rarity color + */ + getRarityColor(rarity) { + const colors = { + common: '#888888', + uncommon: '#00ff00', + rare: '#0088ff', + legendary: '#ff8800', + epic: '#ff00ff' + }; + + return colors[rarity] || '#ffffff'; + } + + /** + * Refresh items from server + */ + async refresh() { + console.log('[ITEM SYSTEM] Refreshing items from server'); + return this.loadFromServer(); + } + + /** + * Event system + */ + on(event, callback) { + if (!this.eventListeners.has(event)) { + this.eventListeners.set(event, []); + } + this.eventListeners.get(event).push(callback); + } + + off(event, callback) { + if (this.eventListeners.has(event)) { + const listeners = this.eventListeners.get(event); + const index = listeners.indexOf(callback); + if (index > -1) { + listeners.splice(index, 1); + } + } + } + + emit(event, data) { + if (this.eventListeners.has(event)) { + for (const callback of this.eventListeners.get(event)) { + try { + callback(data); + } catch (error) { + console.error(`[ITEM SYSTEM] Error in event listener for ${event}:`, error); + } + } + } + } + + /** + * Get system statistics + */ + getStats() { + const stats = { + totalItems: this.itemCatalog.size, + shopItems: this.shopItems.length, + lastUpdated: this.lastUpdated, + isLoading: this.isLoading, + socketConnected: !!(window.game?.socket), + socketId: window.game?.socket?.id + }; + + // Add category breakdown + stats.categories = {}; + for (const item of this.itemCatalog.values()) { + stats.categories[item.type] = (stats.categories[item.type] || 0) + 1; + } + + return stats; + } +} + +// Export for use in other modules +if (typeof module !== 'undefined' && module.exports) { + module.exports = ItemSystem; +} else { + window.ItemSystem = ItemSystem; +} diff --git a/Client/js/systems/QuestSystem.js b/Client/js/systems/QuestSystem.js index 7a443dc..cdd0b2b 100644 --- a/Client/js/systems/QuestSystem.js +++ b/Client/js/systems/QuestSystem.js @@ -13,6 +13,10 @@ class QuestSystem { this.game = gameEngine; + // Server time synchronization + this.serverTimeOffset = 0; // Difference between server and client time + this.lastServerTimeSync = 0; + // Quest types this.questTypes = { main: 'Main Story', @@ -788,14 +792,15 @@ class QuestSystem { this.maxProceduralQuests = 3; this.proceduralQuestRefresh = 30 * 60 * 1000; // 30 minutes - // Statistics + // Initialize stats this.stats = { questsCompleted: 0, + questsFailed: 0, dailyQuestsCompleted: 0, weeklyQuestsCompleted: 0, totalRewardsEarned: { credits: 0, experience: 0, gems: 0 }, - lastDailyReset: Date.now(), - lastWeeklyReset: Date.now() + lastDailyReset: this.getServerTime(), + lastWeeklyReset: this.getServerTime() }; // Initialize daily quests @@ -816,6 +821,43 @@ class QuestSystem { }); } + // Server time synchronization methods + getServerTime() { + // In multiplayer mode, use UTC time as server time + if (window.smartSaveManager?.isMultiplayer) { + // Get current UTC timestamp + const utcTime = Date.now(); + + console.log('[QUEST SYSTEM] Using UTC time as server time:', utcTime); + console.log('[QUEST SYSTEM] Local time:', Date.now()); + + return utcTime; + } + + // Fallback to client time in singleplayer + return Date.now(); + } + + getServerDate() { + // Create a date that displays in UTC + const timestamp = this.getServerTime(); + const utcDate = new Date(timestamp); + + // Force UTC display by using UTC methods + return { + getTime: () => timestamp, + getDay: () => utcDate.getUTCDay(), + getHours: () => utcDate.getUTCHours(), + getMinutes: () => utcDate.getUTCMinutes(), + getSeconds: () => utcDate.getUTCSeconds(), + getDate: () => utcDate.getUTCDate(), + getFullYear: () => utcDate.getUTCFullYear(), + getMonth: () => utcDate.getUTCMonth(), + toString: () => utcDate.toUTCString(), + valueOf: () => timestamp + }; + } + async initialize() { const debugLogger = window.debugLogger; @@ -1119,7 +1161,7 @@ class QuestSystem { // Complete quest quest.status = 'completed'; - quest.completedAt = Date.now(); + quest.completedAt = this.getServerTime(); this.completedQuests.push(quest); if (debugLogger) debugLogger.logStep('Quest marked as completed', { @@ -1131,7 +1173,7 @@ class QuestSystem { // Save completed daily quests to history if (quest.type === 'daily') { - const questCopy = { ...quest, completedAt: Date.now() }; + const questCopy = { ...quest, completedAt: this.getServerTime() }; this.completedDailyQuests.push(questCopy); if (debugLogger) debugLogger.logStep('Daily quest added to history', { @@ -1143,7 +1185,7 @@ class QuestSystem { // Save completed weekly quests to history if (quest.type === 'weekly') { - const questCopy = { ...quest, completedAt: Date.now() }; + const questCopy = { ...quest, completedAt: this.getServerTime() }; this.completedWeeklyQuests.push(questCopy); if (debugLogger) debugLogger.logStep('Weekly quest added to history', { @@ -1218,22 +1260,6 @@ class QuestSystem { giveQuestRewards(quest) { const debugLogger = window.debugLogger; - if (debugLogger) debugLogger.startStep('QuestSystem.giveQuestRewards', { - questId: quest.id, - questName: quest.name, - questType: quest.type, - rewards: quest.rewards - }); - - const oldPlayerStats = { - credits: this.game.systems.economy.credits, - gems: this.game.systems.economy.gems, - experience: this.game.systems.player.stats.experience, - level: this.game.systems.player.stats.level - }; - - const rewardsGiven = {}; - if (quest.rewards.credits) { this.game.systems.economy.addCredits(quest.rewards.credits, 'quest'); this.stats.totalRewardsEarned.credits += quest.rewards.credits; @@ -1558,7 +1584,7 @@ class QuestSystem { this.completedQuests = []; this.dailyQuests = []; this.selectedDailyQuests = []; - this.lastDailyReset = Date.now(); + this.lastDailyReset = this.getServerTime(); // Reset main quest statuses this.mainQuests.forEach(quest => { @@ -1591,7 +1617,7 @@ class QuestSystem { checkDailyReset() { const debugLogger = window.debugLogger; - const now = Date.now(); + const now = this.getServerTime(); const lastReset = this.lastDailyReset; const daysSinceReset = Math.floor((now - lastReset) / (24 * 60 * 60 * 1000)); @@ -1645,20 +1671,27 @@ class QuestSystem { // Update countdown immediately this.updateDailyCountdown(); - // Update every second - this.dailyCountdownInterval = setInterval(() => { - this.updateDailyCountdown(); - }, 1000); + // Only start timer if in multiplayer mode or game is actively running + const shouldStartTimer = window.smartSaveManager?.isMultiplayer || this.game?.isRunning; - console.log('[QUEST SYSTEM] Daily countdown timer started with interval:', this.dailyCountdownInterval); + if (shouldStartTimer) { + // Update every second + this.dailyCountdownInterval = setInterval(() => { + this.updateDailyCountdown(); + }, 1000); + + console.log('[QUEST SYSTEM] Daily countdown timer started with interval:', this.dailyCountdownInterval); + } else { + console.log('[QUEST SYSTEM] Skipping daily countdown timer - not in multiplayer mode'); + } } updateDailyCountdown() { // Always update countdown so it's ready when user switches to daily tab - const now = new Date(); - const tomorrow = new Date(now); - tomorrow.setDate(tomorrow.getDate() + 1); - tomorrow.setHours(0, 0, 0, 0); // Set to midnight + const now = this.getServerDate(); + const tomorrow = new Date(); + tomorrow.setUTCDate(now.getDate() + 1); + tomorrow.setUTCHours(0, 0, 0, 0); // Set to midnight UTC const timeUntilReset = tomorrow - now; const hours = Math.floor(timeUntilReset / (1000 * 60 * 60)); @@ -1723,11 +1756,11 @@ class QuestSystem { checkWeeklyReset() { const debugLogger = window.debugLogger; - const now = Date.now(); + const now = this.getServerTime(); const lastReset = this.lastWeeklyReset || 0; - // Calculate if we need to reset based on Saturday midnight - const currentDateTime = new Date(); + // Calculate if we need to reset based on Saturday midnight (server time) + const currentDateTime = this.getServerDate(); const dayOfWeek = currentDateTime.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday const currentHour = currentDateTime.getHours(); const currentMinute = currentDateTime.getMinutes(); @@ -1805,18 +1838,25 @@ class QuestSystem { // Update countdown immediately this.updateWeeklyCountdown(); - // Update every minute (weekly changes less frequently) - this.weeklyCountdownInterval = setInterval(() => { - this.updateWeeklyCountdown(); - // Check for weekly reset every minute - this.checkWeeklyReset(); - }, 60000); // Update every minute + // Only start timer if in multiplayer mode or game is actively running + const shouldStartTimer = window.smartSaveManager?.isMultiplayer || this.game?.isRunning; - console.log('[QUEST SYSTEM] Weekly countdown timer started with interval:', this.weeklyCountdownInterval); + if (shouldStartTimer) { + // Update every minute (weekly changes less frequently) + this.weeklyCountdownInterval = setInterval(() => { + this.updateWeeklyCountdown(); + // Check for weekly reset every minute + this.checkWeeklyReset(); + }, 60000); // 1 minute + + console.log('[QUEST SYSTEM] Weekly countdown timer started with interval:', this.weeklyCountdownInterval); + } else { + console.log('[QUEST SYSTEM] Skipping weekly countdown timer - not in multiplayer mode'); + } } updateWeeklyCountdown() { - const now = new Date(); + const now = this.getServerDate(); const dayOfWeek = now.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday const currentHour = now.getHours(); const currentMinute = now.getMinutes(); @@ -1850,19 +1890,19 @@ class QuestSystem { console.log('[QUEST SYSTEM] Days until Saturday:', daysUntilSaturday); - // Create the target reset time (Saturday midnight) - const nextSaturday = new Date(now); - nextSaturday.setDate(now.getDate() + daysUntilSaturday); - nextSaturday.setHours(0, 0, 0, 0); // Set to midnight + // Create the target reset time (Saturday midnight UTC) + const nextSaturday = new Date(); + nextSaturday.setUTCDate(now.getDate() + daysUntilSaturday); + nextSaturday.setUTCHours(0, 0, 0, 0); // Set to midnight UTC - console.log('[QUEST SYSTEM] Next Saturday reset time:', nextSaturday.toString()); + console.log('[QUEST SYSTEM] Next Saturday reset time:', nextSaturday.toUTCString()); let timeUntilReset = nextSaturday.getTime() - now.getTime(); // Ensure timeUntilReset is positive (handle edge cases) if (timeUntilReset <= 0) { console.log('[QUEST SYSTEM] Time until reset is negative or zero, adding 7 days'); - nextSaturday.setDate(nextSaturday.getDate() + 7); + nextSaturday.setUTCDate(nextSaturday.getDate() + 7); timeUntilReset = nextSaturday.getTime() - now.getTime(); } @@ -2067,8 +2107,58 @@ class QuestSystem { return completed.sort((a, b) => (b.completedAt || 0) - (a.completedAt || 0)); } + // Load quests from server data + loadServerQuests(serverQuestData) { + console.log('[QUEST SYSTEM] Loading server quest data:', serverQuestData); + + if (!serverQuestData) { + console.log('[QUEST SYSTEM] No server quest data provided'); + return; + } + + // Clear existing quests + this.mainQuests = []; + this.dailyQuests = []; + this.weeklyQuests = []; + this.activeQuests = []; + this.completedQuests = []; + + // Load quests from server data + if (serverQuestData.mainQuests && Array.isArray(serverQuestData.mainQuests)) { + this.mainQuests = serverQuestData.mainQuests; + console.log('[QUEST SYSTEM] Loaded', this.mainQuests.length, 'main quests'); + } + + if (serverQuestData.dailyQuests && Array.isArray(serverQuestData.dailyQuests)) { + this.dailyQuests = serverQuestData.dailyQuests; + console.log('[QUEST SYSTEM] Loaded', this.dailyQuests.length, 'daily quests'); + } + + if (serverQuestData.activeQuests && Array.isArray(serverQuestData.activeQuests)) { + this.activeQuests = serverQuestData.activeQuests; + console.log('[QUEST SYSTEM] Loaded', this.activeQuests.length, 'active quests'); + } + + if (serverQuestData.completedQuests && Array.isArray(serverQuestData.completedQuests)) { + this.completedQuests = serverQuestData.completedQuests; + console.log('[QUEST SYSTEM] Loaded', this.completedQuests.length, 'completed quests'); + } + + console.log('[QUEST SYSTEM] Server quest data loaded successfully'); + console.log('[QUEST SYSTEM] Total quests loaded:', { + main: this.mainQuests.length, + daily: this.dailyQuests.length, + active: this.activeQuests.length, + completed: this.completedQuests.length + }); + } + // UI updates updateUI() { + console.log('[QUEST SYSTEM] updateUI called'); + console.log('[QUEST SYSTEM] Game available:', !!this.game); + console.log('[QUEST SYSTEM] Multiplayer mode:', window.smartSaveManager?.isMultiplayer); + this.updateQuestList(); this.updateQuestStats(); } @@ -2086,6 +2176,7 @@ class QuestSystem { const quests = this.getQuestsByType(activeType); console.log(`[QUEST SYSTEM] Getting quests for type: ${activeType}, found: ${quests.length} quests`); + console.log('[QUEST SYSTEM] First few quests:', quests.slice(0, 3)); questListElement.innerHTML = ''; @@ -2273,7 +2364,10 @@ class QuestSystem { break; } - player.updateUI(); + // Only update player UI if in multiplayer mode or game is actively running + if (this.game.shouldUpdateGUI()) { + player.updateUI(); + } } // Retry failed quest diff --git a/Client/js/systems/SkillSystem.js b/Client/js/systems/SkillSystem.js index 2f1a59e..1148052 100644 --- a/Client/js/systems/SkillSystem.js +++ b/Client/js/systems/SkillSystem.js @@ -304,8 +304,12 @@ return true; player.stats.skillPoints--; this.levelUpSkill(category, skillId); - // Update UI to refresh skill points display - this.updateUI(); + // Update UI to refresh skill points display only if in multiplayer mode or game is actively running + const shouldUpdateUI = window.smartSaveManager?.isMultiplayer || this.game?.isRunning; + + if (shouldUpdateUI) { + this.updateUI(); + } return true; } @@ -341,8 +345,12 @@ return true; this.applySkillEffects(); - // Update UI to refresh skill points display - this.updateUI(); + // Update UI to refresh skill points display only if in multiplayer mode or game is actively running + const shouldUpdateUI = window.smartSaveManager?.isMultiplayer || this.game?.isRunning; + + if (shouldUpdateUI) { + this.updateUI(); + } this.game.showNotification(`${skill.name} unlocked!`, 'success', 4000); return true; diff --git a/Client/js/ui/LiveMainMenu.js b/Client/js/ui/LiveMainMenu.js index 4ffcb3f..b18d573 100644 --- a/Client/js/ui/LiveMainMenu.js +++ b/Client/js/ui/LiveMainMenu.js @@ -37,8 +37,16 @@ class LiveMainMenu { // Check for existing auth token this.checkExistingAuth(); - // Check for local server and update URLs if needed - this.checkForLocalServer(); + // DISABLE local server check - always use remote multiplayer server + // this.checkForLocalServer(); + console.log('[LIVE MAIN MENU] Local server check disabled - using remote multiplayer server only'); + console.log('[LIVE MAIN MENU] Using remote API:', this.apiBaseUrl); + console.log('[LIVE MAIN MENU] Using remote game server:', this.gameServerUrl); + + // Initialize SmartSaveManager to singleplayer mode by default + if (window.smartSaveManager) { + window.smartSaveManager.setMultiplayerMode(false); + } console.log('[LIVE MAIN MENU] Constructor completed'); } @@ -435,6 +443,9 @@ class LiveMainMenu { } async refreshServerList() { + // Build server list with local server, dev server, and API servers + const servers = []; + if (!this.authToken) { console.error('[LIVE MAIN MENU] No auth token for server list'); return; @@ -447,9 +458,6 @@ class LiveMainMenu { try { let response; - // Build server list with local server, dev server, and API servers - const servers = []; - // Add local server if available if (this.isLocalMode && window.localServerManager && window.localServerManager.localServer && window.localServerManager.localServer.mockRequest) { console.log('[LIVE MAIN MENU] Using SimpleLocalServer mock API for local server'); @@ -459,32 +467,8 @@ class LiveMainMenu { } } - // Add dev game server - try { - const devServerResponse = await fetch('http://localhost:3002/health'); - if (devServerResponse.ok) { - const devServerInfo = await devServerResponse.json(); - servers.push({ - id: 'dev-game-server', - name: 'Dev Game Server', - description: 'Development game server for testing', - type: 'development', - region: 'local', - maxPlayers: 8, - currentPlayers: 0, - owner: 'Developer', - address: 'localhost', - port: 3002, - status: 'online', - createdAt: new Date().toISOString(), - ping: 0, - isLocal: true, - isDev: true - }); - } - } catch (error) { - console.log('[LIVE MAIN MENU] Dev game server not available:', error.message); - } + // Skip local dev server check to avoid blocking + console.log('[LIVE MAIN MENU] Skipping local dev server check to avoid blocking'); // Add API servers console.log('[LIVE MAIN MENU] Fetching server list from:', `${this.apiBaseUrl}/servers`); @@ -495,37 +479,35 @@ class LiveMainMenu { 'Content-Type': 'application/json' } }); - if (apiResponse.ok) { const apiData = await apiResponse.json(); if (apiData.success && apiData.servers) { servers.push(...apiData.servers); + console.log(`[LIVE MAIN MENU] Server list loaded: ${servers.length} servers found`); + + // Debug: Log server list data + console.log('[LIVE MAIN MENU] Server list loaded:', servers); + if (servers.length > 0) { + console.log('[LIVE MAIN MENU] First server details:', servers[0]); + } } } - - this.servers = servers; - - console.log('[LIVE MAIN MENU] Server list loaded:', servers.length, 'servers found'); - - // Display servers - this.renderServerList(); - } catch (error) { - console.error('[LIVE MAIN MENU] Server list error:', error); - this.servers = []; - this.renderServerList(); - - // Show error message to user - this.showLoginNotice(`Network error: ${error.message}`, 'error'); + console.error('[LIVE MAIN MENU] Error fetching server list:', error); + this.showLoginNotice('Connection error. Please try again.', 'error'); } finally { // Hide loading state if (this.serverLoading) this.serverLoading.classList.add('hidden'); } + + // Store servers + this.servers = servers; + + // Render server list + this.renderServerList(); } renderServerList() { - if (!this.servers) return; - const filteredServers = this.getFilteredServers(); // Handle empty state @@ -553,6 +535,22 @@ class LiveMainMenu { this.updateServerItemsSmoothly(currentItems, newItems); } + removeServerItemsSmoothly() { + const existingItems = this.serverList?.querySelectorAll('.server-item') || []; + + if (existingItems.length === 0) return; + + // Add fade-out transition + existingItems.forEach(item => { + item.style.transition = 'opacity 0.2s ease-out'; + item.style.opacity = '0'; + }); + + // Remove items after fade out + setTimeout(() => { + existingItems.forEach(item => item.remove()); + }, 200); +} removeServerItemsSmoothly() { const existingItems = this.serverList?.querySelectorAll('.server-item') || []; @@ -1026,6 +1024,13 @@ Status: ${this.selectedServer.status} launchMultiplayerGame(server, serverData) { console.log('[LIVE MAIN MENU] Launching multiplayer game on server:', server.name); + + // Set SmartSaveManager to multiplayer mode + if (window.smartSaveManager && window.gameInitializer) { + window.smartSaveManager.setMultiplayerMode(true, window.gameInitializer); + console.log('[LIVE MAIN MENU] SmartSaveManager set to multiplayer mode'); + } + // Hide main menu and start game in multiplayer mode this.hideLoadingScreenAndShowGame(); this.initializeMultiplayerGame(server, serverData); @@ -1051,278 +1056,88 @@ Status: ${this.selectedServer.status} initializeMultiplayerGame(server, serverData) { console.log('[LIVE MAIN MENU] Initializing multiplayer game'); - // Check if GameEngine is available, if not create a basic fallback - if (typeof GameEngine === 'undefined') { - console.warn('[LIVE MAIN MENU] GameEngine class not available, creating fallback'); - - // Create a basic fallback GameEngine class - window.GameEngine = class GameEngine extends EventTarget { - constructor() { - super(); - this.systems = {}; - this.gameTime = 0; - this.isRunning = false; - this.isFallback = true; // Mark as fallback for UIManager - console.log('[LIVE MAIN MENU] Fallback GameEngine created'); - } - - async init() { - console.log('[LIVE MAIN MENU] Fallback GameEngine init() called'); - return true; - } - - setMultiplayerMode(isMultiplayer, socket, serverData, currentUser) { - console.log('[LIVE MAIN MENU] Fallback GameEngine setMultiplayerMode called'); - this.isMultiplayer = isMultiplayer; - this.socket = socket; - this.serverData = serverData; - this.currentUser = currentUser; - } - - startGame(continueGame) { - console.log('[LIVE MAIN MENU] Fallback GameEngine startGame called, continueGame:', continueGame); - this.isRunning = true; - - // Add simple return to menu functionality - window.returnToMainMenu = () => { - console.log('[LIVE MAIN MENU] Return to main menu requested'); - if (window.liveMainMenu) { - window.liveMainMenu.showLoginSection(); - } - }; - } - - getPerformanceStats() { - console.log('[LIVE MAIN MENU] Fallback GameEngine getPerformanceStats called'); - return { - gameTime: this.gameTime, - isRunning: this.isRunning, - fps: 60, - memory: 'N/A (fallback mode)' - }; - } - - showNotification(message, type = 'info', duration = 3000) { - console.log(`[LIVE MAIN MENU] Fallback notification: ${message} (${type})`); - // Simple console notification for fallback mode - if (typeof console !== 'undefined') { - console.log(`📢 ${type.toUpperCase()}: ${message}`); - } - } - - save() { - console.log('[LIVE MAIN MENU] Fallback GameEngine save called'); - // Save will be handled by the main save logic when it detects local mode - return Promise.resolve({ success: true }); - } - }; + // DELAY GameEngine creation until after multiplayer connection is ready + // First, set up GameInitializer and socket connection + if (!window.gameInitializer) { + console.log('[LIVE MAIN MENU] Creating new GameInitializer for multiplayer'); + window.gameInitializer = new GameInitializer(); } - // Create GameEngine if it doesn't exist - if (!window.game) { - console.log('[LIVE MAIN MENU] Creating new GameEngine instance for multiplayer'); - try { - window.game = new GameEngine(); - - // Initialize the game engine - window.game.init().then(() => { - console.log('[LIVE MAIN MENU] GameEngine initialized successfully for multiplayer'); - - // Apply pending save data if available (after GameEngine is ready) - if (window.liveMainMenu && window.liveMainMenu.pendingSaveData) { - console.log('[LIVE MAIN MENU] Applying pending save data after GameEngine init...'); - - try { - const saveData = window.liveMainMenu.pendingSaveData; - console.log('[LIVE MAIN MENU] Save data structure:', { - hasPlayer: !!saveData.player, - hasInventory: !!saveData.inventory, - hasEconomy: !!saveData.economy, - hasIdleSystem: !!saveData.idleSystem, - hasDungeonSystem: !!saveData.dungeonSystem, - hasSkillSystem: !!saveData.skillSystem, - hasBaseSystem: !!saveData.baseSystem, - hasQuestSystem: !!saveData.questSystem, - gameTime: saveData.gameTime, - playerLevel: saveData.player?.stats?.level - }); - - console.log('[LIVE MAIN MENU] Game systems available:', { - hasGame: !!window.game, - hasSystems: !!window.game?.systems, - hasPlayer: !!window.game?.systems?.player, - hasInventory: !!window.game?.systems?.inventory, - hasEconomy: !!window.game?.systems?.economy, - hasIdleSystem: !!window.game?.systems?.idleSystem, - hasDungeonSystem: !!window.game?.systems?.dungeonSystem, - hasSkillSystem: !!window.game?.systems?.skillSystem, - hasBaseSystem: !!window.game?.systems?.baseSystem, - hasQuestSystem: !!window.game?.systems?.questSystem - }); - - let appliedCount = 0; - - // Apply save data to game systems - if (saveData.player && window.game.systems.player) { - window.game.systems.player.load(saveData.player); - console.log('[LIVE MAIN MENU] ✅ Player data loaded from pending save'); - appliedCount++; - } else { - console.log('[LIVE MAIN MENU] ❌ Player data NOT loaded - saveData.player:', !!saveData.player, 'window.game.systems.player:', !!window.game?.systems?.player); - } - - if (saveData.inventory && window.game.systems.inventory) { - window.game.systems.inventory.load(saveData.inventory); - console.log('[LIVE MAIN MENU] ✅ Inventory data loaded from pending save'); - appliedCount++; - } else { - console.log('[LIVE MAIN MENU] ❌ Inventory data NOT loaded - saveData.inventory:', !!saveData.inventory, 'window.game.systems.inventory:', !!window.game?.systems?.inventory); - } - - if (saveData.economy && window.game.systems.economy) { - window.game.systems.economy.load(saveData.economy); - console.log('[LIVE MAIN MENU] ✅ Economy data loaded from pending save'); - appliedCount++; - } else { - console.log('[LIVE MAIN MENU] ❌ Economy data NOT loaded - saveData.economy:', !!saveData.economy, 'window.game.systems.economy:', !!window.game?.systems?.economy); - } - - if (saveData.idleSystem && window.game.systems.idleSystem) { - window.game.systems.idleSystem.load(saveData.idleSystem); - console.log('[LIVE MAIN MENU] ✅ Idle system data loaded from pending save'); - appliedCount++; - } else { - console.log('[LIVE MAIN MENU] ❌ Idle system data NOT loaded - saveData.idleSystem:', !!saveData.idleSystem, 'window.game.systems.idleSystem:', !!window.game?.systems?.idleSystem); - } - - if (saveData.dungeonSystem && window.game.systems.dungeonSystem) { - window.game.systems.dungeonSystem.load(saveData.dungeonSystem); - console.log('[LIVE MAIN MENU] ✅ Dungeon system data loaded from pending save'); - appliedCount++; - } else { - console.log('[LIVE MAIN MENU] ❌ Dungeon system data NOT loaded - saveData.dungeonSystem:', !!saveData.dungeonSystem, 'window.game.systems.dungeonSystem:', !!window.game?.systems?.dungeonSystem); - } - - if (saveData.skillSystem && window.game.systems.skillSystem) { - window.game.systems.skillSystem.load(saveData.skillSystem); - console.log('[LIVE MAIN MENU] ✅ Skill system data loaded from pending save'); - appliedCount++; - } else { - console.log('[LIVE MAIN MENU] ❌ Skill system data NOT loaded - saveData.skillSystem:', !!saveData.skillSystem, 'window.game.systems.skillSystem:', !!window.game?.systems?.skillSystem); - } - - if (saveData.baseSystem && window.game.systems.baseSystem) { - window.game.systems.baseSystem.load(saveData.baseSystem); - console.log('[LIVE MAIN MENU] ✅ Base system data loaded from pending save'); - appliedCount++; - } else { - console.log('[LIVE MAIN MENU] ❌ Base system data NOT loaded - saveData.baseSystem:', !!saveData.baseSystem, 'window.game.systems.baseSystem:', !!window.game?.systems?.baseSystem); - } - - if (saveData.questSystem && window.game.systems.questSystem) { - window.game.systems.questSystem.load(saveData.questSystem); - console.log('[LIVE MAIN MENU] ✅ Quest system data loaded from pending save'); - appliedCount++; - } else { - console.log('[LIVE MAIN MENU] ❌ Quest system data NOT loaded - saveData.questSystem:', !!saveData.questSystem, 'window.game.systems.questSystem:', !!window.game?.systems?.questSystem); - } - - if (saveData.gameTime && window.game) { - window.game.gameTime = saveData.gameTime; - console.log('[LIVE MAIN MENU] ✅ Game time loaded from pending save:', saveData.gameTime); - appliedCount++; - } else { - console.log('[LIVE MAIN MENU] ❌ Game time NOT loaded - saveData.gameTime:', !!saveData.gameTime, 'window.game:', !!window.game); - } - - console.log(`[LIVE MAIN MENU] Save data application complete: ${appliedCount}/9 systems loaded`); - - if (appliedCount === 0) { - console.warn('[LIVE MAIN MENU] ⚠️ NO save data was applied! This is why the UI shows fresh game state.'); - console.warn('[LIVE MAIN MENU] ⚠️ The fallback GameEngine does not have the required game systems.'); - } - - console.log('[LIVE MAIN MENU] Pending save data applied successfully'); - - // Clear pending save data - window.liveMainMenu.pendingSaveData = null; - - } catch (error) { - console.error('[LIVE MAIN MENU] Error applying pending save data:', error); - } - } else { - console.log('[LIVE MAIN MENU] No pending save data to apply'); - } - - // Now initialize multiplayer mode through GameInitializer - if (window.gameInitializer) { - // Determine the correct game server URL based on server type - let gameServerUrl = this.gameServerUrl; // Default to dev server - - if (server.isLocal) { - gameServerUrl = this.localGameServerUrl; - } else if (server.isDev) { - gameServerUrl = this.gameServerUrl; - } else { - // Use server address and port for remote servers - gameServerUrl = `http://${server.address}:${server.port}`; - } - - console.log('[LIVE MAIN MENU] Using game server URL:', gameServerUrl); - window.gameInitializer.updateServerUrls(this.apiBaseUrl, gameServerUrl); - window.gameInitializer.initializeMultiplayer(server, serverData, this.authToken, this.currentUser); - } - }).catch(error => { - console.error('[LIVE MAIN MENU] Failed to initialize GameEngine for multiplayer:', error); - this.showLoginNotice('Failed to initialize game: ' + error.message, 'error'); - }); - } catch (error) { - console.error('[LIVE MAIN MENU] Error creating GameEngine:', error); - this.showLoginNotice('Failed to create game engine: ' + error.message, 'error'); + // Set up server URLs and connection first + window.gameInitializer.updateServerUrls( + 'https://api.korvarix.com/api', + server.url || 'https://dev.gameserver.galaxystrike.online' + ); + + // Initialize multiplayer mode (this will handle GameEngine creation after authentication) + window.gameInitializer.initializeMultiplayer(server, serverData, this.authToken, this.currentUser); + + // Add simple return to menu functionality + window.returnToMainMenu = () => { + console.log('[LIVE MAIN MENU] Return to main menu requested'); + if (window.liveMainMenu) { + window.liveMainMenu.showLoginSection(); } - } else { - console.log('[LIVE MAIN MENU] GameEngine already exists, initializing multiplayer mode'); + }; + } + + checkForLocalServer() { + console.log('[LIVE MAIN MENU] Checking for local server...'); + + // Check if local server manager is available and running + if (window.localServerManager) { + const serverStatus = window.localServerManager.getStatus(); - // Initialize multiplayer mode through GameInitializer - if (window.gameInitializer) { - // Determine the correct game server URL based on server type - let gameServerUrl = this.gameServerUrl; // Default to dev server + if (serverStatus.isRunning) { + console.log('[LIVE MAIN MENU] Local server detected, switching to local mode'); + this.isLocalMode = true; + this.apiBaseUrl = `http://localhost:${serverStatus.port}/api`; + this.gameServerUrl = `http://localhost:${serverStatus.port}`; - if (server.isLocal) { - gameServerUrl = this.localGameServerUrl; - } else if (server.isDev) { - gameServerUrl = this.gameServerUrl; - } else { - // Use server address and port for remote servers - gameServerUrl = `http://${server.address}:${server.port}`; + console.log(`[LIVE MAIN MENU] Updated API URL to: ${this.apiBaseUrl}`); + + // Update button to show local mode + if (this.createServerBtn) { + this.createServerBtn.innerHTML = ' Local Server Running'; + this.createServerBtn.classList.remove('btn-primary'); + this.createServerBtn.classList.add('btn-success'); } - console.log('[LIVE MAIN MENU] Using game server URL:', gameServerUrl); - window.gameInitializer.updateServerUrls(this.apiBaseUrl, gameServerUrl); - window.gameInitializer.initializeMultiplayer(server, serverData, this.authToken, this.currentUser); + // Auto-login for local mode + this.autoLoginLocalMode(); + + } else { + console.log('[LIVE MAIN MENU] No local server running'); } + } else { + console.log('[LIVE MAIN MENU] Local server manager not available'); } } async startLocalServer() { console.log('[LIVE MAIN MENU] Starting local server...'); + // Disable button to prevent multiple clicks + if (this.createServerBtn) { + this.createServerBtn.disabled = true; + this.createServerBtn.innerHTML = ' Starting...'; + } + try { + // Check if LocalServerManager is available if (!window.localServerManager) { console.error('[LIVE MAIN MENU] LocalServerManager not available'); this.showLoginNotice('Local server manager not available', 'error'); + + // Reset button + if (this.createServerBtn) { + this.createServerBtn.disabled = false; + this.createServerBtn.innerHTML = ' Start Local Server'; + } return; } - // Update button to show loading state - if (this.createServerBtn) { - this.createServerBtn.innerHTML = ' Starting...'; - this.createServerBtn.disabled = true; - } - - // Show loading message - this.showLoginNotice('Starting local server...', 'info'); - + // Start the local server const result = await window.localServerManager.startServer(); if (result.success) { @@ -1370,117 +1185,16 @@ Status: ${this.selectedServer.status} } } } - - checkForLocalServer() { - console.log('[LIVE MAIN MENU] Checking for local server...'); - - // Check if local server manager is available and running - if (window.localServerManager) { - const serverStatus = window.localServerManager.getStatus(); - - if (serverStatus.isRunning) { - console.log('[LIVE MAIN MENU] Local server detected, switching to local mode'); - this.isLocalMode = true; - this.apiBaseUrl = `http://localhost:${serverStatus.port}/api`; - this.gameServerUrl = `http://localhost:${serverStatus.port}`; - - console.log(`[LIVE MAIN MENU] Updated API URL to: ${this.apiBaseUrl}`); - console.log(`[LIVE MAIN MENU] Updated Game Server URL to: ${this.gameServerUrl}`); - - // Update login notice to show local mode - this.showLoginNotice('Local server active - Singleplayer mode available', 'info'); - - // Auto-login for local mode - this.autoLoginLocalMode(); - } else { - console.log('[LIVE MAIN MENU] Local server not running, using remote servers'); - } - } else { - console.log('[LIVE MAIN MENU] LocalServerManager not available'); - } - } - - async autoLoginLocalMode() { - console.log('[LIVE MAIN MENU] Auto-logging in to local mode...'); - - try { - let response; - - // Use SimpleLocalServer mock API if available - if (window.localServerManager && window.localServerManager.localServer && window.localServerManager.localServer.mockRequest) { - console.log('[LIVE MAIN MENU] Using SimpleLocalServer mock API'); - response = await window.localServerManager.localServer.mockRequest('POST', '/api/auth/login', { - email: 'local@player.com', - password: 'local' - }); - } else { - // Fallback to real fetch - console.log('[LIVE MAIN MENU] Using real fetch for local login'); - response = await fetch(`${this.apiBaseUrl}/auth/login`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - email: 'local@player.com', - password: 'local' - }) - }); - } - - const data = await response.json(); - - if (data.success) { - console.log('[LIVE MAIN MENU] Local mode login successful'); - this.authToken = data.token; - this.currentUser = data.user; - - // Update UI - this.hideLoginNotice(); - this.showServerSection(false, false); // Show section without refreshing or animating - - // Load servers (will show local server) - this.refreshServerList(); - } else { - console.error('[LIVE MAIN MENU] Local mode login failed:', data); - } - } catch (error) { - console.error('[LIVE MAIN MENU] Error in local mode auto-login:', error); - } - } } -// Initialize the live main menu when DOM is ready -function initializeLiveMainMenu() { - console.log('[LIVE MAIN MENU] Initializing LiveMainMenu...'); - - // Wait a bit for DOM to be fully ready - setTimeout(() => { - if (!window.liveMainMenu) { - console.log('[LIVE MAIN MENU] Creating new LiveMainMenu instance'); - window.liveMainMenu = new LiveMainMenu(); - } else { - console.log('[LIVE MAIN MENU] LiveMainMenu already exists'); - } - }, 100); -} +// End of LiveMainMenu class -// Try multiple initialization methods -if (document.readyState === 'loading') { - // DOM is still loading, wait for DOMContentLoaded - document.addEventListener('DOMContentLoaded', initializeLiveMainMenu); -} else { - // DOM is already ready, initialize immediately - initializeLiveMainMenu(); -} - -// Also try initialization as a fallback -setTimeout(() => { - if (!window.liveMainMenu) { - console.warn('[LIVE MAIN MENU] Fallback initialization triggered'); +// Initialize LiveMainMenu when DOM is ready +document.addEventListener('DOMContentLoaded', () => { + if (typeof window !== 'undefined') { window.liveMainMenu = new LiveMainMenu(); } -}, 1000); +}); // Export for use in other scripts if (typeof module !== 'undefined' && module.exports) { diff --git a/Client/js/ui/UIManager.js b/Client/js/ui/UIManager.js index 04913cb..9b3bdc2 100644 --- a/Client/js/ui/UIManager.js +++ b/Client/js/ui/UIManager.js @@ -43,16 +43,19 @@ class UIManager { const gameInterface = document.getElementById('gameInterface'); const navButtons = document.querySelectorAll('.nav-btn'); - if (debugLogger) debugLogger.logStep('DOM Check', { + console.log('[UI MANAGER] DOM Check:', { gameInterfaceExists: !!gameInterface, gameInterfaceHidden: gameInterface?.classList.contains('hidden'), navButtonsFound: navButtons.length, documentReady: document.readyState }); - if (gameInterface && !gameInterface.classList.contains('hidden') && navButtons.length > 0) { + // Less strict condition - proceed if we have nav buttons, even if interface is still hidden + if (navButtons.length > 0) { + console.log('[UI MANAGER] Navigation buttons found, proceeding with initialization'); this.proceedWithInitialization(); } else { + console.log('[UI MANAGER] Waiting for navigation buttons...'); setTimeout(waitForDOM, 100); } }; @@ -117,10 +120,6 @@ class UIManager { }); if (debugLogger) debugLogger.endStep('UIManager.setupNavigation', { - navButtonsSetup: navButtons.length, - skillCatButtonsSetup: skillCatButtons.length, - shopCatButtonsSetup: shopCatButtons.length, - questTabButtonsSetup: questTabButtons.length, craftingCatButtonsSetup: craftingCatButtons.length }); } @@ -140,10 +139,11 @@ class UIManager { if (navButtons.length === 0) { console.warn('[UIManager] No navigation buttons found!'); if (debugLogger) debugLogger.logStep('No navigation buttons found'); + return; } navButtons.forEach((btn, index) => { - console.log(`[UIManager] Setting up button ${index}:`, btn.dataset.tab); + console.log(`[UIManager] Setting up button ${index}:`, btn.dataset.tab, btn); // Check if button already has a listener to prevent duplicates if (btn.getAttribute('data-has-listener') === 'true') { console.log(`[UIManager] Button ${btn.dataset.tab} already has listener, skipping`); @@ -151,16 +151,30 @@ class UIManager { } btn.addEventListener('click', (e) => { - console.log(`[UIManager] Navigation button clicked: ${btn.dataset.tab}`); + console.log(`[UIManager] Navigation button clicked: ${btn.dataset.tab}`, e); const tab = btn.dataset.tab; if (tab) { this.switchTab(tab); + } else { + console.warn('[UIManager] Button clicked but no tab data found'); } }); - // Mark button as having a listener + // Mark as having listener btn.setAttribute('data-has-listener', 'true'); - console.log(`[UIManager] Button ${btn.dataset.tab} setup complete`); + console.log(`[UIManager] Event listener added to button: ${btn.dataset.tab}`); + }); + + // Add a global fallback click handler for navigation buttons + document.addEventListener('click', (e) => { + if (e.target.classList.contains('nav-btn') || e.target.closest('.nav-btn')) { + const button = e.target.classList.contains('nav-btn') ? e.target : e.target.closest('.nav-btn'); + const tab = button.dataset.tab; + if (tab && window.game && window.game.systems && window.game.systems.ui) { + console.log('[UI MANAGER] Global fallback: Navigation button clicked:', tab); + window.game.systems.ui.switchTab(tab); + } + } }); // Set up UI update event listener from GameEngine @@ -284,7 +298,7 @@ class UIManager { // if (debugLogger) debugLogger.logStep('Setting up return to menu button'); returnToMenuBtn.addEventListener('click', () => { if (debugLogger) debugLogger.log('Return to menu button clicked'); - this.returnToMainMenu(); + this.showReturnToMainMenuModal(); }); // Mark as having listener @@ -697,43 +711,92 @@ class UIManager { } showReturnToMainMenuModal() { - const debugLogger = window.debugLogger; + const debugLogger = window.debugLogger; - // Show confirmation modal instead of browser confirm dialog - let content = '
'; - content += '

Are you sure you want to return to the main menu?

'; - content += '

Warning: Any unsaved progress will be lost.

'; - content += '
'; + // Show confirmation modal instead of browser confirm dialog + let content = '
'; + content += '

Are you sure you want to return to the main menu?

'; + content += '

Warning: Any unsaved progress will be lost.

'; + content += '
'; - // Check if using fallback GameEngine - if (window.game && window.game.isFallback) { - content += ''; - content += ''; - } else { - content += ''; - content += ''; + // Check if using fallback GameEngine + if (window.game && window.game.isFallback) { + content += ''; + content += ''; + } else { + content += ''; + content += ''; + } + + content += '
'; + content += '
'; + + this.showModal('Return to Main Menu', content); + if (debugLogger) debugLogger.endStep('UIManager.returnToMainMenu', { success: true, confirmationShown: true }); } -} - confirmReturnToMainMenu() { + async confirmReturnToMainMenu() { try { const debugLogger = window.debugLogger; - if (debugLogger) debugLogger.startStep('UIManager.confirmReturnToMainMenu', { - gameRunning: this.game ? this.game.isRunning : false - }); + // Reset multiplayer mode when returning to main menu + if (window.smartSaveManager) { + window.smartSaveManager.setMultiplayerMode(false); + } + // Check if we're in multiplayer mode - if so, don't save locally + const isMultiplayer = window.smartSaveManager?.isMultiplayer; - // Stop the game engine and save - if (this.game && typeof this.game.isRunning === 'boolean') { + // Always stop the game and clear timers regardless of mode + this.game.isRunning = false; + + // Force save before stopping in multiplayer mode + if (isMultiplayer && this.game && this.game.save) { + console.log('[UI MANAGER] Force saving game before leaving multiplayer mode'); + try { + await this.game.save(); + console.log('[UI MANAGER] Game saved successfully before leaving server'); + } catch (error) { + console.error('[UI MANAGER] Error saving game before leaving server:', error); + } + } + + // Clear game logic timer + if (this.game.gameLogicTimer) { + clearInterval(this.game.gameLogicTimer); + this.game.gameLogicTimer = null; + } + + // Clear auto-save timer + if (this.game.autoSaveTimer) { + clearInterval(this.game.autoSaveTimer); + this.game.autoSaveTimer = null; + } + + // Stop economy system timers + if (this.game.systems.economy) { + this.game.systems.economy.stopShopRefreshTimer(); + } + + if (isMultiplayer) { + console.log('[UI MANAGER] Skipping local save - returning from multiplayer mode'); - // if (debugLogger) debugLogger.logStep('Stopping game engine and saving'); + // Show main menu immediately + this.showMainMenu(); + this.closeModal(); - // Handle async stop properly + if (debugLogger) debugLogger.endStep('UIManager.confirmReturnToMainMenu', { + success: true, + multiplayerMode: true, + localSaveSkipped: true, + mainMenuShown: true + }); + } else { + // Handle async stop properly for singleplayer mode this.game.stop().then(() => { try { @@ -747,6 +810,25 @@ class UIManager { mainMenuShown: true }); } catch (error) { + try { + + if (debugLogger) debugLogger.errorEvent('UIManager.confirmReturnToMainMenu', error, { + phase: 'game_stop_and_save' + }); + + // Still return to menu even if save fails + this.showMainMenu(); + this.closeModal(); + + if (debugLogger) debugLogger.endStep('UIManager.confirmReturnToMainMenu', { + success: true, + gameStopped: true, + saveError: true, + mainMenuShown: true + }); + } catch (loggerError) { + console.error('[UI MANAGER] Debug logger error:', loggerError); + } } }).catch(error => { try { @@ -765,26 +847,36 @@ class UIManager { saveError: true, mainMenuShown: true }); - } catch (fallbackError) { + } catch (loggerError) { + console.error('[UI MANAGER] Debug logger error:', loggerError); } }); } - - // Always show main menu regardless of game state + } catch (error) { + console.error('[UI MANAGER] Error in confirmReturnToMainMenu:', error); + if (debugLogger) debugLogger.errorEvent('UIManager.confirmReturnToMainMenu', error, { + phase: 'main_logic' + }); + } + + // Always show main menu regardless of game state + try { setTimeout(() => { this.showMainMenu(); this.closeModal(); }, 5000); // 5 second delay to ensure full cleanup and menu readiness - if (debugLogger) debugLogger.endStep('UIManager.confirmReturnToMainMenu', { success: true, - gameStopped: this.game ? !this.game.isRunning : false, - mainMenuShown: true - }); + gameStopped: this.game ? !this.game.isRunning : false, + mainMenuShown: true + }); } catch (error) { + console.error('[UI MANAGER] Error during return to main menu:', error); + if (debugLogger) debugLogger.errorEvent('UIManager.confirmReturnToMainMenu', error, { + phase: 'return_to_main_menu' + }); } } - showMainMenu() { const debugLogger = window.debugLogger; @@ -1005,8 +1097,10 @@ class UIManager { gameStateUpdated: true }); - // Update specific tab content - this.updateTabContent(tabName); + // Update specific tab content only if in multiplayer mode or game is actively running + if (this.shouldUpdateUI()) { + this.updateTabContent(tabName); + } if (debugLogger) debugLogger.endStep('UIManager.switchTab', { success: true, @@ -1019,6 +1113,10 @@ class UIManager { } updateTabContent(tabName) { + if (!this.shouldUpdateUI()) { + return; + } + if (debugLogger) debugLogger.startStep('UIManager.updateTabContent', { tabName: tabName, currentTab: this.currentTab @@ -1208,8 +1306,14 @@ class UIManager { }); try { + // Call economy system update this.game.systems.economy.updateUI(); + // Also call global shop display function for additional debugging + if (typeof updateShopDisplay === 'function') { + updateShopDisplay(); + } + if (debugLogger) debugLogger.endStep('UIManager.switchShopCategory', { success: true, category: category, @@ -1229,13 +1333,7 @@ class UIManager { } } - switchQuestType(type) { - const debugLogger = window.debugLogger; - - if (debugLogger) debugLogger.startStep('UIManager.switchQuestType', { - type: type - }); - + updateQuestTabs(type) { const questTabButtons = document.querySelectorAll('.quest-tab-btn'); let buttonsUpdated = 0; @@ -1255,7 +1353,13 @@ class UIManager { }); try { - this.game.systems.questSystem.updateUI(); + // REMOVED: Client-side QuestSystem is now server-driven + // this.game.systems.questSystem.updateUI(); + + // Call the global quest display update function instead + if (typeof updateQuestDisplay === 'function') { + updateQuestDisplay(); + } if (debugLogger) debugLogger.endStep('UIManager.switchQuestType', { success: true, @@ -1403,7 +1507,29 @@ class UIManager { }); } - // Handle UI update events from GameEngine + // Force refresh all UI elements (called when server data is updated) + forceRefreshAllUI() { + if (window.smartSaveManager?.isMultiplayer) { + console.log('[UI MANAGER] Force refreshing all UI elements with server data'); + + // Update all resource displays + this.updateResourceDisplay(); + + // Update current tab content + if (this.currentTab) { + this.updateTabContent(this.currentTab); + } + + // Update quest system + if (this.game && this.game.systems && this.game.systems.questSystem) { + this.game.systems.questSystem.updateUI(); + } + + console.log('[UI MANAGER] UI refresh completed'); + } + } + + // Handle UI update events from game engine handleUIUpdate(data) { const debugLogger = window.debugLogger; @@ -1447,6 +1573,10 @@ class UIManager { } updateAllUI() { + if (!this.shouldUpdateUI()) { + return; + } + // if (debugLogger) debugLogger.log('Updating all UI elements'); this.updateResourceDisplay(); @@ -1481,18 +1611,40 @@ class UIManager { }); } + // Centralized UI update control - blocks all UI updates when not in multiplayer mode + shouldUpdateUI() { + const shouldUpdate = window.smartSaveManager?.isMultiplayer || this.game?.isRunning; + + if (!shouldUpdate) { + console.log('[UI MANAGER] Blocking UI update - not in multiplayer mode'); + return false; + } + + return true; + } + + // Centralized update resource display - all calls should go through this updateResourceDisplay() { - const debugLogger = window.debugLogger; + if (!this.shouldUpdateUI()) { + return; + } + + // In multiplayer mode, ensure we're using server data + if (window.smartSaveManager?.isMultiplayer) { + // console.log('[UI MANAGER] Updating resource display in multiplayer mode - using server data'); + } + + // const debugLogger = window.debugLogger; try { // Safety checks - return early if systems aren't available if (!this.game || !this.game.systems) { - if (debugLogger) debugLogger.log('Game systems not available, skipping resource display update'); + // if (debugLogger) debugLogger.log('Game systems not available, skipping resource display update'); return; } if (!this.game.systems.player || !this.game.systems.economy) { - if (debugLogger) debugLogger.log('Player or economy system not available, skipping resource display update'); + // if (debugLogger) debugLogger.log('Player or economy system not available, skipping resource display update'); return; } @@ -1501,18 +1653,18 @@ class UIManager { // Additional safety checks for player properties if (!player.stats || !player.attributes || !player.ship) { - if (debugLogger) debugLogger.log('Player properties not fully initialized, skipping resource display update'); + // if (debugLogger) debugLogger.log('Player properties not fully initialized, skipping resource display update'); return; } - if (debugLogger) debugLogger.startStep('UIManager.updateResourceDisplay', { - gameSystemsAvailable: !!(this.game && this.game.systems), - playerSystemAvailable: !!(this.game && this.game.systems && this.game.systems.player), - economySystemAvailable: !!(this.game && this.game.systems && this.game.systems.economy), - playerStatsAvailable: !!player.stats, - playerAttributesAvailable: !!player.attributes, - playerShipAvailable: !!player.ship - }); + // if (debugLogger) debugLogger.startStep('UIManager.updateResourceDisplay', { + // gameSystemsAvailable: !!(this.game && this.game.systems), + // playerSystemAvailable: !!(this.game && this.game.systems && this.game.systems.player), + // economySystemAvailable: !!(this.game && this.game.systems && this.game.systems.economy), + // playerStatsAvailable: !!player.stats, + // playerAttributesAvailable: !!player.attributes, + // playerShipAvailable: !!player.ship + // }); let elementsUpdated = 0; let elementsNotFound = 0; @@ -1522,6 +1674,7 @@ class UIManager { if (playerLevelElement) { const oldLevel = playerLevelElement.textContent; const playerLevel = player.stats.level || 1; + // console.log('[UI MANAGER] Updating player level:', { oldLevel, newLevel: playerLevel, playerStats: player.stats }); playerLevelElement.textContent = `Lv. ${playerLevel}`; elementsUpdated++; @@ -1532,7 +1685,7 @@ class UIManager { }); } else { elementsNotFound++; - if (debugLogger) debugLogger.log('Player level element not found'); + // if (debugLogger) debugLogger.log('Player level element not found'); } // Update ship level with safety checks @@ -1559,6 +1712,7 @@ class UIManager { if (creditsElement) { const oldCredits = creditsElement.textContent; const creditsAmount = economy.credits || 0; + // console.log('[UI MANAGER] Credits debug - economy.credits:', economy.credits, 'creditsAmount:', creditsAmount); creditsElement.textContent = this.game.formatNumber(creditsAmount); elementsUpdated++; @@ -1578,6 +1732,7 @@ class UIManager { if (gemsElement) { const oldGems = gemsElement.textContent; const gemsAmount = economy.gems || 0; + // console.log('[UI MANAGER] Gems debug - economy.gems:', economy.gems, 'gemsAmount:', gemsAmount); gemsElement.textContent = this.game.formatNumber(gemsAmount); elementsUpdated++; @@ -1596,9 +1751,9 @@ class UIManager { const energyElement = document.getElementById('energy'); if (energyElement) { const oldEnergy = energyElement.textContent; - // Ensure we're using the correct energy values, not credits - const currentEnergy = Math.floor(player.attributes.energy || 0); - const maxEnergy = Math.floor(player.attributes.maxEnergy || 100); + const currentEnergy = player.attributes.currentEnergy || player.attributes.energy || 0; + const maxEnergy = player.attributes.maxEnergy || 100; + // console.log('[UI MANAGER] Energy debug - player.attributes.currentEnergy:', player.attributes.currentEnergy, 'player.attributes.maxEnergy:', player.attributes.maxEnergy); energyElement.textContent = `${currentEnergy}/${maxEnergy}`; elementsUpdated++; @@ -1615,6 +1770,65 @@ class UIManager { if (debugLogger) debugLogger.log('Energy element not found'); } + // Update play time (keep this here as it's part of the core resource display) + const playTimeElement = document.getElementById('playTime'); + if (playTimeElement) { + const playTimeMs = player.stats.playTime || 0; + playTimeElement.textContent = this.formatPlayTime(playTimeMs); + elementsUpdated++; + } + + // Update player stats panel + const playerLevelDisplayElement = document.getElementById('playerLevelDisplay'); + if (playerLevelDisplayElement) { + playerLevelDisplayElement.textContent = player.stats.level || 1; + elementsUpdated++; + } + + const playerXPElement = document.getElementById('playerXP'); + if (playerXPElement) { + const currentXP = player.stats.experience || 0; + const xpToNext = player.stats.experienceToNext || 100; + playerXPElement.textContent = `${currentXP} / ${xpToNext}`; + elementsUpdated++; + } + + const skillPointsElement = document.getElementById('skillPoints'); + if (skillPointsElement) { + skillPointsElement.textContent = player.stats.skillPoints || 0; + elementsUpdated++; + } + + const totalXPElement = document.getElementById('totalXP'); + if (totalXPElement) { + totalXPElement.textContent = this.game.formatNumber(player.stats.totalXP || 0); + elementsUpdated++; + } + + const questsCompletedElement = document.getElementById('questsCompleted'); + if (questsCompletedElement) { + // Use player stats from server first, then quest system stats as fallback + const questsCompleted = player.stats.questsCompleted || + (this.game.systems.questSystem?.stats?.questsCompleted) || 0; + questsCompletedElement.textContent = questsCompleted; + elementsUpdated++; + } + + const lastLoginElement = document.getElementById('lastLogin'); + if (lastLoginElement) { + const lastLogin = player.stats.lastLogin; + if (lastLogin) { + const loginDate = new Date(lastLogin); + lastLoginElement.textContent = loginDate.toLocaleDateString() + ' ' + loginDate.toLocaleTimeString(); + } else { + lastLoginElement.textContent = 'Never'; + } + elementsUpdated++; + } + + // Update all player stats + this.updatePlayerStats(); + if (debugLogger) debugLogger.endStep('UIManager.updateResourceDisplay', { elementsUpdated: elementsUpdated, elementsNotFound: elementsNotFound, @@ -1633,29 +1847,132 @@ class UIManager { } } + formatPlayTime(milliseconds) { + // Format play time showing only the two most relevant units + const totalSeconds = Math.floor(milliseconds / 1000); + + if (totalSeconds < 60) { + // Less than 1 minute: show seconds only + return `${totalSeconds}s`; + } else if (totalSeconds < 3600) { + // Less than 1 hour: show minutes and seconds + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + return `${minutes}m ${seconds}s`; + } else if (totalSeconds < 86400) { + // Less than 1 day: show hours and minutes + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + return `${hours}h ${minutes}m`; + } else { + // 1 day or more: show days and hours + const days = Math.floor(totalSeconds / 86400); + const hours = Math.floor((totalSeconds % 86400) / 3600); + return `${days}d ${hours}h`; + } + } + + updatePlayerStats() { + // Update just the player stats panel (excluding those handled in updateResourceDisplay) + if (!this.game || !this.game.systems || !this.game.systems.player) { + return; + } + + const player = this.game.systems.player; + let elementsUpdated = 0; + + // Update player stats panel + const playerLevelDisplayElement = document.getElementById('playerLevelDisplay'); + if (playerLevelDisplayElement) { + playerLevelDisplayElement.textContent = player.stats.level || 1; + elementsUpdated++; + } + + const playerXPElement = document.getElementById('playerXP'); + if (playerXPElement) { + const currentXP = player.stats.experience || 0; + const xpToNext = player.stats.experienceToNext || 100; + playerXPElement.textContent = `${currentXP} / ${xpToNext}`; + elementsUpdated++; + } + + const skillPointsElement = document.getElementById('skillPoints'); + if (skillPointsElement) { + skillPointsElement.textContent = player.stats.skillPoints || 0; + elementsUpdated++; + } + + const totalXPElement = document.getElementById('totalXP'); + if (totalXPElement) { + totalXPElement.textContent = this.game.formatNumber(player.stats.totalXP || 0); + elementsUpdated++; + } + + const questsCompletedElement = document.getElementById('questsCompleted'); + if (questsCompletedElement) { + // Use player stats from server first, then quest system stats as fallback + const questsCompleted = player.stats.questsCompleted || + (this.game.systems.questSystem?.stats?.questsCompleted) || 0; + questsCompletedElement.textContent = questsCompleted; + elementsUpdated++; + } + + const lastLoginElement = document.getElementById('lastLogin'); + if (lastLoginElement) { + const lastLogin = player.stats.lastLogin; + if (lastLogin) { + const loginDate = new Date(lastLogin); + lastLoginElement.textContent = loginDate.toLocaleDateString() + ' ' + loginDate.toLocaleTimeString(); + } else { + lastLoginElement.textContent = 'Never'; + } + elementsUpdated++; + } + + // Update stats moved from Idle Progress card + const totalKillsElement = document.getElementById('totalKills'); + if (totalKillsElement) { + totalKillsElement.textContent = this.game.formatNumber(player.stats.totalKills || 0); + elementsUpdated++; + } + + const dungeonsClearedElement = document.getElementById('dungeonsCleared'); + if (dungeonsClearedElement) { + dungeonsClearedElement.textContent = player.stats.dungeonsCleared || 0; + elementsUpdated++; + } + + // DISABLED: Reduce console spam for quest debugging + // console.log(`[UI MANAGER] Player stats updated: ${elementsUpdated} elements`); + } + updateUI() { - const debugLogger = window.debugLogger; + // const debugLogger = window.debugLogger; - if (debugLogger) debugLogger.startStep('UIManager.updateUI', { - currentTab: this.currentTab, - modalOpen: this.modalOpen - }); + // if (debugLogger) debugLogger.startStep('UIManager.updateUI', { + // currentTab: this.currentTab, + // modalOpen: this.modalOpen + // }); - // Update resource display - if (debugLogger) debugLogger.logStep('Updating resource display'); - this.updateResourceDisplay(); + // Update resource display only if in multiplayer mode or game is actively running + if (this.shouldUpdateUI()) { + // if (debugLogger) debugLogger.logStep('Updating resource display'); + this.updateResourceDisplay(); + } - // Update tab content - if (debugLogger) debugLogger.logStep('Updating tab content', { - currentTab: this.currentTab - }); - this.updateTabContent(this.currentTab); + // Update tab content only if in multiplayer mode or game is actively running + if (this.shouldUpdateUI()) { + // if (debugLogger) debugLogger.logStep('Updating tab content', { + // currentTab: this.currentTab + // }); + this.updateTabContent(this.currentTab); + } - if (debugLogger) debugLogger.endStep('UIManager.updateUI', { - resourceDisplayUpdated: true, - tabContentUpdated: true, - currentTab: this.currentTab - }); + // if (debugLogger) debugLogger.endStep('UIManager.updateUI', { + // resourceDisplayUpdated: true, + // tabContentUpdated: true, + // currentTab: this.currentTab + // }); } // Menus @@ -1807,8 +2124,8 @@ class UIManager { } else { } - // Reset economy - if (this.game.systems.economy) { + // Reset economy - only reset if not in multiplayer mode + if (this.game.systems.economy && !window.smartSaveManager?.isMultiplayer) { this.game.systems.economy.credits = 1000; this.game.systems.economy.gems = 10; } @@ -1927,8 +2244,8 @@ class UIManager { }; } - // Clear economy data - if (this.game.systems.economy) { + // Clear economy data - only reset if not in multiplayer mode + if (this.game.systems.economy && !window.smartSaveManager?.isMultiplayer) { this.game.systems.economy.credits = 1000; this.game.systems.economy.gems = 10; } diff --git a/Client/styles/main.css b/Client/styles/main.css index 2b6e807..2da022d 100644 --- a/Client/styles/main.css +++ b/Client/styles/main.css @@ -1196,6 +1196,13 @@ body { .player-info { display: flex; flex-direction: column; + gap: 0.2rem; +} + +.player-info > div { + display: flex; + align-items: center; + gap: 0.3rem; } .player-name { @@ -1203,6 +1210,18 @@ body { color: var(--text-primary); } +.player-title { + font-weight: 500; + color: var(--text-secondary); + font-size: 0.9rem; +} + +.player-username { + font-weight: 600; + color: var(--accent-color); + font-size: 0.85rem; +} + .player-level { font-size: 0.8rem; color: var(--primary-color); @@ -1381,6 +1400,20 @@ body { gap: 0.5rem; } +.player-stats-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.5rem; + max-height: 400px; + overflow-y: auto; +} + +@media (max-width: 768px) { + .player-stats-grid { + grid-template-columns: 1fr; + } +} + .stat { display: flex; justify-content: space-between;