/** * Galaxy Strike Online - Client Item System * Dynamically loads and manages items from the GameServer */ class ItemSystem { constructor(gameEngine) { this.game = gameEngine; // Item storage this.itemCatalog = new Map(); // itemId -> item data this.shopItems = []; // Array of shop items (legacy) this.shopItemsByCategory = {}; // Categorized shop items (new structure) this.lastUpdated = null; // Loading state this.isLoading = false; this.loadPromise = null; // Event listeners this.eventListeners = new Map(); } /** * Initialize the item system and load data from server */ async initialize() { console.log('[ITEM SYSTEM] Initializing client item system'); if (this.loadPromise) { return this.loadPromise; } this.loadPromise = this.loadFromServer(); return this.loadPromise; } /** * Load all items from the GameServer */ async loadFromServer() { if (this.isLoading) { console.log('[ITEM SYSTEM] Already loading items from server'); return this.loadPromise; } this.isLoading = true; try { console.log('[ITEM SYSTEM] Loading items from GameServer - Multiplayer Mode'); console.log('[ITEM SYSTEM] Socket connection status:', !!window.game?.socket); if (!window.game || !window.game.socket) { throw new Error('Not connected to server - multiplayer mode requires server connection'); } // Load shop items from server const shopItems = await this.fetchShopItems(); // 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`); console.log('[ITEM SYSTEM] Item categories loaded:', Object.keys(this.itemCatalog).length); // Emit loaded event this.emit('itemsLoaded', { itemCount: this.itemCatalog.size, shopItemCount: this.shopItems.length, timestamp: this.lastUpdated }); return true; } catch (error) { console.error('[ITEM SYSTEM] Failed to load items from server:', error); console.error('[ITEM SYSTEM] Error details:', { message: error.message, stack: error.stack, socketConnected: !!window.game?.socket, socketId: window.game?.socket?.id }); // No fallback - emit error event this.emit('itemsLoadError', error); return false; } finally { this.isLoading = false; } } /** * Fetch shop items from the GameServer */ async fetchShopItems() { console.log('[ITEM SYSTEM] Starting fetchShopItems'); if (!window.game || !window.game.socket) { console.error('[ITEM SYSTEM] No socket connection available'); throw new Error('Not connected to server'); } console.log('[ITEM SYSTEM] Socket ID:', window.game.socket.id); console.log('[ITEM SYSTEM] Socket connected:', window.game.socket.connected); return new Promise((resolve, reject) => { const timeout = setTimeout(() => { console.error('[ITEM SYSTEM] Server request timeout after 10 seconds'); window.game.socket.off('shopItemsReceived', handleResponse); reject(new Error('Server request timeout')); }, 10000); // 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) => { console.log('[ITEM SYSTEM] Received shopItemsReceived response:', data); clearTimeout(timeout); window.game.socket.off('shopItemsReceived', handleResponse); console.log('[ITEM SYSTEM] Response success:', data.success); console.log('[ITEM SYSTEM] Response shopItems keys:', data.shopItems ? Object.keys(data.shopItems) : 'none'); if (data.success) { console.log('[ITEM SYSTEM] Successfully received shop data'); console.log('[ITEM SYSTEM] Response timestamp:', data.timestamp); // 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')); } }; 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); }); } /** * Fetch specific item details from server */ async fetchItemDetails(itemId) { if (!window.game || !window.game.socket) { throw new Error('Not connected to server'); } return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Server request timeout')); }, 5000); // Request item details from server window.game.socket.emit('getItemDetails', { itemId }); // Listen for response const handleResponse = (data) => { clearTimeout(timeout); window.game.socket.off('itemDetailsReceived', handleResponse); if (data.success) { // Cache the item this.itemCatalog.set(itemId, data.item); resolve(data.item); } else { reject(new Error(data.error || 'Item not found')); } }; window.game.socket.on('itemDetailsReceived', handleResponse); }); } /** * Process items received from server */ processServerItems(items) { // 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)); this.itemCatalog.clear(); this.shopItems = []; for (const item of items) { // Store in catalog this.itemCatalog.set(item.id, item); // Add to shop items if available for shop if (item.categories && item.categories.includes('shop')) { this.shopItems.push(item); } // console.log('[ITEM SYSTEM] Added item:', { // id: item.id, // name: item.name, // type: item.type, // rarity: item.rarity, // price: item.price, // categories: item.categories // }); } console.log('[ITEM SYSTEM] Processing complete - Catalog:', this.itemCatalog.size, 'Shop items:', this.shopItems.length); console.log('[ITEM SYSTEM] Shop items by type:', this.shopItems.reduce((acc, item) => { acc[item.type] = (acc[item.type] || 0) + 1; return acc; }, {})); } /** * Get item by ID */ getItem(itemId) { // Return from cache if available if (this.itemCatalog.has(itemId)) { return this.itemCatalog.get(itemId); } // Try to fetch from server if not cached if (window.game && window.game.socket) { this.fetchItemDetails(itemId).catch(error => { console.warn(`[ITEM SYSTEM] Failed to fetch item ${itemId}:`, error); }); } return null; } /** * Get all shop items */ getShopItems() { return [...this.shopItems]; } /** * Get shop items by category (new structure) */ getShopItemsByCategory() { return this.shopItemsByCategory || {}; } /** * Get items by category */ getItemsByCategory(category) { return Array.from(this.itemCatalog.values()).filter(item => item.type === category || (item.categories && item.categories.includes(category)) ); } /** * Get items by type */ getItemsByType(type) { return Array.from(this.itemCatalog.values()).filter(item => item.type === type); } /** * Get items by rarity */ getItemsByRarity(rarity) { return Array.from(this.itemCatalog.values()).filter(item => item.rarity === rarity); } /** * Check if player can use item based on requirements */ canPlayerUseItem(item, playerLevel = null) { if (!item.requirements) return true; // Get player level if not provided if (playerLevel === null && window.game && window.game.systems && window.game.systems.player) { playerLevel = window.game.systems.player.level; } // Check level requirement if (item.requirements.level && playerLevel < item.requirements.level) { return false; } // Add other requirement checks here return true; } /** * Get filtered shop items for current player */ getAvailableShopItems() { return this.shopItems.filter(item => this.canPlayerUseItem(item)); } /** * Format item price for display */ formatPrice(item) { if (!item.price) return 'Free'; const currency = item.currency || 'credits'; const price = this.game.formatNumber(item.price); return `${price} ${currency}`; } /** * Get item rarity color */ getRarityColor(rarity) { const colors = { common: '#888888', uncommon: '#00ff00', rare: '#0088ff', legendary: '#ff8800', epic: '#ff00ff' }; return colors[rarity] || '#ffffff'; } /** * Refresh items from server */ async refresh() { console.log('[ITEM SYSTEM] Refreshing items from server'); return this.loadFromServer(); } /** * Event system */ on(event, callback) { if (!this.eventListeners.has(event)) { this.eventListeners.set(event, []); } this.eventListeners.get(event).push(callback); } off(event, callback) { if (this.eventListeners.has(event)) { const listeners = this.eventListeners.get(event); const index = listeners.indexOf(callback); if (index > -1) { listeners.splice(index, 1); } } } emit(event, data) { if (this.eventListeners.has(event)) { for (const callback of this.eventListeners.get(event)) { try { callback(data); } catch (error) { console.error(`[ITEM SYSTEM] Error in event listener for ${event}:`, error); } } } } /** * catalog getter — alias for shopItemsByCategory, used by Economy.updateShopUI */ get catalog() { return this.shopItemsByCategory; } /** * Get system statistics */ getStats() { const stats = { totalItems: this.itemCatalog.size, shopItems: this.shopItems.length, lastUpdated: this.lastUpdated, isLoading: this.isLoading, socketConnected: !!(window.game?.socket), socketId: window.game?.socket?.id }; // Add category breakdown stats.categories = {}; for (const item of this.itemCatalog.values()) { stats.categories[item.type] = (stats.categories[item.type] || 0) + 1; } return stats; } } // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = ItemSystem; } else { window.ItemSystem = ItemSystem; }