diff --git a/EOLib/Domain/Map/CurrentMapStateRepository.cs b/EOLib/Domain/Map/CurrentMapStateRepository.cs index 8770e6917..31b113ad9 100644 --- a/EOLib/Domain/Map/CurrentMapStateRepository.cs +++ b/EOLib/Domain/Map/CurrentMapStateRepository.cs @@ -24,6 +24,10 @@ public interface ICurrentMapStateRepository HashSet VisibleSpikeTraps { get; set; } WarpState MapWarpState { get; set; } + + HashSet UnknownPlayerIDs { get; set; } + + HashSet UnknownNPCIndexes { get; set; } } public interface ICurrentMapStateProvider @@ -44,7 +48,11 @@ public interface ICurrentMapStateProvider IReadOnlyCollection VisibleSpikeTraps { get; } - WarpState MapWarpState { get; set; } + WarpState MapWarpState { get; } + + HashSet UnknownPlayerIDs { get; } + + HashSet UnknownNPCIndexes { get; } } [AutoMappedType(IsSingleton = true)] @@ -68,6 +76,10 @@ public class CurrentMapStateRepository : ICurrentMapStateRepository, ICurrentMap public WarpState MapWarpState { get; set; } + public HashSet UnknownPlayerIDs { get; set; } + + public HashSet UnknownNPCIndexes { get; set; } + IReadOnlyDictionary ICurrentMapStateProvider.Characters => Characters; IReadOnlyCollection ICurrentMapStateProvider.NPCs => NPCs; @@ -96,6 +108,8 @@ public void ResetState() OpenDoors = new HashSet(); PendingDoors = new HashSet(); VisibleSpikeTraps = new HashSet(); + UnknownPlayerIDs = new HashSet(); + UnknownNPCIndexes = new HashSet(); MapWarpState = WarpState.None; } diff --git a/EOLib/Net/PacketFamily.cs b/EOLib/Net/PacketFamily.cs index a81b0bba1..79de2bccf 100644 --- a/EOLib/Net/PacketFamily.cs +++ b/EOLib/Net/PacketFamily.cs @@ -26,9 +26,9 @@ public enum PacketFamily : byte Party = (byte)24, Refresh = (byte)25, NPC = (byte)26, - AutoRefresh = (byte)27, - AutoRefresh2 = (byte)28, - Appear = (byte)29, + CharacterMapInfo = (byte)27, + NPCMapInfo = (byte)28, + MapInfo = (byte)29, PaperDoll = (byte)30, Effect = (byte)31, Trade = (byte)32, diff --git a/EOLib/Net/Translators/CharacterFromPacketFactory.cs b/EOLib/Net/Translators/CharacterFromPacketFactory.cs index 8cb94e0d2..bf9cd2877 100644 --- a/EOLib/Net/Translators/CharacterFromPacketFactory.cs +++ b/EOLib/Net/Translators/CharacterFromPacketFactory.cs @@ -27,7 +27,7 @@ public ICharacter CreateCharacter(IPacket packet) var yLoc = packet.ReadShort(); var direction = (EODirection)packet.ReadChar(); - packet.ReadChar(); //value is always 6? Unknown use + var classID = packet.ReadChar(); var guildTag = packet.ReadString(3); var level = packet.ReadChar(); @@ -78,6 +78,7 @@ public ICharacter CreateCharacter(IPacket packet) return new Character() .WithName(name) .WithID(id) + .WithClassID(classID) .WithMapID(mapID) .WithGuildTag(guildTag) .WithStats(stats) diff --git a/EOLib/Net/Translators/NPCFromPacketFactory.cs b/EOLib/Net/Translators/NPCFromPacketFactory.cs new file mode 100644 index 000000000..51afe8bfd --- /dev/null +++ b/EOLib/Net/Translators/NPCFromPacketFactory.cs @@ -0,0 +1,27 @@ +using AutomaticTypeMapper; +using EOLib.Domain.NPC; + +namespace EOLib.Net.Translators +{ + [AutoMappedType] + public class NPCFromPacketFactory : INPCFromPacketFactory + { + public INPC CreateNPC(IPacket packet) + { + var index = packet.ReadChar(); + var id = packet.ReadShort(); + var x = packet.ReadChar(); + var y = packet.ReadChar(); + var direction = (EODirection)packet.ReadChar(); + + INPC npc = new NPC(id, index); + npc = npc.WithX(x).WithY(y).WithDirection(direction).WithFrame(NPCFrame.Standing); + return npc; + } + } + + public interface INPCFromPacketFactory + { + INPC CreateNPC(IPacket packet); + } +} diff --git a/EOLib/PacketHandlers/AdminHideHandler.cs b/EOLib/PacketHandlers/AdminHideHandler.cs index 4a6f3f386..13b5a172e 100644 --- a/EOLib/PacketHandlers/AdminHideHandler.cs +++ b/EOLib/PacketHandlers/AdminHideHandler.cs @@ -33,12 +33,17 @@ public override bool HandlePacket(IPacket packet) _characterRepository.MainCharacter = Hidden(_characterRepository.MainCharacter); else { - if (!_currentMapStateRepository.Characters.ContainsKey(id)) - return false; - var character = _currentMapStateRepository.Characters[id]; - - var updatedCharacter = Hidden(character); - _currentMapStateRepository.Characters[id] = updatedCharacter; + if (_currentMapStateRepository.Characters.ContainsKey(id)) + { + var character = _currentMapStateRepository.Characters[id]; + + var updatedCharacter = Hidden(character); + _currentMapStateRepository.Characters[id] = updatedCharacter; + } + else + { + _currentMapStateRepository.UnknownPlayerIDs.Add(id); + } } return true; diff --git a/EOLib/PacketHandlers/AdminShowHandler.cs b/EOLib/PacketHandlers/AdminShowHandler.cs index 79591fd22..368aa4b76 100644 --- a/EOLib/PacketHandlers/AdminShowHandler.cs +++ b/EOLib/PacketHandlers/AdminShowHandler.cs @@ -33,12 +33,17 @@ public override bool HandlePacket(IPacket packet) _characterRepository.MainCharacter = Shown(_characterRepository.MainCharacter); else { - if (!_currentMapStateRepository.Characters.ContainsKey(id)) - return false; - var character = _currentMapStateRepository.Characters[id]; - - var updatedCharacter = Shown(character); - _currentMapStateRepository.Characters[id] = updatedCharacter; + if (_currentMapStateRepository.Characters.ContainsKey(id)) + { + var character = _currentMapStateRepository.Characters[id]; + + var updatedCharacter = Shown(character); + _currentMapStateRepository.Characters[id] = updatedCharacter; + } + else + { + _currentMapStateRepository.UnknownPlayerIDs.Add(id); + } } return true; diff --git a/EOLib/PacketHandlers/Effects/PlayerSpikeDamageHandler.cs b/EOLib/PacketHandlers/Effects/PlayerSpikeDamageHandler.cs index b88623cf1..6546a7162 100644 --- a/EOLib/PacketHandlers/Effects/PlayerSpikeDamageHandler.cs +++ b/EOLib/PacketHandlers/Effects/PlayerSpikeDamageHandler.cs @@ -35,15 +35,19 @@ public override bool HandlePacket(IPacket packet) var isDead = packet.ReadChar() != 0; var damageTaken = packet.ReadThree(); - if (!_currentMapStateRepository.Characters.ContainsKey(characterId)) - return false; - - var updatedCharacter = _currentMapStateRepository.Characters[characterId].WithDamage(damageTaken, isDead); - _currentMapStateRepository.Characters[characterId] = updatedCharacter; + if (_currentMapStateRepository.Characters.ContainsKey(characterId)) + { + var updatedCharacter = _currentMapStateRepository.Characters[characterId].WithDamage(damageTaken, isDead); + _currentMapStateRepository.Characters[characterId] = updatedCharacter; - foreach (var notifier in _otherCharacterEventNotifiers) + foreach (var notifier in _otherCharacterEventNotifiers) + { + notifier.OtherCharacterTakeDamage(characterId, playerPercentHealth, damageTaken); + } + } + else { - notifier.OtherCharacterTakeDamage(characterId, playerPercentHealth, damageTaken); + _currentMapStateRepository.UnknownPlayerIDs.Add(characterId); } return true; diff --git a/EOLib/PacketHandlers/ItemEquipHandler.cs b/EOLib/PacketHandlers/ItemEquipHandler.cs index abdd49227..9efa9882c 100644 --- a/EOLib/PacketHandlers/ItemEquipHandler.cs +++ b/EOLib/PacketHandlers/ItemEquipHandler.cs @@ -107,6 +107,11 @@ protected bool HandlePaperdollPacket(IPacket packet, bool itemUnequipped) } }); + update.MatchNone(() => + { + _currentMapStateRepository.UnknownPlayerIDs.Add(playerId); + }); + return true; } } diff --git a/EOLib/PacketHandlers/MainPlayerWalkHandler.cs b/EOLib/PacketHandlers/MainPlayerWalkHandler.cs index 2783ae533..8df46d5f4 100644 --- a/EOLib/PacketHandlers/MainPlayerWalkHandler.cs +++ b/EOLib/PacketHandlers/MainPlayerWalkHandler.cs @@ -3,6 +3,7 @@ using EOLib.Domain.Map; using EOLib.Net; using EOLib.Net.Handlers; +using System.Linq; namespace EOLib.PacketHandlers { @@ -23,8 +24,25 @@ public MainPlayerWalkHandler(IPlayerInfoProvider playerInfoProvider, public override bool HandlePacket(IPacket packet) { - if (packet.ReadByte() != 255 || packet.ReadByte() != 255) - return false; + while (packet.PeekByte() != 0xFF) + { + var playerID = packet.ReadShort(); + if (!_currentMapStateRepository.Characters.ContainsKey(playerID)) + { + _currentMapStateRepository.UnknownPlayerIDs.Add(playerID); + } + } + packet.ReadByte(); + + while (packet.PeekByte() != 0xFF) + { + var index = packet.ReadChar(); + if (!_currentMapStateRepository.NPCs.Any((npc) => npc.Index == index)) + { + _currentMapStateRepository.UnknownNPCIndexes.Add(index); + } + } + packet.ReadByte(); var numberOfMapItems = packet.PeekEndString().Length / 9; for (int i = 0; i < numberOfMapItems; ++i) diff --git a/EOLib/PacketHandlers/MapInfoHandler.cs b/EOLib/PacketHandlers/MapInfoHandler.cs new file mode 100644 index 000000000..f247d7979 --- /dev/null +++ b/EOLib/PacketHandlers/MapInfoHandler.cs @@ -0,0 +1,71 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Extensions; +using EOLib.Domain.Login; +using EOLib.Domain.Map; +using EOLib.IO.Extensions; +using EOLib.IO.Repositories; +using EOLib.Net; +using EOLib.Net.Handlers; +using EOLib.Net.Translators; + +namespace EOLib.PacketHandlers +{ + [AutoMappedType] + public class MapInfoHandler : InGameOnlyPacketHandler + { + private readonly ICurrentMapStateRepository _currentMapStateRepository; + private readonly ICharacterFromPacketFactory _characterFromPacketFactory; + private readonly INPCFromPacketFactory _npcFromPacketFactory; + private readonly IEIFFileProvider _eifFileProvider; + + public override PacketFamily Family => PacketFamily.MapInfo; + + public override PacketAction Action => PacketAction.Reply; + + public MapInfoHandler(IPlayerInfoProvider playerInfoProvider, + ICurrentMapStateRepository currentMapStateRepository, + ICharacterFromPacketFactory characterFromPacketFactory, + INPCFromPacketFactory npcFromPacketFactory, + IEIFFileProvider eifFileProvider + ) + : base(playerInfoProvider) + { + _currentMapStateRepository = currentMapStateRepository; + _characterFromPacketFactory = characterFromPacketFactory; + _npcFromPacketFactory = npcFromPacketFactory; + _eifFileProvider = eifFileProvider; + } + + public override bool HandlePacket(IPacket packet) + { + var numOfEntities = packet.ReadChar(); + + if (packet.PeekByte() == 0xFF) + { + packet.ReadByte(); + for (var i = 0; i < numOfEntities; i++) + { + var character = _characterFromPacketFactory.CreateCharacter(packet); + if (_currentMapStateRepository.Characters.ContainsKey(character.ID)) + { + var existingCharacter = _currentMapStateRepository.Characters[character.ID]; + var isRangedWeapon = _eifFileProvider.EIFFile.IsRangedWeapon(character.RenderProperties.WeaponGraphic); + character = existingCharacter.WithAppliedData(character, isRangedWeapon); + } + _currentMapStateRepository.Characters[character.ID] = character; + if (packet.ReadByte() != 255) + throw new MalformedPacketException("Missing 255 byte after character data", packet); + } + } + + while (packet.ReadPosition < packet.Length) + { + var npc = _npcFromPacketFactory.CreateNPC(packet); + _currentMapStateRepository.NPCs.RemoveWhere(n => n.Index == npc.Index); + _currentMapStateRepository.NPCs.Add(npc); + } + + return true; + } + } +} diff --git a/EOLib/PacketHandlers/NPCActionHandler.cs b/EOLib/PacketHandlers/NPCActionHandler.cs index a5b9d99a0..533ae8469 100644 --- a/EOLib/PacketHandlers/NPCActionHandler.cs +++ b/EOLib/PacketHandlers/NPCActionHandler.cs @@ -69,7 +69,11 @@ public override bool HandlePacket(IPacket packet) { npc = _currentMapStateRepository.NPCs.Single(n => n.Index == index); } - catch (InvalidOperationException) { return false; } + catch (InvalidOperationException) + { + _currentMapStateRepository.UnknownNPCIndexes.Add(index); + return true; + } var updatedNpc = Option.None(); switch (num255s) @@ -142,6 +146,10 @@ private INPC HandleNPCAttack(IPacket packet, INPC npc) foreach (var notifier in _otherCharacterNotifiers) notifier.OtherCharacterTakeDamage(characterID, playerPercentHealth, damageTaken); } + else + { + _currentMapStateRepository.UnknownPlayerIDs.Add(characterID); + } foreach (var notifier in _npcAnimationNotifiers) notifier.StartNPCAttackAnimation(npc.Index); diff --git a/EOLib/PacketHandlers/NPCEnterMapHandler.cs b/EOLib/PacketHandlers/NPCEnterMapHandler.cs deleted file mode 100644 index fd7feb790..000000000 --- a/EOLib/PacketHandlers/NPCEnterMapHandler.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Linq; -using AutomaticTypeMapper; -using EOLib.Domain.Login; -using EOLib.Domain.Map; -using EOLib.Domain.NPC; -using EOLib.Net; -using EOLib.Net.Handlers; - -namespace EOLib.PacketHandlers -{ - [AutoMappedType] - public class NPCEnterMapHandler : InGameOnlyPacketHandler - { - private readonly ICurrentMapStateRepository _currentMapStateRepository; - - public override PacketFamily Family => PacketFamily.Appear; - - public override PacketAction Action => PacketAction.Reply; - - public NPCEnterMapHandler(IPlayerInfoProvider playerInfoProvider, - ICurrentMapStateRepository currentMapStateRepository) - : base(playerInfoProvider) - { - _currentMapStateRepository = currentMapStateRepository; - } - - public override bool HandlePacket(IPacket packet) - { - if (packet.Length - packet.ReadPosition != 8) - throw new MalformedPacketException("Invalid packet length for new NPC in map.", packet); - if (!packet.ReadBytes(2).SequenceEqual(new byte[] {1, 255})) - throw new MalformedPacketException("Invalid header data for new NPC in map. Expected byte values 0x01, 0xff.", packet); - - var index = packet.ReadChar(); - var id = packet.ReadShort(); - var x = packet.ReadChar(); - var y = packet.ReadChar(); - var direction = (EODirection) packet.ReadChar(); - - INPC npc = new NPC(id, index); - npc = npc.WithX(x).WithY(y).WithDirection(direction).WithFrame(NPCFrame.Standing); - - _currentMapStateRepository.NPCs.RemoveWhere(n => n.Index == index); - _currentMapStateRepository.NPCs.Add(npc); - - return true; - } - } -} diff --git a/EOLib/PacketHandlers/NPCLeaveMapHandler.cs b/EOLib/PacketHandlers/NPCLeaveMapHandler.cs index 3ccf6a1b2..d4aa52bd7 100644 --- a/EOLib/PacketHandlers/NPCLeaveMapHandler.cs +++ b/EOLib/PacketHandlers/NPCLeaveMapHandler.cs @@ -113,6 +113,10 @@ private void UpdatePlayerDirection(short playerID, EODirection playerDirection) var updatedCharacter = _currentMapStateRepository.Characters[playerID].WithRenderProperties(updatedRenderProps); _currentMapStateRepository.Characters[playerID] = updatedCharacter; } + else + { + _currentMapStateRepository.UnknownPlayerIDs.Add(playerID); + } } private void UpdateCharacterStat(CharacterStat whichStat, int statValue) diff --git a/EOLib/PacketHandlers/NPCTakeDamageHandler.cs b/EOLib/PacketHandlers/NPCTakeDamageHandler.cs index 1bd3f2073..2cc6fd01a 100644 --- a/EOLib/PacketHandlers/NPCTakeDamageHandler.cs +++ b/EOLib/PacketHandlers/NPCTakeDamageHandler.cs @@ -68,6 +68,10 @@ public override bool HandlePacket(IPacket packet) var updatedCharacter = _currentMapStateRepository.Characters[fromPlayerId].WithRenderProperties(renderProps); _currentMapStateRepository.Characters[fromPlayerId] = updatedCharacter; } + else + { + _currentMapStateRepository.UnknownPlayerIDs.Add(fromPlayerId); + } // todo: this has the potential to bug out if the opponent ID is never reset and the player dies/leaves try @@ -79,7 +83,11 @@ public override bool HandlePacket(IPacket packet) foreach (var notifier in _npcNotifiers) notifier.NPCTakeDamage(npcIndex, fromPlayerId, damageToNpc, npcPctHealth, spellId); } - catch (InvalidOperationException) { return false; } + catch (InvalidOperationException) + { + _currentMapStateRepository.UnknownNPCIndexes.Add((byte)npcIndex); + return true; + } return true; } diff --git a/EOLib/PacketHandlers/PlayerAttackHandler.cs b/EOLib/PacketHandlers/PlayerAttackHandler.cs index e292f584f..cd9249d66 100644 --- a/EOLib/PacketHandlers/PlayerAttackHandler.cs +++ b/EOLib/PacketHandlers/PlayerAttackHandler.cs @@ -32,18 +32,22 @@ public override bool HandlePacket(IPacket packet) var playerID = packet.ReadShort(); var direction = (EODirection)packet.ReadChar(); - if (!_currentMapStateRepository.Characters.ContainsKey(playerID)) - return false; - - var character = _currentMapStateRepository.Characters[playerID]; - if (character.RenderProperties.Direction != direction) + if (_currentMapStateRepository.Characters.ContainsKey(playerID)) { - var renderProperties = character.RenderProperties.WithDirection(direction); - _currentMapStateRepository.Characters[playerID] = character.WithRenderProperties(renderProperties); + var character = _currentMapStateRepository.Characters[playerID]; + if (character.RenderProperties.Direction != direction) + { + var renderProperties = character.RenderProperties.WithDirection(direction); + _currentMapStateRepository.Characters[playerID] = character.WithRenderProperties(renderProperties); + } + + foreach (var notifier in _otherCharacterAnimationNotifiers) + notifier.StartOtherCharacterAttackAnimation(playerID); + } + else + { + _currentMapStateRepository.UnknownPlayerIDs.Add(playerID); } - - foreach (var notifier in _otherCharacterAnimationNotifiers) - notifier.StartOtherCharacterAttackAnimation(playerID); return true; } diff --git a/EOLib/PacketHandlers/PlayerAvatarChangeHandler.cs b/EOLib/PacketHandlers/PlayerAvatarChangeHandler.cs index 6c74a4ef1..c5ea97d30 100644 --- a/EOLib/PacketHandlers/PlayerAvatarChangeHandler.cs +++ b/EOLib/PacketHandlers/PlayerAvatarChangeHandler.cs @@ -46,7 +46,8 @@ public override bool HandlePacket(IPacket packet) } else { - return false; + _currentMapStateRepository.UnknownPlayerIDs.Add(playerID); + return true; } var currentRenderProps = currentCharacter.RenderProperties; diff --git a/EOLib/PacketHandlers/PlayerLeaveMapHandler.cs b/EOLib/PacketHandlers/PlayerLeaveMapHandler.cs index cee526c38..8c7180b93 100644 --- a/EOLib/PacketHandlers/PlayerLeaveMapHandler.cs +++ b/EOLib/PacketHandlers/PlayerLeaveMapHandler.cs @@ -39,12 +39,16 @@ public override bool HandlePacket(IPacket packet) notifier.NotifyWarpLeaveEffect(id, anim); } - if (!_currentMapStateRepository.Characters.ContainsKey(id)) - return false; - - var character = _currentMapStateRepository.Characters[id]; - _currentMapStateRepository.Characters.Remove(id); - _currentMapStateRepository.VisibleSpikeTraps.Remove(new MapCoordinate(character.RenderProperties.MapX, character.RenderProperties.MapY)); + if (_currentMapStateRepository.Characters.ContainsKey(id)) + { + var character = _currentMapStateRepository.Characters[id]; + _currentMapStateRepository.Characters.Remove(id); + _currentMapStateRepository.VisibleSpikeTraps.Remove(new MapCoordinate(character.RenderProperties.MapX, character.RenderProperties.MapY)); + } + else + { + _currentMapStateRepository.UnknownPlayerIDs.Add(id); + } return true; } diff --git a/EOLib/PacketHandlers/PlayerSitHandler.cs b/EOLib/PacketHandlers/PlayerSitHandler.cs index 9c1a39008..4db159986 100644 --- a/EOLib/PacketHandlers/PlayerSitHandler.cs +++ b/EOLib/PacketHandlers/PlayerSitHandler.cs @@ -56,7 +56,8 @@ public override bool HandlePacket(IPacket packet) } else { - return false; + _currentMapStateRepository.UnknownPlayerIDs.Add(playerId); + return true; } return true; diff --git a/EOLib/PacketHandlers/PlayerStandHandler.cs b/EOLib/PacketHandlers/PlayerStandHandler.cs index de06041d1..7a925f7cb 100644 --- a/EOLib/PacketHandlers/PlayerStandHandler.cs +++ b/EOLib/PacketHandlers/PlayerStandHandler.cs @@ -48,7 +48,7 @@ public override bool HandlePacket(IPacket packet) } else { - return false; + _currentMapStateRepository.UnknownPlayerIDs.Add(playerId); } return true; diff --git a/EOLib/PacketHandlers/PlayerTargetOtherSpellHandler.cs b/EOLib/PacketHandlers/PlayerTargetOtherSpellHandler.cs index f1a382eec..96673451f 100644 --- a/EOLib/PacketHandlers/PlayerTargetOtherSpellHandler.cs +++ b/EOLib/PacketHandlers/PlayerTargetOtherSpellHandler.cs @@ -52,7 +52,8 @@ public override bool HandlePacket(IPacket packet) } else { - return false; + _currentMapStateRepository.UnknownPlayerIDs.Add(sourcePlayerId); + return true; } if (packet.ReadPosition != packet.Length) diff --git a/EOLib/PacketHandlers/PlayerWalkHandler.cs b/EOLib/PacketHandlers/PlayerWalkHandler.cs index d410ed3af..257e82a39 100644 --- a/EOLib/PacketHandlers/PlayerWalkHandler.cs +++ b/EOLib/PacketHandlers/PlayerWalkHandler.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using AutomaticTypeMapper; using EOLib.Domain.Character; using EOLib.Domain.Extensions; @@ -35,26 +33,30 @@ public override bool HandlePacket(IPacket packet) { var characterID = packet.ReadShort(); - if (!_currentMapStateRepository.Characters.ContainsKey(characterID)) - return false; + if (_currentMapStateRepository.Characters.ContainsKey(characterID)) + { + var dir = (EODirection)packet.ReadChar(); + var x = packet.ReadChar(); + var y = packet.ReadChar(); - var dir = (EODirection)packet.ReadChar(); - var x = packet.ReadChar(); - var y = packet.ReadChar(); + var character = _currentMapStateRepository.Characters[characterID]; - var character = _currentMapStateRepository.Characters[characterID]; + // if character is walking, that means animator is handling position of character + // if character is not walking (this is true in EOBot), update the domain model here + if (!character.RenderProperties.IsActing(CharacterActionState.Walking)) + { + var renderProperties = EnsureCorrectXAndY(character.RenderProperties.WithDirection(dir), x, y); + _currentMapStateRepository.Characters[characterID] = character.WithRenderProperties(renderProperties); + } - // if character is walking, that means animator is handling position of character - // if character is not walking (this is true in EOBot), update the domain model here - if (!character.RenderProperties.IsActing(CharacterActionState.Walking)) + foreach (var notifier in _otherCharacterAnimationNotifiers) + notifier.StartOtherCharacterWalkAnimation(characterID, x, y, dir); + } + else { - var renderProperties = EnsureCorrectXAndY(character.RenderProperties.WithDirection(dir), x, y); - _currentMapStateRepository.Characters[characterID] = character.WithRenderProperties(renderProperties); + _currentMapStateRepository.UnknownPlayerIDs.Add(characterID); } - foreach (var notifier in _otherCharacterAnimationNotifiers) - notifier.StartOtherCharacterWalkAnimation(characterID, x, y, dir); - return true; } diff --git a/EndlessClient/HUD/Controls/HudControlIdentifier.cs b/EndlessClient/HUD/Controls/HudControlIdentifier.cs index 4d98ce989..f1181ea08 100644 --- a/EndlessClient/HUD/Controls/HudControlIdentifier.cs +++ b/EndlessClient/HUD/Controls/HudControlIdentifier.cs @@ -69,6 +69,7 @@ public enum HudControlIdentifier UserInputHandler, CharacterAnimator, NPCAnimator, + UnknownEntitiesRequester, PreviousUserInputTracker = Int32.MaxValue, //this should always be last! } diff --git a/EndlessClient/HUD/Controls/HudControlsFactory.cs b/EndlessClient/HUD/Controls/HudControlsFactory.cs index b22523fcd..ca01f980a 100644 --- a/EndlessClient/HUD/Controls/HudControlsFactory.cs +++ b/EndlessClient/HUD/Controls/HudControlsFactory.cs @@ -10,6 +10,7 @@ using EndlessClient.HUD.Panels; using EndlessClient.HUD.StatusBars; using EndlessClient.Input; +using EndlessClient.Network; using EndlessClient.Rendering; using EndlessClient.Rendering.Character; using EndlessClient.Rendering.Factories; @@ -19,6 +20,7 @@ using EOLib.Domain.Map; using EOLib.Graphics; using EOLib.Localization; +using EOLib.Net.Communication; using Microsoft.Xna.Framework; using XNAControls; @@ -53,6 +55,7 @@ public class HudControlsFactory : IHudControlsFactory private readonly IPathFinder _pathFinder; private readonly ICharacterActions _characterActions; private readonly IWalkValidationActions _walkValidationActions; + private readonly IPacketSendService _packetSendService; private IChatController _chatController; public HudControlsFactory(IHudButtonController hudButtonController, @@ -76,7 +79,8 @@ public HudControlsFactory(IHudButtonController hudButtonController, IArrowKeyController arrowKeyController, IPathFinder pathFinder, ICharacterActions characterActions, - IWalkValidationActions walkValidationActions) + IWalkValidationActions walkValidationActions, + IPacketSendService packetSendService) { _hudButtonController = hudButtonController; _hudPanelFactory = hudPanelFactory; @@ -100,6 +104,7 @@ public HudControlsFactory(IHudButtonController hudButtonController, _pathFinder = pathFinder; _characterActions = characterActions; _walkValidationActions = walkValidationActions; + _packetSendService = packetSendService; } public void InjectChatController(IChatController chatController) @@ -158,6 +163,7 @@ public IReadOnlyDictionary CreateHud() {HudControlIdentifier.UserInputHandler, CreateUserInputHandler()}, {HudControlIdentifier.CharacterAnimator, CreateCharacterAnimator()}, {HudControlIdentifier.NPCAnimator, CreateNPCAnimator()}, + {HudControlIdentifier.UnknownEntitiesRequester, CreateUnknownEntitiesRequester()}, {HudControlIdentifier.PreviousUserInputTracker, CreatePreviousUserInputTracker()} }; @@ -359,6 +365,11 @@ private UsageTrackerComponent CreateUsageTracker() return new UsageTrackerComponent(_endlessGameProvider, _characterRepository); } + private UnknownEntitiesRequester CreateUnknownEntitiesRequester() + { + return new UnknownEntitiesRequester(_endlessGameProvider, _currentMapStateRepository, _packetSendService); + } + private StatusBarLabel CreateStatusLabel() { return new StatusBarLabel(_clientWindowSizeProvider, _statusLabelTextProvider) { DrawOrder = HUD_CONTROL_LAYER }; diff --git a/EndlessClient/Network/UnknownEntitiesRequester.cs b/EndlessClient/Network/UnknownEntitiesRequester.cs new file mode 100644 index 000000000..696f9be80 --- /dev/null +++ b/EndlessClient/Network/UnknownEntitiesRequester.cs @@ -0,0 +1,104 @@ +using EndlessClient.GameExecution; +using EOLib.Domain.Map; +using EOLib.Net; +using EOLib.Net.Communication; +using Microsoft.Xna.Framework; +using System; + +namespace EndlessClient.Network +{ + public class UnknownEntitiesRequester : GameComponent + { + private readonly ICurrentMapStateRepository _currentMapStateRepository; + private readonly IPacketSendService _packetSendService; + private DateTime _lastRequestTime; + private const double REQUEST_INTERVAL_SECONDS = 1; + + public UnknownEntitiesRequester(IEndlessGameProvider gameProvider, + ICurrentMapStateRepository currentMapStateRepository, + IPacketSendService packetSendService) + : base((Game) gameProvider.Game) + { + _currentMapStateRepository = currentMapStateRepository; + _packetSendService = packetSendService; + _lastRequestTime = DateTime.Now; + } + + public override void Update(GameTime gameTime) + { + if ((DateTime.Now - _lastRequestTime).TotalSeconds >= REQUEST_INTERVAL_SECONDS) + { + IPacket request = null; + if (_currentMapStateRepository.UnknownNPCIndexes.Count > 0 && _currentMapStateRepository.UnknownPlayerIDs.Count > 0) + { + request = CreateRequestForBoth(); + } + else if (_currentMapStateRepository.UnknownNPCIndexes.Count > 0) + { + request = CreateRequestForNPCs(); + } + else if (_currentMapStateRepository.UnknownPlayerIDs.Count > 0) + { + request = CreateRequestForPlayers(); + } + + try + { + if (request != null) + { + _packetSendService.SendPacket(request); + _currentMapStateRepository.UnknownNPCIndexes.Clear(); + _currentMapStateRepository.UnknownPlayerIDs.Clear(); + } + } + catch (NoDataSentException) + { } // Swallow error. Will try again on next interval + finally + { + _lastRequestTime = DateTime.Now; + } + } + + base.Update(gameTime); + } + + private IPacket CreateRequestForBoth() + { + IPacketBuilder builder = new PacketBuilder(PacketFamily.MapInfo, PacketAction.Request); + foreach (var id in _currentMapStateRepository.UnknownPlayerIDs) + { + builder = builder.AddShort(id); + } + builder = builder.AddByte(0xFF); + foreach (var index in _currentMapStateRepository.UnknownNPCIndexes) + { + builder = builder.AddChar(index); + } + + return builder.Build(); + } + + private IPacket CreateRequestForNPCs() + { + IPacketBuilder builder = new PacketBuilder(PacketFamily.NPCMapInfo, PacketAction.Request) + .AddChar((byte)_currentMapStateRepository.UnknownNPCIndexes.Count) + .AddByte(0xFF); + + foreach (var index in _currentMapStateRepository.UnknownNPCIndexes) + { + builder = builder.AddChar(index); + } + return builder.Build(); + } + + private IPacket CreateRequestForPlayers() + { + IPacketBuilder builder = new PacketBuilder(PacketFamily.CharacterMapInfo, PacketAction.Request); + foreach (var id in _currentMapStateRepository.UnknownPlayerIDs) + { + builder = builder.AddShort(id); + } + return builder.Build(); + } + } +}