using Content.Client.Audio; using Content.Client.Lobby; using Content.Client.RoundEnd; using Content.Client.Viewport; using Content.Shared.CCVar; using Content.Shared.GameTicking; using Content.Shared.GameWindow; using JetBrains.Annotations; using Robust.Client.Graphics; using Robust.Client.State; using Robust.Shared.Audio; using Robust.Shared.Configuration; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Utility; namespace Content.Client.GameTicking.Managers { [UsedImplicitly] public sealed class ClientGameTicker : SharedGameTicker { [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IConfigurationManager _configManager = default!; [ViewVariables] private bool _initialized; private Dictionary> _jobsAvailable = new(); private Dictionary _stationNames = new(); [ViewVariables] public bool AreWeReady { get; private set; } [ViewVariables] public bool IsGameStarted { get; private set; } [ViewVariables] public string? LobbySong { get; private set; } [ViewVariables] public string? RestartSound { get; private set; } [ViewVariables] public string? LobbyBackground { get; private set; } [ViewVariables] public bool DisallowedLateJoin { get; private set; } [ViewVariables] public string? ServerInfoBlob { get; private set; } [ViewVariables] public TimeSpan StartTime { get; private set; } [ViewVariables] public TimeSpan RoundStartTimeSpan { get; private set; } [ViewVariables] public new bool Paused { get; private set; } [ViewVariables] public IReadOnlyDictionary> JobsAvailable => _jobsAvailable; [ViewVariables] public IReadOnlyDictionary StationNames => _stationNames; public event Action? InfoBlobUpdated; public event Action? LobbyStatusUpdated; public event Action? LobbyReadyUpdated; public event Action? LobbyLateJoinStatusUpdated; public event Action>>? LobbyJobsAvailableUpdated; public override void Initialize() { DebugTools.Assert(!_initialized); SubscribeNetworkEvent(JoinLobby); SubscribeNetworkEvent(JoinGame); SubscribeNetworkEvent(LobbyStatus); SubscribeNetworkEvent(LobbyInfo); SubscribeNetworkEvent(LobbyCountdown); SubscribeNetworkEvent(LobbyReady); SubscribeNetworkEvent(RoundEnd); SubscribeNetworkEvent(msg => { IoCManager.Resolve().RequestWindowAttention(); }); SubscribeNetworkEvent(LateJoinStatus); SubscribeNetworkEvent(UpdateJobsAvailable); SubscribeNetworkEvent(RoundRestartCleanup); _initialized = true; } private void LateJoinStatus(TickerLateJoinStatusEvent message) { DisallowedLateJoin = message.Disallowed; LobbyLateJoinStatusUpdated?.Invoke(); } private void UpdateJobsAvailable(TickerJobsAvailableEvent message) { _jobsAvailable = message.JobsAvailableByStation; _stationNames = message.StationNames; LobbyJobsAvailableUpdated?.Invoke(JobsAvailable); } private void JoinLobby(TickerJoinLobbyEvent message) { _stateManager.RequestStateChange(); } private void LobbyStatus(TickerLobbyStatusEvent message) { StartTime = message.StartTime; RoundStartTimeSpan = message.RoundStartTimeSpan; IsGameStarted = message.IsRoundStarted; AreWeReady = message.YouAreReady; LobbySong = message.LobbySong; LobbyBackground = message.LobbyBackground; Paused = message.Paused; LobbyStatusUpdated?.Invoke(); } private void LobbyInfo(TickerLobbyInfoEvent message) { ServerInfoBlob = message.TextBlob; InfoBlobUpdated?.Invoke(); } private void JoinGame(TickerJoinGameEvent message) { _stateManager.RequestStateChange(); } private void LobbyCountdown(TickerLobbyCountdownEvent message) { StartTime = message.StartTime; Paused = message.Paused; } private void LobbyReady(TickerLobbyReadyEvent message) { LobbyReadyUpdated?.Invoke(); } private void RoundEnd(RoundEndMessageEvent message) { if (message.LobbySong != null) { LobbySong = message.LobbySong; Get().StartLobbyMusic(); } RestartSound = message.RestartSound; //This is not ideal at all, but I don't see an immediately better fit anywhere else. var roundEnd = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, _entityManager); } private void RoundRestartCleanup(RoundRestartCleanupEvent ev) { if (string.IsNullOrEmpty(RestartSound)) return; if (!_configManager.GetCVar(CCVars.RestartSoundsEnabled)) { RestartSound = null; return; } SoundSystem.Play(RestartSound, Filter.Empty()); // Cleanup the sound, we only want it to play when the round restarts after it ends normally. RestartSound = null; } } }