using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; using TMPro; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; public class TaskTimer : MonoBehaviour { [Header("Timer Settings")] [Tooltip("Total time for the task in seconds")] public float taskDuration = 300f; // 5 minutes default [Tooltip("Start timer automatically on scene load")] public bool autoStart = true; [Tooltip("Show warning when time is low (in seconds)")] public float warningThreshold = 30f; [Header("UDP Control Settings")] [SerializeField] private int udpPort = 5555; [SerializeField] private bool enableUDPControl = true; [Tooltip("If enabled, timer will only start via UDP command (ignores autoStart)")] [SerializeField] private bool waitForUDPStart = false; [Header("UI References")] [Tooltip("TextMeshPro component to display timer (recommended for VR)")] public TextMeshProUGUI timerTextTMP; [Tooltip("Legacy UI Text component (if not using TextMeshPro)")] public Text timerTextLegacy; [Header("Visual Settings")] [Tooltip("Normal color for timer text")] public Color normalColor = Color.white; [Tooltip("Warning color when time is running low")] public Color warningColor = Color.red; [Tooltip("Format: 'MM:SS' or 'HH:MM:SS'")] public bool showHours = false; private float timeRemaining; private bool isRunning; // UDP infrastructure private UdpClient udpClient; private Thread udpListenerThread; private bool isListening = false; private string pendingCommand = null; private readonly object commandLock = new object(); private void Start() { timeRemaining = taskDuration; if (enableUDPControl) { StartUDPListener(); } // Only auto-start if not waiting for UDP command if (autoStart && !waitForUDPStart) { StartTimer(); } UpdateTimerDisplay(); } private void Update() { // Check for pending UDP commands (must be done on main thread) lock (commandLock) { if (!string.IsNullOrEmpty(pendingCommand)) { ProcessTimerCommand(pendingCommand); pendingCommand = null; } } if (!isRunning) return; timeRemaining -= Time.deltaTime; // Check if time is up if (timeRemaining <= 0) { timeRemaining = 0; OnTimerExpired(); } UpdateTimerDisplay(); } public void StartTimer() { isRunning = true; Debug.Log($"TaskTimer: Timer started for {taskDuration} seconds"); } public void PauseTimer() { isRunning = false; Debug.Log("TaskTimer: Timer paused"); } public void ResumeTimer() { isRunning = true; Debug.Log("TaskTimer: Timer resumed"); } public void ResetTimer() { timeRemaining = taskDuration; UpdateTimerDisplay(); Debug.Log("TaskTimer: Timer reset"); } public void AddTime(float seconds) { timeRemaining += seconds; Debug.Log($"TaskTimer: Added {seconds} seconds. New time: {timeRemaining}"); } private void UpdateTimerDisplay() { string timeText = FormatTime(timeRemaining); // Update TextMeshPro if (timerTextTMP != null) { timerTextTMP.text = timeText; timerTextTMP.color = timeRemaining <= warningThreshold ? warningColor : normalColor; } // Update Legacy Text if (timerTextLegacy != null) { timerTextLegacy.text = timeText; timerTextLegacy.color = timeRemaining <= warningThreshold ? warningColor : normalColor; } } private string FormatTime(float time) { int hours = Mathf.FloorToInt(time / 3600f); int minutes = Mathf.FloorToInt((time % 3600f) / 60f); int seconds = Mathf.FloorToInt(time % 60f); if (showHours) { return string.Format("{0:00}:{1:00}:{2:00}", hours, minutes, seconds); } else { int totalMinutes = Mathf.FloorToInt(time / 60f); return string.Format("{0:00}:{1:00}", totalMinutes, seconds); } } private void OnTimerExpired() { isRunning = false; Debug.Log("TaskTimer: Time expired! Returning to Lobby..."); // Load Lobby scene SceneManager.LoadScene("Lobby"); } // Public methods for external control public float GetTimeRemaining() { return timeRemaining; } public bool IsRunning() { return isRunning; } public float GetProgress() { return 1f - (timeRemaining / taskDuration); } // UDP Control Methods private void StartUDPListener() { try { isListening = true; udpClient = new UdpClient(udpPort); udpListenerThread = new Thread(new ThreadStart(ListenForUDPCommands)); udpListenerThread.IsBackground = true; udpListenerThread.Start(); Debug.Log($"TaskTimer: UDP Listener started on port {udpPort}"); } catch (System.Exception e) { Debug.LogError($"TaskTimer: Failed to start UDP listener: {e.Message}"); } } private void ListenForUDPCommands() { IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, udpPort); while (isListening) { try { byte[] data = udpClient.Receive(ref remoteEndPoint); string message = Encoding.UTF8.GetString(data); ProcessUDPCommand(message); } catch (System.Exception e) { if (isListening) { Debug.LogError($"TaskTimer: UDP receive error: {e.Message}"); } } } } private void ProcessUDPCommand(string message) { Debug.Log($"TaskTimer: Received UDP command: {message}"); if (string.IsNullOrEmpty(message)) return; // Parse command format: TIMER:COMMAND (e.g., TIMER:START, TIMER:PAUSE, TIMER:RESET, TIMER:RESUME) string[] parts = message.Split(':'); if (parts.Length == 2 && parts[0].Trim().ToUpper() == "TIMER") { string command = parts[1].Trim().ToUpper(); lock (commandLock) { pendingCommand = command; } Debug.Log($"TaskTimer: Scheduled command: {command}"); } else { Debug.LogWarning($"TaskTimer: Invalid UDP command format: {message}. Expected format: TIMER:COMMAND (e.g., TIMER:START)"); } } private void ProcessTimerCommand(string command) { switch (command) { case "START": StartTimer(); break; case "PAUSE": PauseTimer(); break; case "RESUME": ResumeTimer(); break; case "RESET": ResetTimer(); break; default: Debug.LogWarning($"TaskTimer: Unknown command: {command}. Valid commands: START, PAUSE, RESUME, RESET"); break; } } private void OnDestroy() { StopUDPListener(); } private void OnApplicationQuit() { StopUDPListener(); } private void StopUDPListener() { isListening = false; if (udpClient != null) { udpClient.Close(); udpClient = null; } if (udpListenerThread != null && udpListenerThread.IsAlive) { udpListenerThread.Abort(); udpListenerThread = null; } Debug.Log("TaskTimer: UDP Listener stopped"); } }