using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; namespace MedievalShopkeeper { public enum NPCState { Entering, Browsing, Queueing, CheckingOut, Leaving } [RequireComponent(typeof(NavMeshAgent))] public class CustomerNPC : Interactable { [Header("NPC Data")] public string npcName = "Peasant"; public float browseTimePerShelf = 2f; private NPCState state; private NavMeshAgent agent; private List basket = new List(); private ShopTill targetTill; public override string InteractionPrompt => $"Talk to {npcName}"; private void Start() { agent = GetComponent(); state = NPCState.Entering; StartCoroutine(BrainRoutine()); } private IEnumerator BrainRoutine() { state = NPCState.Browsing; int itemsToBuy = Random.Range(1, 4); for (int i = 0; i < itemsToBuy; i++) { // Find all shelves that ACTUALLY have items on them Shelf[] allShelves = FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); List stockedShelves = new List(); foreach (var shelf in allShelves) { if (shelf.HasStock) stockedShelves.Add(shelf); } if (stockedShelves.Count == 0) { Debug.Log($"[{npcName}] Looked around, but there are no shelves with stock right now."); break; } Shelf targetShelf = stockedShelves[Random.Range(0, stockedShelves.Count)]; Debug.Log($"[{npcName}] Decided to check out {targetShelf.gameObject.name}..."); agent.SetDestination(targetShelf.InteractionTransform.position); // Wait until we reach the shelf (or fail) bool reached = false; float timeout = 15f; float timer = 0f; while (timer < timeout) { if (!agent.pathPending) { if (agent.pathStatus == NavMeshPathStatus.PathComplete && agent.remainingDistance < 1.5f) { reached = true; break; } if (agent.pathStatus == NavMeshPathStatus.PathPartial || agent.pathStatus == NavMeshPathStatus.PathInvalid) { Debug.LogWarning($"[{npcName}] Path to {targetShelf.gameObject.name} is completely blocked (Status: {agent.pathStatus}). Giving up on this shelf."); break; } } timer += Time.deltaTime; yield return null; } if (reached) { Debug.Log($"[{npcName}] Successfully arrived at {targetShelf.gameObject.name}. Browsing..."); yield return new WaitForSeconds(browseTimePerShelf); if (targetShelf.TryTakeItem(out ItemData item)) { Debug.Log($"[{npcName}] Picked up 1x {item.itemName}!"); basket.Add(item); } else { Debug.Log($"[{npcName}] Bummer, someone took the last item before I could reach it."); } } else { Debug.LogWarning($"[{npcName}] Timed out trying to reach {targetShelf.gameObject.name}. It might be physically unreachable."); } } if (basket.Count > 0) { targetTill = FindFirstObjectByType(); if (targetTill != null) { state = NPCState.Queueing; targetTill.JoinQueue(this); } else { Debug.LogWarning($"[{npcName}] No ShopTill found! Dropping items and leaving."); ReturnItemsAndLeave(); } } else { Debug.Log($"[{npcName}] Couldn't find what I wanted. Leaving."); ReturnItemsAndLeave(); } } private void Update() { if (state == NPCState.Queueing && targetTill != null) { Vector3 qPos = targetTill.GetQueuePosition(this); agent.SetDestination(qPos); // Ensure they actually reached the queue spot and aren't stuck outside on a partial path if (targetTill.IsFrontOfQueue(this) && !agent.pathPending && agent.pathStatus == NavMeshPathStatus.PathComplete && agent.remainingDistance < 1.5f) { state = NPCState.CheckingOut; targetTill.PlaceItemsOnCounter(this, basket); } } } public List GetBasket() { return basket; } public void FinishCheckoutAndLeave() { basket.Clear(); LeaveShop(); } public void KickOut() { Debug.Log($"[{npcName}] 'Fine, I'm leaving!'"); if (targetTill != null && (state == NPCState.Queueing || state == NPCState.CheckingOut)) { targetTill.LeaveQueue(this); } ReturnItemsAndLeave(); } private void ReturnItemsAndLeave() { if (ShopInventory.Instance != null) { foreach (var item in basket) { if (item != null) ShopInventory.Instance.AddStock(item, 1); } } basket.Clear(); LeaveShop(); } private void LeaveShop() { if (state == NPCState.Leaving) return; state = NPCState.Leaving; StopAllCoroutines(); if (ShopDoor.Instance != null) { agent.SetDestination(ShopDoor.Instance.InteractionTransform.position); } StartCoroutine(WaitAndDestroy()); } private IEnumerator WaitAndDestroy() { float timeout = 15f; float timer = 0f; while (timer < timeout) { if (!agent.pathPending && agent.pathStatus == NavMeshPathStatus.PathComplete && agent.remainingDistance < 1.5f) { break; } timer += Time.deltaTime; yield return null; } Destroy(gameObject); } public override void Interact(PlayerController player) { base.Interact(player); if (UIManager.Instance != null) { UIManager.Instance.OpenNPCMenu(this, player); } else { Debug.Log($"[CustomerNPC] You interacted with {npcName}, but no UIManager is present."); } } public void TalkTo() { if (state == NPCState.Browsing) { Debug.Log($"[{npcName}] 'Just browsing, thank you!'"); } else if (state == NPCState.Queueing) { Debug.Log($"[{npcName}] 'I am waiting to pay...'"); } else { Debug.Log($"[{npcName}] 'Hello shopkeeper!'"); } } } }