]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
fix: lobby music volume will be changed on options change without restart (also lobby...
authorFildrance <fildrance@gmail.com>
Sat, 2 Mar 2024 20:40:04 +0000 (23:40 +0300)
committerGitHub <noreply@github.com>
Sat, 2 Mar 2024 20:40:04 +0000 (15:40 -0500)
* 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 <pa.pecherskij@interfax.ru>
13 files changed:
Content.Client/Audio/BackgroundAudioSystem.cs [deleted file]
Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs [new file with mode: 0644]
Content.Client/Audio/ContentAudioSystem.cs
Content.Client/GameTicking/Managers/ClientGameTicker.cs
Content.Client/Lobby/LobbyState.cs
Content.Client/Lobby/UI/LobbyGui.xaml.cs
Content.Server/Audio/ContentAudioSystem.cs
Content.Server/GameTicking/GameTicker.Lobby.cs
Content.Server/GameTicking/GameTicker.LobbyMusic.cs [deleted file]
Content.Server/GameTicking/GameTicker.RoundFlow.cs
Content.Server/GameTicking/GameTicker.cs
Content.Shared/Audio/Events/LobbyPlaylistChangedEvent.cs [new file with mode: 0644]
Content.Shared/GameTicking/SharedGameTicker.cs

diff --git a/Content.Client/Audio/BackgroundAudioSystem.cs b/Content.Client/Audio/BackgroundAudioSystem.cs
deleted file mode 100644 (file)
index 702f810..0000000
+++ /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<RoundRestartCleanupEvent>(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 (file)
index 0000000..0fdcc7a
--- /dev/null
@@ -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);
+
+    /// <summary>
+    /// EntityUid of lobby restart sound component.
+    /// </summary>
+    private EntityUid? _lobbyRoundRestartAudioStream;
+
+    /// <summary>
+    /// Shuffled list of soundtrack file-names.
+    /// </summary>
+    private string[]? _lobbyPlaylist;
+
+    /// <summary>
+    /// Short info about lobby soundtrack currently playing. Is null if soundtrack is not playing.
+    /// </summary>
+    private LobbySoundtrackInfo? _lobbySoundtrackInfo;
+
+    private Action<LobbySoundtrackChangedEvent>? _lobbySoundtrackChanged;
+
+    /// <summary>
+    /// Event for subscription on lobby soundtrack changes.
+    /// </summary>
+    public event Action<LobbySoundtrackChangedEvent>? LobbySoundtrackChanged
+    {
+        add
+        {
+            if (value != null)
+            {
+                if (_lobbySoundtrackInfo != null)
+                {
+                    value(new LobbySoundtrackChangedEvent(_lobbySoundtrackInfo.Filename));
+                }
+
+                _lobbySoundtrackChanged += value;
+            }
+        }
+        remove => _lobbySoundtrackChanged -= value;
+    }
+
+    /// <summary>
+    /// Initializes subscriptions that are related to lobby music.
+    /// </summary>
+    private void InitializeLobbyMusic()
+    {
+        Subs.CVar(_configManager, CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
+        Subs.CVar(_configManager, CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
+
+        _stateManager.OnStateChanged += StateManagerOnStateChanged;
+
+        _client.PlayerLeaveServer += OnLeave;
+
+        SubscribeNetworkEvent<LobbyMusicStopEvent>(OnLobbySongStopped);
+        SubscribeNetworkEvent<LobbyPlaylistChangedEvent>(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);
+    }
+
+    /// <summary>
+    /// Re-starts playing lobby music from playlist, last sent from server. if there is currently none - does nothing.
+    /// </summary>
+    private void StartLobbyMusic()
+    {
+        if (_lobbyPlaylist == null || _lobbyPlaylist.Length == 0)
+        {
+            return;
+        }
+
+        StartLobbyMusic(_lobbyPlaylist);
+    }
+
+    /// <summary>
+    /// Starts playing lobby music from playlist. If playlist is empty, or lobby music setting is turned off - does nothing.
+    /// </summary>
+    /// <param name="playlist">Array of soundtrack filenames for lobby playlist.</param>
+    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];
+    }
+
+    /// <summary> Container for lobby soundtrack information. </summary>
+    /// <param name="Filename">Soundtrack filename.</param>
+    /// <param name="NextTrackOn">Time (based on <see cref="IGameTiming.CurTime"/>) when this track is going to finish playing and next track have to be started.</param>
+    /// <param name="MusicStreamEntityUid">
+    /// EntityUid of launched soundtrack (from <see cref="SharedAudioSystem.PlayGlobal(string,Robust.Shared.Player.Filter,bool,System.Nullable{Robust.Shared.Audio.AudioParams})"/>).
+    /// </param>
+    private sealed record LobbySoundtrackInfo(string Filename, TimeSpan NextTrackOn, EntityUid MusicStreamEntityUid);
+}
+
+/// <summary>
+/// Event of changing lobby soundtrack (or stopping lobby music - will pass null for <paramref name="SoundtrackFilename"/> in that case).
+/// Is used by <see cref="ContentAudioSystem.LobbySoundtrackChanged"/> and <see cref="LobbyState.UpdateLobbySoundtrackInfo"/>.
+/// </summary>
+/// <param name="SoundtrackFilename">Filename of newly set soundtrack, or null if soundtrack playback is stopped.</param>
+public sealed record LobbySoundtrackChangedEvent(string? SoundtrackFilename = null);
index 56921ee43016b9566451789ade5b066b17283261..c7fc7bcf73987fcf0a6697a9b132d18e117d2a61 100644 (file)
@@ -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<RoundRestartCleanupEvent>(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<BackgroundAudioSystem>().LobbyMusicStream;
+        var lobbyMusic = _lobbySoundtrackInfo?.MusicStreamEntityUid;
         TryComp(lobbyMusic, out AudioComponent? lobbyMusicComp);
         var oldMusicGain = lobbyMusicComp?.Gain;
 
-        var restartAudio = EntityManager.System<BackgroundAudioSystem>().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);
     }
 
index 25b6802bd73d31add7c8e0f8e555024d459e8100..df709e944461cae21d7bee9d7c0f00e65ab32b3d 100644 (file)
@@ -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<IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>>>? 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).
index 457163a5b5bef94a26256f5f81853b2d1aacdab9..fe31dce062911113ab817228c20ae4613efa2acb 100644 (file)
@@ -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<ChatUIController>();
             _gameTicker = _entityManager.System<ClientGameTicker>();
+            _contentAudioSystem = _entityManager.System<ContentAudioSystem>();
+            _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<AudioResource>(_gameTicker.LobbySong, out var lobbySongResource))
+            else if (
+                ev.SoundtrackFilename != null
+                && _resourceCache.TryGetResource<AudioResource>(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),
index d45a99c16f94660a909411ab0771d9f6d437d351..69867ea90cb4dfb1d3889d2a671f2ed1dbe46005 100644 (file)
@@ -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<OptionsUIController>().ToggleWindow();
         }
index b1db01141befdf3ce4ab9e2f59f28bf27ae2747b..f36d14cbaf037f631cc4b90c360513d942b47bfc 100644 (file)
@@ -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<SoundCollectionPrototype>]
+    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<SoundCollectionPrototype>(LobbyMusicCollection);
+        _lobbyPlaylist = ShuffleLobbyPlaylist();
+
+        SubscribeLocalEvent<RoundEndMessageEvent>(OnRoundEnd);
+        SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
         SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundCleanup);
         SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
         SubscribeLocalEvent<PrototypesReloadedEventArgs>(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;
+    }
 }
index 3a6f742baa4c49975d462454ff31d2fa22743a29..82ef8c6012bc908ddf5dde174cfa4297a3fe8c59 100644 (file)
@@ -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 (file)
index 5a44320..0000000
+++ /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<SoundCollectionPrototype>]
-        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<SoundCollectionPrototype>(LobbyMusicCollection);
-
-            // Now that the collection is set, the lobby music has been initialized and we can choose a random song.
-            _lobbyMusicInitialized = true;
-
-            ChooseRandomLobbySong();
-        }
-
-        /// <summary>
-        ///     Sets the current lobby song, or stops it if null.
-        /// </summary>
-        /// <param name="song">The lobby song to play, or null to stop any lobby songs.</param>
-        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
-        }
-
-        /// <summary>
-        ///     Plays a random song from the LobbyMusic sound collection.
-        /// </summary>
-        public void ChooseRandomLobbySong()
-        {
-            DebugTools.Assert(_lobbyMusicInitialized);
-            SetLobbySong(_robustRandom.Pick(_lobbyMusicCollection.PickFiles).ToString());
-        }
-
-        /// <summary>
-        ///     Stops the current lobby song being played.
-        /// </summary>
-        public void StopLobbySong()
-        {
-            SetLobbySong(null);
-        }
-    }
-}
index 69624ed5b2803a637e55c2d30258b1b94fb2a650..004508ab91668e32855d175bc85fd787aa6c3c5c 100644 (file)
@@ -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;
index 1d07814ecebe633de29833c51548c03b8e55ab22..efda3df0ca19a475a4a7413574d953830f1dff46 100644 (file)
@@ -93,7 +93,6 @@ namespace Content.Server.GameTicking
             InitializeStatusShell();
             InitializeCVars();
             InitializePlayer();
-            InitializeLobbyMusic();
             InitializeLobbyBackground();
             InitializeGamePreset();
             DebugTools.Assert(_prototypeManager.Index<JobPrototype>(FallbackOverflowJob).Name == FallbackOverflowJobName,
diff --git a/Content.Shared/Audio/Events/LobbyPlaylistChangedEvent.cs b/Content.Shared/Audio/Events/LobbyPlaylistChangedEvent.cs
new file mode 100644 (file)
index 0000000..87f4e7d
--- /dev/null
@@ -0,0 +1,29 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Audio.Events;
+
+/// <summary>
+/// Event of changing lobby music playlist (on server).
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class LobbyPlaylistChangedEvent : EntityEventArgs
+{
+    /// <inheritdoc />
+    public LobbyPlaylistChangedEvent(string[] playlist)
+    {
+        Playlist = playlist;
+    }
+
+    /// <summary>
+    /// List of soundtrack filenames for lobby playlist.
+    /// </summary>
+    public string[] Playlist;
+}
+
+/// <summary>
+/// Event of stopping lobby music.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class LobbyMusicStopEvent : EntityEventArgs
+{
+}
index 2677d499c2ff996d86a0fbb0f34f698457e1223e..95da4f4c38d33a3cbf7c8e3220866a1f3e810b90 100644 (file)
@@ -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;
 
         /// <summary>
         /// 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;
         }
     }