using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.SceneManagement; namespace PuzzleS { /// /// Manages puzzle step registration, dependency management, and step completion for the puzzle system. /// public class PuzzleManager : MonoBehaviour { private static PuzzleManager _instance; private static bool _isQuitting; [SerializeField] private float proximityCheckInterval = 0.02f; // Reference to player transform for proximity calculations private Transform _playerTransform; private Coroutine _proximityCheckCoroutine; /// /// Singleton instance of the PuzzleManager. /// public static PuzzleManager Instance { get { if (_instance == null && Application.isPlaying && !_isQuitting) { _instance = FindAnyObjectByType(); if (_instance == null) { var go = new GameObject("PuzzleManager"); _instance = go.AddComponent(); // DontDestroyOnLoad(go); } } return _instance; } } // Events to notify about step lifecycle public event Action OnStepCompleted; public event Action OnStepUnlocked; private HashSet _completedSteps = new HashSet(); private HashSet _unlockedSteps = new HashSet(); // Registration for ObjectiveStepBehaviour private Dictionary _stepBehaviours = new Dictionary(); // Runtime dependency graph private Dictionary> _runtimeDependencies = new Dictionary>(); void Awake() { _instance = this; // DontDestroyOnLoad(gameObject); SceneManager.sceneLoaded += OnSceneLoaded; } void Start() { // Find player transform _playerTransform = GameObject.FindGameObjectWithTag("Player")?.transform; // Start proximity check coroutine StartProximityChecks(); } void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; StopProximityChecks(); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { SceneManager.sceneLoaded -= OnSceneLoaded; Debug.Log("[MDPI] OnSceneLoaded"); _runtimeDependencies.Clear(); BuildRuntimeDependencies(); UnlockInitialSteps(); // Find player transform again in case it changed with scene load _playerTransform = GameObject.FindGameObjectWithTag("Player")?.transform; // Restart proximity checks StartProximityChecks(); } /// /// Start the proximity check coroutine. /// private void StartProximityChecks() { StopProximityChecks(); _proximityCheckCoroutine = StartCoroutine(CheckProximityRoutine()); } /// /// Stop the proximity check coroutine. /// private void StopProximityChecks() { if (_proximityCheckCoroutine != null) { StopCoroutine(_proximityCheckCoroutine); _proximityCheckCoroutine = null; } } /// /// Coroutine that periodically checks player proximity to all puzzle steps. /// private IEnumerator CheckProximityRoutine() { WaitForSeconds wait = new WaitForSeconds(proximityCheckInterval); while (true) { if (_playerTransform != null) { // Get the proximity threshold from settings (half of the prompt range) float proximityThreshold = GameManager.Instance.DefaultPuzzlePromptRange; // Check distance to each step behavior foreach (var kvp in _stepBehaviours) { if (kvp.Value == null) continue; float distance = Vector3.Distance(_playerTransform.position, kvp.Value.transform.position); // Determine the proximity state - only Close or Far now ObjectiveStepBehaviour.ProximityState state = (distance <= proximityThreshold) ? ObjectiveStepBehaviour.ProximityState.Close : ObjectiveStepBehaviour.ProximityState.Far; // Update the step's proximity state kvp.Value.UpdateProximityState(state); } } yield return wait; } } /// /// Registers a step behaviour with the manager. /// /// The step behaviour to register. public void RegisterStepBehaviour(ObjectiveStepBehaviour behaviour) { if (behaviour?.stepData == null) return; if (!_stepBehaviours.ContainsKey(behaviour.stepData)) { _stepBehaviours.Add(behaviour.stepData, behaviour); _runtimeDependencies.Clear(); foreach (var step in _stepBehaviours.Values) { step.LockStep(); } _unlockedSteps.Clear(); BuildRuntimeDependencies(); UnlockInitialSteps(); Debug.Log($"[Puzzles] Registered step: {behaviour.stepData.stepId} on {behaviour.gameObject.name}"); } } /// /// Unregisters a step behaviour from the manager. /// /// The step behaviour to unregister. public void UnregisterStepBehaviour(ObjectiveStepBehaviour behaviour) { if (behaviour?.stepData == null) return; _stepBehaviours.Remove(behaviour.stepData); Debug.Log($"[Puzzles] Unregistered step: {behaviour.stepData.stepId} on {behaviour.gameObject.name}"); } /// /// Builds the runtime dependency graph for all registered steps. /// private void BuildRuntimeDependencies() { _runtimeDependencies = PuzzleGraphUtility.BuildDependencyGraph(_stepBehaviours.Keys); foreach (var step in _runtimeDependencies.Keys) { foreach (var dep in _runtimeDependencies[step]) { Debug.Log($"[Puzzles] Step {step.stepId} depends on {dep.stepId}"); } } Debug.Log($"[Puzzles] Runtime dependencies built. Total steps: {_stepBehaviours.Count}"); } /// /// Unlocks all initial steps (those with no dependencies) and any steps whose dependencies are already met. /// private void UnlockInitialSteps() { // First, unlock all steps with no dependencies (initial steps) var initialSteps = PuzzleGraphUtility.FindInitialSteps(_runtimeDependencies); foreach (var step in initialSteps) { Debug.Log($"[Puzzles] Initial step unlocked: {step.stepId}"); UnlockStep(step); } // Keep trying to unlock steps as long as we're making progress bool madeProgress; do { madeProgress = false; // Check all steps that haven't been unlocked yet foreach (var step in _runtimeDependencies.Keys.Where(s => !_unlockedSteps.Contains(s))) { // Check if all dependencies have been completed if (AreRuntimeDependenciesMet(step)) { Debug.Log($"[Puzzles] Chain step unlocked: {step.stepId}"); UnlockStep(step); madeProgress = true; } } } while (madeProgress); } /// /// Called when a step is completed. Unlocks dependent steps if their dependencies are met. /// /// The completed step. public void MarkPuzzleStepCompleted(PuzzleStepSO step) { if (_completedSteps.Contains(step)) return; _completedSteps.Add(step); Debug.Log($"[Puzzles] Step completed: {step.stepId}"); // Broadcast completion OnStepCompleted?.Invoke(step); foreach (var unlock in step.unlocks) { if (AreRuntimeDependenciesMet(unlock)) { Debug.Log($"[Puzzles] Unlocking step {unlock.stepId} after completing {step.stepId}"); UnlockStep(unlock); } else { Debug.Log($"[Puzzles] Step {unlock.stepId} not unlocked yet, waiting for other dependencies"); } } CheckPuzzleCompletion(); } /// /// Checks if all dependencies for a step are met. /// /// The step to check. /// True if all dependencies are met, false otherwise. private bool AreRuntimeDependenciesMet(PuzzleStepSO step) { if (!_runtimeDependencies.ContainsKey(step) || _runtimeDependencies[step].Count == 0) return true; foreach (var dep in _runtimeDependencies[step]) { if (!_completedSteps.Contains(dep)) return false; } return true; } /// /// Unlocks a specific step and notifies its behaviour. /// /// The step to unlock. private void UnlockStep(PuzzleStepSO step) { if (_unlockedSteps.Contains(step)) return; _unlockedSteps.Add(step); if (_stepBehaviours.TryGetValue(step, out var behaviour)) { behaviour.UnlockStep(); } Debug.Log($"[Puzzles] Step unlocked: {step.stepId}"); // Broadcast unlock OnStepUnlocked?.Invoke(step); } /// /// Checks if the puzzle is complete (all steps finished). /// private void CheckPuzzleCompletion() { if (_completedSteps.Count == _stepBehaviours.Count) { Debug.Log("[Puzzles] Puzzle complete! All steps finished."); // TODO: Fire puzzle complete event or trigger outcome logic } } /// /// Returns whether a step is already unlocked. /// public bool IsStepUnlocked(PuzzleStepSO step) { // _runtimeDependencies.Clear(); // BuildRuntimeDependencies(); // UnlockInitialSteps(); return _unlockedSteps.Contains(step); } /// /// Checks if a puzzle step with the specified ID has been completed /// /// The ID of the puzzle step to check /// True if the step has been completed, false otherwise public bool IsPuzzleStepCompleted(string stepId) { return _completedSteps.Any(step => step.stepId == stepId); } void OnApplicationQuit() { _isQuitting = true; } } }