Skip to content

Commit

Permalink
Merge pull request #303 from ethanmoffat/bugfixes
Browse files Browse the repository at this point in the history
Miscellaneous bugfixes. Preparing for beta release.
  • Loading branch information
ethanmoffat authored May 4, 2023
2 parents b991303 + c339317 commit 133e2c6
Show file tree
Hide file tree
Showing 25 changed files with 332 additions and 129 deletions.
4 changes: 2 additions & 2 deletions EOLib/Domain/Chat/ChatActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ public ChatActions(IChatRepository chatRepository,
}
else if (chatType == ChatType.Party && !_partyDataProvider.Members.Any())
{
return (ChatResult.HideAll, String.Empty);
return (ChatResult.HideAll, string.Empty);
}
else if (chatType == ChatType.Global && _currentMapStateProvider.IsJail)
{
return (ChatResult.JailProtection, String.Empty);
return (ChatResult.JailProtection, string.Empty);
}

chat = _chatProcessor.RemoveFirstCharacterIfNeeded(chat, chatType, targetCharacter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,10 @@ public static CharacterRenderProperties WithNextEmoteFrame(this CharacterRenderP
{
var props = rp.ToBuilder();
props.EmoteFrame = (props.EmoteFrame + 1) % CharacterRenderProperties.MAX_NUMBER_OF_EMOTE_FRAMES;

var resetAction = props.SitState == SitState.Standing ? CharacterActionState.Standing : CharacterActionState.Sitting;
props.CurrentAction = props.EmoteFrame == 0
? CharacterActionState.Standing
? resetAction
: props.CurrentAction == CharacterActionState.Attacking // when using an instrument keep the current state as "Attacking"
? CharacterActionState.Attacking
: CharacterActionState.Emote;
Expand Down
4 changes: 3 additions & 1 deletion EOLib/Domain/Map/MapActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ public MapActions(IPacketSendService packetSendService,

public void RequestRefresh()
{
var packet = new PacketBuilder(PacketFamily.Refresh, PacketAction.Request).Build();
var packet = new PacketBuilder(PacketFamily.Refresh, PacketAction.Request)
.AddByte(255)
.Build();
_packetSendService.SendPacket(packet);
}

Expand Down
20 changes: 20 additions & 0 deletions EOLib/Net/FileTransfer/FileRequestActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using EOLib.IO.Services;
using Optional;
using System;
using System.IO;
using System.Threading.Tasks;

namespace EOLib.Net.FileTransfer
Expand Down Expand Up @@ -83,15 +84,20 @@ public async Task GetMapFromServer(int mapID, int sessionID)

public async Task GetItemFileFromServer(int sessionID)
{
DeleteExisting(PubFileNameConstants.EIFFilter);

var itemFiles = await _fileRequestService.RequestFile<EIFRecord>(InitFileType.Item, sessionID);
foreach (var file in itemFiles)
_pubFileSaveService.SaveFile(string.Format(PubFileNameConstants.EIFFormat, file.ID), file, rewriteChecksum: false);

_pubFileRepository.EIFFiles = itemFiles;
_pubFileRepository.EIFFile = PubFileExtensions.Merge(itemFiles);
}

public async Task GetNPCFileFromServer(int sessionID)
{
DeleteExisting(PubFileNameConstants.ENFFilter);

var npcFiles = await _fileRequestService.RequestFile<ENFRecord>(InitFileType.Npc, sessionID);
foreach (var file in npcFiles)
_pubFileSaveService.SaveFile(string.Format(PubFileNameConstants.ENFFormat, file.ID), file, rewriteChecksum: false);
Expand All @@ -101,6 +107,8 @@ public async Task GetNPCFileFromServer(int sessionID)

public async Task GetSpellFileFromServer(int sessionID)
{
DeleteExisting(PubFileNameConstants.ESFFilter);

var spellFiles = await _fileRequestService.RequestFile<ESFRecord>(InitFileType.Spell, sessionID);
foreach (var file in spellFiles)
_pubFileSaveService.SaveFile(string.Format(PubFileNameConstants.ESFFormat, file.ID), file, rewriteChecksum: false);
Expand All @@ -110,6 +118,8 @@ public async Task GetSpellFileFromServer(int sessionID)

public async Task GetClassFileFromServer(int sessionID)
{
DeleteExisting(PubFileNameConstants.ECFFilter);

var classFiles = await _fileRequestService.RequestFile<ECFRecord>(InitFileType.Class, sessionID);
foreach (var file in classFiles)
_pubFileSaveService.SaveFile(string.Format(PubFileNameConstants.ECFFormat, file.ID), file, rewriteChecksum: false);
Expand Down Expand Up @@ -152,6 +162,16 @@ private bool NeedPub(InitFileType fileType)
throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null);
}
}

private static void DeleteExisting(string filter)
{
try
{
foreach (var file in Directory.GetFiles("pub", filter))
File.Delete(file);
}
catch (IOException) { }
}
}

public interface IFileRequestActions
Expand Down
214 changes: 123 additions & 91 deletions EOLib/PacketHandlers/NPC/NPCPlayerHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
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 Optional.Collections;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using DomainNPC = EOLib.Domain.NPC.NPC;
Expand All @@ -23,10 +26,6 @@ namespace EOLib.PacketHandlers.NPC
[AutoMappedType]
public class NPCPlayerHandler : 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;
Expand Down Expand Up @@ -60,116 +59,149 @@ public NPCPlayerHandler(IPlayerInfoProvider playerInfoProvider,

public override bool HandlePacket(IPacket packet)
{
var num255s = 0;
while (packet.PeekByte() == byte.MaxValue)
var chunks = new List<IPacket>();
while (packet.ReadPosition < packet.Length)
{
num255s++;
packet.ReadByte();
}
var data = packet.RawData.Skip(packet.ReadPosition).TakeWhile(x => x != 255).ToArray();
packet.Seek(data.Length, SeekOrigin.Current);
if (packet.ReadPosition < packet.Length)
packet.ReadByte();

var index = packet.ReadChar();
DomainNPC npc;
try
{
npc = _currentMapStateRepository.NPCs.Single(n => n.Index == index);
}
catch (InvalidOperationException)
{
_currentMapStateRepository.UnknownNPCIndexes.Add(index);
return true;
chunks.Add(new PacketBuilder(packet.Family, packet.Action).AddBytes(data).Build());
}

var updatedNpc = Option.None<DomainNPC>();
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);
}
if (chunks.Count < 3 || chunks.Count > 4)
throw new MalformedPacketException($"Expected 3 or 4 chunks in NPC_PLAYER packet, got {chunks.Count}", packet);

updatedNpc.MatchSome(n =>
HandleNPCWalk(chunks[0]);
HandleNPCAttack(chunks[1]);
HandleNPCTalk(chunks[2]);

if (chunks.Count > 3)
{
_currentMapStateRepository.NPCs.Remove(npc);
_currentMapStateRepository.NPCs.Add(n);
});
var hp = chunks[3].ReadShort();
var tp = chunks[3].ReadShort();

var stats = _characterRepository.MainCharacter.Stats
.WithNewStat(CharacterStat.HP, hp)
.WithNewStat(CharacterStat.TP, tp);
_characterRepository.MainCharacter = _characterRepository.MainCharacter.WithStats(stats);
}

return true;
}

private void HandleNPCWalk(IPacket packet, DomainNPC npc)
private void HandleNPCWalk(IPacket packet)
{
//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);
while (packet.ReadPosition < packet.Length)
{
var index = packet.ReadChar();
var x = packet.ReadChar();
var y = packet.ReadChar();
var npcDirection = (EODirection)packet.ReadChar();

var npc = GetNPC(index);
npc.Match(
some: n =>
{
var updated = n.WithDirection(npcDirection);
updated = EnsureCorrectXAndY(updated, x, y);
ReplaceNPC(n, updated);

foreach (var notifier in _npcAnimationNotifiers)
notifier.StartNPCWalkAnimation(n.Index);
},
none: () => _currentMapStateRepository.UnknownNPCIndexes.Add(index));
}
}

private DomainNPC HandleNPCAttack(IPacket packet, DomainNPC npc)
private void HandleNPCAttack(IPacket packet)
{
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, Math.Max(stats[CharacterStat.HP] - damageTaken, 0));
// note: eoserv incorrectly sends playerPercentHealth as a three byte number. GameServer sends a single char.
const int DATA_LENGTH = 9;

var props = characterToUpdate.RenderProperties.WithIsDead(isDead);
_characterRepository.MainCharacter = characterToUpdate.WithStats(stats).WithRenderProperties(props);

foreach (var notifier in _mainCharacterNotifiers)
notifier.NotifyTakeDamage(damageTaken, playerPercentHealth, isHeal: false);
}
else if (_currentMapStateRepository.Characters.ContainsKey(characterID))
while (packet.ReadPosition + DATA_LENGTH < packet.Length)
{
var updatedCharacter = _currentMapStateRepository.Characters[characterID].WithDamage(damageTaken, isDead);
_currentMapStateRepository.Characters[characterID] = updatedCharacter;

foreach (var notifier in _otherCharacterNotifiers)
notifier.OtherCharacterTakeDamage(characterID, playerPercentHealth, damageTaken, isHeal: false);
var index = packet.ReadChar();
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.ReadChar();

if (characterID == _characterRepository.MainCharacter.ID)
{
var characterToUpdate = _characterRepository.MainCharacter;

var stats = characterToUpdate.Stats;
stats = stats.WithNewStat(CharacterStat.HP, Math.Max(stats[CharacterStat.HP] - damageTaken, 0));

var props = characterToUpdate.RenderProperties.WithIsDead(isDead);
_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, isHeal: false);
}
else
{
_currentMapStateRepository.UnknownPlayerIDs.Add(characterID);
}

var npc = GetNPC(index);
npc.Match(
some: n =>
{
var updated = n.WithDirection(npcDirection);
ReplaceNPC(n, updated);

foreach (var notifier in _npcAnimationNotifiers)
notifier.StartNPCAttackAnimation(index);
},
none: () => _currentMapStateRepository.UnknownNPCIndexes.Add(index));
}
else
}

private void HandleNPCTalk(IPacket packet)
{
while (packet.ReadPosition < packet.Length)
{
_currentMapStateRepository.UnknownPlayerIDs.Add(characterID);
var index = packet.ReadChar();
var messageLength = packet.ReadChar();
var message = packet.ReadString(messageLength);

var npc = GetNPC(index);
npc.Match(
some: n =>
{
var npcData = _enfFileProvider.ENFFile[n.ID];

var chatData = new ChatData(ChatTab.Local, npcData.Name, message, ChatIcon.Note);
_chatRepository.AllChat[ChatTab.Local].Add(chatData);

foreach (var notifier in _npcAnimationNotifiers)
notifier.ShowNPCSpeechBubble(index, message);
},
none: () => _currentMapStateRepository.UnknownNPCIndexes.Add(index));
}

foreach (var notifier in _npcAnimationNotifiers)
notifier.StartNPCAttackAnimation(npc.Index);

return npc.WithDirection(npcDirection);
}

private void HandleNPCTalk(IPacket packet, DomainNPC npc)
private Option<DomainNPC> GetNPC(int index)
{
var messageLength = packet.ReadChar();
var message = packet.ReadString(messageLength);

var npcData = _enfFileProvider.ENFFile[npc.ID];

var chatData = new ChatData(ChatTab.Local, npcData.Name, message, ChatIcon.Note);
_chatRepository.AllChat[ChatTab.Local].Add(chatData);
return _currentMapStateRepository.NPCs.SingleOrNone(n => n.Index == index);
}

foreach (var notifier in _npcAnimationNotifiers)
notifier.ShowNPCSpeechBubble(npc.Index, message);
private void ReplaceNPC(DomainNPC npc, DomainNPC updatedNPC)
{
_currentMapStateRepository.NPCs.Remove(npc);
_currentMapStateRepository.NPCs.Add(updatedNPC);
}

private static DomainNPC EnsureCorrectXAndY(DomainNPC npc, int destinationX, int destinationY)
Expand Down
5 changes: 3 additions & 2 deletions EOLib/PacketHandlers/Refresh/RefreshReplyHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,20 @@ public override bool HandlePacket(IPacket packet)
.WithMapY(updatedMainCharacter.RenderProperties.MapY);

var withoutMainCharacter = data.Characters.Where(x => !IDMatches(x));
data = data.WithCharacters(withoutMainCharacter.ToList());

_characterRepository.MainCharacter = _characterRepository.MainCharacter
.WithRenderProperties(updatedRenderProperties);

_currentMapStateRepository.Characters = data.Characters.ToDictionary(k => k.ID, v => v);
_currentMapStateRepository.Characters = withoutMainCharacter.ToDictionary(k => k.ID, v => v);
_currentMapStateRepository.NPCs = new HashSet<DomainNPC>(data.NPCs);
_currentMapStateRepository.MapItems = new HashSet<MapItem>(data.Items);

_currentMapStateRepository.OpenDoors.Clear();
_currentMapStateRepository.PendingDoors.Clear();
_currentMapStateRepository.VisibleSpikeTraps.Clear();

_currentMapStateRepository.MapWarpTime = Optional.Option.Some(System.DateTime.Now.AddMilliseconds(-100));

foreach (var notifier in _mapChangedNotifiers)
notifier.NotifyMapChanged(differentMapID: false, warpAnimation: WarpAnimation.None);

Expand Down
3 changes: 3 additions & 0 deletions EOLib/PacketHandlers/Trade/TradeOfferUpdateHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ public override bool HandlePacket(IPacket packet)
x.PlayerTwoOffer = x.PlayerTwoOffer.WithItems(player1Items);
});

_tradeRepository.PlayerOneOffer = _tradeRepository.PlayerOneOffer.WithAgrees(false);
_tradeRepository.PlayerTwoOffer = _tradeRepository.PlayerTwoOffer.WithAgrees(false);

return true;
}
}
Expand Down
Loading

0 comments on commit 133e2c6

Please sign in to comment.