Add ambient music (#16829)

This commit is contained in:
metalgearsloth
2023-05-29 10:44:11 +10:00
committed by GitHub
parent f35fcff23f
commit 0c83642c5a
84 changed files with 1252 additions and 338 deletions

View File

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

View File

@@ -1,21 +1,12 @@
using System.Threading;
using Content.Client.Gameplay;
using Content.Client.GameTicking.Managers;
using Content.Client.Lobby;
using Content.Shared.CCVar;
using JetBrains.Annotations;
using Robust.Client;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.Audio;
@@ -26,57 +17,18 @@ public sealed class BackgroundAudioSystem : EntitySystem
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly ClientGameTicker _gameTicker = default!;
[Dependency] private readonly IPlayerManager _playMan = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly AudioParams _ambientParams = new(-10f, 1, "Master", 0, 0, 0, true, 0f);
private readonly AudioParams _lobbyParams = new(-5f, 1, "Master", 0, 0, 0, true, 0f);
private IPlayingAudioStream? _ambientStream;
private IPlayingAudioStream? _lobbyStream;
/// <summary>
/// What is currently playing.
/// </summary>
private SoundCollectionPrototype? _playingCollection;
/// <summary>
/// What the ambience has been set to.
/// </summary>
private SoundCollectionPrototype? _currentCollection;
private CancellationTokenSource _timerCancelTokenSource = new();
private SoundCollectionPrototype _spaceAmbience = default!;
private SoundCollectionPrototype _stationAmbience = default!;
public override void Initialize()
{
base.Initialize();
_stationAmbience = _prototypeManager.Index<SoundCollectionPrototype>("StationAmbienceBase");
_spaceAmbience = _prototypeManager.Index<SoundCollectionPrototype>("SpaceAmbienceBase");
_currentCollection = _stationAmbience;
// TODO: Ideally audio loading streamed better / we have more robust audio but this is quite annoying
var cache = IoCManager.Resolve<IResourceCache>();
foreach (var audio in _spaceAmbience.PickFiles)
{
cache.GetResource<AudioResource>(audio.ToString());
}
_configManager.OnValueChanged(CCVars.AmbienceVolume, AmbienceCVarChanged);
_configManager.OnValueChanged(CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
_configManager.OnValueChanged(CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
_configManager.OnValueChanged(CCVars.StationAmbienceEnabled, StationAmbienceCVarChanged);
_configManager.OnValueChanged(CCVars.SpaceAmbienceEnabled, SpaceAmbienceCVarChanged);
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
_stateManager.OnStateChanged += StateManagerOnStateChanged;
@@ -85,28 +37,12 @@ public sealed class BackgroundAudioSystem : EntitySystem
_gameTicker.LobbyStatusUpdated += LobbySongReceived;
}
private void OnPlayerAttached(PlayerAttachedEvent ev)
{
if (!TryComp<TransformComponent>(ev.Entity, out var xform))
return;
CheckAmbience(xform);
}
private void OnPlayerDetached(PlayerDetachedEvent ev)
{
EndAmbience();
}
public override void Shutdown()
{
base.Shutdown();
_configManager.UnsubValueChanged(CCVars.AmbienceVolume, AmbienceCVarChanged);
_configManager.UnsubValueChanged(CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
_configManager.UnsubValueChanged(CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
_configManager.UnsubValueChanged(CCVars.StationAmbienceEnabled, StationAmbienceCVarChanged);
_configManager.UnsubValueChanged(CCVars.SpaceAmbienceEnabled, SpaceAmbienceCVarChanged);
_stateManager.OnStateChanged -= StateManagerOnStateChanged;
@@ -114,61 +50,17 @@ public sealed class BackgroundAudioSystem : EntitySystem
_gameTicker.LobbyStatusUpdated -= LobbySongReceived;
EndAmbience();
EndLobbyMusic();
}
private void CheckAmbience(TransformComponent xform)
{
if (xform.GridUid != null)
ChangeAmbience(_stationAmbience);
else
ChangeAmbience(_spaceAmbience);
}
private void EntParentChanged(ref EntParentChangedMessage message)
{
if (_playMan.LocalPlayer is null
|| _playMan.LocalPlayer.ControlledEntity != message.Entity
|| !(_timing.IsFirstTimePredicted || _timing.ApplyingState))
return;
// Check if we traversed to grid.
CheckAmbience(message.Transform);
}
private void ChangeAmbience(SoundCollectionPrototype newAmbience)
{
if (_currentCollection == newAmbience)
return;
_timerCancelTokenSource.Cancel();
_currentCollection = newAmbience;
_timerCancelTokenSource = new CancellationTokenSource();
Timer.Spawn(1500, () =>
{
// If we traverse a few times then don't interrupt an existing song.
// If we are not in gameplay, don't call StartAmbience because of player movement
if (_playingCollection == _currentCollection || _stateManager.CurrentState is not GameplayState)
return;
StartAmbience();
}, _timerCancelTokenSource.Token);
}
private void StateManagerOnStateChanged(StateChangedEventArgs args)
{
switch (args.NewState)
{
case LobbyState:
EndAmbience();
StartLobbyMusic();
break;
case GameplayState:
EndLobbyMusic();
StartAmbience();
break;
default:
EndAmbience();
EndLobbyMusic();
break;
}
@@ -176,80 +68,9 @@ public sealed class BackgroundAudioSystem : EntitySystem
private void OnLeave(object? sender, PlayerEventArgs args)
{
EndAmbience();
EndLobbyMusic();
}
private void AmbienceCVarChanged(float volume)
{
if (_stateManager.CurrentState is GameplayState)
{
StartAmbience();
}
else
{
EndAmbience();
}
}
private void StartAmbience()
{
EndAmbience();
if (_currentCollection == null || !CanPlayCollection(_currentCollection))
return;
_playingCollection = _currentCollection;
var file = _robustRandom.Pick(_currentCollection.PickFiles).ToString();
_ambientStream = _audio.PlayGlobal(file, Filter.Local(), false,
_ambientParams.WithVolume(_ambientParams.Volume + _configManager.GetCVar(CCVars.AmbienceVolume)));
}
private void EndAmbience()
{
_playingCollection = null;
_ambientStream?.Stop();
_ambientStream = null;
}
private bool CanPlayCollection(SoundCollectionPrototype collection)
{
if (collection.ID == _spaceAmbience.ID)
return _configManager.GetCVar(CCVars.SpaceAmbienceEnabled);
if (collection.ID == _stationAmbience.ID)
return _configManager.GetCVar(CCVars.StationAmbienceEnabled);
return true;
}
private void StationAmbienceCVarChanged(bool enabled)
{
if (_currentCollection == null)
return;
if (enabled && _stateManager.CurrentState is GameplayState && _currentCollection.ID == _stationAmbience.ID)
{
StartAmbience();
}
else if (_currentCollection.ID == _stationAmbience.ID)
{
EndAmbience();
}
}
private void SpaceAmbienceCVarChanged(bool enabled)
{
if (_currentCollection == null)
return;
if (enabled && _stateManager.CurrentState is GameplayState && _currentCollection.ID == _spaceAmbience.ID)
{
StartAmbience();
}
else if (_currentCollection.ID == _spaceAmbience.ID)
{
EndAmbience();
}
}
private void LobbyMusicVolumeCVarChanged(float volume)
{
if (_stateManager.CurrentState is LobbyState)

View File

@@ -0,0 +1,262 @@
using System.Linq;
using Content.Client.Gameplay;
using Content.Shared.Audio;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Random;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Audio;
public sealed partial class ContentAudioSystem
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceCache _resource = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IStateManager _state = default!;
[Dependency] private readonly RulesSystem _rules = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
private readonly TimeSpan _minAmbienceTime = TimeSpan.FromSeconds(30);
private readonly TimeSpan _maxAmbienceTime = TimeSpan.FromSeconds(60);
private const float AmbientMusicFadeTime = 10f;
private static float _volumeSlider;
// Don't need to worry about this being serializable or pauseable as it doesn't affect the sim.
private TimeSpan _nextAudio;
private AudioSystem.PlayingStream? _ambientMusicStream;
private AmbientMusicPrototype? _musicProto;
/// <summary>
/// If we find a better ambient music proto can we interrupt this one.
/// </summary>
private bool _interruptable;
/// <summary>
/// Track what ambient sounds we've played. This is so they all get played an even
/// number of times.
/// When we get to the end of the list we'll re-shuffle
/// </summary>
private readonly Dictionary<string, List<ResPath>> _ambientSounds = new();
private ISawmill _sawmill = default!;
private void InitializeAmbientMusic()
{
// TODO: Shitty preload
foreach (var audio in _proto.Index<SoundCollectionPrototype>("AmbienceSpace").PickFiles)
{
_resource.GetResource<AudioResource>(audio.ToString());
}
_configManager.OnValueChanged(CCVars.AmbientMusicVolume, AmbienceCVarChanged, true);
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("audio.ambience");
// Reset audio
_nextAudio = TimeSpan.MaxValue;
SetupAmbientSounds();
_proto.PrototypesReloaded += OnProtoReload;
_state.OnStateChanged += OnStateChange;
// On round end summary OR lobby cut audio.
SubscribeNetworkEvent<RoundEndMessageEvent>(OnRoundEndMessage);
}
private void AmbienceCVarChanged(float obj)
{
_volumeSlider = obj;
if (_ambientMusicStream != null && _musicProto != null)
{
_ambientMusicStream.Volume = _musicProto.Sound.Params.Volume + _volumeSlider;
}
}
private void ShutdownAmbientMusic()
{
_configManager.UnsubValueChanged(CCVars.AmbientMusicVolume, AmbienceCVarChanged);
_proto.PrototypesReloaded -= OnProtoReload;
_state.OnStateChanged -= OnStateChange;
_ambientMusicStream?.Stop();
}
private void OnProtoReload(PrototypesReloadedEventArgs obj)
{
if (!obj.ByType.ContainsKey(typeof(AmbientMusicPrototype)) &&
!obj.ByType.ContainsKey(typeof(RulesPrototype)))
{
return;
}
_ambientSounds.Clear();
SetupAmbientSounds();
}
private void OnStateChange(StateChangedEventArgs obj)
{
if (obj.NewState is not GameplayState)
return;
// If they go to game then reset the ambience timer.
_nextAudio = _timing.CurTime + _random.Next(_minAmbienceTime, _maxAmbienceTime);
}
private void SetupAmbientSounds()
{
foreach (var ambience in _proto.EnumeratePrototypes<AmbientMusicPrototype>())
{
var tracks = _ambientSounds.GetOrNew(ambience.ID);
RefreshTracks(ambience.Sound, tracks, null);
_random.Shuffle(tracks);
}
}
private void OnRoundEndMessage(RoundEndMessageEvent ev)
{
// If scoreboard shows then just stop the music
_ambientMusicStream?.Stop();
_ambientMusicStream = null;
_nextAudio = TimeSpan.FromMinutes(3);
}
private void RefreshTracks(SoundSpecifier sound, List<ResPath> tracks, ResPath? lastPlayed)
{
DebugTools.Assert(tracks.Count == 0);
switch (sound)
{
case SoundCollectionSpecifier collection:
if (collection.Collection == null)
break;
var slothCud = _proto.Index<SoundCollectionPrototype>(collection.Collection);
tracks.AddRange(slothCud.PickFiles);
break;
case SoundPathSpecifier path:
tracks.Add(path.Path);
break;
}
// Just so the same track doesn't play twice
if (tracks.Count > 1 && tracks[^1] == lastPlayed)
{
(tracks[0], tracks[^1]) = (tracks[^1], tracks[0]);
}
}
private void UpdateAmbientMusic()
{
// Update still runs in lobby so just ignore it.
if (_state.CurrentState is not GameplayState)
{
FadeOut(_ambientMusicStream);
_ambientMusicStream = null;
_musicProto = null;
return;
}
var isDone = _ambientMusicStream?.Done;
if (_interruptable)
{
var player = _player.LocalPlayer?.ControlledEntity;
if (player == null || _musicProto == null || !_rules.IsTrue(player.Value, _proto.Index<RulesPrototype>(_musicProto.Rules)))
{
FadeOut(_ambientMusicStream, AmbientMusicFadeTime);
_musicProto = null;
_interruptable = false;
isDone = true;
}
}
// Still running existing ambience
if (isDone == false)
return;
// If ambience finished reset the CD (this also means if we have long ambience it won't clip)
if (isDone == true)
{
// Also don't need to worry about rounding here as it doesn't affect the sim
_nextAudio = _timing.CurTime + _random.Next(_minAmbienceTime, _maxAmbienceTime);
}
_ambientMusicStream = null;
if (_nextAudio > _timing.CurTime)
return;
_musicProto = GetAmbience();
if (_musicProto == null)
{
_interruptable = false;
return;
}
_interruptable = _musicProto.Interruptable;
var tracks = _ambientSounds[_musicProto.ID];
var track = tracks[^1];
tracks.RemoveAt(tracks.Count - 1);
var strim = _audio.PlayGlobal(
track.ToString(),
Filter.Local(),
false,
AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider));
if (strim != null)
{
_ambientMusicStream = (AudioSystem.PlayingStream) strim;
if (_musicProto.FadeIn)
{
FadeIn(_ambientMusicStream, AmbientMusicFadeTime);
}
}
// Refresh the list
if (tracks.Count == 0)
{
RefreshTracks(_musicProto.Sound, tracks, track);
}
}
private AmbientMusicPrototype? GetAmbience()
{
var player = _player.LocalPlayer?.ControlledEntity;
if (player == null)
return null;
var ambiences = _proto.EnumeratePrototypes<AmbientMusicPrototype>().ToList();
ambiences.Sort((x, y) => y.Priority.CompareTo(x.Priority));
foreach (var amb in ambiences)
{
if (!_rules.IsTrue(player.Value, _proto.Index<RulesPrototype>(amb.Rules)))
continue;
return amb;
}
_sawmill.Warning($"Unable to find fallback ambience track");
return null;
}
}

View File

@@ -1,8 +1,124 @@
using Content.Shared.Audio;
using Robust.Client.GameObjects;
namespace Content.Client.Audio;
public sealed class ContentAudioSystem : SharedContentAudioSystem
public sealed partial class ContentAudioSystem : SharedContentAudioSystem
{
// Need how much volume to change per tick and just remove it when it drops below "0"
private readonly Dictionary<AudioSystem.PlayingStream, float> _fadingOut = new();
// Need volume change per tick + target volume.
private readonly Dictionary<AudioSystem.PlayingStream, (float VolumeChange, float TargetVolume)> _fadingIn = new();
private readonly List<AudioSystem.PlayingStream> _fadeToRemove = new();
private const float MinVolume = -32f;
private const float DefaultDuration = 2f;
public override void Initialize()
{
base.Initialize();
UpdatesOutsidePrediction = true;
InitializeAmbientMusic();
}
public override void Shutdown()
{
base.Shutdown();
ShutdownAmbientMusic();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
if (!_timing.IsFirstTimePredicted)
return;
UpdateAmbientMusic();
UpdateFades(frameTime);
}
#region Fades
public void FadeOut(AudioSystem.PlayingStream? stream, float duration = DefaultDuration)
{
if (stream == null || duration <= 0f)
return;
// Just in case
// TODO: Maybe handle the removals by making it seamless?
_fadingIn.Remove(stream);
var diff = stream.Volume - MinVolume;
_fadingOut.Add(stream, diff / duration);
}
public void FadeIn(AudioSystem.PlayingStream? stream, float duration = DefaultDuration)
{
if (stream == null || duration <= 0f || stream.Volume < MinVolume)
return;
_fadingOut.Remove(stream);
var curVolume = stream.Volume;
var change = (curVolume - MinVolume) / duration;
_fadingIn.Add(stream, (change, stream.Volume));
stream.Volume = MinVolume;
}
private void UpdateFades(float frameTime)
{
_fadeToRemove.Clear();
foreach (var (stream, change) in _fadingOut)
{
// Cancelled elsewhere
if (stream.Done)
{
_fadeToRemove.Add(stream);
continue;
}
var volume = stream.Volume - change * frameTime;
stream.Volume = MathF.Max(MinVolume, volume);
if (stream.Volume.Equals(MinVolume))
{
stream.Stop();
_fadeToRemove.Add(stream);
}
}
foreach (var stream in _fadeToRemove)
{
_fadingOut.Remove(stream);
}
_fadeToRemove.Clear();
foreach (var (stream, (change, target)) in _fadingIn)
{
// Cancelled elsewhere
if (stream.Done)
{
_fadeToRemove.Add(stream);
continue;
}
var volume = stream.Volume + change * frameTime;
stream.Volume = MathF.Min(target, volume);
if (stream.Volume.Equals(target))
{
_fadeToRemove.Add(stream);
}
}
foreach (var stream in _fadeToRemove)
{
_fadingIn.Remove(stream);
}
}
#endregion
}

View File

@@ -27,7 +27,7 @@
<Control MinSize="8 0" />
<Slider Name="MidiVolumeSlider"
MinValue="0"
MaxValue="200"
MaxValue="100"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
@@ -35,12 +35,25 @@
<Label Name="MidiVolumeLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
<Label Text="{Loc 'ui-options-ambient-music-volume'}" HorizontalExpand="True" />
<Control MinSize="8 0" />
<Slider Name="AmbientMusicVolumeSlider"
MinValue="0"
MaxValue="100"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="AmbientMusicVolumeLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
<Label Text="{Loc 'ui-options-ambience-volume'}" HorizontalExpand="True" />
<Control MinSize="8 0" />
<Slider Name="AmbienceVolumeSlider"
MinValue="0"
MaxValue="300"
MaxValue="100"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
@@ -53,7 +66,7 @@
<Control MinSize="8 0" />
<Slider Name="LobbyVolumeSlider"
MinValue="0"
MaxValue="200"
MaxValue="100"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
@@ -79,8 +92,6 @@
<CheckBox Name="RestartSoundsCheckBox" Text="{Loc 'ui-options-restart-sounds'}" />
<CheckBox Name="EventMusicCheckBox" Text="{Loc 'ui-options-event-music'}" />
<CheckBox Name="AdminSoundsCheckBox" Text="{Loc 'ui-options-admin-sounds'}" />
<CheckBox Name="StationAmbienceCheckBox" Text="{Loc 'ui-options-station-ambience'}" />
<CheckBox Name="SpaceAmbienceCheckBox" Text="{Loc 'ui-options-space-ambience'}" />
</BoxContainer>
</BoxContainer>
<controls:StripeBack HasBottomEdge="False" HasMargins="False">

View File

@@ -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 =

View File

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

View File

@@ -1,47 +0,0 @@
using Robust.Shared.Utility;
namespace Content.Server.Prayer
{
/// <summary>
/// Allows an entity to be prayed on in the context menu
/// </summary>
[RegisterComponent]
public sealed class PrayableComponent : Component
{
/// <summary>
/// If bible users are only allowed to use this prayable entity
/// </summary>
[DataField("bibleUserOnly")]
[ViewVariables(VVAccess.ReadWrite)]
public bool BibleUserOnly;
/// <summary>
/// Message given to user to notify them a message was sent
/// </summary>
[DataField("sentMessage")]
[ViewVariables(VVAccess.ReadWrite)]
public string SentMessage = "prayer-popup-notify-pray-sent";
/// <summary>
/// Prefix used in the notification to admins
/// </summary>
[DataField("notifiactionPrefix")]
[ViewVariables(VVAccess.ReadWrite)]
public string NotifiactionPrefix = "prayer-chat-notify-pray";
/// <summary>
/// Used in window title and context menu
/// </summary>
[DataField("verb")]
[ViewVariables(VVAccess.ReadOnly)]
public string Verb = "prayer-verbs-pray";
/// <summary>
/// Context menu image
/// </summary>
[DataField("verbImage")]
[ViewVariables(VVAccess.ReadOnly)]
public SpriteSpecifier? VerbImage = new SpriteSpecifier.Texture(new ("/Textures/Interface/pray.svg.png"));
}
}

View File

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

View File

@@ -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;
/// <summary>
/// Attaches a rules prototype to sound files to play ambience.
/// </summary>
[Prototype("ambientMusic")]
public sealed class AmbientMusicPrototype : IPrototype
{
[IdDataField] public string ID { get; } = string.Empty;
/// <summary>
/// Traditionally you'd prioritise most rules to least as priority but in our case we'll just be explicit.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("priority")]
public int Priority = 0;
/// <summary>
/// Can we interrupt this ambience for a better prototype if possible?
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("interruptable")]
public bool Interruptable = false;
/// <summary>
/// Do we fade-in. Useful for songs.
/// </summary>
[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<RulesPrototype>))]
public string Rules = string.Empty;
}

View File

@@ -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<AmbientSoundComponent>
{
[RegisterComponent]
[NetworkedComponent]
[Access(typeof(SharedAmbientSoundSystem))]
public sealed class AmbientSoundComponent : Component, IComponentTreeEntry<AmbientSoundComponent>
{
[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!;
/// <summary>
/// How far away this ambient sound can potentially be heard.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] // only for map editing
[DataField("range")]
public float Range = 2f;
/// <summary>
/// How far away this ambient sound can potentially be heard.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] // only for map editing
[DataField("range")]
public float Range = 2f;
/// <summary>
/// Applies this volume to the sound being played.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] // only for map editing
[DataField("volume")]
public float Volume = -10f;
/// <summary>
/// Applies this volume to the sound being played.
/// </summary>
[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<ComponentTreeEntry<AmbientSoundComponent>>? Tree { get; set; }
public DynamicTree<ComponentTreeEntry<AmbientSoundComponent>>? 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; }
}

View File

@@ -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<bool> AmbienceBasicEnabled =
CVarDef.Create("ambiance.basic_enabled", true, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// 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<float> 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;
/// <summary>
/// Ambience music volume.
/// </summary>
public static readonly CVarDef<float> AmbientMusicVolume =
CVarDef.Create("ambience.music_volume", 0.0f, CVar.ARCHIVE | CVar.CLIENTONLY);
public const float AmbientMusicMultiplier = 2f;
/// <summary>
/// Lobby / round end music volume.
/// </summary>
public static readonly CVarDef<float> LobbyMusicVolume =
CVarDef.Create("ambience.lobby_music_volume", 0.0f, CVar.ARCHIVE | CVar.CLIENTONLY);
/// <summary>
/// Whether to play the station ambience (humming) sound
/// </summary>
public static readonly CVarDef<bool> StationAmbienceEnabled =
CVarDef.Create("ambience.station_ambience", true, CVar.ARCHIVE | CVar.CLIENTONLY);
/// <summary>
/// Whether to play the space ambience
/// </summary>
public static readonly CVarDef<bool> SpaceAmbienceEnabled =
CVarDef.Create("ambience.space_ambience", true, CVar.ARCHIVE | CVar.CLIENTONLY);
/*
* Status
*/

View File

@@ -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
{
/// <summary>

View File

@@ -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.

View File

@@ -0,0 +1,46 @@
using Robust.Shared.GameStates;
using Robust.Shared.Utility;
namespace Content.Shared.Prayer;
/// <summary>
/// Allows an entity to be prayed on in the context menu
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class PrayableComponent : Component
{
/// <summary>
/// If bible users are only allowed to use this prayable entity
/// </summary>
[DataField("bibleUserOnly")]
[ViewVariables(VVAccess.ReadWrite)]
public bool BibleUserOnly;
/// <summary>
/// Message given to user to notify them a message was sent
/// </summary>
[DataField("sentMessage")]
[ViewVariables(VVAccess.ReadWrite)]
public string SentMessage = "prayer-popup-notify-pray-sent";
/// <summary>
/// Prefix used in the notification to admins
/// </summary>
[DataField("notifiactionPrefix")]
[ViewVariables(VVAccess.ReadWrite)]
public string NotificationPrefix = "prayer-chat-notify-pray";
/// <summary>
/// Used in window title and context menu
/// </summary>
[DataField("verb")]
[ViewVariables(VVAccess.ReadOnly)]
public string Verb = "prayer-verbs-pray";
/// <summary>
/// Context menu image
/// </summary>
[DataField("verbImage")]
[ViewVariables(VVAccess.ReadOnly)]
public SpriteSpecifier? VerbImage = new SpriteSpecifier.Texture(new ("/Textures/Interface/pray.svg.png"));
}

View File

@@ -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;
/// <summary>
/// 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"
/// </summary>
[Prototype("rules")]
public sealed class RulesPrototype : IPrototype
{
[IdDataField] public string ID { get; } = string.Empty;
[DataField("rules", required: true)]
public List<RulesRule> Rules = new();
}
[ImplicitDataDefinitionForInheritors]
public abstract class RulesRule
{
}
/// <summary>
/// Returns true if the attached entity is in space.
/// </summary>
public sealed class InSpaceRule : RulesRule
{
}
/// <summary>
/// Checks for entities matching the whitelist in range.
/// This is more expensive than <see cref="NearbyComponentsRule"/> so prefer that!
/// </summary>
public sealed class NearbyEntitiesRule : RulesRule
{
/// <summary>
/// How many of the entity need to be nearby.
/// </summary>
[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<ContentTileDefinition>))]
public List<string> Tiles = new();
[DataField("range")]
public float Range = 10f;
}
/// <summary>
/// Always returns true. Used for fallbacks.
/// </summary>
public sealed class AlwaysTrueRule : RulesRule
{
}
/// <summary>
/// Returns true if on a grid or in range of one.
/// </summary>
public sealed class GridInRangeRule : RulesRule
{
[DataField("range")]
public float Range = 10f;
[DataField("inverted")]
public bool Inverted = false;
}
/// <summary>
/// Returns true if griduid and mapuid match (AKA on 'planet').
/// </summary>
public sealed class OnMapGridRule : RulesRule
{
}
/// <summary>
/// Checks for an entity nearby with the specified access.
/// </summary>
public sealed class NearbyAccessRule : RulesRule
{
// This exists because of doorelectronics contained inside doors.
/// <summary>
/// Does the access entity need to be anchored.
/// </summary>
[DataField("anchored")]
public bool Anchored = true;
/// <summary>
/// Count of entities that need to be nearby.
/// </summary>
[DataField("count")]
public int Count = 1;
[DataField("access", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<AccessLevelPrototype>))]
public List<string> 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;
}

View File

@@ -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<TransformComponent>(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<TransformComponent>(uid, out var xform) ||
xform.GridUid != null)
{
return false;
}
break;
}
case NearbyAccessRule access:
{
var xformQuery = GetEntityQuery<TransformComponent>();
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<AccessReaderComponent>(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<TransformComponent>(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<TransformComponent>(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<TransformComponent>(uid, out var xform) ||
!TryComp<MapGridComponent>(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<TransformComponent>(uid, out var xform) ||
xform.GridUid != xform.MapUid ||
xform.MapUid == null)
{
return false;
}
break;
}
default:
throw new NotImplementedException();
}
}
return true;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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/"
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"

Binary file not shown.

View File

@@ -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"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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") }

View File

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

View File

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