using UnityEngine; using UnityEngine.AI; [RequireComponent(typeof(CharacterStats))] [RequireComponent(typeof(CombatController))] public class CombatAI : MonoBehaviour { [Header("Aggro & Retaliation")] [Tooltip("If true, automatically attacks target when taking damage.")] [SerializeField] private bool autoRetaliate = true; [Tooltip("Detection distance for hostile eyesight vision.")] [SerializeField] private float eyesightRange = 8.0f; [Tooltip("Layer mask for targeting (default Player).")] [SerializeField] private LayerMask targetLayer = ~0; [Header("Movement")] [SerializeField] private float chaseSpeed = 3.5f; private CharacterStats stats; private CombatController combat; private NavMeshAgent navAgent; private NPC npcComponent; private void Awake() { stats = GetComponent(); combat = GetComponent(); navAgent = GetComponent(); npcComponent = GetComponent(); stats.onDeath.AddListener(OnDeath); stats.onHealthChanged.AddListener(OnHealthChanged); } private void Update() { if (stats.IsDead) return; bool isHostile = (npcComponent != null) ? npcComponent.Relationship == NPCRelationship.Hostile : false; // Hostile eyesight check if no active target if (isHostile && combat.CurrentTarget == null) { ScanForHostileTarget(); } // Process combat chase & attack if (combat.CurrentTarget != null) { if (combat.CurrentTarget.IsDead) { combat.ClearTarget(); StopMovement(); SetNPCState(NPCState.Idle); return; } // Set NPC state to Attacking to stop wandering SetNPCState(NPCState.Attacking); float distanceToTarget = Vector3.Distance(transform.position, combat.CurrentTarget.transform.position); if (distanceToTarget > stats.AttackRange) { // Chase target MoveToPosition(combat.CurrentTarget.transform.position); } else { // In attack range StopMovement(); combat.TryExecuteAttack(); } } } private void SetNPCState(NPCState newState) { if (npcComponent != null && npcComponent.CurrentState != newState) { npcComponent.CurrentState = newState; } } private void ScanForHostileTarget() { Collider[] hits = Physics.OverlapSphere(transform.position, eyesightRange, targetLayer); foreach (var hit in hits) { if (hit.CompareTag("Player")) { CharacterStats playerStats = hit.GetComponent(); if (playerStats != null && !playerStats.IsDead) { combat.SetTarget(playerStats); Debug.Log($"[{gameObject.name}] Aggroed on Player '{playerStats.CharacterName}' via Eyesight Range!"); break; } } } } private void OnHealthChanged(float currentHealth, float maxHealth) { // Auto Retaliation on being attacked if (autoRetaliate && combat.CurrentTarget == null) { // Find attacker by scanning nearby player or closest damage dealer GameObject playerObj = GameObject.FindGameObjectWithTag("Player"); if (playerObj != null) { CharacterStats playerStats = playerObj.GetComponent(); if (playerStats != null && !playerStats.IsDead) { combat.SetTarget(playerStats); Debug.Log($"[{gameObject.name}] Auto-retaliating against '{playerStats.CharacterName}'!"); } } } } private void MoveToPosition(Vector3 position) { if (navAgent != null && navAgent.enabled) { navAgent.speed = chaseSpeed; navAgent.stoppingDistance = stats.AttackRange * 0.8f; navAgent.SetDestination(position); } else { Vector3 dir = (position - transform.position); dir.y = 0; if (dir.magnitude > stats.AttackRange) { transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10f * Time.deltaTime); transform.position += dir.normalized * chaseSpeed * Time.deltaTime; } } } private void StopMovement() { if (navAgent != null && navAgent.enabled) { if (navAgent.hasPath) navAgent.ResetPath(); } } private void OnDeath(GameObject killer) { combat.ClearTarget(); StopMovement(); SetNPCState(NPCState.Dead); enabled = false; } private void OnDrawGizmosSelected() { bool isHostileGizmo = (npcComponent != null) ? npcComponent.Relationship == NPCRelationship.Hostile : false; if (isHostileGizmo) { Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, eyesightRange); } } }