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 @@
+
+
+
+
+
+
+
+
@@ -53,7 +66,7 @@
@@ -79,8 +92,6 @@
-
-
diff --git a/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs b/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
index 1729bfc706..5875c4a33a 100644
--- a/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
@@ -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 =
diff --git a/Content.Server/Morgue/MorgueSystem.cs b/Content.Server/Morgue/MorgueSystem.cs
index 2226ca98bd..df9cd35b14 100644
--- a/Content.Server/Morgue/MorgueSystem.cs
+++ b/Content.Server/Morgue/MorgueSystem.cs
@@ -3,6 +3,7 @@ using Content.Server.Storage.Components;
using Content.Shared.Body.Components;
using Content.Shared.Examine;
using Content.Shared.Morgue;
+using Content.Shared.Morgue.Components;
using Robust.Server.GameObjects;
namespace Content.Server.Morgue;
diff --git a/Content.Server/Prayer/PrayableComponent.cs b/Content.Server/Prayer/PrayableComponent.cs
deleted file mode 100644
index e458e59fd5..0000000000
--- a/Content.Server/Prayer/PrayableComponent.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using Robust.Shared.Utility;
-
-namespace Content.Server.Prayer
-{
- ///
- /// Allows an entity to be prayed on in the context menu
- ///
- [RegisterComponent]
- public sealed class PrayableComponent : Component
- {
- ///
- /// If bible users are only allowed to use this prayable entity
- ///
- [DataField("bibleUserOnly")]
- [ViewVariables(VVAccess.ReadWrite)]
- public bool BibleUserOnly;
-
- ///
- /// Message given to user to notify them a message was sent
- ///
- [DataField("sentMessage")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string SentMessage = "prayer-popup-notify-pray-sent";
-
- ///
- /// Prefix used in the notification to admins
- ///
- [DataField("notifiactionPrefix")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string NotifiactionPrefix = "prayer-chat-notify-pray";
-
- ///
- /// Used in window title and context menu
- ///
- [DataField("verb")]
- [ViewVariables(VVAccess.ReadOnly)]
- public string Verb = "prayer-verbs-pray";
-
- ///
- /// Context menu image
- ///
- [DataField("verbImage")]
- [ViewVariables(VVAccess.ReadOnly)]
- public SpriteSpecifier? VerbImage = new SpriteSpecifier.Texture(new ("/Textures/Interface/pray.svg.png"));
- }
-}
-
diff --git a/Content.Server/Prayer/PrayerSystem.cs b/Content.Server/Prayer/PrayerSystem.cs
index 02328d2fae..2056586a41 100644
--- a/Content.Server/Prayer/PrayerSystem.cs
+++ b/Content.Server/Prayer/PrayerSystem.cs
@@ -8,6 +8,7 @@ using Content.Shared.Popups;
using Robust.Server.Player;
using Robust.Shared.Player;
using Content.Shared.Chat;
+using Content.Shared.Prayer;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
@@ -100,10 +101,9 @@ public sealed class PrayerSystem : EntitySystem
if (sender.AttachedEntity == null)
return;
-
_popupSystem.PopupEntity(Loc.GetString(comp.SentMessage), sender.AttachedEntity.Value, sender, PopupType.Medium);
- _chatManager.SendAdminAnnouncement($"{Loc.GetString(comp.NotifiactionPrefix)} <{sender.Name}>: {message}");
- _adminLogger.Add(LogType.AdminMessage, LogImpact.Low, $"{ToPrettyString(sender.AttachedEntity.Value):player} sent prayer ({Loc.GetString(comp.NotifiactionPrefix)}): {message}");
+ _chatManager.SendAdminAnnouncement($"{Loc.GetString(comp.NotificationPrefix)} <{sender.Name}>: {message}");
+ _adminLogger.Add(LogType.AdminMessage, LogImpact.Low, $"{ToPrettyString(sender.AttachedEntity.Value):player} sent prayer ({Loc.GetString(comp.NotificationPrefix)}): {message}");
}
}
diff --git a/Content.Shared/Audio/AmbientMusicPrototype.cs b/Content.Shared/Audio/AmbientMusicPrototype.cs
new file mode 100644
index 0000000000..ad6f67b175
--- /dev/null
+++ b/Content.Shared/Audio/AmbientMusicPrototype.cs
@@ -0,0 +1,39 @@
+using Content.Shared.Random;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared.Audio;
+
+///
+/// Attaches a rules prototype to sound files to play ambience.
+///
+[Prototype("ambientMusic")]
+public sealed class AmbientMusicPrototype : IPrototype
+{
+ [IdDataField] public string ID { get; } = string.Empty;
+
+ ///
+ /// Traditionally you'd prioritise most rules to least as priority but in our case we'll just be explicit.
+ ///
+ [ViewVariables(VVAccess.ReadWrite), DataField("priority")]
+ public int Priority = 0;
+
+ ///
+ /// Can we interrupt this ambience for a better prototype if possible?
+ ///
+ [ViewVariables(VVAccess.ReadWrite), DataField("interruptable")]
+ public bool Interruptable = false;
+
+ ///
+ /// Do we fade-in. Useful for songs.
+ ///
+ [ViewVariables(VVAccess.ReadWrite), DataField("fadeIn")]
+ public bool FadeIn;
+
+ [ViewVariables(VVAccess.ReadWrite), DataField("sound", required: true)]
+ public SoundSpecifier Sound = default!;
+
+ [ViewVariables(VVAccess.ReadWrite), DataField("rules", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))]
+ public string Rules = string.Empty;
+}
diff --git a/Content.Shared/Audio/AmbientSoundComponent.cs b/Content.Shared/Audio/AmbientSoundComponent.cs
index 53867b5976..81b7a75ed8 100644
--- a/Content.Shared/Audio/AmbientSoundComponent.cs
+++ b/Content.Shared/Audio/AmbientSoundComponent.cs
@@ -4,48 +4,47 @@ using Robust.Shared.GameStates;
using Robust.Shared.Physics;
using Robust.Shared.Serialization;
-namespace Content.Shared.Audio
+namespace Content.Shared.Audio;
+
+[RegisterComponent]
+[NetworkedComponent]
+[Access(typeof(SharedAmbientSoundSystem))]
+public sealed class AmbientSoundComponent : Component, IComponentTreeEntry
{
- [RegisterComponent]
- [NetworkedComponent]
- [Access(typeof(SharedAmbientSoundSystem))]
- public sealed class AmbientSoundComponent : Component, IComponentTreeEntry
- {
- [DataField("enabled")]
- [ViewVariables(VVAccess.ReadWrite)] // only for map editing
- public bool Enabled { get; set; } = true;
+ [DataField("enabled")]
+ [ViewVariables(VVAccess.ReadWrite)] // only for map editing
+ public bool Enabled { get; set; } = true;
- [DataField("sound", required: true), ViewVariables(VVAccess.ReadWrite)] // only for map editing
- public SoundSpecifier Sound = default!;
+ [DataField("sound", required: true), ViewVariables(VVAccess.ReadWrite)] // only for map editing
+ public SoundSpecifier Sound = default!;
- ///
- /// How far away this ambient sound can potentially be heard.
- ///
- [ViewVariables(VVAccess.ReadWrite)] // only for map editing
- [DataField("range")]
- public float Range = 2f;
+ ///
+ /// How far away this ambient sound can potentially be heard.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)] // only for map editing
+ [DataField("range")]
+ public float Range = 2f;
- ///
- /// Applies this volume to the sound being played.
- ///
- [ViewVariables(VVAccess.ReadWrite)] // only for map editing
- [DataField("volume")]
- public float Volume = -10f;
+ ///
+ /// Applies this volume to the sound being played.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)] // only for map editing
+ [DataField("volume")]
+ public float Volume = -10f;
- public EntityUid? TreeUid { get; set; }
+ public EntityUid? TreeUid { get; set; }
- public DynamicTree>? Tree { get; set; }
+ public DynamicTree>? Tree { get; set; }
- public bool AddToTree => Enabled;
+ public bool AddToTree => Enabled;
- public bool TreeUpdateQueued { get; set; }
- }
-
- [Serializable, NetSerializable]
- public sealed class AmbientSoundComponentState : ComponentState
- {
- public bool Enabled { get; init; }
- public float Range { get; init; }
- public float Volume { get; init; }
- }
+ public bool TreeUpdateQueued { get; set; }
+}
+
+[Serializable, NetSerializable]
+public sealed class AmbientSoundComponentState : ComponentState
+{
+ public bool Enabled { get; init; }
+ public float Range { get; init; }
+ public float Volume { get; init; }
}
diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs
index eef0e16de9..a3c64b415a 100644
--- a/Content.Shared/CCVar/CCVars.cs
+++ b/Content.Shared/CCVar/CCVars.cs
@@ -33,10 +33,6 @@ namespace Content.Shared.CCVar
/*
* Ambience
*/
- //TODO: This is so that this compiles, yell at me if this is still in
- public static readonly CVarDef AmbienceBasicEnabled =
- CVarDef.Create("ambiance.basic_enabled", true, CVar.CLIENTONLY | CVar.ARCHIVE);
-
///
/// How long we'll wait until re-sampling nearby objects for ambience. Should be pretty fast, but doesn't have to match the tick rate.
@@ -74,24 +70,25 @@ namespace Content.Shared.CCVar
public static readonly CVarDef AmbienceVolume =
CVarDef.Create("ambience.volume", 0.0f, CVar.ARCHIVE | CVar.CLIENTONLY);
+ // Midi is on engine so deal
+ public const float MidiMultiplier = 3f;
+
+ public const float AmbienceMultiplier = 2f;
+
+ ///
+ /// Ambience music volume.
+ ///
+ public static readonly CVarDef AmbientMusicVolume =
+ CVarDef.Create("ambience.music_volume", 0.0f, CVar.ARCHIVE | CVar.CLIENTONLY);
+
+ public const float AmbientMusicMultiplier = 2f;
+
///
/// Lobby / round end music volume.
///
public static readonly CVarDef LobbyMusicVolume =
CVarDef.Create("ambience.lobby_music_volume", 0.0f, CVar.ARCHIVE | CVar.CLIENTONLY);
- ///
- /// Whether to play the station ambience (humming) sound
- ///
- public static readonly CVarDef StationAmbienceEnabled =
- CVarDef.Create("ambience.station_ambience", true, CVar.ARCHIVE | CVar.CLIENTONLY);
-
- ///
- /// Whether to play the space ambience
- ///
- public static readonly CVarDef SpaceAmbienceEnabled =
- CVarDef.Create("ambience.space_ambience", true, CVar.ARCHIVE | CVar.CLIENTONLY);
-
/*
* Status
*/
diff --git a/Content.Server/Morgue/Components/MorgueComponent.cs b/Content.Shared/Morgue/Components/MorgueComponent.cs
similarity index 85%
rename from Content.Server/Morgue/Components/MorgueComponent.cs
rename to Content.Shared/Morgue/Components/MorgueComponent.cs
index e1503dbb28..1b21d174d6 100644
--- a/Content.Server/Morgue/Components/MorgueComponent.cs
+++ b/Content.Shared/Morgue/Components/MorgueComponent.cs
@@ -1,8 +1,9 @@
using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
-namespace Content.Server.Morgue.Components;
+namespace Content.Shared.Morgue.Components;
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent]
public sealed class MorgueComponent : Component
{
///
diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs
index b9e4bafb1a..0c44e83850 100644
--- a/Content.Shared/Movement/Systems/SharedMoverController.cs
+++ b/Content.Shared/Movement/Systems/SharedMoverController.cs
@@ -253,10 +253,10 @@ namespace Content.Shared.Movement.Systems
if (!weightless && mobMoverQuery.TryGetComponent(uid, out var mobMover) &&
TryGetSound(weightless, uid, mover, mobMover, xform, out var sound))
{
- var soundModifier = mover.Sprinting ? 1.5f : 1f;
+ var soundModifier = mover.Sprinting ? 3.5f : 1.5f;
var audioParams = sound.Params
- .WithVolume(sound.Params.Volume * soundModifier)
+ .WithVolume(sound.Params.Volume + soundModifier)
.WithVariation(sound.Params.Variation ?? FootstepVariation);
// If we're a relay target then predict the sound for all relays.
diff --git a/Content.Shared/Prayer/PrayableComponent.cs b/Content.Shared/Prayer/PrayableComponent.cs
new file mode 100644
index 0000000000..04ded16f79
--- /dev/null
+++ b/Content.Shared/Prayer/PrayableComponent.cs
@@ -0,0 +1,46 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Prayer;
+
+///
+/// Allows an entity to be prayed on in the context menu
+///
+[RegisterComponent, NetworkedComponent]
+public sealed class PrayableComponent : Component
+{
+ ///
+ /// If bible users are only allowed to use this prayable entity
+ ///
+ [DataField("bibleUserOnly")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool BibleUserOnly;
+
+ ///
+ /// Message given to user to notify them a message was sent
+ ///
+ [DataField("sentMessage")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string SentMessage = "prayer-popup-notify-pray-sent";
+
+ ///
+ /// Prefix used in the notification to admins
+ ///
+ [DataField("notifiactionPrefix")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string NotificationPrefix = "prayer-chat-notify-pray";
+
+ ///
+ /// Used in window title and context menu
+ ///
+ [DataField("verb")]
+ [ViewVariables(VVAccess.ReadOnly)]
+ public string Verb = "prayer-verbs-pray";
+
+ ///
+ /// Context menu image
+ ///
+ [DataField("verbImage")]
+ [ViewVariables(VVAccess.ReadOnly)]
+ public SpriteSpecifier? VerbImage = new SpriteSpecifier.Texture(new ("/Textures/Interface/pray.svg.png"));
+}
\ No newline at end of file
diff --git a/Content.Shared/Random/RulesPrototype.cs b/Content.Shared/Random/RulesPrototype.cs
new file mode 100644
index 0000000000..a785447bec
--- /dev/null
+++ b/Content.Shared/Random/RulesPrototype.cs
@@ -0,0 +1,130 @@
+using Content.Shared.Access;
+using Content.Shared.Maps;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Shared.Random;
+
+///
+/// Rules-based item selection. Can be used for any sort of conditional selection
+/// Every single condition needs to be true for this to be selected.
+/// e.g. "choose maintenance audio if 90% of tiles nearby are maintenance tiles"
+///
+[Prototype("rules")]
+public sealed class RulesPrototype : IPrototype
+{
+ [IdDataField] public string ID { get; } = string.Empty;
+
+ [DataField("rules", required: true)]
+ public List Rules = new();
+}
+
+[ImplicitDataDefinitionForInheritors]
+public abstract class RulesRule
+{
+
+}
+
+///
+/// Returns true if the attached entity is in space.
+///
+public sealed class InSpaceRule : RulesRule
+{
+
+}
+
+///
+/// Checks for entities matching the whitelist in range.
+/// This is more expensive than so prefer that!
+///
+public sealed class NearbyEntitiesRule : RulesRule
+{
+ ///
+ /// How many of the entity need to be nearby.
+ ///
+ [DataField("count")]
+ public int Count = 1;
+
+ [DataField("whitelist", required: true)]
+ public EntityWhitelist Whitelist = new();
+
+ [DataField("range")]
+ public float Range = 10f;
+}
+
+public sealed class NearbyTilesPercentRule : RulesRule
+{
+ [DataField("percent", required: true)]
+ public float Percent;
+
+ [DataField("tiles", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer))]
+ public List Tiles = new();
+
+ [DataField("range")]
+ public float Range = 10f;
+}
+
+///
+/// Always returns true. Used for fallbacks.
+///
+public sealed class AlwaysTrueRule : RulesRule
+{
+
+}
+
+///
+/// Returns true if on a grid or in range of one.
+///
+public sealed class GridInRangeRule : RulesRule
+{
+ [DataField("range")]
+ public float Range = 10f;
+
+ [DataField("inverted")]
+ public bool Inverted = false;
+}
+
+///
+/// Returns true if griduid and mapuid match (AKA on 'planet').
+///
+public sealed class OnMapGridRule : RulesRule
+{
+
+}
+
+///
+/// Checks for an entity nearby with the specified access.
+///
+public sealed class NearbyAccessRule : RulesRule
+{
+ // This exists because of doorelectronics contained inside doors.
+ ///
+ /// Does the access entity need to be anchored.
+ ///
+ [DataField("anchored")]
+ public bool Anchored = true;
+
+ ///
+ /// Count of entities that need to be nearby.
+ ///
+ [DataField("count")]
+ public int Count = 1;
+
+ [DataField("access", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public List Access = new();
+
+ [DataField("range")]
+ public float Range = 10f;
+}
+
+public sealed class NearbyComponentsRule : RulesRule
+{
+ [DataField("count")] public int Count;
+
+ [DataField("components", required: true)]
+ public ComponentRegistry Components = default!;
+
+ [DataField("range")]
+ public float Range = 10f;
+}
diff --git a/Content.Shared/Random/RulesSystem.cs b/Content.Shared/Random/RulesSystem.cs
new file mode 100644
index 0000000000..5fb2de28c1
--- /dev/null
+++ b/Content.Shared/Random/RulesSystem.cs
@@ -0,0 +1,207 @@
+using Content.Shared.Access.Components;
+using Content.Shared.Access.Systems;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+
+namespace Content.Shared.Random;
+
+public sealed class RulesSystem : EntitySystem
+{
+ [Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly ITileDefinitionManager _tileDef = default!;
+ [Dependency] private readonly AccessReaderSystem _reader = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ public bool IsTrue(EntityUid uid, RulesPrototype rules)
+ {
+ foreach (var rule in rules.Rules)
+ {
+ switch (rule)
+ {
+ case AlwaysTrueRule:
+ break;
+ case GridInRangeRule griddy:
+ {
+ if (!TryComp(uid, out var xform))
+ {
+ return false;
+ }
+
+ if (xform.GridUid != null)
+ {
+ return !griddy.Inverted;
+ }
+
+ var worldPos = _transform.GetWorldPosition(xform);
+
+ foreach (var _ in _mapManager.FindGridsIntersecting(
+ xform.MapID,
+ new Box2(worldPos - griddy.Range, worldPos + griddy.Range)))
+ {
+ return !griddy.Inverted;
+ }
+
+ break;
+ }
+ case InSpaceRule:
+ {
+ if (!TryComp(uid, out var xform) ||
+ xform.GridUid != null)
+ {
+ return false;
+ }
+
+ break;
+ }
+ case NearbyAccessRule access:
+ {
+ var xformQuery = GetEntityQuery();
+
+ if (!xformQuery.TryGetComponent(uid, out var xform) ||
+ xform.MapUid == null)
+ {
+ return false;
+ }
+
+ var found = false;
+ var worldPos = _transform.GetWorldPosition(xform, xformQuery);
+ var count = 0;
+
+ // TODO: Update this when we get the callback version
+ foreach (var comp in _lookup.GetComponentsInRange(xform.MapID,
+ worldPos, access.Range))
+ {
+ if (access.Anchored && !xformQuery.GetComponent(comp.Owner).Anchored ||
+ !_reader.AreAccessTagsAllowed(access.Access, comp))
+ {
+ continue;
+ }
+
+ count++;
+
+ if (count < access.Count)
+ continue;
+
+ found = true;
+ break;
+ }
+
+ if (!found)
+ return false;
+
+ break;
+ }
+ case NearbyComponentsRule nearbyComps:
+ {
+ if (!TryComp(uid, out var xform) ||
+ xform.MapUid == null)
+ {
+ return false;
+ }
+
+ var found = false;
+ var worldPos = _transform.GetWorldPosition(xform);
+ var count = 0;
+
+ foreach (var comp in nearbyComps.Components.Values)
+ {
+ // TODO: Update this when we get the callback version
+ foreach (var _ in _lookup.GetComponentsInRange(comp.Component.GetType(), xform.MapID,
+ worldPos, nearbyComps.Range))
+ {
+ count++;
+
+ if (count >= nearbyComps.Count)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (found)
+ break;
+ }
+
+ if (!found)
+ return false;
+
+ break;
+ }
+ case NearbyEntitiesRule entity:
+ {
+ if (!TryComp(uid, out var xform) ||
+ xform.MapUid == null)
+ {
+ return false;
+ }
+
+ var found = false;
+ var worldPos = _transform.GetWorldPosition(xform);
+ var count = 0;
+
+ foreach (var ent in _lookup.GetEntitiesInRange(xform.MapID, worldPos, entity.Range))
+ {
+ if (!entity.Whitelist.IsValid(ent, EntityManager))
+ continue;
+
+ count++;
+
+ if (count < entity.Count)
+ continue;
+
+ found = true;
+ break;
+ }
+
+ if (!found)
+ return false;
+
+ break;
+ }
+ case NearbyTilesPercentRule tiles:
+ {
+ if (!TryComp(uid, out var xform) ||
+ !TryComp(xform.GridUid, out var grid))
+ {
+ return false;
+ }
+
+ var tileCount = 0;
+ var matchingTileCount = 0;
+
+ foreach (var tile in grid.GetTilesIntersecting(new Circle(_transform.GetWorldPosition(xform),
+ tiles.Range)))
+ {
+ tileCount++;
+
+ if (!tiles.Tiles.Contains(_tileDef[tile.Tile.TypeId].ID))
+ continue;
+
+ matchingTileCount++;
+ }
+
+ if (matchingTileCount / (float) tileCount < tiles.Percent)
+ return false;
+
+ break;
+ }
+ case OnMapGridRule:
+ {
+ if (!TryComp(uid, out var xform) ||
+ xform.GridUid != xform.MapUid ||
+ xform.MapUid == null)
+ {
+ return false;
+ }
+
+ break;
+ }
+ default:
+ throw new NotImplementedException();
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/Resources/Audio/Ambience/ambiatmos.ogg b/Resources/Audio/Ambience/ambiatmos.ogg
new file mode 100644
index 0000000000..c832c677d5
Binary files /dev/null and b/Resources/Audio/Ambience/ambiatmos.ogg differ
diff --git a/Resources/Audio/Ambience/ambiatmos2.ogg b/Resources/Audio/Ambience/ambiatmos2.ogg
new file mode 100644
index 0000000000..9651049c25
Binary files /dev/null and b/Resources/Audio/Ambience/ambiatmos2.ogg differ
diff --git a/Resources/Audio/Ambience/ambicave.ogg b/Resources/Audio/Ambience/ambicave.ogg
new file mode 100644
index 0000000000..d78dc46a4c
Binary files /dev/null and b/Resources/Audio/Ambience/ambicave.ogg differ
diff --git a/Resources/Audio/Ambience/ambicha1.ogg b/Resources/Audio/Ambience/ambicha1.ogg
new file mode 100644
index 0000000000..4055a831b5
Binary files /dev/null and b/Resources/Audio/Ambience/ambicha1.ogg differ
diff --git a/Resources/Audio/Ambience/ambicha2.ogg b/Resources/Audio/Ambience/ambicha2.ogg
new file mode 100644
index 0000000000..c95f16af08
Binary files /dev/null and b/Resources/Audio/Ambience/ambicha2.ogg differ
diff --git a/Resources/Audio/Ambience/ambicha3.ogg b/Resources/Audio/Ambience/ambicha3.ogg
new file mode 100644
index 0000000000..6297f7df1c
Binary files /dev/null and b/Resources/Audio/Ambience/ambicha3.ogg differ
diff --git a/Resources/Audio/Ambience/ambicha4.ogg b/Resources/Audio/Ambience/ambicha4.ogg
new file mode 100644
index 0000000000..1139fa7797
Binary files /dev/null and b/Resources/Audio/Ambience/ambicha4.ogg differ
diff --git a/Resources/Audio/Ambience/ambidanger.ogg b/Resources/Audio/Ambience/ambidanger.ogg
new file mode 100644
index 0000000000..265b51f2c9
Binary files /dev/null and b/Resources/Audio/Ambience/ambidanger.ogg differ
diff --git a/Resources/Audio/Ambience/ambidanger2.ogg b/Resources/Audio/Ambience/ambidanger2.ogg
new file mode 100644
index 0000000000..761c63a480
Binary files /dev/null and b/Resources/Audio/Ambience/ambidanger2.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen1.ogg b/Resources/Audio/Ambience/ambigen1.ogg
new file mode 100644
index 0000000000..6a5a2d27c9
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen1.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen10.ogg b/Resources/Audio/Ambience/ambigen10.ogg
new file mode 100644
index 0000000000..2a8177f274
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen10.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen11.ogg b/Resources/Audio/Ambience/ambigen11.ogg
new file mode 100644
index 0000000000..604a013a5e
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen11.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen12.ogg b/Resources/Audio/Ambience/ambigen12.ogg
new file mode 100644
index 0000000000..522aec4afe
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen12.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen13.ogg b/Resources/Audio/Ambience/ambigen13.ogg
new file mode 100644
index 0000000000..0ae24a9a68
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen13.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen14.ogg b/Resources/Audio/Ambience/ambigen14.ogg
new file mode 100644
index 0000000000..30171814ad
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen14.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen15.ogg b/Resources/Audio/Ambience/ambigen15.ogg
new file mode 100644
index 0000000000..63afd437a6
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen15.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen2.ogg b/Resources/Audio/Ambience/ambigen2.ogg
new file mode 100644
index 0000000000..6a5a2d27c9
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen2.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen3.ogg b/Resources/Audio/Ambience/ambigen3.ogg
new file mode 100644
index 0000000000..6e7372811d
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen3.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen4.ogg b/Resources/Audio/Ambience/ambigen4.ogg
new file mode 100644
index 0000000000..bf7237cc70
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen4.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen5.ogg b/Resources/Audio/Ambience/ambigen5.ogg
new file mode 100644
index 0000000000..90cc999b32
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen5.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen6.ogg b/Resources/Audio/Ambience/ambigen6.ogg
new file mode 100644
index 0000000000..ae366e1359
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen6.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen7.ogg b/Resources/Audio/Ambience/ambigen7.ogg
new file mode 100644
index 0000000000..be3cf1b703
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen7.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen8.ogg b/Resources/Audio/Ambience/ambigen8.ogg
new file mode 100644
index 0000000000..9562178a2b
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen8.ogg differ
diff --git a/Resources/Audio/Ambience/ambigen9.ogg b/Resources/Audio/Ambience/ambigen9.ogg
new file mode 100644
index 0000000000..4214afd9b8
Binary files /dev/null and b/Resources/Audio/Ambience/ambigen9.ogg differ
diff --git a/Resources/Audio/Ambience/ambiholy.ogg b/Resources/Audio/Ambience/ambiholy.ogg
new file mode 100644
index 0000000000..cd53dc2789
Binary files /dev/null and b/Resources/Audio/Ambience/ambiholy.ogg differ
diff --git a/Resources/Audio/Ambience/ambiholy2.ogg b/Resources/Audio/Ambience/ambiholy2.ogg
new file mode 100644
index 0000000000..4532dd0a81
Binary files /dev/null and b/Resources/Audio/Ambience/ambiholy2.ogg differ
diff --git a/Resources/Audio/Ambience/ambiholy3.ogg b/Resources/Audio/Ambience/ambiholy3.ogg
new file mode 100644
index 0000000000..93f7a897e1
Binary files /dev/null and b/Resources/Audio/Ambience/ambiholy3.ogg differ
diff --git a/Resources/Audio/Ambience/ambilava1.ogg b/Resources/Audio/Ambience/ambilava1.ogg
new file mode 100644
index 0000000000..b552965faa
Binary files /dev/null and b/Resources/Audio/Ambience/ambilava1.ogg differ
diff --git a/Resources/Audio/Ambience/ambilava2.ogg b/Resources/Audio/Ambience/ambilava2.ogg
new file mode 100644
index 0000000000..12e496670d
Binary files /dev/null and b/Resources/Audio/Ambience/ambilava2.ogg differ
diff --git a/Resources/Audio/Ambience/ambilava3.ogg b/Resources/Audio/Ambience/ambilava3.ogg
new file mode 100644
index 0000000000..8913ad4a0e
Binary files /dev/null and b/Resources/Audio/Ambience/ambilava3.ogg differ
diff --git a/Resources/Audio/Ambience/ambimaint1.ogg b/Resources/Audio/Ambience/ambimaint1.ogg
new file mode 100644
index 0000000000..0c18c46af6
Binary files /dev/null and b/Resources/Audio/Ambience/ambimaint1.ogg differ
diff --git a/Resources/Audio/Ambience/ambimaint2.ogg b/Resources/Audio/Ambience/ambimaint2.ogg
new file mode 100644
index 0000000000..655d940cda
Binary files /dev/null and b/Resources/Audio/Ambience/ambimaint2.ogg differ
diff --git a/Resources/Audio/Ambience/ambimaint3.ogg b/Resources/Audio/Ambience/ambimaint3.ogg
new file mode 100644
index 0000000000..9891916010
Binary files /dev/null and b/Resources/Audio/Ambience/ambimaint3.ogg differ
diff --git a/Resources/Audio/Ambience/ambimaint4.ogg b/Resources/Audio/Ambience/ambimaint4.ogg
new file mode 100644
index 0000000000..1d01e2016c
Binary files /dev/null and b/Resources/Audio/Ambience/ambimaint4.ogg differ
diff --git a/Resources/Audio/Ambience/ambimaint5.ogg b/Resources/Audio/Ambience/ambimaint5.ogg
new file mode 100644
index 0000000000..7a065ecbcb
Binary files /dev/null and b/Resources/Audio/Ambience/ambimaint5.ogg differ
diff --git a/Resources/Audio/Ambience/ambimine.ogg b/Resources/Audio/Ambience/ambimine.ogg
new file mode 100644
index 0000000000..6a7fbc391d
Binary files /dev/null and b/Resources/Audio/Ambience/ambimine.ogg differ
diff --git a/Resources/Audio/Ambience/ambimo1.ogg b/Resources/Audio/Ambience/ambimo1.ogg
new file mode 100644
index 0000000000..da0d522ad2
Binary files /dev/null and b/Resources/Audio/Ambience/ambimo1.ogg differ
diff --git a/Resources/Audio/Ambience/ambimo2.ogg b/Resources/Audio/Ambience/ambimo2.ogg
new file mode 100644
index 0000000000..bc77cd28c6
Binary files /dev/null and b/Resources/Audio/Ambience/ambimo2.ogg differ
diff --git a/Resources/Audio/Ambience/ambimystery.ogg b/Resources/Audio/Ambience/ambimystery.ogg
new file mode 100644
index 0000000000..b6e65c0bf6
Binary files /dev/null and b/Resources/Audio/Ambience/ambimystery.ogg differ
diff --git a/Resources/Audio/Ambience/ambinice.ogg b/Resources/Audio/Ambience/ambinice.ogg
new file mode 100644
index 0000000000..6ce351521b
Binary files /dev/null and b/Resources/Audio/Ambience/ambinice.ogg differ
diff --git a/Resources/Audio/Ambience/ambiodd.ogg b/Resources/Audio/Ambience/ambiodd.ogg
new file mode 100644
index 0000000000..dde64a9858
Binary files /dev/null and b/Resources/Audio/Ambience/ambiodd.ogg differ
diff --git a/Resources/Audio/Ambience/ambireebe1.ogg b/Resources/Audio/Ambience/ambireebe1.ogg
new file mode 100644
index 0000000000..77599fac5b
Binary files /dev/null and b/Resources/Audio/Ambience/ambireebe1.ogg differ
diff --git a/Resources/Audio/Ambience/ambireebe3.ogg b/Resources/Audio/Ambience/ambireebe3.ogg
new file mode 100644
index 0000000000..3ef1211897
Binary files /dev/null and b/Resources/Audio/Ambience/ambireebe3.ogg differ
diff --git a/Resources/Audio/Ambience/ambiruin.ogg b/Resources/Audio/Ambience/ambiruin.ogg
new file mode 100644
index 0000000000..ff4cef4a41
Binary files /dev/null and b/Resources/Audio/Ambience/ambiruin.ogg differ
diff --git a/Resources/Audio/Ambience/ambiruin2.ogg b/Resources/Audio/Ambience/ambiruin2.ogg
new file mode 100644
index 0000000000..2dc408e2b6
Binary files /dev/null and b/Resources/Audio/Ambience/ambiruin2.ogg differ
diff --git a/Resources/Audio/Ambience/ambiruin3.ogg b/Resources/Audio/Ambience/ambiruin3.ogg
new file mode 100644
index 0000000000..7a97ff9c60
Binary files /dev/null and b/Resources/Audio/Ambience/ambiruin3.ogg differ
diff --git a/Resources/Audio/Ambience/ambiruin4.ogg b/Resources/Audio/Ambience/ambiruin4.ogg
new file mode 100644
index 0000000000..ad56a915f9
Binary files /dev/null and b/Resources/Audio/Ambience/ambiruin4.ogg differ
diff --git a/Resources/Audio/Ambience/ambiruin5.ogg b/Resources/Audio/Ambience/ambiruin5.ogg
new file mode 100644
index 0000000000..2073b5a277
Binary files /dev/null and b/Resources/Audio/Ambience/ambiruin5.ogg differ
diff --git a/Resources/Audio/Ambience/ambiruin6.ogg b/Resources/Audio/Ambience/ambiruin6.ogg
new file mode 100644
index 0000000000..4b6c79a72d
Binary files /dev/null and b/Resources/Audio/Ambience/ambiruin6.ogg differ
diff --git a/Resources/Audio/Ambience/ambiruin7.ogg b/Resources/Audio/Ambience/ambiruin7.ogg
new file mode 100644
index 0000000000..ed88fcd52d
Binary files /dev/null and b/Resources/Audio/Ambience/ambiruin7.ogg differ
diff --git a/Resources/Audio/Ambience/ambisin1.ogg b/Resources/Audio/Ambience/ambisin1.ogg
new file mode 100644
index 0000000000..a31c7b226e
Binary files /dev/null and b/Resources/Audio/Ambience/ambisin1.ogg differ
diff --git a/Resources/Audio/Ambience/ambisin2.ogg b/Resources/Audio/Ambience/ambisin2.ogg
new file mode 100644
index 0000000000..fbac9a73e9
Binary files /dev/null and b/Resources/Audio/Ambience/ambisin2.ogg differ
diff --git a/Resources/Audio/Ambience/ambisin3.ogg b/Resources/Audio/Ambience/ambisin3.ogg
new file mode 100644
index 0000000000..c71d1ae293
Binary files /dev/null and b/Resources/Audio/Ambience/ambisin3.ogg differ
diff --git a/Resources/Audio/Ambience/ambisin4.ogg b/Resources/Audio/Ambience/ambisin4.ogg
new file mode 100644
index 0000000000..76be2ef85c
Binary files /dev/null and b/Resources/Audio/Ambience/ambisin4.ogg differ
diff --git a/Resources/Audio/Ambience/ambitech.ogg b/Resources/Audio/Ambience/ambitech.ogg
new file mode 100644
index 0000000000..5f21514e5c
Binary files /dev/null and b/Resources/Audio/Ambience/ambitech.ogg differ
diff --git a/Resources/Audio/Ambience/ambitech2.ogg b/Resources/Audio/Ambience/ambitech2.ogg
new file mode 100644
index 0000000000..bd6428bff3
Binary files /dev/null and b/Resources/Audio/Ambience/ambitech2.ogg differ
diff --git a/Resources/Audio/Ambience/ambitech3.ogg b/Resources/Audio/Ambience/ambitech3.ogg
new file mode 100644
index 0000000000..effd23b132
Binary files /dev/null and b/Resources/Audio/Ambience/ambitech3.ogg differ
diff --git a/Resources/Audio/Ambience/attributions.yml b/Resources/Audio/Ambience/attributions.yml
index e6979603be..b129339a71 100644
--- a/Resources/Audio/Ambience/attributions.yml
+++ b/Resources/Audio/Ambience/attributions.yml
@@ -1,4 +1,65 @@
- files: ["anomaly_drone.ogg"]
license: "CC0-1.0"
copyright: "Created by Joao_Janz, edited and converted to Mono by EmoGarbage"
- source: "https://freesound.org/people/Joao_Janz/sounds/478472/"
\ No newline at end of file
+ source: "https://freesound.org/people/Joao_Janz/sounds/478472/"
+
+- files:
+ - "ambiatmos.ogg"
+ - "ambiatmos2.ogg"
+ - "ambicave.ogg"
+ - "ambicha1.ogg"
+ - "ambicha2.ogg"
+ - "ambicha3.ogg"
+ - "ambicha4.ogg"
+ - "ambidanger.ogg"
+ - "ambidanger2.ogg"
+ - "ambigen1.ogg"
+ - "ambigen2.ogg"
+ - "ambigen3.ogg"
+ - "ambigen4.ogg"
+ - "ambigen5.ogg"
+ - "ambigen6.ogg"
+ - "ambigen7.ogg"
+ - "ambigen8.ogg"
+ - "ambigen9.ogg"
+ - "ambigen10.ogg"
+ - "ambigen11.ogg"
+ - "ambigen12.ogg"
+ - "ambigen13.ogg"
+ - "ambigen14.ogg"
+ - "ambigen15.ogg"
+ - "ambiholy.ogg"
+ - "ambiholy2.ogg"
+ - "ambiholy3.ogg"
+ - "ambilava1.ogg"
+ - "ambilava2.ogg"
+ - "ambilava3.ogg"
+ - "ambimaint1.ogg"
+ - "ambimaint2.ogg"
+ - "ambimaint3.ogg"
+ - "ambimaint4.ogg"
+ - "ambimaint5.ogg"
+ - "ambimine.ogg"
+ - "ambimo1.ogg"
+ - "ambimo2.ogg"
+ - "ambimystery.ogg"
+ - "ambinice.ogg"
+ - "ambiodd.ogg"
+ - "ambiruin.ogg"
+ - "ambiruin2.ogg"
+ - "ambiruin3.ogg"
+ - "ambiruin4.ogg"
+ - "ambiruin5.ogg"
+ - "ambiruin6.ogg"
+ - "ambiruin7.ogg"
+ - "ambisin1.ogg"
+ - "ambisin2.ogg"
+ - "ambisin3.ogg"
+ - "ambisin4.ogg"
+ - "ambitech.ogg"
+ - "ambitech2.ogg"
+ - "ambitech3.ogg"
+ - "maintambience.ogg"
+ license: "CC-BY-SA-3.0"
+ copyright: "Taken from tgstation"
+ source: "https://github.com/tgstation/tgstation/tree/ae9767664701396501af5dcef8e34c4b5add3d47/sound/ambience"
\ No newline at end of file
diff --git a/Resources/Audio/Ambience/maintambience.ogg b/Resources/Audio/Ambience/maintambience.ogg
new file mode 100644
index 0000000000..1ac3764b17
Binary files /dev/null and b/Resources/Audio/Ambience/maintambience.ogg differ
diff --git a/Resources/Audio/Voice/Misc/attributions.yml b/Resources/Audio/Voice/Misc/attributions.yml
new file mode 100644
index 0000000000..d9e11b3f0c
--- /dev/null
+++ b/Resources/Audio/Voice/Misc/attributions.yml
@@ -0,0 +1,8 @@
+- files:
+ - "lowHiss1.ogg"
+ - "lowHiss2.ogg"
+ - "lowHiss3.ogg"
+ - "lowHiss4.ogg"
+ license: "CC-BY-SA-3.0"
+ copyright: "Taken from tgstation"
+ source: "https://github.com/tgstation/tgstation/tree/ae9767664701396501af5dcef8e34c4b5add3d47/sound/ambience"
\ No newline at end of file
diff --git a/Resources/Audio/Voice/Misc/lowHiss1.ogg b/Resources/Audio/Voice/Misc/lowHiss1.ogg
new file mode 100644
index 0000000000..1373f010f8
Binary files /dev/null and b/Resources/Audio/Voice/Misc/lowHiss1.ogg differ
diff --git a/Resources/Audio/Voice/Misc/lowHiss2.ogg b/Resources/Audio/Voice/Misc/lowHiss2.ogg
new file mode 100644
index 0000000000..90a91db5dd
Binary files /dev/null and b/Resources/Audio/Voice/Misc/lowHiss2.ogg differ
diff --git a/Resources/Audio/Voice/Misc/lowHiss3.ogg b/Resources/Audio/Voice/Misc/lowHiss3.ogg
new file mode 100644
index 0000000000..ab1f2d5bdb
Binary files /dev/null and b/Resources/Audio/Voice/Misc/lowHiss3.ogg differ
diff --git a/Resources/Audio/Voice/Misc/lowHiss4.ogg b/Resources/Audio/Voice/Misc/lowHiss4.ogg
new file mode 100644
index 0000000000..a5c95b0297
Binary files /dev/null and b/Resources/Audio/Voice/Misc/lowHiss4.ogg differ
diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
index 1a50c932ff..e2ff7bc411 100644
--- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
+++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
@@ -14,6 +14,7 @@ ui-options-default = Default
ui-options-master-volume = Master Volume:
ui-options-midi-volume = MIDI (Instrument) Volume:
+ui-options-ambient-music-volume = Ambient music volume:
ui-options-ambience-volume = Ambience volume:
ui-options-lobby-volume = Lobby & Round-end volume:
ui-options-ambience-max-sounds = Ambience simultaneous sounds:
@@ -21,8 +22,6 @@ ui-options-lobby-music = Lobby & Round-end Music
ui-options-restart-sounds = Round Restart Sounds
ui-options-event-music = Event Music
ui-options-admin-sounds = Play Admin Sounds
-ui-options-station-ambience = Station Ambience
-ui-options-space-ambience = Space Ambience
ui-options-volume-label = Volume
ui-options-volume-percent = { TOSTRING($volume, "P0") }
diff --git a/Resources/Prototypes/SoundCollections/ambience.yml b/Resources/Prototypes/SoundCollections/ambience.yml
deleted file mode 100644
index 463d916a40..0000000000
--- a/Resources/Prototypes/SoundCollections/ambience.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-- type: soundCollection
- id: StationAmbienceBase
- files:
- - /Audio/Ambience/shipambience.ogg
-
-- type: soundCollection
- id: SpaceAmbienceBase
- files:
- - /Audio/Ambience/starlight.ogg
- - /Audio/Ambience/constellations.ogg
- - /Audio/Ambience/drifting.ogg
diff --git a/Resources/Prototypes/audio.yml b/Resources/Prototypes/audio.yml
new file mode 100644
index 0000000000..d3a96e2764
--- /dev/null
+++ b/Resources/Prototypes/audio.yml
@@ -0,0 +1,262 @@
+- type: ambientMusic
+ id: Morgue
+ sound:
+ params:
+ volume: -12
+ collection: AmbienceSpooky
+ rules: NearMorgue
+ priority: 4
+
+- type: ambientMusic
+ id: Holy
+ sound:
+ params:
+ volume: -12
+ collection: AmbienceHoly
+ rules: NearPrayable
+ priority: 4
+
+# Departments
+- type: ambientMusic
+ id: Medical
+ sound:
+ params:
+ volume: -12
+ collection: AmbienceMedical
+ rules: NearMedical
+ priority: 3
+
+- type: ambientMusic
+ id: Engineering
+ sound:
+ params:
+ volume: -12
+ collection: AmbienceEngineering
+ rules: NearEngineering
+ priority: 3
+
+# General areas
+- type: ambientMusic
+ id: Maintenance
+ sound:
+ params:
+ volume: -12
+ collection: AmbienceMaintenance
+ rules: NearMaintenance
+ priority: 2
+
+- type: ambientMusic
+ id: Space
+ sound:
+ params:
+ volume: -10
+ collection: AmbienceSpace
+ fadeIn: true
+ interruptable: true
+ rules: InSpace
+ priority: 1
+
+- type: ambientMusic
+ id: Mining
+ sound:
+ params:
+ volume: -12
+ collection: AmbienceMining
+ rules: OnMapGrid
+ fadeIn: true
+ interruptable: true
+ priority: 1
+
+## Fallback if nothing else found
+- type: ambientMusic
+ id: General
+ sound:
+ params:
+ volume: -12
+ collection: AmbienceGeneral
+ rules: AlwaysTrue
+
+# Sound collections
+- type: soundCollection
+ id: AmbienceEngineering
+ files:
+ - /Audio/Ambience/ambiatmos.ogg
+ - /Audio/Ambience/ambiatmos2.ogg
+ - /Audio/Ambience/ambisin1.ogg
+ - /Audio/Ambience/ambisin2.ogg
+ - /Audio/Ambience/ambisin3.ogg
+ - /Audio/Ambience/ambisin4.ogg
+ - /Audio/Ambience/ambitech.ogg
+ - /Audio/Ambience/ambitech2.ogg
+ - /Audio/Ambience/ambitech3.ogg
+
+- type: soundCollection
+ id: AmbienceGeneral
+ files:
+ - /Audio/Ambience/ambigen1.ogg
+ - /Audio/Ambience/ambigen3.ogg
+ - /Audio/Ambience/ambigen4.ogg
+ - /Audio/Ambience/ambigen5.ogg
+ - /Audio/Ambience/ambigen6.ogg
+ - /Audio/Ambience/ambigen7.ogg
+ - /Audio/Ambience/ambigen8.ogg
+ - /Audio/Ambience/ambigen9.ogg
+ - /Audio/Ambience/ambigen10.ogg
+ - /Audio/Ambience/ambigen11.ogg
+ - /Audio/Ambience/ambigen12.ogg
+ - /Audio/Ambience/ambigen14.ogg
+ - /Audio/Ambience/ambigen15.ogg
+
+- type: soundCollection
+ id: AmbienceHoly
+ files:
+ - /Audio/Ambience/ambicha1.ogg
+ - /Audio/Ambience/ambicha2.ogg
+ - /Audio/Ambience/ambicha3.ogg
+ - /Audio/Ambience/ambicha4.ogg
+ - /Audio/Ambience/ambiholy.ogg
+ - /Audio/Ambience/ambiholy2.ogg
+ - /Audio/Ambience/ambiholy3.ogg
+
+- type: soundCollection
+ id: AmbienceMaintenance
+ files:
+ - /Audio/Ambience/ambimaint1.ogg
+ - /Audio/Ambience/ambimaint2.ogg
+ - /Audio/Ambience/ambimaint3.ogg
+ - /Audio/Ambience/ambimaint4.ogg
+ - /Audio/Ambience/ambimaint5.ogg
+ - /Audio/Ambience/ambitech2.ogg
+ - /Audio/Voice/Misc/lowHiss1.ogg
+ - /Audio/Voice/Misc/lowHiss2.ogg
+ - /Audio/Voice/Misc/lowHiss3.ogg
+ - /Audio/Voice/Misc/lowHiss4.ogg
+ - /Audio/Ambience/maintambience.ogg
+
+- type: soundCollection
+ id: AmbienceMedical
+ files:
+ - /Audio/Ambience/ambinice.ogg
+
+- type: soundCollection
+ id: AmbienceMining
+ files:
+ - /Audio/Ambience/ambicave.ogg
+ - /Audio/Ambience/ambidanger.ogg
+ - /Audio/Ambience/ambidanger2.ogg
+ - /Audio/Ambience/ambilava1.ogg
+ - /Audio/Ambience/ambilava2.ogg
+ - /Audio/Ambience/ambilava3.ogg
+ - /Audio/Ambience/ambimaint1.ogg
+ - /Audio/Ambience/ambimine.ogg
+ - /Audio/Ambience/ambiruin.ogg
+ - /Audio/Ambience/ambiruin2.ogg
+ - /Audio/Ambience/ambiruin3.ogg
+ - /Audio/Ambience/ambiruin4.ogg
+ - /Audio/Ambience/ambiruin5.ogg
+ - /Audio/Ambience/ambiruin6.ogg
+ - /Audio/Ambience/ambiruin7.ogg
+
+- type: soundCollection
+ id: AmbienceRuins
+ files:
+ - /Audio/Ambience/ambicave.ogg
+ - /Audio/Ambience/ambidanger.ogg
+ - /Audio/Ambience/ambidanger2.ogg
+ - /Audio/Ambience/ambimaint1.ogg
+ - /Audio/Ambience/ambimine.ogg
+ - /Audio/Ambience/ambimystery.ogg
+ - /Audio/Ambience/ambiruin.ogg
+ - /Audio/Ambience/ambiruin2.ogg
+ - /Audio/Ambience/ambiruin3.ogg
+ - /Audio/Ambience/ambiruin4.ogg
+ - /Audio/Ambience/ambiruin5.ogg
+ - /Audio/Ambience/ambiruin6.ogg
+ - /Audio/Ambience/ambiruin7.ogg
+
+- type: soundCollection
+ id: AmbienceSpace
+ files:
+ - /Audio/Ambience/starlight.ogg
+ - /Audio/Ambience/constellations.ogg
+ - /Audio/Ambience/drifting.ogg
+
+- type: soundCollection
+ id: AmbienceSpooky
+ files:
+ - /Audio/Ambience/ambimo1.ogg
+ - /Audio/Ambience/ambimo2.ogg
+ - /Audio/Ambience/ambimystery.ogg
+ - /Audio/Ambience/ambiodd.ogg
+ - /Audio/Ambience/ambiruin6.ogg
+ - /Audio/Ambience/ambiruin7.ogg
+
+## Background noise on station, separate to ambient music.
+- type: soundCollection
+ id: AmbienceStation
+ files:
+ - /Audio/Ambience/shipambience.ogg
+
+# Rules
+- type: rules
+ id: AlwaysTrue
+ rules:
+ - !type:AlwaysTrueRule
+
+# TODO: Need to make sure no grids nearby
+- type: rules
+ id: InSpace
+ rules:
+ - !type:InSpaceRule
+ - !type:GridInRangeRule
+ inverted: true
+ range: 10
+
+# TODO
+- type: rules
+ id: NearEngineering
+ rules:
+ - !type:NearbyAccessRule
+ access:
+ - Engineering
+ range: 3
+
+- type: rules
+ id: NearMaintenance
+ rules:
+ - !type:NearbyTilesPercentRule
+ percent: 0.25
+ tiles:
+ - Plating
+ range: 3
+
+- type: rules
+ id: NearMedical
+ rules:
+ - !type:NearbyTilesPercentRule
+ percent: 0.5
+ tiles:
+ - FloorWhite
+ range: 5
+
+- type: rules
+ id: NearPrayable
+ rules:
+ - !type:NearbyComponentsRule
+ components:
+ - type: Prayable
+ range: 5
+
+- type: rules
+ id: NearMorgue
+ rules:
+ - !type:NearbyComponentsRule
+ count: 2
+ components:
+ - type: Morgue
+ range: 3
+
+- type: rules
+ id: OnMapGrid
+ rules:
+ - !type:OnMapGridRule