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; // ==== external hold via token set (e.g., bird pickup) ==== private readonly HashSet _externalHolders = new HashSet(); public bool IsExternallyHeld => _externalHolders.Count > 0; public void BeginPickupHold(object holder) { if (holder == null) return; if (_externalHolders.Add(holder) && rb != null) { // freeze horizontal immediately, keep gravity rb.velocity = new Vector3(0f, rb.velocity.y, 0f); } } public void EndPickupHold(object holder) { if (holder == null) return; _externalHolders.Remove(holder); } public void ForceReleaseAllExternalHolds() { _externalHolders.Clear(); } // ========================================================= void Awake() { zibuController = GetComponent(); rb = GetComponent(); zibuController.isPlayerControlled = false; if (arenaCube != null) arenaBounds = new Bounds(arenaCube.position, arenaCube.localScale); moveSpeed = Random.Range(moveSpeedRange.x, moveSpeedRange.y); decisionTimer = Random.Range(0f, decisionInterval); PickRoamTarget(); } void Update() { // --------- IMPORTANT CHANGE --------- // If externally held by the bird: freeze XZ (so pickup is clean) if (IsExternallyHeld) { if (rb) rb.velocity = new Vector3(0f, rb.velocity.y, 0f); return; } // If control is locked (e.g., push stun): DO NOTHING, but DON'T touch velocity. // This allows physics impulses from bumps to carry the NPC naturally. if (zibuController.ControlLocked) return; // ------------------------------------ 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]; ReRollSpeedSlightly(); return; } } target = null; PickRoamTarget(); } void MoveTowards(Vector3 destination) { // Respect holds/stuns (these are already early-returned in Update) 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() { 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 { Vector3 randomOffset = new Vector3(Random.Range(-roamRadius, roamRadius), 0, Random.Range(-roamRadius, roamRadius)); roamTarget = transform.position + randomOffset; } 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; yield return new WaitForSeconds(Random.Range(0.15f, 0.6f)); if (!zibuController.ControlLocked && !IsExternallyHeld) zibuController.PerformBump(transform.forward); lastTarget = target; target = null; PickRoamTarget(); yield return new WaitForSeconds(Random.Range(1.2f, 2.5f)); isBumpCooling = false; } 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); } void OnDrawGizmosSelected() { Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, roamRadius); if (arenaCube != null) { Gizmos.color = Color.cyan; Gizmos.DrawWireCube(arenaCube.position, arenaCube.localScale); } } }