using OWML.Common; using QSB.DeathSync; using QSB.Events; using QSB.TimeSync.Events; using QSB.Utility; using QuantumUNET; using UnityEngine; namespace QSB.TimeSync { public class WakeUpSync : QNetworkBehaviour { public static WakeUpSync LocalInstance { get; private set; } private const float TimeThreshold = 0.5f; private const float MaxFastForwardSpeed = 60f; private const float MaxFastForwardDiff = 20f; private const float MinFastForwardSpeed = 2f; private enum State { NotLoaded, Loaded, FastForwarding, Pausing } private State _state = State.NotLoaded; private float _sendTimer; private float _serverTime; private float _timeScale; private bool _isInputEnabled = true; private bool _isFirstFastForward = true; private int _localLoopCount; private int _serverLoopCount; private InputMode _storedMode; public override void OnStartLocalPlayer() => LocalInstance = this; public void Start() { if (!IsLocalPlayer) { return; } if (QSBSceneManager.IsInUniverse) { Init(); } QSBSceneManager.OnSceneLoaded += OnSceneLoaded; GlobalMessenger.AddListener(EventNames.RestartTimeLoop, OnLoopStart); GlobalMessenger.AddListener(EventNames.WakeUp, OnWakeUp); } private void OnWakeUp() { DebugLog.DebugWrite($"OnWakeUp", MessageType.Info); if (QNetworkServer.active) { QSBCore.HasWokenUp = true; RespawnOnDeath.Instance.Init(); } } public void OnDestroy() { QSBSceneManager.OnSceneLoaded -= OnSceneLoaded; GlobalMessenger.RemoveListener(EventNames.RestartTimeLoop, OnLoopStart); GlobalMessenger.RemoveListener(EventNames.WakeUp, OnWakeUp); } private void OnSceneLoaded(OWScene scene, bool isInUniverse) { if (isInUniverse) { Init(); } else { _state = State.NotLoaded; } } private void OnLoopStart() => _localLoopCount++; private void Init() { QSBEventManager.FireEvent(EventNames.QSBPlayerStatesRequest); _state = State.Loaded; gameObject.AddComponent(); if (IsServer) { SendServerTime(); } else { WakeUpOrSleep(); } } private void SendServerTime() => QSBEventManager.FireEvent(EventNames.QSBServerTime, Time.timeSinceLevelLoad, _localLoopCount); public void OnClientReceiveMessage(ServerTimeMessage message) { _serverTime = message.ServerTime; _serverLoopCount = message.LoopCount; WakeUpOrSleep(); } private void WakeUpOrSleep() { if (_state == State.NotLoaded || _localLoopCount != _serverLoopCount) { return; } var myTime = Time.timeSinceLevelLoad; var diff = myTime - _serverTime; if (diff > TimeThreshold) { StartPausing(); return; } if (diff < -TimeThreshold) { StartFastForwarding(); } } private void StartFastForwarding() { if (_state == State.FastForwarding) { TimeSyncUI.TargetTime = _serverTime; return; } DebugLog.DebugWrite($"START FASTFORWARD (Target:{_serverTime} Current:{Time.timeSinceLevelLoad})", MessageType.Info); if (Locator.GetPlayerCamera() != null) { Locator.GetPlayerCamera().enabled = false; } _timeScale = MaxFastForwardSpeed; _state = State.FastForwarding; OWTime.SetMaxDeltaTime(0.033333335f); TimeSyncUI.TargetTime = _serverTime; TimeSyncUI.Start(TimeSyncType.Fastforwarding); DisableInput(); } private void StartPausing() { if (_state == State.Pausing) { return; } DebugLog.DebugWrite($"START PAUSING (Target:{_serverTime} Current:{Time.timeSinceLevelLoad})", MessageType.Info); Locator.GetPlayerCamera().enabled = false; _timeScale = 0f; _state = State.Pausing; SpinnerUI.Show(); TimeSyncUI.Start(TimeSyncType.Pausing); DisableInput(); } private void ResetTimeScale() { _timeScale = 1f; OWTime.SetMaxDeltaTime(0.06666667f); Locator.GetPlayerCamera().enabled = true; _state = State.Loaded; if (!_isInputEnabled) { EnableInput(); } DebugLog.DebugWrite($"RESET TIMESCALE", MessageType.Info); _isFirstFastForward = false; QSBCore.HasWokenUp = true; Physics.SyncTransforms(); SpinnerUI.Hide(); TimeSyncUI.Stop(); QSBEventManager.FireEvent(EventNames.QSBPlayerStatesRequest); RespawnOnDeath.Instance.Init(); } private void DisableInput() { DebugLog.DebugWrite($"disable input - current:{OWInput.GetInputMode()}"); _isInputEnabled = false; _storedMode = OWInput.GetInputMode(); OWInput.ChangeInputMode(InputMode.None); } private void EnableInput() { DebugLog.DebugWrite($"enable input - stored:{_storedMode}"); _isInputEnabled = true; OWInput.ChangeInputMode( _storedMode != InputMode.None ? _storedMode : InputMode.Character); } public void Update() { if (IsServer) { UpdateServer(); } else if (IsLocalPlayer) { UpdateLocal(); } } private void UpdateServer() { if (_state != State.Loaded) { return; } _sendTimer += Time.unscaledDeltaTime; if (_sendTimer > 1) { SendServerTime(); _sendTimer = 0; } } private void UpdateLocal() { _serverTime += Time.unscaledDeltaTime; if (!_isInputEnabled && OWInput.GetInputMode() != InputMode.None) { DisableInput(); } if (_state == State.NotLoaded) { return; } if (_state == State.FastForwarding) { if (Locator.GetPlayerCamera() != null && !Locator.GetPlayerCamera().enabled) { Locator.GetPlayerCamera().enabled = false; } var diff = _serverTime - Time.timeSinceLevelLoad; Time.timeScale = Mathf.SmoothStep(MinFastForwardSpeed, MaxFastForwardSpeed, Mathf.Abs(diff) / MaxFastForwardDiff); if (QSBSceneManager.CurrentScene == OWScene.SolarSystem && _isFirstFastForward) { var spawnPoint = Locator.GetPlayerBody().GetComponent().GetInitialSpawnPoint().transform; Locator.GetPlayerTransform().position = spawnPoint.position; Locator.GetPlayerTransform().rotation = spawnPoint.rotation; Physics.SyncTransforms(); } } else { Time.timeScale = _timeScale; } var isDoneFastForwarding = _state == State.FastForwarding && Time.timeSinceLevelLoad >= _serverTime; var isDonePausing = _state == State.Pausing && Time.timeSinceLevelLoad < _serverTime; if (isDoneFastForwarding || isDonePausing) { ResetTimeScale(); } } } }