using UnityEngine; using UnityEngine.AI; using System.Collections.Generic; using UnityEngine.UI; public class NPC : MonoBehaviour { public enum Job { Builder, Farmer, Soldier, Forester, Merchant, Miner, Citizen } public Job job = Job.Citizen; // Added a simple state tracker to cleanly separate gathering and traveling public enum NPCState { Gathering, Delivering, Idle } public NPCState currentState = NPCState.Idle; private Resource.ResourceType resourceToWorkWith, carryingResource; public Dictionary inventory = new Dictionary(); [SerializeField] int gatherAmount, inventoryCapacity; [SerializeField] NavMeshAgent navMeshAgent; [SerializeField] Animator animationController; [SerializeField] float movementSpeed, interactionRadius; [SerializeField] Image jobImg; [SerializeField] Sprite miner, forester, citizen, farmer, merchant, soldier, builder; private Resource currentTargetResource; private Building currentTargetBuilding; // Cache the TownHall so we don't search for it every frame void Start() { navMeshAgent.speed = movementSpeed; SetJob(job); } void Update() { // 1. Evaluate if we need to switch from Gathering to Delivering if (currentState == NPCState.Gathering && !CanAddToInventory(gatherAmount)) { animationController.SetBool("IsHarvesting", false); currentTargetResource = null; currentState = NPCState.Delivering; } // 2. Execute the current state's logic switch (currentState) { case NPCState.Gathering: HandleGatheringPhase(); break; case NPCState.Delivering: HandleDeliveryPhase(); break; case NPCState.Idle: navMeshAgent.ResetPath(); animationController.SetBool("MovementInputHeld", false); animationController.SetFloat("MoveSpeed", 0f); break; } } private void HandleGatheringPhase() { if (currentTargetResource == null || currentTargetResource.resourceState != Resource.ResourceState.Full) { FindResources(); } else { float distanceToTarget = Vector3.Distance(transform.position, currentTargetResource.transform.position); if (distanceToTarget <= interactionRadius) { GatherResource(); } else { MoveToLocation(currentTargetResource.transform.position); } } } private void HandleDeliveryPhase() { // 1. Find a TownHall if we haven't cached one yet if (currentTargetBuilding == null) { Building[] activeBuildings = Object.FindObjectsByType(FindObjectsSortMode.None); foreach (Building building in activeBuildings) { if (building.GetBuildingState() == Building.BuildingState.Active && building.GetBuildingType() == Building.BuildingType.TownHall) { currentTargetBuilding = building; break; // Exit the loop once we find one } } // If there's no TownHall active on the map, stand idle if (currentTargetBuilding == null) { currentState = NPCState.Idle; return; } } // 2. Move towards the TownHall float distanceToTarget = Vector3.Distance(transform.position, currentTargetBuilding.transform.position); if (distanceToTarget <= interactionRadius) { DepositResources(); } else { MoveToLocation(currentTargetBuilding.transform.position); } } private void MoveToLocation(Vector3 destination) { navMeshAgent.SetDestination(destination); animationController.SetBool("MovementInputHeld", true); animationController.SetFloat("MoveSpeed", movementSpeed); } private void FindResources() { Resource[] resources = Object.FindObjectsByType(FindObjectsSortMode.None); Resource closestResource = null; float closestDistance = Mathf.Infinity; foreach (Resource res in resources) { if (res.resourceType == resourceToWorkWith && res.resourceState == Resource.ResourceState.Full) { float distance = Vector3.Distance(transform.position, res.transform.position); if (distance < closestDistance) { closestDistance = distance; closestResource = res; } } } if (closestResource != null) { currentTargetResource = closestResource; MoveToLocation(currentTargetResource.transform.position); } else { // If the map is empty, check our pockets. // If we have items, go deliver them. If empty, stand idle. if (GetTotalInventoryAmount() > 0) { currentState = NPCState.Delivering; } else { currentState = NPCState.Idle; } } } private void GatherResource() { navMeshAgent.ResetPath(); animationController.SetBool("MovementInputHeld", false); animationController.SetFloat("MoveSpeed", 0f); transform.LookAt(currentTargetResource.transform.position); animationController.SetBool("IsHarvesting", true); if (carryingResource != resourceToWorkWith) { carryingResource = resourceToWorkWith; } } private void Harvest() { if (currentTargetResource != null) { // Safety check: if full, stop hitting and trigger delivery on the next frame if (!CanAddToInventory(gatherAmount)) { animationController.SetBool("IsHarvesting", false); currentTargetResource = null; currentState = NPCState.Delivering; return; } currentTargetResource.RemoveResource(gatherAmount); AddToInventory(resourceToWorkWith, gatherAmount); if (currentTargetResource.GetResourceState() == false) { currentTargetResource = null; animationController.SetBool("IsHarvesting", false); } } else { animationController.SetBool("IsHarvesting", false); } } public void SetJob(Job job) { switch (job) { case Job.Builder: //resourceToWorkWith = Resource.ResourceType.Building; jobImg.sprite = builder; break; case Job.Soldier: break; case Job.Farmer: resourceToWorkWith = Resource.ResourceType.Food; break; case Job.Forester: resourceToWorkWith = Resource.ResourceType.Wood; jobImg.sprite = forester; break; case Job.Merchant: resourceToWorkWith = Resource.ResourceType.Money; break; case Job.Miner: resourceToWorkWith = Resource.ResourceType.Stone; jobImg.sprite = miner; break; case Job.Citizen: resourceToWorkWith = Resource.ResourceType.Population; jobImg.sprite = citizen; break; } // Don't instantly teleport resources. Put the NPC into Delivery state. if (inventory.Count > 0 && carryingResource != resourceToWorkWith) { currentState = NPCState.Delivering; } carryingResource = resourceToWorkWith; this.job = job; } public Job GetJob() { return job; } private bool AddToInventory(Resource.ResourceType resourceToAdd, int amountToAdd) { if (amountToAdd <= 0) return true; if (!inventory.ContainsKey(resourceToAdd)) { inventory.Add(resourceToAdd, 0); } inventory[resourceToAdd] += amountToAdd; return true; } private bool CanAddToInventory(int amountToAdd) { return GetTotalInventoryAmount() + amountToAdd <= inventoryCapacity; } // Efficiency: Created a helper method so we aren't writing out the dictionary loop constantly private int GetTotalInventoryAmount() { int currentTotal = 0; foreach (int amount in inventory.Values) { currentTotal += amount; } return currentTotal; } private void DepositResources() { navMeshAgent.ResetPath(); animationController.SetBool("MovementInputHeld", false); animationController.SetFloat("MoveSpeed", 0f); if (currentTargetBuilding != null) { currentTargetBuilding.Interact(this); } // CRITICAL: You must clear the local inventory so CanAddToInventory() becomes true again. inventory.Clear(); currentTargetBuilding = null; currentState = NPCState.Gathering; } public Dictionary GetInventory() { return inventory; } }