using System; using System.Collections; using UnityEngine; using UnityEngine.InputSystem; namespace _Project.Scripts.PlayerUtils { public class PlayerPossession : MonoBehaviour, IDamageable { [Header("Профиль Духа (Форма по умолчанию)")] [SerializeField] private BodyProfile spiritProfile; [Header("Настройки Вселения (Possession)")] [Tooltip("Радиус зоны вселения в метрах")] [SerializeField] private float possessRange = 12f; [Tooltip("С какого максимального расстояния прицел замечает врага и показывает сферу зоны")] [SerializeField] private float maxAimLookDistance = 60f; [SerializeField] private float possessDashDuration = 0.15f; [SerializeField] private LayerMask possessRayMask = ~0; [Header("Визуал Сферы Зоны Вселения")] [Tooltip("Если true — сфера рисуется вокруг врага (показывая зону, куда надо войти). Если false — вокруг игрока.")] [SerializeField] private bool centerSphereOnTarget = true; [SerializeField] private Color zoneReadyColor = new Color(0.1f, 0.95f, 1f, 0.10f); [SerializeField] private Color zoneOutOfRangeColor = new Color(1f, 0.35f, 0.35f, 0.06f); [Header("Замедление времени в форме Духа")] [SerializeField, Range(0.1f, 1f)] private float spiritTimeScale = 0.45f; // Ивент окончательной смерти игрока (когда убили в форме Духа) public event Action OnPlayerDied; public BodyProfile CurrentProfile { get; private set; } public bool IsInSpiritForm => CurrentProfile != null && CurrentProfile.bodyClass == BodyClass.Spirit; public bool IsDead { get; private set; } public float CurrentHealth { get; private set; } public float MaxHealth => CurrentProfile != null ? CurrentProfile.maxHealth : 25f; public EnemyAI AimedEnemy { get; private set; } public bool IsTargetInPossessZone { get; private set; } public float PlayerTimeMultiplier => IsInSpiritForm ? (1f / Mathf.Max(0.1f, Time.timeScale)) * 0.85f : 1f; private Transform _camTransform; private bool _isTransitioning; private float _invulnerableTimer; private GameObject _zoneSphere; private Renderer _zoneRenderer; private Material _zoneMaterial; private void Awake() { if (spiritProfile == null) spiritProfile = BodyProfile.CreateDefault(BodyClass.Spirit); CreatePossessionZoneSphere(); } private void Start() { _camTransform = Player.Instance.CameraController.CameraTransform; EnterSpiritForm(false); } private void CreatePossessionZoneSphere() { _zoneSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere); _zoneSphere.name = "PossessionZoneSphere"; var col = _zoneSphere.GetComponent(); if (col != null) Destroy(col); _zoneRenderer = _zoneSphere.GetComponent(); _zoneRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; _zoneRenderer.receiveShadows = false; Shader shader = Shader.Find("Sprites/Default"); _zoneMaterial = new Material(shader); _zoneMaterial.color = Color.clear; _zoneRenderer.material = _zoneMaterial; _zoneSphere.SetActive(false); } private void Update() { if (IsDead) return; if (_invulnerableTimer > 0f) _invulnerableTimer -= Time.unscaledDeltaTime; if (_isTransitioning) { if (_zoneSphere != null) _zoneSphere.SetActive(false); return; } UpdateAimedTarget(); UpdateZoneVisual(); HandleInput(); } private void UpdateAimedTarget() { AimedEnemy = null; IsTargetInPossessZone = false; if (_camTransform == null) return; Ray ray = new Ray(_camTransform.position, _camTransform.forward); if (Physics.SphereCast(ray, 0.5f, out RaycastHit hit, maxAimLookDistance, possessRayMask, QueryTriggerInteraction.Ignore)) { var enemy = hit.collider.GetComponentInParent(); if (enemy != null && enemy.CanBePossessed) { AimedEnemy = enemy; float dist = Vector3.Distance(transform.position, enemy.transform.position); IsTargetInPossessZone = dist <= possessRange; } } } private void UpdateZoneVisual() { if (_zoneSphere == null) return; if (AimedEnemy != null) { if (!_zoneSphere.activeSelf) _zoneSphere.SetActive(true); float diameter = possessRange * 2f; _zoneSphere.transform.localScale = new Vector3(diameter, diameter, diameter); Vector3 center = centerSphereOnTarget ? AimedEnemy.transform.position : transform.position; _zoneSphere.transform.position = center; Color targetColor = IsTargetInPossessZone ? zoneReadyColor : zoneOutOfRangeColor; _zoneMaterial.color = Color.Lerp(_zoneMaterial.color, targetColor, Time.unscaledDeltaTime * 12f); } else { if (_zoneSphere.activeSelf) { _zoneMaterial.color = Color.clear; _zoneSphere.SetActive(false); } } } private void HandleInput() { if (Keyboard.current == null) return; if (Keyboard.current.eKey.wasPressedThisFrame && AimedEnemy != null && IsTargetInPossessZone) { StartCoroutine(PossessRoutine(AimedEnemy)); } else if (Keyboard.current.qKey.wasPressedThisFrame && !IsInSpiritForm) { EnterSpiritForm(true); } } private IEnumerator PossessRoutine(EnemyAI target) { _isTransitioning = true; if (_zoneSphere != null) _zoneSphere.SetActive(false); BodyProfile newProfile = target.profile != null ? target.profile : BodyProfile.CreateDefault(target.enemyClass); float inheritedHealth = Mathf.Max(target.CurrentHealth, newProfile.maxHealth * 0.5f); Vector3 startPos = transform.position; Vector3 endPos = target.transform.position; Quaternion targetRot = target.transform.rotation; Destroy(target.gameObject); float elapsed = 0f; while (elapsed < possessDashDuration) { elapsed += Time.unscaledDeltaTime; float t = Mathf.Clamp01(elapsed / possessDashDuration); transform.position = Vector3.Lerp(startPos, endPos, t); yield return null; } transform.position = endPos; Player.Instance.CameraController.SetYaw(targetRot.eulerAngles.y); ApplyProfile(newProfile, inheritedHealth); _invulnerableTimer = 0.4f; _isTransitioning = false; } public void EnterSpiritForm(bool ejectImpulse) { if (IsDead) return; ApplyProfile(spiritProfile, spiritProfile.maxHealth); _invulnerableTimer = 0.5f; if (ejectImpulse && Player.Instance.Movement != null) { Player.Instance.Movement.AddVerticalImpulse(8f); } } public void ApplyProfile(BodyProfile profile, float startHealth) { CurrentProfile = profile; CurrentHealth = Mathf.Clamp(startHealth, 1f, profile.maxHealth); bool isSpirit = profile.bodyClass == BodyClass.Spirit; Time.timeScale = isSpirit ? spiritTimeScale : 1f; Time.fixedDeltaTime = 0.02f * Time.timeScale; Player.Instance.Movement.ApplyProfile(profile); Player.Instance.CameraController.ApplyProfile(profile); Player.Instance.Combat.ApplyProfile(profile); Player.Instance.Sound.SetFootstepPitch(profile.footstepPitch); } public void TakeDamage(float amount) { if (IsDead || _invulnerableTimer > 0f || _isTransitioning) return; CurrentHealth -= amount; if (CurrentHealth <= 0f) { if (!IsInSpiritForm) { // Тело уничтожено — выбрасывает обратно в Духа Debug.Log("Носитель уничтожен! Выброс в форму Духа!"); EnterSpiritForm(true); } else { // Убили самого Духа — проигрыш! Die(); } } } private void Die() { if (IsDead) return; IsDead = true; CurrentHealth = 0f; if (_zoneSphere != null) _zoneSphere.SetActive(false); // Останавливаем время и отключаем управление персонажа Time.timeScale = 0f; Player.Instance.Controls.Player.Disable(); // Освобождаем курсор мыши для клика по кнопкам панели проигрыша Cursor.lockState = CursorLockMode.None; Cursor.visible = true; Debug.Log("ДУХ РАЗВЕЯН! Игрок погиб."); OnPlayerDied?.Invoke(); } private void OnDestroy() { if (_zoneSphere != null) Destroy(_zoneSphere); Time.timeScale = 1f; Time.fixedDeltaTime = 0.02f; } } }