using System; using System.Collections.Generic; using System.Linq; using BriarQueen.Data.IO.Saves; using BriarQueen.Framework.Assets; 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.IO; using BriarQueen.Framework.Managers.Levels.Data; using BriarQueen.Framework.Registries; using BriarQueen.Framework.Services.Destruction; using BriarQueen.Framework.Services.Puzzles; using BriarQueen.Framework.Services.Puzzles.Base; 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 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) { _activeLoadTask = LoadLevelInternal(levelAssetID); return _activeLoadTask; } } private async UniTask LoadLevelInternal(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; } _eventCoordinator.PublishImmediate(new FadeEvent(false, LEVEL_FADE_DURATION)); await UniTask.Delay(TimeSpan.FromSeconds(LEVEL_FADE_DURATION)); if (_currentLevel != null) await UnloadLevelInternal(); 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(); if (_currentLevel is BasePuzzle puzzle) await _puzzleService.LoadPuzzle(puzzle); _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}"); if (_currentLevel != null) { try { await _destructionService.Destroy(_currentLevel.gameObject); } catch (Exception destroyEx) { Debug.LogWarning($"[LevelManager] Failed to destroy broken level instance: {destroyEx}"); } _currentLevel = null; } return false; } } 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 { if (level is BasePuzzle puzzle) await _puzzleService.SavePuzzle(puzzle); _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); } } }