From 4c87dcd3cb92855df1fa01dea52d2ddd09f2adce Mon Sep 17 00:00:00 2001 From: Fildrance Date: Sat, 2 Mar 2024 23:40:04 +0300 Subject: [PATCH] fix: lobby music volume will be changed on options change without restart (also lobby music not looped anymore) (#25530) * fix: lobby music volume will be changed on options change without restart (also lobby music not looped anymore) * refactor: now lobby music is part of ContentAudioSystem. Lobby playlist is used instead of single track. Client now selects next lobby soundtrack after previous finished. * refactor: incapsulated info on current lobby track in simple record * refactor: fixed inconsistent naming between song and soundtrack for lobbymusic * refactor: xml-doc for LobbyPlaylistChangedEvent * fix: inverted invalid _audio.PlayGlobal check to return only if lobby soundtrack play call failed --------- Co-authored-by: pa.pecherskij --- Content.Client/Audio/BackgroundAudioSystem.cs | 156 ---------- .../Audio/ContentAudioSystem.LobbyMusic.cs | 283 ++++++++++++++++++ Content.Client/Audio/ContentAudioSystem.cs | 11 +- .../GameTicking/Managers/ClientGameTicker.cs | 19 -- Content.Client/Lobby/LobbyState.cs | 27 +- Content.Client/Lobby/UI/LobbyGui.xaml.cs | 3 + Content.Server/Audio/ContentAudioSystem.cs | 47 +++ .../GameTicking/GameTicker.Lobby.cs | 2 +- .../GameTicking/GameTicker.LobbyMusic.cs | 68 ----- .../GameTicking/GameTicker.RoundFlow.cs | 19 +- Content.Server/GameTicking/GameTicker.cs | 1 - .../Audio/Events/LobbyPlaylistChangedEvent.cs | 29 ++ .../GameTicking/SharedGameTicker.cs | 7 +- 13 files changed, 402 insertions(+), 270 deletions(-) delete mode 100644 Content.Client/Audio/BackgroundAudioSystem.cs create mode 100644 Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs delete mode 100644 Content.Server/GameTicking/GameTicker.LobbyMusic.cs create mode 100644 Content.Shared/Audio/Events/LobbyPlaylistChangedEvent.cs diff --git a/Content.Client/Audio/BackgroundAudioSystem.cs b/Content.Client/Audio/BackgroundAudioSystem.cs deleted file mode 100644 index 702f810e27..0000000000 --- a/Content.Client/Audio/BackgroundAudioSystem.cs +++ /dev/null @@ -1,156 +0,0 @@ -using Content.Client.GameTicking.Managers; -using Content.Client.Lobby; -using Content.Shared.CCVar; -using Content.Shared.GameTicking; -using JetBrains.Annotations; -using Robust.Client; -using Robust.Client.State; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Configuration; -using Robust.Shared.Player; - -namespace Content.Client.Audio; - -[UsedImplicitly] -public sealed class BackgroundAudioSystem : EntitySystem -{ - /* - * TODO: Nuke this system and merge into contentaudiosystem - */ - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly IBaseClient _client = default!; - [Dependency] private readonly IConfigurationManager _configManager = default!; - [Dependency] private readonly ClientGameTicker _gameTicker = default!; - [Dependency] private readonly IStateManager _stateManager = default!; - - private readonly AudioParams _lobbyParams = new(-5f, 1, "Master", 0, 0, 0, true, 0f); - private readonly AudioParams _roundEndParams = new(-5f, 1, "Master", 0, 0, 0, false, 0f); - - public EntityUid? LobbyMusicStream; - public EntityUid? LobbyRoundRestartAudioStream; - - public override void Initialize() - { - base.Initialize(); - - Subs.CVar(_configManager, CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged); - Subs.CVar(_configManager, CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged); - - _stateManager.OnStateChanged += StateManagerOnStateChanged; - - _client.PlayerLeaveServer += OnLeave; - - _gameTicker.LobbySongUpdated += LobbySongUpdated; - - SubscribeNetworkEvent(PlayRestartSound); - } - - public override void Shutdown() - { - base.Shutdown(); - - _stateManager.OnStateChanged -= StateManagerOnStateChanged; - - _client.PlayerLeaveServer -= OnLeave; - - _gameTicker.LobbySongUpdated -= LobbySongUpdated; - - EndLobbyMusic(); - } - - private void StateManagerOnStateChanged(StateChangedEventArgs args) - { - switch (args.NewState) - { - case LobbyState: - StartLobbyMusic(); - break; - default: - EndLobbyMusic(); - break; - } - } - - private void OnLeave(object? sender, PlayerEventArgs args) - { - EndLobbyMusic(); - } - - private void LobbyMusicVolumeCVarChanged(float volume) - { - if (_stateManager.CurrentState is LobbyState) - { - RestartLobbyMusic(); - } - } - - private void LobbyMusicCVarChanged(bool musicEnabled) - { - if (!musicEnabled) - { - EndLobbyMusic(); - } - else if (_stateManager.CurrentState is LobbyState) - { - StartLobbyMusic(); - } - else - { - EndLobbyMusic(); - } - } - - private void LobbySongUpdated() - { - RestartLobbyMusic(); - } - - public void RestartLobbyMusic() - { - EndLobbyMusic(); - StartLobbyMusic(); - } - - public void StartLobbyMusic() - { - if (LobbyMusicStream != null || !_configManager.GetCVar(CCVars.LobbyMusicEnabled)) - return; - - var file = _gameTicker.LobbySong; - if (file == null) // We have not received the lobby song yet. - { - return; - } - - LobbyMusicStream = _audio.PlayGlobal( - file, - Filter.Local(), - false, - _lobbyParams.WithVolume(_lobbyParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume))))?.Entity; - } - - private void EndLobbyMusic() - { - LobbyMusicStream = _audio.Stop(LobbyMusicStream); - } - - private void PlayRestartSound(RoundRestartCleanupEvent ev) - { - if (!_configManager.GetCVar(CCVars.RestartSoundsEnabled)) - return; - - var file = _gameTicker.RestartSound; - if (string.IsNullOrEmpty(file)) - { - return; - } - - LobbyRoundRestartAudioStream = _audio.PlayGlobal( - file, - Filter.Local(), - false, - _roundEndParams.WithVolume(_roundEndParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume))) - )?.Entity; - } -} diff --git a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs new file mode 100644 index 0000000000..0fdcc7a86d --- /dev/null +++ b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs @@ -0,0 +1,283 @@ +using System.Linq; +using Content.Client.GameTicking.Managers; +using Content.Client.Lobby; +using Content.Shared.Audio.Events; +using Content.Shared.CCVar; +using Content.Shared.GameTicking; +using Robust.Client; +using Robust.Client.ResourceManagement; +using Robust.Client.State; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Client.Audio; + +// Part of ContentAudioSystem that is responsible for lobby music playing/stopping and round-end sound-effect. +public sealed partial class ContentAudioSystem +{ + [Dependency] private readonly IBaseClient _client = default!; + [Dependency] private readonly ClientGameTicker _gameTicker = default!; + [Dependency] private readonly IStateManager _stateManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + + private readonly AudioParams _lobbySoundtrackParams = new(-5f, 1, "Master", 0, 0, 0, false, 0f); + private readonly AudioParams _roundEndSoundEffectParams = new(-5f, 1, "Master", 0, 0, 0, false, 0f); + + /// + /// EntityUid of lobby restart sound component. + /// + private EntityUid? _lobbyRoundRestartAudioStream; + + /// + /// Shuffled list of soundtrack file-names. + /// + private string[]? _lobbyPlaylist; + + /// + /// Short info about lobby soundtrack currently playing. Is null if soundtrack is not playing. + /// + private LobbySoundtrackInfo? _lobbySoundtrackInfo; + + private Action? _lobbySoundtrackChanged; + + /// + /// Event for subscription on lobby soundtrack changes. + /// + public event Action? LobbySoundtrackChanged + { + add + { + if (value != null) + { + if (_lobbySoundtrackInfo != null) + { + value(new LobbySoundtrackChangedEvent(_lobbySoundtrackInfo.Filename)); + } + + _lobbySoundtrackChanged += value; + } + } + remove => _lobbySoundtrackChanged -= value; + } + + /// + /// Initializes subscriptions that are related to lobby music. + /// + private void InitializeLobbyMusic() + { + Subs.CVar(_configManager, CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged); + Subs.CVar(_configManager, CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged); + + _stateManager.OnStateChanged += StateManagerOnStateChanged; + + _client.PlayerLeaveServer += OnLeave; + + SubscribeNetworkEvent(OnLobbySongStopped); + SubscribeNetworkEvent(OnLobbySongChanged); + } + + private void OnLobbySongStopped(LobbyMusicStopEvent ev) + { + EndLobbyMusic(); + } + + private void StateManagerOnStateChanged(StateChangedEventArgs args) + { + switch (args.NewState) + { + case LobbyState: + StartLobbyMusic(); + break; + default: + EndLobbyMusic(); + break; + } + } + + private void OnLeave(object? sender, PlayerEventArgs args) + { + EndLobbyMusic(); + } + + private void LobbyMusicVolumeCVarChanged(float volume) + { + if (_lobbySoundtrackInfo != null) + { + _audio.SetVolume( + _lobbySoundtrackInfo.MusicStreamEntityUid, + _lobbySoundtrackParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume)) + ); + } + } + + private void LobbyMusicCVarChanged(bool musicEnabled) + { + if (musicEnabled && _stateManager.CurrentState is LobbyState) + { + StartLobbyMusic(); + } + else + { + EndLobbyMusic(); + } + } + + private void OnLobbySongChanged(LobbyPlaylistChangedEvent playlistChangedEvent) + { + var playlist = playlistChangedEvent.Playlist; + //playlist is already playing, no need to restart it + if (_lobbySoundtrackInfo != null + && _lobbyPlaylist != null + && _lobbyPlaylist.SequenceEqual(playlist) + ) + { + return; + } + + EndLobbyMusic(); + StartLobbyMusic(playlistChangedEvent.Playlist); + } + + /// + /// Re-starts playing lobby music from playlist, last sent from server. if there is currently none - does nothing. + /// + private void StartLobbyMusic() + { + if (_lobbyPlaylist == null || _lobbyPlaylist.Length == 0) + { + return; + } + + StartLobbyMusic(_lobbyPlaylist); + } + + /// + /// Starts playing lobby music from playlist. If playlist is empty, or lobby music setting is turned off - does nothing. + /// + /// Array of soundtrack filenames for lobby playlist. + private void StartLobbyMusic(string[] playlist) + { + if (_lobbySoundtrackInfo != null || !_configManager.GetCVar(CCVars.LobbyMusicEnabled)) + return; + + _lobbyPlaylist = playlist; + if (_lobbyPlaylist.Length == 0) + { + return; + } + + PlaySoundtrack(playlist[0]); + } + + private void PlaySoundtrack(string soundtrackFilename) + { + if (!_resourceCache.TryGetResource(new ResPath(soundtrackFilename), out AudioResource? audio)) + { + return; + } + + var playResult = _audio.PlayGlobal( + soundtrackFilename, + Filter.Local(), + false, + _lobbySoundtrackParams.WithVolume(_lobbySoundtrackParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume))) + ); + if (playResult.Value.Entity == default) + { + _sawmill.Warning( + $"Tried to play lobby soundtrack '{{Filename}}' using {nameof(SharedAudioSystem)}.{nameof(SharedAudioSystem.PlayGlobal)} but it returned default value of EntityUid!", + soundtrackFilename); + return; + } + + var nextTrackOn = _timing.CurTime + audio.AudioStream.Length; + _lobbySoundtrackInfo = new LobbySoundtrackInfo(soundtrackFilename, nextTrackOn, playResult.Value.Entity); + + var lobbySongChangedEvent = new LobbySoundtrackChangedEvent(soundtrackFilename); + _lobbySoundtrackChanged?.Invoke(lobbySongChangedEvent); + } + + private void EndLobbyMusic() + { + if (_lobbySoundtrackInfo == null) + { + return; + } + + _audio.Stop(_lobbySoundtrackInfo.MusicStreamEntityUid); + _lobbySoundtrackInfo = null; + var lobbySongChangedEvent = new LobbySoundtrackChangedEvent(); + _lobbySoundtrackChanged?.Invoke(lobbySongChangedEvent); + } + + private void PlayRestartSound(RoundRestartCleanupEvent ev) + { + if (!_configManager.GetCVar(CCVars.RestartSoundsEnabled)) + return; + + var file = _gameTicker.RestartSound; + if (string.IsNullOrEmpty(file)) + { + return; + } + + _lobbyRoundRestartAudioStream = _audio.PlayGlobal( + file, + Filter.Local(), + false, + _roundEndSoundEffectParams.WithVolume(_roundEndSoundEffectParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume))) + )?.Entity; + } + + private void ShutdownLobbyMusic() + { + _stateManager.OnStateChanged -= StateManagerOnStateChanged; + + _client.PlayerLeaveServer -= OnLeave; + + EndLobbyMusic(); + } + + private void UpdateLobbyMusic() + { + if ( + _lobbySoundtrackInfo != null + && _timing.CurTime >= _lobbySoundtrackInfo.NextTrackOn + && _lobbyPlaylist?.Length > 0 + ) + { + var nextSoundtrackFilename = GetNextSoundtrackFromPlaylist(_lobbySoundtrackInfo.Filename, _lobbyPlaylist); + PlaySoundtrack(nextSoundtrackFilename); + } + } + + private static string GetNextSoundtrackFromPlaylist(string currentSoundtrackFilename, string[] playlist) + { + var indexOfCurrent = Array.IndexOf(playlist, currentSoundtrackFilename); + var nextTrackIndex = indexOfCurrent + 1; + if (nextTrackIndex > playlist.Length - 1) + { + nextTrackIndex = 0; + } + + return playlist[nextTrackIndex]; + } + + /// Container for lobby soundtrack information. + /// Soundtrack filename. + /// Time (based on ) when this track is going to finish playing and next track have to be started. + /// + /// EntityUid of launched soundtrack (from ). + /// + private sealed record LobbySoundtrackInfo(string Filename, TimeSpan NextTrackOn, EntityUid MusicStreamEntityUid); +} + +/// +/// Event of changing lobby soundtrack (or stopping lobby music - will pass null for in that case). +/// Is used by and . +/// +/// Filename of newly set soundtrack, or null if soundtrack playback is stopped. +public sealed record LobbySoundtrackChangedEvent(string? SoundtrackFilename = null); diff --git a/Content.Client/Audio/ContentAudioSystem.cs b/Content.Client/Audio/ContentAudioSystem.cs index 56921ee430..c7fc7bcf73 100644 --- a/Content.Client/Audio/ContentAudioSystem.cs +++ b/Content.Client/Audio/ContentAudioSystem.cs @@ -29,12 +29,14 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem public const float AmbientMusicMultiplier = 3f; public const float LobbyMultiplier = 3f; public const float InterfaceMultiplier = 2f; - + public override void Initialize() { base.Initialize(); + UpdatesOutsidePrediction = true; InitializeAmbientMusic(); + InitializeLobbyMusic(); SubscribeNetworkEvent(OnRoundCleanup); } @@ -43,11 +45,11 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem _fadingOut.Clear(); // Preserve lobby music but everything else should get dumped. - var lobbyMusic = EntityManager.System().LobbyMusicStream; + var lobbyMusic = _lobbySoundtrackInfo?.MusicStreamEntityUid; TryComp(lobbyMusic, out AudioComponent? lobbyMusicComp); var oldMusicGain = lobbyMusicComp?.Gain; - var restartAudio = EntityManager.System().LobbyRoundRestartAudioStream; + var restartAudio = _lobbyRoundRestartAudioStream; TryComp(restartAudio, out AudioComponent? restartComp); var oldAudioGain = restartComp?.Gain; @@ -62,12 +64,14 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem { Audio.SetGain(restartAudio, oldAudioGain.Value, restartComp); } + PlayRestartSound(ev); } public override void Shutdown() { base.Shutdown(); ShutdownAmbientMusic(); + ShutdownLobbyMusic(); } public override void Update(float frameTime) @@ -78,6 +82,7 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem return; UpdateAmbientMusic(); + UpdateLobbyMusic(); UpdateFades(frameTime); } diff --git a/Content.Client/GameTicking/Managers/ClientGameTicker.cs b/Content.Client/GameTicking/Managers/ClientGameTicker.cs index 25b6802bd7..df709e9444 100644 --- a/Content.Client/GameTicking/Managers/ClientGameTicker.cs +++ b/Content.Client/GameTicking/Managers/ClientGameTicker.cs @@ -1,16 +1,11 @@ using Content.Client.Gameplay; using Content.Client.Lobby; using Content.Client.RoundEnd; -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.Audio.Systems; -using Robust.Shared.Configuration; -using Robust.Shared.Player; using Robust.Shared.Utility; namespace Content.Client.GameTicking.Managers @@ -32,7 +27,6 @@ namespace Content.Client.GameTicking.Managers [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; } @@ -45,7 +39,6 @@ namespace Content.Client.GameTicking.Managers public event Action? InfoBlobUpdated; public event Action? LobbyStatusUpdated; - public event Action? LobbySongUpdated; public event Action? LobbyLateJoinStatusUpdated; public event Action>>? LobbyJobsAvailableUpdated; @@ -70,16 +63,6 @@ namespace Content.Client.GameTicking.Managers _initialized = true; } - public void SetLobbySong(string? song, bool forceUpdate = false) - { - var updated = song != LobbySong; - - LobbySong = song; - - if (updated || forceUpdate) - LobbySongUpdated?.Invoke(); - } - private void LateJoinStatus(TickerLateJoinStatusEvent message) { DisallowedLateJoin = message.Disallowed; @@ -120,7 +103,6 @@ namespace Content.Client.GameTicking.Managers RoundStartTimeSpan = message.RoundStartTimeSpan; IsGameStarted = message.IsRoundStarted; AreWeReady = message.YouAreReady; - SetLobbySong(message.LobbySong); LobbyBackground = message.LobbyBackground; Paused = message.Paused; @@ -148,7 +130,6 @@ namespace Content.Client.GameTicking.Managers private void RoundEnd(RoundEndMessageEvent message) { // Force an update in the event of this song being the same as the last. - SetLobbySong(message.LobbySong, true); RestartSound = message.RestartSound; // Don't open duplicate windows (mainly for replays). diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs index 457163a5b5..fe31dce062 100644 --- a/Content.Client/Lobby/LobbyState.cs +++ b/Content.Client/Lobby/LobbyState.cs @@ -1,3 +1,4 @@ +using Content.Client.Audio; using Content.Client.GameTicking.Managers; using Content.Client.LateJoin; using Content.Client.Lobby.UI; @@ -34,6 +35,7 @@ namespace Content.Client.Lobby [ViewVariables] private CharacterSetupGui? _characterSetup; private ClientGameTicker _gameTicker = default!; + private ContentAudioSystem _contentAudioSystem = default!; protected override Type? LinkedScreenType { get; } = typeof(LobbyGui); private LobbyGui? _lobby; @@ -49,6 +51,8 @@ namespace Content.Client.Lobby var chatController = _userInterfaceManager.GetUIController(); _gameTicker = _entityManager.System(); + _contentAudioSystem = _entityManager.System(); + _contentAudioSystem.LobbySoundtrackChanged += UpdateLobbySoundtrackInfo; _characterSetup = new CharacterSetupGui(_entityManager, _resourceCache, _preferencesManager, _prototypeManager, _configurationManager); LayoutContainer.SetAnchorPreset(_characterSetup, LayoutContainer.LayoutPreset.Wide); @@ -93,6 +97,7 @@ namespace Content.Client.Lobby _gameTicker.InfoBlobUpdated -= UpdateLobbyUi; _gameTicker.LobbyStatusUpdated -= LobbyStatusUpdated; _gameTicker.LobbyLateJoinStatusUpdated -= LobbyLateJoinStatusUpdated; + _contentAudioSystem.LobbySoundtrackChanged -= UpdateLobbySoundtrackInfo; _voteManager.ClearPopupContainer(); @@ -207,22 +212,28 @@ namespace Content.Client.Lobby { _lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob); } + } - if (_gameTicker.LobbySong == null) + private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev) + { + if (ev.SoundtrackFilename == null) { _lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text")); } - else if (_resourceCache.TryGetResource(_gameTicker.LobbySong, out var lobbySongResource)) + else if ( + ev.SoundtrackFilename != null + && _resourceCache.TryGetResource(ev.SoundtrackFilename, out var lobbySongResource) + ) { var lobbyStream = lobbySongResource.AudioStream; - var title = string.IsNullOrEmpty(lobbyStream.Title) ? - Loc.GetString("lobby-state-song-unknown-title") : - lobbyStream.Title; + var title = string.IsNullOrEmpty(lobbyStream.Title) + ? Loc.GetString("lobby-state-song-unknown-title") + : lobbyStream.Title; - var artist = string.IsNullOrEmpty(lobbyStream.Artist) ? - Loc.GetString("lobby-state-song-unknown-artist") : - lobbyStream.Artist; + var artist = string.IsNullOrEmpty(lobbyStream.Artist) + ? Loc.GetString("lobby-state-song-unknown-artist") + : lobbyStream.Artist; var markup = Loc.GetString("lobby-state-song-text", ("songTitle", title), diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml.cs b/Content.Client/Lobby/UI/LobbyGui.xaml.cs index d45a99c16f..69867ea90c 100644 --- a/Content.Client/Lobby/UI/LobbyGui.xaml.cs +++ b/Content.Client/Lobby/UI/LobbyGui.xaml.cs @@ -1,5 +1,6 @@ using Content.Client.Chat.UI; using Content.Client.Info; +using Content.Client.Message; using Content.Client.Preferences; using Content.Client.Preferences.UI; using Content.Client.UserInterface.Screens; @@ -33,6 +34,8 @@ namespace Content.Client.Lobby.UI SetAnchorPreset(MainContainer, LayoutPreset.Wide); SetAnchorPreset(Background, LayoutPreset.Wide); + LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text")); + LeaveButton.OnPressed += _ => _consoleHost.ExecuteCommand("disconnect"); OptionsButton.OnPressed += _ => _userInterfaceManager.GetUIController().ToggleWindow(); } diff --git a/Content.Server/Audio/ContentAudioSystem.cs b/Content.Server/Audio/ContentAudioSystem.cs index b1db01141b..f36d14cbaf 100644 --- a/Content.Server/Audio/ContentAudioSystem.cs +++ b/Content.Server/Audio/ContentAudioSystem.cs @@ -1,19 +1,37 @@ +using System.Linq; +using Content.Server.GameTicking; using Content.Server.GameTicking.Events; using Content.Shared.Audio; +using Content.Shared.Audio.Events; using Content.Shared.GameTicking; using Robust.Server.Audio; using Robust.Shared.Audio; using Robust.Shared.Prototypes; +using Robust.Shared.Random; namespace Content.Server.Audio; public sealed class ContentAudioSystem : SharedContentAudioSystem { + [ValidatePrototypeId] + private const string LobbyMusicCollection = "LobbyMusic"; + [Dependency] private readonly AudioSystem _serverAudio = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + private SoundCollectionPrototype _lobbyMusicCollection = default!; + private string[]? _lobbyPlaylist; public override void Initialize() { base.Initialize(); + + _lobbyMusicCollection = _prototypeManager.Index(LobbyMusicCollection); + _lobbyPlaylist = ShuffleLobbyPlaylist(); + + SubscribeLocalEvent(OnRoundEnd); + SubscribeLocalEvent(OnPlayerJoinedLobby); SubscribeLocalEvent(OnRoundCleanup); SubscribeLocalEvent(OnRoundStart); SubscribeLocalEvent(OnProtoReload); @@ -36,4 +54,33 @@ public sealed class ContentAudioSystem : SharedContentAudioSystem // yeah it's whacky af. _serverAudio.ReloadPresets(); } + + private void OnPlayerJoinedLobby(PlayerJoinedLobbyEvent ev) + { + if (_lobbyPlaylist != null) + { + var session = ev.PlayerSession; + RaiseNetworkEvent(new LobbyPlaylistChangedEvent(_lobbyPlaylist), session); + } + } + + private void OnRoundEnd(RoundEndMessageEvent ev) + { + // The lobby song is set here instead of in RestartRound, + // because ShowRoundEndScoreboard triggers the start of the music playing + // at the end of a round, and this needs to be set before RestartRound + // in order for the lobby song status display to be accurate. + _lobbyPlaylist = ShuffleLobbyPlaylist(); + RaiseNetworkEvent(new LobbyPlaylistChangedEvent(_lobbyPlaylist)); + } + + private string[] ShuffleLobbyPlaylist() + { + var playlist = _lobbyMusicCollection.PickFiles + .Select(x => x.ToString()) + .ToArray(); + _robustRandom.Shuffle(playlist); + + return playlist; + } } diff --git a/Content.Server/GameTicking/GameTicker.Lobby.cs b/Content.Server/GameTicking/GameTicker.Lobby.cs index 3a6f742baa..82ef8c6012 100644 --- a/Content.Server/GameTicking/GameTicker.Lobby.cs +++ b/Content.Server/GameTicking/GameTicker.Lobby.cs @@ -94,7 +94,7 @@ namespace Content.Server.GameTicking private TickerLobbyStatusEvent GetStatusMsg(ICommonSession session) { _playerGameStatuses.TryGetValue(session.UserId, out var status); - return new TickerLobbyStatusEvent(RunLevel != GameRunLevel.PreRoundLobby, LobbySong, LobbyBackground, status == PlayerGameStatus.ReadyToPlay, _roundStartTime, RoundPreloadTime, RoundStartTimeSpan, Paused); + return new TickerLobbyStatusEvent(RunLevel != GameRunLevel.PreRoundLobby, LobbyBackground, status == PlayerGameStatus.ReadyToPlay, _roundStartTime, RoundPreloadTime, RoundStartTimeSpan, Paused); } private void SendStatusToAll() diff --git a/Content.Server/GameTicking/GameTicker.LobbyMusic.cs b/Content.Server/GameTicking/GameTicker.LobbyMusic.cs deleted file mode 100644 index 5a44320ff3..0000000000 --- a/Content.Server/GameTicking/GameTicker.LobbyMusic.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.Random; -using Robust.Shared.Utility; - -namespace Content.Server.GameTicking -{ - public sealed partial class GameTicker - { - [ValidatePrototypeId] - private const string LobbyMusicCollection = "LobbyMusic"; - - [ViewVariables] - private bool _lobbyMusicInitialized = false; - - [ViewVariables] - private SoundCollectionPrototype _lobbyMusicCollection = default!; - - [ViewVariables] - public string? LobbySong { get; private set; } - - private void InitializeLobbyMusic() - { - DebugTools.Assert(!_lobbyMusicInitialized); - _lobbyMusicCollection = _prototypeManager.Index(LobbyMusicCollection); - - // Now that the collection is set, the lobby music has been initialized and we can choose a random song. - _lobbyMusicInitialized = true; - - ChooseRandomLobbySong(); - } - - /// - /// Sets the current lobby song, or stops it if null. - /// - /// The lobby song to play, or null to stop any lobby songs. - public void SetLobbySong(string? song) - { - DebugTools.Assert(_lobbyMusicInitialized); - - if (song == null) - { - LobbySong = null; - return; - // TODO GAMETICKER send song stop event - } - - LobbySong = song; - // TODO GAMETICKER send song change event - } - - /// - /// Plays a random song from the LobbyMusic sound collection. - /// - public void ChooseRandomLobbySong() - { - DebugTools.Assert(_lobbyMusicInitialized); - SetLobbySong(_robustRandom.Pick(_lobbyMusicCollection.PickFiles).ToString()); - } - - /// - /// Stops the current lobby song being played. - /// - public void StopLobbySong() - { - SetLobbySong(null); - } - } -} diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 69624ed5b2..004508ab91 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -301,12 +301,6 @@ namespace Content.Server.GameTicking RunLevel = GameRunLevel.PostRound; - // The lobby song is set here instead of in RestartRound, - // because ShowRoundEndScoreboard triggers the start of the music playing - // at the end of a round, and this needs to be set before RestartRound - // in order for the lobby song status display to be accurate. - LobbySong = _robustRandom.Pick(_lobbyMusicCollection.PickFiles).ToString(); - ShowRoundEndScoreboard(text); SendRoundEndDiscordMessage(); } @@ -394,8 +388,17 @@ namespace Content.Server.GameTicking var listOfPlayerInfoFinal = listOfPlayerInfo.OrderBy(pi => pi.PlayerOOCName).ToArray(); var sound = RoundEndSoundCollection == null ? null : _audio.GetSound(new SoundCollectionSpecifier(RoundEndSoundCollection)); - RaiseNetworkEvent(new RoundEndMessageEvent(gamemodeTitle, roundEndText, roundDuration, RoundId, - listOfPlayerInfoFinal.Length, listOfPlayerInfoFinal, LobbySong, sound)); + var roundEndMessageEvent = new RoundEndMessageEvent( + gamemodeTitle, + roundEndText, + roundDuration, + RoundId, + listOfPlayerInfoFinal.Length, + listOfPlayerInfoFinal, + sound + ); + RaiseNetworkEvent(roundEndMessageEvent); + RaiseLocalEvent(roundEndMessageEvent); _replayRoundPlayerInfo = listOfPlayerInfoFinal; _replayRoundText = roundEndText; diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 1d07814ece..efda3df0ca 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -93,7 +93,6 @@ namespace Content.Server.GameTicking InitializeStatusShell(); InitializeCVars(); InitializePlayer(); - InitializeLobbyMusic(); InitializeLobbyBackground(); InitializeGamePreset(); DebugTools.Assert(_prototypeManager.Index(FallbackOverflowJob).Name == FallbackOverflowJobName, diff --git a/Content.Shared/Audio/Events/LobbyPlaylistChangedEvent.cs b/Content.Shared/Audio/Events/LobbyPlaylistChangedEvent.cs new file mode 100644 index 0000000000..87f4e7d446 --- /dev/null +++ b/Content.Shared/Audio/Events/LobbyPlaylistChangedEvent.cs @@ -0,0 +1,29 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Audio.Events; + +/// +/// Event of changing lobby music playlist (on server). +/// +[Serializable, NetSerializable] +public sealed class LobbyPlaylistChangedEvent : EntityEventArgs +{ + /// + public LobbyPlaylistChangedEvent(string[] playlist) + { + Playlist = playlist; + } + + /// + /// List of soundtrack filenames for lobby playlist. + /// + public string[] Playlist; +} + +/// +/// Event of stopping lobby music. +/// +[Serializable, NetSerializable] +public sealed class LobbyMusicStopEvent : EntityEventArgs +{ +} diff --git a/Content.Shared/GameTicking/SharedGameTicker.cs b/Content.Shared/GameTicking/SharedGameTicker.cs index 2677d499c2..95da4f4c38 100644 --- a/Content.Shared/GameTicking/SharedGameTicker.cs +++ b/Content.Shared/GameTicking/SharedGameTicker.cs @@ -78,7 +78,6 @@ namespace Content.Shared.GameTicking public sealed class TickerLobbyStatusEvent : EntityEventArgs { public bool IsRoundStarted { get; } - public string? LobbySong { get; } public string? LobbyBackground { get; } public bool YouAreReady { get; } // UTC. @@ -86,10 +85,9 @@ namespace Content.Shared.GameTicking public TimeSpan RoundStartTimeSpan { get; } public bool Paused { get; } - public TickerLobbyStatusEvent(bool isRoundStarted, string? lobbySong, string? lobbyBackground, bool youAreReady, TimeSpan startTime, TimeSpan preloadTime, TimeSpan roundStartTimeSpan, bool paused) + public TickerLobbyStatusEvent(bool isRoundStarted, string? lobbyBackground, bool youAreReady, TimeSpan startTime, TimeSpan preloadTime, TimeSpan roundStartTimeSpan, bool paused) { IsRoundStarted = isRoundStarted; - LobbySong = lobbySong; LobbyBackground = lobbyBackground; YouAreReady = youAreReady; StartTime = startTime; @@ -185,7 +183,6 @@ namespace Content.Shared.GameTicking public int RoundId { get; } public int PlayerCount { get; } public RoundEndPlayerInfo[] AllPlayersEndInfo { get; } - public string? LobbySong; /// /// Sound gets networked due to how entity lifecycle works between client / server and to avoid clipping. @@ -199,7 +196,6 @@ namespace Content.Shared.GameTicking int roundId, int playerCount, RoundEndPlayerInfo[] allPlayersEndInfo, - string? lobbySong, string? restartSound) { GamemodeTitle = gamemodeTitle; @@ -208,7 +204,6 @@ namespace Content.Shared.GameTicking RoundId = roundId; PlayerCount = playerCount; AllPlayersEndInfo = allPlayersEndInfo; - LobbySong = lobbySong; RestartSound = restartSound; } }