using UnityEngine; using System; using Unheimlich.Gameplay.Player; using Unheimlich.Interactions.Dialogues; using Unheimlich.Interactions.FloatingWords; using Unheimlich.Data.Dialogues; namespace Unheimlich.Managers { /// /// Manages gaze-based focus detection for dialogue choices using raycasts. /// Handles progressive focus timers, aim assist integration, visual feedback, /// and automatic choice selection when player maintains focus for a duration. /// public class DialogueFocusSystem { private readonly DialogueManager _manager; private readonly Camera _mainCamera; private readonly PlayerLook _playerLook; private ChoiceFocusHandler _currentFocusedHandler; private float _focusTimer = 0f; private float _retentionTimer = 0f; private float _raycastTimer = 0f; private bool _isFocusLocked = false; private readonly LayerMask _choiceLayer; private readonly float _focusRange; private readonly float _focusTime; private readonly float _raycastInterval; private readonly float _recoveryDuration; private readonly float _focusRetentionTime; private readonly float _shakeIntensity; private static readonly Vector3 ViewportCenter = new Vector3(0.5f, 0.5f, 0f); public event Action OnChoiceMade; public DialogueFocusSystem( DialogueManager manager, Camera mainCamera, PlayerLook playerLook, LayerMask choiceLayer, float focusRange, float focusTime, float raycastInterval, float recoveryDuration, float focusRetentionTime, float shakeIntensity) { _manager = manager; _mainCamera = mainCamera; _playerLook = playerLook; _choiceLayer = choiceLayer; _focusRange = focusRange; _focusTime = focusTime; _raycastInterval = raycastInterval; _recoveryDuration = recoveryDuration; _focusRetentionTime = focusRetentionTime; _shakeIntensity = shakeIntensity; } /// /// Updates the focus detection system. Should be called every frame during active dialogue. /// Performs raycasts at configured intervals to detect choice focus. /// public void Update() { if (!_manager.IsInDialogue || !_manager.CurrentDialogueUI.AreChoicesVisible()) return; _raycastTimer += Time.deltaTime; if (_raycastTimer >= _raycastInterval) { CheckForChoiceFocus(); _raycastTimer = 0f; } } /// /// Performs raycast from camera center to detect focused dialogue choices. /// Manages focus state transitions, retention timers, visual feedback, and choice selection. /// Implements stabilization to prevent rapid focus switching between choices. /// private void CheckForChoiceFocus() { if (_isFocusLocked) { if (_retentionTimer > 0f) _retentionTimer -= Time.deltaTime; return; } if (_mainCamera == null) return; Ray ray = _mainCamera.ViewportPointToRay(ViewportCenter); ChoiceFocusHandler hitHandler = null; bool raycastHitChoice = Physics.Raycast(ray, out RaycastHit hit, _focusRange, _choiceLayer); if (raycastHitChoice) { hitHandler = hit.collider.GetComponent(); if (_playerLook != null) _playerLook.ApplyAimAssist(hit.collider.bounds.center); } if (hitHandler != _currentFocusedHandler) { _retentionTimer -= Time.deltaTime; if (hitHandler != null && _retentionTimer <= 0f) { if (_currentFocusedHandler != null) { _isFocusLocked = true; _currentFocusedHandler.ResetFeedback(_recoveryDuration); } _currentFocusedHandler = hitHandler; _focusTimer = 0f; _retentionTimer = _focusRetentionTime; if (_playerLook != null) _playerLook.LockAim(true); DialogueAnimationSettings settings = _manager.CurrentAnimationSettings; bool shouldRotate = settings == null || settings.EnableRotation; if (shouldRotate && _manager.CurrentDialogueUI != null) { float focusSpeed = settings != null ? settings.FocusRotationSpeed : 60f; _manager.CurrentDialogueUI.RotationAnimation(focusSpeed); } } } else if (hitHandler != null) { _retentionTimer = _focusRetentionTime; } bool isFocusActive = _currentFocusedHandler != null && (_retentionTimer > 0f); if (isFocusActive) { _focusTimer += Time.deltaTime; float progress = _focusTimer / _focusTime; _currentFocusedHandler.ApplyFocusFeedback(progress); if (_playerLook != null) _playerLook.Shake(progress / _shakeIntensity); if (_focusTimer >= _focusTime) { _isFocusLocked = true; _currentFocusedHandler.ResetFeedback(_recoveryDuration); if (_currentFocusedHandler.DialogueData != null) { OnChoiceMade?.Invoke(_currentFocusedHandler.DialogueData); DialogueNode nextNode = _currentFocusedHandler.GetNextNode(); _manager.GoToNextNode(nextNode); } DialogueAnimationSettings settings = _manager.CurrentAnimationSettings; bool shouldRotate = settings == null || settings.EnableRotation; if (shouldRotate && _manager.CurrentDialogueUI != null) { float normalSpeed = settings != null ? settings.NormalRotationSpeed : 36f; _manager.CurrentDialogueUI.RotationAnimation(normalSpeed); } ResetAnimationTimers(); if (_playerLook != null) _playerLook.LockAim(false); } } else if (_currentFocusedHandler != null && _retentionTimer <= 0f) { _isFocusLocked = true; _currentFocusedHandler.ResetFeedback(_recoveryDuration); DialogueAnimationSettings settings = _manager.CurrentAnimationSettings; bool shouldRotate = settings == null || settings.EnableRotation; if (shouldRotate && _manager.CurrentDialogueUI != null) { float normalSpeed = settings != null ? settings.NormalRotationSpeed : 36f; _manager.CurrentDialogueUI.RotationAnimation(normalSpeed); } ResetAnimationTimers(); if (_playerLook != null) _playerLook.LockAim(false); } } private void ResetAnimationTimers() { _currentFocusedHandler = null; _focusTimer = 0f; _retentionTimer = 0f; } public void UnlockFocus() { _isFocusLocked = false; } } }