From c9bb922d13bd3ca716e6f7e6058d92c81f56fed5 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Wed, 7 Sep 2022 12:32:14 -0700 Subject: [PATCH] Implement EFFECT_AGREE handler for rendering an effect at given coordinates --- EOLib/Domain/Notifiers/IEffectNotifier.cs | 10 +++ .../Effects/EffectAgreeHandler.cs | 37 ++++++++ .../Character/CharacterAnimationActions.cs | 9 ++ .../Effects/CustomEffectSpriteInfo.cs | 21 +++++ .../Rendering/Effects/EffectSpriteManager.cs | 13 ++- .../Factories/MapGridEffectTargetFactory.cs | 53 ++++++++++++ .../Rendering/Factories/MapRendererFactory.cs | 8 +- EndlessClient/Rendering/Map/IMapRenderer.cs | 4 + .../Rendering/Map/MapChangedActions.cs | 4 +- .../Rendering/Map/MapGridEffectTarget.cs | 86 +++++++++++++++++++ EndlessClient/Rendering/Map/MapRenderer.cs | 39 ++++++++- .../Rendering/RenderOffsetCalculator.cs | 15 ++++ 12 files changed, 292 insertions(+), 7 deletions(-) create mode 100644 EOLib/PacketHandlers/Effects/EffectAgreeHandler.cs create mode 100644 EndlessClient/Rendering/Effects/CustomEffectSpriteInfo.cs create mode 100644 EndlessClient/Rendering/Factories/MapGridEffectTargetFactory.cs create mode 100644 EndlessClient/Rendering/Map/MapGridEffectTarget.cs diff --git a/EOLib/Domain/Notifiers/IEffectNotifier.cs b/EOLib/Domain/Notifiers/IEffectNotifier.cs index 57c5e5892..604a7382c 100644 --- a/EOLib/Domain/Notifiers/IEffectNotifier.cs +++ b/EOLib/Domain/Notifiers/IEffectNotifier.cs @@ -7,17 +7,27 @@ namespace EOLib.Domain.Notifiers public interface IEffectNotifier { void NotifyWarpLeaveEffect(short characterId, WarpAnimation anim); + void NotifyWarpEnterEffect(short characterId, WarpAnimation anim); + void NotifyPotionEffect(short playerId, int effectId); + void NotifyMapEffect(MapEffect effect, byte strength = 0); + + void NotifyEffectAtLocation(byte x, byte y, short effectId); } [AutoMappedType] 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 NotifyMapEffect(MapEffect effect, byte strength = 0) { } + + public void NotifyEffectAtLocation(byte x, byte y, short effectId) { } } } diff --git a/EOLib/PacketHandlers/Effects/EffectAgreeHandler.cs b/EOLib/PacketHandlers/Effects/EffectAgreeHandler.cs new file mode 100644 index 000000000..ff9d9e7d8 --- /dev/null +++ b/EOLib/PacketHandlers/Effects/EffectAgreeHandler.cs @@ -0,0 +1,37 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Net; +using EOLib.Net.Handlers; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Effects +{ + [AutoMappedType] + public class EffectAgreeHandler : InGameOnlyPacketHandler + { + private readonly IEnumerable _effectNotifiers; + + public override PacketFamily Family => PacketFamily.Effect; + public override PacketAction Action => PacketAction.Agree; + + public EffectAgreeHandler(IPlayerInfoProvider playerInfoProvider, + IEnumerable effectNotifiers) + : base(playerInfoProvider) + { + _effectNotifiers = effectNotifiers; + } + + public override bool HandlePacket(IPacket packet) + { + var x = packet.ReadChar(); + var y = packet.ReadChar(); + var effectId = packet.ReadShort(); + + foreach (var notifier in _effectNotifiers) + notifier.NotifyEffectAtLocation(x, y, effectId); + + return true; + } + } +} diff --git a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs index 0e504063f..0aa2eade2 100644 --- a/EndlessClient/Rendering/Character/CharacterAnimationActions.cs +++ b/EndlessClient/Rendering/Character/CharacterAnimationActions.cs @@ -306,6 +306,15 @@ public void MakeMainPlayerDrunk() _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_ITEM_USE_DRUNK); } + public void NotifyEffectAtLocation(byte x, byte y, short effectId) + { + if (_hudControlProvider.IsInGame) + { + _hudControlProvider.GetComponent(HudControlIdentifier.MapRenderer) + .RenderEffect(x, y, effectId); + } + } + private void ShowWaterSplashiesIfNeeded(CharacterActionState action, int characterID) { var character = characterID == _characterRepository.MainCharacter.ID diff --git a/EndlessClient/Rendering/Effects/CustomEffectSpriteInfo.cs b/EndlessClient/Rendering/Effects/CustomEffectSpriteInfo.cs new file mode 100644 index 000000000..3f82db48c --- /dev/null +++ b/EndlessClient/Rendering/Effects/CustomEffectSpriteInfo.cs @@ -0,0 +1,21 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace EndlessClient.Rendering.Effects +{ + public class CustomEffectSpriteInfo : EffectSpriteInfo + { + public CustomEffectSpriteInfo(int numberOfFrames, int repeats, bool onTopOfCharacter, int alpha, Texture2D graphic) + : base(numberOfFrames, repeats, onTopOfCharacter, alpha, graphic) + { + } + + protected override Vector2 GetDrawLocation(Rectangle textureSourceRectangle, Rectangle targetActorRectangle) + { + var targetX = targetActorRectangle.X + (targetActorRectangle.Width - textureSourceRectangle.Width) / 2 - targetActorRectangle.Width / 2; + var targetY = targetActorRectangle.Y - textureSourceRectangle.Height; + + return new Vector2(targetX, targetY); + } + } +} diff --git a/EndlessClient/Rendering/Effects/EffectSpriteManager.cs b/EndlessClient/Rendering/Effects/EffectSpriteManager.cs index bb521e4cc..503171bd2 100644 --- a/EndlessClient/Rendering/Effects/EffectSpriteManager.cs +++ b/EndlessClient/Rendering/Effects/EffectSpriteManager.cs @@ -80,8 +80,17 @@ private IList ResolveSpellEffect(HardCodedSpellGraphic effect return new List(retList); } - //not implemented spell graphics will just not render anything - return new IEffectSpriteInfo[] { }; + // not implemented spell graphics have a default rendering set + // spell effects seem to start at GFX 128 and go in sets of 3, indexed on (spellGraphic - 10) + // first gfx is behind character, other 2 are in front + // 255 alpha assumed until proven otherwise + // 4 frames assumed until proven otherwise + return new List + { + new CustomEffectSpriteInfo(4, 1, false, 255, GetGraphic(((int)effect - 9)*3 + 128)), + new CustomEffectSpriteInfo(4, 1, true, 255, GetGraphic(((int)effect - 9)*3 + 129)), + new CustomEffectSpriteInfo(4, 1, true, 255, GetGraphic(((int)effect - 9)*3 + 130)) + }; } private IList GetWarpEffect(EffectType warpEffect) diff --git a/EndlessClient/Rendering/Factories/MapGridEffectTargetFactory.cs b/EndlessClient/Rendering/Factories/MapGridEffectTargetFactory.cs new file mode 100644 index 000000000..769cbd06c --- /dev/null +++ b/EndlessClient/Rendering/Factories/MapGridEffectTargetFactory.cs @@ -0,0 +1,53 @@ +using AutomaticTypeMapper; +using EndlessClient.Audio; +using EndlessClient.Rendering.Character; +using EndlessClient.Rendering.Effects; +using EndlessClient.Rendering.Map; +using EOLib.Domain.Character; +using EOLib.Domain.Map; +using EOLib.Graphics; + +namespace EndlessClient.Rendering.Factories +{ + [AutoMappedType] + public class MapGridEffectTargetFactory : IMapGridEffectTargetFactory + { + private readonly INativeGraphicsManager _nativeGraphicsManager; + private readonly ISfxPlayer _sfxPlayer; + private readonly IRenderOffsetCalculator _renderOffsetCalculator; + private readonly ICharacterRendererProvider _characterRendererProvider; + private readonly ICharacterTextures _characterTextures; + + public MapGridEffectTargetFactory(INativeGraphicsManager nativeGraphicsManager, + ISfxPlayer sfxPlayer, + IRenderOffsetCalculator renderOffsetCalculator, + ICharacterRendererProvider characterRendererProvider, + ICharacterTextures characterTextures) + { + _nativeGraphicsManager = nativeGraphicsManager; + _sfxPlayer = sfxPlayer; + _renderOffsetCalculator = renderOffsetCalculator; + _characterRendererProvider = characterRendererProvider; + _characterTextures = characterTextures; + } + + public IMapGridEffectTarget Create(byte x, byte y) + { + if (_characterTextures.Skin == null) + _characterTextures.Refresh(new CharacterRenderProperties.Builder().ToImmutable()); + + return new MapGridEffectTarget( + _nativeGraphicsManager, + _sfxPlayer, + _renderOffsetCalculator, + _characterRendererProvider, + _characterTextures, + new MapCoordinate(x, y)); + } + } + + public interface IMapGridEffectTargetFactory + { + IMapGridEffectTarget Create(byte x, byte y); + } +} diff --git a/EndlessClient/Rendering/Factories/MapRendererFactory.cs b/EndlessClient/Rendering/Factories/MapRendererFactory.cs index de3115b93..fb5e1ca3e 100644 --- a/EndlessClient/Rendering/Factories/MapRendererFactory.cs +++ b/EndlessClient/Rendering/Factories/MapRendererFactory.cs @@ -24,6 +24,7 @@ public class MapRendererFactory : IMapRendererFactory private readonly IConfigurationProvider _configurationProvider; private readonly IMouseCursorRendererFactory _mouseCursorRendererFactory; private readonly IRenderOffsetCalculator _renderOffsetCalculator; + private readonly IMapGridEffectTargetFactory _mapGridEffectTargetFactory; private readonly INPCRendererUpdater _npcRendererUpdater; private readonly IDynamicMapObjectUpdater _dynamicMapObjectUpdater; @@ -38,7 +39,8 @@ public class MapRendererFactory : IMapRendererFactory IDynamicMapObjectUpdater dynamicMapObjectUpdater, IConfigurationProvider configurationProvider, IMouseCursorRendererFactory mouseCursorRendererFactory, - IRenderOffsetCalculator renderOffsetCalculator) + IRenderOffsetCalculator renderOffsetCalculator, + IMapGridEffectTargetFactory mapGridEffectTargetFactory) { _endlessGameProvider = endlessGameProvider; _renderTargetFactory = renderTargetFactory; @@ -52,6 +54,7 @@ public class MapRendererFactory : IMapRendererFactory _configurationProvider = configurationProvider; _mouseCursorRendererFactory = mouseCursorRendererFactory; _renderOffsetCalculator = renderOffsetCalculator; + _mapGridEffectTargetFactory = mapGridEffectTargetFactory; } public IMapRenderer CreateMapRenderer() @@ -67,7 +70,8 @@ public IMapRenderer CreateMapRenderer() _dynamicMapObjectUpdater, _configurationProvider, _mouseCursorRendererFactory.Create(), - _renderOffsetCalculator); + _renderOffsetCalculator, + _mapGridEffectTargetFactory); } } } \ No newline at end of file diff --git a/EndlessClient/Rendering/Map/IMapRenderer.cs b/EndlessClient/Rendering/Map/IMapRenderer.cs index 61380d596..e45115f0f 100644 --- a/EndlessClient/Rendering/Map/IMapRenderer.cs +++ b/EndlessClient/Rendering/Map/IMapRenderer.cs @@ -14,5 +14,9 @@ public interface IMapRenderer : IGameComponent void StartEarthquake(byte strength); void RedrawGroundLayer(); + + void RenderEffect(byte x, byte y, short effectId); + + void ClearTransientRenderables(); } } diff --git a/EndlessClient/Rendering/Map/MapChangedActions.cs b/EndlessClient/Rendering/Map/MapChangedActions.cs index f9334a410..460603c40 100644 --- a/EndlessClient/Rendering/Map/MapChangedActions.cs +++ b/EndlessClient/Rendering/Map/MapChangedActions.cs @@ -153,9 +153,11 @@ private void ShowMapNameIfAvailable(bool differentMapID) private void ShowMapTransition(bool showMapTransition) { + var mapRenderer = _hudControlProvider.GetComponent(HudControlIdentifier.MapRenderer); + mapRenderer.ClearTransientRenderables(); + if (showMapTransition) { - var mapRenderer = _hudControlProvider.GetComponent(HudControlIdentifier.MapRenderer); mapRenderer.StartMapTransition(); } } diff --git a/EndlessClient/Rendering/Map/MapGridEffectTarget.cs b/EndlessClient/Rendering/Map/MapGridEffectTarget.cs new file mode 100644 index 000000000..1a4ebc103 --- /dev/null +++ b/EndlessClient/Rendering/Map/MapGridEffectTarget.cs @@ -0,0 +1,86 @@ +using EndlessClient.Audio; +using EndlessClient.Rendering.Character; +using EndlessClient.Rendering.Effects; +using EOLib.Domain.Map; +using EOLib.Graphics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace EndlessClient.Rendering.Map +{ + public class MapGridEffectTarget : IMapGridEffectTarget + { + private IEffectRenderer _renderer; + private readonly IRenderOffsetCalculator _renderOffsetCalculator; + private readonly ICharacterRendererProvider _characterRendererProvider; + private readonly ICharacterTextures _characterTextures; + private readonly MapCoordinate _location; + + public Rectangle EffectTargetArea { get; private set; } + + public MapGridEffectTarget(INativeGraphicsManager nativeGraphicsManager, + ISfxPlayer sfxPlayer, + IRenderOffsetCalculator renderOffsetCalculator, + ICharacterRendererProvider characterRendererProvider, + ICharacterTextures characterTextures, + MapCoordinate location) + { + _renderer = new EffectRenderer(nativeGraphicsManager, sfxPlayer, this); + _renderOffsetCalculator = renderOffsetCalculator; + _characterRendererProvider = characterRendererProvider; + _characterTextures = characterTextures; + _location = location; + } + + public bool EffectIsPlaying() => _renderer.State == EffectState.Playing; + + public void ShowPotionAnimation(int potionId) { } + + public void ShowSpellAnimation(int spellGraphic) + { + _renderer.PlayEffect(EffectType.Spell, spellGraphic); + } + + public void ShowWarpArrive() { } + + public void ShowWarpLeave() { } + + public void ShowWaterSplashies() { } + + public void Update() + { + EffectTargetArea = _characterRendererProvider.MainCharacterRenderer + .Match( + some: mainRenderer => + { + var offsetX = _renderOffsetCalculator.CalculateOffsetX(_location); + var offsetY = _renderOffsetCalculator.CalculateOffsetY(_location); + + var mainOffsetX = _renderOffsetCalculator.CalculateOffsetX(mainRenderer.Character.RenderProperties); + var mainOffsetY = _renderOffsetCalculator.CalculateOffsetY(mainRenderer.Character.RenderProperties); + + return new Rectangle( + offsetX + 320 - mainOffsetX, + offsetY + 168 - mainOffsetY, + _characterTextures.Skin.SourceRectangle.Width, + _characterTextures.Skin.SourceRectangle.Height); + }, + none: () => new Rectangle(0, 0, 1, 1)); + + _renderer.Update(); + } + + public void Draw(SpriteBatch sb, bool beginHasBeenCalled = true) + { + _renderer.DrawBehindTarget(sb, beginHasBeenCalled); + _renderer.DrawInFrontOfTarget(sb, beginHasBeenCalled); + } + } + + public interface IMapGridEffectTarget : IEffectTarget + { + void Update(); + + void Draw(SpriteBatch sb, bool beginHasBeenCalled = true); + } +} diff --git a/EndlessClient/Rendering/Map/MapRenderer.cs b/EndlessClient/Rendering/Map/MapRenderer.cs index a84a0b16d..1736f5710 100644 --- a/EndlessClient/Rendering/Map/MapRenderer.cs +++ b/EndlessClient/Rendering/Map/MapRenderer.cs @@ -30,7 +30,7 @@ public class MapRenderer : DrawableGameComponent, IMapRenderer private readonly IConfigurationProvider _configurationProvider; private readonly IMouseCursorRenderer _mouseCursorRenderer; private readonly IRenderOffsetCalculator _renderOffsetCalculator; - + private readonly IMapGridEffectTargetFactory _mapGridEffectTargetFactory; private RenderTarget2D _mapBaseTarget, _mapObjectTarget; private SpriteBatch _sb; private MapTransitionState _mapTransitionState = MapTransitionState.Default; @@ -38,6 +38,8 @@ public class MapRenderer : DrawableGameComponent, IMapRenderer private Option _quakeState; + private IDictionary _mapGridEffectTargets; + public bool MouseOver { get @@ -61,7 +63,8 @@ public bool MouseOver IDynamicMapObjectUpdater dynamicMapObjectUpdater, IConfigurationProvider configurationProvider, IMouseCursorRenderer mouseCursorRenderer, - IRenderOffsetCalculator renderOffsetCalculator) + IRenderOffsetCalculator renderOffsetCalculator, + IMapGridEffectTargetFactory mapGridEffectTargetFactory) : base((Game)endlessGame) { _renderTargetFactory = renderTargetFactory; @@ -75,6 +78,9 @@ public bool MouseOver _configurationProvider = configurationProvider; _mouseCursorRenderer = mouseCursorRenderer; _renderOffsetCalculator = renderOffsetCalculator; + _mapGridEffectTargetFactory = mapGridEffectTargetFactory; + + _mapGridEffectTargets = new Dictionary(); } public override void Initialize() @@ -114,6 +120,9 @@ public override void Update(GameTime gameTime) _mouseCursorRenderer.Update(gameTime); UpdateQuakeState(); + + foreach (var target in _mapGridEffectTargets.Values) + target.Update(); } _lastMapChecksum = _currentMapProvider.CurrentMap.Properties.ChecksumInt; @@ -149,6 +158,28 @@ public void RedrawGroundLayer() _mapTransitionState = new MapTransitionState(Option.Some(DateTime.Now - new TimeSpan(0, 5, 0)), 255); } + public void RenderEffect(byte x, byte y, short effectId) + { + if (_mapGridEffectTargets.TryGetValue(new MapCoordinate(x, y), out var target) && !target.EffectIsPlaying()) + { + target.ShowSpellAnimation(effectId); + } + else + { + var renderer = _mapGridEffectTargetFactory.Create(x, y); + renderer.ShowSpellAnimation(effectId); + + _mapGridEffectTargets[new MapCoordinate(x, y)] = renderer; + } + } + + public void ClearTransientRenderables() + { + _mapGridEffectTargets.Clear(); + // todo: clear NPC speech bubbles + // todo: hide map item/character/npc name + } + private void UpdateQuakeState() { // when quake: @@ -267,6 +298,10 @@ private void DrawToSpriteBatch(SpriteBatch spriteBatch, GameTime gameTime) spriteBatch.Draw(_mapObjectTarget, new Vector2(offset, 0), Color.White); + + foreach (var target in _mapGridEffectTargets.Values) + target.Draw(spriteBatch); + spriteBatch.End(); } diff --git a/EndlessClient/Rendering/RenderOffsetCalculator.cs b/EndlessClient/Rendering/RenderOffsetCalculator.cs index 4af28bdc9..3b839ab5c 100644 --- a/EndlessClient/Rendering/RenderOffsetCalculator.cs +++ b/EndlessClient/Rendering/RenderOffsetCalculator.cs @@ -2,6 +2,7 @@ using EOLib; using EOLib.Domain.Character; using EOLib.Domain.Extensions; +using EOLib.Domain.Map; using EOLib.Domain.NPC; namespace EndlessClient.Rendering @@ -55,6 +56,16 @@ public int CalculateOffsetY(EOLib.Domain.NPC.NPC npc) //walkAdjust * multiplier is the old ViewAdjustY return npc.X*HeightFactor + npc.Y*HeightFactor + walkAdjust*multiplier; } + + public int CalculateOffsetX(MapCoordinate coordinate) + { + return coordinate.X * WidthFactor - coordinate.Y * WidthFactor; + } + + public int CalculateOffsetY(MapCoordinate coordinate) + { + return coordinate.X * HeightFactor + coordinate.Y * HeightFactor; + } } public interface IRenderOffsetCalculator @@ -68,5 +79,9 @@ public interface IRenderOffsetCalculator int CalculateOffsetX(EOLib.Domain.NPC.NPC npc); int CalculateOffsetY(EOLib.Domain.NPC.NPC npc); + + int CalculateOffsetX(MapCoordinate coordinate); + + int CalculateOffsetY(MapCoordinate coordinate); } } \ No newline at end of file