using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using System; using System.Collections; using System.Collections.Generic; using Unheimlich.Core; using Unheimlich.Features.Dialogue.UI; namespace Unheimlich.Features.Hallucination { /// /// Central manager for the hallucination system. public class HallucinationManager : Singleton { private const float MinSanity = 0f; private const float MaxSanity = 1f; [Header("Sanity")] [SerializeField, Range(0f, 1f)] private float _initialSanity = 0f; [SerializeField, Range(0f, 0.1f)] private float _sanityRecoveryRate = 0f; [Header("Effect Lerp")] [SerializeField] private float _effectLerpSpeed = 2f; [Header("URP Post-Process Volume")] [SerializeField] private Volume _hallucinationVolume; [Header("Chromatic Aberration")] [SerializeField] private bool _driveChromaticAberration = true; [SerializeField, Range(0f, 1f)] private float _chromaticAberrationMin = 0f; [SerializeField, Range(0f, 1f)] private float _chromaticAberrationMax = 0.8f; [Header("Lens Distortion")] [SerializeField] private bool _driveLensDistortion = true; [SerializeField, Range(-1f, 1f)] private float _lensDistortionMin = 0f; [SerializeField, Range(-1f, 1f)] private float _lensDistortionMax = -0.4f; [Header("Vignette")] [SerializeField] private bool _driveVignette = true; [SerializeField, Range(0f, 1f)] private float _vignetteMin = 0.3f; [SerializeField, Range(0f, 1f)] private float _vignetteMax = 0.7f; [Header("Film Grain")] [SerializeField] private bool _driveFilmGrain = true; [SerializeField, Range(0f, 1f)] private float _filmGrainMin = 0f; [SerializeField, Range(0f, 1f)] private float _filmGrainMax = 0.6f; [Header("Bloom")] [SerializeField] private bool _driveBloom = true; [SerializeField, Min(0f)] private float _bloomIntensityMin = 0f; [SerializeField, Min(0f)] private float _bloomIntensityMax = 1f; [SerializeField, Min(0f)] private float _bloomThresholdMin = 1f; [SerializeField, Min(0f)] private float _bloomThresholdMax = 0.5f; [SerializeField, Range(0f, 1f)] private float _bloomScatterMin = 0f; [SerializeField, Range(0f, 1f)] private float _bloomScatterMax = 0.7f; [SerializeField, Min(0f)] private float _bloomDirtIntensityMin = 0f; [SerializeField, Min(0f)] private float _bloomDirtIntensityMax = 0f; [Header("Floating Label — Baseline Influence")] [Tooltip("Labels within this radius of the player receive the sanity-driven baseline.")] [SerializeField, Min(0f)] private float _labelInfluenceRadius = 12f; [Header("Floating Label — Shake")] [Tooltip("Shake radius applied to labels at sanity = 0.")] [SerializeField, Min(0f)] private float _labelShakeRadiusAtZero = 0f; [SerializeField, Min(0f)] private float _labelShakeSpeedAtZero = 1.5f; [SerializeField, Min(0f)] private float _labelShakeSmoothnessAtZero = 2f; [Tooltip("Shake radius applied to labels at sanity = 1.")] [SerializeField, Min(0f)] private float _labelShakeRadiusAtOne = 0.2f; [SerializeField, Min(0f)] private float _labelShakeSpeedAtOne = 4f; [SerializeField, Min(0f)] private float _labelShakeSmoothnessAtOne = 5f; [Header("Floating Label — Ghost Trail")] [SerializeField] private bool _ghostEnabledAtZero = false; [SerializeField] private bool _ghostEnabledAtOne = true; [SerializeField, Range(0f, 1f)] private float _ghostAlphaMin = 0.1f; [SerializeField, Range(0f, 1f)] private float _ghostAlphaMax = 0.5f; [Header("Word Shake (legacy — for other systems)")] [SerializeField] private float _shakeMultiplierLegacyAtZero = 1f; [SerializeField] private float _shakeMultiplierLegacyAtOne = 3f; [Header("Debug")] [SerializeField] private bool _debugMode; // ─── State ───────────────────────────────────────────────────────────── private float _currentSanity; private float _displayedSanity; private Coroutine _lerpCoroutine; private bool _vignetteExternallyOverridden; private ChromaticAberration _chromaticAberration; private LensDistortion _lensDistortion; private Vignette _vignette; private FilmGrain _filmGrain; private Bloom _bloom; private Transform _playerTransform; private readonly List _labelsInRange = new(); // ─── Public API ──────────────────────────────────────────────────────── public float SanityLevel => _currentSanity; public float WordShakeMultiplier => Mathf.Lerp(_shakeMultiplierLegacyAtZero, _shakeMultiplierLegacyAtOne, _displayedSanity); public event Action OnSanityChanged; public event Action OnHallucinationTriggered; // ─── Lifecycle ───────────────────────────────────────────────────────── protected override void OnAwake() { _currentSanity = Mathf.Clamp(_initialSanity, MinSanity, MaxSanity); _displayedSanity = _currentSanity; CacheVolumeOverrides(); ApplyPostProcessImmediate(_currentSanity); } private void Update() { if (_sanityRecoveryRate > 0f && _currentSanity > MinSanity) SetSanity(_currentSanity - _sanityRecoveryRate * Time.deltaTime); DriveFloatingLabelBaseline(); } // ─── Public Methods ──────────────────────────────────────────────────── public void AddInsanity(float amount) => SetSanity(_currentSanity + amount); public void SetSanity(float value) { float clamped = Mathf.Clamp(value, MinSanity, MaxSanity); if (Mathf.Approximately(clamped, _currentSanity)) return; _currentSanity = clamped; OnSanityChanged?.Invoke(_currentSanity); if (_debugMode) Debug.Log($"[HallucinationManager] Sanity → {_currentSanity:F2}", this); RestartLerp(); } public void SetVignetteOverriddenExternally(bool overridden) { _vignetteExternallyOverridden = overridden; if (_vignette != null) _vignette.intensity.value = overridden ? 0f : Mathf.Lerp(_vignetteMin, _vignetteMax, _displayedSanity); } /// /// Fires a discrete hallucination event: /// 1. Applies SanityCost. /// 2. Dispatches per-label overrides defined in the event asset. /// 3. Raises OnHallucinationTriggered. /// public void TriggerHallucinationEvent(HallucinationEvent hallucinationEvent) { if (hallucinationEvent == null) { Debug.LogError("[HallucinationManager] Null HallucinationEvent!", this); return; } if (hallucinationEvent.SanityCost > 0f) AddInsanity(hallucinationEvent.SanityCost); hallucinationEvent.ApplyLabelOverrides(); if (_debugMode) Debug.Log($"[HallucinationManager] Event: {hallucinationEvent.Label} " + $"({hallucinationEvent.LabelOverrides.Count} label overrides)", this); OnHallucinationTriggered?.Invoke(hallucinationEvent); } // ─── Floating Label Baseline ─────────────────────────────────────────── private void DriveFloatingLabelBaseline() { EnsurePlayerTransform(); if (_playerTransform == null) return; HallucinationLabelRegistry.GetLabelsInRadius( _playerTransform.position, _labelInfluenceRadius, _labelsInRange); float t = _displayedSanity; float shakeRadius = Mathf.Lerp(_labelShakeRadiusAtZero, _labelShakeRadiusAtOne, t); float shakeSpeed = Mathf.Lerp(_labelShakeSpeedAtZero, _labelShakeSpeedAtOne, t); float shakeSmoothness = Mathf.Lerp(_labelShakeSmoothnessAtZero, _labelShakeSmoothnessAtOne, t); bool ghostEnabled = t >= 0.5f ? _ghostEnabledAtOne : _ghostEnabledAtZero; float ghostAlpha = Mathf.Lerp(_ghostAlphaMin, _ghostAlphaMax, t); foreach (FloatingLabel label in _labelsInRange) { label.ApplyBaseline(shakeRadius, shakeSpeed, shakeSmoothness, ghostEnabled, ghostAlpha); } } private void EnsurePlayerTransform() { if (_playerTransform != null) return; if (Unheimlich.Features.Player.PlayerController.Instance != null) _playerTransform = Unheimlich.Features.Player.PlayerController.Instance.transform; else if (Camera.main != null) _playerTransform = Camera.main.transform; } // ─── Lerp ────────────────────────────────────────────────────────────── private void RestartLerp() { if (_lerpCoroutine != null) StopCoroutine(_lerpCoroutine); _lerpCoroutine = StartCoroutine(LerpEffects()); } private IEnumerator LerpEffects() { while (!Mathf.Approximately(_displayedSanity, _currentSanity)) { _displayedSanity = Mathf.MoveTowards( _displayedSanity, _currentSanity, _effectLerpSpeed * Time.deltaTime); ApplyPostProcessImmediate(_displayedSanity); yield return null; } _displayedSanity = _currentSanity; ApplyPostProcessImmediate(_displayedSanity); _lerpCoroutine = null; } // ─── Post-Process Effects ────────────────────────────────────────────── private void CacheVolumeOverrides() { if (_hallucinationVolume == null || _hallucinationVolume.profile == null) return; _hallucinationVolume.profile.TryGet(out _chromaticAberration); _hallucinationVolume.profile.TryGet(out _lensDistortion); _hallucinationVolume.profile.TryGet(out _vignette); _hallucinationVolume.profile.TryGet(out _filmGrain); _hallucinationVolume.profile.TryGet(out _bloom); _hallucinationVolume.weight = 1f; _hallucinationVolume.enabled = true; } private void ApplyPostProcessImmediate(float t) { if (_driveChromaticAberration && _chromaticAberration != null) _chromaticAberration.intensity.value = Mathf.Lerp(_chromaticAberrationMin, _chromaticAberrationMax, t); if (_driveLensDistortion && _lensDistortion != null) _lensDistortion.intensity.value = Mathf.Lerp(_lensDistortionMin, _lensDistortionMax, t); if (_driveVignette && _vignette != null && !_vignetteExternallyOverridden) _vignette.intensity.value = Mathf.Lerp(_vignetteMin, _vignetteMax, t); if (_driveFilmGrain && _filmGrain != null) _filmGrain.intensity.value = Mathf.Lerp(_filmGrainMin, _filmGrainMax, t); if (_driveBloom && _bloom != null) { _bloom.intensity.value = Mathf.Lerp(_bloomIntensityMin, _bloomIntensityMax, t); _bloom.threshold.value = Mathf.Lerp(_bloomThresholdMin, _bloomThresholdMax, t); _bloom.scatter.value = Mathf.Lerp(_bloomScatterMin, _bloomScatterMax, t); _bloom.dirtIntensity.value = Mathf.Lerp(_bloomDirtIntensityMin, _bloomDirtIntensityMax, t); } } } }