Add ambient music (#16829)
This commit is contained in:
@@ -11,6 +11,7 @@ using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Audio
|
||||
{
|
||||
@@ -106,7 +107,19 @@ namespace Content.Client.Audio
|
||||
_playingCount.Remove(sound.Sound);
|
||||
}
|
||||
|
||||
private void SetAmbienceVolume(float value) => _ambienceVolume = value;
|
||||
private void SetAmbienceVolume(float value)
|
||||
{
|
||||
_ambienceVolume = value;
|
||||
|
||||
foreach (var (comp, values) in _playingSounds)
|
||||
{
|
||||
if (values.Stream == null)
|
||||
continue;
|
||||
|
||||
var stream = (AudioSystem.PlayingStream) values.Stream;
|
||||
stream.Volume = _params.Volume + comp.Volume + _ambienceVolume;
|
||||
}
|
||||
}
|
||||
private void SetCooldown(float value) => _cooldown = value;
|
||||
private void SetAmbientCount(int value) => _maxAmbientCount = value;
|
||||
private void SetAmbientRange(float value) => _maxAmbientRange = value;
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
using System.Threading;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Shared.CCVar;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Client.Audio;
|
||||
|
||||
@@ -26,57 +17,18 @@ public sealed class BackgroundAudioSystem : EntitySystem
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly ClientGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IPlayerManager _playMan = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly AudioParams _ambientParams = new(-10f, 1, "Master", 0, 0, 0, true, 0f);
|
||||
private readonly AudioParams _lobbyParams = new(-5f, 1, "Master", 0, 0, 0, true, 0f);
|
||||
|
||||
private IPlayingAudioStream? _ambientStream;
|
||||
private IPlayingAudioStream? _lobbyStream;
|
||||
|
||||
/// <summary>
|
||||
/// What is currently playing.
|
||||
/// </summary>
|
||||
private SoundCollectionPrototype? _playingCollection;
|
||||
|
||||
/// <summary>
|
||||
/// What the ambience has been set to.
|
||||
/// </summary>
|
||||
private SoundCollectionPrototype? _currentCollection;
|
||||
private CancellationTokenSource _timerCancelTokenSource = new();
|
||||
|
||||
private SoundCollectionPrototype _spaceAmbience = default!;
|
||||
private SoundCollectionPrototype _stationAmbience = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_stationAmbience = _prototypeManager.Index<SoundCollectionPrototype>("StationAmbienceBase");
|
||||
_spaceAmbience = _prototypeManager.Index<SoundCollectionPrototype>("SpaceAmbienceBase");
|
||||
_currentCollection = _stationAmbience;
|
||||
|
||||
// TODO: Ideally audio loading streamed better / we have more robust audio but this is quite annoying
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
|
||||
foreach (var audio in _spaceAmbience.PickFiles)
|
||||
{
|
||||
cache.GetResource<AudioResource>(audio.ToString());
|
||||
}
|
||||
|
||||
_configManager.OnValueChanged(CCVars.AmbienceVolume, AmbienceCVarChanged);
|
||||
_configManager.OnValueChanged(CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
|
||||
_configManager.OnValueChanged(CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
|
||||
_configManager.OnValueChanged(CCVars.StationAmbienceEnabled, StationAmbienceCVarChanged);
|
||||
_configManager.OnValueChanged(CCVars.SpaceAmbienceEnabled, SpaceAmbienceCVarChanged);
|
||||
|
||||
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
|
||||
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
_stateManager.OnStateChanged += StateManagerOnStateChanged;
|
||||
|
||||
@@ -85,28 +37,12 @@ public sealed class BackgroundAudioSystem : EntitySystem
|
||||
_gameTicker.LobbyStatusUpdated += LobbySongReceived;
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(PlayerAttachedEvent ev)
|
||||
{
|
||||
if (!TryComp<TransformComponent>(ev.Entity, out var xform))
|
||||
return;
|
||||
|
||||
CheckAmbience(xform);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(PlayerDetachedEvent ev)
|
||||
{
|
||||
EndAmbience();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_configManager.UnsubValueChanged(CCVars.AmbienceVolume, AmbienceCVarChanged);
|
||||
_configManager.UnsubValueChanged(CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
|
||||
_configManager.UnsubValueChanged(CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
|
||||
_configManager.UnsubValueChanged(CCVars.StationAmbienceEnabled, StationAmbienceCVarChanged);
|
||||
_configManager.UnsubValueChanged(CCVars.SpaceAmbienceEnabled, SpaceAmbienceCVarChanged);
|
||||
|
||||
_stateManager.OnStateChanged -= StateManagerOnStateChanged;
|
||||
|
||||
@@ -114,61 +50,17 @@ public sealed class BackgroundAudioSystem : EntitySystem
|
||||
|
||||
_gameTicker.LobbyStatusUpdated -= LobbySongReceived;
|
||||
|
||||
EndAmbience();
|
||||
EndLobbyMusic();
|
||||
}
|
||||
|
||||
private void CheckAmbience(TransformComponent xform)
|
||||
{
|
||||
if (xform.GridUid != null)
|
||||
ChangeAmbience(_stationAmbience);
|
||||
else
|
||||
ChangeAmbience(_spaceAmbience);
|
||||
}
|
||||
|
||||
private void EntParentChanged(ref EntParentChangedMessage message)
|
||||
{
|
||||
if (_playMan.LocalPlayer is null
|
||||
|| _playMan.LocalPlayer.ControlledEntity != message.Entity
|
||||
|| !(_timing.IsFirstTimePredicted || _timing.ApplyingState))
|
||||
return;
|
||||
|
||||
// Check if we traversed to grid.
|
||||
CheckAmbience(message.Transform);
|
||||
}
|
||||
|
||||
private void ChangeAmbience(SoundCollectionPrototype newAmbience)
|
||||
{
|
||||
if (_currentCollection == newAmbience)
|
||||
return;
|
||||
|
||||
_timerCancelTokenSource.Cancel();
|
||||
_currentCollection = newAmbience;
|
||||
_timerCancelTokenSource = new CancellationTokenSource();
|
||||
Timer.Spawn(1500, () =>
|
||||
{
|
||||
// If we traverse a few times then don't interrupt an existing song.
|
||||
// If we are not in gameplay, don't call StartAmbience because of player movement
|
||||
if (_playingCollection == _currentCollection || _stateManager.CurrentState is not GameplayState)
|
||||
return;
|
||||
StartAmbience();
|
||||
}, _timerCancelTokenSource.Token);
|
||||
}
|
||||
|
||||
private void StateManagerOnStateChanged(StateChangedEventArgs args)
|
||||
{
|
||||
switch (args.NewState)
|
||||
{
|
||||
case LobbyState:
|
||||
EndAmbience();
|
||||
StartLobbyMusic();
|
||||
break;
|
||||
case GameplayState:
|
||||
EndLobbyMusic();
|
||||
StartAmbience();
|
||||
break;
|
||||
default:
|
||||
EndAmbience();
|
||||
EndLobbyMusic();
|
||||
break;
|
||||
}
|
||||
@@ -176,80 +68,9 @@ public sealed class BackgroundAudioSystem : EntitySystem
|
||||
|
||||
private void OnLeave(object? sender, PlayerEventArgs args)
|
||||
{
|
||||
EndAmbience();
|
||||
EndLobbyMusic();
|
||||
}
|
||||
|
||||
private void AmbienceCVarChanged(float volume)
|
||||
{
|
||||
if (_stateManager.CurrentState is GameplayState)
|
||||
{
|
||||
StartAmbience();
|
||||
}
|
||||
else
|
||||
{
|
||||
EndAmbience();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartAmbience()
|
||||
{
|
||||
EndAmbience();
|
||||
if (_currentCollection == null || !CanPlayCollection(_currentCollection))
|
||||
return;
|
||||
_playingCollection = _currentCollection;
|
||||
var file = _robustRandom.Pick(_currentCollection.PickFiles).ToString();
|
||||
_ambientStream = _audio.PlayGlobal(file, Filter.Local(), false,
|
||||
_ambientParams.WithVolume(_ambientParams.Volume + _configManager.GetCVar(CCVars.AmbienceVolume)));
|
||||
}
|
||||
|
||||
private void EndAmbience()
|
||||
{
|
||||
_playingCollection = null;
|
||||
_ambientStream?.Stop();
|
||||
_ambientStream = null;
|
||||
}
|
||||
|
||||
private bool CanPlayCollection(SoundCollectionPrototype collection)
|
||||
{
|
||||
if (collection.ID == _spaceAmbience.ID)
|
||||
return _configManager.GetCVar(CCVars.SpaceAmbienceEnabled);
|
||||
if (collection.ID == _stationAmbience.ID)
|
||||
return _configManager.GetCVar(CCVars.StationAmbienceEnabled);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void StationAmbienceCVarChanged(bool enabled)
|
||||
{
|
||||
if (_currentCollection == null)
|
||||
return;
|
||||
|
||||
if (enabled && _stateManager.CurrentState is GameplayState && _currentCollection.ID == _stationAmbience.ID)
|
||||
{
|
||||
StartAmbience();
|
||||
}
|
||||
else if (_currentCollection.ID == _stationAmbience.ID)
|
||||
{
|
||||
EndAmbience();
|
||||
}
|
||||
}
|
||||
|
||||
private void SpaceAmbienceCVarChanged(bool enabled)
|
||||
{
|
||||
if (_currentCollection == null)
|
||||
return;
|
||||
|
||||
if (enabled && _stateManager.CurrentState is GameplayState && _currentCollection.ID == _spaceAmbience.ID)
|
||||
{
|
||||
StartAmbience();
|
||||
}
|
||||
else if (_currentCollection.ID == _spaceAmbience.ID)
|
||||
{
|
||||
EndAmbience();
|
||||
}
|
||||
}
|
||||
|
||||
private void LobbyMusicVolumeCVarChanged(float volume)
|
||||
{
|
||||
if (_stateManager.CurrentState is LobbyState)
|
||||
|
||||
262
Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
Normal file
262
Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Random;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Audio;
|
||||
|
||||
public sealed partial class ContentAudioSystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IStateManager _state = default!;
|
||||
[Dependency] private readonly RulesSystem _rules = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
private readonly TimeSpan _minAmbienceTime = TimeSpan.FromSeconds(30);
|
||||
private readonly TimeSpan _maxAmbienceTime = TimeSpan.FromSeconds(60);
|
||||
|
||||
private const float AmbientMusicFadeTime = 10f;
|
||||
private static float _volumeSlider;
|
||||
|
||||
// Don't need to worry about this being serializable or pauseable as it doesn't affect the sim.
|
||||
private TimeSpan _nextAudio;
|
||||
|
||||
private AudioSystem.PlayingStream? _ambientMusicStream;
|
||||
private AmbientMusicPrototype? _musicProto;
|
||||
|
||||
/// <summary>
|
||||
/// If we find a better ambient music proto can we interrupt this one.
|
||||
/// </summary>
|
||||
private bool _interruptable;
|
||||
|
||||
/// <summary>
|
||||
/// Track what ambient sounds we've played. This is so they all get played an even
|
||||
/// number of times.
|
||||
/// When we get to the end of the list we'll re-shuffle
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, List<ResPath>> _ambientSounds = new();
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private void InitializeAmbientMusic()
|
||||
{
|
||||
// TODO: Shitty preload
|
||||
foreach (var audio in _proto.Index<SoundCollectionPrototype>("AmbienceSpace").PickFiles)
|
||||
{
|
||||
_resource.GetResource<AudioResource>(audio.ToString());
|
||||
}
|
||||
|
||||
_configManager.OnValueChanged(CCVars.AmbientMusicVolume, AmbienceCVarChanged, true);
|
||||
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("audio.ambience");
|
||||
|
||||
// Reset audio
|
||||
_nextAudio = TimeSpan.MaxValue;
|
||||
|
||||
SetupAmbientSounds();
|
||||
_proto.PrototypesReloaded += OnProtoReload;
|
||||
_state.OnStateChanged += OnStateChange;
|
||||
// On round end summary OR lobby cut audio.
|
||||
SubscribeNetworkEvent<RoundEndMessageEvent>(OnRoundEndMessage);
|
||||
}
|
||||
|
||||
private void AmbienceCVarChanged(float obj)
|
||||
{
|
||||
_volumeSlider = obj;
|
||||
|
||||
if (_ambientMusicStream != null && _musicProto != null)
|
||||
{
|
||||
_ambientMusicStream.Volume = _musicProto.Sound.Params.Volume + _volumeSlider;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShutdownAmbientMusic()
|
||||
{
|
||||
_configManager.UnsubValueChanged(CCVars.AmbientMusicVolume, AmbienceCVarChanged);
|
||||
_proto.PrototypesReloaded -= OnProtoReload;
|
||||
_state.OnStateChanged -= OnStateChange;
|
||||
_ambientMusicStream?.Stop();
|
||||
}
|
||||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (!obj.ByType.ContainsKey(typeof(AmbientMusicPrototype)) &&
|
||||
!obj.ByType.ContainsKey(typeof(RulesPrototype)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ambientSounds.Clear();
|
||||
SetupAmbientSounds();
|
||||
}
|
||||
|
||||
private void OnStateChange(StateChangedEventArgs obj)
|
||||
{
|
||||
if (obj.NewState is not GameplayState)
|
||||
return;
|
||||
|
||||
// If they go to game then reset the ambience timer.
|
||||
_nextAudio = _timing.CurTime + _random.Next(_minAmbienceTime, _maxAmbienceTime);
|
||||
}
|
||||
|
||||
private void SetupAmbientSounds()
|
||||
{
|
||||
foreach (var ambience in _proto.EnumeratePrototypes<AmbientMusicPrototype>())
|
||||
{
|
||||
var tracks = _ambientSounds.GetOrNew(ambience.ID);
|
||||
RefreshTracks(ambience.Sound, tracks, null);
|
||||
_random.Shuffle(tracks);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRoundEndMessage(RoundEndMessageEvent ev)
|
||||
{
|
||||
// If scoreboard shows then just stop the music
|
||||
_ambientMusicStream?.Stop();
|
||||
_ambientMusicStream = null;
|
||||
_nextAudio = TimeSpan.FromMinutes(3);
|
||||
}
|
||||
|
||||
private void RefreshTracks(SoundSpecifier sound, List<ResPath> tracks, ResPath? lastPlayed)
|
||||
{
|
||||
DebugTools.Assert(tracks.Count == 0);
|
||||
|
||||
switch (sound)
|
||||
{
|
||||
case SoundCollectionSpecifier collection:
|
||||
if (collection.Collection == null)
|
||||
break;
|
||||
|
||||
var slothCud = _proto.Index<SoundCollectionPrototype>(collection.Collection);
|
||||
tracks.AddRange(slothCud.PickFiles);
|
||||
break;
|
||||
case SoundPathSpecifier path:
|
||||
tracks.Add(path.Path);
|
||||
break;
|
||||
}
|
||||
|
||||
// Just so the same track doesn't play twice
|
||||
if (tracks.Count > 1 && tracks[^1] == lastPlayed)
|
||||
{
|
||||
(tracks[0], tracks[^1]) = (tracks[^1], tracks[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAmbientMusic()
|
||||
{
|
||||
// Update still runs in lobby so just ignore it.
|
||||
if (_state.CurrentState is not GameplayState)
|
||||
{
|
||||
FadeOut(_ambientMusicStream);
|
||||
_ambientMusicStream = null;
|
||||
_musicProto = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var isDone = _ambientMusicStream?.Done;
|
||||
|
||||
if (_interruptable)
|
||||
{
|
||||
var player = _player.LocalPlayer?.ControlledEntity;
|
||||
|
||||
if (player == null || _musicProto == null || !_rules.IsTrue(player.Value, _proto.Index<RulesPrototype>(_musicProto.Rules)))
|
||||
{
|
||||
FadeOut(_ambientMusicStream, AmbientMusicFadeTime);
|
||||
_musicProto = null;
|
||||
_interruptable = false;
|
||||
isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Still running existing ambience
|
||||
if (isDone == false)
|
||||
return;
|
||||
|
||||
// If ambience finished reset the CD (this also means if we have long ambience it won't clip)
|
||||
if (isDone == true)
|
||||
{
|
||||
// Also don't need to worry about rounding here as it doesn't affect the sim
|
||||
_nextAudio = _timing.CurTime + _random.Next(_minAmbienceTime, _maxAmbienceTime);
|
||||
}
|
||||
|
||||
_ambientMusicStream = null;
|
||||
|
||||
if (_nextAudio > _timing.CurTime)
|
||||
return;
|
||||
|
||||
_musicProto = GetAmbience();
|
||||
|
||||
if (_musicProto == null)
|
||||
{
|
||||
_interruptable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_interruptable = _musicProto.Interruptable;
|
||||
var tracks = _ambientSounds[_musicProto.ID];
|
||||
|
||||
var track = tracks[^1];
|
||||
tracks.RemoveAt(tracks.Count - 1);
|
||||
|
||||
var strim = _audio.PlayGlobal(
|
||||
track.ToString(),
|
||||
Filter.Local(),
|
||||
false,
|
||||
AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider));
|
||||
|
||||
if (strim != null)
|
||||
{
|
||||
_ambientMusicStream = (AudioSystem.PlayingStream) strim;
|
||||
|
||||
if (_musicProto.FadeIn)
|
||||
{
|
||||
FadeIn(_ambientMusicStream, AmbientMusicFadeTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the list
|
||||
if (tracks.Count == 0)
|
||||
{
|
||||
RefreshTracks(_musicProto.Sound, tracks, track);
|
||||
}
|
||||
}
|
||||
|
||||
private AmbientMusicPrototype? GetAmbience()
|
||||
{
|
||||
var player = _player.LocalPlayer?.ControlledEntity;
|
||||
|
||||
if (player == null)
|
||||
return null;
|
||||
|
||||
var ambiences = _proto.EnumeratePrototypes<AmbientMusicPrototype>().ToList();
|
||||
ambiences.Sort((x, y) => y.Priority.CompareTo(x.Priority));
|
||||
|
||||
foreach (var amb in ambiences)
|
||||
{
|
||||
if (!_rules.IsTrue(player.Value, _proto.Index<RulesPrototype>(amb.Rules)))
|
||||
continue;
|
||||
|
||||
return amb;
|
||||
}
|
||||
|
||||
_sawmill.Warning($"Unable to find fallback ambience track");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,124 @@
|
||||
using Content.Shared.Audio;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Audio;
|
||||
|
||||
public sealed class ContentAudioSystem : SharedContentAudioSystem
|
||||
public sealed partial class ContentAudioSystem : SharedContentAudioSystem
|
||||
{
|
||||
// Need how much volume to change per tick and just remove it when it drops below "0"
|
||||
private readonly Dictionary<AudioSystem.PlayingStream, float> _fadingOut = new();
|
||||
|
||||
// Need volume change per tick + target volume.
|
||||
private readonly Dictionary<AudioSystem.PlayingStream, (float VolumeChange, float TargetVolume)> _fadingIn = new();
|
||||
|
||||
private readonly List<AudioSystem.PlayingStream> _fadeToRemove = new();
|
||||
|
||||
private const float MinVolume = -32f;
|
||||
private const float DefaultDuration = 2f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdatesOutsidePrediction = true;
|
||||
InitializeAmbientMusic();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
ShutdownAmbientMusic();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
UpdateAmbientMusic();
|
||||
UpdateFades(frameTime);
|
||||
}
|
||||
|
||||
#region Fades
|
||||
|
||||
public void FadeOut(AudioSystem.PlayingStream? stream, float duration = DefaultDuration)
|
||||
{
|
||||
if (stream == null || duration <= 0f)
|
||||
return;
|
||||
|
||||
// Just in case
|
||||
// TODO: Maybe handle the removals by making it seamless?
|
||||
_fadingIn.Remove(stream);
|
||||
var diff = stream.Volume - MinVolume;
|
||||
_fadingOut.Add(stream, diff / duration);
|
||||
}
|
||||
|
||||
public void FadeIn(AudioSystem.PlayingStream? stream, float duration = DefaultDuration)
|
||||
{
|
||||
if (stream == null || duration <= 0f || stream.Volume < MinVolume)
|
||||
return;
|
||||
|
||||
_fadingOut.Remove(stream);
|
||||
var curVolume = stream.Volume;
|
||||
var change = (curVolume - MinVolume) / duration;
|
||||
_fadingIn.Add(stream, (change, stream.Volume));
|
||||
stream.Volume = MinVolume;
|
||||
}
|
||||
|
||||
private void UpdateFades(float frameTime)
|
||||
{
|
||||
_fadeToRemove.Clear();
|
||||
|
||||
foreach (var (stream, change) in _fadingOut)
|
||||
{
|
||||
// Cancelled elsewhere
|
||||
if (stream.Done)
|
||||
{
|
||||
_fadeToRemove.Add(stream);
|
||||
continue;
|
||||
}
|
||||
|
||||
var volume = stream.Volume - change * frameTime;
|
||||
stream.Volume = MathF.Max(MinVolume, volume);
|
||||
|
||||
if (stream.Volume.Equals(MinVolume))
|
||||
{
|
||||
stream.Stop();
|
||||
_fadeToRemove.Add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var stream in _fadeToRemove)
|
||||
{
|
||||
_fadingOut.Remove(stream);
|
||||
}
|
||||
|
||||
_fadeToRemove.Clear();
|
||||
|
||||
foreach (var (stream, (change, target)) in _fadingIn)
|
||||
{
|
||||
// Cancelled elsewhere
|
||||
if (stream.Done)
|
||||
{
|
||||
_fadeToRemove.Add(stream);
|
||||
continue;
|
||||
}
|
||||
|
||||
var volume = stream.Volume + change * frameTime;
|
||||
stream.Volume = MathF.Min(target, volume);
|
||||
|
||||
if (stream.Volume.Equals(target))
|
||||
{
|
||||
_fadeToRemove.Add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var stream in _fadeToRemove)
|
||||
{
|
||||
_fadingIn.Remove(stream);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="MidiVolumeSlider"
|
||||
MinValue="0"
|
||||
MaxValue="200"
|
||||
MaxValue="100"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
@@ -35,12 +35,25 @@
|
||||
<Label Name="MidiVolumeLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
|
||||
<Label Text="{Loc 'ui-options-ambient-music-volume'}" HorizontalExpand="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="AmbientMusicVolumeSlider"
|
||||
MinValue="0"
|
||||
MaxValue="100"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Label Name="AmbientMusicVolumeLabel" MinSize="48 0" Align="Right" />
|
||||
<Control MinSize="4 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
|
||||
<Label Text="{Loc 'ui-options-ambience-volume'}" HorizontalExpand="True" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="AmbienceVolumeSlider"
|
||||
MinValue="0"
|
||||
MaxValue="300"
|
||||
MaxValue="100"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
@@ -53,7 +66,7 @@
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="LobbyVolumeSlider"
|
||||
MinValue="0"
|
||||
MaxValue="200"
|
||||
MaxValue="100"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
Rounded="True" />
|
||||
@@ -79,8 +92,6 @@
|
||||
<CheckBox Name="RestartSoundsCheckBox" Text="{Loc 'ui-options-restart-sounds'}" />
|
||||
<CheckBox Name="EventMusicCheckBox" Text="{Loc 'ui-options-event-music'}" />
|
||||
<CheckBox Name="AdminSoundsCheckBox" Text="{Loc 'ui-options-admin-sounds'}" />
|
||||
<CheckBox Name="StationAmbienceCheckBox" Text="{Loc 'ui-options-station-ambience'}" />
|
||||
<CheckBox Name="SpaceAmbienceCheckBox" Text="{Loc 'ui-options-space-ambience'}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<controls:StripeBack HasBottomEdge="False" HasMargins="False">
|
||||
|
||||
@@ -25,13 +25,12 @@ namespace Content.Client.Options.UI.Tabs
|
||||
RestartSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.RestartSoundsEnabled);
|
||||
EventMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.EventMusicEnabled);
|
||||
AdminSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.AdminSoundsEnabled);
|
||||
StationAmbienceCheckBox.Pressed = _cfg.GetCVar(CCVars.StationAmbienceEnabled);
|
||||
SpaceAmbienceCheckBox.Pressed = _cfg.GetCVar(CCVars.SpaceAmbienceEnabled);
|
||||
|
||||
ApplyButton.OnPressed += OnApplyButtonPressed;
|
||||
ResetButton.OnPressed += OnResetButtonPressed;
|
||||
MasterVolumeSlider.OnValueChanged += OnMasterVolumeSliderChanged;
|
||||
MidiVolumeSlider.OnValueChanged += OnMidiVolumeSliderChanged;
|
||||
AmbientMusicVolumeSlider.OnValueChanged += OnAmbientMusicVolumeSliderChanged;
|
||||
AmbienceVolumeSlider.OnValueChanged += OnAmbienceVolumeSliderChanged;
|
||||
AmbienceSoundsSlider.OnValueChanged += OnAmbienceSoundsSliderChanged;
|
||||
LobbyVolumeSlider.OnValueChanged += OnLobbyVolumeSliderChanged;
|
||||
@@ -39,8 +38,6 @@ namespace Content.Client.Options.UI.Tabs
|
||||
RestartSoundsCheckBox.OnToggled += OnRestartSoundsCheckToggled;
|
||||
EventMusicCheckBox.OnToggled += OnEventMusicCheckToggled;
|
||||
AdminSoundsCheckBox.OnToggled += OnAdminSoundsCheckToggled;
|
||||
StationAmbienceCheckBox.OnToggled += OnStationAmbienceCheckToggled;
|
||||
SpaceAmbienceCheckBox.OnToggled += OnSpaceAmbienceCheckToggled;
|
||||
|
||||
AmbienceSoundsSlider.MinValue = _cfg.GetCVar(CCVars.MinMaxAmbientSourcesConfigured);
|
||||
AmbienceSoundsSlider.MaxValue = _cfg.GetCVar(CCVars.MaxMaxAmbientSourcesConfigured);
|
||||
@@ -54,6 +51,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
ResetButton.OnPressed -= OnResetButtonPressed;
|
||||
MasterVolumeSlider.OnValueChanged -= OnMasterVolumeSliderChanged;
|
||||
MidiVolumeSlider.OnValueChanged -= OnMidiVolumeSliderChanged;
|
||||
AmbientMusicVolumeSlider.OnValueChanged -= OnAmbientMusicVolumeSliderChanged;
|
||||
AmbienceVolumeSlider.OnValueChanged -= OnAmbienceVolumeSliderChanged;
|
||||
LobbyVolumeSlider.OnValueChanged -= OnLobbyVolumeSliderChanged;
|
||||
base.Dispose(disposing);
|
||||
@@ -64,6 +62,11 @@ namespace Content.Client.Options.UI.Tabs
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnAmbientMusicVolumeSliderChanged(Range obj)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnAmbienceVolumeSliderChanged(Range obj)
|
||||
{
|
||||
UpdateChanges();
|
||||
@@ -103,29 +106,21 @@ namespace Content.Client.Options.UI.Tabs
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnStationAmbienceCheckToggled(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnSpaceAmbienceCheckToggled(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
_cfg.SetCVar(CVars.AudioMasterVolume, MasterVolumeSlider.Value / 100);
|
||||
_cfg.SetCVar(CVars.MidiVolume, LV100ToDB(MidiVolumeSlider.Value));
|
||||
_cfg.SetCVar(CCVars.AmbienceVolume, LV100ToDB(AmbienceVolumeSlider.Value));
|
||||
// Want the CVar updated values to have the multiplier applied
|
||||
// For the UI we just display 0-100 still elsewhere
|
||||
_cfg.SetCVar(CVars.MidiVolume, LV100ToDB(MidiVolumeSlider.Value, CCVars.MidiMultiplier));
|
||||
_cfg.SetCVar(CCVars.AmbienceVolume, LV100ToDB(AmbienceVolumeSlider.Value, CCVars.AmbienceMultiplier));
|
||||
_cfg.SetCVar(CCVars.AmbientMusicVolume, LV100ToDB(AmbientMusicVolumeSlider.Value, CCVars.AmbientMusicMultiplier));
|
||||
|
||||
_cfg.SetCVar(CCVars.LobbyMusicVolume, LV100ToDB(LobbyVolumeSlider.Value));
|
||||
_cfg.SetCVar(CCVars.MaxAmbientSources, (int)AmbienceSoundsSlider.Value);
|
||||
_cfg.SetCVar(CCVars.LobbyMusicEnabled, LobbyMusicCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.RestartSoundsEnabled, RestartSoundsCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.EventMusicEnabled, EventMusicCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.AdminSoundsEnabled, AdminSoundsCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.StationAmbienceEnabled, StationAmbienceCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.SpaceAmbienceEnabled, SpaceAmbienceCheckBox.Pressed);
|
||||
_cfg.SaveToFile();
|
||||
UpdateChanges();
|
||||
}
|
||||
@@ -138,30 +133,32 @@ namespace Content.Client.Options.UI.Tabs
|
||||
private void Reset()
|
||||
{
|
||||
MasterVolumeSlider.Value = _cfg.GetCVar(CVars.AudioMasterVolume) * 100;
|
||||
MidiVolumeSlider.Value = DBToLV100(_cfg.GetCVar(CVars.MidiVolume));
|
||||
AmbienceVolumeSlider.Value = DBToLV100(_cfg.GetCVar(CCVars.AmbienceVolume));
|
||||
MidiVolumeSlider.Value = DBToLV100(_cfg.GetCVar(CVars.MidiVolume), CCVars.MidiMultiplier);
|
||||
AmbienceVolumeSlider.Value = DBToLV100(_cfg.GetCVar(CCVars.AmbienceVolume), CCVars.AmbienceMultiplier);
|
||||
AmbientMusicVolumeSlider.Value =
|
||||
DBToLV100(_cfg.GetCVar(CCVars.AmbientMusicVolume), CCVars.AmbientMusicMultiplier);
|
||||
LobbyVolumeSlider.Value = DBToLV100(_cfg.GetCVar(CCVars.LobbyMusicVolume));
|
||||
AmbienceSoundsSlider.Value = _cfg.GetCVar(CCVars.MaxAmbientSources);
|
||||
LobbyMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.LobbyMusicEnabled);
|
||||
RestartSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.RestartSoundsEnabled);
|
||||
EventMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.EventMusicEnabled);
|
||||
AdminSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.AdminSoundsEnabled);
|
||||
StationAmbienceCheckBox.Pressed = _cfg.GetCVar(CCVars.StationAmbienceEnabled);
|
||||
SpaceAmbienceCheckBox.Pressed = _cfg.GetCVar(CCVars.SpaceAmbienceEnabled);
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
// Note: Rather than moving these functions somewhere, instead switch MidiManager to using linear units rather than dB
|
||||
// Do be sure to rename the setting though
|
||||
private float DBToLV100(float db)
|
||||
private float DBToLV100(float db, float multiplier = 1f)
|
||||
{
|
||||
return (MathF.Pow(10, (db / 10)) * 100);
|
||||
var weh = (float) (Math.Pow(10, db / 10) * 100 / multiplier);
|
||||
return weh;
|
||||
}
|
||||
|
||||
private float LV100ToDB(float lv100)
|
||||
private float LV100ToDB(float lv100, float multiplier = 1f)
|
||||
{
|
||||
// Saving negative infinity doesn't work, so use -10000000 instead (MidiManager does it)
|
||||
return MathF.Max(-10000000, MathF.Log(lv100 / 100, 10) * 10);
|
||||
var weh = MathF.Max(-10000000, (float) (Math.Log(lv100 * multiplier / 100, 10) * 10));
|
||||
return weh;
|
||||
}
|
||||
|
||||
private void UpdateChanges()
|
||||
@@ -169,9 +166,11 @@ namespace Content.Client.Options.UI.Tabs
|
||||
var isMasterVolumeSame =
|
||||
Math.Abs(MasterVolumeSlider.Value - _cfg.GetCVar(CVars.AudioMasterVolume) * 100) < 0.01f;
|
||||
var isMidiVolumeSame =
|
||||
Math.Abs(MidiVolumeSlider.Value - DBToLV100(_cfg.GetCVar(CVars.MidiVolume))) < 0.01f;
|
||||
Math.Abs(MidiVolumeSlider.Value - DBToLV100(_cfg.GetCVar(CVars.MidiVolume), CCVars.MidiMultiplier)) < 0.01f;
|
||||
var isAmbientVolumeSame =
|
||||
Math.Abs(AmbienceVolumeSlider.Value - DBToLV100(_cfg.GetCVar(CCVars.AmbienceVolume))) < 0.01f;
|
||||
Math.Abs(AmbienceVolumeSlider.Value - DBToLV100(_cfg.GetCVar(CCVars.AmbienceVolume), CCVars.AmbienceMultiplier)) < 0.01f;
|
||||
var isAmbientMusicVolumeSame =
|
||||
Math.Abs(AmbientMusicVolumeSlider.Value - DBToLV100(_cfg.GetCVar(CCVars.AmbientMusicVolume), CCVars.AmbientMusicMultiplier)) < 0.01f;
|
||||
var isLobbyVolumeSame =
|
||||
Math.Abs(LobbyVolumeSlider.Value - DBToLV100(_cfg.GetCVar(CCVars.LobbyMusicVolume))) < 0.01f;
|
||||
var isAmbientSoundsSame = (int)AmbienceSoundsSlider.Value == _cfg.GetCVar(CCVars.MaxAmbientSources);
|
||||
@@ -179,16 +178,16 @@ namespace Content.Client.Options.UI.Tabs
|
||||
var isRestartSoundsSame = RestartSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.RestartSoundsEnabled);
|
||||
var isEventSame = EventMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.EventMusicEnabled);
|
||||
var isAdminSoundsSame = AdminSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.AdminSoundsEnabled);
|
||||
var isStationAmbienceSame = StationAmbienceCheckBox.Pressed == _cfg.GetCVar(CCVars.StationAmbienceEnabled);
|
||||
var isSpaceAmbienceSame = SpaceAmbienceCheckBox.Pressed == _cfg.GetCVar(CCVars.SpaceAmbienceEnabled);
|
||||
var isEverythingSame = isMasterVolumeSame && isMidiVolumeSame && isAmbientVolumeSame && isAmbientSoundsSame && isLobbySame && isRestartSoundsSame && isEventSame
|
||||
&& isAdminSoundsSame && isStationAmbienceSame && isSpaceAmbienceSame && isLobbyVolumeSame;
|
||||
var isEverythingSame = isMasterVolumeSame && isMidiVolumeSame && isAmbientVolumeSame && isAmbientMusicVolumeSame && isAmbientSoundsSame && isLobbySame && isRestartSoundsSame && isEventSame
|
||||
&& isAdminSoundsSame && isLobbyVolumeSame;
|
||||
ApplyButton.Disabled = isEverythingSame;
|
||||
ResetButton.Disabled = isEverythingSame;
|
||||
MasterVolumeLabel.Text =
|
||||
Loc.GetString("ui-options-volume-percent", ("volume", MasterVolumeSlider.Value / 100));
|
||||
MidiVolumeLabel.Text =
|
||||
Loc.GetString("ui-options-volume-percent", ("volume", MidiVolumeSlider.Value / 100));
|
||||
AmbientMusicVolumeLabel.Text =
|
||||
Loc.GetString("ui-options-volume-percent", ("volume", AmbientMusicVolumeSlider.Value / 100));
|
||||
AmbienceVolumeLabel.Text =
|
||||
Loc.GetString("ui-options-volume-percent", ("volume", AmbienceVolumeSlider.Value / 100));
|
||||
LobbyVolumeLabel.Text =
|
||||
|
||||
Reference in New Issue
Block a user