using UnityEngine; using System; using Unheimlich.Core; using Unheimlich.Features.Dialogue.Data; using Unheimlich.Features.DetectiveBoard.Data; using Unheimlich.Features.DetectiveBoard; using Unheimlich.Features.Player; using Unheimlich.Features.Dialogue.UI; using Unheimlich.Features.Dialogue; using System.Collections.Generic; using InputManager = Unheimlich.Core.Input.InputManager; namespace Unheimlich.Features.Dialogue { public class DialogueManager : Singleton { [Header("State")] [SerializeField] private bool IsInDialogue = false; [Header("Focus Settings")] [SerializeField] private LayerMask _choiceLayer; [SerializeField] private float _focusRange = 5f; [SerializeField] private float _focusTime = 1.5f; [SerializeField] private float _raycastInterval = 0.05f; [Header("Focus Stabilization")] [SerializeField] private float _recoveryDuration = 0.5f; [SerializeField] private float _focusRetentionTime = 0.1f; [Tooltip("Fallback camera shake intensity if not set in DialogueAnimationSettings")] [SerializeField] private float _fallbackShakeIntensity = 30f; public DialogueAnimationSettings CurrentAnimationSettings => _currentAnimationSettings; public bool IsDialogueActive => IsInDialogue; public Transform CurrentChoiceAnchor => _currentChoiceAnchor; public DialogueUI CurrentDialogueUI => _activeDialogueUI; public object CurrentOwner { get; private set; } public DialogueConversation CurrentConversation => _currentConversation; public DialogueNode CurrentNode => _currentNode; private bool _blurEnabled; public event Action OnChoiceMade; public event Action OnDialogueCompleted; private DialogueFocusSystem _focusSystem; private Camera _mainCamera; private DialogueAnimationSettings _currentAnimationSettings; private DialogueUI _activeDialogueUI; private Transform _currentChoiceAnchor; private DialogueConversation _currentConversation; private DialogueNode _currentNode; private float _autoCloseTimer = 0f; private bool _isWaitingForAutoClose = false; protected override void OnAwake() { } protected override void OnEnabled() { // With Scene Reload disabled, Start() doesn't run again each session. // We recreate the focus system to clear any stale locked state (_isFocusLocked, // _focusTimer, _currentFocusedHandler) that may have carried over. if (_focusSystem != null) { _mainCamera = Camera.main; InitFocusSystem(); // Also reset dialogue state in case we exited mid-dialogue IsInDialogue = false; _isWaitingForAutoClose = false; _autoCloseTimer = 0f; } } private void Start() { _mainCamera = Camera.main; InitFocusSystem(); } /// /// Creates or recreates the DialogueFocusSystem with the current inspector settings. /// Called from Start and OnEnabled (domain-reload-less re-entry). /// private void InitFocusSystem() { _focusSystem = new DialogueFocusSystem( this, _mainCamera, PlayerController.Instance?.CameraEffects, _choiceLayer, _focusRange, _focusTime, _raycastInterval, _recoveryDuration, _focusRetentionTime, _fallbackShakeIntensity ); _focusSystem.OnChoiceMade += (choice) => OnChoiceMade?.Invoke(choice); } private void Update() { if (!IsInDialogue) return; if (_activeDialogueUI == null) { Debug.LogError("[DialogueManager] IsInDialogue is true but _activeDialogueUI is null. Forcing dialogue end.", this); IsInDialogue = false; return; } if (!_activeDialogueUI.AreChoicesVisible()) { if (_isWaitingForAutoClose && _currentNode != null && _currentNode.AutoCloseDelay > 0f) { _autoCloseTimer += Time.deltaTime; if (_autoCloseTimer >= _currentNode.AutoCloseDelay) { _activeDialogueUI.FadeOutAndHide(() => { OnDialogueCompleted?.Invoke(CurrentOwner); CleanupDialogue(); }); IsInDialogue = false; _isWaitingForAutoClose = false; if (_blurEnabled && DialogueBlurEffectURP.Instance != null) DialogueBlurEffectURP.Instance.HideBlur(); return; } } var interactAction = InputManager.GetInteractAction(); if (interactAction != null && interactAction.WasPressedThisFrame()) { EndDialogue(); } } else { _focusSystem?.Update(); } } public void StartConversation(DialogueConversation conversation, Transform choiceAnchor = null, DialogueAnimationSettings animationSettings = null, DialogueUI customDialogueUI = null, object owner = null, bool showBlur = false) { if (conversation == null) { Debug.LogError("Cannot start conversation: conversation is null"); return; } DialogueNode firstNode = conversation.GetFirstNode(); if (firstNode == null) { Debug.LogError($"Cannot start conversation {conversation.name}: no dialogue nodes found"); return; } _currentConversation = conversation; _currentNode = firstNode; _currentChoiceAnchor = choiceAnchor; _currentAnimationSettings = animationSettings; _activeDialogueUI = customDialogueUI; CurrentOwner = owner; _blurEnabled = showBlur; if (_activeDialogueUI == null) { Debug.LogError("[DialogueManager] StartConversation called with no DialogueUI. Assign a Local Dialogue UI on the DialogueTriggerZone that triggered this conversation.", this); return; } if (_blurEnabled && DialogueBlurEffectURP.Instance != null) DialogueBlurEffectURP.Instance.ShowBlur(); PlayerEvents.TriggerEnterDialogue(); ShowCurrentNode(); } private void ShowCurrentNode() { if (_currentNode == null || _currentConversation == null) return; UnlockFocus(); IsInDialogue = true; if (_currentNode.ClueToUnlock != null) { if (DetectiveBoardManager.Instance != null) { DetectiveBoardManager.Instance.UnlockClue(_currentNode.ClueToUnlock); } else { Debug.LogWarning($"[DialogueManager] Cannot unlock clue '{_currentNode.ClueToUnlock.ClueID}': DetectiveBoardManager not found"); } } if(_currentChoiceAnchor == null) { Debug.LogWarning("No choice anchor found!"); return; } Transform anchor = _currentChoiceAnchor; string speakerName = _currentConversation.GetLocalizedSpeakerName(); string dialogueText = _currentNode.GetLocalizedDialogueText(); DialogueDisplayMode displayMode = _currentConversation.DisplayMode; _activeDialogueUI.ShowDialogueBox(speakerName, dialogueText, displayMode); _autoCloseTimer = 0f; _isWaitingForAutoClose = false; if (_currentNode.Choices != null && _currentNode.Choices.Count > 0) { List validChoices = new List(); foreach (var choiceData in _currentNode.Choices) { if (choiceData != null && choiceData.IsValid()) validChoices.Add(choiceData); } if (validChoices.Count > 0) _activeDialogueUI.ShowChoices(validChoices, anchor, _currentAnimationSettings); } else _isWaitingForAutoClose = true; } public void GoToNextNode(DialogueNode nextNode) { if (nextNode == null) { OnDialogueCompleted?.Invoke(CurrentOwner); EndDialogue(); return; } UnlockFocus(); if (_activeDialogueUI != null) _activeDialogueUI.HideChoices(); _currentNode = nextNode; ShowCurrentNode(); } public void EndDialogue() { IsInDialogue = false; if (_activeDialogueUI != null) _activeDialogueUI.HideDialogueBox(); if (_blurEnabled && DialogueBlurEffectURP.Instance != null) DialogueBlurEffectURP.Instance.HideBlur(); PlayerEvents.TriggerExitDialogue(); CleanupDialogue(); } private void CleanupDialogue() { _currentChoiceAnchor = null; _currentAnimationSettings = null; _activeDialogueUI = null; CurrentOwner = null; _blurEnabled = false; _currentConversation = null; _currentNode = null; _autoCloseTimer = 0f; _isWaitingForAutoClose = false; } public void UnlockFocus() { _focusSystem?.UnlockFocus(); } } }