206 lines
6.3 KiB
C#
206 lines
6.3 KiB
C#
using Code.Audio;
|
|
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
using Code.Gameplay.Player;
|
|
using Code.Gameplay.Character.Controller;
|
|
using Code.Gameplay.Character.Core;
|
|
using Code.Gameplay.Enemies.Animators;
|
|
using Reflex.Attributes;
|
|
using Reflex.Core;
|
|
using Reflex.Injectors;
|
|
|
|
namespace Code.Gameplay.Enemies
|
|
{
|
|
public abstract class BaseEnemy : MonoBehaviour
|
|
{
|
|
[SerializeField] protected NavMeshAgent agent;
|
|
[SerializeField] protected Collider enemyCollider;
|
|
[Inject] protected IAudioService audioService;
|
|
|
|
protected int Damage;
|
|
protected float AttackCooldown;
|
|
|
|
protected float DetectionRange;
|
|
protected float AttackRange;
|
|
|
|
protected float Speed;
|
|
|
|
protected bool IsStunnable;
|
|
protected float StunDuration;
|
|
|
|
protected bool UsesIdleMovement;
|
|
protected float IdleWanderRadius;
|
|
protected float IdleMoveDurationMin;
|
|
protected float IdleMoveDurationMax;
|
|
protected float IdlePauseDurationMin;
|
|
protected float IdlePauseDurationMax;
|
|
|
|
private bool _isStunned;
|
|
private float _currentStunTimer;
|
|
|
|
protected bool _canAttack = true;
|
|
private float _attackCooldownTimer;
|
|
|
|
private enum IdleState { Moving, Paused }
|
|
private IdleState _currentIdleState = IdleState.Paused;
|
|
private float _idleTimer;
|
|
private Vector3 _idleTargetPosition;
|
|
|
|
[Inject] private IPlayerProvider _playerProvider;
|
|
protected CharacterProvider Player => _playerProvider?.Character;
|
|
protected PlayerHealth PlayerHealth => _playerProvider?.Health;
|
|
protected CustomCharacterController PlayerController => Player?.CustomCharacterController;
|
|
|
|
protected virtual void Awake()
|
|
{
|
|
// // TODO Мешает
|
|
// if (_playerProvider == null)
|
|
// {
|
|
// AttributeInjector.Inject(this, Container.ProjectContainer);
|
|
// }
|
|
}
|
|
|
|
protected virtual void Update()
|
|
{
|
|
if (!Player) return;
|
|
|
|
HandleStun();
|
|
HandleAttackCooldown();
|
|
if (_isStunned) return;
|
|
|
|
HandleMovement();
|
|
HandleAttack();
|
|
}
|
|
|
|
protected virtual void HandleMovement()
|
|
{
|
|
if (!Player || !agent) return;
|
|
|
|
var distanceToPlayer = Vector3.Distance(transform.position, Player.transform.position);
|
|
|
|
if (distanceToPlayer <= DetectionRange)
|
|
{
|
|
agent.isStopped = false;
|
|
agent.SetDestination(Player.transform.position);
|
|
}
|
|
else if (UsesIdleMovement)
|
|
{
|
|
HandleIdleMovement();
|
|
}
|
|
else
|
|
{
|
|
agent.isStopped = true;
|
|
}
|
|
}
|
|
|
|
protected virtual void HandleIdleMovement()
|
|
{
|
|
_idleTimer -= Time.deltaTime;
|
|
|
|
switch (_currentIdleState)
|
|
{
|
|
case IdleState.Paused:
|
|
if (_idleTimer <= 0f)
|
|
{
|
|
// Start moving to a new random point
|
|
_currentIdleState = IdleState.Moving;
|
|
SetNewIdleDestination();
|
|
_idleTimer = Random.Range(IdleMoveDurationMin, IdleMoveDurationMax);
|
|
agent.isStopped = false;
|
|
}
|
|
break;
|
|
|
|
case IdleState.Moving:
|
|
if (_idleTimer <= 0f || HasReachedIdleDestination())
|
|
{
|
|
// Stop and pause
|
|
_currentIdleState = IdleState.Paused;
|
|
_idleTimer = Random.Range(IdlePauseDurationMin, IdlePauseDurationMax);
|
|
agent.isStopped = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void SetNewIdleDestination()
|
|
{
|
|
Vector3 randomPoint = transform.position + Random.insideUnitSphere * IdleWanderRadius;
|
|
NavMeshHit hit;
|
|
if (NavMesh.SamplePosition(randomPoint, out hit, 3f, agent.areaMask))
|
|
{
|
|
_idleTargetPosition = hit.position;
|
|
agent.SetDestination(_idleTargetPosition);
|
|
}
|
|
else
|
|
{
|
|
// Fallback: stay
|
|
_idleTargetPosition = transform.position;
|
|
}
|
|
}
|
|
|
|
private bool HasReachedIdleDestination()
|
|
{
|
|
return agent.remainingDistance <= agent.stoppingDistance;
|
|
}
|
|
|
|
protected virtual void HandleAttack() { }
|
|
|
|
protected virtual void HandleStun()
|
|
{
|
|
if (!_isStunned || !IsStunnable) return;
|
|
|
|
_currentStunTimer -= Time.deltaTime;
|
|
|
|
if (!(_currentStunTimer <= 0)) return;
|
|
|
|
_isStunned = false;
|
|
agent.isStopped = false;
|
|
enemyCollider.enabled = true;
|
|
}
|
|
|
|
protected virtual void ApplyStun()
|
|
{
|
|
_isStunned = true;
|
|
_currentStunTimer = StunDuration;
|
|
agent.isStopped = true;
|
|
enemyCollider.enabled = false;
|
|
}
|
|
|
|
protected virtual void HandleAttackCooldown()
|
|
{
|
|
if (_canAttack) return;
|
|
|
|
_attackCooldownTimer -= Time.deltaTime;
|
|
|
|
if (_attackCooldownTimer <= 0)
|
|
{
|
|
_canAttack = true;
|
|
}
|
|
}
|
|
|
|
protected virtual void DealDamage()
|
|
{
|
|
if (PlayerHealth == null) return;
|
|
|
|
PlayerHealth.GetDamage(Damage);
|
|
ApplyEffectsToPlayer();
|
|
|
|
_canAttack = false;
|
|
_attackCooldownTimer = AttackCooldown;
|
|
}
|
|
protected virtual void ApplyEffectsToPlayer()
|
|
{
|
|
if (PlayerController)
|
|
PlayerController.BlockAbilities(3f); // TODO в конфиг
|
|
}
|
|
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
Gizmos.color = Color.yellow;
|
|
Gizmos.DrawWireSphere(transform.position, DetectionRange);
|
|
|
|
Gizmos.color = new Color(0.3f, 0.3f, 0.8f, 0.4f);
|
|
Gizmos.DrawSphere(transform.position, AttackRange);
|
|
}
|
|
}
|
|
} |