Staff.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. using UnityEngine;
  2. using UnityEngine.AI;
  3. using System.Collections.Generic;
  4. using System;
  5. [RequireComponent(typeof(NavMeshAgent))]
  6. public class Staff : MonoBehaviour
  7. {
  8. [Header("Staff Info")]
  9. [SerializeField] private string staffName;
  10. [SerializeField] private StaffType staffType = StaffType.Receptionist;
  11. [SerializeField] private float efficiency = 0.7f; // 0 to 1
  12. [SerializeField] private float workSpeed = 1f;
  13. [Header("Work Settings")]
  14. [SerializeField] private Transform workStation;
  15. [SerializeField] private float workRadius = 5f;
  16. [SerializeField] private LayerMask workLayerMask = -1;
  17. [Header("Movement")]
  18. [SerializeField] private float walkSpeed = 3f;
  19. [SerializeField] private float runSpeed = 5f;
  20. [Header("Visual")]
  21. [SerializeField] private GameObject workIndicator;
  22. [SerializeField] private SpriteRenderer statusIcon;
  23. private NavMeshAgent agent;
  24. private StaffState currentState = StaffState.Idle;
  25. private List<StaffTask> taskQueue = new List<StaffTask>();
  26. private StaffTask currentTask;
  27. private Vector3 homePosition;
  28. private float workTimer = 0f;
  29. private float shiftStartTime;
  30. private float shiftDuration = 8f * 60f; // 8 hours in game minutes
  31. // Events
  32. public static event Action<Staff> OnStaffHired;
  33. public static event Action<Staff> OnStaffCompleteTask;
  34. public static event Action<Staff, StaffTask> OnTaskAssigned;
  35. public enum StaffState
  36. {
  37. Idle,
  38. MovingToTask,
  39. Working,
  40. OnBreak,
  41. OffDuty
  42. }
  43. public enum StaffType
  44. {
  45. Receptionist,
  46. Housekeeper,
  47. Chef,
  48. Janitor,
  49. Bellhop,
  50. Valet,
  51. Security,
  52. Manager
  53. }
  54. #region Properties
  55. public string StaffName
  56. {
  57. get => string.IsNullOrEmpty(staffName) ? GenerateRandomName() : staffName;
  58. set => staffName = value;
  59. }
  60. public StaffState CurrentState => currentState;
  61. public StaffType Type => staffType;
  62. public float Efficiency => efficiency;
  63. public Transform WorkStation => workStation;
  64. public int TaskCount => taskQueue.Count;
  65. public bool IsAvailable => currentState == StaffState.Idle && taskQueue.Count == 0;
  66. #endregion
  67. private void Awake()
  68. {
  69. agent = GetComponent<NavMeshAgent>();
  70. agent.speed = walkSpeed;
  71. SetupWorkIndicator();
  72. homePosition = transform.position;
  73. shiftStartTime = Time.time;
  74. }
  75. private void Start()
  76. {
  77. SetState(StaffState.Idle);
  78. OnStaffHired?.Invoke(this);
  79. // Find or create work station based on staff type
  80. SetupWorkStation();
  81. }
  82. private void Update()
  83. {
  84. UpdateWorkShift();
  85. UpdateTaskExecution();
  86. UpdateMovement();
  87. UpdateVisuals();
  88. }
  89. #region State Management
  90. private void SetState(StaffState newState)
  91. {
  92. currentState = newState;
  93. Debug.Log($"Staff {StaffName} ({staffType}) state changed to: {newState}");
  94. switch (newState)
  95. {
  96. case StaffState.Idle:
  97. agent.speed = walkSpeed;
  98. if (workStation != null)
  99. MoveToPosition(workStation.position);
  100. break;
  101. case StaffState.MovingToTask:
  102. agent.speed = currentTask?.isUrgent == true ? runSpeed : walkSpeed;
  103. break;
  104. case StaffState.Working:
  105. agent.speed = 0f;
  106. StartWork();
  107. break;
  108. case StaffState.OnBreak:
  109. MoveToPosition(homePosition);
  110. break;
  111. }
  112. }
  113. private void UpdateWorkShift()
  114. {
  115. float timeWorked = Time.time - shiftStartTime;
  116. if (timeWorked > shiftDuration)
  117. {
  118. if (currentState != StaffState.OffDuty)
  119. {
  120. SetState(StaffState.OffDuty);
  121. ClearAllTasks();
  122. }
  123. }
  124. else if (timeWorked % (2f * 60f) < Time.deltaTime) // Break every 2 game hours
  125. {
  126. if (currentState == StaffState.Idle && UnityEngine.Random.value < 0.3f)
  127. {
  128. TakeBreak();
  129. }
  130. }
  131. }
  132. private void TakeBreak()
  133. {
  134. SetState(StaffState.OnBreak);
  135. // Return to work after break
  136. Invoke(nameof(ReturnFromBreak), 30f); // 30 second break
  137. }
  138. private void ReturnFromBreak()
  139. {
  140. if (currentState == StaffState.OnBreak)
  141. {
  142. SetState(StaffState.Idle);
  143. }
  144. }
  145. #endregion
  146. #region Task Management
  147. public void AssignTask(StaffTask task)
  148. {
  149. if (task == null) return;
  150. taskQueue.Add(task);
  151. OnTaskAssigned?.Invoke(this, task);
  152. Debug.Log($"Task assigned to {StaffName}: {task.taskType}");
  153. // If idle, start working immediately
  154. if (currentState == StaffState.Idle)
  155. {
  156. ProcessNextTask();
  157. }
  158. }
  159. private void ProcessNextTask()
  160. {
  161. if (taskQueue.Count == 0)
  162. {
  163. SetState(StaffState.Idle);
  164. return;
  165. }
  166. // Get highest priority task
  167. taskQueue.Sort((a, b) => b.priority.CompareTo(a.priority));
  168. currentTask = taskQueue[0];
  169. taskQueue.RemoveAt(0);
  170. // Move to task location
  171. if (currentTask.location != Vector3.zero)
  172. {
  173. SetState(StaffState.MovingToTask);
  174. MoveToPosition(currentTask.location);
  175. }
  176. else
  177. {
  178. SetState(StaffState.Working);
  179. }
  180. }
  181. private void UpdateTaskExecution()
  182. {
  183. if (currentTask == null) return;
  184. switch (currentState)
  185. {
  186. case StaffState.MovingToTask:
  187. if (HasReachedDestination())
  188. {
  189. SetState(StaffState.Working);
  190. }
  191. break;
  192. case StaffState.Working:
  193. workTimer += Time.deltaTime * efficiency * workSpeed;
  194. if (workTimer >= currentTask.duration)
  195. {
  196. CompleteCurrentTask();
  197. }
  198. break;
  199. }
  200. }
  201. private void StartWork()
  202. {
  203. workTimer = 0f;
  204. if (workIndicator != null)
  205. workIndicator.SetActive(true);
  206. Debug.Log($"{StaffName} started working on: {currentTask.taskType}");
  207. }
  208. private void CompleteCurrentTask()
  209. {
  210. if (currentTask == null) return;
  211. Debug.Log($"{StaffName} completed task: {currentTask.taskType}");
  212. // Execute task effects
  213. ExecuteTaskEffects(currentTask);
  214. OnStaffCompleteTask?.Invoke(this);
  215. if (workIndicator != null)
  216. workIndicator.SetActive(false);
  217. currentTask = null;
  218. workTimer = 0f;
  219. // Process next task
  220. ProcessNextTask();
  221. }
  222. private void ExecuteTaskEffects(StaffTask task)
  223. {
  224. switch (task.taskType)
  225. {
  226. case TaskType.CheckInGuest:
  227. // Handle guest check-in
  228. if (task.targetGuest != null)
  229. {
  230. task.targetGuest.GetComponent<Guest>()?.AssignRoom(HotelManager.Instance?.GetAvailableRoom(task.targetGuest.GetComponent<Guest>()));
  231. }
  232. break;
  233. case TaskType.CleanRoom:
  234. // Mark room as clean
  235. if (task.targetRoom != null)
  236. {
  237. Debug.Log($"Room cleaned by {StaffName}");
  238. }
  239. break;
  240. case TaskType.DeliverService:
  241. // Deliver room service
  242. Debug.Log($"Service delivered by {StaffName}");
  243. break;
  244. }
  245. }
  246. public void ClearAllTasks()
  247. {
  248. taskQueue.Clear();
  249. currentTask = null;
  250. workTimer = 0f;
  251. if (workIndicator != null)
  252. workIndicator.SetActive(false);
  253. }
  254. public bool CanHandleTask(TaskType taskType)
  255. {
  256. switch (staffType)
  257. {
  258. case StaffType.Receptionist:
  259. return taskType == TaskType.CheckInGuest || taskType == TaskType.AnswerPhone;
  260. case StaffType.Housekeeper:
  261. return taskType == TaskType.CleanRoom || taskType == TaskType.LaundryService;
  262. case StaffType.Chef:
  263. return taskType == TaskType.PrepareFood || taskType == TaskType.DeliverService;
  264. case StaffType.Janitor:
  265. return taskType == TaskType.CleanArea || taskType == TaskType.Maintenance;
  266. case StaffType.Bellhop:
  267. return taskType == TaskType.CarryLuggage || taskType == TaskType.DeliverService;
  268. case StaffType.Valet:
  269. return taskType == TaskType.ParkCar || taskType == TaskType.CarryLuggage;
  270. default:
  271. return false;
  272. }
  273. }
  274. #endregion
  275. #region Movement
  276. private bool MoveToPosition(Vector3 position)
  277. {
  278. if (!agent.enabled || !agent.isOnNavMesh) return false;
  279. try
  280. {
  281. agent.SetDestination(position);
  282. return true;
  283. }
  284. catch (System.Exception e)
  285. {
  286. Debug.LogWarning($"Failed to set destination for {StaffName}: {e.Message}");
  287. return false;
  288. }
  289. }
  290. private bool HasReachedDestination()
  291. {
  292. return agent.enabled && agent.isOnNavMesh && !agent.pathPending && agent.remainingDistance < 0.5f;
  293. }
  294. private void UpdateMovement()
  295. {
  296. // Update animation or visual effects based on movement
  297. if (agent.enabled && agent.velocity.magnitude > 0.1f)
  298. {
  299. // Staff is moving
  300. }
  301. else
  302. {
  303. // Staff is stationary
  304. }
  305. }
  306. #endregion
  307. #region Work Station Setup
  308. private void SetupWorkStation()
  309. {
  310. if (workStation == null)
  311. {
  312. workStation = FindWorkStation();
  313. }
  314. if (workStation == null)
  315. {
  316. CreateDefaultWorkStation();
  317. }
  318. }
  319. private Transform FindWorkStation()
  320. {
  321. GameObject[] stations = null;
  322. try
  323. {
  324. switch (staffType)
  325. {
  326. case StaffType.Receptionist:
  327. stations = GameObject.FindGameObjectsWithTag("Reception");
  328. break;
  329. case StaffType.Chef:
  330. stations = GameObject.FindGameObjectsWithTag("Kitchen");
  331. break;
  332. case StaffType.Housekeeper:
  333. case StaffType.Janitor:
  334. // These staff types move around, no fixed station
  335. return transform; // Use current position
  336. }
  337. }
  338. catch (UnityException)
  339. {
  340. Debug.LogWarning($"Tag for {staffType} not defined. Staff will create default work station.");
  341. return null;
  342. }
  343. if (stations != null && stations.Length > 0)
  344. {
  345. return stations[0].transform;
  346. }
  347. return null;
  348. }
  349. private void CreateDefaultWorkStation()
  350. {
  351. GameObject station = new GameObject($"{StaffName} Work Station");
  352. station.transform.position = transform.position;
  353. workStation = station.transform;
  354. // Add visual indicator
  355. GameObject indicator = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
  356. indicator.transform.SetParent(station.transform);
  357. indicator.transform.localPosition = Vector3.zero;
  358. indicator.transform.localScale = new Vector3(0.5f, 0.1f, 0.5f);
  359. indicator.GetComponent<Renderer>().material.color = GetStaffTypeColor();
  360. indicator.name = $"{staffType} Station";
  361. }
  362. private Color GetStaffTypeColor()
  363. {
  364. switch (staffType)
  365. {
  366. case StaffType.Receptionist: return Color.blue;
  367. case StaffType.Housekeeper: return Color.green;
  368. case StaffType.Chef: return Color.red;
  369. case StaffType.Janitor: return Color.yellow;
  370. case StaffType.Bellhop: return Color.cyan;
  371. case StaffType.Valet: return Color.magenta;
  372. default: return Color.gray;
  373. }
  374. }
  375. #endregion
  376. #region Visual Setup
  377. private void SetupWorkIndicator()
  378. {
  379. if (workIndicator == null)
  380. {
  381. workIndicator = new GameObject("Work Indicator");
  382. workIndicator.transform.SetParent(transform);
  383. workIndicator.transform.localPosition = Vector3.up * 2f;
  384. // Create a simple spinning cube as work indicator
  385. GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
  386. cube.transform.SetParent(workIndicator.transform);
  387. cube.transform.localScale = Vector3.one * 0.3f;
  388. cube.GetComponent<Renderer>().material.color = Color.yellow;
  389. // Add spinning animation
  390. cube.AddComponent<SpinIndicator>();
  391. workIndicator.SetActive(false);
  392. }
  393. }
  394. private void UpdateVisuals()
  395. {
  396. // Update staff appearance based on state
  397. Renderer renderer = GetComponent<Renderer>();
  398. if (renderer != null)
  399. {
  400. Color baseColor = GetStaffTypeColor();
  401. switch (currentState)
  402. {
  403. case StaffState.Working:
  404. renderer.material.color = Color.Lerp(baseColor, Color.white, 0.3f);
  405. break;
  406. case StaffState.OnBreak:
  407. renderer.material.color = Color.Lerp(baseColor, Color.gray, 0.5f);
  408. break;
  409. case StaffState.OffDuty:
  410. renderer.material.color = Color.Lerp(baseColor, Color.black, 0.5f);
  411. break;
  412. default:
  413. renderer.material.color = baseColor;
  414. break;
  415. }
  416. }
  417. }
  418. #endregion
  419. #region Utility Methods
  420. private string GenerateRandomName()
  421. {
  422. string[] firstNames = { "Alice", "Bob", "Carol", "Dave", "Emma", "Frank", "Grace", "Henry", "Iris", "Jack" };
  423. string[] lastNames = { "Anderson", "Brown", "Clark", "Davis", "Evans", "Foster", "Green", "Harris", "Johnson", "King" };
  424. return $"{firstNames[UnityEngine.Random.Range(0, firstNames.Length)]} {lastNames[UnityEngine.Random.Range(0, lastNames.Length)]}";
  425. }
  426. private void OnMouseDown()
  427. {
  428. // Show staff info when clicked
  429. Debug.Log($"Staff: {StaffName} ({staffType})\nState: {currentState}\nTasks: {taskQueue.Count}\nEfficiency: {efficiency:P0}");
  430. }
  431. private void OnDrawGizmosSelected()
  432. {
  433. // Draw work radius
  434. Gizmos.color = Color.blue;
  435. Gizmos.DrawWireSphere(transform.position, workRadius);
  436. // Draw line to work station
  437. if (workStation != null)
  438. {
  439. Gizmos.color = Color.green;
  440. Gizmos.DrawLine(transform.position, workStation.position);
  441. }
  442. }
  443. #endregion
  444. }
  445. // Supporting classes
  446. [System.Serializable]
  447. public class StaffTask
  448. {
  449. public TaskType taskType;
  450. public Vector3 location;
  451. public float duration = 10f; // seconds
  452. public int priority = 1; // 1-10, higher is more urgent
  453. public bool isUrgent = false;
  454. public GameObject targetGuest;
  455. public Room targetRoom;
  456. public string description;
  457. }
  458. public enum TaskType
  459. {
  460. CheckInGuest,
  461. CleanRoom,
  462. DeliverService,
  463. PrepareFood,
  464. AnswerPhone,
  465. CarryLuggage,
  466. ParkCar,
  467. CleanArea,
  468. Maintenance,
  469. LaundryService
  470. }
  471. // Helper component for work indicator animation
  472. public class SpinIndicator : MonoBehaviour
  473. {
  474. [SerializeField] private float spinSpeed = 90f;
  475. private void Update()
  476. {
  477. transform.Rotate(Vector3.up, spinSpeed * Time.deltaTime);
  478. }
  479. }