/** * Galaxy Strike Online - Idle System * Manages offline progression and idle mechanics */ class IdleSystem { constructor(gameEngine) { this.game = gameEngine; // Idle settings this.maxOfflineTime = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds this.lastActiveTime = Date.now(); this.accumulatedTime = 0; // Track time for resource generation // Idle production rates (online rates) this.productionRates = { credits: 0.1, // 1 credit every 10 seconds (0.1 per second) experience: 0, // no auto experience - only from dungeons energy: 1/300 // 1 energy every 5 minutes (1/300 per second) }; // Offline rates (different from online rates) this.offlineProductionRates = { credits: 1/60, // 1 credit every 1 minute (1/60 per second) experience: 0, // no experience offline - only from dungeons energy: 1/300 // 1 energy every 5 minutes (same as online) }; // Offline rewards 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 }; } async initialize() { // Calculate offline progress if returning this.calculateOfflineProgress(); } calculateOfflineProgress(offlineTime = null) { const currentTime = Date.now(); const actualOfflineTime = offlineTime || (currentTime - this.lastActiveTime); // Cap offline time to maximum const cappedOfflineTime = Math.min(actualOfflineTime, this.maxOfflineTime); if (cappedOfflineTime < 60000) { // Less than 1 minute return; } // Calculate production const totalBonus = this.getTotalBonus(); const productionSeconds = cappedOfflineTime / 1000; this.offlineRewards = { credits: Math.floor(this.productionRates.credits * productionSeconds * totalBonus), experience: Math.floor(this.productionRates.experience * productionSeconds * totalBonus), energy: Math.min( this.game.systems.player.attributes.maxEnergy, Math.floor(this.productionRates.energy * productionSeconds) ), items: this.generateIdleItems(cappedOfflineTime) }; // Update achievements this.achievements.totalOfflineTime += cappedOfflineTime; this.achievements.maxOfflineSession = Math.max(this.achievements.maxOfflineSession, cappedOfflineTime); this.achievements.totalIdleCredits += this.offlineRewards.credits; this.achievements.totalIdleExperience += this.offlineRewards.experience; // Show offline rewards notification this.showOfflineRewards(cappedOfflineTime); } getTotalBonus() { return this.bonuses.premium * this.bonuses.guild * this.bonuses.research; } generateIdleItems(offlineTime) { const items = []; const hours = offlineTime / (1000 * 60 * 60); // Chance to find items based on offline time const itemChance = Math.min(0.5, hours * 0.05); if (Math.random() < itemChance) { const itemCount = Math.floor(hours / 2) + 1; for (let i = 0; i < itemCount; i++) { const rarity = this.getRandomItemRarity(); const item = this.game.systems.inventory.generateItem('consumable', rarity); items.push(item); } } return items; } getRandomItemRarity() { const roll = Math.random(); if (roll < 0.05) return 'legendary'; if (roll < 0.15) return 'epic'; if (roll < 0.35) return 'rare'; if (roll < 0.65) return 'uncommon'; return 'common'; } showOfflineRewards(offlineTime) { const timeString = this.game.formatTime(offlineTime); this.game.showNotification( `Welcome back! You were offline for ${timeString}`, 'info', 5000 ); // Format rewards message let rewardsMessage = 'Offline Rewards:\n'; if (this.offlineRewards.credits > 0) { rewardsMessage += `+${this.game.formatNumber(this.offlineRewards.credits)} credits\n`; } if (this.offlineRewards.experience > 0) { rewardsMessage += `+${this.game.formatNumber(this.offlineRewards.experience)} XP\n`; } if (this.offlineRewards.energy > 0) { rewardsMessage += `+${this.game.formatNumber(this.offlineRewards.energy)} energy\n`; } if (this.offlineRewards.items.length > 0) { rewardsMessage += `+${this.offlineRewards.items.length} items\n`; } this.game.showNotification(rewardsMessage, 'success', 5000); } claimOfflineRewards() { // In multiplayer mode, use server communication if (window.smartSaveManager?.isMultiplayer) { this.game.showNotification('Claiming offline rewards from server...', 'info', 2000); // Send request to server if (window.game && window.game.socket) { window.game.socket.emit('claimOfflineRewards', {}); } else { this.game.showNotification('Not connected to server', 'error', 3000); } return; } // Singleplayer mode - use local logic if (this.offlineRewards.credits === 0 && this.offlineRewards.experience === 0 && this.offlineRewards.items.length === 0) { this.game.showNotification('No offline rewards to claim', 'info', 3000); return; } // Give rewards if (this.offlineRewards.credits > 0) { this.game.systems.economy.addCredits(this.offlineRewards.credits, 'offline'); } if (this.offlineRewards.experience > 0) { this.game.systems.player.addExperience(this.offlineRewards.experience); } if (this.offlineRewards.energy > 0) { this.game.systems.player.restoreEnergy(this.offlineRewards.energy); } // Add items to inventory if (this.offlineRewards.items.length > 0) { const inventory = this.game.systems.inventory; this.offlineRewards.items.forEach(item => { inventory.addItem(item); }); } // Reset offline rewards this.offlineRewards = { credits: 0, experience: 0, energy: 0, items: [] }; this.game.showNotification('Offline rewards claimed!', 'success', 3000); } // Active idle production update(deltaTime) { if (this.game.state.paused) return; // Use real computer time delta const seconds = deltaTime / 1000; const totalBonus = this.getTotalBonus(); // Only add resources once per second, not every frame this.accumulatedTime += seconds; if (this.accumulatedTime >= 1.0) { // Calculate active production const activeCredits = Math.floor(this.productionRates.credits * totalBonus); const activeExperience = Math.floor(this.productionRates.experience * totalBonus); // const activeEnergy = this.productionRates.energy * totalBonus * 0.1; // Energy is handled differently // Add resources if (activeCredits > 0) { this.game.systems.economy.addCredits(activeCredits, 'idle'); } if (activeExperience > 0) { this.game.systems.player.addExperience(activeExperience); } // Regenerate energy this.game.systems.player.restoreEnergy(this.productionRates.energy); // Reset accumulated time, keeping any remainder this.accumulatedTime -= 1.0; // Debugging: Log when resources are added // console.debug(`[IDLE] Added ${activeCredits} credits and ${activeExperience} XP. Accumulated time: ${this.accumulatedTime.toFixed(2)}s`); } // Update last active time for offline calculations this.lastActiveTime = Date.now(); } // Upgrade production rates upgradeProduction(type) { const upgradeCosts = { credits: 100, experience: 150, energy: 80 }; const cost = upgradeCosts[type]; if (!cost || this.game.systems.economy.credits < cost) { return false; } this.game.systems.economy.removeCredits(cost); switch (type) { case 'credits': this.productionRates.credits += 2; break; case 'experience': this.productionRates.experience += 1; break; case 'energy': this.productionRates.energy += 0.2; break; } this.game.showNotification(`Production upgraded: ${type}!`, 'success', 3000); return true; } // Bonus management setBonus(type, value) { if (this.bonuses[type] !== undefined) { this.bonuses[type] = value; this.game.showNotification(`${type} bonus set to ${value}x`, 'info', 3000); } } // Achievement checking checkAchievements() { const achievements = [ { id: 'idle_warrior', name: 'Idle Warrior', description: 'Earn 1,000,000 credits from idle', condition: () => this.achievements.totalIdleCredits >= 1000000, reward: { gems: 50, experience: 1000 } }, { id: 'time_master', name: 'Time Master', description: 'Accumulate 24 hours of offline time', condition: () => this.achievements.totalOfflineTime >= 24 * 60 * 60 * 1000, reward: { gems: 25, experience: 500 } }, { id: 'marathon_idle', name: 'Marathon Idle', description: 'Be offline for more than 12 hours at once', condition: () => this.achievements.maxOfflineSession >= 12 * 60 * 60 * 1000, reward: { gems: 100, experience: 2000 } } ]; achievements.forEach(achievement => { if (achievement.condition()) { this.unlockAchievement(achievement); } }); } unlockAchievement(achievement) { this.game.showNotification(`Achievement Unlocked: ${achievement.name}!`, 'success', 5000); this.game.showNotification(achievement.description, 'info', 3000); // Give rewards if (achievement.reward.gems) { this.game.systems.economy.addGems(achievement.reward.gems, 'achievement'); } if (achievement.reward.experience) { this.game.systems.player.addExperience(achievement.reward.experience); } } // UI updates updateUI() { const offlineTimeElement = document.getElementById('offlineTime'); const offlineResourcesElement = document.getElementById('offlineResources'); const claimOfflineBtn = document.getElementById('claimOfflineBtn'); if (offlineTimeElement) { const totalRewards = this.offlineRewards.credits + this.offlineRewards.experience + (this.offlineRewards.items.length * 100); offlineTimeElement.textContent = totalRewards > 0 ? 'Available' : 'None'; } if (offlineResourcesElement) { const totalRewards = this.offlineRewards.credits + this.offlineRewards.experience + (this.offlineRewards.items.length * 100); offlineResourcesElement.textContent = this.game.formatNumber(totalRewards); } if (claimOfflineBtn) { const hasRewards = this.offlineRewards.credits > 0 || this.offlineRewards.experience > 0 || this.offlineRewards.items.length > 0; claimOfflineBtn.disabled = !hasRewards; } } // Save/Load save() { return { lastActiveTime: this.lastActiveTime, productionRates: this.productionRates, bonuses: this.bonuses, achievements: this.achievements, offlineRewards: this.offlineRewards }; } load(data) { if (data.lastActiveTime) this.lastActiveTime = data.lastActiveTime; if (data.productionRates) this.productionRates = { ...this.productionRates, ...data.productionRates }; if (data.bonuses) this.bonuses = { ...this.bonuses, ...data.bonuses }; if (data.achievements) this.achievements = { ...this.achievements, ...data.achievements }; if (data.offlineRewards) this.offlineRewards = data.offlineRewards; } }