using System; using AppleHills.Core.Settings; using UnityEngine; using Core; using Core.Lifecycle; using UnityEngine.SceneManagement; using Cinematics; using Core.SaveLoad; namespace Bootstrap { /// /// Controller for the boot scene that coordinates bootstrap initialization with loading screen /// public class BootSceneController : ManagedBehaviour { [SerializeField] private string mainSceneName = "AppleHillsOverworld"; [SerializeField] private float minDelayAfterBoot = 0.5f; // Small delay after boot to ensure smooth transition [SerializeField] private bool debugMode = false; [SerializeField] private InitialLoadingScreen initialLoadingScreen; // Reference to our specialized loading screen // Progress distribution between bootstrap and scene loading [SerializeField, Range(0.1f, 0.9f)] private float bootProgressWeight = 0.5f; // Default 50/50 split private enum LoadingPhase { Bootstrap, SceneLoading } private LoadingPhase _currentPhase = LoadingPhase.Bootstrap; private bool _bootComplete = false; private bool _hasStartedLoading = false; private float _sceneLoadingProgress = 0f; private LogVerbosity _logVerbosity = LogVerbosity.Warning; internal override void OnManagedAwake() { Logging.Debug("BootSceneController.Awake() - Initializing loading screen DURING bootstrap"); // Validate loading screen exists if (initialLoadingScreen == null) { Debug.LogError("[BootSceneController] No InitialLoadingScreen assigned! Please assign it in the inspector."); return; } // Show the loading screen immediately with our combined progress provider // This needs to happen DURING bootstrap to show progress initialLoadingScreen.ShowLoadingScreen(GetCombinedProgress); // Subscribe to loading screen completion event initialLoadingScreen.OnLoadingScreenFullyHidden += OnInitialLoadingComplete; // Subscribe to boot progress for real-time updates during bootstrap CustomBoot.OnBootProgressChanged += OnBootProgressChanged; #if UNITY_EDITOR _logVerbosity = DeveloperSettingsProvider.Instance.GetSettings().bootstrapLogVerbosity; #elif DEVELOPMENT_BUILD _logVerbosity = LogVerbosity.Debug; #elif RELEASE_BUILD _logVerbosity = LogVerbosity.Warning; #endif // In debug mode, log additional information if (debugMode) { InvokeRepeating(nameof(LogDebugInfo), 0.1f, 0.5f); } } internal override void OnManagedStart() { Logging.Debug("BootSceneController.OnManagedStart() - Boot is GUARANTEED complete, starting scene loading"); // Boot is GUARANTEED complete at this point - that's the whole point of OnManagedStart! // No need to subscribe to OnBootCompleted or check CustomBoot.Initialised _bootComplete = true; _currentPhase = LoadingPhase.SceneLoading; // Start loading the main scene after a small delay // This prevents jerky transitions if boot happens very quickly Invoke(nameof(StartLoadingMainMenu), minDelayAfterBoot); } internal override void OnManagedDestroy() { // Manual cleanup for events if (initialLoadingScreen != null) { initialLoadingScreen.OnLoadingScreenFullyHidden -= OnInitialLoadingComplete; } CustomBoot.OnBootProgressChanged -= OnBootProgressChanged; } /// /// Called when the initial loading screen is fully hidden /// private void OnInitialLoadingComplete() { Logging.Debug("Initial loading screen fully hidden, boot sequence completed"); // Play the intro cinematic if available if (CinematicsManager.Instance != null) { Logging.Debug("Attempting to play intro cinematic"); // Use LoadAndPlayCinematic to play the intro sequence CinematicsManager.Instance.LoadAndPlayCinematic("IntroSequence", false); CinematicsManager.Instance.ChangeCinematicBackgroundColour(new Color(0f, 0f, 0f, 1f)); //PlayerHudManager.Instance.ResizeCinematicPlayer(); // Immediately unload the StartingScene - no need to wait for cinematic to finish // since CinematicsManager is bootstrapped and won't be unloaded UnloadStartingScene(); } else { // If no cinematics manager, unload the StartingScene directly UnloadStartingScene(); } } /// /// Progress provider that combines bootstrap and scene loading progress /// private float GetCombinedProgress() { switch (_currentPhase) { case LoadingPhase.Bootstrap: // Scale bootstrap progress from 0 to bootProgressWeight return CustomBoot.CurrentProgress * bootProgressWeight; case LoadingPhase.SceneLoading: // Scale scene loading progress from bootProgressWeight to 1.0 return bootProgressWeight + (_sceneLoadingProgress * (1f - bootProgressWeight)); default: return 0f; } } private void OnBootProgressChanged(float progress) { if (debugMode) { Logging.Debug($"Bootstrap progress: {progress:P0}, Combined: {GetCombinedProgress():P0}"); } } private void LogDebugInfo() { Logging.Debug($"Debug - Phase: {_currentPhase}, Bootstrap: {CustomBoot.CurrentProgress:P0}, " + $"Scene: {_sceneLoadingProgress:P0}, Combined: {GetCombinedProgress():P0}, Boot Complete: {_bootComplete}"); } private void StartLoadingMainMenu() { if (_hasStartedLoading) return; _hasStartedLoading = true; _currentPhase = LoadingPhase.SceneLoading; LoadMainScene(); } private async void LoadMainScene() { Logging.Debug($"Loading main menu scene: {mainSceneName}"); try { // Initialize scene loading progress to 0 to ensure proper remapping _sceneLoadingProgress = 0f; // Create a custom progress reporter using a custom class var progressHandler = new ProgressHandler(value => { // Store the raw scene loading progress (0-1) _sceneLoadingProgress = value; if (debugMode) { Logging.Debug($"Scene loading raw: {value:P0}, Combined: {GetCombinedProgress():P0}"); } }); // Step 1: Additively load the main menu scene - don't unload StartingScene yet var op = SceneManager.LoadSceneAsync(mainSceneName, LoadSceneMode.Additive); // Disable scene activation until we're ready to show it op.allowSceneActivation = true; // Track progress while loading while (!op.isDone) { progressHandler.ReportProgress(op.progress); await System.Threading.Tasks.Task.Yield(); } // Update the current gameplay scene in SceneManagerService SceneManagerService.Instance.CurrentGameplayScene = mainSceneName; // Ensure progress is complete _sceneLoadingProgress = 1f; // CRITICAL: Broadcast lifecycle events so components get their OnSceneReady callbacks Logging.Debug($"Broadcasting OnSceneReady for: {mainSceneName}"); LifecycleManager.Instance?.BroadcastSceneReady(mainSceneName); // Restore scene data for the main menu if (SaveLoadManager.Instance != null) { Logging.Debug($"Restoring scene data for: {mainSceneName}"); SaveLoadManager.Instance.RestoreSceneData(); } // Step 2: Scene is fully loaded, now hide the loading screen // This will trigger OnInitialLoadingComplete via the event when animation completes initialLoadingScreen.HideLoadingScreen(); // Step 3: The OnInitialLoadingComplete method will handle playing the intro cinematic // Step 4: StartingScene will be unloaded after the cinematic completes in OnIntroCinematicFinished } catch (Exception e) { Debug.LogError($"[BootSceneController] Error loading main menu: {e.Message}"); // Still try to hide the loading screen even if there was an error initialLoadingScreen.HideLoadingScreen(); } } /// /// Unloads the StartingScene, completing the transition to the main menu /// private async void UnloadStartingScene() { try { // Get the current scene (StartingScene) Scene currentScene = SceneManager.GetActiveScene(); string startingSceneName = currentScene.name; Logging.Debug($"Unloading StartingScene: {startingSceneName}"); // Unload the StartingScene await SceneManager.UnloadSceneAsync(startingSceneName); // Set the main menu scene as the active scene Scene mainMenuScene = SceneManager.GetSceneByName(mainSceneName); SceneManager.SetActiveScene(mainMenuScene); Logging.Debug($"Transition complete: {startingSceneName} unloaded, {mainSceneName} is now active"); // Destroy the boot scene controller since its job is done Destroy(gameObject); } catch (Exception e) { Logging.Warning($"Error unloading StartingScene: {e.Message}"); } } /// /// Helper class to handle progress reporting without running into explicit interface implementation issues /// private class ProgressHandler { private Action _progressAction; public ProgressHandler(Action progressAction) { _progressAction = progressAction; } public void ReportProgress(float value) { _progressAction?.Invoke(value); } } } }