-
Notifications
You must be signed in to change notification settings - Fork 16
/
NPCActionHandler.cs
186 lines (158 loc) · 7.56 KB
/
NPCActionHandler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
using AutomaticTypeMapper;
using EOLib.Domain.Character;
using EOLib.Domain.Chat;
using EOLib.Domain.Extensions;
using EOLib.Domain.Login;
using EOLib.Domain.Map;
using EOLib.Domain.Notifiers;
using EOLib.Domain.NPC;
using EOLib.IO.Repositories;
using EOLib.Net;
using EOLib.Net.Handlers;
using Optional;
using System;
using System.Collections.Generic;
using System.Linq;
namespace EOLib.PacketHandlers
{
[AutoMappedType]
public class NPCActionHandler : InGameOnlyPacketHandler
{
private const int NPC_WALK_ACTION = 0;
private const int NPC_ATTK_ACTION = 1;
private const int NPC_TALK_ACTION = 2;
private readonly ICharacterRepository _characterRepository;
private readonly IChatRepository _chatRepository;
private readonly IENFFileProvider _enfFileProvider;
private readonly ICurrentMapStateRepository _currentMapStateRepository;
private readonly IEnumerable<INPCActionNotifier> _npcAnimationNotifiers;
private readonly IEnumerable<IMainCharacterEventNotifier> _mainCharacterNotifiers;
private readonly IEnumerable<IOtherCharacterEventNotifier> _otherCharacterNotifiers;
public override PacketFamily Family => PacketFamily.NPC;
public override PacketAction Action => PacketAction.Player;
public NPCActionHandler(IPlayerInfoProvider playerInfoProvider,
ICurrentMapStateRepository currentMapStateRepository,
ICharacterRepository characterRepository,
IChatRepository chatRepository,
IENFFileProvider enfFileProvider,
IEnumerable<INPCActionNotifier> npcAnimationNotifiers,
IEnumerable<IMainCharacterEventNotifier> mainCharacterNotifiers,
IEnumerable<IOtherCharacterEventNotifier> otherCharacterNotifiers)
: base(playerInfoProvider)
{
_currentMapStateRepository = currentMapStateRepository;
_characterRepository = characterRepository;
_chatRepository = chatRepository;
_enfFileProvider = enfFileProvider;
_npcAnimationNotifiers = npcAnimationNotifiers;
_mainCharacterNotifiers = mainCharacterNotifiers;
_otherCharacterNotifiers = otherCharacterNotifiers;
}
public override bool HandlePacket(IPacket packet)
{
var num255s = 0;
while (packet.PeekByte() == byte.MaxValue)
{
num255s++;
packet.ReadByte();
}
var index = packet.ReadChar();
INPC npc;
try
{
npc = _currentMapStateRepository.NPCs.Single(n => n.Index == index);
}
catch (InvalidOperationException)
{
_currentMapStateRepository.UnknownNPCIndexes.Add(index);
return true;
}
var updatedNpc = Option.None<INPC>();
switch (num255s)
{
case NPC_WALK_ACTION: HandleNPCWalk(packet, npc); break;
case NPC_ATTK_ACTION: updatedNpc = Option.Some(HandleNPCAttack(packet, npc)); break;
case NPC_TALK_ACTION: HandleNPCTalk(packet, npc); break;
default: throw new MalformedPacketException("Unknown NPC action " + num255s + " specified in packet from server!", packet);
}
updatedNpc.MatchSome(n =>
{
_currentMapStateRepository.NPCs.Remove(npc);
_currentMapStateRepository.NPCs.Add(n);
});
return true;
}
private void HandleNPCWalk(IPacket packet, INPC npc)
{
//npc remove from view sets x/y to either 0,0 or 252,252 based on target coords
var x = packet.ReadChar();
var y = packet.ReadChar();
var npcDirection = (EODirection) packet.ReadChar();
if (packet.ReadBytes(3).Any(b => b != 255))
throw new MalformedPacketException("Expected 3 bytes of value 0xFF in NPC_PLAYER packet for Walk action", packet);
var updatedNPC = npc.WithDirection(npcDirection);
updatedNPC = EnsureCorrectXAndY(updatedNPC, x, y);
_currentMapStateRepository.NPCs.Remove(npc);
_currentMapStateRepository.NPCs.Add(updatedNPC);
foreach (var notifier in _npcAnimationNotifiers)
notifier.StartNPCWalkAnimation(npc.Index);
}
private INPC HandleNPCAttack(IPacket packet, INPC npc)
{
var isDead = packet.ReadChar() == 2; //2 if target player is dead, 1 if alive
var npcDirection = (EODirection)packet.ReadChar();
var characterID = packet.ReadShort();
var damageTaken = packet.ReadThree();
var playerPercentHealth = packet.ReadThree();
if (packet.ReadBytes(2).Any(b => b != 255))
throw new MalformedPacketException("Expected 2 bytes of value 0xFF in NPC_PLAYER packet for Attack action", packet);
if (characterID == _characterRepository.MainCharacter.ID)
{
var characterToUpdate = _characterRepository.MainCharacter;
var stats = characterToUpdate.Stats;
stats = stats.WithNewStat(CharacterStat.HP, (short)Math.Max(stats[CharacterStat.HP] - damageTaken, 0));
var props = characterToUpdate.RenderProperties;
if (isDead)
props = props.WithDead();
_characterRepository.MainCharacter = characterToUpdate.WithStats(stats).WithRenderProperties(props);
foreach (var notifier in _mainCharacterNotifiers)
notifier.NotifyTakeDamage(damageTaken, playerPercentHealth, isHeal: false);
}
else if (_currentMapStateRepository.Characters.ContainsKey(characterID))
{
var updatedCharacter = _currentMapStateRepository.Characters[characterID].WithDamage(damageTaken, isDead);
_currentMapStateRepository.Characters[characterID] = updatedCharacter;
foreach (var notifier in _otherCharacterNotifiers)
notifier.OtherCharacterTakeDamage(characterID, playerPercentHealth, damageTaken);
}
else
{
_currentMapStateRepository.UnknownPlayerIDs.Add(characterID);
}
foreach (var notifier in _npcAnimationNotifiers)
notifier.StartNPCAttackAnimation(npc.Index);
return npc.WithDirection(npcDirection);
}
private void HandleNPCTalk(IPacket packet, INPC npc)
{
var messageLength = packet.ReadChar();
var message = packet.ReadString(messageLength);
var npcData = _enfFileProvider.ENFFile[npc.ID];
var chatData = new ChatData(npcData.Name, message, ChatIcon.Note);
_chatRepository.AllChat[ChatTab.Local].Add(chatData);
foreach (var notifier in _npcAnimationNotifiers)
notifier.ShowNPCSpeechBubble(npc.Index, message);
}
private static INPC EnsureCorrectXAndY(INPC npc, byte destinationX, byte destinationY)
{
var opposite = npc.Direction.Opposite();
var tempNPC = npc
.WithDirection(opposite)
.WithX(destinationX)
.WithY(destinationY);
return npc
.WithX((byte)tempNPC.GetDestinationX())
.WithY((byte)tempNPC.GetDestinationY());
}
}
}