using UnityEngine; using System.Collections; using System.Collections.Generic; [RequireComponent(typeof(CubeClash_ZibuController))] public class CubeClash_ZibuAI : MonoBehaviour { [Header("AI Settings")] [Tooltip("Each NPC rolls a speed in this range so they don't mirror each other.")] public Vector2 moveSpeedRange = new Vector2(2.5f, 4.5f); [Tooltip("Small extra randomization applied when retargeting to keep desynced.")] public float retargetSpeedJitter = 0.3f; [HideInInspector] public float moveSpeed; // current per-agent speed (rolled at runtime) public float decisionInterval = 2f; public float bumpRange = 2.5f; public float roamRadius = 5f; [Header("Arena Bounds")] public Transform arenaCube; private Bounds arenaBounds; private CubeClash_ZibuController zibuController; private Rigidbody rb; private CubeClash_ZibuController target; private Vector3 roamTarget; private float decisionTimer; private bool isBumpCooling = false; private CubeClash_ZibuController lastTarget; void Awake() { zibuController = GetComponent(); rb = GetComponent(); zibuController.isPlayerControlled = false; if (arenaCube != null) arenaBounds = new Bounds(arenaCube.position, arenaCube.localScale); // Roll an initial per-agent speed moveSpeed = Random.Range(moveSpeedRange.x, moveSpeedRange.y); // Randomize starting decision timer so AIs don’t sync up decisionTimer = Random.Range(0f, decisionInterval); PickRoamTarget(); } void Update() { if (zibuController.ControlLocked) return; // don't fight pushes decisionTimer += Time.deltaTime; if (decisionTimer >= decisionInterval) { decisionTimer = 0f; PickTargetOrRoam(); } if (target != null) { MoveTowards(target.transform.position); TryBumpTarget(); } else { MoveTowards(roamTarget); if (Vector3.Distance(transform.position, roamTarget) < 1f) PickRoamTarget(); } } void PickTargetOrRoam() { List allZibus = CubeClash_ZibuController.AllZibus; if (allZibus.Count > 1) { List candidates = new List(); foreach (var z in allZibus) if (z != zibuController) candidates.Add(z); if (candidates.Count > 0) { if (candidates.Count > 1 && lastTarget != null) candidates.Remove(lastTarget); int index = Random.Range(0, candidates.Count); target = candidates[index]; // Tiny re-roll of speed to avoid symmetric face-offs ReRollSpeedSlightly(); return; } } target = null; PickRoamTarget(); } void MoveTowards(Vector3 destination) { if (zibuController.ControlLocked) return; // skip if stunned Vector3 dir = (destination - transform.position).normalized; Vector3 move = new Vector3(dir.x, 0, dir.z) * moveSpeed; rb.velocity = new Vector3(move.x, rb.velocity.y, move.z); if (dir.magnitude > 0.1f) { dir.y = 0; Quaternion targetRot = Quaternion.LookRotation(dir, Vector3.up); transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, Time.deltaTime * 5f); } } void PickRoamTarget() { // Pick anywhere in arena to spread agents out if (arenaCube != null) { arenaBounds = new Bounds(arenaCube.position, arenaCube.localScale); float x = Random.Range(arenaBounds.min.x + 1f, arenaBounds.max.x - 1f); float z = Random.Range(arenaBounds.min.z + 1f, arenaBounds.max.z - 1f); roamTarget = new Vector3(x, transform.position.y, z); } else { // Fallback: local random offset if no arena assigned Vector3 randomOffset = new Vector3(Random.Range(-roamRadius, roamRadius), 0, Random.Range(-roamRadius, roamRadius)); roamTarget = transform.position + randomOffset; } // Slight re-roll on roam choose too ReRollSpeedSlightly(); } void TryBumpTarget() { if (isBumpCooling || target == null) return; float dist = Vector3.Distance(transform.position, target.transform.position); if (dist <= bumpRange) { Vector3 toTarget = (target.transform.position - transform.position).normalized; float dot = Vector3.Dot(transform.forward, toTarget); if (dot > 0.7f) StartCoroutine(BumpWithDelay()); } } IEnumerator BumpWithDelay() { isBumpCooling = true; // Extra jitter before bump to desync yield return new WaitForSeconds(Random.Range(0.15f, 0.6f)); if (!zibuController.ControlLocked) zibuController.PerformBump(transform.forward); lastTarget = target; target = null; PickRoamTarget(); // Randomized cooldown so they don't re-sync yield return new WaitForSeconds(Random.Range(1.2f, 2.5f)); isBumpCooling = false; } // --- Helpers --- void ReRollSpeedSlightly() { float baseRoll = Random.Range(moveSpeedRange.x, moveSpeedRange.y); float jitter = Random.Range(-retargetSpeedJitter, retargetSpeedJitter); moveSpeed = Mathf.Clamp(baseRoll + jitter, moveSpeedRange.x, moveSpeedRange.y); } }