using UnityEngine; using UnityEngine.AI; using System.Collections; /// /// TeamAIController - AI controller for FRIENDLY teammates on the player's team. /// These AI characters fight FOR the player, targeting enemy team members. /// Used in Cash System mode for AI teammates. /// [RequireComponent(typeof(TeamMember))] public class TeamAIController : MonoBehaviour { [Header("AI State")] [SerializeField] private AIState currentState = AIState.Idle; [Header("Detection Settings")] [SerializeField] private float detectionRange = 15f; [SerializeField] private float attackRange = 2f; [SerializeField] private float followDistance = 5f; [Header("Behavior Settings")] [SerializeField] private float decisionInterval = 0.5f; [SerializeField] private bool followPlayer = true; [SerializeField] private float maxDistanceFromPlayer = 20f; [Header("References")] private TeamMember teamMember; private NavMeshAgent navAgent; private Animator animator; private CharacterAttacks characterAttacks; // Combat private TeamMember currentTarget; private float lastAttackTime; private float attackCooldown = 1.5f; // Cash System integration private CashCarrier cashCarrier; private CashSystemAI cashAI; public enum AIState { Idle, FollowPlayer, ChaseEnemy, Attack, CollectCash, DepositCash, Retreat } private void Awake() { teamMember = GetComponent(); navAgent = GetComponent(); animator = GetComponent(); characterAttacks = GetComponent(); cashCarrier = GetComponent(); cashAI = GetComponent(); } private void OnEnable() { Debug.Log($"[TeamAIController] Enabled on {gameObject.name}"); StartCoroutine(AIDecisionLoop()); } private void OnDisable() { Debug.Log($"[TeamAIController] Disabled on {gameObject.name}"); StopAllCoroutines(); } private IEnumerator AIDecisionLoop() { yield return new WaitForSeconds(0.5f); // Initial delay while (enabled) { MakeDecision(); yield return new WaitForSeconds(decisionInterval); } } private void MakeDecision() { // Lazy re-cache: CashSystemAI is added AFTER our Awake() by ConfigureCharacterControllers, // so the Awake-time GetComponent returns null. Re-check here once. if (cashAI == null) cashAI = GetComponent(); // If CashSystemAI is attached and active, delegate ALL decisions to it if (cashAI != null && cashAI.enabled) { // CashSystemAI handles the high-level decision making via its own Update() return; } // Standard team AI decision making TeamMember nearestEnemy = teamMember.FindNearestEnemy(); TeamMember playerControlled = TeamMember.GetPlayerControlledMember(); // Priority 1: Attack nearby enemies if (nearestEnemy != null) { float distanceToEnemy = Vector3.Distance(transform.position, nearestEnemy.transform.position); if (distanceToEnemy <= attackRange) { SetState(AIState.Attack); currentTarget = nearestEnemy; PerformAttack(); return; } else if (distanceToEnemy <= detectionRange) { SetState(AIState.ChaseEnemy); currentTarget = nearestEnemy; MoveToTarget(nearestEnemy.transform.position); return; } } // Priority 2: Follow the player-controlled character if (followPlayer && playerControlled != null && playerControlled != teamMember) { float distanceToPlayer = Vector3.Distance(transform.position, playerControlled.transform.position); if (distanceToPlayer > followDistance) { SetState(AIState.FollowPlayer); MoveToTarget(playerControlled.transform.position); return; } } // Default: Idle SetState(AIState.Idle); StopMovement(); } private void SetState(AIState newState) { if (currentState != newState) { currentState = newState; Debug.Log($"[TeamAIController] {gameObject.name} -> {newState}"); } } private void MoveToTarget(Vector3 position) { if (navAgent != null && navAgent.isActiveAndEnabled && navAgent.isOnNavMesh) { navAgent.isStopped = false; navAgent.SetDestination(position); // Update animator if (animator != null) { float speed = navAgent.velocity.magnitude / navAgent.speed; animator.SetFloat("Speed", speed); } } } private void StopMovement() { if (navAgent != null && navAgent.isActiveAndEnabled && navAgent.isOnNavMesh) { navAgent.isStopped = true; navAgent.velocity = Vector3.zero; } if (animator != null) { animator.SetFloat("Speed", 0f); } } private void PerformAttack() { if (Time.time - lastAttackTime < attackCooldown) return; // Face the target if (currentTarget != null) { Vector3 direction = (currentTarget.transform.position - transform.position).normalized; direction.y = 0; if (direction.sqrMagnitude > 0.001f) { transform.rotation = Quaternion.LookRotation(direction); } } // Perform attack using CharacterAttacks if available if (characterAttacks != null) { // Choose a random attack string[] attacks = { AnimationNames.Jab, AnimationNames.LowKick, AnimationNames.Haymaker }; string randomAttack = attacks[Random.Range(0, attacks.Length)]; characterAttacks.PunchAttacksDirectly(randomAttack); lastAttackTime = Time.time; } else if (animator != null) { // Fallback to direct animation trigger animator.SetTrigger("attack"); lastAttackTime = Time.time; } } private void Update() { // When CashSystemAI is driving, don't interfere with NavMeshAgent if (cashAI != null && cashAI.enabled) return; // Continuous state updates switch (currentState) { case AIState.Attack: if (currentTarget != null) { float distance = Vector3.Distance(transform.position, currentTarget.transform.position); if (distance > attackRange * 1.5f) { // Move closer MoveToTarget(currentTarget.transform.position); } else { StopMovement(); PerformAttack(); } } break; case AIState.ChaseEnemy: if (currentTarget != null) { MoveToTarget(currentTarget.transform.position); } break; } } /// /// Set a specific target for this AI to attack /// public void SetTarget(TeamMember target) { currentTarget = target; if (target != null) { SetState(AIState.ChaseEnemy); } } /// /// Command this AI to move to a specific position /// public void MoveTo(Vector3 position) { MoveToTarget(position); } /// /// Get current AI state /// public AIState GetCurrentState() => currentState; }