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:
283
Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs
Normal file
283
Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs
Normal 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);
|
||||
Reference in New Issue
Block a user