Villager.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. using UnityEngine;
  2. using UnityEngine.InputSystem;
  3. using System.Collections.Generic;
  4. using System;
  5. [System.Serializable]
  6. public enum JobType
  7. {
  8. None, // Unemployed - transports goods
  9. Woodcutter, // Cuts trees
  10. Stonecutter, // Mines stone
  11. Farmer, // Grows food
  12. Builder // Constructs buildings
  13. }
  14. [System.Serializable]
  15. public enum VillagerState
  16. {
  17. Idle,
  18. MovingToWork,
  19. Working,
  20. MovingToDeliver,
  21. Delivering,
  22. Transporting,
  23. BuildingConstruction, // Working on a building construction site
  24. BuildingField // Farmer building a field
  25. }
  26. [System.Serializable]
  27. public class Experience
  28. {
  29. public float woodcutting = 1f;
  30. public float stonecutting = 1f;
  31. public float farming = 1f;
  32. public float building = 1f;
  33. public float GetExperienceForJob(JobType job)
  34. {
  35. return job switch
  36. {
  37. JobType.Woodcutter => woodcutting,
  38. JobType.Stonecutter => stonecutting,
  39. JobType.Farmer => farming,
  40. JobType.Builder => building,
  41. _ => 1f
  42. };
  43. }
  44. public void AddExperience(JobType job, float amount)
  45. {
  46. switch (job)
  47. {
  48. case JobType.Woodcutter:
  49. woodcutting += amount;
  50. break;
  51. case JobType.Stonecutter:
  52. stonecutting += amount;
  53. break;
  54. case JobType.Farmer:
  55. farming += amount;
  56. break;
  57. case JobType.Builder:
  58. building += amount;
  59. break;
  60. }
  61. }
  62. }
  63. public class Villager : MonoBehaviour
  64. {
  65. [Header("Villager Info")]
  66. public string villagerName;
  67. public JobType currentJob = JobType.None;
  68. public VillagerState state = VillagerState.Idle;
  69. [Header("Experience")]
  70. public Experience experience = new Experience();
  71. [Header("Work Settings")]
  72. public float workSpeed = 1f;
  73. public float moveSpeed = 3f;
  74. public float workRange = 2f;
  75. [Header("Current Task")]
  76. public GameObject targetWorkplace;
  77. public GameObject targetDelivery;
  78. public ResourceType carryingResource;
  79. public int carryingAmount = 0;
  80. public int maxCarryCapacity = 10;
  81. [Header("Construction")]
  82. public ConstructionSite assignedConstructionSite;
  83. public Field assignedField;
  84. public GameObject homeBuilding; // For farmers living in farmhouses
  85. [Header("Visual")]
  86. public Renderer villagerRenderer;
  87. public GameObject selectionIndicator;
  88. private Vector3 targetPosition;
  89. private bool isSelected = false;
  90. private float workTimer = 0f;
  91. private VillagerManager villagerManager;
  92. public static event Action<Villager> OnVillagerSelected;
  93. public static event Action<Villager, JobType> OnJobChanged;
  94. void Start()
  95. {
  96. villagerManager = FindFirstObjectByType<VillagerManager>();
  97. if (villagerRenderer == null)
  98. villagerRenderer = GetComponent<Renderer>();
  99. if (selectionIndicator != null)
  100. selectionIndicator.SetActive(false);
  101. // Random starting experience
  102. experience.woodcutting = UnityEngine.Random.Range(0.8f, 1.5f);
  103. experience.stonecutting = UnityEngine.Random.Range(0.8f, 1.5f);
  104. experience.farming = UnityEngine.Random.Range(0.8f, 1.5f);
  105. experience.building = UnityEngine.Random.Range(0.8f, 1.5f);
  106. }
  107. void Update()
  108. {
  109. UpdateBehavior();
  110. }
  111. void UpdateBehavior()
  112. {
  113. switch (state)
  114. {
  115. case VillagerState.Idle:
  116. HandleIdleState();
  117. break;
  118. case VillagerState.MovingToWork:
  119. HandleMovingToWork();
  120. break;
  121. case VillagerState.Working:
  122. HandleWorking();
  123. break;
  124. case VillagerState.MovingToDeliver:
  125. HandleMovingToDeliver();
  126. break;
  127. case VillagerState.Delivering:
  128. HandleDelivering();
  129. break;
  130. case VillagerState.Transporting:
  131. HandleTransporting();
  132. break;
  133. case VillagerState.BuildingConstruction:
  134. HandleConstruction();
  135. break;
  136. case VillagerState.BuildingField:
  137. HandleFieldBuilding();
  138. break;
  139. }
  140. }
  141. void HandleIdleState()
  142. {
  143. if (currentJob == JobType.None)
  144. {
  145. // Unemployed villagers stay idle until assigned a job
  146. // They can be selected and assigned jobs via drag-and-drop
  147. return;
  148. }
  149. else
  150. {
  151. // Look for work appropriate to job
  152. FindWork();
  153. }
  154. }
  155. void HandleMovingToWork()
  156. {
  157. if (MoveToTarget(targetPosition))
  158. {
  159. state = VillagerState.Working;
  160. workTimer = 0f;
  161. }
  162. }
  163. void HandleWorking()
  164. {
  165. if (targetWorkplace == null)
  166. {
  167. state = VillagerState.Idle;
  168. return;
  169. }
  170. workTimer += Time.deltaTime * GetWorkEfficiency();
  171. float workDuration = GetWorkDuration();
  172. if (workTimer >= workDuration)
  173. {
  174. CompleteWork();
  175. }
  176. }
  177. void HandleMovingToDeliver()
  178. {
  179. if (MoveToTarget(targetPosition))
  180. {
  181. state = VillagerState.Delivering;
  182. }
  183. }
  184. void HandleDelivering()
  185. {
  186. if (carryingAmount > 0)
  187. {
  188. DeliverResources();
  189. }
  190. // Always transition back to idle after delivery attempt
  191. state = VillagerState.Idle;
  192. targetPosition = transform.position; // Clear target
  193. }
  194. void HandleTransporting()
  195. {
  196. // Similar to delivering but for transport tasks
  197. if (MoveToTarget(targetPosition))
  198. {
  199. DeliverResources();
  200. state = VillagerState.Idle;
  201. }
  202. }
  203. bool MoveToTarget(Vector3 target)
  204. {
  205. Vector3 direction = (target - transform.position).normalized;
  206. transform.position += direction * moveSpeed * Time.deltaTime;
  207. return Vector3.Distance(transform.position, target) < 0.5f;
  208. }
  209. void FindWork()
  210. {
  211. // Builders should look for construction sites, not resource nodes
  212. if (currentJob == JobType.Builder)
  213. {
  214. FindAvailableConstructionSite();
  215. return;
  216. }
  217. // If villager already has a specific workplace assigned, use it
  218. if (targetWorkplace != null)
  219. {
  220. targetPosition = targetWorkplace.transform.position;
  221. state = VillagerState.MovingToWork;
  222. return;
  223. }
  224. // Otherwise, find appropriate workplace based on job
  225. GameObject workplace = villagerManager.FindWorkplaceForJob(currentJob, transform.position);
  226. if (workplace != null)
  227. {
  228. targetWorkplace = workplace;
  229. targetPosition = workplace.transform.position;
  230. state = VillagerState.MovingToWork;
  231. }
  232. }
  233. void FindTransportTask()
  234. {
  235. // Look for resources that need to be moved
  236. // This is simplified - in a real game you'd have a more complex task system
  237. var townHall = GameObject.FindWithTag("TownHall");
  238. if (townHall != null && carryingAmount == 0)
  239. {
  240. // Simple transport: move to a random resource and bring it to town hall
  241. state = VillagerState.Transporting;
  242. }
  243. }
  244. void CompleteWork()
  245. {
  246. ResourceType producedResource = GetProducedResource();
  247. int amount = GetProducedAmount();
  248. carryingResource = producedResource;
  249. carryingAmount = amount;
  250. // Add experience
  251. experience.AddExperience(currentJob, 0.1f);
  252. // Find delivery target (usually TownHall)
  253. GameObject townHall = GameObject.FindWithTag("TownHall");
  254. if (townHall != null)
  255. {
  256. targetDelivery = townHall;
  257. targetPosition = townHall.transform.position;
  258. state = VillagerState.MovingToDeliver;
  259. }
  260. else
  261. {
  262. // Deliver immediately if no town hall found
  263. DeliverResources();
  264. state = VillagerState.Idle;
  265. }
  266. }
  267. void DeliverResources()
  268. {
  269. if (carryingAmount > 0)
  270. {
  271. Debug.Log($"{villagerName} delivering {carryingAmount} {carryingResource} to storage");
  272. GameManager.Instance.resourceManager.AddResource(carryingResource, carryingAmount);
  273. carryingAmount = 0;
  274. carryingResource = ResourceType.None;
  275. }
  276. }
  277. ResourceType GetProducedResource()
  278. {
  279. return currentJob switch
  280. {
  281. JobType.Woodcutter => ResourceType.Wood,
  282. JobType.Stonecutter => ResourceType.Stone,
  283. JobType.Farmer => ResourceType.Food,
  284. JobType.Builder => ResourceType.None, // Builders don't produce resources
  285. _ => ResourceType.None
  286. };
  287. }
  288. int GetProducedAmount()
  289. {
  290. float efficiency = GetWorkEfficiency();
  291. int baseAmount = UnityEngine.Random.Range(1, 4);
  292. int producedAmount = Mathf.RoundToInt(baseAmount * efficiency);
  293. // Cap maximum production per work cycle to prevent resource explosion
  294. return Mathf.Clamp(producedAmount, 1, 10);
  295. }
  296. float GetWorkEfficiency()
  297. {
  298. float baseEfficiency = experience.GetExperienceForJob(currentJob) * workSpeed;
  299. // Cap efficiency to prevent resource explosion (max 3x efficiency)
  300. return Mathf.Clamp(baseEfficiency, 0.5f, 3f);
  301. }
  302. float GetWorkDuration()
  303. {
  304. return 3f / GetWorkEfficiency(); // Base 3 seconds, modified by efficiency
  305. }
  306. public void SetJob(JobType newJob)
  307. {
  308. currentJob = newJob;
  309. state = VillagerState.Idle;
  310. // Clear previous workplace assignment when changing jobs
  311. if (currentJob == JobType.None)
  312. {
  313. targetWorkplace = null;
  314. }
  315. OnJobChanged?.Invoke(this, newJob);
  316. // Update visual appearance based on job
  317. UpdateAppearance();
  318. }
  319. public void AssignToSpecificWorkplace(GameObject workplace)
  320. {
  321. targetWorkplace = workplace;
  322. if (workplace != null)
  323. {
  324. targetPosition = workplace.transform.position;
  325. state = VillagerState.MovingToWork;
  326. Debug.Log($"{villagerName} assigned to specific workplace: {workplace.name}");
  327. }
  328. }
  329. void UpdateAppearance()
  330. {
  331. if (villagerRenderer != null)
  332. {
  333. Color jobColor = currentJob switch
  334. {
  335. JobType.Woodcutter => Color.green,
  336. JobType.Stonecutter => Color.gray,
  337. JobType.Farmer => Color.yellow,
  338. JobType.Builder => Color.blue,
  339. _ => Color.white
  340. };
  341. villagerRenderer.material.color = jobColor;
  342. }
  343. }
  344. public void SetSelected(bool selected)
  345. {
  346. isSelected = selected;
  347. if (selectionIndicator != null)
  348. selectionIndicator.SetActive(selected);
  349. Debug.Log($"Villager {villagerName} SetSelected: {selected}");
  350. if (selected)
  351. {
  352. Debug.Log($"Villager {villagerName} triggering OnVillagerSelected event");
  353. OnVillagerSelected?.Invoke(this);
  354. }
  355. }
  356. void OnMouseDown()
  357. {
  358. // Use Physics raycast instead of OnMouseDown for better reliability
  359. if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame)
  360. {
  361. if (GameManager.Instance?.selectionManager != null)
  362. {
  363. GameManager.Instance.selectionManager.SelectVillager(this);
  364. }
  365. }
  366. }
  367. public string GetStatusText()
  368. {
  369. string status = $"{villagerName}\nJob: {currentJob}\nState: {state}";
  370. if (carryingAmount > 0)
  371. status += $"\nCarrying: {carryingAmount} {carryingResource}";
  372. if (currentJob == JobType.None)
  373. status += "\n[Click and drag to assign job]";
  374. if (assignedConstructionSite != null)
  375. status += $"\nBuilding: {assignedConstructionSite.buildingName}";
  376. if (assignedField != null)
  377. status += $"\nField: {assignedField.fieldSize}";
  378. return status;
  379. }
  380. // Construction methods
  381. public void AssignToConstruction(ConstructionSite site)
  382. {
  383. assignedConstructionSite = site;
  384. site.AssignBuilder(this);
  385. state = VillagerState.BuildingConstruction;
  386. targetPosition = site.transform.position;
  387. Debug.Log($"{villagerName} assigned to build {site.buildingName}");
  388. }
  389. public void AssignToFieldConstruction(Field field)
  390. {
  391. assignedField = field;
  392. state = VillagerState.BuildingField;
  393. targetPosition = field.transform.position;
  394. Debug.Log($"{villagerName} assigned to build {field.fieldSize} field");
  395. }
  396. void FindAvailableConstructionSite()
  397. {
  398. if (assignedConstructionSite != null) return;
  399. // Find nearest construction site that needs a builder
  400. ConstructionSite[] sites = FindObjectsOfType<ConstructionSite>();
  401. ConstructionSite nearestSite = null;
  402. float nearestDistance = float.MaxValue;
  403. foreach (var site in sites)
  404. {
  405. if (site.assignedBuilder == null && !site.isComplete)
  406. {
  407. float distance = Vector3.Distance(transform.position, site.transform.position);
  408. if (distance < nearestDistance)
  409. {
  410. nearestDistance = distance;
  411. nearestSite = site;
  412. }
  413. }
  414. }
  415. if (nearestSite != null)
  416. {
  417. AssignToConstruction(nearestSite);
  418. Debug.Log($"{villagerName} found construction site: {nearestSite.buildingName}");
  419. }
  420. }
  421. public void SetHomeBuilding(GameObject building)
  422. {
  423. homeBuilding = building;
  424. Debug.Log($"{villagerName} home set to {(building != null ? building.name : "none")}");
  425. }
  426. void HandleConstruction()
  427. {
  428. if (assignedConstructionSite == null)
  429. {
  430. state = VillagerState.Idle;
  431. return;
  432. }
  433. // Move to construction site
  434. float distance = Vector3.Distance(transform.position, assignedConstructionSite.transform.position);
  435. if (distance > workRange)
  436. {
  437. transform.position = Vector3.MoveTowards(transform.position, assignedConstructionSite.transform.position, moveSpeed * Time.deltaTime);
  438. }
  439. else
  440. {
  441. // Work on construction
  442. assignedConstructionSite.AdvanceConstruction(Time.deltaTime * GetWorkEfficiency());
  443. // Add building experience
  444. experience.AddExperience(JobType.Builder, 0.1f * Time.deltaTime);
  445. if (assignedConstructionSite.isComplete)
  446. {
  447. Debug.Log($"{villagerName} completed construction of {assignedConstructionSite.buildingName}");
  448. assignedConstructionSite = null;
  449. state = VillagerState.Idle;
  450. }
  451. }
  452. }
  453. void HandleFieldBuilding()
  454. {
  455. if (assignedField == null)
  456. {
  457. state = VillagerState.Idle;
  458. return;
  459. }
  460. // Move to field location
  461. float distance = Vector3.Distance(transform.position, assignedField.transform.position);
  462. if (distance > workRange)
  463. {
  464. transform.position = Vector3.MoveTowards(transform.position, assignedField.transform.position, moveSpeed * Time.deltaTime);
  465. }
  466. else
  467. {
  468. // Work on field construction
  469. assignedField.AdvanceConstruction(Time.deltaTime * GetWorkEfficiency());
  470. // Add farming experience
  471. experience.AddExperience(JobType.Farmer, 0.1f * Time.deltaTime);
  472. if (!assignedField.isUnderConstruction)
  473. {
  474. Debug.Log($"{villagerName} completed field construction");
  475. assignedField = null;
  476. state = VillagerState.Idle;
  477. }
  478. }
  479. }
  480. }