From 0c83642c5a2c37871bd1ca9e125c2eb0a96c98c7 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Mon, 29 May 2023 10:44:11 +1000 Subject: [PATCH] Add ambient music (#16829) --- Content.Client/Audio/AmbientSoundSystem.cs | 15 +- Content.Client/Audio/BackgroundAudioSystem.cs | 179 ------------ .../Audio/ContentAudioSystem.AmbientMusic.cs | 262 ++++++++++++++++++ Content.Client/Audio/ContentAudioSystem.cs | 118 +++++++- Content.Client/Options/UI/Tabs/AudioTab.xaml | 21 +- .../Options/UI/Tabs/AudioTab.xaml.cs | 63 +++-- Content.Server/Morgue/MorgueSystem.cs | 1 + Content.Server/Prayer/PrayableComponent.cs | 47 ---- Content.Server/Prayer/PrayerSystem.cs | 6 +- Content.Shared/Audio/AmbientMusicPrototype.cs | 39 +++ Content.Shared/Audio/AmbientSoundComponent.cs | 71 +++-- Content.Shared/CCVar/CCVars.cs | 29 +- .../Morgue/Components/MorgueComponent.cs | 5 +- .../Movement/Systems/SharedMoverController.cs | 4 +- Content.Shared/Prayer/PrayableComponent.cs | 46 +++ Content.Shared/Random/RulesPrototype.cs | 130 +++++++++ Content.Shared/Random/RulesSystem.cs | 207 ++++++++++++++ Resources/Audio/Ambience/ambiatmos.ogg | Bin 0 -> 118293 bytes Resources/Audio/Ambience/ambiatmos2.ogg | Bin 0 -> 64725 bytes Resources/Audio/Ambience/ambicave.ogg | Bin 0 -> 145739 bytes Resources/Audio/Ambience/ambicha1.ogg | Bin 0 -> 102387 bytes Resources/Audio/Ambience/ambicha2.ogg | Bin 0 -> 44743 bytes Resources/Audio/Ambience/ambicha3.ogg | Bin 0 -> 43212 bytes Resources/Audio/Ambience/ambicha4.ogg | Bin 0 -> 62500 bytes Resources/Audio/Ambience/ambidanger.ogg | Bin 0 -> 49366 bytes Resources/Audio/Ambience/ambidanger2.ogg | Bin 0 -> 31413 bytes Resources/Audio/Ambience/ambigen1.ogg | Bin 0 -> 83412 bytes Resources/Audio/Ambience/ambigen10.ogg | Bin 0 -> 38533 bytes Resources/Audio/Ambience/ambigen11.ogg | Bin 0 -> 152721 bytes Resources/Audio/Ambience/ambigen12.ogg | Bin 0 -> 194041 bytes Resources/Audio/Ambience/ambigen13.ogg | Bin 0 -> 84154 bytes Resources/Audio/Ambience/ambigen14.ogg | Bin 0 -> 35707 bytes Resources/Audio/Ambience/ambigen15.ogg | Bin 0 -> 27765 bytes Resources/Audio/Ambience/ambigen2.ogg | Bin 0 -> 83412 bytes Resources/Audio/Ambience/ambigen3.ogg | Bin 0 -> 41042 bytes Resources/Audio/Ambience/ambigen4.ogg | Bin 0 -> 30299 bytes Resources/Audio/Ambience/ambigen5.ogg | Bin 0 -> 28856 bytes Resources/Audio/Ambience/ambigen6.ogg | Bin 0 -> 101163 bytes Resources/Audio/Ambience/ambigen7.ogg | Bin 0 -> 51340 bytes Resources/Audio/Ambience/ambigen8.ogg | Bin 0 -> 41360 bytes Resources/Audio/Ambience/ambigen9.ogg | Bin 0 -> 30800 bytes Resources/Audio/Ambience/ambiholy.ogg | Bin 0 -> 44830 bytes Resources/Audio/Ambience/ambiholy2.ogg | Bin 0 -> 44228 bytes Resources/Audio/Ambience/ambiholy3.ogg | Bin 0 -> 80837 bytes Resources/Audio/Ambience/ambilava1.ogg | Bin 0 -> 553362 bytes Resources/Audio/Ambience/ambilava2.ogg | Bin 0 -> 325546 bytes Resources/Audio/Ambience/ambilava3.ogg | Bin 0 -> 205500 bytes Resources/Audio/Ambience/ambimaint1.ogg | Bin 0 -> 122131 bytes Resources/Audio/Ambience/ambimaint2.ogg | Bin 0 -> 87664 bytes Resources/Audio/Ambience/ambimaint3.ogg | Bin 0 -> 97239 bytes Resources/Audio/Ambience/ambimaint4.ogg | Bin 0 -> 106086 bytes Resources/Audio/Ambience/ambimaint5.ogg | Bin 0 -> 103914 bytes Resources/Audio/Ambience/ambimine.ogg | Bin 0 -> 517089 bytes Resources/Audio/Ambience/ambimo1.ogg | Bin 0 -> 226606 bytes Resources/Audio/Ambience/ambimo2.ogg | Bin 0 -> 50936 bytes Resources/Audio/Ambience/ambimystery.ogg | Bin 0 -> 43808 bytes Resources/Audio/Ambience/ambinice.ogg | Bin 0 -> 153865 bytes Resources/Audio/Ambience/ambiodd.ogg | Bin 0 -> 50952 bytes Resources/Audio/Ambience/ambireebe1.ogg | Bin 0 -> 388473 bytes Resources/Audio/Ambience/ambireebe3.ogg | Bin 0 -> 396573 bytes Resources/Audio/Ambience/ambiruin.ogg | Bin 0 -> 50664 bytes Resources/Audio/Ambience/ambiruin2.ogg | Bin 0 -> 28110 bytes Resources/Audio/Ambience/ambiruin3.ogg | Bin 0 -> 49438 bytes Resources/Audio/Ambience/ambiruin4.ogg | Bin 0 -> 59748 bytes Resources/Audio/Ambience/ambiruin5.ogg | Bin 0 -> 47020 bytes Resources/Audio/Ambience/ambiruin6.ogg | Bin 0 -> 50595 bytes Resources/Audio/Ambience/ambiruin7.ogg | Bin 0 -> 49806 bytes Resources/Audio/Ambience/ambisin1.ogg | Bin 0 -> 270932 bytes Resources/Audio/Ambience/ambisin2.ogg | Bin 0 -> 166272 bytes Resources/Audio/Ambience/ambisin3.ogg | Bin 0 -> 206108 bytes Resources/Audio/Ambience/ambisin4.ogg | Bin 0 -> 152847 bytes Resources/Audio/Ambience/ambitech.ogg | Bin 0 -> 36737 bytes Resources/Audio/Ambience/ambitech2.ogg | Bin 0 -> 192195 bytes Resources/Audio/Ambience/ambitech3.ogg | Bin 0 -> 22941 bytes Resources/Audio/Ambience/attributions.yml | 63 ++++- Resources/Audio/Ambience/maintambience.ogg | Bin 0 -> 335808 bytes Resources/Audio/Voice/Misc/attributions.yml | 8 + Resources/Audio/Voice/Misc/lowHiss1.ogg | Bin 0 -> 48410 bytes Resources/Audio/Voice/Misc/lowHiss2.ogg | Bin 0 -> 33538 bytes Resources/Audio/Voice/Misc/lowHiss3.ogg | Bin 0 -> 39289 bytes Resources/Audio/Voice/Misc/lowHiss4.ogg | Bin 0 -> 45436 bytes .../en-US/escape-menu/ui/options-menu.ftl | 3 +- .../Prototypes/SoundCollections/ambience.yml | 11 - Resources/Prototypes/audio.yml | 262 ++++++++++++++++++ 84 files changed, 1252 insertions(+), 338 deletions(-) create mode 100644 Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs delete mode 100644 Content.Server/Prayer/PrayableComponent.cs create mode 100644 Content.Shared/Audio/AmbientMusicPrototype.cs rename {Content.Server => Content.Shared}/Morgue/Components/MorgueComponent.cs (85%) create mode 100644 Content.Shared/Prayer/PrayableComponent.cs create mode 100644 Content.Shared/Random/RulesPrototype.cs create mode 100644 Content.Shared/Random/RulesSystem.cs create mode 100644 Resources/Audio/Ambience/ambiatmos.ogg create mode 100644 Resources/Audio/Ambience/ambiatmos2.ogg create mode 100644 Resources/Audio/Ambience/ambicave.ogg create mode 100644 Resources/Audio/Ambience/ambicha1.ogg create mode 100644 Resources/Audio/Ambience/ambicha2.ogg create mode 100644 Resources/Audio/Ambience/ambicha3.ogg create mode 100644 Resources/Audio/Ambience/ambicha4.ogg create mode 100644 Resources/Audio/Ambience/ambidanger.ogg create mode 100644 Resources/Audio/Ambience/ambidanger2.ogg create mode 100644 Resources/Audio/Ambience/ambigen1.ogg create mode 100644 Resources/Audio/Ambience/ambigen10.ogg create mode 100644 Resources/Audio/Ambience/ambigen11.ogg create mode 100644 Resources/Audio/Ambience/ambigen12.ogg create mode 100644 Resources/Audio/Ambience/ambigen13.ogg create mode 100644 Resources/Audio/Ambience/ambigen14.ogg create mode 100644 Resources/Audio/Ambience/ambigen15.ogg create mode 100644 Resources/Audio/Ambience/ambigen2.ogg create mode 100644 Resources/Audio/Ambience/ambigen3.ogg create mode 100644 Resources/Audio/Ambience/ambigen4.ogg create mode 100644 Resources/Audio/Ambience/ambigen5.ogg create mode 100644 Resources/Audio/Ambience/ambigen6.ogg create mode 100644 Resources/Audio/Ambience/ambigen7.ogg create mode 100644 Resources/Audio/Ambience/ambigen8.ogg create mode 100644 Resources/Audio/Ambience/ambigen9.ogg create mode 100644 Resources/Audio/Ambience/ambiholy.ogg create mode 100644 Resources/Audio/Ambience/ambiholy2.ogg create mode 100644 Resources/Audio/Ambience/ambiholy3.ogg create mode 100644 Resources/Audio/Ambience/ambilava1.ogg create mode 100644 Resources/Audio/Ambience/ambilava2.ogg create mode 100644 Resources/Audio/Ambience/ambilava3.ogg create mode 100644 Resources/Audio/Ambience/ambimaint1.ogg create mode 100644 Resources/Audio/Ambience/ambimaint2.ogg create mode 100644 Resources/Audio/Ambience/ambimaint3.ogg create mode 100644 Resources/Audio/Ambience/ambimaint4.ogg create mode 100644 Resources/Audio/Ambience/ambimaint5.ogg create mode 100644 Resources/Audio/Ambience/ambimine.ogg create mode 100644 Resources/Audio/Ambience/ambimo1.ogg create mode 100644 Resources/Audio/Ambience/ambimo2.ogg create mode 100644 Resources/Audio/Ambience/ambimystery.ogg create mode 100644 Resources/Audio/Ambience/ambinice.ogg create mode 100644 Resources/Audio/Ambience/ambiodd.ogg create mode 100644 Resources/Audio/Ambience/ambireebe1.ogg create mode 100644 Resources/Audio/Ambience/ambireebe3.ogg create mode 100644 Resources/Audio/Ambience/ambiruin.ogg create mode 100644 Resources/Audio/Ambience/ambiruin2.ogg create mode 100644 Resources/Audio/Ambience/ambiruin3.ogg create mode 100644 Resources/Audio/Ambience/ambiruin4.ogg create mode 100644 Resources/Audio/Ambience/ambiruin5.ogg create mode 100644 Resources/Audio/Ambience/ambiruin6.ogg create mode 100644 Resources/Audio/Ambience/ambiruin7.ogg create mode 100644 Resources/Audio/Ambience/ambisin1.ogg create mode 100644 Resources/Audio/Ambience/ambisin2.ogg create mode 100644 Resources/Audio/Ambience/ambisin3.ogg create mode 100644 Resources/Audio/Ambience/ambisin4.ogg create mode 100644 Resources/Audio/Ambience/ambitech.ogg create mode 100644 Resources/Audio/Ambience/ambitech2.ogg create mode 100644 Resources/Audio/Ambience/ambitech3.ogg create mode 100644 Resources/Audio/Ambience/maintambience.ogg create mode 100644 Resources/Audio/Voice/Misc/attributions.yml create mode 100644 Resources/Audio/Voice/Misc/lowHiss1.ogg create mode 100644 Resources/Audio/Voice/Misc/lowHiss2.ogg create mode 100644 Resources/Audio/Voice/Misc/lowHiss3.ogg create mode 100644 Resources/Audio/Voice/Misc/lowHiss4.ogg delete mode 100644 Resources/Prototypes/SoundCollections/ambience.yml create mode 100644 Resources/Prototypes/audio.yml diff --git a/Content.Client/Audio/AmbientSoundSystem.cs b/Content.Client/Audio/AmbientSoundSystem.cs index e357e63c25..d241df4f6f 100644 --- a/Content.Client/Audio/AmbientSoundSystem.cs +++ b/Content.Client/Audio/AmbientSoundSystem.cs @@ -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; diff --git a/Content.Client/Audio/BackgroundAudioSystem.cs b/Content.Client/Audio/BackgroundAudioSystem.cs index 50639a6d05..100ee7458e 100644 --- a/Content.Client/Audio/BackgroundAudioSystem.cs +++ b/Content.Client/Audio/BackgroundAudioSystem.cs @@ -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; - /// - /// What is currently playing. - /// - private SoundCollectionPrototype? _playingCollection; - - /// - /// What the ambience has been set to. - /// - private SoundCollectionPrototype? _currentCollection; - private CancellationTokenSource _timerCancelTokenSource = new(); - - private SoundCollectionPrototype _spaceAmbience = default!; - private SoundCollectionPrototype _stationAmbience = default!; - public override void Initialize() { base.Initialize(); - _stationAmbience = _prototypeManager.Index("StationAmbienceBase"); - _spaceAmbience = _prototypeManager.Index("SpaceAmbienceBase"); - _currentCollection = _stationAmbience; - - // TODO: Ideally audio loading streamed better / we have more robust audio but this is quite annoying - var cache = IoCManager.Resolve(); - - foreach (var audio in _spaceAmbience.PickFiles) - { - cache.GetResource(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(OnPlayerAttached); - SubscribeLocalEvent(EntParentChanged); - SubscribeLocalEvent(OnPlayerDetached); _stateManager.OnStateChanged += StateManagerOnStateChanged; @@ -85,28 +37,12 @@ public sealed class BackgroundAudioSystem : EntitySystem _gameTicker.LobbyStatusUpdated += LobbySongReceived; } - private void OnPlayerAttached(PlayerAttachedEvent ev) - { - if (!TryComp(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) diff --git a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs new file mode 100644 index 0000000000..34bc83bdfb --- /dev/null +++ b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs @@ -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; + + /// + /// If we find a better ambient music proto can we interrupt this one. + /// + private bool _interruptable; + + /// + /// 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 + /// + private readonly Dictionary> _ambientSounds = new(); + + private ISawmill _sawmill = default!; + + private void InitializeAmbientMusic() + { + // TODO: Shitty preload + foreach (var audio in _proto.Index("AmbienceSpace").PickFiles) + { + _resource.GetResource(audio.ToString()); + } + + _configManager.OnValueChanged(CCVars.AmbientMusicVolume, AmbienceCVarChanged, true); + _sawmill = IoCManager.Resolve().GetSawmill("audio.ambience"); + + // Reset audio + _nextAudio = TimeSpan.MaxValue; + + SetupAmbientSounds(); + _proto.PrototypesReloaded += OnProtoReload; + _state.OnStateChanged += OnStateChange; + // On round end summary OR lobby cut audio. + SubscribeNetworkEvent(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()) + { + 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 tracks, ResPath? lastPlayed) + { + DebugTools.Assert(tracks.Count == 0); + + switch (sound) + { + case SoundCollectionSpecifier collection: + if (collection.Collection == null) + break; + + var slothCud = _proto.Index(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(_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().ToList(); + ambiences.Sort((x, y) => y.Priority.CompareTo(x.Priority)); + + foreach (var amb in ambiences) + { + if (!_rules.IsTrue(player.Value, _proto.Index(amb.Rules))) + continue; + + return amb; + } + + _sawmill.Warning($"Unable to find fallback ambience track"); + return null; + } +} diff --git a/Content.Client/Audio/ContentAudioSystem.cs b/Content.Client/Audio/ContentAudioSystem.cs index 7d19acf6ef..cd6a88f01d 100644 --- a/Content.Client/Audio/ContentAudioSystem.cs +++ b/Content.Client/Audio/ContentAudioSystem.cs @@ -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 _fadingOut = new(); + // Need volume change per tick + target volume. + private readonly Dictionary _fadingIn = new(); + + private readonly List _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 } diff --git a/Content.Client/Options/UI/Tabs/AudioTab.xaml b/Content.Client/Options/UI/Tabs/AudioTab.xaml index ccf4d16ede..528b0ac136 100644 --- a/Content.Client/Options/UI/Tabs/AudioTab.xaml +++ b/Content.Client/Options/UI/Tabs/AudioTab.xaml @@ -27,7 +27,7 @@ @@ -35,12 +35,25 @@