diff --git a/EOLib/Domain/Extensions/MapExtensions.cs b/EOLib/Domain/Extensions/MapExtensions.cs new file mode 100644 index 000000000..38890ea9b --- /dev/null +++ b/EOLib/Domain/Extensions/MapExtensions.cs @@ -0,0 +1,28 @@ +using EOLib.Domain.Map; +using EOLib.IO.Map; +using System; + +namespace EOLib.Domain.Extensions +{ + public static class MapExtensions + { + public static int GetDistanceToClosestTileSpec(this IMapFile map, TileSpec spec, MapCoordinate source) + { + var shortestDistance = int.MaxValue; + for (int row = 0; row < map.Properties.Height; row++) + { + for (int col = 0; col < map.Properties.Width; col++) + { + if (map.Tiles[row, col] != spec) + continue; + + var distance = Math.Abs(source.X - col) + Math.Abs(source.Y - row); + if (distance < shortestDistance) + shortestDistance = distance; + } + } + + return shortestDistance; + } + } +} diff --git a/EndlessClient/Audio/SfxPlayer.cs b/EndlessClient/Audio/SfxPlayer.cs index 9c035f3a5..b1c18b37d 100644 --- a/EndlessClient/Audio/SfxPlayer.cs +++ b/EndlessClient/Audio/SfxPlayer.cs @@ -9,7 +9,7 @@ namespace EndlessClient.Audio public sealed class SfxPlayer : ISfxPlayer { private readonly IContentProvider _contentProvider; - private SoundEffectInstance _activeSfx; + private SoundEffectInstance _loopingSfx; public SfxPlayer(IContentProvider contentProvider) { @@ -39,20 +39,30 @@ public void PlayGuitarNote(int index) public void PlayLoopingSfx(SoundEffectID id) { - if (_activeSfx != null && _activeSfx.State != SoundState.Stopped) + if (_loopingSfx != null && _loopingSfx.State != SoundState.Stopped) return; StopLoopingSfx(); - _activeSfx = _contentProvider.SFX[id].CreateInstance(); - _activeSfx.IsLooped = true; - _activeSfx.Play(); + _loopingSfx = _contentProvider.SFX[id].CreateInstance(); + _loopingSfx.IsLooped = true; + _loopingSfx.Volume = 0.5f; + _loopingSfx.Play(); + } + + public void SetLoopingSfxVolume(float volume) + { + if (volume < 0 || volume > 1) + throw new ArgumentException($"Volume {volume} must be between 0 and 1", nameof(volume)); + + if (_loopingSfx != null) + _loopingSfx.Volume = volume; } public void StopLoopingSfx() { - _activeSfx?.Stop(); - _activeSfx?.Dispose(); + _loopingSfx?.Stop(); + _loopingSfx?.Dispose(); } public void Dispose() @@ -71,6 +81,8 @@ public interface ISfxPlayer : IDisposable void PlayLoopingSfx(SoundEffectID id); + void SetLoopingSfxVolume(float volume); + void StopLoopingSfx(); } } diff --git a/EndlessClient/Rendering/Character/CharacterRenderer.cs b/EndlessClient/Rendering/Character/CharacterRenderer.cs index 4e717d89d..3b6e2d1e6 100644 --- a/EndlessClient/Rendering/Character/CharacterRenderer.cs +++ b/EndlessClient/Rendering/Character/CharacterRenderer.cs @@ -9,6 +9,7 @@ using EndlessClient.Rendering.Sprites; using EndlessClient.UIControls; using EOLib; +using EOLib.Config; using EOLib.Domain.Character; using EOLib.Domain.Extensions; using EOLib.Domain.Map; @@ -38,7 +39,9 @@ public class CharacterRenderer : DrawableGameComponent, ICharacterRenderer private readonly IGameStateProvider _gameStateProvider; private readonly ICurrentMapProvider _currentMapProvider; private readonly IUserInputProvider _userInputProvider; + private readonly IConfigurationProvider _configurationProvider; private readonly ISfxPlayer _sfxPlayer; + private readonly IEffectRenderer _effectRenderer; private EOLib.Domain.Character.Character _character; @@ -95,6 +98,7 @@ public CharacterRenderer(INativeGraphicsManager nativeGraphicsmanager, IGameStateProvider gameStateProvider, ICurrentMapProvider currentMapProvider, IUserInputProvider userInputProvider, + IConfigurationProvider configurationProvider, ISfxPlayer sfxPlayer) : base(game) { @@ -111,6 +115,7 @@ public CharacterRenderer(INativeGraphicsManager nativeGraphicsmanager, _gameStateProvider = gameStateProvider; _currentMapProvider = currentMapProvider; _userInputProvider = userInputProvider; + _configurationProvider = configurationProvider; _sfxPlayer = sfxPlayer; _effectRenderer = new EffectRenderer(nativeGraphicsmanager, _sfxPlayer, this); @@ -183,7 +188,8 @@ public override void Update(GameTime gameTime) if (_gameStateProvider.CurrentState == GameStates.PlayingTheGame) { - UpdateNameLabel(gameTime); + UpdateNameLabel(); + UpdateAmbientNoiseVolume(); if (DrawArea.Contains(_userInputProvider.CurrentMouseState.Position)) { @@ -341,7 +347,7 @@ private int GetMainCharacterOffsetY() return _renderOffsetCalculator.CalculateOffsetY(_characterProvider.MainCharacter.RenderProperties); } - private void UpdateNameLabel(GameTime gameTime) + private void UpdateNameLabel() { if (_gameStateProvider.CurrentState != GameStates.PlayingTheGame || _healthBarRenderer == null || @@ -383,6 +389,20 @@ private Vector2 GetNameLabelPosition() TopPixelWithOffset - 8 - _nameLabel.ActualHeight); } + // todo: find a better place to put this + private void UpdateAmbientNoiseVolume() + { + if (_currentMapProvider.CurrentMap.Properties.AmbientNoise <= 0 || !_configurationProvider.SoundEnabled || Character != _characterProvider.MainCharacter) + return; + + // the algorithm in EO main seems to scale volume with distance to the closest ambient source + // distance is the sum of the components of the vector from character position to closest ambient source + // this is scaled from 0-25, with 0 being on top of the tile and 25 being too far away to hear the ambient sound from it + var props = _characterProvider.MainCharacter.RenderProperties; + var distance = _currentMapProvider.CurrentMap.GetDistanceToClosestTileSpec(TileSpec.AmbientSource, new MapCoordinate(props.MapX, props.MapY)); + _sfxPlayer.SetLoopingSfxVolume(Math.Max((25 - distance) / 25f, 0)); + } + private bool GetIsSteppingStone(CharacterRenderProperties renderProps) { if (_gameStateProvider.CurrentState != GameStates.PlayingTheGame) diff --git a/EndlessClient/Rendering/Factories/CharacterRendererFactory.cs b/EndlessClient/Rendering/Factories/CharacterRendererFactory.cs index bea956a50..e57bda633 100644 --- a/EndlessClient/Rendering/Factories/CharacterRendererFactory.cs +++ b/EndlessClient/Rendering/Factories/CharacterRendererFactory.cs @@ -7,6 +7,7 @@ using EndlessClient.Rendering.CharacterProperties; using EndlessClient.Rendering.Chat; using EndlessClient.Rendering.Sprites; +using EOLib.Config; using EOLib.Domain.Character; using EOLib.Domain.Map; using EOLib.Graphics; @@ -31,6 +32,7 @@ public class CharacterRendererFactory : ICharacterRendererFactory private readonly IGameStateProvider _gameStateProvider; private readonly ICurrentMapProvider _currentMapProvider; private readonly IUserInputProvider _userInputProvider; + private readonly IConfigurationProvider _configurationProvider; private readonly ISfxPlayer _sfxPlayer; public CharacterRendererFactory(INativeGraphicsManager nativeGraphicsManager, @@ -47,6 +49,7 @@ public CharacterRendererFactory(INativeGraphicsManager nativeGraphicsManager, IGameStateProvider gameStateProvider, ICurrentMapProvider currentMapProvider, IUserInputProvider userInputProvider, + IConfigurationProvider configurationProvider, ISfxPlayer sfxPlayer) { _nativeGraphicsManager = nativeGraphicsManager; @@ -63,6 +66,7 @@ public CharacterRendererFactory(INativeGraphicsManager nativeGraphicsManager, _gameStateProvider = gameStateProvider; _currentMapProvider = currentMapProvider; _userInputProvider = userInputProvider; + _configurationProvider = configurationProvider; _sfxPlayer = sfxPlayer; } @@ -84,6 +88,7 @@ public ICharacterRenderer CreateCharacterRenderer(EOLib.Domain.Character.Charact _gameStateProvider, _currentMapProvider, _userInputProvider, + _configurationProvider, _sfxPlayer); } }