/** * Galaxy Strike Online - Economy System * Manages player currency, transactions, and shop functionality * Now uses server-side ItemSystem for all item data */ class Economy { constructor(gameEngine) { this.game = gameEngine; // 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 } // Transaction history this.transactions = []; // Shop categories this.shopCategories = { ships: 'Ships', weapons: 'Weapons', armors: 'Armors', cosmetics: 'Cosmetics', consumables: 'Consumables', materials: 'Materials' }; // Random shop system - now uses server ItemSystem this.randomShopItems = {}; // Current random items per category this.shopRefreshInterval = null; // Timer for 2-hour refresh this.shopHeartbeatInterval = null; // Timer for live countdown updates this.lastShopRefresh = null; // Timestamp of last refresh this.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 // Shop items - now loaded from server ItemSystem this.shopItems = null; // Will be populated by ItemSystem in multiplayer // Owned cosmetics this.ownedCosmetics = []; // Owned ships this.ownedShips = []; console.log('[ECONOMY] Economy system initialized'); // 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'); } /** * Set up socket listeners for economy data synchronization */ setupSocketListeners() { if (!this.game.socket) { console.warn('[ECONOMY] No socket available for economy sync'); return; } // Listen for economy data updates from server this.game.socket.on('economy_data', (data) => { console.log('[ECONOMY] Received economy data from server:', data); 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(); } 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; } }); } /** * Request economy data from server */ requestEconomyData() { if (this.game.socket) { console.log('[ECONOMY] Requesting economy data from server'); this.game.socket.emit('get_economy_data'); } else { console.warn('[ECONOMY] Cannot request economy data - no socket available'); } } async initialize() { console.log('[ECONOMY] Initializing economy system'); // In multiplayer mode, wait for ItemSystem to be ready (handled by event listener) this.game.on('itemSystemReady', () => { console.log('[ECONOMY] ItemSystem is ready, updating shop UI'); this.updateShopUI(); }); if (window.smartSaveManager?.isMultiplayer) { console.log('[ECONOMY] Multiplayer mode - waiting for ItemSystem to be ready'); // ItemSystem initialization removed - wait for event instead } else { console.log('[ECONOMY] Singleplayer mode - using local shop data'); // Initialize random shop for singleplayer this.initializeRandomShop(); } console.log('[ECONOMY] Economy system initialized'); } // Shop functionality - now uses ItemSystem in multiplayer purchaseItem(itemId, quantity = 1) { const debugLogger = window.debugLogger; // In multiplayer mode, send request to server if (window.smartSaveManager?.isMultiplayer) { if (debugLogger) debugLogger.logStep('Sending purchase request to server', { itemId: itemId, quantity: quantity }); // Send purchase request to server if (window.game && window.game.socket) { window.game.socket.emit('purchaseItem', { itemId: itemId, quantity: quantity }); // Show loading message this.game.showNotification('Processing purchase...', 'info', 2000); } else { this.game.showNotification('Not connected to server', 'error', 3000); } return; } // Singleplayer mode - use local logic const item = this.findShopItem(itemId); if (!item) { if (debugLogger) debugLogger.logStep('Item purchase failed - item not found', { itemId: itemId, quantity: quantity }); this.game.showNotification('Item not found in shop', 'error', 3000); return false; } const totalCost = item.price * quantity; const currency = item.currency; const oldCredits = this.credits; const oldGems = this.gems; if (debugLogger) debugLogger.logStep('Item purchase attempted', { itemId: itemId, itemName: item.name, itemType: item.type, quantity: quantity, unitPrice: item.price, totalCost: totalCost, currency: currency, currentCredits: oldCredits, currentGems: oldGems }); // Check if player can afford if (currency === 'credits' && this.credits < totalCost) { if (debugLogger) debugLogger.logStep('Item purchase failed - insufficient credits', { totalCost: totalCost, currentCredits: oldCredits, deficit: totalCost - oldCredits }); this.game.showNotification('Not enough credits!', 'error', 3000); return false; } if (currency === 'gems' && this.gems < totalCost) { if (debugLogger) debugLogger.logStep('Item purchase failed - insufficient gems', { totalCost: totalCost, currentGems: oldGems, deficit: totalCost - oldGems }); this.game.showNotification('Not enough gems!', 'error', 3000); return false; } // Check if already owns this cosmetic if (item.type === 'cosmetic' && this.ownedCosmetics.includes(item.id)) { this.game.showNotification('You already own this cosmetic!', 'error', 3000); return false; } // Process payment and give item based on type if (currency === 'credits') { this.credits -= totalCost; } else if (currency === 'gems') { this.gems -= totalCost; } switch (item.type) { case 'ship': this.purchaseShip(item, quantity); break; case 'cosmetic': this.purchaseCosmetic(item, quantity); break; case 'consumable': this.purchaseConsumable(item, quantity); break; case 'material': this.purchaseMaterial(item, quantity); break; default: console.warn(`[ECONOMY] Unknown item type: ${item.type}`); return false; } // Show success message this.game.showNotification(`Purchased ${item.name}!`, 'success', 3000); if (debugLogger) debugLogger.logStep('Item purchase completed successfully', { itemId: itemId, itemName: item.name, itemType: item.type, quantity: quantity, totalCost: totalCost, currency: currency, oldCredits: oldCredits, newCredits: this.credits, oldGems: oldGems, newGems: this.gems }); // Update UI without calling updateShopUI to avoid circular updates return true; } findShopItem(itemId) { const debugLogger = window.debugLogger; console.log('[ECONOMY] Looking for shop item:', itemId); console.log('[ECONOMY] Multiplayer mode:', window.smartSaveManager?.isMultiplayer); console.log('[ECONOMY] ItemSystem available:', !!(this.game.systems.itemSystem)); // In multiplayer mode, use ItemSystem (required) if (window.smartSaveManager?.isMultiplayer) { // Check if ItemSystem is ready before using it if (!this.game.systems.itemSystem || !this.game.systems.itemSystem.itemCatalog) { console.log('[ECONOMY] ItemSystem not ready yet, cannot find shop item'); if (debugLogger) debugLogger.logStep('Shop item lookup failed - ItemSystem not ready', { itemId: itemId, multiplayer: true }); return null; } // Search in ItemSystem catalog const item = this.game.systems.itemSystem.itemCatalog.get(itemId); if (item) { console.log('[ECONOMY] Found item in ItemSystem:', item.name); return item; } else { console.log('[ECONOMY] Item not found in ItemSystem:', itemId); return null; } } else { // Singleplayer mode - search in local random shop for (const categoryItems of Object.values(this.randomShopItems)) { const item = categoryItems.find(item => item.id === itemId); if (item) return item; } return null; } } // Purchase methods purchaseShip(ship) { const debugLogger = window.debugLogger; const player = this.game.systems.player; const oldShipName = player.ship.name; const oldShipClass = player.ship.class; const oldAttributes = { ...player.attributes }; // Update player ship player.ship = { name: ship.name, class: ship.id, texture: ship.texture, stats: ship.stats || {} }; // Update player attributes if (ship.stats) { player.attributes = { ...player.attributes, ...ship.stats }; } // Add to owned ships if (!player.ownedShips) { player.ownedShips = []; } if (!player.ownedShips.includes(ship.id)) { 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, oldShipName: oldShipName, oldShipClass: oldShipClass, newShipName: ship.name, newShipClass: ship.id, oldAttributes: oldAttributes, newAttributes: player.attributes }); } purchaseCosmetic(cosmetic) { const debugLogger = window.debugLogger; const oldOwnedCount = this.ownedCosmetics.length; // Add to owned cosmetics this.ownedCosmetics.push(cosmetic.id); this.game.showNotification(`Cosmetic unlocked: ${cosmetic.name}`, 'success', 3000); if (debugLogger) debugLogger.logStep('Cosmetic purchase completed', { cosmeticId: cosmetic.id, cosmeticName: cosmetic.name, oldOwnedCount: oldOwnedCount, newOwnedCount: this.ownedCosmetics.length, totalOwnedCosmetics: this.ownedCosmetics.length }); } purchaseConsumable(consumable, quantity) { const debugLogger = window.debugLogger; const inventory = this.game.systems.inventory; // Create item object for inventory const item = { id: consumable.id, name: consumable.name, type: consumable.type, rarity: consumable.rarity, quantity: quantity, description: consumable.description, texture: consumable.texture, stats: consumable.stats || {}, acquired: new Date().toISOString() }; try { const oldInventorySize = inventory.items.length; inventory.addItem(item); if (debugLogger) debugLogger.logStep('Consumable purchase completed', { itemId: consumable.id, itemName: consumable.name, quantity: quantity, oldInventorySize: oldInventorySize, newInventorySize: inventory.items.length }); } catch (error) { console.error('[ECONOMY] Error adding consumable to inventory:', error); this.game.showNotification('Failed to add item to inventory', 'error', 3000); } } purchaseMaterial(material, quantity) { const debugLogger = window.debugLogger; const inventory = this.game.systems.inventory; // Create item object for inventory const item = { id: material.id, name: material.name, type: material.type, rarity: material.rarity, quantity: quantity, description: material.description, texture: material.texture, stackable: material.stackable || true, acquired: new Date().toISOString() }; try { const oldInventorySize = inventory.items.length; inventory.addItem(item); if (debugLogger) debugLogger.logStep('Material purchase completed', { itemId: material.id, itemName: material.name, quantity: quantity, oldInventorySize: oldInventorySize, newInventorySize: inventory.items.length }); } catch (error) { console.error('[ECONOMY] Error adding material to inventory:', error); this.game.showNotification('Failed to add item to inventory', 'error', 3000); } } // Currency management addCredits(amount, source = 'unknown') { const oldCredits = this.credits; this.credits += amount; // Add transaction this.addTransaction({ type: 'credit', amount: amount, source: source, balance: this.credits, timestamp: new Date().toISOString() }); console.log(`[ECONOMY] Added ${amount} credits from ${source}. New balance: ${this.credits}`); this.updateUI(); return this.credits - oldCredits; } addGems(amount, source = 'unknown') { const oldGems = this.gems; this.gems += amount; // Add transaction this.addTransaction({ type: 'gem', amount: amount, source: source, balance: this.gems, timestamp: new Date().toISOString() }); console.log(`[ECONOMY] Added ${amount} gems from ${source}. New balance: ${this.gems}`); this.updateUI(); return this.gems - oldGems; } canAfford(cost, currency = 'credits') { if (currency === 'credits') { return this.credits >= cost; } else if (currency === 'gems') { return this.gems >= cost; } else if (currency === 'premium') { return this.premiumCurrency >= cost; } return false; } // Transaction management addTransaction(transaction) { this.transactions.push(transaction); this.transactionHistory.push(transaction); // Keep only last 100 transactions in memory if (this.transactions.length > 100) { this.transactions = this.transactions.slice(-100); } } // 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(); } // Update shop UI if open this.updateShopUI(); } updateShopUI() { const debugLogger = window.debugLogger; console.log('[ECONOMY] updateShopUI called'); if (this.game.multiplayerMode && this.game.itemSystem) { // Support both .catalog getter (new) and .shopItemsByCategory (legacy) const shopItems = this.game.itemSystem.catalog || this.game.itemSystem.shopItemsByCategory || {}; const activeCategory = this.game.itemSystem.activeCategory || 'ships'; const categoryItems = shopItems[activeCategory] || []; this.renderShopItems(categoryItems); } else { // Singleplayer mode - use local shop data const items = Object.values(this.randomShopItems).flat(); // Convert to categorized structure for consistency const categorizedItems = this.randomShopItems || {}; this.renderShopItems(categorizedItems); } } renderShopItems(items) { const shopItemsElement = document.getElementById('shopItems'); if (!shopItemsElement) return; const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships'; console.log('[ECONOMY] Active shop category:', activeCategory); // Handle new shop data structure (items by category) or old structure (flat array) let categoryItems = []; 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; } 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
'; return; } shopItemsElement.innerHTML = categoryItems.map(item => { const canAfford = this.canAfford(item.price, item.currency); const isOwned = item.type === 'cosmetic' && this.ownedCosmetics.includes(item.id); // Generate image URL - server will serve images const imageUrl = this.getItemImageUrl(item); const placeholderUrl = 'https://dev.gameserver.galaxystrike.online/images/ui/placeholder.png'; return `${item.description}