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 } public Job job; 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; // Cache the target so the NPC remembers what it is walking toward private Resource currentTargetResource; private IEnumerable buildings; void Start() { navMeshAgent.speed = movementSpeed; SetJob(job); } void Update() { // 1. If we don't have a valid target, find the nearest one if (currentTargetResource == null || currentTargetResource.resourceState != Resource.ResourceState.Full) { FindResources(); } // 2. If we do have a target, check if we are close enough to gather it else { float distanceToTarget = Vector3.Distance(transform.position, currentTargetResource.transform.position); if (distanceToTarget <= interactionRadius) { GatherResource(); } else { // Ensure movement animation is playing while walking animationController.SetBool("MovementInputHeld", true); animationController.SetFloat("MoveSpeed", movementSpeed); } } } private void MoveToLocation(Vector3 destination) { navMeshAgent.SetDestination(destination); animationController.SetBool("MovementInputHeld", true); animationController.SetFloat("MoveSpeed", movementSpeed); } private void FindResources() { // FindObjectsSortMode.None is required in newer Unity versions for performance Resource[] resources = Object.FindObjectsByType(FindObjectsSortMode.None); Resource closestResource = null; float closestDistance = Mathf.Infinity; // Loop through to find the *closest* valid resource, not just the first one 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 no resources are found, stop moving and stand idle navMeshAgent.ResetPath(); animationController.SetBool("MovementInputHeld", false); animationController.SetFloat("MoveSpeed", 0f); } } private void GatherResource() { // 1. Stop the agent navMeshAgent.ResetPath(); animationController.SetBool("MovementInputHeld", false); animationController.SetFloat("MoveSpeed", 0f); transform.LookAt(currentTargetResource.transform.position); // 2. Start the harvesting animation loop animationController.SetBool("IsHarvesting", true); // 3. Handle resource type switching if necessary if (carryingResource != resourceToWorkWith) { DespositResources(); carryingResource = resourceToWorkWith; } } // 4. This is called automatically by Unity when the tool hits the tree/rock private void Harvest() { // Safety check: Make sure we actually have a target to harvest if (currentTargetResource != null) { // Do the actual resource extraction at the exact moment of the visual hit currentTargetResource.RemoveResource(gatherAmount); AddToInventory(resourceToWorkWith, gatherAmount); // 5. Check if the resource node is now empty if (currentTargetResource.GetResourceState() == false) { // Clear the target and stop the animation because the node is depleted currentTargetResource = null; animationController.SetBool("IsHarvesting", false); } } else { // If there's no resource left, stop animating animationController.SetBool("IsHarvesting", false); } } private void SetJob(Job job) { switch (job) { case Job.Builder: //resourceToWorkWith = Resource.ResourceType.Wood; break; case Job.Soldier: // Uncomment or assign when ready // resourceToWorkWith = Resource.ResourceType.Wood; 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; } } private void AddToInventory(Resource.ResourceType resourceToAdd, int amountToAdd) { // Note: This capacity check assumes you mean "number of unique slots" // If you mean total quantity, you'll need to sum the dictionary values first. if (!inventory.ContainsKey(resourceToAdd) && inventory.Count >= inventoryCapacity) { return; } if (inventory.ContainsKey(resourceToAdd)) { inventory[resourceToAdd] += amountToAdd; } else { inventory.Add(resourceToAdd, amountToAdd); } // Using string interpolation for slightly cleaner formatting Debug.Log($"{amountToAdd} of {resourceToAdd} added to inventory"); } private void DespositResources() { // Properly assign the found buildings to an array so we can loop through them Building[] activeBuildings = Object.FindObjectsByType(FindObjectsSortMode.None); foreach (Building building in activeBuildings) { if (building.GetBuildingState() == Building.BuildingState.Active) { // NOTE: Currently this only interacts with Farms. if (building.GetBuildingType() == Building.BuildingType.Farm) { building.Interact(this); } } } } public Dictionary GetInventory() { return inventory; } }