using System; using System.Collections.Generic; using System.Linq; using BriarQueen.Data.IO.Saves; using BriarQueen.Framework.Coordinators.Events; using BriarQueen.Framework.Events.Gameplay; using BriarQueen.Framework.Events.Progression; using BriarQueen.Framework.Events.Save; using BriarQueen.Framework.Events.UI; using BriarQueen.Framework.Managers.Assets; using BriarQueen.Framework.Managers.IO; using BriarQueen.Framework.Managers.Levels.Data; using BriarQueen.Framework.Registries; using BriarQueen.Framework.Services.Destruction; using BriarQueen.Framework.Services.Puzzles; using Cysharp.Threading.Tasks; using UnityEngine; using VContainer; namespace BriarQueen.Framework.Managers.Levels { public class LevelManager : IDisposable, IManager { private const float LEVEL_FADE_DURATION = 1f; private readonly AddressableManager _addressableManager; private readonly AssetRegistry _assetRegistry; private readonly DestructionService _destructionService; private readonly EventCoordinator _eventCoordinator; private readonly object _lock = new(); private readonly PuzzleService _puzzleService; private readonly SaveManager _saveManager; private UniTask _activeLoadTask = UniTask.FromResult(false); private bool _isLoadInProgress; private BaseLevel _currentLevel; public bool Initialized { get; private set; } [Inject] public LevelManager( AddressableManager addressableManager, AssetRegistry assetRegistry, DestructionService destructionService, EventCoordinator eventCoordinator, SaveManager saveManager, PuzzleService puzzleService) { _addressableManager = addressableManager; _assetRegistry = assetRegistry; _destructionService = destructionService; _eventCoordinator = eventCoordinator; _saveManager = saveManager; _puzzleService = puzzleService; } public void Initialize() { if (Initialized) { return; } Debug.Log($"[{nameof(LevelManager)}] Initializing..."); _saveManager.OnSaveRequested += OnSaveGameRequested; _eventCoordinator.Subscribe(OnHintStageUpdated); Debug.Log($"[{nameof(LevelManager)}] Initialized."); Initialized = true; } public void Dispose() { if (!Initialized) { return; } _saveManager.OnSaveRequested -= OnSaveGameRequested; _eventCoordinator.Unsubscribe(OnHintStageUpdated); Initialized = false; } private void OnHintStageUpdated(UpdateHintProgressEvent evt) { if (_currentLevel == null || evt == null) { return; } if (!string.Equals(evt.LevelID, _currentLevel.LevelID, StringComparison.Ordinal)) { return; } var incoming = Mathf.Max(0, evt.Stage); if (evt.Force) { _currentLevel.CurrentLevelHintStage = incoming; return; } var current = Mathf.Max(0, _currentLevel.CurrentLevelHintStage); _currentLevel.CurrentLevelHintStage = Mathf.Max(current, incoming); } private void OnSaveGameRequested(SaveGame saveGame) { if (saveGame == null || _currentLevel == null) { return; } saveGame.CurrentLevelID = _currentLevel.LevelID; saveGame.CurrentSceneID = _currentLevel.SceneID; saveGame.LevelHintStages ??= new Dictionary(); saveGame.LevelHintStages[_currentLevel.LevelID] = Mathf.Max(0, _currentLevel.CurrentLevelHintStage); } public UniTask LoadLevel(string levelAssetID) { if (string.IsNullOrWhiteSpace(levelAssetID)) { Debug.LogError("[LevelManager] LoadLevel called with null/empty levelAssetID."); return UniTask.FromResult(false); } lock (_lock) { if (_isLoadInProgress) { Debug.LogWarning( $"[LevelManager] LoadLevel('{levelAssetID}') requested while another level load is already in progress. Returning the active load task."); return _activeLoadTask; } _isLoadInProgress = true; _activeLoadTask = LoadLevelTracked(levelAssetID); return _activeLoadTask; } } private async UniTask LoadLevelTracked(string levelAssetID) { try { return await LoadLevelInternal(levelAssetID); } finally { lock (_lock) { _isLoadInProgress = false; _activeLoadTask = UniTask.FromResult(false); } } } private async UniTask LoadLevelInternal(string levelAssetID) { var previousLevelId = _currentLevel != null ? _currentLevel.LevelID : null; _eventCoordinator.PublishImmediate(new FadeEvent(false, LEVEL_FADE_DURATION)); await UniTask.Delay(TimeSpan.FromSeconds(LEVEL_FADE_DURATION)); if (_currentLevel != null) { await UnloadLevelInternal(); } if (await TryLoadLevelCore(levelAssetID)) { return true; } if (!string.IsNullOrWhiteSpace(previousLevelId) && !string.Equals(previousLevelId, levelAssetID, StringComparison.Ordinal)) { Debug.LogWarning( $"[LevelManager] Failed to load '{levelAssetID}'. Attempting recovery by reloading previous level '{previousLevelId}'."); if (await TryLoadLevelCore(previousLevelId)) { Debug.LogWarning( $"[LevelManager] Recovery succeeded by reloading '{previousLevelId}'."); return false; } } _eventCoordinator.PublishImmediate(new FadeEvent(true, LEVEL_FADE_DURATION)); await UniTask.Delay(TimeSpan.FromSeconds(LEVEL_FADE_DURATION)); return false; } private async UniTask TryLoadLevelCore(string levelAssetID) { try { if (_assetRegistry == null) { Debug.LogError("[LevelManager] AssetRegistry is null."); return false; } if (!_assetRegistry.TryGetReference(levelAssetID, out var levelRef) || levelRef == null) { Debug.LogError($"[LevelManager] No level reference found for id '{levelAssetID}'."); return false; } var levelObj = await _addressableManager.InstantiateAsync(levelRef); if (levelObj == null) { Debug.LogError($"[LevelManager] Failed to instantiate level '{levelAssetID}'."); return false; } var level = levelObj.GetComponent(); if (level == null) { Debug.LogError( $"[LevelManager] Instantiated level '{levelAssetID}' has no BaseLevel component. Destroying instance."); await _destructionService.Destroy(levelObj); return false; } _currentLevel = level; RestoreHintStageForCurrentLevel(); await RestoreItemStateForCurrentLevel(); await _currentLevel.PostLoad(); await _puzzleService.LoadPuzzles(_currentLevel.Puzzles); _eventCoordinator.Publish(new LevelChangedEvent(_currentLevel)); _eventCoordinator.PublishImmediate(new FadeEvent(true, LEVEL_FADE_DURATION)); await UniTask.Delay(TimeSpan.FromSeconds(LEVEL_FADE_DURATION)); if (_currentLevel != null) { await _currentLevel.PostActivate(); } _eventCoordinator.Publish(new RequestGameSaveEvent()); return true; } catch (Exception ex) { Debug.LogError($"[LevelManager] Exception while loading '{levelAssetID}': {ex}"); await CleanupFailedCurrentLevel(); return false; } } private async UniTask CleanupFailedCurrentLevel() { if (_currentLevel == null) { return; } try { await _destructionService.Destroy(_currentLevel.gameObject); } catch (Exception destroyEx) { Debug.LogWarning($"[LevelManager] Failed to destroy broken level instance: {destroyEx}"); } finally { _currentLevel = null; } } private void RestoreHintStageForCurrentLevel() { if (_currentLevel == null) { return; } var save = _saveManager.CurrentSave; if (save?.LevelHintStages == null) { _currentLevel.CurrentLevelHintStage = 0; return; } if (save.LevelHintStages.TryGetValue(_currentLevel.LevelID, out var stage)) { _currentLevel.CurrentLevelHintStage = Mathf.Max(0, stage); } else { _currentLevel.CurrentLevelHintStage = 0; } } private async UniTask RestoreItemStateForCurrentLevel() { if (_currentLevel == null) { return; } var save = _saveManager.CurrentSave; if (save == null) { return; } var interactables = _currentLevel.Pickups; if (interactables == null || interactables.Count == 0) { return; } foreach (var interactable in interactables) { if (interactable.ItemData == null) { Debug.Log($"[LevelManager] No Item Data for {interactable.InteractableName}"); continue; } if (save.CollectedItems.Any(x => x.UniqueIdentifier == interactable.ItemData.UniqueID)) { await _destructionService.Destroy(interactable.gameObject); } if (save.RemovedItems.Any(x => x.UniqueIdentifier == interactable.ItemData.UniqueID)) { await _destructionService.Destroy(interactable.gameObject); } } var codexTriggers = _currentLevel.CodexTriggers; foreach (var trigger in codexTriggers) { if (save.DiscoveredCodexEntries.Any(x => x.UniqueIdentifier == trigger.Entry.UniqueID)) { if (trigger.RemoveTrigger) { await _destructionService.Destroy(trigger.gameObject); } } } } public UniTask UnloadLevel() { lock (_lock) { if (_activeLoadTask.Status == UniTaskStatus.Pending) { return _activeLoadTask.ContinueWith(_ => UnloadLevelInternal()); } return UnloadLevelInternal(); } } private async UniTask UnloadLevelInternal() { if (_currentLevel == null) { return; } var level = _currentLevel; _currentLevel = null; try { await _puzzleService.SavePuzzles(level.Puzzles); _eventCoordinator.Publish(new RequestGameSaveEvent()); await level.PreUnload(); } catch (Exception ex) { Debug.LogWarning($"[LevelManager] Exception in PreUnload of {level.name}: {ex}"); } await _destructionService.Destroy(level.gameObject); } } }