using System; using UnityEngine; using Code.Gameplay.Player; using Code.UI; using DG.Tweening; using Cysharp.Threading.Tasks; using Random = UnityEngine.Random; namespace Code.Gameplay.Enemies { public class Boss : MonoBehaviour, IEnemy { [SerializeField] private int _maxHealth = 200; [SerializeField] private int _damagePerHit = 25; [SerializeField] private float _enrageThreshold = 0.5f; [SerializeField] private float _flyUpHeight = 10f; [SerializeField] private float _flyUpSpeed = 8f; [SerializeField] private float _diveSpeed = 18f; [SerializeField] private float _diveCooldown = 4f; [SerializeField] private float _featherCooldown = 3f; [SerializeField] private float _enragedFeatherCooldown = 1.5f; [SerializeField] private float _sweepSpeed = 12f; [SerializeField] private float _sweepYOffset = -1f; [SerializeField] private int _sweepPasses = 3; [SerializeField] private float _sweepRange = 20f; [SerializeField] private Transform _player; [SerializeField] private GameObject _featherPrefab; [SerializeField] private SpriteRenderer _spriteRenderer; [SerializeField] private AudioClip _diveSFX; [SerializeField] private AudioClip _featherSFX; [SerializeField] private AudioClip _sweepSFX; [SerializeField] private AudioClip _hitSFX; [SerializeField] private AudioClip _deathSFX; [SerializeField] private AudioSource _audioSource; private int _currentHealth; private State _currentState = State.Perching; private float _lastDiveTime; private float _lastFeatherTime; private bool isEnraged => (float)_currentHealth / _maxHealth <= _enrageThreshold; private bool _isSweeping; private Vector3 _spawnPosition; private enum State { Perching, Diving, Returning, Sweeping } private void Start() { _currentHealth = _maxHealth; _spawnPosition = transform.position; if (!_player) _player = GameObject.FindWithTag("Player").transform; } private void Update() { if (!_player) return; if (_currentState is State.Perching or State.Returning) { LookAtPlayer(); } if (_currentState != State.Perching) return; if (isEnraged && !_isSweeping && Random.value < 0.03f) { SweepAttack().Forget(); return; } if (Time.time - _lastDiveTime > _diveCooldown && CanDive()) { DiveAttack().Forget(); } else if (Time.time - _lastFeatherTime > (isEnraged ? _enragedFeatherCooldown : _featherCooldown)) { FeatherAttack(); } } private void LookAtPlayer() { if (!_player) return; var direction = _player.position.x - transform.position.x; transform.localScale = new Vector3( direction >= 0 ? Mathf.Abs(transform.localScale.x) : -Mathf.Abs(transform.localScale.x), transform.localScale.y, transform.localScale.z ); } private bool CanDive() => _player && Vector2.Distance(transform.position, _player.position) > 3f && Mathf.Abs(transform.position.y - _player.position.y) < 8f; private async UniTaskVoid DiveAttack() { _currentState = State.Diving; if (_diveSFX && _audioSource) _audioSource.PlayOneShot(_diveSFX); var startPos = transform.position; var flyUpPos = new Vector3(startPos.x, startPos.y + _flyUpHeight, startPos.z); while (Vector3.Distance(transform.position, flyUpPos) > 0.1f) { transform.position = Vector3.MoveTowards(transform.position, flyUpPos, _flyUpSpeed * Time.deltaTime); await UniTask.Yield(); } await UniTask.Delay(TimeSpan.FromSeconds(1.2f)); if (_player) { var diveTarget = new Vector3(_player.position.x, startPos.y, startPos.z); while (Vector3.Distance(transform.position, diveTarget) > 0.1f) { transform.position = Vector3.MoveTowards(transform.position, diveTarget, _diveSpeed * Time.deltaTime); await UniTask.Yield(); } if (Vector2.Distance(transform.position, _player.position) < 1.8f) { _player.GetComponent()?.TakeDamage(); } } _currentState = State.Returning; ReturnToPosition(_spawnPosition); _lastDiveTime = Time.time; _currentState = State.Perching; } private void FeatherAttack() { _lastFeatherTime = Time.time; if (!_featherPrefab || !_player) return; if (_featherSFX && _audioSource) _audioSource.PlayOneShot(_featherSFX); var facingRight = transform.localScale.x > 0; var angles = facingRight ? new[] { 20f, 0f, -20f } : new[] { 160f, 180f, 200f }; foreach (var angle in angles) { var feather = Instantiate(_featherPrefab, transform.position, Quaternion.identity); var featherScript = feather.GetComponent(); if (featherScript) { featherScript.Launch(angle); } } } private async UniTaskVoid SweepAttack() { if (!_player) return; _isSweeping = true; _currentState = State.Sweeping; if (_sweepSFX && _audioSource) _audioSource.PlayOneShot(_sweepSFX); var sweepY = _player.position.y + _sweepYOffset; var leftBound = new Vector3(_player.position.x - _sweepRange, sweepY, transform.position.z); var rightBound = new Vector3(_player.position.x + _sweepRange, sweepY, transform.position.z); for (var i = 0; i < _sweepPasses; i++) { while (transform.position.x < rightBound.x) { transform.position += Vector3.right * (_sweepSpeed * Time.deltaTime); LookAtDirection(true); CheckPlayerCollision(); await UniTask.Yield(); } while (transform.position.x > leftBound.x) { transform.position += Vector3.left * (_sweepSpeed * Time.deltaTime); LookAtDirection(false); CheckPlayerCollision(); await UniTask.Yield(); } } _isSweeping = false; _currentState = State.Returning; ReturnToPosition(_spawnPosition); _currentState = State.Perching; } private void LookAtDirection(bool right) { transform.localScale = new Vector3( right ? Mathf.Abs(transform.localScale.x) : -Mathf.Abs(transform.localScale.x), transform.localScale.y, transform.localScale.z ); } private void ReturnToPosition(Vector3 target) { transform.position = Vector3.MoveTowards(transform.position, target, 10f * Time.deltaTime); } private void CheckPlayerCollision() { if (_player && Vector2.Distance(transform.position, _player.position) < 1.8f) { _player.GetComponent()?.TakeDamage(); } } public void TakeDamage() { _currentHealth -= _damagePerHit; if (_hitSFX && _audioSource) _audioSource.PlayOneShot(_hitSFX); BlinkHit(); if (_currentHealth <= 0) { Die(); } } private void BlinkHit() { if (!_spriteRenderer) return; var originalColor = _spriteRenderer.color; var hitColor = new Color(1f, 0.3f, 0.3f, originalColor.a); DOTween.Sequence() .Append(_spriteRenderer.DOColor(hitColor, 0.1f)) .Append(_spriteRenderer.DOColor(originalColor, 0.1f)) .Append(_spriteRenderer.DOColor(hitColor, 0.1f)) .Append(_spriteRenderer.DOColor(originalColor, 0.1f)) .SetLink(gameObject); } private void Die() { if (_deathSFX && _audioSource) _audioSource.PlayOneShot(_deathSFX); enabled = false; if (_spriteRenderer) { _spriteRenderer.DOFade(0f, 2f) .OnComplete(() => { var gameplayController = FindFirstObjectByType(); gameplayController?.OnBossDefeated(); Destroy(gameObject); }) .SetLink(gameObject); } else { var gameplayController = FindFirstObjectByType(); gameplayController?.OnBossDefeated(); Destroy(gameObject); } } } }