diff --git a/Client/js/GameInitializer.js b/Client/js/GameInitializer.js index c4e6517..763cf67 100644 --- a/Client/js/GameInitializer.js +++ b/Client/js/GameInitializer.js @@ -155,13 +155,37 @@ class GameInitializer { }); this.socket.on('onlineIdleRewards', (data) => { - console.log('[GAME INITIALIZER] Online idle rewards received:', data); + if (data.credits > 0 || data.experience > 0) { + console.log('[GAME INITIALIZER] Online idle rewards received:', data); + } this.onOnlineIdleRewards(data); }); + // Quest completion events + this.socket.on('quest_completed', (data) => { + console.log('[GAME INITIALIZER] Quest completed:', data); + this.onQuestCompleted(data); + }); + + // Player stat update events + this.socket.on('player_stat_update', (data) => { + console.log('[GAME INITIALIZER] Player stat update:', data); + this.onPlayerStatUpdate(data); + }); + + // Quest data events + this.socket.on('quests_data', (data) => { + console.log('[GAME INITIALIZER] Quest data received:', data); + this.onQuestsData(data); + }); + // PlayTime events this.socket.on('playTimeUpdated', (data) => { - console.log('[GAME INITIALIZER] PlayTime updated from server:', data); + // Only log playtime updates every minute (600,000 ms) to reduce spam + if (!this.lastPlayTimeLog || Date.now() - this.lastPlayTimeLog > 60000) { + console.log('[GAME INITIALIZER] PlayTime updated:', `${Math.floor(data.playTime / 3600000)}h ${Math.floor((data.playTime % 3600000) / 60000)}m`); + this.lastPlayTimeLog = Date.now(); + } this.onPlayTimeUpdated(data); }); @@ -342,6 +366,12 @@ class GameInitializer { window.game.loadServerPlayerData(this.serverPlayerData); console.log('[GAME INITIALIZER] Server player data applied to GameEngine'); + // CRITICAL: Force immediate economy sync + if (window.game.systems && window.game.systems.economy && window.game.systems.economy.syncWithServerData) { + console.log('[GAME INITIALIZER] Forcing immediate economy sync with server data'); + window.game.systems.economy.syncWithServerData(this.serverPlayerData); + } + // 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'); @@ -730,65 +760,65 @@ class GameInitializer { 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); + // Try to get from localStorage as fallback + const storedUser = localStorage.getItem('currentUser'); + if (storedUser) { + try { + const user = JSON.parse(storedUser); + const username = user.username || 'anonymous'; + } catch (e) { + console.warn('[GAME INITIALIZER] Failed to parse stored user, using default'); } } 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) { + // Only log if there are actual rewards to process + if (data.credits > 0 || data.experience > 0 || data.energy > 0) { + console.log('[GAME INITIALIZER] Processing online idle rewards:', data); + } + // Update player balance with online idle rewards if (data.credits > 0) { - // The server already updated the balance, just show notification + // Update the Economy system's local credits + if (window.game.systems.economy) { + window.game.systems.economy.credits += data.credits; + + // Update UI immediately + if (window.game.ui) { + window.game.ui.updatePlayerStats(); + console.log('[GAME INITIALIZER] UI updated with new credits'); + } + } else { + console.warn('[GAME INITIALIZER] Economy system not available for credit update'); + } + + // Show notification window.game.showNotification(`+${data.credits} credits (online idle)`, 'success', 2000); } + + // Update experience if provided + if (data.experience > 0 && window.game.systems.player) { + window.game.systems.player.addExperience(data.experience); + console.log('[GAME INITIALIZER] Added idle experience:', data.experience); + } + } else { + console.warn('[GAME INITIALIZER] Game systems not available for idle rewards'); } } onPlayTimeUpdated(data) { - console.log('[GAME INITIALIZER] PlayTime updated from server:', data); - + // PlayTime updates are handled in the socket listener with throttled logging 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(); } @@ -807,6 +837,72 @@ class GameInitializer { economy.gems = data.newBalance; } + // Add item to inventory if it's a consumable or material + if (data.item && (data.item.type === 'consumable' || data.item.type === 'material')) { + if (window.game.systems.inventory) { + console.log('[GAME INITIALIZER] Adding item to inventory:', data.item); + window.game.systems.inventory.addItem(data.item, data.quantity || 1); + window.game.showNotification(`${data.item.name} added to inventory!`, 'success', 3000); + } + } + + // Update owned ships if it's a ship + if (data.item && data.item.type === 'ship') { + if (!economy.ownedShips) economy.ownedShips = []; + if (!economy.ownedShips.includes(data.item.id)) { + economy.ownedShips.push(data.item.id); + console.log('[GAME INITIALIZER] Ship added to owned ships:', data.item.id); + + // Add ship to BaseSystem ship gallery + if (window.game.systems.baseSystem) { + console.log('[GAME INITIALIZER] BaseSystem available, adding ship to gallery'); + const shipData = { + id: data.item.id, + name: data.item.name, + class: data.item.name.replace(/\s+/g, '_').toLowerCase(), // Generate class from name + level: 1, + stats: data.item.stats || {}, + texture: data.item.texturePath || `assets/textures/ships/${data.item.id}.png`, + isCurrent: false, + rarity: data.item.rarity || 'common' + }; + + console.log('[GAME INITIALIZER] Ship data prepared:', shipData); + + // Initialize ship gallery if needed + if (!window.game.systems.baseSystem.purchasedShips) { + console.log('[GAME INITIALIZER] Initializing ship gallery'); + window.game.systems.baseSystem.initializeShipGallery(); + } + + // Check if ship already exists + const existingShip = window.game.systems.baseSystem.purchasedShips.find(s => s.id === shipData.id); + if (!existingShip) { + // Add ship to gallery + window.game.systems.baseSystem.purchasedShips.push(shipData); + console.log('[GAME INITIALIZER] Ship added to gallery. Total ships:', window.game.systems.baseSystem.purchasedShips.length); + + // Update the ship gallery UI + window.game.systems.baseSystem.updateShipGallery(); + console.log('[GAME INITIALIZER] Ship gallery UI updated'); + } else { + console.log('[GAME INITIALIZER] Ship already exists in gallery:', shipData.id); + } + } else { + console.error('[GAME INITIALIZER] BaseSystem not available for ship gallery'); + } + } + } + + // Update owned cosmetics if it's a cosmetic + if (data.item && data.item.type === 'cosmetic') { + if (!economy.ownedCosmetics) economy.ownedCosmetics = []; + if (!economy.ownedCosmetics.includes(data.item.id)) { + economy.ownedCosmetics.push(data.item.id); + console.log('[GAME INITIALIZER] Cosmetic added to owned cosmetics:', data.item.id); + } + } + // Request fresh economy data from server to ensure sync if (economy.requestEconomyData) { setTimeout(() => { @@ -814,9 +910,21 @@ class GameInitializer { }, 500); } + // Also request economy data immediately to prevent reset + if (economy.requestEconomyData) { + economy.requestEconomyData(); + } + // Update UI economy.updateUI(); + // Update inventory UI if item was added + if (data.item && (data.item.type === 'consumable' || data.item.type === 'material')) { + if (window.game.systems.inventory) { + window.game.systems.inventory.updateUI(); + } + } + // Show success message window.game.showNotification(`Purchased ${data.item.name}!`, 'success', 3000); } @@ -828,8 +936,28 @@ class GameInitializer { onShopItemsReceived(data) { if (data.success && window.game && window.game.systems && window.game.systems.itemSystem) { + console.log('[GAME INITIALIZER] Processing shop items data structure:', Object.keys(data)); + + // Handle both old (data.items) and new (data.shopItems) structures + let itemsToProcess = null; + + if (data.shopItems && typeof data.shopItems === 'object') { + // New structure: categorized items + console.log('[GAME INITIALIZER] Using new shop structure'); + itemsToProcess = Object.values(data.shopItems).flat(); + console.log('[GAME INITIALIZER] Flattened', itemsToProcess.length, 'items from categories'); + } else if (data.items && Array.isArray(data.items)) { + // Old structure: flat array + console.log('[GAME INITIALIZER] Using old shop structure'); + itemsToProcess = data.items; + console.log('[GAME INITIALIZER] Processing', itemsToProcess.length, 'items from flat array'); + } else { + console.warn('[GAME INITIALIZER] Invalid shop items structure:', data); + return; + } + // Update ItemSystem with server data - window.game.systems.itemSystem.processServerItems(data.items); + window.game.systems.itemSystem.processServerItems(itemsToProcess); console.log('[GAME INITIALIZER] ItemSystem updated with server shop items'); // Update Economy shop UI @@ -872,6 +1000,101 @@ class GameInitializer { this.currentUser = null; this.serverPlayerData = null; } + + onQuestCompleted(data) { + console.log('[GAME INITIALIZER] Processing quest completion:', data); + + // Show quest completion notification + if (window.game && window.game.showNotification) { + const questName = data.questId.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); + window.game.showNotification(`Quest Completed: ${questName}! 🎯`, 'success', 5000); + } + + // Award quest rewards + if (data.rewards) { + if (data.rewards.experience > 0 && window.game.systems.player) { + window.game.systems.player.addExperience(data.rewards.experience); + console.log('[GAME INITIALIZER] Awarded quest experience:', data.rewards.experience); + } + + if (data.rewards.credits > 0 && window.game.systems.economy) { + window.game.systems.economy.credits += data.rewards.credits; + console.log('[GAME INITIALIZER] Awarded quest credits:', data.rewards.credits); + } + } + + // Update quest UI if quest system exists + if (window.game && window.game.systems && window.game.systems.questSystem) { + window.game.systems.questSystem.completeQuest(data.questId, data.rewards); + + // Force fetch fresh quest data from server (Fix #1 - Corrected) + console.log('[GAME INITIALIZER] Requesting fresh quest data from server after completion'); + + // Request fresh quest data from server + if (window.gameInitializer && window.gameInitializer.socket) { + window.gameInitializer.socket.emit('get_quests'); + } + } + + // Force quest UI refresh for server-driven quests + if (typeof updateQuestDisplay === 'function') { + updateQuestDisplay(); + } + + // Update UI + if (window.game && window.game.ui) { + window.game.ui.updatePlayerStats(); + } + } + + onPlayerStatUpdate(data) { + console.log('[GAME INITIALIZER] Processing player stat update:', data); + + if (window.game && window.game.systems && window.game.systems.player && window.game.systems.player.stats) { + // Update the player stat + window.game.systems.player.stats[data.stat] = data.value; + console.log('[GAME INITIALIZER] Updated player stat:', data.stat, '=', data.value); + + // Update UI to reflect the change + if (window.game.ui) { + window.game.ui.updatePlayerStats(); + } + + // Update quest system to check for quest progress + if (window.game.systems.questSystem) { + window.game.systems.questSystem.checkQuestAvailability(); + } + } + } + + onQuestsData(data) { + console.log('[GAME INITIALIZER] Processing quest data from server:', data); + console.log('[GAME INITIALIZER] Quest data keys:', Object.keys(data || {})); + console.log('[GAME INITIALIZER] Main quests count:', (data?.mainQuests || []).length); + console.log('[GAME INITIALIZER] Daily quests count:', (data?.dailyQuests || []).length); + console.log('[GAME INITIALIZER] Weekly quests count:', (data?.weeklyQuests || []).length); + + if (window.game && window.game.systems && window.game.systems.questSystem) { + // Load quest data into the quest system + if (window.game.systems.questSystem.loadServerQuests) { + console.log('[GAME INITIALIZER] Calling loadServerQuests with data'); + window.game.systems.questSystem.loadServerQuests(data); + console.log('[GAME INITIALIZER] Loaded quest data into quest system'); + } else { + console.log('[GAME INITIALIZER] loadServerQuests method not found'); + } + + // Update quest UI + if (typeof updateQuestDisplay === 'function') { + console.log('[GAME INITIALIZER] Calling updateQuestDisplay'); + updateQuestDisplay(); + } else { + console.log('[GAME INITIALIZER] updateQuestDisplay function not found'); + } + } else { + console.log('[GAME INITIALIZER] Quest system not available'); + } + } } // Create global instance diff --git a/Client/js/core/Economy.js b/Client/js/core/Economy.js index 963f784..9c97d92 100644 --- a/Client/js/core/Economy.js +++ b/Client/js/core/Economy.js @@ -8,28 +8,25 @@ class Economy { constructor(gameEngine) { this.game = gameEngine; - // 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; + // Currency - don't override in multiplayer mode, will be set by server data + if (window.smartSaveManager?.isMultiplayer) { + this.credits = 0; // Will be updated by server + this.gems = 0; // Will be updated by server + this.premiumCurrency = 0; // Will be updated by server + } else { + this.credits = 10000; // Starting credits for singleplayer + this.gems = 50; // Starting premium currency + this.premiumCurrency = 0; // Additional premium currency + } - // Player resources - this.credits = preservedCredits; - this.gems = preservedGems; - this.premiumCurrency = 0; - - // Transaction tracking - this.transactionHistory = []; - this.transactions = []; // Add missing transactions array - - // Owned cosmetics - this.ownedCosmetics = []; // Add missing owned cosmetics array + // Transaction history + this.transactions = []; // Shop categories this.shopCategories = { ships: 'Ships', - weapons: 'Weapons', - modules: 'Modules', + weapons: 'Weapons', + armors: 'Armors', cosmetics: 'Cosmetics', consumables: 'Consumables', materials: 'Materials' @@ -40,6 +37,7 @@ class Economy { this.shopRefreshInterval = null; // Timer for 2-hour refresh this.shopHeartbeatInterval = null; // Timer for live countdown updates this.lastShopRefresh = null; // Timestamp of last refresh + this.currentShopData = null; // Current shop data from server this.SHOP_REFRESH_INTERVAL = 2 * 60 * 60 * 1000; // 2 hours in milliseconds this.MAX_ITEMS_PER_CATEGORY = 8; this.categoryPurchaseLimits = {}; // Track purchases per category per refresh @@ -50,18 +48,92 @@ class Economy { // Owned cosmetics this.ownedCosmetics = []; - console.log('[ECONOMY] Economy system initialized with server-side ItemSystem'); - console.log('[ECONOMY] Preserved values - Credits:', this.credits, 'Gems:', this.gems); + // Owned ships + this.ownedShips = []; - // Set up socket listeners for economy sync - this.setupSocketListeners(); + console.log('[ECONOMY] Economy system initialized'); - // Request fresh economy data after a short delay to ensure sync - if (window.smartSaveManager?.isMultiplayer) { - setTimeout(() => { - this.requestEconomyData(); - }, 1000); - } + // Initialize global purchase function + Economy.initGlobalPurchaseFunction(); + } + + // Create global purchase function for shop buttons + static initGlobalPurchaseFunction() { + window.purchaseShopItem = function(itemId) { + console.log('[GLOBAL] Purchase shop item called:', itemId); + + if (window.game && window.game.systems && window.game.systems.economy) { + window.game.systems.economy.purchaseItem(itemId, 1); + } else { + console.error('[GLOBAL] Economy system not available for purchase'); + } + }; + + // Add test function for idle system + window.testIdleRewards = function() { + console.log('[GLOBAL] Testing idle rewards...'); + + if (window.game && window.game.socket) { + window.game.socket.emit('testIdleRewards', {}); + + // Listen for response + window.game.socket.once('testIdleRewards', (data) => { + console.log('[GLOBAL] Test idle rewards response:', data); + }); + } else { + console.error('[GLOBAL] No socket available for idle test'); + } + }; + + // Add socket event monitor + window.monitorSocketEvents = function() { + if (window.game && window.game.socket) { + console.log('[GLOBAL] Monitoring socket events...'); + + // Monitor all incoming events + const originalOn = window.game.socket.on; + window.game.socket.on = function(event, callback) { + const wrappedCallback = function(data) { + if (event === 'onlineIdleRewards' || event === 'economy_data') { + console.log('[SOCKET MONITOR] Received event:', event, data); + } + return callback(data); + }; + return originalOn.call(this, event, wrappedCallback); + }; + + console.log('[GLOBAL] Socket event monitoring enabled'); + } else { + console.error('[GLOBAL] No socket available for monitoring'); + } + }; + + // Add function to give player energy for testing dungeons + window.addEnergy = function(amount = 50) { + if (window.game && window.game.systems && window.game.systems.player) { + const player = window.game.systems.player; + const oldEnergy = player.attributes.energy || 0; + player.attributes.energy = Math.min(oldEnergy + amount, player.attributes.maxEnergy || 100); + console.log('[GLOBAL] Added energy:', oldEnergy, '->', player.attributes.energy); + + // Update UI + if (player.updateUI) { + player.updateUI(); + } + + // Update dungeon UI if available + if (window.game.systems.dungeonSystem && window.game.systems.dungeonSystem.updateUI) { + window.game.systems.dungeonSystem.updateUI(); + } + + return player.attributes.energy; + } else { + console.error('[GLOBAL] Player system not available'); + return 0; + } + }; + + console.log('[GLOBAL] Global functions initialized - purchaseShopItem() and testIdleRewards() available'); } /** @@ -76,9 +148,15 @@ class Economy { // Listen for economy data updates from server this.game.socket.on('economy_data', (data) => { console.log('[ECONOMY] Received economy data from server:', data); + console.log('[ECONOMY] Current credits before update:', this.credits); + console.log('[ECONOMY] Current gems before update:', this.gems); + this.credits = data.credits || 0; this.gems = data.gems || 0; + console.log('[ECONOMY] Updated credits:', this.credits); + console.log('[ECONOMY] Updated gems:', this.gems); + // Update UI immediately if (this.game.ui) { this.game.ui.updatePlayerStats(); @@ -86,6 +164,18 @@ class Economy { console.log('[ECONOMY] Economy synced - Credits:', this.credits, 'Gems:', this.gems); }); + + // Note: onlineIdleRewards is handled by GameInitializer to avoid duplicate event handling + + // Listen for play time updates from server + this.game.socket.on('playTimeUpdated', (data) => { + console.log('[ECONOMY] Received play time update from server:', data); + + // Update player stats if available + if (this.game.systems.player && this.game.systems.player.stats) { + this.game.systems.player.stats.playTime = data.playTime; + } + }); } /** @@ -314,6 +404,33 @@ class Economy { player.ownedShips.push(ship.id); } + // Add ship to BaseSystem ship gallery (singleplayer) + if (this.game.systems.baseSystem) { + const shipData = { + id: ship.id, + name: ship.name, + class: ship.name.replace(/\s+/g, '_').toLowerCase(), // Generate class from name + level: 1, + stats: ship.stats || {}, + texture: ship.texture || `assets/textures/ships/${ship.id}.png`, + isCurrent: false, + rarity: ship.rarity || 'common' + }; + + // Initialize ship gallery if needed + if (!this.game.systems.baseSystem.purchasedShips) { + this.game.systems.baseSystem.initializeShipGallery(); + } + + // Add ship to gallery + this.game.systems.baseSystem.purchasedShips.push(shipData); + + // Update the ship gallery UI + this.game.systems.baseSystem.updateShipGallery(); + + console.log('[ECONOMY] Ship added to BaseSystem gallery (singleplayer):', shipData.name); + } + if (debugLogger) debugLogger.logStep('Ship purchase completed', { shipId: ship.id, shipName: ship.name, @@ -472,8 +589,39 @@ class Economy { } } + // Manual sync with server data - call this to force update + syncWithServerData(serverPlayerData) { + console.log('[ECONOMY] Manual sync with server data:', { + serverCredits: serverPlayerData?.stats?.credits, + serverGems: serverPlayerData?.stats?.gems, + currentCredits: this.credits, + currentGems: this.gems + }); + + if (serverPlayerData?.stats?.credits !== undefined) { + this.credits = serverPlayerData.stats.credits; + console.log('[ECONOMY] Updated credits from server:', this.credits); + } + + if (serverPlayerData?.stats?.gems !== undefined) { + this.gems = serverPlayerData.stats.gems; + console.log('[ECONOMY] Updated gems from server:', this.gems); + } + + // Update UI after sync + this.updateUI(); + } + // UI updates updateUI() { + // Debug logging to track current values + console.log('[ECONOMY] updateUI called - Current values:', { + credits: this.credits, + gems: this.gems, + gameSystemsAvailable: !!(this.game && this.game.systems), + uiSystemAvailable: !!(this.game && this.game.systems && this.game.systems.ui) + }); + // Update resource display if (this.game.systems.ui) { this.game.systems.ui.updateResourceDisplay(); @@ -487,28 +635,33 @@ class Economy { 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; - } + if (this.game.multiplayerMode && this.game.itemSystem && this.game.itemSystem.catalog) { + console.log('[ECONOMY] Multiplayer mode:', true); + console.log('[ECONOMY] ItemSystem available:', !!this.game.itemSystem); + console.log('[ECONOMY] ItemSystem catalog:', !!this.game.itemSystem.catalog); - // 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); + const shopItems = this.game.itemSystem.catalog; + console.log('[ECONOMY] Got categorized shop items:', Object.keys(shopItems)); + + // Get current active category + const activeCategory = this.game.itemSystem.activeCategory || 'ships'; + console.log('[ECONOMY] Active shop category:', activeCategory); + + // Filter items for active category + const categoryItems = shopItems[activeCategory] || []; + console.log('[ECONOMY] Using new shop structure - found', categoryItems.length, 'categories'); + console.log('[ECONOMY] Filtered items for category', activeCategory, ':', categoryItems.length, 'items'); + console.log('[ECONOMY] Item types in category:', categoryItems.map(item => item.type)); + + this.renderShopItems(categoryItems); } 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); + + // Convert to categorized structure for consistency + const categorizedItems = this.randomShopItems || {}; + this.renderShopItems(categorizedItems); } } @@ -518,25 +671,27 @@ class Economy { 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' - }; + // Handle new shop data structure (items by category) or old structure (flat array) + let categoryItems = []; - const targetItemType = categoryTypeMap[activeCategory] || activeCategory; - console.log('[ECONOMY] Mapped category', activeCategory, 'to item type', targetItemType); + if (items && typeof items === 'object' && !Array.isArray(items)) { + // New structure: { ships: [...], weapons: [...], ... } + categoryItems = items[activeCategory] || []; + console.log('[ECONOMY] Using new shop structure - found', Object.keys(items).length, 'categories'); + } else if (Array.isArray(items)) { + // Old structure: flat array of items + const targetItemType = activeCategory.slice(0, -1); // Remove 's' from 'ships', 'weapons', etc. + categoryItems = items.filter(item => item.type === targetItemType); + console.log('[ECONOMY] Using old shop structure - filtered', items.length, 'total items'); + } else { + console.warn('[ECONOMY] Invalid shop items structure:', typeof items); + shopItemsElement.innerHTML = '

No items available

'; + return; + } - const categoryItems = items.filter(item => item.type === targetItemType); - console.log('[ECONOMY] Filtered items for category', activeCategory, '(type:', targetItemType, ') :', categoryItems.length, 'items'); + console.log('[ECONOMY] Filtered items for category', activeCategory, ':', categoryItems.length, 'items'); + console.log('[ECONOMY] Item types in category:', categoryItems.map(item => item.type)); if (categoryItems.length === 0) { shopItemsElement.innerHTML = '

No items available in this category

'; @@ -573,6 +728,7 @@ class Economy { `; }).join(''); + + // Add event listeners to purchase buttons + shopItemsElement.querySelectorAll('.shop-item-purchase-btn').forEach(button => { + button.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + + const itemId = button.getAttribute('data-item-id'); + if (itemId && !button.disabled) { + console.log('[ECONOMY] Purchase button clicked for item:', itemId); + this.purchaseItem(itemId, 1); + } + }); + }); } formatPrice(item) { diff --git a/Client/js/core/GameEngine.js b/Client/js/core/GameEngine.js index 713040a..7be3764 100644 --- a/Client/js/core/GameEngine.js +++ b/Client/js/core/GameEngine.js @@ -184,7 +184,10 @@ class GameEngine extends EventTarget { console.error('[GAME ENGINE] Failed to initialize DungeonSystem:', error); }); } - // REMOVED: QuestSystem should be server-driven only + if (typeof QuestSystem !== 'undefined') { + this.systems.questSystem = new QuestSystem(this); + console.log('[GAME ENGINE] QuestSystem created'); + } if (typeof CraftingSystem !== 'undefined') { this.systems.crafting = new CraftingSystem(this); } @@ -426,7 +429,22 @@ class GameEngine extends EventTarget { console.log('[GAME ENGINE] Auto-saving game...'); try { - await this.save(); + // In multiplayer mode, save to server + if (window.smartSaveManager?.isMultiplayer) { + console.log('[GAME ENGINE] Auto-saving to server...'); + if (this.socket) { + this.socket.emit('saveGameData', { + timestamp: Date.now(), + gameTime: this.gameTime + }); + } else { + console.warn('[GAME ENGINE] No socket available for server save'); + } + } else { + // Singleplayer mode - local save (not implemented yet) + console.log('[GAME ENGINE] Local auto-save not implemented'); + } + this.showNotification('Game auto-saved', 'info', 2000); console.log('[GAME ENGINE] Auto-save completed successfully'); @@ -644,6 +662,12 @@ class GameEngine extends EventTarget { console.log('[GAME ENGINE] Applied gems from server:', playerData.stats.gems); } + // Force manual sync to ensure economy is updated + if (this.systems.economy && this.systems.economy.syncWithServerData) { + console.log('[GAME ENGINE] Forcing manual economy sync'); + this.systems.economy.syncWithServerData(playerData); + } + // Request fresh economy data from server to ensure sync if (this.systems.economy && this.systems.economy.requestEconomyData) { setTimeout(() => { @@ -662,6 +686,25 @@ class GameEngine extends EventTarget { console.log('[GAME ENGINE] Applied max energy from server:', playerData.stats.maxEnergy); } + // Ensure player has minimum energy for dungeon access + if (this.systems.player.attributes) { + // Check if energy is missing or too low + if (!this.systems.player.attributes.energy || this.systems.player.attributes.energy < 10) { + const oldEnergy = this.systems.player.attributes.energy; + this.systems.player.attributes.energy = 100; + this.systems.player.attributes.maxEnergy = Math.max(this.systems.player.attributes.maxEnergy || 0, 100); + console.log('[GAME ENGINE] Set minimum energy for dungeon access:', oldEnergy, '->', this.systems.player.attributes.energy); + } + + // Also ensure currentEnergy is set if it exists + if (this.systems.player.attributes.currentEnergy !== undefined) { + if (this.systems.player.attributes.currentEnergy < 10) { + this.systems.player.attributes.currentEnergy = 100; + console.log('[GAME ENGINE] Set minimum currentEnergy for dungeon access'); + } + } + } + console.log('[GAME ENGINE] Final player stats after application:', this.systems.player.stats); } else { console.log('[GAME ENGINE] Missing player stats or player system'); diff --git a/Client/js/systems/DungeonSystem.js b/Client/js/systems/DungeonSystem.js index 6f4bddd..ada1e64 100644 --- a/Client/js/systems/DungeonSystem.js +++ b/Client/js/systems/DungeonSystem.js @@ -3,6 +3,69 @@ * Server-driven dungeon management client */ +// Create global function for dungeon start that's more reliable +window.startDungeon = function(dungeonId) { + console.log('[DUNGEON SYSTEM] startDungeon called with:', dungeonId); + console.log('[DUNGEON SYSTEM] Game available:', !!window.game); + console.log('[DUNGEON SYSTEM] Game systems available:', !!(window.game && window.game.systems)); + console.log('[DUNGEON SYSTEM] Dungeon system available:', !!(window.game && window.game.systems && window.game.systems.dungeonSystem)); + + if (window.game && window.game.systems && window.game.systems.dungeonSystem) { + return window.game.systems.dungeonSystem.startDungeon(dungeonId); + } + + console.warn('[DUNGEON SYSTEM] Game systems not available for dungeon start'); +}; + +// Create global function for process encounter that's more reliable +window.processEncounter = function() { + console.log('[DUNGEON SYSTEM] processEncounter called'); + + if (window.game && window.game.systems && window.game.systems.dungeonSystem) { + return window.game.systems.dungeonSystem.processEncounter(); + } + + console.warn('[DUNGEON SYSTEM] Game systems not available for process encounter'); +}; + +// Create global function for dungeon toggle that's more reliable +window.toggleDungeonSection = function(sectionId) { + // Try to use the dungeon system if available + if (window.game && window.game.systems && window.game.systems.dungeonSystem) { + return window.game.systems.dungeonSystem.toggleDungeonSection(sectionId); + } + + // Fallback: Direct DOM manipulation + const section = document.getElementById(sectionId); + const indicator = document.getElementById(`${sectionId}-indicator`); + + if (!section || !indicator) { + console.warn('[DUNGEON SYSTEM] Section or indicator not found:', sectionId); + return; + } + + const isCollapsed = section.classList.contains('collapsed'); + + if (isCollapsed) { + // Expand + section.classList.remove('collapsed'); + indicator.classList.remove('fa-chevron-right'); + indicator.classList.add('fa-chevron-down'); + } else { + // Collapse + section.classList.add('collapsed'); + indicator.classList.remove('fa-chevron-down'); + indicator.classList.add('fa-chevron-right'); + } + + // Save the state in the dungeon system if available + if (window.game && window.game.systems && window.game.systems.dungeonSystem) { + window.game.systems.dungeonSystem.collapseStates.set(sectionId, !isCollapsed); + } + + console.log(`[DUNGEON SYSTEM] Toggled section ${sectionId}: ${isCollapsed ? 'expanded' : 'collapsed'}`); +}; + class DungeonSystem { constructor(gameEngine) { this.game = gameEngine; @@ -13,8 +76,23 @@ class DungeonSystem { this.dungeonProgress = 0; this.isExploring = false; - // Server data loaded from server - this.serverDungeons = []; + // Debouncing to prevent multiple rapid clicks + this.lastProcessTime = 0; + this.processCooldown = 1000; // 1 second cooldown + + // Prevent duplicate event processing + this.lastEncounterData = null; + this.lastNextRoomData = null; + + // Store collapse states to preserve them during regeneration + this.collapseStates = new Map(); + + // Track last generation to prevent unnecessary regenerations + this.lastGenerationTime = 0; + this.generationThrottle = 500; // 500ms throttle + + // Server dungeons data + this.serverDungeons = null; this.roomTypes = {}; this.enemyTemplates = {}; @@ -39,14 +117,16 @@ class DungeonSystem { 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(); + this.forceGenerateDungeonList(); }); // Listen for room types response this.game.socket.on('room_types_data', (data) => { console.log('[DUNGEON SYSTEM] Received room types data:', data); this.roomTypes = data; - console.log('[DUNGEON SYSTEM] Loaded room types from server'); + console.log(`[DUNGEON SYSTEM] Loaded ${Object.keys(this.roomTypes).length} room types from server`); + // Update UI when room data is loaded + this.forceGenerateDungeonList(); }); // Listen for enemy templates response @@ -55,22 +135,157 @@ class DungeonSystem { 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(); + this.forceGenerateDungeonList(); }); // Listen for dungeon start response this.game.socket.on('dungeon_started', (data) => { console.log('[DUNGEON SYSTEM] Dungeon started:', data); + + // Handle error responses + if (data.success === false) { + console.error('[DUNGEON SYSTEM] Failed to start dungeon:', data.error); + if (this.game && this.game.showNotification) { + this.game.showNotification(data.error, 'error', 5000); + } + return; + } + + // Clear any existing dungeon state first + if (this.currentDungeon) { + console.warn('[DUNGEON SYSTEM] Clearing existing dungeon state before starting new one'); + this.currentDungeon = null; + this.currentRoom = null; + this.isExploring = false; + this.dungeonProgress = 0; + } + this.currentDungeon = data.instance; this.isExploring = true; this.dungeonProgress = 0; + + console.log('[DUNGEON SYSTEM] About to update UI - State:', { + currentDungeon: !!this.currentDungeon, + isExploring: this.isExploring, + dungeonProgress: this.dungeonProgress, + gameUIManager: !!this.game.systems.ui, + instanceId: this.currentDungeon?.id + }); + + // Update UI to show dungeon exploration + this.updateUI(); + + // Show notification to player + if (this.game && this.game.showNotification) { + this.game.showNotification(`Entered ${data.instance.dungeonId} dungeon!`, 'success', 3000); + } }); // Listen for encounter response this.game.socket.on('encounter_data', (data) => { + // Skip duplicate events + if (this.lastEncounterData && + this.lastEncounterData.encounterIndex === data.encounterIndex && + this.lastEncounterData.encounter?.name === data.encounter?.name) { + console.log('[DUNGEON SYSTEM] Skipping duplicate encounter data'); + return; + } + this.lastEncounterData = data; + console.log('[DUNGEON SYSTEM] Encounter received:', data); + console.log('[DUNGEON SYSTEM] Current state before update:', { + currentDungeonId: this.currentDungeon?.id, + currentProgress: this.dungeonProgress, + newEncounterIndex: data.encounterIndex, + encounterType: data.encounter?.type, + encounterName: data.encounter?.name + }); + this.currentRoom = data.encounter; - this.dungeonProgress++; + this.dungeonProgress = data.encounterIndex; // Use server data, not local increment + + // Update UI to show the new encounter + this.updateUI(); + }); + + // Listen for encounter completion (auto-combat) + this.game.socket.on('encounter_completed', (data) => { + console.log('[DUNGEON SYSTEM] Encounter completed:', data); + if (data.success) { + // Check if dungeon is complete + if (data.isComplete) { + console.log('[DUNGEON SYSTEM] Dungeon completed!'); + + // Clear all dungeon state + this.currentDungeon = null; + this.currentRoom = null; + this.dungeonProgress = 0; + this.isExploring = false; + this.lastEncounterData = null; + this.lastNextRoomData = null; + + // Show completion notification + if (this.game && this.game.showNotification) { + this.game.showNotification('Dungeon completed! 🎉', 'success', 5000); + } + + // Force UI to show dungeon list + setTimeout(() => { + this.updateUI(); + }, 1000); + } else { + this.currentRoom = data.nextEncounter; + this.dungeonProgress = data.encounterIndex; + + // Show rewards notification + if (data.rewards && (data.rewards.credits > 0 || data.rewards.experience > 0)) { + const rewardText = []; + if (data.rewards.credits > 0) rewardText.push(`${data.rewards.credits} credits`); + if (data.rewards.experience > 0) rewardText.push(`${data.rewards.experience} exp`); + + if (this.game && this.game.showNotification) { + this.game.showNotification(`Combat complete! Gained: ${rewardText.join(', ')}`, 'success', 3000); + } + } + } + + // Update UI to show the new state + this.updateUI(); + } else { + console.error('[DUNGEON SYSTEM] Error completing encounter:', data.error); + } + }); + + // Listen for next room response + this.game.socket.on('next_room_data', (data) => { + // Skip duplicate events + if (this.lastNextRoomData && + this.lastNextRoomData.encounterIndex === data.encounterIndex && + this.lastNextRoomData.encounter?.name === data.encounter?.name) { + console.log('[DUNGEON SYSTEM] Skipping duplicate next room data'); + return; + } + this.lastNextRoomData = data; + + console.log('[DUNGEON SYSTEM] Next room received:', data); + console.log('[DUNGEON SYSTEM] Current state before update:', { + currentDungeonId: this.currentDungeon?.id, + currentProgress: this.dungeonProgress, + newEncounterIndex: data.encounterIndex, + encounterType: data.encounter?.type, + encounterName: data.encounter?.name, + isComplete: data.isComplete + }); + + if (data.success) { + this.currentRoom = data.encounter; + this.dungeonProgress = data.encounterIndex; + + // Update UI to show the new room + this.updateUI(); + } else { + console.error('[DUNGEON SYSTEM] Error moving to next room:', data.error); + } }); // Listen for dungeon completion response @@ -169,7 +384,7 @@ class DungeonSystem { // Send packet to start dungeon this.game.socket.emit('start_dungeon', { dungeonId: dungeonId, - userId: this.game.player?.id || 'anonymous' + userId: this.game.systems.player?.id || 'anonymous' }); console.log('[DUNGEON SYSTEM] Dungeon start packet sent'); @@ -182,15 +397,24 @@ class DungeonSystem { } /** - * Process current room encounter using Socket.IO packets + * Process encounter in current dungeon room */ async processEncounter() { - if (!this.currentDungeon || !this.isExploring) { - console.warn('[DUNGEON SYSTEM] No active dungeon to process'); + // Debounce to prevent multiple rapid clicks + const now = Date.now(); + if (now - this.lastProcessTime < this.processCooldown) { + console.log('[DUNGEON SYSTEM] Process throttled, please wait...'); return null; } + this.lastProcessTime = now; try { + // Safety check - make sure we have an active dungeon + if (!this.currentDungeon) { + console.error('[DUNGEON SYSTEM] No active dungeon to process encounter for'); + return null; + } + console.log(`[DUNGEON SYSTEM] Processing encounter for dungeon: ${this.currentDungeon.id}`); if (!this.game.socket) { @@ -201,7 +425,7 @@ class DungeonSystem { // Send packet to process encounter this.game.socket.emit('process_encounter', { instanceId: this.currentDungeon.id, - userId: this.game.player?.id || 'anonymous' + userId: this.game.systems.player?.id || 'anonymous' }); console.log('[DUNGEON SYSTEM] Encounter process packet sent'); @@ -233,7 +457,7 @@ class DungeonSystem { // Send packet to complete dungeon this.game.socket.emit('complete_dungeon', { instanceId: this.currentDungeon.id, - userId: this.game.player?.id || 'anonymous' + userId: this.game.systems.player?.id || 'anonymous' }); console.log('[DUNGEON SYSTEM] Dungeon completion packet sent'); @@ -257,7 +481,7 @@ class DungeonSystem { // Send packet to get dungeon status this.game.socket.emit('get_dungeon_status', { - userId: this.game.player?.id || 'anonymous' + userId: this.game.systems.player?.id || 'anonymous' }); console.log('[DUNGEON SYSTEM] Dungeon status request packet sent'); @@ -270,11 +494,27 @@ class DungeonSystem { return null; } + /** + * Force generate dungeon list (bypasses throttle) + */ + forceGenerateDungeonList() { + this.lastGenerationTime = 0; // Reset throttle + this.generateDungeonList(); + } + /** * Generate dungeon list UI using server data */ generateDungeonList() { - console.log('[DUNGEON SYSTEM] Generating dungeon list UI with server data...'); + const now = Date.now(); + + // Throttle generation to prevent excessive calls + if (now - this.lastGenerationTime < this.generationThrottle) { + return; // Silently skip instead of logging + } + + this.lastGenerationTime = now; + // console.log('[DUNGEON SYSTEM] Generating dungeon list UI'); const dungeonListElement = document.getElementById('dungeonList'); if (!dungeonListElement) { @@ -300,13 +540,22 @@ class DungeonSystem { const difficultyTitle = difficulty === 'tutorial' ? 'Tutorial Dungeons' : difficulty.charAt(0).toUpperCase() + difficulty.slice(1) + ' Dungeons'; const difficultyIcon = this.getDifficultyIcon(difficulty); + const sectionId = `dungeon-section-${difficulty}`; - // Add difficulty header + // Add collapsible difficulty header html += ` -

- - ${difficultyTitle} -

+
+
+
+ + ${difficultyTitle} + (${dungeons.length}) +
+
+ +
+
+
`; dungeons.forEach(dungeon => { @@ -332,16 +581,90 @@ class DungeonSystem {
`; }); + + // Close the section + html += ` + + + `; }); dungeonListElement.innerHTML = html; - console.log('[DUNGEON SYSTEM] Dungeon list UI generated successfully'); + + // Initialize default collapse states + this.initializeDungeonSections(); + } + + /** + * Initialize dungeon sections with saved collapse states + */ + initializeDungeonSections() { + // Default states: tutorial and easy expanded, others collapsed + const defaultStates = { + 'dungeon-section-tutorial': false, // expanded + 'dungeon-section-easy': false, // expanded + 'dungeon-section-medium': true, // collapsed + 'dungeon-section-hard': true, // collapsed + 'dungeon-section-extreme': true // collapsed + }; + + Object.entries(defaultStates).forEach(([sectionId, defaultCollapsed]) => { + const section = document.getElementById(sectionId); + const indicator = document.getElementById(`${sectionId}-indicator`); + + if (section && indicator) { + // Use saved state if available, otherwise use default + const shouldCollapse = this.collapseStates.has(sectionId) ? + this.collapseStates.get(sectionId) : defaultCollapsed; + + if (shouldCollapse) { + section.classList.add('collapsed'); + indicator.classList.remove('fa-chevron-down'); + indicator.classList.add('fa-chevron-right'); + } + } + }); + } + + /** + * Toggle dungeon section collapse/expand + */ + toggleDungeonSection(sectionId) { + // Check if game and systems are available + if (!window.game || !window.game.systems || !window.game.systems.dungeonSystem) { + console.warn('[DUNGEON SYSTEM] Game systems not available for toggle'); + return; + } + + const section = document.getElementById(sectionId); + const indicator = document.getElementById(`${sectionId}-indicator`); + + if (!section || !indicator) return; + + const isCollapsed = section.classList.contains('collapsed'); + + if (isCollapsed) { + // Expand + section.classList.remove('collapsed'); + indicator.classList.remove('fa-chevron-right'); + indicator.classList.add('fa-chevron-down'); + } else { + // Collapse + section.classList.add('collapsed'); + indicator.classList.remove('fa-chevron-down'); + indicator.classList.add('fa-chevron-right'); + } + + // Save the state + this.collapseStates.set(sectionId, !isCollapsed); + + console.log(`[DUNGEON SYSTEM] Toggled section ${sectionId}: ${isCollapsed ? 'expanded' : 'collapsed'}`); } /** @@ -390,45 +713,88 @@ class DungeonSystem { * Check if player can enter dungeon */ canEnterDungeon(dungeon) { - if (!this.game.player) { - console.log('[DUNGEON SYSTEM] No player data available'); + if (!this.game.systems.player) { return false; } - const playerLevel = this.game.player.stats?.level || 1; + const playerLevel = this.game.systems.player.stats?.level || 1; const minLevel = dungeon.minLevel || 1; const maxLevel = dungeon.maxLevel || 999; const energyCost = dungeon.energyCost || 0; - const playerEnergy = this.game.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 - }); + const playerEnergy = this.game.systems.player.attributes?.energy || 0; return playerLevel >= minLevel && playerLevel <= maxLevel && playerEnergy >= energyCost; } - + + /** + * Exit current dungeon + */ + exitDungeon() { + console.log('[DUNGEON SYSTEM] Exiting dungeon'); + + if (this.currentDungeon) { + // Send exit packet to server + if (this.game.socket) { + this.game.socket.emit('exit_dungeon', { + instanceId: this.currentDungeon.id, + userId: this.game.systems.player?.id || 'anonymous' + }); + } + } + + // Reset local state + this.currentDungeon = null; + this.currentRoom = null; + this.isExploring = false; + this.dungeonProgress = 0; + + // Update UI to show dungeon list + this.updateUI(); + + // Show notification + if (this.game && this.game.showNotification) { + this.game.showNotification('Exited dungeon', 'info', 2000); + } + } + + /** + * Move to next room (for rooms without enemies) + */ + moveToNextRoom() { + console.log('[DUNGEON SYSTEM] Moving to next room'); + + if (!this.currentDungeon) { + console.warn('[DUNGEON SYSTEM] No active dungeon to continue'); + return; + } + + // Request next room from server + if (this.game.socket) { + this.game.socket.emit('next_room', { + instanceId: this.currentDungeon.id, + userId: this.game.systems.player?.id || 'anonymous' + }); + } + } + /** * Update UI with current dungeon information */ updateUI() { - if (this.game.uiManager) { - this.game.uiManager.updateDungeonUI({ + if (this.game.systems.ui) { + this.game.systems.ui.updateDungeonUI({ currentDungeon: this.currentDungeon, currentRoom: this.currentRoom, progress: this.dungeonProgress, isExploring: this.isExploring }); + } else { + console.warn('[DUNGEON SYSTEM] UI manager not available in game.systems.ui'); } } - + /** * Initialize system and load server data */ @@ -447,15 +813,19 @@ class DungeonSystem { console.error('[DUNGEON SYSTEM] Socket still not available after retry'); } }, 1000); - } else { - this.setupSocketListeners(); - await this.loadServerData(); + return; } - console.log('[DUNGEON SYSTEM] Client dungeon system initialization complete'); + this.setupSocketListeners(); + await this.loadServerData(); } } +// Export DungeonSystem to global scope +if (typeof window !== 'undefined') { + window.DungeonSystem = DungeonSystem; +} + // Export for use in GameEngine if (typeof module !== 'undefined' && module.exports) { module.exports = DungeonSystem; diff --git a/Client/js/systems/ItemSystem.js b/Client/js/systems/ItemSystem.js index 0c15b49..53af214 100644 --- a/Client/js/systems/ItemSystem.js +++ b/Client/js/systems/ItemSystem.js @@ -9,7 +9,8 @@ class ItemSystem { // Item storage this.itemCatalog = new Map(); // itemId -> item data - this.shopItems = []; // Array of shop items + this.shopItems = []; // Array of shop items (legacy) + this.shopItemsByCategory = {}; // Categorized shop items (new structure) this.lastUpdated = null; // Loading state @@ -56,10 +57,31 @@ class ItemSystem { // 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); + // Handle new shop structure (categorized) vs old structure (flat array) + let totalItems = 0; + if (Array.isArray(shopItems)) { + // Old structure: flat array + totalItems = shopItems.length; + console.log('[ITEM SYSTEM] Received', totalItems, 'items from server (old structure)'); + this.processServerItems(shopItems); + this.shopItemsByCategory = {}; // Clear categorized data + } else if (shopItems && typeof shopItems === 'object') { + // New structure: categorized object + totalItems = Object.values(shopItems).reduce((sum, categoryItems) => sum + categoryItems.length, 0); + console.log('[ITEM SYSTEM] Received', totalItems, 'items from server (new structure)'); + console.log('[ITEM SYSTEM] Categories:', Object.keys(shopItems)); + + // Store categorized data + this.shopItemsByCategory = shopItems; + + // Flatten all items for processing + const allItems = Object.values(shopItems).flat(); + this.processServerItems(allItems); + } else { + console.warn('[ITEM SYSTEM] Invalid shop items structure received:', typeof shopItems); + totalItems = 0; + this.shopItemsByCategory = {}; + } this.lastUpdated = Date.now(); console.log(`[ITEM SYSTEM] Successfully loaded ${this.itemCatalog.size} items from server`); @@ -113,9 +135,36 @@ class ItemSystem { reject(new Error('Server request timeout')); }, 10000); + // Test server connection first + console.log('[ITEM SYSTEM] Testing server connection...'); + window.game.socket.emit('ping', { timestamp: Date.now() }); + + // Listen for ping response + const pingHandler = (data) => { + console.log('[ITEM SYSTEM] Ping response received:', data); + console.log('[ITEM SYSTEM] Server is responding! Ping roundtrip:', Date.now() - data.received, 'ms'); + window.game.socket.off('ping', pingHandler); + window.game.socket.off('pong', pingHandler); + }; + window.game.socket.on('ping', pingHandler); + + // Listen for pong response (backup) + const pongHandler = (data) => { + console.log('[ITEM SYSTEM] Pong response received:', data); + console.log('[ITEM SYSTEM] Server is responding! Pong roundtrip:', Date.now() - data.timestamp, 'ms'); + window.game.socket.off('pong', pongHandler); + }; + window.game.socket.on('pong', pongHandler); + // Request shop items from server console.log('[ITEM SYSTEM] Emitting getShopItems request'); + console.log('[ITEM SYSTEM] Socket state:', { + connected: window.game.socket.connected, + id: window.game.socket.id + }); + window.game.socket.emit('getShopItems', {}); + console.log('[ITEM SYSTEM] Request sent, waiting for response...'); // Listen for response const handleResponse = (data) => { @@ -124,13 +173,20 @@ class ItemSystem { window.game.socket.off('shopItemsReceived', handleResponse); console.log('[ITEM SYSTEM] Response success:', data.success); - console.log('[ITEM SYSTEM] Response items count:', data.items?.length || 0); + console.log('[ITEM SYSTEM] Response shopItems keys:', data.shopItems ? Object.keys(data.shopItems) : 'none'); if (data.success) { - console.log('[ITEM SYSTEM] Successfully received', data.items?.length || 0, 'items'); + console.log('[ITEM SYSTEM] Successfully received shop data'); console.log('[ITEM SYSTEM] Response timestamp:', data.timestamp); - console.log('[ITEM SYSTEM] Sample item:', data.items?.[0]); - resolve(data.items || []); + + // Log item counts per category + if (data.shopItems) { + Object.entries(data.shopItems).forEach(([category, items]) => { + console.log(`[ITEM SYSTEM] ${category}: ${items.length} items`); + }); + } + + resolve(data.shopItems || {}); } else { console.error('[ITEM SYSTEM] Server returned error:', data.error); reject(new Error(data.error || 'Failed to load shop items')); @@ -139,6 +195,10 @@ class ItemSystem { console.log('[ITEM SYSTEM] Setting up shopItemsReceived listener'); window.game.socket.on('shopItemsReceived', handleResponse); + + // Verify the listener was set up + const listeners = window.game.socket.listeners('shopItemsReceived'); + console.log('[ITEM SYSTEM] shopItemsReceived listeners count:', listeners.length); }); } @@ -180,6 +240,13 @@ class ItemSystem { * Process items received from server */ processServerItems(items) { + // Safety check for items parameter + if (!items || !Array.isArray(items)) { + console.error('[ITEM SYSTEM] Invalid items parameter:', items); + console.error('[ITEM SYSTEM] Expected array, got:', typeof items); + return; + } + console.log('[ITEM SYSTEM] Processing', items.length, 'items from server'); console.log('[ITEM SYSTEM] Sample items:', items.slice(0, 3)); @@ -195,14 +262,14 @@ class ItemSystem { 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] 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); @@ -238,6 +305,13 @@ class ItemSystem { return [...this.shopItems]; } + /** + * Get shop items by category (new structure) + */ + getShopItemsByCategory() { + return this.shopItemsByCategory || {}; + } + /** * Get items by category */ diff --git a/Client/js/systems/QuestSystem.js b/Client/js/systems/QuestSystem.js index cdd0b2b..aaddf2b 100644 --- a/Client/js/systems/QuestSystem.js +++ b/Client/js/systems/QuestSystem.js @@ -39,682 +39,56 @@ class QuestSystem { questStatus: Object.keys(this.questStatus) }); - // Main story quests - this.mainQuests = [ - { - id: 'tutorial_complete', - name: 'First Steps', - description: 'Complete the tutorial dungeon and learn the basics', - type: 'main', - status: 'available', - requirements: { level: 1 }, - objectives: [ - { id: 'clear_tutorial_dungeon', description: 'Complete the tutorial dungeon', target: 1, current: 0, type: 'tutorial_dungeon' }, - { id: 'reach_level_2', description: 'Reach level 2', target: 2, current: 0, type: 'level' } - ], - rewards: { credits: 500, experience: 100, gems: 5 }, - nextQuest: 'first_ship_upgrade' - }, - { - id: 'first_ship_upgrade', - name: 'Ship Enhancement', - description: 'Upgrade your ship for better performance', - type: 'main', - status: 'available', - requirements: { quest: 'tutorial_complete' }, - objectives: [ - { id: 'upgrade_weapon', description: 'Upgrade ship weapons', target: 1, current: 0, type: 'upgrade' }, - { id: 'upgrade_shield', description: 'Upgrade ship shields', target: 1, current: 0, type: 'upgrade' } - ], - rewards: { credits: 1000, experience: 200, gems: 10 }, - nextQuest: 'join_guild' - }, - { - id: 'join_guild', - name: 'Guild Recruitment', - description: 'Join a guild and participate in guild activities', - type: 'main', - status: 'available', - requirements: { quest: 'first_ship_upgrade', level: 5 }, - objectives: [ - { id: 'join_guild', description: 'Join a guild', target: 1, current: 0, type: 'guild' }, - { id: 'guild_contribution', description: 'Contribute to guild', target: 100, current: 0, type: 'contribution' } - ], - rewards: { credits: 2000, experience: 500, gems: 20 }, - nextQuest: 'master_commander' - }, - { - id: 'master_commander', - name: 'Master Commander', - description: 'Become a master commander and lead your fleet to victory', - type: 'main', - status: 'available', - requirements: { quest: 'join_guild', level: 10 }, - objectives: [ - { id: 'reach_level_10', description: 'Reach level 10', target: 10, current: 0, type: 'level' }, - { id: 'clear_10_dungeons', description: 'Clear 10 dungeons', target: 10, current: 0, type: 'dungeon' }, - { id: 'max_skill', description: 'Max out one skill', target: 10, current: 0, type: 'skill' } - ], - rewards: { credits: 5000, experience: 1000, gems: 50, item: 'legendary_weapon' } - } - ]; + // Main story quests - populated by server + this.mainQuests = []; - // All possible daily quests (20 total) - this.allDailyQuests = [ - // Easy quests (difficulty: 1) - { - id: 'daily_dungeon_easy', - name: 'Quick Dungeon Run', - description: 'Complete any dungeon', - type: 'daily', - difficulty: 1, - status: 'available', - objectives: [ - { id: 'clear_dungeon', description: 'Clear 1 dungeon', target: 1, current: 0, type: 'dungeon' } - ], - rewards: { credits: 100, experience: 25, gems: 1 } - }, - { - id: 'daily_combat_easy', - name: 'Light Combat', - description: 'Defeat a few enemies', - type: 'daily', - difficulty: 1, - status: 'available', - objectives: [ - { id: 'defeat_enemies', description: 'Defeat 10 enemies', target: 10, current: 0, type: 'combat' } - ], - rewards: { credits: 80, experience: 20, gems: 1 } - }, - { - id: 'daily_crafting_easy', - name: 'Basic Crafting', - description: 'Craft some items', - type: 'daily', - difficulty: 1, - status: 'available', - objectives: [ - { id: 'craft_items', description: 'Craft 2 items', target: 2, current: 0, type: 'crafting' } - ], - rewards: { credits: 90, experience: 22, gems: 1 } - }, - { - id: 'daily_level_easy', - name: 'Level Up', - description: 'Gain experience and level up', - type: 'daily', - difficulty: 1, - status: 'available', - objectives: [ - { id: 'gain_level', description: 'Gain 1 level', target: 1, current: 0, type: 'level' } - ], - rewards: { credits: 120, experience: 30, gems: 2 } - }, - { - id: 'daily_energy_easy', - name: 'Energy Management', - description: 'Use energy efficiently', - type: 'daily', - difficulty: 1, - status: 'available', - objectives: [ - { id: 'use_energy', description: 'Use 50 energy', target: 50, current: 0, type: 'energy' } - ], - rewards: { credits: 70, experience: 18, gems: 1 } - }, - // Medium quests (difficulty: 2) - { - id: 'daily_dungeon_medium', - name: 'Dungeon Explorer', - description: 'Complete multiple dungeons', - type: 'daily', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'clear_dungeons', description: 'Clear 3 dungeons', target: 3, current: 0, type: 'dungeon' } - ], - rewards: { credits: 300, experience: 75, gems: 3 } - }, - { - id: 'daily_combat_medium', - name: 'Combat Training', - description: 'Defeat enemies in combat', - type: 'daily', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'defeat_enemies', description: 'Defeat 20 enemies', target: 20, current: 0, type: 'combat' } - ], - rewards: { credits: 150, experience: 40, gems: 1 } - }, - { - id: 'daily_combat_hard', - name: 'Combat Veteran', - description: 'Defeat many enemies', - type: 'daily', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'defeat_enemies', description: 'Defeat 50 enemies', target: 50, current: 0, type: 'combat' } - ], - rewards: { credits: 250, experience: 60, gems: 3 } - }, - { - id: 'daily_crafting_medium', - name: 'Master Crafter', - description: 'Craft many items', - type: 'daily', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'craft_items', description: 'Craft 5 items', target: 5, current: 0, type: 'crafting' } - ], - rewards: { credits: 280, experience: 70, gems: 3 } - }, - { - id: 'daily_upgrade_medium', - name: 'Equipment Upgrade', - description: 'Upgrade your equipment', - type: 'daily', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'upgrade_items', description: 'Upgrade 3 items', target: 3, current: 0, type: 'upgrade' } - ], - rewards: { credits: 320, experience: 80, gems: 4 } - }, - { - id: 'daily_wealth_medium', - name: 'Wealth Accumulator', - description: 'Earn credits', - type: 'daily', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'earn_credits', description: 'Earn 1000 credits', target: 1000, current: 0, type: 'credits' } - ], - rewards: { credits: 400, experience: 50, gems: 3 } - }, - // Hard quests (difficulty: 3) - { - id: 'daily_dungeon_hard', - name: 'Dungeon Master', - description: 'Complete many dungeons', - type: 'daily', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'clear_dungeons', description: 'Clear 5 dungeons', target: 5, current: 0, type: 'dungeon' } - ], - rewards: { credits: 600, experience: 150, gems: 6 } - }, - { - id: 'daily_combat_hard', - name: 'Combat Master', - description: 'Defeat many powerful enemies', - type: 'daily', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'defeat_enemies', description: 'Defeat 100 enemies', target: 100, current: 0, type: 'combat' } - ], - rewards: { credits: 500, experience: 120, gems: 5 } - }, - { - id: 'daily_level_hard', - name: 'Power Leveling', - description: 'Gain multiple levels', - type: 'daily', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'gain_levels', description: 'Gain 3 levels', target: 3, current: 0, type: 'level' } - ], - rewards: { credits: 700, experience: 200, gems: 7 } - }, - { - id: 'daily_boss_hard', - name: 'Boss Hunter', - description: 'Defeat boss enemies', - type: 'daily', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'defeat_bosses', description: 'Defeat 3 bosses', target: 3, current: 0, type: 'boss' } - ], - rewards: { credits: 800, experience: 180, gems: 8 } - }, - { - id: 'daily_collection_hard', - name: 'Master Collector', - description: 'Collect rare items', - type: 'daily', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'collect_rare', description: 'Collect 10 rare items', target: 10, current: 0, type: 'collection' } - ], - rewards: { credits: 650, experience: 140, gems: 6 } - }, - // Special quests (difficulty: 4) - { - id: 'daily_speedrun', - name: 'Speed Runner', - description: 'Complete dungeons quickly', - type: 'daily', - difficulty: 4, - status: 'available', - objectives: [ - { id: 'fast_dungeon', description: 'Complete 2 dungeons under 5 minutes', target: 2, current: 0, type: 'speedrun' } - ], - rewards: { credits: 1000, experience: 250, gems: 10 } - }, - { - id: 'daily_perfection', - name: 'Perfectionist', - description: 'Complete objectives without taking damage', - type: 'daily', - difficulty: 4, - status: 'available', - objectives: [ - { id: 'perfect_runs', description: '3 perfect dungeon runs', target: 3, current: 0, type: 'perfect' } - ], - rewards: { credits: 1200, experience: 300, gems: 12 } - }, - { - id: 'daily_multitask', - name: 'Multitask Master', - description: 'Complete multiple quest types', - type: 'daily', - difficulty: 4, - status: 'available', - objectives: [ - { id: 'dungeon_task', description: 'Clear 2 dungeons', target: 2, current: 0, type: 'dungeon' }, - { id: 'combat_task', description: 'Defeat 30 enemies', target: 30, current: 0, type: 'combat' }, - { id: 'craft_task', description: 'Craft 2 items', target: 2, current: 0, type: 'crafting' } - ], - rewards: { credits: 1500, experience: 400, gems: 15 } - }, - { - id: 'daily_endurance', - name: 'Endurance Test', - description: 'Complete long activities', - type: 'daily', - difficulty: 4, - status: 'available', - objectives: [ - { id: 'long_dungeon', description: 'Complete 1 dungeon without healing', target: 1, current: 0, type: 'endurance' } - ], - rewards: { credits: 1100, experience: 280, gems: 11 } - }, - { - id: 'daily_legendary', - name: 'Legendary Challenge', - description: 'Complete legendary difficulty content', - type: 'daily', - difficulty: 4, - status: 'available', - objectives: [ - { id: 'legendary_content', description: 'Complete 1 legendary dungeon', target: 1, current: 0, type: 'legendary' } - ], - rewards: { credits: 2000, experience: 500, gems: 20, item: 'rare_material' } - } - ]; - - // Weekly quests (25 total quests with varied objectives) - this.allWeeklyQuests = [ - // Combat-focused weekly quests - { - id: 'weekly_combat_basic', - name: 'Weekly Combat Duty', - description: 'Complete combat objectives throughout the week', - type: 'weekly', - difficulty: 1, - status: 'available', - objectives: [ - { id: 'defeat_enemies', description: 'Defeat 100 enemies', target: 100, current: 0, type: 'combat' } - ], - rewards: { credits: 800, experience: 200, gems: 8 } - }, - { - id: 'weekly_combat_elite', - name: 'Elite Hunter Weekly', - description: 'Hunt down elite enemies', - type: 'weekly', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'defeat_elites', description: 'Defeat 25 elite enemies', target: 25, current: 0, type: 'elite_combat' } - ], - rewards: { credits: 1500, experience: 400, gems: 15 } - }, - { - id: 'weekly_boss_hunter', - name: 'Boss Hunter Weekly', - description: 'Defeat powerful boss enemies', - type: 'weekly', - difficulty: 4, - status: 'available', - objectives: [ - { id: 'defeat_bosses', description: 'Defeat 10 bosses', target: 10, current: 0, type: 'boss' } - ], - rewards: { credits: 2500, experience: 600, gems: 25, item: 'boss_material' } - }, - - // Dungeon-focused weekly quests - { - id: 'weekly_dungeon_explorer', - name: 'Weekly Dungeon Explorer', - description: 'Explore various dungeons', - type: 'weekly', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'clear_dungeons', description: 'Clear 15 dungeons', target: 15, current: 0, type: 'dungeon' } - ], - rewards: { credits: 1200, experience: 300, gems: 12 } - }, - { - id: 'weekly_dungeon_master', - name: 'Weekly Dungeon Master', - description: 'Master difficult dungeons', - type: 'weekly', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'clear_hard_dungeons', description: 'Clear 10 hard dungeons', target: 10, current: 0, type: 'hard_dungeon' } - ], - rewards: { credits: 2000, experience: 500, gems: 20 } - }, - { - id: 'weekly_dungeon_extreme', - name: 'Extreme Dungeon Challenge', - description: 'Conquer extreme difficulty dungeons', - type: 'weekly', - difficulty: 4, - status: 'available', - objectives: [ - { id: 'clear_extreme_dungeons', description: 'Clear 5 extreme dungeons', target: 5, current: 0, type: 'extreme_dungeon' } - ], - rewards: { credits: 3500, experience: 800, gems: 35, item: 'extreme_material' } - }, - - // Crafting and upgrade weekly quests - { - id: 'weekly_crafting_basic', - name: 'Weekly Crafting Session', - description: 'Craft items throughout the week', - type: 'weekly', - difficulty: 1, - status: 'available', - objectives: [ - { id: 'craft_items', description: 'Craft 20 items', target: 20, current: 0, type: 'crafting' } - ], - rewards: { credits: 600, experience: 150, gems: 6 } - }, - { - id: 'weekly_crafting_master', - name: 'Master Crafter Weekly', - description: 'Craft advanced items', - type: 'weekly', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'craft_advanced', description: 'Craft 10 advanced items', target: 10, current: 0, type: 'advanced_crafting' } - ], - rewards: { credits: 1800, experience: 450, gems: 18 } - }, - { - id: 'weekly_upgrade_specialist', - name: 'Weekly Upgrade Specialist', - description: 'Upgrade equipment and systems', - type: 'weekly', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'upgrade_items', description: 'Upgrade 15 items', target: 15, current: 0, type: 'upgrade' } - ], - rewards: { credits: 1400, experience: 350, gems: 14 } - }, - - // Progression weekly quests - { - id: 'weekly_level_up', - name: 'Weekly Level Up Challenge', - description: 'Gain levels throughout the week', - type: 'weekly', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'gain_levels', description: 'Gain 5 levels', target: 5, current: 0, type: 'level' } - ], - rewards: { credits: 1000, experience: 250, gems: 10 } - }, - { - id: 'weekly_skill_master', - name: 'Weekly Skill Mastery', - description: 'Improve your skills', - type: 'weekly', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'improve_skills', description: 'Gain 20 skill points', target: 20, current: 0, type: 'skill' } - ], - rewards: { credits: 1600, experience: 400, gems: 16 } - }, - - // Resource and wealth weekly quests - { - id: 'weekly_wealth_collector', - name: 'Weekly Wealth Collector', - description: 'Accumulate wealth', - type: 'weekly', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'earn_credits', description: 'Earn 5000 credits', target: 5000, current: 0, type: 'credits' } - ], - rewards: { credits: 2000, experience: 300, gems: 12 } - }, - { - id: 'weekly_resource_gatherer', - name: 'Weekly Resource Gathering', - description: 'Collect valuable resources', - type: 'weekly', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'collect_resources', description: 'Collect 500 resources', target: 500, current: 0, type: 'collection' } - ], - rewards: { credits: 1200, experience: 320, gems: 13 } - }, - - // Special activity weekly quests - { - id: 'weekly_energy_management', - name: 'Weekly Energy Management', - description: 'Use energy efficiently', - type: 'weekly', - difficulty: 1, - status: 'available', - objectives: [ - { id: 'use_energy', description: 'Use 500 energy', target: 500, current: 0, type: 'energy' } - ], - rewards: { credits: 800, experience: 180, gems: 8 } - }, - { - id: 'weekly_speed_demon', - name: 'Weekly Speed Demon', - description: 'Complete activities quickly', - type: 'weekly', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'speed_runs', description: 'Complete 10 speed runs', target: 10, current: 0, type: 'speedrun' } - ], - rewards: { credits: 2200, experience: 550, gems: 22 } - }, - { - id: 'weekly_perfectionist', - name: 'Weekly Perfectionist', - description: 'Complete flawless runs', - type: 'weekly', - difficulty: 4, - status: 'available', - objectives: [ - { id: 'perfect_runs', description: 'Complete 8 perfect runs', target: 8, current: 0, type: 'perfect' } - ], - rewards: { credits: 3000, experience: 700, gems: 30, item: 'perfection_material' } - }, - - // Multi-objective weekly quests - { - id: 'weekly_all_rounder', - name: 'Weekly All-Rounder', - description: 'Complete various activities', - type: 'weekly', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'dungeon_task', description: 'Clear 8 dungeons', target: 8, current: 0, type: 'dungeon' }, - { id: 'combat_task', description: 'Defeat 50 enemies', target: 50, current: 0, type: 'combat' }, - { id: 'craft_task', description: 'Craft 5 items', target: 5, current: 0, type: 'crafting' } - ], - rewards: { credits: 2500, experience: 600, gems: 25 } - }, - { - id: 'weekly_specialist', - name: 'Weekly Specialist', - description: 'Focus on specialized activities', - type: 'weekly', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'special_dungeons', description: 'Clear 5 themed dungeons', target: 5, current: 0, type: 'themed_dungeon' }, - { id: 'special_crafting', description: 'Craft 8 themed items', target: 8, current: 0, type: 'themed_crafting' } - ], - rewards: { credits: 2300, experience: 580, gems: 23 } - }, - - // Exploration and discovery weekly quests - { - id: 'weekly_explorer', - name: 'Weekly Explorer', - description: 'Explore new areas and content', - type: 'weekly', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'explore_areas', description: 'Explore 20 new areas', target: 20, current: 0, type: 'exploration' } - ], - rewards: { credits: 1300, experience: 340, gems: 13 } - }, - { - id: 'weekly_discovery', - name: 'Weekly Discovery', - description: 'Discover hidden secrets', - type: 'weekly', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'discover_secrets', description: 'Discover 15 secrets', target: 15, current: 0, type: 'discovery' } - ], - rewards: { credits: 1900, experience: 480, gems: 19 } - }, - - // Endurance and challenge weekly quests - { - id: 'weekly_endurance', - name: 'Weekly Endurance Test', - description: 'Complete long-form challenges', - type: 'weekly', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'endurance_runs', description: 'Complete 5 endurance runs', target: 5, current: 0, type: 'endurance' } - ], - rewards: { credits: 2100, experience: 530, gems: 21 } - }, - { - id: 'weekly_survivor', - name: 'Weekly Survivor', - description: 'Survive challenging conditions', - type: 'weekly', - difficulty: 4, - status: 'available', - objectives: [ - { id: 'survival_runs', description: 'Complete 3 survival runs', target: 3, current: 0, type: 'survival' } - ], - rewards: { credits: 3200, experience: 750, gems: 32, item: 'survival_material' } - }, - - // Social and community weekly quests - { - id: 'weekly_helper', - name: 'Weekly Helper', - description: 'Assist other players', - type: 'weekly', - difficulty: 2, - status: 'available', - objectives: [ - { id: 'assist_players', description: 'Assist 10 players', target: 10, current: 0, type: 'assist' } - ], - rewards: { credits: 1100, experience: 280, gems: 11 } - }, - { - id: 'weekly_leader', - name: 'Weekly Leader', - description: 'Lead group activities', - type: 'weekly', - difficulty: 3, - status: 'available', - objectives: [ - { id: 'lead_activities', description: 'Lead 5 group activities', target: 5, current: 0, type: 'leadership' } - ], - rewards: { credits: 1700, experience: 430, gems: 17 } - }, - - // Legendary weekly quests - { - id: 'weekly_legendary', - name: 'Weekly Legendary Challenge', - description: 'Complete legendary difficulty content', - type: 'weekly', - difficulty: 5, - status: 'available', - objectives: [ - { id: 'legendary_content', description: 'Complete 3 legendary dungeons', target: 3, current: 0, type: 'legendary' } - ], - rewards: { credits: 5000, experience: 1200, gems: 50, item: 'legendary_material' } - }, - { - id: 'weekly_mythic', - name: 'Weekly Mythic Trial', - description: 'Face mythic level challenges', - type: 'weekly', - difficulty: 5, - status: 'available', - objectives: [ - { id: 'mythic_trials', description: 'Complete 2 mythic trials', target: 2, current: 0, type: 'mythic' } - ], - rewards: { credits: 6000, experience: 1500, gems: 60, item: 'mythic_material' } - } - ]; - - // Currently active daily quests (3 random from allDailyQuests) + // Daily quests - populated by server this.dailyQuests = []; - this.selectedDailyQuests = []; - - // Currently active weekly quests (5 random from allWeeklyQuests) this.weeklyQuests = []; - this.selectedWeeklyQuests = []; - - // Current active quests this.activeQuests = []; this.completedQuests = []; this.failedQuests = []; - this.completedDailyQuests = []; // History of completed daily quests - this.completedWeeklyQuests = []; // History of completed weekly quests + + // Quest tracking arrays + this.selectedDailyQuests = []; + this.selectedWeeklyQuests = []; + this.completedDailyQuests = []; + this.completedWeeklyQuests = []; + + // Procedural quest templates (server-driven) + this.proceduralTemplates = {}; + + // Quest generation settings + this.maxProceduralQuests = 3; + this.proceduralQuestRefresh = 30 * 60 * 1000; // 30 minutes + + // Initialize stats + this.stats = { + questsCompleted: 0, + dailyQuestsCompleted: 0, + weeklyQuestsCompleted: 0, + totalRewardsEarned: { credits: 0, experience: 0, gems: 0 }, + lastDailyReset: this.getServerTime(), + lastWeeklyReset: this.getServerTime() + }; + + console.log('[QUEST SYSTEM] Client quest system initialized - waiting for server data'); + + if (debugLogger) debugLogger.endStep('QuestSystem.constructor', { + mainQuestsCount: this.mainQuests.length, + dailyQuestsCount: this.dailyQuests.length, + weeklyQuestsCount: this.weeklyQuests.length, + maxProceduralQuests: this.maxProceduralQuests, + proceduralQuestRefresh: this.proceduralQuestRefresh, + initialStats: this.stats, + dailyQuestsInitialized: this.dailyQuests.length, + weeklyQuestsInitialized: this.weeklyQuests.length + }); + + // Start countdown timers for server-driven quests + console.log('[QUEST SYSTEM] Starting countdown timers'); + this.startDailyCountdown(); + this.startWeeklyCountdown(); // Initialize daily quests with safety check try { diff --git a/Client/js/ui/UIManager.js b/Client/js/ui/UIManager.js index 9b3bdc2..cc8e93c 100644 --- a/Client/js/ui/UIManager.js +++ b/Client/js/ui/UIManager.js @@ -1712,7 +1712,9 @@ 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); + if (oldCredits !== this.game.formatNumber(creditsAmount)) { + console.log('[UI MANAGER] Credits updated:', this.game.formatNumber(creditsAmount)); + } creditsElement.textContent = this.game.formatNumber(creditsAmount); elementsUpdated++; @@ -1732,7 +1734,9 @@ 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); + if (oldGems !== this.game.formatNumber(gemsAmount)) { + console.log('[UI MANAGER] Gems updated:', this.game.formatNumber(gemsAmount)); + } gemsElement.textContent = this.game.formatNumber(gemsAmount); elementsUpdated++; @@ -1753,8 +1757,11 @@ class UIManager { const oldEnergy = energyElement.textContent; 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}`; + const newEnergy = `${currentEnergy}/${maxEnergy}`; + if (oldEnergy !== newEnergy) { + console.log('[UI MANAGER] Energy updated:', newEnergy); + } + energyElement.textContent = newEnergy; elementsUpdated++; if (debugLogger) debugLogger.logStep('Energy updated', { @@ -2630,6 +2637,94 @@ class UIManager { `; } + + // Update dungeon UI when player enters/exits dungeons + updateDungeonUI(dungeonData) { + console.log('[UI MANAGER] Updating dungeon UI:', dungeonData); + console.log('[UI MANAGER] Dungeon state check:', { + isExploring: dungeonData.isExploring, + hasCurrentDungeon: !!dungeonData.currentDungeon, + hasCurrentRoom: !!dungeonData.currentRoom, + progress: dungeonData.progress, + dungeonViewElement: !!document.getElementById('dungeonView') + }); + + // Check if dungeon is completed (progress = -1) + if (dungeonData.progress === -1 || !dungeonData.isExploring) { + // Player is not in dungeon or dungeon is completed - show normal dungeon list + console.log('[UI MANAGER] Player not exploring or dungeon completed, showing dungeon list'); + this.switchTab('dungeons'); + return; + } + + // Player is in dungeon - show exploration interface + const dungeonView = document.getElementById('dungeonView'); + if (dungeonView) { + console.log('[UI MANAGER] Found dungeonView element, creating exploration UI'); + const dungeon = dungeonData.currentDungeon; + const progress = dungeonData.progress || 0; + const currentRoom = dungeonData.currentRoom; + + let content = ` +
+
+

${dungeon.dungeonId} Dungeon

+
+ Progress: Room ${progress + 1} +
+ +
+ `; + + if (currentRoom) { + content += ` +
+

${currentRoom.name}

+

${currentRoom.description}

+
+
Enemies:
+ ${currentRoom.enemies && currentRoom.enemies.length > 0 ? + currentRoom.enemies.map((enemy, index) => { + console.log(`[UI MANAGER] Rendering enemy ${index}:`, enemy); + return ` +
+ ${enemy.name || 'Unknown Enemy'} +
HP: ${enemy.health || '??'} | ATK: ${enemy.attack || '??'} | DEF: ${enemy.defense || '??'}
+
+ `}).join('') : + '

No enemies in this room

' + } +
+ ${currentRoom.enemies && currentRoom.enemies.length > 0 ? + `` : + `` + } +
+ `; + } else { + content += ` +
+

Loading next room...

+ +
+ `; + } + + content += '
'; + dungeonView.innerHTML = content; + console.log('[UI MANAGER] Dungeon exploration UI applied successfully'); + } else { + console.error('[UI MANAGER] dungeonView element not found!'); + } + } } // Export UIManager to global scope diff --git a/Client/styles/components.css b/Client/styles/components.css index bc076f6..deecfc2 100644 --- a/Client/styles/components.css +++ b/Client/styles/components.css @@ -1533,6 +1533,134 @@ body.fullscreen .shop-container { color: var(--text-secondary); } +/* Collapsible Dungeon Sections */ +.dungeon-section { + margin-bottom: 1rem; + border: 1px solid var(--border-color); + border-radius: 8px; + overflow: hidden; + background: var(--bg-secondary); +} + +.difficulty-header.collapsible { + padding: 1rem; + cursor: pointer; + user-select: none; + display: flex; + justify-content: space-between; + align-items: center; + transition: background-color 0.3s ease; + margin: 0; + border: none; + border-radius: 0; +} + +.difficulty-header.collapsible:hover { + background: var(--bg-tertiary); +} + +.header-content { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.header-content i { + font-size: 1.2rem; +} + +.header-content span { + font-weight: 600; + font-size: 1.1rem; +} + +.dungeon-count { + background: rgba(255, 255, 255, 0.2); + padding: 0.2rem 0.5rem; + border-radius: 12px; + font-size: 0.8rem; + font-weight: 600; + min-width: 2rem; + text-align: center; +} + +.collapse-indicator { + transition: transform 0.3s ease; + background: rgba(255, 255, 255, 0.1); + width: 2rem; + height: 2rem; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.collapse-indicator i { + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.9); +} + +.difficulty-header.collapsible:hover .collapse-indicator { + background: rgba(255, 255, 255, 0.2); + transform: scale(1.1); +} + +.dungeon-content { + padding: 0 1rem 1rem; + max-height: 2000px; + overflow: hidden; + transition: all 0.3s ease; + opacity: 1; +} + +.dungeon-content.collapsed { + max-height: 0; + padding: 0 1rem; + opacity: 0; +} + +/* Difficulty-specific colors for collapsible headers */ +.difficulty-header.tutorial { + background: linear-gradient(135deg, var(--info-color), #2c5aa0); + color: white; +} + +.difficulty-header.easy { + background: linear-gradient(135deg, var(--success-color), #27ae60); + color: white; +} + +.difficulty-header.medium { + background: linear-gradient(135deg, var(--warning-color), #f39c12); + color: white; +} + +.difficulty-header.hard { + background: linear-gradient(135deg, var(--error-color), #e74c3c); + color: white; +} + +.difficulty-header.extreme { + background: linear-gradient(135deg, #8e44ad, #9b59b6); + color: white; +} + +/* Enhanced Dungeon Items in Collapsible Sections */ +.dungeon-content .dungeon-item { + margin-bottom: 0.75rem; + border-left: 4px solid transparent; + transition: all 0.3s ease; +} + +.dungeon-content .dungeon-item:hover { + border-left-color: var(--primary-color); + transform: translateX(4px); +} + +.dungeon-content .dungeon-item:last-child { + margin-bottom: 0; +} + /* Shop Items */ .shop-item { background: var(--bg-tertiary); diff --git a/GameServer/server.js b/GameServer/server.js index 3efe609..694b3f6 100644 --- a/GameServer/server.js +++ b/GameServer/server.js @@ -54,6 +54,9 @@ const io = socketIo(server, { } }); +// Pass io instance to systems that need it +dungeonSystem.setIO(io); + // Game state const gameInstances = new Map(); const connectedClients = new Map(); @@ -180,7 +183,7 @@ app.get('/api/shop/items', (req, res) => { const shopItems = itemSystem.getRandomShopItems(); res.status(200).json({ success: true, - items: shopItems, + shopItems: shopItems, timestamp: new Date().toISOString() }); } catch (error) { @@ -195,12 +198,12 @@ app.get('/api/shop/items', (req, res) => { app.get('/api/shop/items/:category', (req, res) => { try { const { category } = req.params; - const items = itemSystem.getItemsByType(category); + const items = itemSystem.getRandomItemsByCategory(category); res.status(200).json({ success: true, - items: items, category: category, + items: items, timestamp: new Date().toISOString() }); } catch (error) { @@ -299,6 +302,7 @@ app.use('/api/base', require('./routes/base')); // Socket.IO handlers (based on LocalServer) io.on('connection', (socket) => { + console.log('[GAME SERVER] === NEW CLIENT CONNECTION ==='); console.log('[GAME SERVER] Client connected:', socket.id); connectedClients.set(socket.id, { connectedAt: Date.now(), @@ -329,6 +333,13 @@ io.on('connection', (socket) => { clearTimeout(authTimeout); }); + // Log all incoming events for debugging + socket.onAny((eventName, ...args) => { + if (eventName !== 'ping' && eventName !== 'pong') { + console.log(`[GAME SERVER] Event received: ${eventName} from ${socket.id}`, args); + } + }); + // Authentication (similar to LocalServer) socket.on('authenticate', async (data) => { console.log('[GAME SERVER] Authenticating client:', socket.id, data); @@ -376,6 +387,10 @@ io.on('connection', (socket) => { if (playerData.stats.totalKills === undefined) playerData.stats.totalKills = 0; if (playerData.stats.questsCompleted === undefined) playerData.stats.questsCompleted = 0; + // Ensure idle system production rates are initialized + idleSystem.initializePlayerData(playerData.userId); + console.log('[GAME SERVER] Idle system initialized for player:', playerData.username); + await savePlayerData(playerData.userId, playerData); // In production, validate with API server @@ -459,26 +474,63 @@ io.on('connection', (socket) => { }); }); + // Test idle system manually + socket.on('testIdleRewards', (data) => { + console.log('[GAME SERVER] Testing idle rewards for:', socket.id); + + const clientData = connectedClients.get(socket.id); + if (clientData && clientData.userId) { + const onlineRewards = idleSystem.generateOnlineIdleRewards(clientData.userId, 10000); + console.log('[GAME SERVER] Test idle rewards:', onlineRewards); + + socket.emit('testIdleRewards', { + rewards: onlineRewards, + productionRates: idleSystem.playerProductionRates.get(clientData.userId) + }); + } else { + socket.emit('testIdleRewards', { error: 'Not authenticated' }); + } + }); + // Shop and item system events + socket.on('ping', (data) => { + console.log('[GAME SERVER] Ping received from:', socket.id, data); + socket.emit('pong', { + timestamp: Date.now(), + received: data.timestamp, + serverTime: new Date().toISOString() + }); + }); + socket.on('getShopItems', (data) => { + console.log('[GAME SERVER] === getShopItems REQUEST START ==='); console.log('[GAME SERVER] getShopItems request received from:', socket.id); console.log('[GAME SERVER] Request data:', data); + console.log('[GAME SERVER] ItemSystem available:', !!itemSystem); + console.log('[GAME SERVER] ItemSystem.getRandomShopItems available:', typeof itemSystem.getRandomShopItems); try { console.log('[GAME SERVER] Getting shop items from ItemSystem...'); const shopItems = itemSystem.getRandomShopItems(); - console.log('[GAME SERVER] Got shop items:', shopItems.length, 'items'); - console.log('[GAME SERVER] Sample item:', shopItems[0]); + console.log('[GAME SERVER] Got shop items by category:', Object.keys(shopItems)); + + // Log item counts per category + Object.entries(shopItems).forEach(([category, items]) => { + console.log(`[GAME SERVER] ${category}: ${items.length} items`); + }); const response = { success: true, - items: shopItems, + shopItems: shopItems, // Changed from 'items' to 'shopItems' timestamp: new Date().toISOString() }; console.log('[GAME SERVER] Sending response:', response); + console.log('[GAME SERVER] About to emit shopItemsReceived to:', socket.id); + socket.emit('shopItemsReceived', response); - console.log('[GAME SERVER] Response sent successfully'); + console.log('[GAME SERVER] shopItemsReceived emitted successfully'); + console.log('[GAME SERVER] === getShopItems REQUEST COMPLETE ==='); } catch (error) { console.error('[GAME SERVER] Error sending shop items:', error); console.error('[GAME SERVER] Error stack:', error.stack); @@ -486,6 +538,44 @@ io.on('connection', (socket) => { success: false, error: 'Failed to load shop items' }); + console.log('[GAME SERVER] Error response sent'); + } + }); + + socket.on('getShopCategory', (data) => { + console.log('[GAME SERVER] getShopCategory request received from:', socket.id); + console.log('[GAME SERVER] Request data:', data); + + try { + const { category } = data; + if (!category) { + socket.emit('shopCategoryReceived', { + success: false, + error: 'Category required' + }); + return; + } + + console.log('[GAME SERVER] Getting items for category:', category); + const categoryItems = itemSystem.getRandomItemsByCategory(category); + console.log('[GAME SERVER] Got', categoryItems.length, 'items for category', category); + + const response = { + success: true, + category: category, + items: categoryItems, + timestamp: new Date().toISOString() + }; + + console.log('[GAME SERVER] Sending category response:', response); + socket.emit('shopCategoryReceived', response); + console.log('[GAME SERVER] Category response sent successfully'); + } catch (error) { + console.error('[GAME SERVER] Error sending category items:', error); + socket.emit('shopCategoryReceived', { + success: false, + error: 'Failed to load category items' + }); } }); @@ -601,7 +691,7 @@ io.on('connection', (socket) => { } try { - const playerData = await loadPlayerData(clientData.userId); + const playerData = await loadPlayerData(clientData.userId, clientData.username || 'Player'); const offlineRewards = idleSystem.calculateOfflineRewards(clientData.userId); if (offlineRewards.offlineTime > 0 && offlineRewards.rewards.credits > 0) { @@ -665,7 +755,17 @@ io.on('connection', (socket) => { } // Load current player data - const playerData = await loadPlayerData(clientData.userId); + console.log('[GAME SERVER] Loading player data for purchase:', { + userId: clientData.userId, + username: clientData.username + }); + + const playerData = await loadPlayerData(clientData.userId, clientData.username || 'Player'); + if (!playerData) { + console.log('[GAME SERVER] Purchase failed - could not load player data'); + socket.emit('purchaseCompleted', { success: false, error: 'Failed to load player data' }); + return; + } console.log('[GAME SERVER] Player data loaded:', { username: playerData.username, credits: playerData.stats.credits, @@ -801,7 +901,18 @@ io.on('connection', (socket) => { // Send success response const response = { success: true, - item: item, + item: { + id: item.id, + name: item.name, + type: item.type, + rarity: item.rarity, + description: item.description, + price: item.price, + currency: item.currency, + quantity: quantity, + stats: item.stats || {}, + texturePath: item.texturePath || item.texture + }, quantity: quantity, totalCost: totalCost, currency: currency, @@ -865,6 +976,20 @@ io.on('connection', (socket) => { console.log('[GAME SERVER] Starting dungeon for:', socket.id, data); try { const { dungeonId, userId } = data; + + // Check if dungeon is one-time only and already completed + const dungeon = dungeonSystem.getDungeon(dungeonId); + if (dungeon && dungeon.oneTimeOnly) { + const completedDungeons = dungeonSystem.getPlayerCompletedDungeons(userId); + if (completedDungeons.includes(dungeonId)) { + socket.emit('dungeon_started', { + success: false, + error: 'This dungeon can only be completed once per character.' + }); + return; + } + } + const instance = dungeonSystem.createInstance(dungeonId, userId, []); socket.emit('dungeon_started', { instance }); } catch (error) { @@ -877,8 +1002,29 @@ io.on('connection', (socket) => { console.log('[GAME SERVER] Processing encounter for:', socket.id, data); try { const { instanceId, userId } = data; - const encounter = dungeonSystem.startEncounter(instanceId, userId); - socket.emit('encounter_data', { encounter }); + const result = dungeonSystem.startEncounter(instanceId, userId); + + // Auto-complete combat for enemies with 0 attack + if (result.encounter.enemies && result.encounter.enemies.length > 0) { + const allZeroAttack = result.encounter.enemies.every(enemy => enemy.attack === 0); + if (allZeroAttack) { + console.log('[GAME SERVER] Auto-combat: All enemies have 0 attack, completing encounter'); + const completionResult = dungeonSystem.completeEncounter(instanceId, userId, { victory: true }); + socket.emit('encounter_completed', { + success: true, + rewards: completionResult.rewards, + nextEncounter: completionResult.nextEncounter, + encounterIndex: completionResult.encounterIndex + }); + return; + } + } + + socket.emit('encounter_data', { + encounter: result.encounter, + encounterIndex: result.encounterIndex, + instance: result.instance + }); } catch (error) { console.error('[GAME SERVER] Error processing encounter:', error); socket.emit('encounter_data', { success: false, error: error.message }); @@ -897,6 +1043,45 @@ io.on('connection', (socket) => { } }); + socket.on('get_quests', (data) => { + console.log('[GAME SERVER] Getting quests for:', socket.id, data); + try { + // Get quest data from quest system + const questData = questSystem.getPlayerQuests(socket.userId || 'anonymous'); + socket.emit('quests_data', questData); + } catch (error) { + console.error('[GAME SERVER] Error getting quests:', error); + socket.emit('quests_data', { success: false, error: error.message }); + } + }); + + // Listen for quest completion events from other systems + io.on('quest_completed', (data) => { + console.log('[GAME SERVER] Quest completion event received:', data); + try { + const result = questSystem.completeQuest(data.userId, data.questId, data.rewards); + if (result.success) { + console.log('[GAME SERVER] Quest completed successfully on server:', data.questId); + } else { + console.error('[GAME SERVER] Failed to complete quest:', result.error); + } + } catch (error) { + console.error('[GAME SERVER] Error processing quest completion:', error); + } + }); + + socket.on('next_room', (data) => { + console.log('[GAME SERVER] Moving to next room for:', socket.id, data); + try { + const { instanceId, userId } = data; + const result = dungeonSystem.moveToNextRoom(instanceId, userId); + socket.emit('next_room_data', result); + } catch (error) { + console.error('[GAME SERVER] Error moving to next room:', error); + socket.emit('next_room_data', { success: false, error: error.message }); + } + }); + socket.on('get_dungeon_status', (data) => { console.log('[GAME SERVER] Getting dungeon status for:', socket.id, data); try { @@ -1224,9 +1409,12 @@ async function startServer() { // Start online idle rewards generation (every 10 seconds) setInterval(async () => { + console.log('[GAME SERVER] Idle reward timer triggered - checking', connectedClients.size, 'connected clients'); + for (const [socketId, clientData] of connectedClients.entries()) { if (clientData.userId && clientData.playerData) { try { + console.log('[GAME SERVER] Processing idle rewards for client:', socketId, 'user:', clientData.username); // Update playTime for active players const sessionTime = clientData.playerData.updatePlayTime(); console.log(`[GAME SERVER] Updated playTime for ${clientData.username}: +${sessionTime}ms, Total: ${clientData.playerData.stats.playTime}ms`); @@ -1239,11 +1427,15 @@ async function startServer() { const onlineRewards = idleSystem.generateOnlineIdleRewards(clientData.userId, 10000); // 10 seconds + console.log('[GAME SERVER] Generated online rewards for', clientData.username, ':', onlineRewards); + if (onlineRewards.credits > 0) { // Update player data with online rewards clientData.playerData.stats.credits = (clientData.playerData.stats.credits || 0) + onlineRewards.credits; clientData.playerData.stats.experience = (clientData.playerData.stats.experience || 0) + onlineRewards.experience; + console.log('[GAME SERVER] Applied idle rewards - Credits:', onlineRewards.credits, 'New balance:', clientData.playerData.stats.credits); + // Send update to client io.to(socketId).emit('onlineIdleRewards', { credits: onlineRewards.credits, @@ -1251,6 +1443,10 @@ async function startServer() { newBalance: clientData.playerData.stats.credits, playTime: clientData.playerData.stats.playTime }); + + console.log('[GAME SERVER] Sent onlineIdleRewards to client:', socketId); + } else { + console.log('[GAME SERVER] No idle rewards generated for', clientData.username); } } catch (error) { console.error(`[GAME SERVER] Error generating online idle rewards for ${socketId}:`, error); diff --git a/GameServer/systems/DungeonSystem.js b/GameServer/systems/DungeonSystem.js index e163cb1..b8c0df4 100644 --- a/GameServer/systems/DungeonSystem.js +++ b/GameServer/systems/DungeonSystem.js @@ -1024,10 +1024,121 @@ class DungeonSystem { console.log(`[DUNGEON SYSTEM] Initialized ${this.dungeons.size} dungeons`); } + setIO(io) { + this.io = io; + console.log('[DUNGEON SYSTEM] Socket.IO instance set for quest events'); + } + + generateDungeonEncounters(dungeon) { + console.log('[DUNGEON SYSTEM] Generating encounters for dungeon:', dungeon.id); + + const encounters = []; + const encounterCount = Math.max(3, Math.floor(dungeon.estimatedTime / 5)); // 1 encounter per ~5 minutes + + console.log('[DUNGEON SYSTEM] Encounter count:', encounterCount, 'for difficulty:', dungeon.difficulty); + + // Always start with entrance + encounters.push({ + type: 'entrance', + name: 'Dungeon Entrance', + description: 'The entrance to the dungeon', + enemies: [], + rewards: false + }); + + // Generate random encounters based on dungeon difficulty + for (let i = 1; i < encounterCount - 1; i++) { + const encounterTypes = ['corridor', 'chamber']; + if (i === Math.floor(encounterCount / 2)) { + encounterTypes.push('treasure'); // Mid-dungeon treasure room + } + + const type = encounterTypes[Math.floor(Math.random() * encounterTypes.length)]; + const template = this.roomTypes[type]; + + encounters.push({ + type, + name: template.name, + description: `A ${template.name.toLowerCase()} in the dungeon`, + enemies: this.generateEnemies(dungeon.enemyTypes, template.enemies), + rewards: template.rewards + }); + } + + // Always end with boss room (except tutorial) + if (dungeon.difficulty !== 'tutorial') { + encounters.push({ + type: 'boss', + name: 'Boss Chamber', + description: 'The final chamber with a powerful enemy', + enemies: this.generateEnemies(dungeon.enemyTypes, 1, true), + rewards: true, + isBoss: true + }); + } else { + // Tutorial ends with simple chamber (only practice targets for auto-completion) + encounters.push({ + type: 'chamber', + name: 'Training Room', + description: 'The final training room', + enemies: this.generateEnemies(['practice_target'], 1), // Only practice targets + rewards: true + }); + } + + console.log('[DUNGEON SYSTEM] Generated encounters:', encounters.map((e, i) => ({ + index: i, + type: e.type, + name: e.name, + enemyCount: e.enemies?.length || 0 + }))); + + return encounters; + } + + generateEnemies(enemyTypes, count, isBoss = false) { + console.log('[DUNGEON SYSTEM] Generating enemies:', { enemyTypes, count, isBoss }); + console.log('[DUNGEON SYSTEM] Available enemy templates:', Object.keys(this.enemyTemplates)); + + const enemies = []; + + for (let i = 0; i < count; i++) { + const enemyTypeId = enemyTypes[Math.floor(Math.random() * enemyTypes.length)]; + const template = this.enemyTemplates[enemyTypeId]; + + console.log(`[DUNGEON SYSTEM] Generating enemy ${i}: type=${enemyTypeId}, template=`, template); + + if (!template) { + console.warn(`[DUNGEON SYSTEM] Enemy template not found for type: ${enemyTypeId}`); + continue; + } + + // Scale enemy for boss fights + const enemy = { ...template }; + if (isBoss) { + enemy.health *= 3; + enemy.attack *= 2; + enemy.defense *= 1.5; + enemy.experience *= 5; + enemy.credits *= 3; + enemy.name = `Elite ${enemy.name}`; + } + + enemies.push(enemy); + } + + console.log('[DUNGEON SYSTEM] Generated enemies:', enemies); + return enemies; + } + addDungeon(id, dungeon) { + // Generate encounters for this dungeon + const encounters = this.generateDungeonEncounters(dungeon); + this.dungeons.set(id, { id, ...dungeon, + encounters, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }); @@ -1076,11 +1187,19 @@ class DungeonSystem { } createInstance(dungeonId, creatorId, playerIds = []) { + console.log('[DUNGEON SYSTEM] Creating instance:', { dungeonId, creatorId, playerIds }); + const dungeon = this.getDungeon(dungeonId); if (!dungeon) { throw new Error('Dungeon not found'); } + console.log('[DUNGEON SYSTEM] Dungeon found:', { + id: dungeon.id, + encountersCount: dungeon.encounters?.length || 0, + encounters: dungeon.encounters?.map((e, i) => ({ index: i, type: e.type, name: e.name })) + }); + // Validate requirements if (playerIds.length > dungeon.maxPlayers) { throw new Error('Too many players for this dungeon'); @@ -1235,6 +1354,7 @@ class DungeonSystem { return { success: true, nextEncounter: instance.currentEncounter < dungeon.encounters.length ? dungeon.encounters[instance.currentEncounter] : null, + encounterIndex: instance.currentEncounter, instance }; } @@ -1258,16 +1378,58 @@ class DungeonSystem { // Remove players from instance tracking for (const playerId of instance.players) { this.playerInstances.delete(playerId); + + // Check for quest completion + this.checkQuestCompletion(playerId, instance.dungeonId); } return { success: true, dungeon, rewards, - instance + instance, + encounterIndex: -1, // Dungeon complete + isComplete: true }; } + // Check if dungeon completion should trigger quests + checkQuestCompletion(userId, dungeonId) { + // Tutorial dungeon completion should trigger main story progression + if (dungeonId === 'tutorial') { + console.log('[DUNGEON SYSTEM] Tutorial dungeon completed, triggering quest progression'); + + // Set player stat for tutorial completion + if (this.io) { + this.io.emit('player_stat_update', { + userId: userId, + stat: 'tutorialDungeonCompleted', + value: true + }); + + // Emit quest completion event for quest system + this.io.emit('quest_completed', { + userId: userId, + questId: 'main_story_first_dungeon', + questType: 'dungeon', + dungeonId: dungeonId, + rewards: { + experience: 50, + credits: 25 + } + }); + } + } + } + + getPlayerCompletedDungeons(userId) { + const instances = Array.from(this.instances.values()).filter(instance => + instance.players.has(userId) && instance.status === 'completed' + ); + + return instances.map(instance => instance.dungeonId); + } + calculateRewards(dungeon, instance) { const rewards = { experience: 0, @@ -1275,10 +1437,21 @@ class DungeonSystem { items: [] }; + // Check if dungeon has rewards defined + if (!dungeon.rewards) { + console.warn('[DUNGEON SYSTEM] No rewards defined for dungeon:', dungeon.id); + return rewards; + } + // Calculate base rewards const expRange = dungeon.rewards.experience; const creditRange = dungeon.rewards.credits; + if (!expRange || !creditRange) { + console.warn('[DUNGEON SYSTEM] Incomplete rewards defined for dungeon:', dungeon.id); + return rewards; + } + rewards.experience = Math.floor(Math.random() * (expRange.max - expRange.min + 1)) + expRange.min; rewards.credits = Math.floor(Math.random() * (creditRange.max - creditRange.min + 1)) + creditRange.min; @@ -1292,7 +1465,7 @@ class DungeonSystem { const itemChance = Math.max(0.1, 0.5 - (totalDeaths * 0.1)); // Lower chance with more deaths if (Math.random() < itemChance) { const itemPool = dungeon.rewards.items; - if (itemPool.length > 0) { + if (itemPool && itemPool.length > 0) { rewards.items.push(itemPool[Math.floor(Math.random() * itemPool.length)]); } } @@ -1308,6 +1481,58 @@ class DungeonSystem { ); } + moveToNextRoom(instanceId, userId) { + console.log('[DUNGEON SYSTEM] moveToNextRoom called:', { instanceId, userId }); + + const instance = this.getInstance(instanceId); + if (!instance) { + throw new Error('Instance not found'); + } + + if (!instance.players.has(userId)) { + throw new Error('Player not in instance'); + } + + const dungeon = this.getDungeon(instance.dungeonId); + + console.log('[DUNGEON SYSTEM] Current state:', { + currentEncounter: instance.currentEncounter, + totalEncounters: dungeon.encounters.length, + dungeonId: dungeon.id + }); + + // Move to next encounter + instance.currentEncounter++; + + console.log('[DUNGEON SYSTEM] After increment:', { + newEncounter: instance.currentEncounter, + isComplete: instance.currentEncounter >= dungeon.encounters.length + }); + + // Check if dungeon is complete + if (instance.currentEncounter >= dungeon.encounters.length) { + return this.completeDungeon(instanceId); + } + + // Get next encounter + const encounter = dungeon.encounters[instance.currentEncounter]; + + console.log('[DUNGEON SYSTEM] Next encounter:', { + encounterIndex: instance.currentEncounter, + encounterType: encounter.type, + encounterName: encounter.name, + enemies: encounter.enemies + }); + + return { + success: true, + encounter, + encounterIndex: instance.currentEncounter, + instance, + isComplete: false + }; + } + getDungeonStatistics(userId) { const instances = Array.from(this.instances.values()).filter(instance => instance.players.has(userId) diff --git a/GameServer/systems/IdleSystem.js b/GameServer/systems/IdleSystem.js index 9926bfe..938a822 100644 --- a/GameServer/systems/IdleSystem.js +++ b/GameServer/systems/IdleSystem.js @@ -50,15 +50,25 @@ class IdleSystem { } initializePlayerData(userId) { + console.log('[IDLE SYSTEM] initializePlayerData called for userId:', userId); + console.log('[IDLE SYSTEM] Current playerProductionRates size:', this.playerProductionRates.size); + if (!this.playerLastActive.has(userId)) { + console.log('[IDLE SYSTEM] Initializing new player data for:', userId); this.playerLastActive.set(userId, Date.now()); this.playerProductionRates.set(userId, { ...this.defaultProductionRates }); + console.log('[IDLE SYSTEM] Set production rates for', userId, ':', this.defaultProductionRates); this.playerAchievements.set(userId, { totalOfflineTime: 0, maxOfflineSession: 0, totalIdleCredits: 0, totalIdleExperience: 0 }); + console.log('[IDLE SYSTEM] Player data initialized successfully for:', userId); + } else { + console.log('[IDLE SYSTEM] Player data already exists for:', userId); + const existingRates = this.playerProductionRates.get(userId); + console.log('[IDLE SYSTEM] Existing production rates:', existingRates); } } @@ -250,8 +260,13 @@ class IdleSystem { // Generate online idle rewards (called periodically while player is online) generateOnlineIdleRewards(userId, deltaTimeMs) { + console.log('[IDLE SYSTEM] generateOnlineIdleRewards called for userId:', userId, 'deltaTimeMs:', deltaTimeMs); + const productionRates = this.playerProductionRates.get(userId); + console.log('[IDLE SYSTEM] Production rates for', userId, ':', productionRates); + if (!productionRates) { + console.log('[IDLE SYSTEM] No production rates found for', userId, '- returning 0 rewards'); return { credits: 0, experience: 0, @@ -260,12 +275,18 @@ class IdleSystem { } const deltaTimeSeconds = deltaTimeMs / 1000; + console.log('[IDLE SYSTEM] Delta time seconds:', deltaTimeSeconds); + console.log('[IDLE SYSTEM] Raw calculation:', productionRates.credits, '*', deltaTimeSeconds, '=', productionRates.credits * deltaTimeSeconds); + + const creditsBeforeFloor = productionRates.credits * deltaTimeSeconds; const rewards = { - credits: Math.floor(productionRates.credits * deltaTimeSeconds), + credits: Math.floor(creditsBeforeFloor), experience: Math.floor(productionRates.experience * deltaTimeSeconds), energy: Math.floor(productionRates.energy * deltaTimeSeconds) }; + console.log('[IDLE SYSTEM] Credits before floor:', creditsBeforeFloor, 'after floor:', rewards.credits); + console.log('[IDLE SYSTEM] Calculated rewards for', userId, ':', rewards); return rewards; } diff --git a/GameServer/systems/ItemSystem.js b/GameServer/systems/ItemSystem.js index 54e236d..22d411e 100644 --- a/GameServer/systems/ItemSystem.js +++ b/GameServer/systems/ItemSystem.js @@ -81,6 +81,20 @@ class ItemSystem { categories: ['shop', 'dungeon_reward'], requirements: { level: 7 }, stackable: false + }, + { + id: 'heavy_cruiser_rare', + name: 'Heavy Cruiser', + type: 'ship', + rarity: 'rare', + price: 35000, + currency: 'credits', + description: 'Powerful heavy cruiser with superior firepower and armor', + texturePath: 'images/ships/heavy_cruiser_rare.png', + stats: { attack: 28, speed: 8, defense: 25, hull: 200 }, + categories: ['shop', 'dungeon_reward'], + requirements: { level: 12 }, + stackable: false } ], @@ -145,6 +159,81 @@ class ItemSystem { stackable: true, maxStack: 10, effects: { attack: 20, defense: 20 } + }, + { + id: 'titanium_alloy', + name: 'Titanium Alloy', + type: 'material', + rarity: 'uncommon', + price: 300, + currency: 'credits', + description: 'Lightweight yet durable metal for ship construction', + texture: 'http://localhost:3002/images/items/materials/titanium_alloy.png', + categories: ['shop', 'dungeon_loot', 'crafting'], + requirements: { level: 8 }, + stackable: true, + maxStack: 75, + effects: { defense: 8, speed: 3 } + }, + { + id: 'plasma_coil', + name: 'Plasma Coil', + type: 'material', + rarity: 'rare', + price: 750, + currency: 'credits', + description: 'High-energy component for weapon systems', + texture: 'http://localhost:3002/images/items/materials/plasma_coil.png', + categories: ['shop', 'dungeon_loot', 'crafting'], + requirements: { level: 12 }, + stackable: true, + maxStack: 30, + effects: { attack: 12, energy: 8 } + }, + { + id: 'nanite_gel', + name: 'Nanite Gel', + type: 'material', + rarity: 'epic', + price: 1200, + currency: 'gems', + description: 'Self-repairing nanomachines in gel form', + texture: 'http://localhost:3002/images/items/materials/nanite_gel.png', + categories: ['shop', 'dungeon_loot', 'crafting'], + requirements: { level: 18 }, + stackable: true, + maxStack: 15, + effects: { hull_repair: 25, defense: 5 } + }, + { + id: 'cryo_crystal', + name: 'Cryo Crystal', + type: 'material', + rarity: 'rare', + price: 600, + currency: 'credits', + description: 'Frozen crystal that enhances cooling systems', + texture: 'http://localhost:3002/images/items/materials/cryo_crystal.png', + categories: ['shop', 'dungeon_loot', 'crafting'], + requirements: { level: 10 }, + stackable: true, + maxStack: 40, + effects: { energy: 15, speed: 5 } + }, + { + id: 'void_shard', + name: 'Void Shard', + type: 'material', + rarity: 'legendary', + price: 2500, + currency: 'gems', + description: 'Fragment of void space with reality-bending properties', + texture: 'http://localhost:3002/images/items/materials/void_shard.png', + categories: ['shop', 'dungeon_loot', 'crafting'], + requirements: { level: 20 }, + stackable: true, + maxStack: 8, + effects: { attack: 15, defense: 15, speed: 10 } } ], @@ -217,6 +306,91 @@ class ItemSystem { effects: { damage_multiplier: 1.5, duration: 45000 }, // 45 seconds consumable: true, cooldown: 120000 // 2 minutes + }, + { + id: 'speed_boost', + name: 'Speed Boost', + type: 'consumable', + rarity: 'uncommon', + price: 200, + currency: 'credits', + description: 'Temporary speed increase for quick escapes', + texture: 'http://localhost:3002/images/items/consumables/speed_boost.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 6 }, + stackable: true, + maxStack: 8, + effects: { speed_multiplier: 1.3, duration: 30000 }, // 30 seconds + consumable: true, + cooldown: 90000 // 1.5 minutes + }, + { + id: 'nanite_repair', + name: 'Nanite Repair', + type: 'consumable', + rarity: 'epic', + price: 500, + currency: 'credits', + description: 'Advanced nanites that continuously repair hull damage', + texture: 'http://localhost:3002/images/items/consumables/nanite_repair.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 12 }, + stackable: true, + maxStack: 3, + effects: { hull_repair_rate: 10, duration: 60000 }, // 10 HP/sec for 1 minute + consumable: true, + cooldown: 180000 // 3 minutes + }, + { + id: 'stealth_field', + name: 'Stealth Field Generator', + type: 'consumable', + rarity: 'rare', + price: 400, + currency: 'credits', + description: 'Temporary invisibility to enemy sensors', + texture: 'http://localhost:3002/images/items/consumables/stealth_field.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 10 }, + stackable: true, + maxStack: 4, + effects: { stealth: true, duration: 20000 }, // 20 seconds + consumable: true, + cooldown: 150000 // 2.5 minutes + }, + { + id: 'emergency_warp', + name: 'Emergency Warp Core', + type: 'consumable', + rarity: 'legendary', + price: 1000, + currency: 'gems', + description: 'Instant emergency teleport to safety', + texture: 'http://localhost:3002/images/items/consumables/emergency_warp.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 15 }, + stackable: true, + maxStack: 2, + effects: { emergency_escape: true }, + consumable: true, + cooldown: 300000 // 5 minutes + }, + { + id: 'combat_stim', + name: 'Combat Stimulant', + type: 'consumable', + rarity: 'uncommon', + price: 250, + currency: 'credits', + description: 'Enhances combat abilities for short duration', + texture: 'http://localhost:3002/images/items/consumables/combat_stim.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 7 }, + stackable: true, + maxStack: 6, + effects: { attack: 10, speed: 5, duration: 45000 }, // 45 seconds + consumable: true, + cooldown: 120000 // 2 minutes } ], @@ -281,6 +455,81 @@ class ItemSystem { stackable: false, cosmetic: true, slot: 'engine' + }, + { + id: 'skull_decal', + name: 'Skull Decal', + type: 'cosmetic', + rarity: 'uncommon', + price: 1500, + currency: 'credits', + description: 'Intimidating skull decal for your ship', + texture: 'http://localhost:3002/images/items/cosmetics/skull_decal.png', + categories: ['shop'], + requirements: { level: 3 }, + stackable: false, + cosmetic: true, + slot: 'decal' + }, + { + id: 'neon_lights', + name: 'Neon Underglow', + type: 'cosmetic', + rarity: 'rare', + price: 3000, + currency: 'credits', + description: 'Colorful neon lights under your ship', + texture: 'http://localhost:3002/images/items/cosmetics/neon_lights.png', + categories: ['shop'], + requirements: { level: 8 }, + stackable: false, + cosmetic: true, + slot: 'lights' + }, + { + id: 'chrome_finish', + name: 'Chrome Finish', + type: 'cosmetic', + rarity: 'epic', + price: 5000, + currency: 'credits', + description: 'Reflective chrome coating for premium look', + texture: 'http://localhost:3002/images/items/cosmetics/chrome_finish.png', + categories: ['shop'], + requirements: { level: 12 }, + stackable: false, + cosmetic: true, + slot: 'finish' + }, + { + id: 'holographic_display', + name: 'Holographic Display', + type: 'cosmetic', + rarity: 'legendary', + price: 8000, + currency: 'gems', + description: 'Advanced holographic projection system', + texture: 'http://localhost:3002/images/items/cosmetics/holographic_display.png', + categories: ['shop'], + requirements: { level: 15 }, + stackable: false, + cosmetic: true, + slot: 'display' + }, + { + id: 'carbon_fiber', + name: 'Carbon Fiber Body', + type: 'cosmetic', + rarity: 'uncommon', + price: 2000, + currency: 'credits', + description: 'Lightweight carbon fiber ship body', + texture: 'http://localhost:3002/images/items/cosmetics/carbon_fiber.png', + categories: ['shop'], + requirements: { level: 6 }, + stackable: false, + cosmetic: true, + slot: 'body' } ], @@ -327,6 +576,76 @@ class ItemSystem { requirements: { level: 10 }, stackable: false, stats: { attack: 30, range: 150, fire_rate: 1 } + }, + { + id: 'ion_cannon_uncommon', + name: 'Ion Cannon', + type: 'weapon', + rarity: 'uncommon', + price: 3500, + currency: 'credits', + description: 'Ion-based weapon effective against shields', + texture: 'http://localhost:3002/images/weapons/ion_cannon_uncommon.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 6 }, + stackable: false, + stats: { attack: 22, range: 110, fire_rate: 1.8, shield_damage: 1.5 } + }, + { + id: 'missile_launcher_rare', + name: 'Missile Launcher', + type: 'weapon', + rarity: 'rare', + price: 8000, + currency: 'credits', + description: 'Homing missile system for heavy damage', + texture: 'http://localhost:3002/images/weapons/missile_launcher_rare.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 12 }, + stackable: false, + stats: { attack: 45, range: 200, fire_rate: 0.8, explosive: true } + }, + { + id: 'plasma_thrower_epic', + name: 'Plasma Thrower', + type: 'weapon', + rarity: 'epic', + price: 12000, + currency: 'credits', + description: 'Continuous plasma stream weapon', + texture: 'http://localhost:3002/images/weapons/plasma_thrower_epic.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 15 }, + stackable: false, + stats: { attack: 35, range: 80, fire_rate: 5, continuous: true } + }, + { + id: 'railgun_legendary', + name: 'Railgun', + type: 'weapon', + rarity: 'legendary', + price: 20000, + currency: 'gems', + description: 'Electromagnetic railgun with piercing shots', + texture: 'http://localhost:3002/images/weapons/railgun_legendary.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 18 }, + stackable: false, + stats: { attack: 60, range: 300, fire_rate: 0.5, piercing: true } + }, + { + id: 'pulse_cannon_common', + name: 'Pulse Cannon', + type: 'weapon', + rarity: 'common', + price: 1500, + currency: 'credits', + description: 'Rapid-fire pulse weapon', + texture: 'http://localhost:3002/images/weapons/pulse_cannon_common.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 2 }, + stackable: false, + stats: { attack: 8, range: 90, fire_rate: 4 } } ], @@ -373,6 +692,76 @@ class ItemSystem { requirements: { level: 10 }, stackable: false, stats: { defense: 25, shield_capacity: 200, recharge_rate: 12 } + }, + { + id: 'plasma_barrier_uncommon', + name: 'Plasma Barrier', + type: 'armor', + rarity: 'uncommon', + price: 3000, + currency: 'credits', + description: 'Plasma-based barrier system', + texture: 'http://localhost:3002/images/armors/plasma_barrier_uncommon.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 7 }, + stackable: false, + stats: { defense: 18, shield_capacity: 120, recharge_rate: 10, energy_resistance: 0.8 } + }, + { + id: 'reactive_armor_epic', + name: 'Reactive Armor', + type: 'armor', + rarity: 'epic', + price: 9000, + currency: 'credits', + description: 'Adaptive armor that responds to damage types', + texture: 'http://localhost:3002/images/armors/reactive_armor_epic.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 14 }, + stackable: false, + stats: { defense: 35, shield_capacity: 250, recharge_rate: 15, adaptive: true } + }, + { + id: 'kinetic_dampener_rare', + name: 'Kinetic Dampener', + type: 'armor', + rarity: 'rare', + price: 6000, + currency: 'credits', + description: 'Specialized armor against kinetic weapons', + texture: 'http://localhost:3002/images/armors/kinetic_dampener_rare.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 11 }, + stackable: false, + stats: { defense: 22, shield_capacity: 180, recharge_rate: 11, kinetic_resistance: 0.7 } + }, + { + id: 'phase_shifter_legendary', + name: 'Phase Shifter', + type: 'armor', + rarity: 'legendary', + price: 15000, + currency: 'gems', + description: 'Experimental phase-shifting armor system', + texture: 'http://localhost:3002/images/armors/phase_shifter_legendary.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 18 }, + stackable: false, + stats: { defense: 40, shield_capacity: 300, recharge_rate: 20, phase_shift: 0.2 } + }, + { + id: 'carbon_plate_common', + name: 'Carbon Plate Armor', + type: 'armor', + rarity: 'common', + price: 1200, + currency: 'credits', + description: 'Lightweight carbon composite armor', + texture: 'http://localhost:3002/images/armors/carbon_plate_common.png', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 3 }, + stackable: false, + stats: { defense: 12, shield_capacity: 70, recharge_rate: 6, weight_reduction: 0.1 } } ], @@ -689,17 +1078,44 @@ class ItemSystem { } /** - * Get random shop items with processed URLs + * Get random shop items with processed URLs - 6 items per category */ - getRandomShopItems(count = 8) { - const allItems = Object.values(this.itemCatalog).flat(); + getRandomShopItems(count = 6) { + const shopItems = {}; + + // Get 6 random items from each category + Object.keys(this.itemCatalog).forEach(category => { + const categoryItems = this.itemCatalog[category] || []; + const selectedItems = []; + + if (categoryItems.length > 0) { + // Randomly select items from this category + const shuffled = [...categoryItems].sort(() => Math.random() - 0.5); + + for (let i = 0; i < Math.min(count, shuffled.length); i++) { + selectedItems.push(this.processItem(shuffled[i])); + } + } + + shopItems[category] = selectedItems; + }); + + return shopItems; + } + + /** + * Get random items for a specific category + */ + getRandomItemsByCategory(category, count = 6) { + const categoryItems = this.itemCatalog[category] || []; const selectedItems = []; - // Randomly select items - const shuffled = [...allItems].sort(() => Math.random() - 0.5); - - for (let i = 0; i < Math.min(count, shuffled.length); i++) { - selectedItems.push(this.processItem(shuffled[i])); + if (categoryItems.length > 0) { + const shuffled = [...categoryItems].sort(() => Math.random() - 0.5); + + for (let i = 0; i < Math.min(count, shuffled.length); i++) { + selectedItems.push(this.processItem(shuffled[i])); + } } return selectedItems; diff --git a/GameServer/systems/QuestSystem.js b/GameServer/systems/QuestSystem.js index 582ae61..f115046 100644 --- a/GameServer/systems/QuestSystem.js +++ b/GameServer/systems/QuestSystem.js @@ -660,6 +660,149 @@ class QuestSystem { return { success: true, message: 'Daily quests reset' }; } + getPlayerQuests(userId) { + console.log('[QUEST SYSTEM] Getting quests for user:', userId); + + // Get or create player data + let playerData = this.playerQuests.get(userId); + if (!playerData) { + playerData = { + activeQuests: new Map(), + completedQuests: new Map(), + failedQuests: new Map() + }; + this.playerQuests.set(userId, playerData); + } + + // Prepare quest data for client + const questData = { + mainQuests: [], + dailyQuests: [], + weeklyQuests: [], + activeQuests: [], + completedQuests: [], + failedQuests: [] + }; + + // Add main quests + for (const [questId, quest] of this.quests) { + if (quest.type === 'main') { + const playerQuest = playerData.activeQuests.get(questId) || playerData.completedQuests.get(questId) || playerData.failedQuests.get(questId); + const status = playerQuest ? playerQuest.status : 'available'; + + questData.mainQuests.push({ + ...quest, + status: status, + objectives: quest.objectives.map(obj => ({ + ...obj, + current: playerQuest && playerQuest.objectives && playerQuest.objectives[obj.id] ? playerQuest.objectives[obj.id].current || 0 : 0 + })) + }); + } + } + + // Add daily quests + for (const [questId, quest] of this.quests) { + if (quest.type === 'daily') { + const playerQuest = playerData.activeQuests.get(questId) || playerData.completedQuests.get(questId) || playerData.failedQuests.get(questId); + const status = playerQuest ? playerQuest.status : 'available'; + + questData.dailyQuests.push({ + ...quest, + status: status, + objectives: quest.objectives.map(obj => ({ + ...obj, + current: playerQuest && playerQuest.objectives && playerQuest.objectives[obj.id] ? playerQuest.objectives[obj.id].current || 0 : 0 + })) + }); + } + } + + // Add weekly quests + for (const [questId, quest] of this.quests) { + if (quest.type === 'weekly') { + const playerQuest = playerData.activeQuests.get(questId) || playerData.completedQuests.get(questId) || playerData.failedQuests.get(questId); + const status = playerQuest ? playerQuest.status : 'available'; + + questData.weeklyQuests.push({ + ...quest, + status: status, + objectives: quest.objectives.map(obj => ({ + ...obj, + current: playerQuest && playerQuest.objectives && playerQuest.objectives[obj.id] ? playerQuest.objectives[obj.id].current || 0 : 0 + })) + }); + } + } + + // Add active quests (for compatibility) + questData.activeQuests = Array.from(playerData.activeQuests.values()); + questData.completedQuests = Array.from(playerData.completedQuests.values()); + questData.failedQuests = Array.from(playerData.failedQuests.values()); + + console.log('[QUEST SYSTEM] Returning quest data:', { + mainQuests: questData.mainQuests.length, + dailyQuests: questData.dailyQuests.length, + weeklyQuests: questData.weeklyQuests.length, + activeQuests: questData.activeQuests.length, + completedQuests: questData.completedQuests.length + }); + + return questData; + } + + completeQuest(userId, questId, rewards = null) { + console.log('[QUEST SYSTEM] Completing quest:', questId, 'for user:', userId); + + const playerData = this.playerQuests.get(userId); + if (!playerData) { + console.log('[QUEST SYSTEM] Player data not found for user:', userId); + return { success: false, error: 'Player not found' }; + } + + const quest = this.quests.get(questId); + if (!quest) { + console.log('[QUEST SYSTEM] Quest not found:', questId); + return { success: false, error: 'Quest not found' }; + } + + // Move quest from active to completed + const activeQuest = playerData.activeQuests.get(questId); + if (activeQuest) { + // Mark all objectives as completed + const completedQuest = { + ...activeQuest, + status: 'completed', + completedAt: Date.now(), + objectives: quest.objectives.map(obj => ({ + ...obj, + current: obj.target + })) + }; + + playerData.activeQuests.delete(questId); + playerData.completedQuests.set(questId, completedQuest); + + console.log('[QUEST SYSTEM] Quest completed successfully:', questId); + return { success: true, quest: completedQuest }; + } else { + // Quest might not be active, try to complete it anyway + const completedQuest = { + ...quest, + status: 'completed', + completedAt: Date.now(), + objectives: quest.objectives.map(obj => ({ + ...obj, + current: obj.target + })) + }; + + playerData.completedQuests.set(questId, completedQuest); + console.log('[QUEST SYSTEM] Quest force-completed:', questId); + return { success: true, quest: completedQuest }; + } + } + resetWeeklyQuests(userId) { const playerData = this.getPlayerData(userId);