using System; using Unity.Netcode; using UnityEngine; public enum RoundPhase : byte { WaitingForPlayers, DayInProgress, WaitingForPlayersToReturn } [RequireComponent(typeof(NetworkObject))] public sealed class RoundGameLoopController : NetworkBehaviour { [Header("Day")] [SerializeField, Min(1f)] private float dayDurationSeconds = 300f; [SerializeField, Min(0.05f)] private float timerSyncInterval = 0.1f; private readonly NetworkVariable phase = new( RoundPhase.WaitingForPlayers, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); private readonly NetworkVariable dayNumber = new( 0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); private readonly NetworkVariable remainingDaySeconds = new( 0f, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); private StartRoomZone startRoom; private float dayEndTime; private float nextTimerSyncTime; public RoundPhase Phase => phase.Value; public int DayNumber => dayNumber.Value; public float RemainingDaySeconds => remainingDaySeconds.Value; public event Action PhaseChanged; public event Action DayStarted; public event Action DayTimerChanged; public override void OnNetworkSpawn() { phase.OnValueChanged += HandlePhaseChanged; dayNumber.OnValueChanged += HandleDayNumberChanged; remainingDaySeconds.OnValueChanged += HandleTimerChanged; startRoom = FindFirstObjectByType(); if (IsServer) { phase.Value = RoundPhase.WaitingForPlayers; remainingDaySeconds.Value = 0f; } } public override void OnNetworkDespawn() { phase.OnValueChanged -= HandlePhaseChanged; dayNumber.OnValueChanged -= HandleDayNumberChanged; remainingDaySeconds.OnValueChanged -= HandleTimerChanged; } private void Update() { if (!IsServer) return; if (startRoom == null) startRoom = FindFirstObjectByType(); if (startRoom == null || Time.time < nextTimerSyncTime) return; nextTimerSyncTime = Time.time + timerSyncInterval; switch (phase.Value) { case RoundPhase.WaitingForPlayers: case RoundPhase.WaitingForPlayersToReturn: if (AreAllPlayersInStartRoom()) StartNextDay(); break; case RoundPhase.DayInProgress: float secondsLeft = Mathf.Max(0f, dayEndTime - Time.time); remainingDaySeconds.Value = secondsLeft; if (secondsLeft <= 0f) { phase.Value = RoundPhase.WaitingForPlayersToReturn; remainingDaySeconds.Value = 0f; } break; } } private bool AreAllPlayersInStartRoom() { if (NetworkManager == null || NetworkManager.ConnectedClients.Count == 0) return false; foreach (NetworkClient client in NetworkManager.ConnectedClients.Values) { if (client.PlayerObject == null || !startRoom.Contains(client.PlayerObject.transform.position)) return false; } return true; } private void StartNextDay() { dayNumber.Value++; dayEndTime = Time.time + dayDurationSeconds; remainingDaySeconds.Value = dayDurationSeconds; phase.Value = RoundPhase.DayInProgress; } private void HandlePhaseChanged(RoundPhase _, RoundPhase current) => PhaseChanged?.Invoke(current); private void HandleDayNumberChanged(int _, int current) => DayStarted?.Invoke(current); private void HandleTimerChanged(float _, float current) => DayTimerChanged?.Invoke(current); }