Files
VillageBuilder/Assets/Scripts/NPC.cs

383 lines
12 KiB
C#

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, Building }
public NPCState currentState = NPCState.Idle;
private Resource.ResourceType resourceToWorkWith, carryingResource;
public Dictionary<Resource.ResourceType, int> inventory = new Dictionary<Resource.ResourceType, int>();
[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);
if (CanAddToInventory(gatherAmount) && job != Job.Citizen)
if (inventory.ContainsKey(resourceToWorkWith))
currentState = NPCState.Gathering;
else
DepositResources();
break;
case NPCState.Building:
HandleBuildingPhase();
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<Building>(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 HandleBuildingPhase()
{
// 1. If we finished our building, or someone else finished it, look for a new one
if (currentTargetBuilding == null || currentTargetBuilding.GetBuildingState() != Building.BuildingState.Unbuilt)
{
Building newJob = JobManager.Instance.RequestJob(Job.Builder);
if (newJob != null)
{
currentTargetBuilding = newJob;
}
else
{
currentState = NPCState.Idle;
return;
}
}
// 2. Move towards the unfinished building
float distanceToTarget = Vector3.Distance(transform.position, currentTargetBuilding.transform.position);
if (distanceToTarget <= interactionRadius)
{
WorkOnBuilding();
}
else
{
MoveToLocation(currentTargetBuilding.transform.position);
}
}
private void WorkOnBuilding()
{
// Stop moving and look at the building
navMeshAgent.ResetPath();
animationController.SetBool("MovementInputHeld", false);
animationController.SetFloat("MoveSpeed", 0f);
transform.LookAt(currentTargetBuilding.transform.position);
// You can reuse your IsHarvesting animation boolean, or create an "IsBuilding" one in the Animator
animationController.SetBool("IsHarvesting", true);
// Tell the building to progress its construction
currentTargetBuilding.StartBuilding();
}
private void MoveToLocation(Vector3 destination)
{
navMeshAgent.SetDestination(destination);
animationController.SetBool("MovementInputHeld", true);
animationController.SetFloat("MoveSpeed", movementSpeed);
}
private void FindResources()
{
Resource[] resources = Object.FindObjectsByType<Resource>(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;
CancelTask();
Building buildingTarget = JobManager.Instance.RequestJob(job);
if (buildingTarget != null)
{
currentTargetBuilding = buildingTarget;
currentState = NPCState.Building; // Put them in the Building state
}
else
{
// No buildings need building right now. Stand by.
currentState = NPCState.Idle;
}
break;
case Job.Soldier:
CancelTask();
break;
case Job.Farmer:
resourceToWorkWith = Resource.ResourceType.Food;
CancelTask();
break;
case Job.Forester:
resourceToWorkWith = Resource.ResourceType.Wood;
jobImg.sprite = forester;
CancelTask();
break;
case Job.Merchant:
resourceToWorkWith = Resource.ResourceType.Money;
CancelTask();
break;
case Job.Miner:
resourceToWorkWith = Resource.ResourceType.Stone;
jobImg.sprite = miner;
CancelTask();
break;
case Job.Citizen:
resourceToWorkWith = Resource.ResourceType.Population;
jobImg.sprite = citizen;
CancelTask();
break;
default:
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<Resource.ResourceType, int> GetInventory()
{
return inventory;
}
private void CancelTask()
{
navMeshAgent.destination = transform.position;
animationController.SetBool("MovementInputHeld", false);
animationController.SetFloat("MoveSpeed", 0f);
currentTargetResource = null;
currentTargetBuilding = null;
currentState = NPCState.Idle;
}
}