301 lines
10 KiB
C#
301 lines
10 KiB
C#
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<bool> _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<UpdateHintProgressEvent>(OnHintStageUpdated);
|
|
Debug.Log($"[{nameof(LevelManager)}] Initialized.");
|
|
|
|
Initialized = true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (!Initialized)
|
|
return;
|
|
|
|
_saveManager.OnSaveRequested -= OnSaveGameRequested;
|
|
_eventCoordinator.Unsubscribe<UpdateHintProgressEvent>(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<string, int>();
|
|
saveGame.LevelHintStages[_currentLevel.LevelID] = Mathf.Max(0, _currentLevel.CurrentLevelHintStage);
|
|
}
|
|
|
|
public UniTask<bool> 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<bool> 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<BaseLevel>();
|
|
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);
|
|
}
|
|
}
|
|
} |