using System.Linq; using Content.Server.Chat.Managers; using Content.Shared.CCVar; using Robust.Server; using Robust.Server.Player; using Robust.Server.ServerStatus; using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Player; using Robust.Shared.Timing; namespace Content.Server.ServerUpdates; /// /// Responsible for restarting the server for update, when not disruptive. /// public sealed class ServerUpdateManager { [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IWatchdogApi _watchdog = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IBaseServer _server = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [ViewVariables] private bool _updateOnRoundEnd; private TimeSpan? _restartTime; public void Initialize() { _watchdog.UpdateReceived += WatchdogOnUpdateReceived; _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged; } public void Update() { if (_restartTime != null && _restartTime < _gameTiming.RealTime) { DoShutdown(); } } /// /// Notify that the round just ended, which is a great time to restart if necessary! /// /// True if the server is going to restart. public bool RoundEnded() { if (_updateOnRoundEnd) { DoShutdown(); return true; } return false; } private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) { switch (e.NewStatus) { case SessionStatus.Connecting: _restartTime = null; break; case SessionStatus.Disconnected: ServerEmptyUpdateRestartCheck(); break; } } private void WatchdogOnUpdateReceived() { _chatManager.DispatchServerAnnouncement(Loc.GetString("server-updates-received")); _updateOnRoundEnd = true; ServerEmptyUpdateRestartCheck(); } /// /// Checks whether there are still players on the server, /// and if not starts a timer to automatically reboot the server if an update is available. /// private void ServerEmptyUpdateRestartCheck() { // Can't simple check the current connected player count since that doesn't update // before PlayerStatusChanged gets fired. // So in the disconnect handler we'd still see a single player otherwise. var playersOnline = _playerManager.Sessions.Any(p => p.Status != SessionStatus.Disconnected); if (playersOnline || !_updateOnRoundEnd) { // Still somebody online. return; } if (_restartTime != null) { // Do nothing because I guess we already have a timer running..? return; } var restartDelay = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.UpdateRestartDelay)); _restartTime = restartDelay + _gameTiming.RealTime; } private void DoShutdown() { _server.Shutdown(Loc.GetString("server-updates-shutdown")); } }