/** * Galaxy Strike Online - Server Idle System * Manages offline progression and idle mechanics */ class IdleSystem { constructor() { // Idle settings this.maxOfflineTime = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds this.playerLastActive = new Map(); // userId -> last active timestamp this.playerProductionRates = new Map(); // userId -> production rates this.playerAchievements = new Map(); // userId -> achievements // Default production rates this.defaultProductionRates = { credits: 0.1, // 1 credit every 10 seconds (0.1 per second) experience: 0, // no auto experience - only from dungeons energy: 1/300 // 1 energy every 5 minutes (1/300 per second) }; // Offline rates (different from online rates) this.offlineProductionRates = { credits: 1/60, // 1 credit every 1 minute (1/60 per second) experience: 0, // no experience offline - only from dungeons energy: 1/300 // 1 energy every 5 minutes (same as online) }; // Idle rewards this.offlineRewards = { credits: 0, experience: 0, energy: 0, items: [] }; // Idle bonuses this.bonuses = { premium: 1.0, guild: 1.0, research: 1.0 }; // Idle achievements this.achievements = { totalOfflineTime: 0, maxOfflineSession: 0, totalIdleCredits: 0, totalIdleExperience: 0 }; } 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); } } updateLastActive(userId) { this.playerLastActive.set(userId, Date.now()); } updateProductionRates(userId, rates) { this.playerProductionRates.set(userId, { ...this.defaultProductionRates, ...rates }); } calculateOfflineProgress(userId) { this.initializePlayerData(userId); const lastActive = this.playerLastActive.get(userId); const currentTime = Date.now(); const offlineTime = currentTime - lastActive; // Cap offline time to maximum const cappedOfflineTime = Math.min(offlineTime, this.maxOfflineTime); if (cappedOfflineTime <= 0) { return { offlineTime: 0, rewards: { credits: 0, experience: 0, energy: 0, items: [] } }; } const productionRates = this.playerProductionRates.get(userId); const achievements = this.playerAchievements.get(userId); // Calculate offline rewards using offline rates const offlineSeconds = Math.floor(cappedOfflineTime / 1000); const offlineRates = this.offlineProductionRates; const rewards = { credits: Math.floor(offlineRates.credits * offlineSeconds), experience: Math.floor(offlineRates.experience * offlineSeconds), energy: Math.floor(offlineRates.energy * offlineSeconds), items: [] }; // Update achievements achievements.totalOfflineTime += cappedOfflineTime; achievements.maxOfflineSession = Math.max(achievements.maxOfflineSession, cappedOfflineTime); achievements.totalIdleCredits += rewards.credits; achievements.totalIdleExperience += rewards.experience; // Update last active time this.playerLastActive.set(userId, currentTime); return { offlineTime: cappedOfflineTime, offlineSeconds, rewards, productionRates }; } applyIdleRewards(userId, playerData) { const progress = this.calculateOfflineProgress(userId); if (progress.offlineTime > 0) { // Apply rewards to player data if (playerData.stats) { playerData.stats.credits = (playerData.stats.credits || 0) + progress.rewards.credits; playerData.stats.experience = (playerData.stats.experience || 0) + progress.rewards.experience; } if (playerData.resources) { playerData.resources.energy = (playerData.resources.energy || 0) + progress.rewards.energy; } // Add items if any if (progress.rewards.items && progress.rewards.items.length > 0) { if (!playerData.inventory) { playerData.inventory = []; } playerData.inventory.push(...progress.rewards.items); } } return progress; } getProductionRates(userId) { this.initializePlayerData(userId); return this.playerProductionRates.get(userId); } updateBonuses(userId, bonuses) { this.initializePlayerData(userId); const currentRates = this.playerProductionRates.get(userId); // Apply bonuses to production rates const bonusMultiplier = (bonuses.premium || this.defaultBonuses.premium) * (bonuses.guild || this.defaultBonuses.guild) * (bonuses.research || this.defaultBonuses.research); const updatedRates = {}; for (const [resource, baseRate] of Object.entries(this.defaultProductionRates)) { updatedRates[resource] = Math.floor(baseRate * bonusMultiplier); } this.playerProductionRates.set(userId, updatedRates); return updatedRates; } getAchievements(userId) { this.initializePlayerData(userId); return this.playerAchievements.get(userId); } getIdleStats(userId) { this.initializePlayerData(userId); const achievements = this.playerAchievements.get(userId); const productionRates = this.playerProductionRates.get(userId); return { lastActive: this.playerLastActive.get(userId), productionRates, achievements, maxOfflineTime: this.maxOfflineTime, currentOfflineTime: Date.now() - this.playerLastActive.get(userId) }; } // Calculate idle income for a specific time period calculateIncomeForTime(userId, timeInSeconds) { this.initializePlayerData(userId); const productionRates = this.playerProductionRates.get(userId); return { credits: Math.floor(productionRates.credits * timeInSeconds), experience: Math.floor(productionRates.experience * timeInSeconds), energy: Math.floor(productionRates.energy * timeInSeconds) }; } // Get time until next milestone getTimeUntilMilestone(userId, targetCredits = 10000) { this.initializePlayerData(userId); const productionRates = this.playerProductionRates.get(userId); const currentOfflineTime = Date.now() - this.playerLastActive.get(userId); if (productionRates.credits <= 0) { return { seconds: Infinity, hours: Infinity }; } const creditsPerSecond = productionRates.credits; const secondsNeeded = Math.ceil(targetCredits / creditsPerSecond); return { seconds: secondsNeeded, minutes: Math.ceil(secondsNeeded / 60), hours: Math.ceil(secondsNeeded / 3600) }; } // Process idle rewards for multiple users (batch processing) processBatchIdleRewards(userIds) { const results = new Map(); for (const userId of userIds) { try { const progress = this.calculateOfflineProgress(userId); results.set(userId, { success: true, progress }); } catch (error) { results.set(userId, { success: false, error: error.message }); } } return results; } // 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, energy: 0 }; } 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(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; } // Reset idle data for a user resetPlayerData(userId) { this.playerLastActive.delete(userId); this.playerProductionRates.delete(userId); this.playerAchievements.delete(userId); } // Get global idle statistics getGlobalStats() { const totalPlayers = this.playerLastActive.size; let totalOfflineTime = 0; let totalIdleCredits = 0; let totalIdleExperience = 0; for (const achievements of this.playerAchievements.values()) { totalOfflineTime += achievements.totalOfflineTime; totalIdleCredits += achievements.totalIdleCredits; totalIdleExperience += achievements.totalIdleExperience; } return { totalPlayers, totalOfflineTime, totalIdleCredits, totalIdleExperience, averageOfflineTime: totalPlayers > 0 ? totalOfflineTime / totalPlayers : 0 }; } } module.exports = IdleSystem;