/** * Campaign Manager * Handles The Academy's 30-level campaign progression system */ class CampaignManager { constructor() { this.initializeProgress(); // Map levels to features they unlock this.featureUnlocks = { 3: ['video'], // Level 3: Video playback 6: ['webcam'], // Level 6: Webcam mirror 7: ['dual-video'], // Level 7: Dual video 8: ['tts'], // Level 8: Text-to-speech 9: ['quad-video'], // Level 9: Quad video 10: ['hypno-spiral'], // Level 10: Hypno spiral 11: ['hypno-captions'], // Level 11: Hypno with captions 12: ['dynamic-captions'], // Level 12: Dynamic captions 13: ['tts-hypno-sync'], // Level 13: TTS + Hypno sync 15: ['interruptions'], // Level 15: Random interruptions 16: ['denial-training'], // Level 16: Denial mechanics 17: ['popup-images'], // Level 17: Popup images 18: ['edge-counter'], // Level 18: Edge counter 19: ['sensory-overload'], // Level 19: All features combined 25: ['advanced-mode'], // Level 25: Advanced training mode 30: ['graduation'] // Level 30: Graduate status }; } /** * Initialize academy progress if it doesn't exist */ initializeProgress() { console.log('đŸŽ¯ CampaignManager: Initializing progress...'); console.log('đŸŽ¯ window.gameData exists:', !!window.gameData); console.log('đŸŽ¯ window.gameData.academyProgress exists:', !!window.gameData?.academyProgress); // gameData is available globally via window.gameData if (!window.gameData.academyProgress) { console.log('🆕 Creating fresh academy progress'); window.gameData.academyProgress = { version: 1, currentLevel: 1, highestUnlockedLevel: 1, completedLevels: [], currentArc: 'Foundation', failedAttempts: {}, totalSessionTime: 0, lastPlayedLevel: null, lastPlayedDate: null, graduationCompleted: false, freeplayUnlocked: false, ascendedModeUnlocked: false, selectedPath: null, featuresUnlocked: [] }; this.saveProgress(); } else { console.log('✅ Existing academy progress loaded:', { currentLevel: window.gameData.academyProgress.currentLevel, highestUnlocked: window.gameData.academyProgress.highestUnlockedLevel, completedLevels: window.gameData.academyProgress.completedLevels, features: window.gameData.academyProgress.featuresUnlocked }); // 🔧 Migration: Fix currentLevel if it's behind the completion progress this.fixCurrentLevelMismatch(); // 🔧 Migration: Fix highestUnlockedLevel if it was set too high by old dev mode this.fixHighestUnlockedLevel(); // Log the actual localStorage contents for debugging const stored = localStorage.getItem('webGame-data'); if (stored) { try { const parsed = JSON.parse(stored); console.log('🔍 localStorage academyProgress:', parsed.academyProgress); } catch (e) { console.error('❌ Failed to parse localStorage data:', e); } } else { console.warn('âš ī¸ No data in localStorage yet'); } } } /** * Fix highestUnlockedLevel if it was set incorrectly by old dev mode */ fixHighestUnlockedLevel() { const progress = window.gameData.academyProgress; const completedLevels = progress.completedLevels || []; // Calculate what the highest unlocked level SHOULD be based on completions let expectedHighest = 1; // Always at least level 1 if (completedLevels.length > 0) { const highestCompleted = Math.max(...completedLevels); expectedHighest = Math.min(highestCompleted + 1, 30); // Next level after highest completed } // If highestUnlockedLevel is suspiciously high (like 30 when you've only completed 7 levels), // it was probably set by old dev mode - reset it if (progress.highestUnlockedLevel > expectedHighest) { console.warn(`🔧 Fixing highestUnlockedLevel: was ${progress.highestUnlockedLevel}, should be ${expectedHighest}`); console.log(`📊 Completed levels: [${completedLevels.join(', ')}]`); console.log(`🏆 Highest completed: ${completedLevels.length > 0 ? Math.max(...completedLevels) : 'none'}`); progress.highestUnlockedLevel = expectedHighest; this.saveProgress(); console.log(`✅ Fixed: highestUnlockedLevel is now ${expectedHighest}`); } } /** * Fix currentLevel if it's behind completed levels (migration helper) */ fixCurrentLevelMismatch() { const progress = window.gameData.academyProgress; const completedLevels = progress.completedLevels || []; if (completedLevels.length === 0) return; // No completed levels, currentLevel should be 1 // Find the highest completed level const highestCompleted = Math.max(...completedLevels); // currentLevel should be at least highestCompleted + 1 (next level after highest completed) const expectedCurrentLevel = Math.min(highestCompleted + 1, 30); if (progress.currentLevel < expectedCurrentLevel) { console.warn(`🔧 Fixing currentLevel mismatch: was ${progress.currentLevel}, should be ${expectedCurrentLevel}`); console.log(`📊 Completed levels: [${completedLevels.join(', ')}]`); console.log(`🏆 Highest completed: ${highestCompleted}`); progress.currentLevel = expectedCurrentLevel; progress.currentArc = this.getArcForLevel(expectedCurrentLevel); // Also ensure highestUnlockedLevel is correct if (progress.highestUnlockedLevel < expectedCurrentLevel) { progress.highestUnlockedLevel = expectedCurrentLevel; console.log(`🔓 Also updated highestUnlockedLevel to ${expectedCurrentLevel}`); } this.saveProgress(); console.log(`✅ Fixed: currentLevel is now ${progress.currentLevel} (${progress.currentArc} Arc)`); } } /** * Save progress to localStorage */ saveProgress() { try { console.log(`💾 saveProgress() CALLED - completedLevels:`, window.gameData.academyProgress?.completedLevels); console.trace('Stack trace:'); // Ensure academyProgress exists if (!window.gameData.academyProgress) { console.error('❌ academyProgress is missing from gameData!'); return; } // Save to localStorage directly const dataToSave = JSON.stringify(window.gameData); localStorage.setItem('webGame-data', dataToSave); // Verify the save worked const verified = localStorage.getItem('webGame-data'); if (!verified) { console.error('❌ Failed to save to localStorage - data not persisted'); return; } // Also update simpleDataManager if available if (window.simpleDataManager) { window.simpleDataManager.data = window.gameData; window.simpleDataManager.saveData(); } console.log('💾 Campaign progress saved successfully:', { currentLevel: window.gameData.academyProgress.currentLevel, highestUnlocked: window.gameData.academyProgress.highestUnlockedLevel, completed: window.gameData.academyProgress.completedLevels, features: window.gameData.academyProgress.featuresUnlocked, savedSize: dataToSave.length + ' bytes' }); } catch (error) { console.error('❌ Error saving campaign progress:', error); } } /** * Get list of unlocked levels * @returns {Array} Array of unlocked level numbers */ getUnlockedLevels() { const unlocked = []; const highest = window.gameData.academyProgress.highestUnlockedLevel; for (let i = 1; i <= highest; i++) { unlocked.push(i); } return unlocked; } /** * Check if a level is unlocked * @param {number} levelNum - Level number to check * @returns {boolean} */ isLevelUnlocked(levelNum) { // If dev mode is active, all levels are unlocked if (window.isDevMode && window.isDevMode()) { return true; } // Otherwise, check against the highest unlocked level return levelNum <= window.gameData.academyProgress.highestUnlockedLevel; } /** * Check if a level is completed * @param {number} levelNum - Level number to check * @returns {boolean} */ isLevelCompleted(levelNum) { return window.gameData.academyProgress.completedLevels.includes(levelNum); } /** * Start a level * @param {number} levelNum - Level number to start * @returns {Object|null} Level config or null if invalid */ startLevel(levelNum) { // Validate level is unlocked if (!this.isLevelUnlocked(levelNum)) { console.error(`Level ${levelNum} is locked. Complete previous levels first.`); return null; } // Update current level window.gameData.academyProgress.currentLevel = levelNum; window.gameData.academyProgress.lastPlayedLevel = levelNum; window.gameData.academyProgress.lastPlayedDate = new Date().toISOString(); // Update current arc window.gameData.academyProgress.currentArc = this.getArcForLevel(levelNum); this.saveProgress(); console.log(`Started Level ${levelNum} (${gameData.academyProgress.currentArc} Arc)`); return { levelNum, arc: window.gameData.academyProgress.currentArc, isCheckpoint: this.isCheckpointLevel(levelNum) }; } /** * Complete a level * @param {number} levelNum - Level number completed * @param {Object} sessionData - Data from the session * @returns {Object} Completion results */ completeLevel(levelNum, sessionData = {}) { const progress = window.gameData.academyProgress; console.log(`🎓 Completing Level ${levelNum}`, { currentCompleted: progress.completedLevels, currentHighest: progress.highestUnlockedLevel }); // Add to completed levels if not already there if (!progress.completedLevels.includes(levelNum)) { progress.completedLevels.push(levelNum); console.log(`✅ Added ${levelNum} to completed levels`); console.log(`📋 completedLevels array is now:`, progress.completedLevels); } // Check for feature unlocks const unlockedFeatures = []; if (this.featureUnlocks[levelNum]) { const newFeatures = this.featureUnlocks[levelNum]; newFeatures.forEach(feature => { if (!progress.featuresUnlocked.includes(feature)) { progress.featuresUnlocked.push(feature); unlockedFeatures.push(feature); console.log(`🎁 Feature unlocked: ${feature}`); } }); } // Unlock next level and advance currentLevel const nextLevel = levelNum + 1; let nextLevelUnlocked = false; if (nextLevel <= 30 && nextLevel > progress.highestUnlockedLevel) { progress.highestUnlockedLevel = nextLevel; nextLevelUnlocked = true; console.log(`🔓 Level ${nextLevel} unlocked! (highestUnlockedLevel updated to ${nextLevel})`); } else if (nextLevel <= 30) { console.log(`â„šī¸ Level ${nextLevel} already unlocked (highest: ${progress.highestUnlockedLevel})`); } // Advance currentLevel to the next level if available if (nextLevel <= 30) { progress.currentLevel = nextLevel; progress.currentArc = this.getArcForLevel(nextLevel); console.log(`âžĄī¸ Advanced currentLevel to ${nextLevel} (${progress.currentArc} Arc)`); } else { console.log(`🎓 All levels completed! (Level ${levelNum} was final)`); } // Update session time if (sessionData.duration) { progress.totalSessionTime += sessionData.duration; } // Check for arc completion const arcComplete = this.checkArcCompletion(levelNum); // Reset consecutive failures on success progress.consecutiveLevelsWithoutFailure = (progress.consecutiveLevelsWithoutFailure || 0) + 1; console.log(`💾 BEFORE saveProgress() - completedLevels:`, progress.completedLevels); this.saveProgress(); // Verify save immediately after const verifyData = localStorage.getItem('webGame-data'); if (verifyData) { const parsed = JSON.parse(verifyData); console.log(`✅ VERIFIED localStorage after save - completedLevels:`, parsed.academyProgress.completedLevels); } console.log(`✅ Level ${levelNum} completed!`); return { levelCompleted: levelNum, nextLevelUnlocked, nextLevel: nextLevelUnlocked ? nextLevel : null, arcComplete, completedArc: arcComplete ? this.getArcForLevel(levelNum) : null, unlockedFeatures: unlockedFeatures }; } /** * Fail a level * @param {number} levelNum - Level that was failed * @param {string} reason - Failure reason ('cumming', 'abandoned', 'feature-closed') */ failLevel(levelNum, reason) { const progress = window.gameData.academyProgress; // Increment failed attempts for this level if (!progress.failedAttempts[levelNum]) { progress.failedAttempts[levelNum] = 0; } progress.failedAttempts[levelNum]++; // Track total failures if (!progress.totalFailedAttempts) { progress.totalFailedAttempts = 0; } progress.totalFailedAttempts++; // Track failure by reason if (!progress.failuresByReason) { progress.failuresByReason = { cumming: 0, abandoned: 0, featureClosed: 0 }; } if (progress.failuresByReason[reason] !== undefined) { progress.failuresByReason[reason]++; } // Reset consecutive success streak progress.consecutiveLevelsWithoutFailure = 0; this.saveProgress(); console.log(`❌ Level ${levelNum} failed (${reason}). Attempts: ${progress.failedAttempts[levelNum]}`); } /** * Get the arc name for a level * @param {number} levelNum - Level number * @returns {string} Arc name */ getArcForLevel(levelNum) { if (levelNum >= 1 && levelNum <= 5) return 'Foundation'; if (levelNum >= 6 && levelNum <= 10) return 'Feature Discovery'; if (levelNum >= 11 && levelNum <= 15) return 'Mind & Body'; if (levelNum >= 16 && levelNum <= 20) return 'Advanced Training'; if (levelNum >= 21 && levelNum <= 25) return 'Path Specialization'; if (levelNum >= 26 && levelNum <= 30) return 'Ultimate Mastery'; return 'Unknown'; } /** * Get current arc * @returns {string} Current arc name */ getCurrentArc() { return window.gameData.academyProgress.currentArc; } /** * Check if level is a checkpoint (1, 5, 10, 15, 20, 25) * @param {number} levelNum - Level number * @returns {boolean} */ isCheckpointLevel(levelNum) { return [1, 5, 10, 15, 20, 25].includes(levelNum); } /** * Check if an arc is complete * @param {number} levelNum - Level just completed * @returns {boolean} */ checkArcCompletion(levelNum) { const arcEndLevels = [5, 10, 15, 20, 25, 30]; return arcEndLevels.includes(levelNum); } /** * Get progress statistics * @returns {Object} Progress stats */ getProgressStats() { const progress = window.gameData.academyProgress; return { currentLevel: progress.currentLevel, highestUnlockedLevel: progress.highestUnlockedLevel, completedLevels: progress.completedLevels.length, totalLevels: 30, percentComplete: (progress.completedLevels.length / 30) * 100, currentArc: progress.currentArc, totalSessionTime: progress.totalSessionTime, failedAttempts: Object.values(progress.failedAttempts).reduce((a, b) => a + b, 0), consecutiveSuccesses: progress.consecutiveLevelsWithoutFailure || 0, featuresUnlocked: progress.featuresUnlocked || [] }; } /** * Check if a feature is unlocked * @param {string} featureName - Feature to check * @returns {boolean} True if unlocked */ isFeatureUnlocked(featureName) { const progress = window.gameData.academyProgress; return progress.featuresUnlocked && progress.featuresUnlocked.includes(featureName); } /** * Get all unlocked features * @returns {Array} List of unlocked features */ getUnlockedFeatures() { const progress = window.gameData.academyProgress; return progress.featuresUnlocked || []; } /** * Get features that will be unlocked at a specific level * @param {number} levelNum - Level number * @returns {Array} Features unlocked at this level */ getFeaturesForLevel(levelNum) { return this.featureUnlocks[levelNum] || []; } /** * Reset progress (for testing or fresh start) */ resetProgress() { window.gameData.academyProgress = { version: 1, currentLevel: 1, highestUnlockedLevel: 1, completedLevels: [], currentArc: 'Foundation', failedAttempts: {}, totalSessionTime: 0, lastPlayedLevel: null, lastPlayedDate: null, graduationCompleted: false, freeplayUnlocked: false, ascendedModeUnlocked: false, selectedPath: null, featuresUnlocked: [], totalFailedAttempts: 0, failuresByReason: { cumming: 0, abandoned: 0, featureClosed: 0 }, consecutiveLevelsWithoutFailure: 0 }; this.saveProgress(); console.log('🔄 Academy progress reset'); } } // Create singleton instance and expose globally window.campaignManager = new CampaignManager(); console.log('🎓 Campaign Manager initialized');