using UnityEngine; using UnityEngine.AI; using System.Collections.Generic; using System; [RequireComponent(typeof(NavMeshAgent))] public class Staff : MonoBehaviour { [Header("Staff Info")] [SerializeField] private string staffName; [SerializeField] private StaffType staffType = StaffType.Receptionist; [SerializeField] private float efficiency = 0.7f; // 0 to 1 [SerializeField] private float workSpeed = 1f; [Header("Work Settings")] [SerializeField] private Transform workStation; [SerializeField] private float workRadius = 5f; [SerializeField] private LayerMask workLayerMask = -1; [Header("Movement")] [SerializeField] private float walkSpeed = 3f; [SerializeField] private float runSpeed = 5f; [Header("Visual")] [SerializeField] private GameObject workIndicator; [SerializeField] private SpriteRenderer statusIcon; private NavMeshAgent agent; private StaffState currentState = StaffState.Idle; private List taskQueue = new List(); private StaffTask currentTask; private Vector3 homePosition; private float workTimer = 0f; private float shiftStartTime; private float shiftDuration = 8f * 60f; // 8 hours in game minutes // Events public static event Action OnStaffHired; public static event Action OnStaffCompleteTask; public static event Action OnTaskAssigned; public enum StaffState { Idle, MovingToTask, Working, OnBreak, OffDuty } public enum StaffType { Receptionist, Housekeeper, Chef, Janitor, Bellhop, Valet, Security, Manager } #region Properties public string StaffName { get => string.IsNullOrEmpty(staffName) ? GenerateRandomName() : staffName; set => staffName = value; } public StaffState CurrentState => currentState; public StaffType Type => staffType; public float Efficiency => efficiency; public Transform WorkStation => workStation; public int TaskCount => taskQueue.Count; public bool IsAvailable => currentState == StaffState.Idle && taskQueue.Count == 0; #endregion private void Awake() { agent = GetComponent(); agent.speed = walkSpeed; SetupWorkIndicator(); homePosition = transform.position; shiftStartTime = Time.time; } private void Start() { SetState(StaffState.Idle); OnStaffHired?.Invoke(this); // Find or create work station based on staff type SetupWorkStation(); } private void Update() { UpdateWorkShift(); UpdateTaskExecution(); UpdateMovement(); UpdateVisuals(); } #region State Management private void SetState(StaffState newState) { currentState = newState; Debug.Log($"Staff {StaffName} ({staffType}) state changed to: {newState}"); switch (newState) { case StaffState.Idle: agent.speed = walkSpeed; if (workStation != null) MoveToPosition(workStation.position); break; case StaffState.MovingToTask: agent.speed = currentTask?.isUrgent == true ? runSpeed : walkSpeed; break; case StaffState.Working: agent.speed = 0f; StartWork(); break; case StaffState.OnBreak: MoveToPosition(homePosition); break; } } private void UpdateWorkShift() { float timeWorked = Time.time - shiftStartTime; if (timeWorked > shiftDuration) { if (currentState != StaffState.OffDuty) { SetState(StaffState.OffDuty); ClearAllTasks(); } } else if (timeWorked % (2f * 60f) < Time.deltaTime) // Break every 2 game hours { if (currentState == StaffState.Idle && UnityEngine.Random.value < 0.3f) { TakeBreak(); } } } private void TakeBreak() { SetState(StaffState.OnBreak); // Return to work after break Invoke(nameof(ReturnFromBreak), 30f); // 30 second break } private void ReturnFromBreak() { if (currentState == StaffState.OnBreak) { SetState(StaffState.Idle); } } #endregion #region Task Management public void AssignTask(StaffTask task) { if (task == null) return; taskQueue.Add(task); OnTaskAssigned?.Invoke(this, task); Debug.Log($"Task assigned to {StaffName}: {task.taskType}"); // If idle, start working immediately if (currentState == StaffState.Idle) { ProcessNextTask(); } } private void ProcessNextTask() { if (taskQueue.Count == 0) { SetState(StaffState.Idle); return; } // Get highest priority task taskQueue.Sort((a, b) => b.priority.CompareTo(a.priority)); currentTask = taskQueue[0]; taskQueue.RemoveAt(0); // Move to task location if (currentTask.location != Vector3.zero) { SetState(StaffState.MovingToTask); MoveToPosition(currentTask.location); } else { SetState(StaffState.Working); } } private void UpdateTaskExecution() { if (currentTask == null) return; switch (currentState) { case StaffState.MovingToTask: if (HasReachedDestination()) { SetState(StaffState.Working); } break; case StaffState.Working: workTimer += Time.deltaTime * efficiency * workSpeed; if (workTimer >= currentTask.duration) { CompleteCurrentTask(); } break; } } private void StartWork() { workTimer = 0f; if (workIndicator != null) workIndicator.SetActive(true); Debug.Log($"{StaffName} started working on: {currentTask.taskType}"); } private void CompleteCurrentTask() { if (currentTask == null) return; Debug.Log($"{StaffName} completed task: {currentTask.taskType}"); // Execute task effects ExecuteTaskEffects(currentTask); OnStaffCompleteTask?.Invoke(this); if (workIndicator != null) workIndicator.SetActive(false); currentTask = null; workTimer = 0f; // Process next task ProcessNextTask(); } private void ExecuteTaskEffects(StaffTask task) { switch (task.taskType) { case TaskType.CheckInGuest: // Handle guest check-in if (task.targetGuest != null) { task.targetGuest.GetComponent()?.AssignRoom(HotelManager.Instance?.GetAvailableRoom(task.targetGuest.GetComponent())); } break; case TaskType.CleanRoom: // Mark room as clean if (task.targetRoom != null) { Debug.Log($"Room cleaned by {StaffName}"); } break; case TaskType.DeliverService: // Deliver room service Debug.Log($"Service delivered by {StaffName}"); break; } } public void ClearAllTasks() { taskQueue.Clear(); currentTask = null; workTimer = 0f; if (workIndicator != null) workIndicator.SetActive(false); } public bool CanHandleTask(TaskType taskType) { switch (staffType) { case StaffType.Receptionist: return taskType == TaskType.CheckInGuest || taskType == TaskType.AnswerPhone; case StaffType.Housekeeper: return taskType == TaskType.CleanRoom || taskType == TaskType.LaundryService; case StaffType.Chef: return taskType == TaskType.PrepareFood || taskType == TaskType.DeliverService; case StaffType.Janitor: return taskType == TaskType.CleanArea || taskType == TaskType.Maintenance; case StaffType.Bellhop: return taskType == TaskType.CarryLuggage || taskType == TaskType.DeliverService; case StaffType.Valet: return taskType == TaskType.ParkCar || taskType == TaskType.CarryLuggage; default: return false; } } #endregion #region Movement private bool MoveToPosition(Vector3 position) { if (!agent.enabled || !agent.isOnNavMesh) return false; try { agent.SetDestination(position); return true; } catch (System.Exception e) { Debug.LogWarning($"Failed to set destination for {StaffName}: {e.Message}"); return false; } } private bool HasReachedDestination() { return agent.enabled && agent.isOnNavMesh && !agent.pathPending && agent.remainingDistance < 0.5f; } private void UpdateMovement() { // Update animation or visual effects based on movement if (agent.enabled && agent.velocity.magnitude > 0.1f) { // Staff is moving } else { // Staff is stationary } } #endregion #region Work Station Setup private void SetupWorkStation() { if (workStation == null) { workStation = FindWorkStation(); } if (workStation == null) { CreateDefaultWorkStation(); } } private Transform FindWorkStation() { GameObject[] stations = null; try { switch (staffType) { case StaffType.Receptionist: stations = GameObject.FindGameObjectsWithTag("Reception"); break; case StaffType.Chef: stations = GameObject.FindGameObjectsWithTag("Kitchen"); break; case StaffType.Housekeeper: case StaffType.Janitor: // These staff types move around, no fixed station return transform; // Use current position } } catch (UnityException) { Debug.LogWarning($"Tag for {staffType} not defined. Staff will create default work station."); return null; } if (stations != null && stations.Length > 0) { return stations[0].transform; } return null; } private void CreateDefaultWorkStation() { GameObject station = new GameObject($"{StaffName} Work Station"); station.transform.position = transform.position; workStation = station.transform; // Add visual indicator GameObject indicator = GameObject.CreatePrimitive(PrimitiveType.Cylinder); indicator.transform.SetParent(station.transform); indicator.transform.localPosition = Vector3.zero; indicator.transform.localScale = new Vector3(0.5f, 0.1f, 0.5f); indicator.GetComponent().material.color = GetStaffTypeColor(); indicator.name = $"{staffType} Station"; } private Color GetStaffTypeColor() { switch (staffType) { case StaffType.Receptionist: return Color.blue; case StaffType.Housekeeper: return Color.green; case StaffType.Chef: return Color.red; case StaffType.Janitor: return Color.yellow; case StaffType.Bellhop: return Color.cyan; case StaffType.Valet: return Color.magenta; default: return Color.gray; } } #endregion #region Visual Setup private void SetupWorkIndicator() { if (workIndicator == null) { workIndicator = new GameObject("Work Indicator"); workIndicator.transform.SetParent(transform); workIndicator.transform.localPosition = Vector3.up * 2f; // Create a simple spinning cube as work indicator GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.transform.SetParent(workIndicator.transform); cube.transform.localScale = Vector3.one * 0.3f; cube.GetComponent().material.color = Color.yellow; // Add spinning animation cube.AddComponent(); workIndicator.SetActive(false); } } private void UpdateVisuals() { // Update staff appearance based on state Renderer renderer = GetComponent(); if (renderer != null) { Color baseColor = GetStaffTypeColor(); switch (currentState) { case StaffState.Working: renderer.material.color = Color.Lerp(baseColor, Color.white, 0.3f); break; case StaffState.OnBreak: renderer.material.color = Color.Lerp(baseColor, Color.gray, 0.5f); break; case StaffState.OffDuty: renderer.material.color = Color.Lerp(baseColor, Color.black, 0.5f); break; default: renderer.material.color = baseColor; break; } } } #endregion #region Utility Methods private string GenerateRandomName() { string[] firstNames = { "Alice", "Bob", "Carol", "Dave", "Emma", "Frank", "Grace", "Henry", "Iris", "Jack" }; string[] lastNames = { "Anderson", "Brown", "Clark", "Davis", "Evans", "Foster", "Green", "Harris", "Johnson", "King" }; return $"{firstNames[UnityEngine.Random.Range(0, firstNames.Length)]} {lastNames[UnityEngine.Random.Range(0, lastNames.Length)]}"; } private void OnMouseDown() { // Show staff info when clicked Debug.Log($"Staff: {StaffName} ({staffType})\nState: {currentState}\nTasks: {taskQueue.Count}\nEfficiency: {efficiency:P0}"); } private void OnDrawGizmosSelected() { // Draw work radius Gizmos.color = Color.blue; Gizmos.DrawWireSphere(transform.position, workRadius); // Draw line to work station if (workStation != null) { Gizmos.color = Color.green; Gizmos.DrawLine(transform.position, workStation.position); } } #endregion } // Supporting classes [System.Serializable] public class StaffTask { public TaskType taskType; public Vector3 location; public float duration = 10f; // seconds public int priority = 1; // 1-10, higher is more urgent public bool isUrgent = false; public GameObject targetGuest; public Room targetRoom; public string description; } public enum TaskType { CheckInGuest, CleanRoom, DeliverService, PrepareFood, AnswerPhone, CarryLuggage, ParkCar, CleanArea, Maintenance, LaundryService } // Helper component for work indicator animation public class SpinIndicator : MonoBehaviour { [SerializeField] private float spinSpeed = 90f; private void Update() { transform.Rotate(Vector3.up, spinSpeed * Time.deltaTime); } }