using UnityEngine; using UnityEngine.InputSystem; using System.Collections.Generic; using System; [System.Serializable] public enum JobType { None, // Unemployed - transports goods Woodcutter, // Cuts trees Stonecutter, // Mines stone Farmer, // Grows food Builder // Constructs buildings } [System.Serializable] public enum VillagerState { Idle, MovingToWork, Working, MovingToDeliver, Delivering, Transporting, BuildingConstruction, // Working on a building construction site BuildingField // Farmer building a field } [System.Serializable] public class Experience { public float woodcutting = 1f; public float stonecutting = 1f; public float farming = 1f; public float building = 1f; public float GetExperienceForJob(JobType job) { return job switch { JobType.Woodcutter => woodcutting, JobType.Stonecutter => stonecutting, JobType.Farmer => farming, JobType.Builder => building, _ => 1f }; } public void AddExperience(JobType job, float amount) { switch (job) { case JobType.Woodcutter: woodcutting += amount; break; case JobType.Stonecutter: stonecutting += amount; break; case JobType.Farmer: farming += amount; break; case JobType.Builder: building += amount; break; } } } public class Villager : MonoBehaviour { [Header("Villager Info")] public string villagerName; public JobType currentJob = JobType.None; public VillagerState state = VillagerState.Idle; [Header("Experience")] public Experience experience = new Experience(); [Header("Work Settings")] public float workSpeed = 1f; public float moveSpeed = 3f; public float workRange = 2f; [Header("Current Task")] public GameObject targetWorkplace; public GameObject targetDelivery; public ResourceType carryingResource; public int carryingAmount = 0; public int maxCarryCapacity = 10; [Header("Construction")] public ConstructionSite assignedConstructionSite; public Field assignedField; public GameObject homeBuilding; // For farmers living in farmhouses [Header("Visual")] public Renderer villagerRenderer; public GameObject selectionIndicator; private Vector3 targetPosition; private bool isSelected = false; private float workTimer = 0f; private VillagerManager villagerManager; public static event Action OnVillagerSelected; public static event Action OnJobChanged; void Start() { villagerManager = FindFirstObjectByType(); if (villagerRenderer == null) villagerRenderer = GetComponent(); if (selectionIndicator != null) selectionIndicator.SetActive(false); // Random starting experience experience.woodcutting = UnityEngine.Random.Range(0.8f, 1.5f); experience.stonecutting = UnityEngine.Random.Range(0.8f, 1.5f); experience.farming = UnityEngine.Random.Range(0.8f, 1.5f); experience.building = UnityEngine.Random.Range(0.8f, 1.5f); } void Update() { UpdateBehavior(); } void UpdateBehavior() { switch (state) { case VillagerState.Idle: HandleIdleState(); break; case VillagerState.MovingToWork: HandleMovingToWork(); break; case VillagerState.Working: HandleWorking(); break; case VillagerState.MovingToDeliver: HandleMovingToDeliver(); break; case VillagerState.Delivering: HandleDelivering(); break; case VillagerState.Transporting: HandleTransporting(); break; case VillagerState.BuildingConstruction: HandleConstruction(); break; case VillagerState.BuildingField: HandleFieldBuilding(); break; } } void HandleIdleState() { if (currentJob == JobType.None) { // Unemployed villagers stay idle until assigned a job // They can be selected and assigned jobs via drag-and-drop return; } else { // Look for work appropriate to job FindWork(); } } void HandleMovingToWork() { if (MoveToTarget(targetPosition)) { state = VillagerState.Working; workTimer = 0f; // Start harvesting on the resource node if (targetWorkplace != null) { ResourceNode node = targetWorkplace.GetComponent(); if (node != null) { node.StartHarvesting(this); } } } } void HandleWorking() { if (targetWorkplace == null) { state = VillagerState.Idle; return; } workTimer += Time.deltaTime * GetWorkEfficiency(); float workDuration = GetWorkDuration(); if (workTimer >= workDuration) { // Actually harvest resources from the node ResourceNode node = targetWorkplace.GetComponent(); if (node != null) { int requestedAmount = GetProducedAmount(); int harvestedAmount = node.HarvestResources(requestedAmount, GetWorkEfficiency()); carryingAmount = harvestedAmount; carryingResource = node.resourceType; if (harvestedAmount > 0) { // Stop harvesting before leaving node.StopHarvesting(); CompleteWork(); } else { // No resources left, stop harvesting and find new workplace node.StopHarvesting(); targetWorkplace = null; state = VillagerState.Idle; } } else { // Fallback for non-node workplaces CompleteWork(); } } } void HandleMovingToDeliver() { if (MoveToTarget(targetPosition)) { state = VillagerState.Delivering; } } void HandleDelivering() { if (carryingAmount > 0) { DeliverResources(); } // Always transition back to idle after delivery attempt state = VillagerState.Idle; targetPosition = transform.position; // Clear target } void HandleTransporting() { // Similar to delivering but for transport tasks if (MoveToTarget(targetPosition)) { DeliverResources(); state = VillagerState.Idle; } } bool MoveToTarget(Vector3 target) { Vector3 direction = (target - transform.position).normalized; transform.position += direction * moveSpeed * Time.deltaTime; return Vector3.Distance(transform.position, target) < 0.5f; } void FindWork() { // Builders should look for construction sites, not resource nodes if (currentJob == JobType.Builder) { FindAvailableConstructionSite(); return; } // Farmers should check if they need to plant a field if (currentJob == JobType.Farmer && homeBuilding != null) { // Check if there are fewer than 2 fields near the farmhouse Field[] nearbyFields = FindObjectsByType(FindObjectsSortMode.None); int fieldsNearHome = 0; foreach (Field field in nearbyFields) { if (field.farmhouse == homeBuilding) fieldsNearHome++; } // Plant a new field if needed if (fieldsNearHome < 2) { PlantNewField(); return; } } // If villager already has a specific workplace assigned, use it if (targetWorkplace != null) { targetPosition = targetWorkplace.transform.position; state = VillagerState.MovingToWork; return; } // Otherwise, find appropriate workplace based on job GameObject workplace = villagerManager.FindWorkplaceForJob(currentJob, transform.position); if (workplace != null) { targetWorkplace = workplace; targetPosition = workplace.transform.position; state = VillagerState.MovingToWork; } } void FindTransportTask() { // Look for resources that need to be moved // This is simplified - in a real game you'd have a more complex task system var townHall = GameObject.FindWithTag("TownHall"); if (townHall != null && carryingAmount == 0) { // Simple transport: move to a random resource and bring it to town hall state = VillagerState.Transporting; } } void CompleteWork() { // carryingResource and carryingAmount are already set by HandleWorking if harvesting from a node // Only set them here if not already set (for jobs that don't use ResourceNode) if (carryingAmount == 0) { ResourceType producedResource = GetProducedResource(); int amount = GetProducedAmount(); carryingResource = producedResource; carryingAmount = amount; } // Add experience experience.AddExperience(currentJob, 0.1f); // Find delivery target (usually TownHall) GameObject townHall = GameObject.FindWithTag("TownHall"); if (townHall != null) { targetDelivery = townHall; targetPosition = townHall.transform.position; state = VillagerState.MovingToDeliver; } else { // Deliver immediately if no town hall found DeliverResources(); state = VillagerState.Idle; } } void DeliverResources() { if (carryingAmount > 0) { Debug.Log($"{villagerName} delivering {carryingAmount} {carryingResource} to storage"); GameManager.Instance.resourceManager.AddResource(carryingResource, carryingAmount); carryingAmount = 0; carryingResource = ResourceType.None; } } ResourceType GetProducedResource() { return currentJob switch { JobType.Woodcutter => ResourceType.Wood, JobType.Stonecutter => ResourceType.Stone, JobType.Farmer => ResourceType.Food, JobType.Builder => ResourceType.None, // Builders don't produce resources _ => ResourceType.None }; } int GetProducedAmount() { float efficiency = GetWorkEfficiency(); int baseAmount = UnityEngine.Random.Range(1, 4); int producedAmount = Mathf.RoundToInt(baseAmount * efficiency); // Cap maximum production per work cycle to prevent resource explosion return Mathf.Clamp(producedAmount, 1, 10); } float GetWorkEfficiency() { float baseEfficiency = experience.GetExperienceForJob(currentJob) * workSpeed; // Cap efficiency to prevent resource explosion (max 3x efficiency) return Mathf.Clamp(baseEfficiency, 0.5f, 3f); } float GetWorkDuration() { return 3f / GetWorkEfficiency(); // Base 3 seconds, modified by efficiency } public void SetJob(JobType newJob) { currentJob = newJob; state = VillagerState.Idle; // Clear previous workplace assignment when changing jobs if (currentJob == JobType.None) { targetWorkplace = null; } OnJobChanged?.Invoke(this, newJob); // Update visual appearance based on job UpdateAppearance(); } public void AssignToSpecificWorkplace(GameObject workplace) { targetWorkplace = workplace; if (workplace != null) { targetPosition = workplace.transform.position; state = VillagerState.MovingToWork; Debug.Log($"{villagerName} assigned to specific workplace: {workplace.name}"); } } void UpdateAppearance() { if (villagerRenderer != null) { Color jobColor = currentJob switch { JobType.Woodcutter => Color.green, JobType.Stonecutter => Color.gray, JobType.Farmer => Color.yellow, JobType.Builder => Color.blue, _ => Color.white }; villagerRenderer.material.color = jobColor; } } public void SetSelected(bool selected) { isSelected = selected; if (selectionIndicator != null) selectionIndicator.SetActive(selected); Debug.Log($"Villager {villagerName} SetSelected: {selected}"); if (selected) { Debug.Log($"Villager {villagerName} triggering OnVillagerSelected event"); OnVillagerSelected?.Invoke(this); } } void OnMouseDown() { // Use Physics raycast instead of OnMouseDown for better reliability if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame) { if (GameManager.Instance?.selectionManager != null) { GameManager.Instance.selectionManager.SelectVillager(this); } } } public string GetStatusText() { string status = $"{villagerName}\nJob: {currentJob}\nState: {state}"; if (carryingAmount > 0) status += $"\nCarrying: {carryingAmount} {carryingResource}"; if (currentJob == JobType.None) status += "\n[Click and drag to assign job]"; if (assignedConstructionSite != null) status += $"\nBuilding: {assignedConstructionSite.buildingName}"; if (assignedField != null) status += $"\nField: {assignedField.fieldSize}"; return status; } // Construction methods public void AssignToConstruction(ConstructionSite site) { assignedConstructionSite = site; site.AssignBuilder(this); state = VillagerState.BuildingConstruction; targetPosition = site.transform.position; Debug.Log($"{villagerName} assigned to build {site.buildingName}"); } public void AssignToFieldConstruction(Field field) { assignedField = field; state = VillagerState.BuildingField; targetPosition = field.transform.position; Debug.Log($"{villagerName} assigned to build {field.fieldSize} field"); } void FindAvailableConstructionSite() { if (assignedConstructionSite != null) return; // Find nearest construction site that needs a builder ConstructionSite[] sites = FindObjectsByType(FindObjectsSortMode.None); ConstructionSite nearestSite = null; float nearestDistance = float.MaxValue; foreach (var site in sites) { if (site.assignedBuilder == null && !site.isComplete) { float distance = Vector3.Distance(transform.position, site.transform.position); if (distance < nearestDistance) { nearestDistance = distance; nearestSite = site; } } } if (nearestSite != null) { AssignToConstruction(nearestSite); Debug.Log($"{villagerName} found construction site: {nearestSite.buildingName}"); } } public void SetHomeBuilding(GameObject building) { homeBuilding = building; Debug.Log($"{villagerName} home set to {(building != null ? building.name : "none")}"); } void HandleConstruction() { if (assignedConstructionSite == null) { state = VillagerState.Idle; return; } // Move to construction site float distance = Vector3.Distance(transform.position, assignedConstructionSite.transform.position); if (distance > workRange) { transform.position = Vector3.MoveTowards(transform.position, assignedConstructionSite.transform.position, moveSpeed * Time.deltaTime); } else { // Work on construction assignedConstructionSite.AdvanceConstruction(Time.deltaTime * GetWorkEfficiency()); // Add building experience experience.AddExperience(JobType.Builder, 0.1f * Time.deltaTime); if (assignedConstructionSite.isComplete) { Debug.Log($"{villagerName} completed construction of {assignedConstructionSite.buildingName}"); assignedConstructionSite = null; // Return to builder's workshop if (targetWorkplace != null) { targetPosition = targetWorkplace.transform.position; state = VillagerState.MovingToWork; } else { state = VillagerState.Idle; } } } } void HandleFieldBuilding() { if (assignedField == null) { state = VillagerState.Idle; return; } // Move to field location float distance = Vector3.Distance(transform.position, assignedField.transform.position); if (distance > workRange) { transform.position = Vector3.MoveTowards(transform.position, assignedField.transform.position, moveSpeed * Time.deltaTime); } else { // Work on field construction assignedField.AdvanceConstruction(Time.deltaTime * GetWorkEfficiency()); // Add farming experience experience.AddExperience(JobType.Farmer, 0.1f * Time.deltaTime); if (!assignedField.isUnderConstruction) { Debug.Log($"{villagerName} completed field construction"); assignedField = null; state = VillagerState.Idle; } } } void PlantNewField() { if (homeBuilding == null) return; // Find a position near the farmhouse to plant a field Vector3 farmhousePos = homeBuilding.transform.position; Vector2 randomCircle = UnityEngine.Random.insideUnitCircle * 8f; // 8 units away Vector3 fieldPosition = farmhousePos + new Vector3(randomCircle.x, 0, randomCircle.y); fieldPosition.y = 0.1f; // Slightly above ground // Create the field GameObject fieldObj = GameObject.CreatePrimitive(PrimitiveType.Cube); fieldObj.name = $"Field_{homeBuilding.name}"; fieldObj.tag = "Farm"; fieldObj.transform.position = fieldPosition; Field field = fieldObj.AddComponent(); field.farmhouse = homeBuilding; field.fieldSize = FieldSize.Small; field.assignedFarmer = this; // Assign this field to the farmer to build assignedField = field; state = VillagerState.BuildingField; Debug.Log($"{villagerName} started planting a new field near {homeBuilding.name}"); } }