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 <pa.pecherskij@interfax.ru>
This commit is contained in:
Fildrance
2024-03-02 23:40:04 +03:00
committed by GitHub
parent 4f7facbd73
commit 4c87dcd3cb
13 changed files with 402 additions and 270 deletions

View File

@@ -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;
}
}

View File

@@ -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);

View 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);
}

View 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).

View 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),

View 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();
}