using Input; using Interactions; using UnityEngine; using Core; using Core.Lifecycle; namespace PuzzleS { /// /// Manages the state and interactions for a single puzzle step, including unlock/lock logic and event handling. /// [RequireComponent(typeof(InteractableBase))] public class ObjectiveStepBehaviour : ManagedBehaviour, IPuzzlePrompt { /// /// The data object representing this puzzle step. /// public PuzzleStepSO stepData; [Header("Indicator Settings")] [SerializeField] private GameObject puzzleIndicator; [SerializeField] private bool drawPromptRangeGizmo = true; private InteractableBase _interactable; private bool _isUnlocked = false; private bool _isCompleted = false; private IPuzzlePrompt _indicator; // Current proximity state tracked by PuzzleManager private ProximityState _currentProximityState = ProximityState.Far; // Enum for tracking proximity state (simplified to just Close and Far) public enum ProximityState { Close, Far } internal override void OnManagedAwake() { _interactable = GetComponent(); // Initialize the indicator if it exists, but ensure it's hidden initially if (puzzleIndicator != null) { // The indicator should start inactive until we determine its proper state puzzleIndicator.SetActive(false); // Get the IPuzzlePrompt component _indicator = puzzleIndicator.GetComponent(); if (_indicator == null) { // Try to find it in children if not on the root _indicator = puzzleIndicator.GetComponentInChildren(); } if (_indicator == null) { Logging.Warning($"[Puzzles] Indicator prefab for {stepData?.stepId} does not implement IPuzzlePrompt"); } } } internal override void OnManagedStart() { // Register with PuzzleManager - safe to access .Instance here if (stepData != null && PuzzleManager.Instance != null) { PuzzleManager.Instance.RegisterStepBehaviour(this); } else if (stepData == null) { Logging.Warning($"[Puzzles] Cannot register step on {gameObject.name}: stepData is null"); } } void OnEnable() { if (_interactable == null) _interactable = GetComponent(); if (_interactable != null) { _interactable.interactionStarted.AddListener(OnInteractionStarted); _interactable.interactionComplete.AddListener(OnInteractionComplete); } } internal override void OnManagedDestroy() { if (PuzzleManager.Instance != null && stepData != null) { PuzzleManager.Instance.UnregisterStepBehaviour(this); } if (_interactable != null) { _interactable.interactionStarted.RemoveListener(OnInteractionStarted); _interactable.interactionComplete.RemoveListener(OnInteractionComplete); } } /// /// Updates the proximity state from PuzzleManager and triggers appropriate methods. /// /// The new proximity state. public void UpdateProximityState(ProximityState newState) { if (_currentProximityState == newState) return; if (!_isUnlocked) return; // Don't process state changes if locked // Determine state changes and call appropriate methods if (newState == ProximityState.Close) { // Transitioning from Far to Close ShowClose(); } else // newState == ProximityState.Far { // Transitioning from Close to Far HideClose(); } _currentProximityState = newState; } // IPuzzlePrompt interface implementation - delegates to indicator if available /// /// Called when the prompt should be initially shown (e.g., when step is unlocked) /// public virtual void OnShow() { if (puzzleIndicator != null) puzzleIndicator.SetActive(true); // Delegate to indicator if available if (IsIndicatorValid()) { _indicator.OnShow(); return; } Logging.Debug($"[Puzzles] Prompt shown for {stepData?.stepId} on {gameObject.name}"); } /// /// Called when the prompt should be hidden (e.g., when step is locked) /// public virtual void OnHide() { if (puzzleIndicator != null) puzzleIndicator.SetActive(false); // Delegate to indicator if available if (IsIndicatorValid()) { _indicator.OnHide(); } Logging.Debug($"[Puzzles] Prompt hidden for {stepData?.stepId} on {gameObject.name}"); } /// /// Called when player enters the far range /// public virtual void ShowFar() { // Only show if step is unlocked if (!_isUnlocked) return; // Delegate to indicator if available if (IsIndicatorValid()) { _indicator.ShowFar(); return; } } /// /// Called when player enters the close range /// public virtual void ShowClose() { // Only show if step is unlocked if (!_isUnlocked) return; // Delegate to indicator if available if (IsIndicatorValid()) { _indicator.ShowClose(); return; } } /// /// Called when player exits the close range /// public virtual void HideClose() { // Only respond if step is unlocked if (!_isUnlocked) return; // Delegate to indicator if available if (IsIndicatorValid()) { _indicator.HideClose(); return; } } /// /// Called when player exits the far range /// public virtual void HideFar() { // Only respond if step is unlocked if (!_isUnlocked) return; // Delegate to indicator if available if (IsIndicatorValid()) { _indicator.HideFar(); return; } } /// /// Unlocks this puzzle step, allowing interaction. /// public void UnlockStep() { if (_isUnlocked) return; _isUnlocked = true; Logging.Debug($"[Puzzles] Step unlocked: {stepData?.stepId} on {gameObject.name}"); // Make the indicator visible since this step is now unlocked OnShow(); if (IsIndicatorValid()) { // Set the correct state based on current player distance Transform playerTransform = GameObject.FindGameObjectWithTag("Player")?.transform; if (playerTransform != null) { float distance = Vector3.Distance(transform.position, playerTransform.position); float promptRange = AppleHills.SettingsAccess.GetPuzzlePromptRange(); if (distance <= promptRange) { // Player is in close range _currentProximityState = ProximityState.Close; _indicator.ShowClose(); } else { // Player is in far range _currentProximityState = ProximityState.Far; _indicator.ShowFar(); } } else { // Default to far if player not found _currentProximityState = ProximityState.Far; _indicator.ShowFar(); } } } /// /// Locks this puzzle step, preventing interaction. /// public void LockStep() { if (!_isUnlocked && puzzleIndicator != null) { // Make sure indicator is hidden if we're already locked puzzleIndicator.SetActive(false); return; } _isUnlocked = false; Logging.Debug($"[Puzzles] Step locked: {stepData?.stepId} on {gameObject.name}"); // Hide the indicator OnHide(); } /// /// Returns whether this step is currently unlocked. /// public bool IsStepUnlocked() { return _isUnlocked; } /// /// Handles the start of an interaction (can be used for visual feedback). /// private void OnInteractionStarted(PlayerTouchController playerRef, FollowerController followerRef) { // Empty - handled by Interactable } /// /// Handles completion of the interaction, notifies PuzzleManager if successful and unlocked. /// /// Whether the interaction was successful. private void OnInteractionComplete(bool success) { if (!_isUnlocked) return; if (success && !_isCompleted) { Logging.Debug($"[Puzzles] Step interacted: {stepData?.stepId} on {gameObject.name}"); _isCompleted = true; PuzzleManager.Instance?.MarkPuzzleStepCompleted(stepData); if (puzzleIndicator != null) { Destroy(puzzleIndicator); _indicator = null; } } } private bool IsIndicatorValid() { return _indicator != null && puzzleIndicator != null && !_isCompleted; } /// /// Visualizes the puzzle prompt ranges in the editor. /// private void OnDrawGizmos() { if (!drawPromptRangeGizmo) return; // Use the global puzzle prompt range from settings float promptRange = AppleHills.SettingsAccess.GetPuzzlePromptRange(); // Draw threshold circle Gizmos.color = Color.cyan; Gizmos.DrawWireSphere(transform.position, promptRange); } } }