using UnityEngine; using Pathfinding; using UnityEngine.SceneManagement; public class FollowerController : MonoBehaviour { [Header("Follower Settings")] public bool debugDrawTarget = true; public float followUpdateInterval = 0.1f; // How often to update follow logic public float manualMoveSmooth = 8f; // Smoothing factor for manual movement private Transform playerTransform; private AIPath playerAIPath; private AIPath aiPath; private Vector3 targetPoint; private float timer; private bool isManualFollowing = true; // Default to manual following private Vector3 lastMoveDir = Vector3.right; // Default direction private float currentSpeed = 0f; private Animator animator; private Transform artTransform; [Header("Held Item")] public PickupItemData currentlyHeldItem; public SpriteRenderer heldObjectRenderer; public float heldIconDisplayHeight = 2.0f; // Desired height for held item icon private bool isReturningToPlayer = false; // Track if follower is returning after pickup // Speed fields for follower private float playerMaxSpeed = 5f; private float followerMaxSpeed = 6f; private float defaultFollowerMaxSpeed = 6f; // Pickup events public delegate void FollowerPickupHandler(); public event FollowerPickupHandler OnPickupArrived; public event FollowerPickupHandler OnPickupReturned; private Coroutine pickupCoroutine; private bool lastInteractionSuccess = true; public bool justCombined = false; void Awake() { aiPath = GetComponent(); // Find art prefab and animator artTransform = transform.Find("CharacterArt"); if (artTransform != null) { animator = artTransform.GetComponent(); } else { animator = GetComponentInChildren(); // fallback } } void OnEnable() { SceneManager.sceneLoaded += OnSceneLoaded; FindPlayerReference(); } void OnDisable() { SceneManager.sceneLoaded -= OnSceneLoaded; } void OnSceneLoaded(Scene scene, LoadSceneMode mode) { FindPlayerReference(); } void UpdateFollowTarget() { if (playerTransform == null) { FindPlayerReference(); if (playerTransform == null) return; } if (isManualFollowing) { Vector3 playerPos = playerTransform.position; Vector3 moveDir = Vector3.zero; if (playerAIPath != null && playerAIPath.velocity.magnitude > 0.01f) { moveDir = playerAIPath.velocity.normalized; lastMoveDir = moveDir; } else { moveDir = lastMoveDir; } // Use GameSettings for followDistance targetPoint = playerPos - moveDir * GameManager.Instance.FollowDistance; targetPoint.z = 0; if (aiPath != null) { aiPath.enabled = false; } } } void Update() { if (playerTransform == null) { FindPlayerReference(); if (playerTransform == null) return; } timer += Time.deltaTime; if (timer >= GameManager.Instance.FollowUpdateInterval) { timer = 0f; UpdateFollowTarget(); } if (isManualFollowing) { Vector2 current2D = new Vector2(transform.position.x, transform.position.y); Vector2 target2D = new Vector2(targetPoint.x, targetPoint.y); float dist = Vector2.Distance(current2D, target2D); float minSpeed = followerMaxSpeed * 0.3f; float lerpFactor = GameManager.Instance.ManualMoveSmooth * Time.deltaTime; float targetSpeed = 0f; if (dist > GameManager.Instance.StopThreshold) { if (dist > GameManager.Instance.ThresholdFar) { targetSpeed = followerMaxSpeed; } else if (dist > GameManager.Instance.ThresholdNear && dist <= GameManager.Instance.ThresholdFar) { targetSpeed = followerMaxSpeed; } else if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear) { targetSpeed = minSpeed; } currentSpeed = Mathf.Lerp(currentSpeed, targetSpeed, lerpFactor); if (dist > GameManager.Instance.StopThreshold && dist <= GameManager.Instance.ThresholdNear) { currentSpeed = Mathf.Max(currentSpeed, minSpeed); } Vector3 dir = (targetPoint - transform.position).normalized; transform.position += dir * currentSpeed * Time.deltaTime; } else { currentSpeed = 0f; } } if (isReturningToPlayer && aiPath != null && aiPath.enabled && playerTransform != null) { aiPath.destination = playerTransform.position; } if (animator != null) { float normalizedSpeed = 0f; if (isManualFollowing) { normalizedSpeed = currentSpeed / followerMaxSpeed; } else if (aiPath != null) { normalizedSpeed = aiPath.velocity.magnitude / followerMaxSpeed; } animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed)); } } void FindPlayerReference() { GameObject playerObj = GameObject.FindGameObjectWithTag("Player"); if (playerObj != null) { playerTransform = playerObj.transform; playerAIPath = playerObj.GetComponent(); if (playerAIPath != null) { playerMaxSpeed = playerAIPath.maxSpeed; defaultFollowerMaxSpeed = playerMaxSpeed; followerMaxSpeed = playerMaxSpeed * GameManager.Instance.FollowerSpeedMultiplier; } } else { playerTransform = null; playerAIPath = null; } } // Command follower to go to a specific point (pathfinding mode) public void GoToPoint(Vector2 worldPosition) { isManualFollowing = false; if (aiPath != null) { aiPath.enabled = true; aiPath.maxSpeed = followerMaxSpeed; aiPath.destination = new Vector3(worldPosition.x, worldPosition.y, 0); } } // Command follower to go to a specific point and return to player public void GoToPointAndReturn(Vector2 itemPosition, Transform playerTransform) { if (pickupCoroutine != null) StopCoroutine(pickupCoroutine); if (aiPath != null) aiPath.maxSpeed = followerMaxSpeed; pickupCoroutine = StartCoroutine(PickupSequence(itemPosition, playerTransform)); } public void SetHeldItem(PickupItemData itemData) { currentlyHeldItem = itemData; if (heldObjectRenderer != null) { if (currentlyHeldItem != null && currentlyHeldItem.mapSprite != null) { heldObjectRenderer.sprite = currentlyHeldItem.mapSprite; heldObjectRenderer.enabled = true; // Scale held icon to fixed height, preserve aspect ratio var sprite = currentlyHeldItem.mapSprite; float spriteHeight = sprite.bounds.size.y; float spriteWidth = sprite.bounds.size.x; if (spriteHeight > 0f) { float scaleY = heldIconDisplayHeight / spriteHeight; float scaleX = scaleY * (spriteWidth / spriteHeight); heldObjectRenderer.transform.localScale = new Vector3(scaleX, scaleY, 1f); } } else { heldObjectRenderer.sprite = null; heldObjectRenderer.enabled = false; } } } public void SetInteractionResult(bool success) { lastInteractionSuccess = success; } private System.Collections.IEnumerator PickupSequence(Vector2 itemPosition, Transform playerTransform) { isManualFollowing = false; isReturningToPlayer = false; if (aiPath != null) { aiPath.enabled = true; aiPath.maxSpeed = followerMaxSpeed; aiPath.destination = new Vector3(itemPosition.x, itemPosition.y, 0); } // Wait until follower reaches item (2D distance) while (Vector2.Distance(new Vector2(transform.position.x, transform.position.y), new Vector2(itemPosition.x, itemPosition.y)) > GameManager.Instance.StopThreshold) { yield return null; } OnPickupArrived?.Invoke(); // Only perform pickup/swap logic if interaction succeeded if (lastInteractionSuccess && heldObjectRenderer != null) { Collider2D[] hits = Physics2D.OverlapCircleAll(itemPosition, 0.2f); foreach (var hit in hits) { var pickup = hit.GetComponent(); if (pickup != null) { var slotBehavior = pickup.GetComponent(); if (slotBehavior != null) { // Slot item: do not destroy or swap, just return to player break; } if (justCombined) { // Combination: just destroy the pickup, don't spawn anything GameObject.Destroy(pickup.gameObject); justCombined = false; break; } // Swap logic: if holding an item, drop it here if (currentlyHeldItem != null) { var basePickupPrefab = GameManager.Instance.BasePickupPrefab; if (basePickupPrefab != null) { var droppedPickupObj = GameObject.Instantiate(basePickupPrefab, pickup.transform.position, Quaternion.identity); var droppedPickup = droppedPickupObj.GetComponent(); if (droppedPickup != null) { droppedPickup.itemData = currentlyHeldItem; droppedPickup.ApplyItemData(); } } else { Debug.LogWarning("BasePickup prefab not assigned in GameSettings"); } } SetHeldItem(pickup.itemData); GameObject.Destroy(pickup.gameObject); break; } } } // Wait briefly, then return to player yield return new WaitForSeconds(0.2f); if (aiPath != null && playerTransform != null) { aiPath.maxSpeed = followerMaxSpeed; aiPath.destination = playerTransform.position; } isReturningToPlayer = true; // Wait until follower returns to player (2D distance) while (playerTransform != null && Vector2.Distance(new Vector2(transform.position.x, transform.position.y), new Vector2(playerTransform.position.x, playerTransform.position.y)) > GameManager.Instance.StopThreshold) { yield return null; } isReturningToPlayer = false; OnPickupReturned?.Invoke(); // Reset follower speed to normal after pickup followerMaxSpeed = defaultFollowerMaxSpeed; if (aiPath != null) aiPath.maxSpeed = followerMaxSpeed; isManualFollowing = true; if (aiPath != null) aiPath.enabled = false; pickupCoroutine = null; } void OnDrawGizmos() { if (debugDrawTarget && Application.isPlaying) { Gizmos.color = Color.cyan; Gizmos.DrawSphere(targetPoint, 0.2f); Gizmos.color = Color.yellow; Gizmos.DrawLine(transform.position, targetPoint); } } }