using Mirror; using OWML.Common; using QSB.ClientServerStateSync; using QSB.ClientServerStateSync.Messages; using QSB.DeathSync; using QSB.Inputs; using QSB.Messaging; using QSB.Player; using QSB.Player.Messages; using QSB.TimeSync.Messages; using QSB.TimeSync.Patches; using QSB.Utility; using QSB.WorldSync; using System; using UnityEngine; namespace QSB.TimeSync; public class WakeUpSync : MonoBehaviour, IAddComponentOnStart { public static WakeUpSync LocalInstance { get; private set; } private const float PauseOrFastForwardThreshold = 1.0f; private const float TimescaleBounds = 0.3f; private const float MaxFastForwardSpeed = 60f; private const float MaxFastForwardDiff = 20f; private const float MinFastForwardSpeed = 2f; public enum State { NotLoaded, Loaded, FastForwarding, Pausing } public State CurrentState { get; private set; } = State.NotLoaded; public Enum CurrentReason { get; private set; } private float _sendTimer; private float _serverTime; private int _serverLoopCount; public bool HasWokenUp; public void OnDisconnect() { OWTime.SetTimeScale(1f); OWTime.SetMaxDeltaTime(0.06666667f); Locator.GetActiveCamera().enabled = true; CurrentState = State.NotLoaded; CurrentReason = null; Physics.SyncTransforms(); SpinnerUI.Hide(); TimeSyncUI.Stop(); QSBInputManager.Instance.SetInputsEnabled(true); } public void Start() { if (QSBSceneManager.IsInUniverse) { Init(); } QSBSceneManager.OnSceneLoaded += OnSceneLoaded; GlobalMessenger.AddListener(OWEvents.WakeUp, OnWakeUp); } public float GetTimeDifference() { if (QSBCore.IsHost) { return 0f; } var myTime = Time.timeSinceLevelLoad; return myTime - _serverTime; } private void OnWakeUp() { DebugLog.DebugWrite($"OnWakeUp", MessageType.Info); if (QSBCore.IsHost) { new ServerStateMessage(ServerState.InSolarSystem).Send(); RespawnOnDeath.Instance.Init(); } HasWokenUp = true; } public void OnDestroy() { QSBSceneManager.OnSceneLoaded -= OnSceneLoaded; GlobalMessenger.RemoveListener(OWEvents.WakeUp, OnWakeUp); } private void OnSceneLoaded(OWScene oldScene, OWScene newScene, bool isInUniverse) { HasWokenUp = false; if (isInUniverse) { if (newScene == OWScene.EyeOfTheUniverse) { HasWokenUp = true; } LocalInstance = this; Init(); } else { LocalInstance = null; CurrentState = State.NotLoaded; } } private void Init() { new RequestStateResyncMessage().Send(); CurrentState = State.Loaded; if (QSBCore.IsHost) { _serverTime = Time.timeSinceLevelLoad; SendServerTime(); } else { if (!QSBCore.DebugSettings.AvoidTimeSync) { WakeUpOrSleep(); } else { // dont bother sleeping, just wake up if (!HasWokenUp) { Delay.RunWhen(() => QSBWorldSync.AllObjectsReady, WakeUp); } } } } private void SendServerTime() => new ServerTimeMessage(_serverTime, PlayerData.LoadLoopCount(), TimeLoop.GetSecondsRemaining()).Send(); public void OnClientReceiveMessage(float time, int count, float secondsRemaining) { _serverTime = time; _serverLoopCount = count; // prevents accidental supernova at start of loop if (_serverLoopCount == PlayerData.LoadLoopCount()) { TimeLoop.SetSecondsRemaining(secondsRemaining); } } private void WakeUpOrSleep() { if (CurrentState == State.NotLoaded) { return; } if (PlayerData.LoadLoopCount() != _serverLoopCount && !QSBCore.IsHost) { DebugLog.ToConsole($"Warning - ServerLoopCount is not the same as local loop count! local:{PlayerData.LoadLoopCount()} server:{_serverLoopCount}"); return; } var diff = GetTimeDifference(); if (ServerStateManager.Instance.GetServerState() is not (ServerState.InSolarSystem or ServerState.InEye)) { return; } if (diff > PauseOrFastForwardThreshold) { StartPausing(PauseReason.TooFarAhead); } else if (diff < -PauseOrFastForwardThreshold) { StartFastForwarding(FastForwardReason.TooFarBehind); } else { // should only happen from Init so we gotta wait if (!HasWokenUp) { Delay.RunWhen(() => QSBWorldSync.AllObjectsReady, WakeUp); } } } private void StartFastForwarding(FastForwardReason reason) { if (QSBCore.IsHost) { DebugLog.ToConsole($"Tried to fast-forward as server??? What???? _serverTime = {_serverTime}, GetTimeDifference() = {GetTimeDifference()}", MessageType.Error); return; } if (CurrentState == State.FastForwarding) { TimeSyncUI.TargetTime = _serverTime; return; } DebugLog.DebugWrite($"START FASTFORWARD (Target:{_serverTime} Current:{Time.timeSinceLevelLoad})", MessageType.Info); if (Locator.GetActiveCamera() != null) { Locator.GetActiveCamera().enabled = false; } //OWInput.ChangeInputMode(InputMode.None); QSBInputManager.Instance.SetInputsEnabled(false); CurrentState = State.FastForwarding; CurrentReason = reason; OWTime.SetMaxDeltaTime(0.033333335f); TimeSyncUI.TargetTime = _serverTime; TimeSyncUI.Start(TimeSyncType.FastForwarding, reason); } private void StartPausing(PauseReason reason) { if (CurrentState == State.Pausing) { TimeSyncUI.TargetTime = _serverTime; return; } DebugLog.DebugWrite($"START PAUSING (Target:{_serverTime} Current:{Time.timeSinceLevelLoad})", MessageType.Info); Locator.GetActiveCamera().enabled = false; //OWInput.ChangeInputMode(InputMode.None); QSBInputManager.Instance.SetInputsEnabled(false); OWTime.SetTimeScale(0f); CurrentState = State.Pausing; CurrentReason = reason; SpinnerUI.Show(); TimeSyncUI.TargetTime = _serverTime; TimeSyncUI.Start(TimeSyncType.Pausing, reason); } private void ResetTimeScale() { OWTime.SetTimeScale(1f); OWTime.SetMaxDeltaTime(0.06666667f); Locator.GetActiveCamera().enabled = true; CurrentState = State.Loaded; CurrentReason = null; DebugLog.DebugWrite($"RESET TIMESCALE", MessageType.Info); Physics.SyncTransforms(); SpinnerUI.Hide(); TimeSyncUI.Stop(); new RequestStateResyncMessage().Send(); RespawnOnDeath.Instance.Init(); QSBInputManager.Instance.SetInputsEnabled(true); if (!HasWokenUp) { WakeUp(); } } private void WakeUp() => Locator.GetPlayerCamera().GetComponent().WakeUp(); public void Update() { if (QSBCore.IsHost) { UpdateServer(); } else if (NetworkClient.active && QSBSceneManager.IsInUniverse && !QSBCore.DebugSettings.AvoidTimeSync) { UpdateClient(); } } private void UpdateServer() { _serverTime = Time.timeSinceLevelLoad; if (ServerStateManager.Instance == null) { return; } if (QSBPlayerManager.LocalPlayer == null) { return; } var serverState = ServerStateManager.Instance.GetServerState(); var clientState = QSBPlayerManager.LocalPlayer.State; if (serverState == ServerState.WaitingForAllPlayersToReady && clientState == ClientState.WaitingForOthersToBeReady) { if (CurrentState != State.Pausing) { StartPausing(PauseReason.WaitingForAllPlayersToBeReady); } } if (CurrentState == State.Pausing && (PauseReason)CurrentReason == PauseReason.WaitingForAllPlayersToBeReady) { if ((clientState == ClientState.AliveInSolarSystem && serverState == ServerState.InSolarSystem) || (clientState == ClientState.AliveInEye && serverState == ServerState.InEye)) { ResetTimeScale(); } } if (serverState == ServerState.WaitingForAllPlayersToDie && clientState == ClientState.WaitingForOthersToBeReady) { if (CurrentState == State.Pausing && (PauseReason)CurrentReason == PauseReason.WaitingForAllPlayersToBeReady) { //? DebugLog.ToConsole($"Warning - Server waiting for players to die, but players waiting for ready signal! Assume players correct.", MessageType.Warning); new ServerStateMessage(ServerState.WaitingForAllPlayersToReady).Send(); } } if (CurrentState != State.Loaded) { return; } _sendTimer += Time.unscaledDeltaTime; if (_sendTimer > 1) { SendServerTime(); _sendTimer = 0; } } private void UpdateClient() { _serverTime += Time.unscaledDeltaTime; var serverState = ServerStateManager.Instance.GetServerState(); var clientState = QSBPlayerManager.LocalPlayer.State; var currentScene = QSBSceneManager.CurrentScene; // set fastforwarding timescale if (CurrentState == State.FastForwarding && (FastForwardReason)CurrentReason == FastForwardReason.TooFarBehind) { if (Locator.GetPlayerCamera() != null && !Locator.GetPlayerCamera().enabled) { Locator.GetPlayerCamera().enabled = false; } var diff = _serverTime - Time.timeSinceLevelLoad; OWTime.SetTimeScale(Mathf.SmoothStep(MinFastForwardSpeed, MaxFastForwardSpeed, Mathf.Abs(diff) / MaxFastForwardDiff)); TimeSyncUI.TargetTime = _serverTime; } if (CurrentState == State.Pausing && (PauseReason)CurrentReason == PauseReason.TooFarAhead) { TimeSyncUI.TargetTime = _serverTime; } if (CurrentState != State.Loaded && CurrentState != State.NotLoaded && CurrentReason == null) { DebugLog.ToConsole($"Warning - CurrentReason is null.", MessageType.Warning); } // Checks to pause/fastforward if (clientState is ClientState.NotLoaded or ClientState.InTitleScreen) { return; } if (serverState == ServerState.NotLoaded && CurrentState != State.Pausing && QSBSceneManager.IsInUniverse) { StartPausing(PauseReason.ServerNotStarted); } if (serverState == ServerState.WaitingForAllPlayersToReady && CurrentState != State.Pausing && clientState == ClientState.WaitingForOthersToBeReady) { StartPausing(PauseReason.WaitingForAllPlayersToBeReady); } if (serverState == ServerState.WaitingForAllPlayersToDie && CurrentState != State.Pausing && clientState == ClientState.WaitingForOthersToBeReady) { StartPausing(PauseReason.WaitingForAllPlayersToBeReady); } // Checks to revert to normal if (CurrentState == State.Pausing && (PauseReason)CurrentReason == PauseReason.ServerNotStarted) { if (serverState != ServerState.NotLoaded) { ResetTimeScale(); } } if (CurrentState == State.Pausing && (PauseReason)CurrentReason == PauseReason.WaitingForAllPlayersToBeReady) { if ((clientState == ClientState.AliveInSolarSystem && serverState == ServerState.InSolarSystem) || (clientState == ClientState.AliveInEye && serverState == ServerState.InEye)) { ResetTimeScale(); } } if (CurrentState == State.Pausing && (PauseReason)CurrentReason == PauseReason.TooFarAhead) { if (Time.timeSinceLevelLoad <= _serverTime) { ResetTimeScale(); } } if (CurrentState == State.FastForwarding && (FastForwardReason)CurrentReason == FastForwardReason.TooFarBehind) { if (Time.timeSinceLevelLoad >= _serverTime) { ResetTimeScale(); } } if (CurrentState == State.Loaded) { CheckTimeDifference(); } } private void CheckTimeDifference() { var diff = GetTimeDifference(); if (diff is > PauseOrFastForwardThreshold or < -PauseOrFastForwardThreshold) { WakeUpOrSleep(); return; } var mappedTimescale = diff.Map(-PauseOrFastForwardThreshold, PauseOrFastForwardThreshold, 1 + TimescaleBounds, 1 - TimescaleBounds, true); if (mappedTimescale > 100f) { DebugLog.ToConsole($"Warning - CheckTimeDifference() returned over 100 - should have switched into fast-forward!", MessageType.Warning); mappedTimescale = 0f; } if (mappedTimescale < 0) { DebugLog.ToConsole($"Warning - CheckTimeDifference() returned below 0 - should have switched into pausing!", MessageType.Warning); mappedTimescale = 0f; } OWTime.SetTimeScale(mappedTimescale); } }