diff --git a/EOLib.IO/Map/MapEffect.cs b/EOLib.IO/Map/MapEffect.cs index 2e173f45d..ba0039932 100644 --- a/EOLib.IO/Map/MapEffect.cs +++ b/EOLib.IO/Map/MapEffect.cs @@ -8,6 +8,9 @@ public enum MapEffect : byte Quake1 = 3, Quake2 = 4, Quake3 = 5, - Quake4 = 6 + Quake4 = 6, + + // not a recognized value for IO; used internally + Spikes = 0x7f } } \ No newline at end of file diff --git a/EOLib/Domain/Notifiers/IEffectNotifier.cs b/EOLib/Domain/Notifiers/IEffectNotifier.cs index d3f38e510..57c5e5892 100644 --- a/EOLib/Domain/Notifiers/IEffectNotifier.cs +++ b/EOLib/Domain/Notifiers/IEffectNotifier.cs @@ -1,5 +1,6 @@ using AutomaticTypeMapper; using EOLib.Domain.Map; +using EOLib.IO.Map; namespace EOLib.Domain.Notifiers { @@ -8,7 +9,7 @@ public interface IEffectNotifier void NotifyWarpLeaveEffect(short characterId, WarpAnimation anim); void NotifyWarpEnterEffect(short characterId, WarpAnimation anim); void NotifyPotionEffect(short playerId, int effectId); - void NotifyEarthquake(byte strength); + void NotifyMapEffect(MapEffect effect, byte strength = 0); } [AutoMappedType] @@ -17,6 +18,6 @@ public class NoOpEffectNotifier : IEffectNotifier public void NotifyWarpLeaveEffect(short characterId, WarpAnimation anim) { } public void NotifyWarpEnterEffect(short characterId, WarpAnimation anim) { } public void NotifyPotionEffect(short playerId, int effectId) { } - public void NotifyEarthquake(byte strength) { } + public void NotifyMapEffect(MapEffect effect, byte strength = 0) { } } } diff --git a/EOLib/PacketHandlers/Effects/MapDebuffHandler.cs b/EOLib/PacketHandlers/Effects/MapDebuffHandler.cs index 1f9fec0f8..6ed791c55 100644 --- a/EOLib/PacketHandlers/Effects/MapDebuffHandler.cs +++ b/EOLib/PacketHandlers/Effects/MapDebuffHandler.cs @@ -2,6 +2,7 @@ using EOLib.Domain.Character; using EOLib.Domain.Login; using EOLib.Domain.Notifiers; +using EOLib.IO.Map; using EOLib.Net; using EOLib.Net.Handlers; using System; @@ -17,6 +18,7 @@ public class MapDebuffHandler : InGameOnlyPacketHandler private readonly ICharacterRepository _characterRepository; private readonly IEnumerable _mainCharacterEventNotifiers; + private readonly IEnumerable _effectNotifiers; public override PacketFamily Family => PacketFamily.Effect; @@ -24,11 +26,13 @@ public class MapDebuffHandler : InGameOnlyPacketHandler public MapDebuffHandler(IPlayerInfoProvider playerInfoProvider, ICharacterRepository characterRepository, - IEnumerable mainCharacterEventNotifiers) + IEnumerable mainCharacterEventNotifiers, + IEnumerable effectNotifiers) : base(playerInfoProvider) { _characterRepository = characterRepository; _mainCharacterEventNotifiers = mainCharacterEventNotifiers; + _effectNotifiers = effectNotifiers; } public override bool HandlePacket(IPacket packet) @@ -50,6 +54,9 @@ public override bool HandlePacket(IPacket packet) _characterRepository.MainCharacter = character.WithStats( originalStats.WithNewStat(CharacterStat.TP, tp) .WithNewStat(CharacterStat.MaxTP, maxTp)); + + foreach (var notifier in _effectNotifiers) + notifier.NotifyMapEffect(MapEffect.TPDrain); } break; case EFFECT_DAMAGE_SPIKE: diff --git a/EOLib/PacketHandlers/Effects/MapHpDrainHandler.cs b/EOLib/PacketHandlers/Effects/MapHpDrainHandler.cs index 21cbc973d..831dbe027 100644 --- a/EOLib/PacketHandlers/Effects/MapHpDrainHandler.cs +++ b/EOLib/PacketHandlers/Effects/MapHpDrainHandler.cs @@ -4,6 +4,7 @@ using EOLib.Domain.Extensions; using EOLib.Domain.Login; using EOLib.Domain.Notifiers; +using EOLib.IO.Map; using EOLib.Net; using EOLib.Net.Handlers; using System; @@ -17,6 +18,7 @@ public class MapHpDrainHandler : InGameOnlyPacketHandler private readonly ICharacterRepository _characterRepository; private readonly IEnumerable _mainCharacterEventNotifiers; private readonly IEnumerable _otherCharacterEventNotifiers; + private readonly IEnumerable _effectNotifiers; public override PacketFamily Family => PacketFamily.Effect; @@ -25,12 +27,14 @@ public class MapHpDrainHandler : InGameOnlyPacketHandler public MapHpDrainHandler(IPlayerInfoProvider playerInfoProvider, ICharacterRepository characterRepository, IEnumerable mainCharacterEventNotifiers, - IEnumerable otherCharacterEventNotifiers) + IEnumerable otherCharacterEventNotifiers, + IEnumerable effectNotifiers) : base(playerInfoProvider) { _characterRepository = characterRepository; _mainCharacterEventNotifiers = mainCharacterEventNotifiers; _otherCharacterEventNotifiers = otherCharacterEventNotifiers; + _effectNotifiers = effectNotifiers; } public override bool HandlePacket(IPacket packet) @@ -42,7 +46,10 @@ public override bool HandlePacket(IPacket packet) _characterRepository.MainCharacter = _characterRepository.MainCharacter.WithDamage(damage, hp == 0); foreach (var notifier in _mainCharacterEventNotifiers) - notifier.NotifyTakeDamage(damage, (int)Math.Round((double)hp / maxhp), isHeal: false); + notifier.NotifyTakeDamage(damage, (int)Math.Round(((double)hp / maxhp) * 100), isHeal: false); + + foreach (var notifier in _effectNotifiers) + notifier.NotifyMapEffect(MapEffect.HPDrain); while (packet.ReadPosition != packet.Length) { diff --git a/EOLib/PacketHandlers/Effects/MapQuakeHandler.cs b/EOLib/PacketHandlers/Effects/MapQuakeHandler.cs index 6ce8f265b..663c3d3cc 100644 --- a/EOLib/PacketHandlers/Effects/MapQuakeHandler.cs +++ b/EOLib/PacketHandlers/Effects/MapQuakeHandler.cs @@ -1,6 +1,7 @@ using AutomaticTypeMapper; using EOLib.Domain.Login; using EOLib.Domain.Notifiers; +using EOLib.IO.Map; using EOLib.Net; using EOLib.Net.Handlers; using System.Collections.Generic; @@ -32,7 +33,7 @@ public override bool HandlePacket(IPacket packet) var strength = packet.ReadChar(); foreach (var notifier in _effectNotifiers) - notifier.NotifyEarthquake(strength); + notifier.NotifyMapEffect(MapEffect.Quake1, strength); return true; } diff --git a/EOLib/PacketHandlers/Effects/TimedSpikeEffectHandler.cs b/EOLib/PacketHandlers/Effects/TimedSpikeEffectHandler.cs index 19ede42f2..525162c23 100644 --- a/EOLib/PacketHandlers/Effects/TimedSpikeEffectHandler.cs +++ b/EOLib/PacketHandlers/Effects/TimedSpikeEffectHandler.cs @@ -1,26 +1,37 @@ using AutomaticTypeMapper; using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.IO.Map; using EOLib.Net; using EOLib.Net.Handlers; +using System.Collections.Generic; namespace EOLib.PacketHandlers.Effects { [AutoMappedType] public class TimedSpikeEffectHandler : InGameOnlyPacketHandler { + private readonly IEnumerable _effectNotifiers; + public override PacketFamily Family => PacketFamily.Effect; public override PacketAction Action => PacketAction.Report; - public TimedSpikeEffectHandler(IPlayerInfoProvider playerInfoProvider) - : base(playerInfoProvider) { } + public TimedSpikeEffectHandler(IPlayerInfoProvider playerInfoProvider, + IEnumerable effectNotifiers) + : base(playerInfoProvider) + { + _effectNotifiers = effectNotifiers; + } public override bool HandlePacket(IPacket packet) { - if ((char)packet.ReadChar() != 'S') + if ((char)packet.ReadByte() != 'S') return false; - // todo: play sound effect for timed spikes + foreach (var notifier in _effectNotifiers) + notifier.NotifyMapEffect(MapEffect.Spikes); + return true; } } diff --git a/EndlessClient/Controllers/ArrowKeyController.cs b/EndlessClient/Controllers/ArrowKeyController.cs index 2bcbc0ac8..000bf3012 100644 --- a/EndlessClient/Controllers/ArrowKeyController.cs +++ b/EndlessClient/Controllers/ArrowKeyController.cs @@ -136,7 +136,7 @@ private void AttemptToStartWalking() coordinate = new MapCoordinate( _characterProvider.MainCharacter.RenderProperties.GetDestinationX(), _characterProvider.MainCharacter.RenderProperties.GetDestinationY()); - _spikeTrapActions.ShowSpikeTrap(coordinate); + _spikeTrapActions.ShowSpikeTrap(coordinate, isMainCharacter: true); } } } diff --git a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs index 17c5ed1bf..428ddbbe1 100644 --- a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs +++ b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs @@ -80,6 +80,11 @@ public void StartWalking() CancelSpellPrep(); Animator.StartMainCharacterWalkAnimation(Option.None()); ShowWaterSplashiesIfNeeded(CharacterActionState.Walking, _characterRepository.MainCharacter.ID); + + if (_characterRepository.MainCharacter.NoWall) + _sfxPlayer.PlaySfx(SoundEffectID.NoWallWalk); + else if (IsSteppingStone(_characterRepository.MainCharacter.RenderProperties)) + _sfxPlayer.PlaySfx(SoundEffectID.JumpStone); } public void StartAttacking(int noteIndex = -1) @@ -117,7 +122,7 @@ public void Emote(Emote whichEmote) public void StartOtherCharacterWalkAnimation(int characterID, byte destinationX, byte destinationY, EODirection direction) { - if (!_hudControlProvider.IsInGame) + if (!_hudControlProvider.IsInGame || !_currentMapStateProvider.Characters.ContainsKey(characterID)) return; Animator.StartOtherCharacterWalkAnimation(characterID, destinationX, destinationY, direction); @@ -125,6 +130,9 @@ public void StartOtherCharacterWalkAnimation(int characterID, byte destinationX, ShowWaterSplashiesIfNeeded(CharacterActionState.Walking, characterID); _spikeTrapActions.HideSpikeTrap(characterID); _spikeTrapActions.ShowSpikeTrap(characterID); + + if (IsSteppingStone(_currentMapStateProvider.Characters[characterID].RenderProperties)) + _sfxPlayer.PlaySfx(SoundEffectID.JumpStone); } public void StartOtherCharacterAttackAnimation(int characterID, int noteIndex = -1) @@ -223,10 +231,28 @@ public void NotifyTargetOtherSpellCast(short sourcePlayerID, short targetPlayerI } } - public void NotifyEarthquake(byte strength) + public void NotifyMapEffect(MapEffect effect, byte strength = 0) { - var mapRenderer = _hudControlProvider.GetComponent(HudControlIdentifier.MapRenderer); - mapRenderer.StartEarthquake(strength); + switch (effect) + { + case MapEffect.Quake1: + case MapEffect.Quake2: + case MapEffect.Quake3: + case MapEffect.Quake4: + var mapRenderer = _hudControlProvider.GetComponent(HudControlIdentifier.MapRenderer); + mapRenderer.StartEarthquake(strength); + _sfxPlayer.PlaySfx(SoundEffectID.Earthquake); + break; + case MapEffect.HPDrain: + _sfxPlayer.PlaySfx(SoundEffectID.MapEffectHPDrain); + break; + case MapEffect.TPDrain: + _sfxPlayer.PlaySfx(SoundEffectID.MapEffectTPDrain); + break; + case MapEffect.Spikes: + _sfxPlayer.PlaySfx(SoundEffectID.Spikes); + break; + } } public void NotifyEmote(short playerId, Emote emote) @@ -338,6 +364,12 @@ private bool IsInstrumentWeapon(int weaponGraphic) .Match(some: x => Constants.InstrumentIDs.ToList().FindIndex(y => y == x.ID) >= 0, none: () => false); } + private bool IsSteppingStone(CharacterRenderProperties renderProps) + { + return _currentMapProvider.CurrentMap.Tiles[renderProps.MapY, renderProps.MapX] == TileSpec.Jump + || _currentMapProvider.CurrentMap.Tiles[renderProps.GetDestinationY(), renderProps.GetDestinationX()] == TileSpec.Jump; + } + private ICharacterAnimator Animator => _hudControlProvider.GetComponent(HudControlIdentifier.CharacterAnimator); } diff --git a/EndlessClient/Rendering/Map/DynamicMapObjectUpdater.cs b/EndlessClient/Rendering/Map/DynamicMapObjectUpdater.cs index 9ff15e212..994fc7e41 100644 --- a/EndlessClient/Rendering/Map/DynamicMapObjectUpdater.cs +++ b/EndlessClient/Rendering/Map/DynamicMapObjectUpdater.cs @@ -1,4 +1,5 @@ using AutomaticTypeMapper; +using EndlessClient.Audio; using EndlessClient.Controllers; using EndlessClient.Input; using EOLib.Domain.Character; @@ -30,6 +31,7 @@ private class DoorTimePair private readonly ICurrentMapProvider _currentMapProvider; private readonly IMapObjectBoundsCalculator _mapObjectBoundsCalculator; private readonly IMapInteractionController _mapInteractionController; + private readonly ISfxPlayer _sfxPlayer; private readonly List _cachedDoorState; public DynamicMapObjectUpdater(ICharacterProvider characterProvider, @@ -37,7 +39,8 @@ public DynamicMapObjectUpdater(ICharacterProvider characterProvider, IUserInputRepository userInputRepository, ICurrentMapProvider currentMapProvider, IMapObjectBoundsCalculator mapObjectBoundsCalculator, - IMapInteractionController mapInteractionController) + IMapInteractionController mapInteractionController, + ISfxPlayer sfxPlayer) { _characterProvider = characterProvider; _currentMapStateRepository = currentMapStateRepository; @@ -45,6 +48,7 @@ public DynamicMapObjectUpdater(ICharacterProvider characterProvider, _currentMapProvider = currentMapProvider; _mapObjectBoundsCalculator = mapObjectBoundsCalculator; _mapInteractionController = mapInteractionController; + _sfxPlayer = sfxPlayer; _cachedDoorState = new List(); } @@ -63,7 +67,10 @@ private void OpenNewDoors(DateTime now) { var newDoors = _currentMapStateRepository.OpenDoors.Where(x => _cachedDoorState.All(d => d.Door != x)); foreach (var door in newDoors) + { _cachedDoorState.Add(new DoorTimePair { Door = door, OpenTime = now }); + _sfxPlayer.PlaySfx(SoundEffectID.DoorOpen); + } } private void CloseExpiredDoors(DateTime now) @@ -72,7 +79,11 @@ private void CloseExpiredDoors(DateTime now) foreach (var door in expiredDoors) { _cachedDoorState.Remove(door); - _currentMapStateRepository.OpenDoors.Remove(door.Door); + if (_currentMapStateRepository.OpenDoors.Contains(door.Door)) + { + _currentMapStateRepository.OpenDoors.Remove(door.Door); + _sfxPlayer.PlaySfx(SoundEffectID.DoorClose); + } } } diff --git a/EndlessClient/Rendering/Map/MapChangedActions.cs b/EndlessClient/Rendering/Map/MapChangedActions.cs index 3fdb6962f..1dee65e00 100644 --- a/EndlessClient/Rendering/Map/MapChangedActions.cs +++ b/EndlessClient/Rendering/Map/MapChangedActions.cs @@ -71,6 +71,7 @@ public void NotifyMapChanged(WarpAnimation warpAnimation, bool differentMapID) StopAllAnimations(); ClearCharacterRenderersAndCache(); ClearNPCRenderersAndCache(); + ClearOpenDoors(); ShowMapNameIfAvailable(differentMapID); //todo: show message if map is a PK map ShowMapTransition(differentMapID); @@ -110,6 +111,11 @@ private void ClearNPCRenderersAndCache() _npcStateCache.Reset(); } + private void ClearOpenDoors() + { + _currentMapStateRepository.OpenDoors.Clear(); + } + private void ShowMapNameIfAvailable(bool differentMapID) { if (!differentMapID || string.IsNullOrWhiteSpace(_currentMapProvider.CurrentMap.Properties.Name)) diff --git a/EndlessClient/Rendering/Map/SpikeTrapActions.cs b/EndlessClient/Rendering/Map/SpikeTrapActions.cs index 36ba12cd5..3b94a6260 100644 --- a/EndlessClient/Rendering/Map/SpikeTrapActions.cs +++ b/EndlessClient/Rendering/Map/SpikeTrapActions.cs @@ -1,4 +1,5 @@ using AutomaticTypeMapper; +using EndlessClient.Audio; using EOLib.Domain.Extensions; using EOLib.Domain.Map; using EOLib.IO.Map; @@ -10,20 +11,28 @@ public class SpikeTrapActions : ISpikeTrapActions { private readonly ICurrentMapStateRepository _currentMapStateRepository; private readonly ICurrentMapProvider _currentMapProvider; + private readonly ISfxPlayer _sfxPlayer; public SpikeTrapActions(ICurrentMapStateRepository currentMapStateRepository, - ICurrentMapProvider currentMapProvider) + ICurrentMapProvider currentMapProvider, + ISfxPlayer sfxPlayer) { _currentMapStateRepository = currentMapStateRepository; _currentMapProvider = currentMapProvider; + _sfxPlayer = sfxPlayer; } - public void ShowSpikeTrap(MapCoordinate coordinate) + public void ShowSpikeTrap(MapCoordinate coordinate, bool isMainCharacter = false) { if (!_currentMapStateRepository.VisibleSpikeTraps.Contains(coordinate) && _currentMapProvider.CurrentMap.Tiles[coordinate.Y, coordinate.X] == TileSpec.SpikesTrap) { _currentMapStateRepository.VisibleSpikeTraps.Add(coordinate); + + if (isMainCharacter) + { + _sfxPlayer.PlaySfx(SoundEffectID.Spikes); + } } } @@ -57,7 +66,7 @@ public void HideSpikeTrap(int characterId) public interface ISpikeTrapActions { - void ShowSpikeTrap(MapCoordinate coordinate); + void ShowSpikeTrap(MapCoordinate coordinate, bool isMainCharacter = false); void ShowSpikeTrap(int characterId);