using UnityEngine; using System; using Unheimlich.Features.Player; using Unheimlich.Features.Dialogue; using Unheimlich.Features.Dialogue.UI; using Unheimlich.Features.Dialogue.Data; using Unheimlich.Core.Utils; namespace Unheimlich.Features.Dialogue { /// /// Manages gaze-based focus detection for dialogue choices using raycasts. /// public class DialogueFocusSystem { private readonly DialogueManager _manager; private readonly Camera _mainCamera; private readonly CameraEffects _cameraEffects; 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 _fallbackShakeIntensity; public event Action OnChoiceMade; public DialogueFocusSystem( DialogueManager manager, Camera mainCamera, CameraEffects cameraEffects, LayerMask choiceLayer, float focusRange, float focusTime, float raycastInterval, float recoveryDuration, float focusRetentionTime, float fallbackShakeIntensity) { _manager = manager; _mainCamera = mainCamera; _cameraEffects = cameraEffects; _choiceLayer = choiceLayer; _focusRange = focusRange; _focusTime = focusTime; _raycastInterval = raycastInterval; _recoveryDuration = recoveryDuration; _focusRetentionTime = focusRetentionTime; _fallbackShakeIntensity = fallbackShakeIntensity; } /// /// 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.IsDialogueActive || !_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; else _isFocusLocked = false; return; } if (_mainCamera == null) return; ChoiceFocusHandler hitHandler = null; if (RaycastHelper.RaycastFromCameraCenter(_mainCamera, _focusRange, _choiceLayer, out RaycastHit hit)) { hitHandler = hit.collider.GetComponent(); if (_cameraEffects != null && hitHandler != null) _cameraEffects.ApplyAimAssist(hit.collider.bounds.center); } if (hitHandler != _currentFocusedHandler) { _retentionTimer -= Time.deltaTime; if (hitHandler != null && _retentionTimer <= 0f) { if (_currentFocusedHandler != null) { _currentFocusedHandler.ResetFeedback(_recoveryDuration); ResetAllUnfocusEffects(); } _currentFocusedHandler = hitHandler; _focusTimer = 0f; _retentionTimer = _focusRetentionTime; if (_cameraEffects != null) _cameraEffects.LockAim(true); ApplyUnfocusEffectToOthers(hitHandler); 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); DialogueAnimationSettings currentSettings = _manager.CurrentAnimationSettings; bool shouldShake = currentSettings == null || currentSettings.EnableCameraShakeOnFocus; if (shouldShake && _cameraEffects != null) { float shakeIntensity = currentSettings != null ? currentSettings.CameraShakeIntensity : _fallbackShakeIntensity; float shakeSmoothness = currentSettings != null ? currentSettings.CameraShakeSmoothness : 10f; _cameraEffects.ShakeCamera(progress / shakeIntensity, shakeSmoothness); } if (_focusTimer >= _focusTime) { ChoiceFocusHandler selectedHandler = _currentFocusedHandler; if (selectedHandler.DialogueData != null) { OnChoiceMade?.Invoke(selectedHandler.DialogueData); DialogueNode nextNode = selectedHandler.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(); ResetAllUnfocusEffects(); selectedHandler.ResetFeedback(_recoveryDuration); if (_cameraEffects != null) _cameraEffects.LockAim(false); } } else if (_currentFocusedHandler != null && _retentionTimer <= 0f) { _currentFocusedHandler.ResetFeedback(_recoveryDuration); ResetAllUnfocusEffects(); 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 (_cameraEffects != null) _cameraEffects.LockAim(false); } } private void ApplyUnfocusEffectToOthers(ChoiceFocusHandler focusedHandler) { if (_manager.CurrentDialogueUI == null || _manager.CurrentDialogueUI.ChoiceContainer == null) return; DialogueAnimationSettings settings = _manager.CurrentAnimationSettings; float shakeSlowdown = settings != null ? settings.UnfocusedShakeSpeedMultiplier : 0.3f; foreach (Transform child in _manager.CurrentDialogueUI.ChoiceContainer.transform) { ChoiceFocusHandler handler = child.GetComponent(); if (handler != null && handler != focusedHandler) { handler.SetShakeSpeedMultiplier(shakeSlowdown); handler.ApplySketchFade(); } } } private void ResetAllUnfocusEffects() { if (_manager.CurrentDialogueUI == null || _manager.CurrentDialogueUI.ChoiceContainer == null) return; foreach (Transform child in _manager.CurrentDialogueUI.ChoiceContainer.transform) { ChoiceFocusHandler handler = child.GetComponent(); if (handler != null) { handler.SetShakeSpeedMultiplier(1f); handler.ResetSketchFade(); } } } private void ResetAnimationTimers() { _currentFocusedHandler = null; _focusTimer = 0f; _retentionTimer = 0f; } public void UnlockFocus() { _isFocusLocked = false; } } }