From e277af5241c388124d70813bc8f6de67b8152f64 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Tue, 16 May 2023 08:44:55 -0700 Subject: [PATCH] Add packet handling for Board packets --- EOLib.Localization/DialogResourceID.cs | 10 +-- EOLib/Domain/Interact/Board/BoardActions.cs | 70 ++++++++++++++++++ .../Interact/Board/BoardDataRepository.cs | 55 ++++++++++++++ .../{ => Interact}/Board/BoardMapEntity.cs | 2 +- .../Domain/Interact/Board/BoardPostDetail.cs | 9 +++ EOLib/Domain/Interact/Board/BoardPostInfo.cs | 14 ++++ .../PacketHandlers/Board/BoardOpenHandler.cs | 74 +++++++++++++++++++ .../PacketHandlers/Board/BoardTakeHandler.cs | 45 +++++++++++ EndlessClient/HUD/ServerMessageActions.cs | 1 + .../Rendering/Map/ClickDispatcher.cs | 6 +- 10 files changed, 275 insertions(+), 11 deletions(-) create mode 100644 EOLib/Domain/Interact/Board/BoardActions.cs create mode 100644 EOLib/Domain/Interact/Board/BoardDataRepository.cs rename EOLib/Domain/{ => Interact}/Board/BoardMapEntity.cs (84%) create mode 100644 EOLib/Domain/Interact/Board/BoardPostDetail.cs create mode 100644 EOLib/Domain/Interact/Board/BoardPostInfo.cs create mode 100644 EOLib/PacketHandlers/Board/BoardOpenHandler.cs create mode 100644 EOLib/PacketHandlers/Board/BoardTakeHandler.cs diff --git a/EOLib.Localization/DialogResourceID.cs b/EOLib.Localization/DialogResourceID.cs index 3b700974b..1c0b1bf96 100644 --- a/EOLib.Localization/DialogResourceID.cs +++ b/EOLib.Localization/DialogResourceID.cs @@ -58,14 +58,12 @@ public enum DialogResourceID BANK_ACCOUNT_UNABLE_TO_DEPOSIT = 102, SHOP_NOTHING_IS_FOR_SALE = 104, //106: confirmed (AFAIK) that the client does not show this message separately from NOT_BUYING_YOUR_ITEMS message -// ReSharper disable once UnusedMember.Global SHOP_NOT_BUYING_USED_ITEMS = 106, SHOP_NOT_BUYING_YOUR_ITEMS = 108, WARNING_YOU_HAVE_NOT_ENOUGH = 110, SHOP_DOES_NOT_BUY = 112, LOCKER_FULL_SINGLE_ITEM_MAX = 114, //116: confirmed (AFAIK) that the client does not show a message when you hit the max (unless the server is missing a message for this) -// ReSharper disable once UnusedMember.Global LOCKER_FULL_DIFF_ITEMS_MAX = 116, LOCKER_DEPOSIT_GOLD_ERROR = 118, DROP_MANY_GOLD_ON_GROUND = 120, @@ -108,10 +106,10 @@ public enum DialogResourceID GUILD_DEPOSIT_NEW_BALANCE = 194, ITEM_IS_LORE_ITEM = 196, ITEM_IS_CURSED_ITEM = 198, - BOARD_ERROR_NO_SUBJECT = 200, - BOARD_ERROR_NO_MESSAGE = 202, - BOARD_ERROR_MSG_NO_SUBJECT = 204, - BOARD_ERROR_SUBJECT_NO_MSG = 206, + DEPRECATED_BOARD_ERROR_NO_SUBJECT = 200, + DEPRECATED_BOARD_ERROR_NO_MESSAGE = 202, + BOARD_ERROR_NO_SUBJECT = 204, + BOARD_ERROR_NO_MESSAGE = 206, BOARD_ERROR_TOO_MANY_MESSAGES = 208, ITEM_CURSE_REMOVE_PROMPT = 210, JUKEBOX_REQUESTED_RECENTLY = 212, diff --git a/EOLib/Domain/Interact/Board/BoardActions.cs b/EOLib/Domain/Interact/Board/BoardActions.cs new file mode 100644 index 000000000..4d1164578 --- /dev/null +++ b/EOLib/Domain/Interact/Board/BoardActions.cs @@ -0,0 +1,70 @@ +using AutomaticTypeMapper; +using EOLib.Net; +using EOLib.Net.Communication; + +namespace EOLib.Domain.Interact.Board +{ + [AutoMappedType] + public class BoardActions : IBoardActions + { + private readonly IPacketSendService _packetSendService; + private readonly IBoardProvider _boardProvider; + + public BoardActions(IPacketSendService packetSendService, + IBoardProvider boardProvider) + { + _packetSendService = packetSendService; + _boardProvider = boardProvider; + } + + public void AddPost(string subject, string body) + { + _boardProvider.BoardId.MatchSome(boardId => + { + var packet = new PacketBuilder(PacketFamily.Board, PacketAction.Create) + .AddShort(boardId) + .AddByte(255) + .AddBreakString(subject.Replace('y', (char)255)) // this is in EOSERV for some reason. Probably due to chunking (see Sanitization here: https://github.com/Cirras/eo-protocol/blob/master/docs/chunks.md) + .AddBreakString(body) + .Build(); + + _packetSendService.SendPacket(packet); + }); + } + + public void DeletePost(int postId) + { + _boardProvider.BoardId.MatchSome(boardId => + { + var packet = new PacketBuilder(PacketFamily.Board, PacketAction.Remove) + .AddShort(boardId) + .AddShort(postId) + .Build(); + + _packetSendService.SendPacket(packet); + }); + } + + public void ViewPost(int postId) + { + _boardProvider.BoardId.MatchSome(boardId => + { + var packet = new PacketBuilder(PacketFamily.Board, PacketAction.Take) + .AddShort(boardId) + .AddShort(postId) + .Build(); + + _packetSendService.SendPacket(packet); + }); + } + } + + public interface IBoardActions + { + void AddPost(string subject, string body); + + void ViewPost(int postId); + + void DeletePost(int postId); + } +} diff --git a/EOLib/Domain/Interact/Board/BoardDataRepository.cs b/EOLib/Domain/Interact/Board/BoardDataRepository.cs new file mode 100644 index 000000000..cc09ddaa3 --- /dev/null +++ b/EOLib/Domain/Interact/Board/BoardDataRepository.cs @@ -0,0 +1,55 @@ +using AutomaticTypeMapper; +using Optional; +using System.Collections.Generic; + +namespace EOLib.Domain.Interact.Board +{ + public interface IBoardRepository + { + Option BoardId { get; set; } + + HashSet Posts { get; set; } + + Option ActivePost { get; set; } + + Option ActivePostMessage { get; set; } + } + + public interface IBoardProvider + { + Option BoardId { get; } + + IReadOnlyCollection Posts { get; } + + Option ActivePost { get; } + + Option ActivePostMessage { get; } + } + + [AutoMappedType(IsSingleton = true)] + public class BoardDataRepository : IBoardRepository, IBoardProvider, IResettable + { + public Option BoardId { get; set; } + + public HashSet Posts { get; set; } + + IReadOnlyCollection IBoardProvider.Posts => Posts; + + public Option ActivePost { get; set; } + + public Option ActivePostMessage { get; set; } + + public BoardDataRepository() + { + ResetState(); + } + + public void ResetState() + { + BoardId = Option.None(); + Posts = new HashSet(); + ActivePost = Option.None(); + ActivePostMessage = Option.None(); + } + } +} diff --git a/EOLib/Domain/Board/BoardMapEntity.cs b/EOLib/Domain/Interact/Board/BoardMapEntity.cs similarity index 84% rename from EOLib/Domain/Board/BoardMapEntity.cs rename to EOLib/Domain/Interact/Board/BoardMapEntity.cs index 43f7e78d8..641cfff48 100644 --- a/EOLib/Domain/Board/BoardMapEntity.cs +++ b/EOLib/Domain/Interact/Board/BoardMapEntity.cs @@ -1,7 +1,7 @@ using Amadevus.RecordGenerator; using EOLib.IO.Map; -namespace EOLib.Domain.Board +namespace EOLib.Domain.Interact.Board { [Record] public sealed partial class BoardMapEntity : IMapEntity diff --git a/EOLib/Domain/Interact/Board/BoardPostDetail.cs b/EOLib/Domain/Interact/Board/BoardPostDetail.cs new file mode 100644 index 000000000..1d0c02adf --- /dev/null +++ b/EOLib/Domain/Interact/Board/BoardPostDetail.cs @@ -0,0 +1,9 @@ +using Amadevus.RecordGenerator; + +namespace EOLib.Domain.Interact.Board +{ + [Record(Features.Default | Features.ObjectEquals | Features.EquatableEquals)] + public sealed partial class BoardPostDetail + { + } +} diff --git a/EOLib/Domain/Interact/Board/BoardPostInfo.cs b/EOLib/Domain/Interact/Board/BoardPostInfo.cs new file mode 100644 index 000000000..41a368abd --- /dev/null +++ b/EOLib/Domain/Interact/Board/BoardPostInfo.cs @@ -0,0 +1,14 @@ +using Amadevus.RecordGenerator; + +namespace EOLib.Domain.Interact.Board +{ + [Record(Features.Default | Features.ObjectEquals | Features.EquatableEquals)] + public sealed partial class BoardPostInfo + { + public int PostId { get; } + + public string Author { get; } + + public string Subject { get; } + } +} diff --git a/EOLib/PacketHandlers/Board/BoardOpenHandler.cs b/EOLib/PacketHandlers/Board/BoardOpenHandler.cs new file mode 100644 index 000000000..6bccca786 --- /dev/null +++ b/EOLib/PacketHandlers/Board/BoardOpenHandler.cs @@ -0,0 +1,74 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Interact.Board; +using EOLib.Domain.Login; +using EOLib.Domain.Notifiers; +using EOLib.Net; +using EOLib.Net.Handlers; +using Optional; +using System.Collections.Generic; + +namespace EOLib.PacketHandlers.Board +{ + /// + /// Sent by the server when a board should be opened + /// + [AutoMappedType] + public class BoardOpenHandler : InGameOnlyPacketHandler + { + private readonly IBoardRepository _boardRepository; + private readonly IEnumerable _userInterfaceNotifiers; + + public override PacketFamily Family => PacketFamily.Board; + + public override PacketAction Action => PacketAction.Open; + + public BoardOpenHandler(IPlayerInfoProvider playerInfoProvider, + IBoardRepository boardRepository, + IEnumerable userInterfaceNotifiers) + : base(playerInfoProvider) + { + _boardRepository = boardRepository; + _userInterfaceNotifiers = userInterfaceNotifiers; + } + + public override bool HandlePacket(IPacket packet) + { + _boardRepository.BoardId = Option.Some(packet.ReadChar()); + + var numPosts = packet.ReadChar(); + _boardRepository.Posts = new HashSet(); + + var chunks = new List(); + while (packet.ReadPosition < packet.Length) + { + var chunkData = new List { (byte)PacketFamily.Board, (byte)PacketAction.Open }; + while (packet.ReadPosition < packet.Length && packet.PeekByte() != 255) + chunkData.Add(packet.ReadByte()); + + if (packet.ReadPosition < packet.Length) + packet.ReadByte(); + + chunks.Add(new Packet(chunkData)); + } + + if (chunks.Count % 3 != 0 || chunks.Count / 3 != numPosts) + throw new MalformedPacketException("Unexpected number of elements in BOARD_OPEN packet", packet); + + for (int i = 0; i < chunks.Count / 3; i += 3) + { + var postId = chunks[i].ReadShort(); + var author = chunks[i + 1].ReadEndString(); + var subject = chunks[i + 2].ReadEndString(); + _boardRepository.Posts.Add(new BoardPostInfo(postId, author, subject)); + } + + _boardRepository.ActivePost = Option.None(); + _boardRepository.ActivePostMessage = Option.None(); + + foreach (var notifier in _userInterfaceNotifiers) + notifier.NotifyPacketDialog(PacketFamily.Board); + + return true; + } + } +} diff --git a/EOLib/PacketHandlers/Board/BoardTakeHandler.cs b/EOLib/PacketHandlers/Board/BoardTakeHandler.cs new file mode 100644 index 000000000..37767beb5 --- /dev/null +++ b/EOLib/PacketHandlers/Board/BoardTakeHandler.cs @@ -0,0 +1,45 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Interact.Board; +using EOLib.Domain.Login; +using EOLib.Net; +using EOLib.Net.Handlers; +using Optional; + +namespace EOLib.PacketHandlers.Board +{ + /// + /// Sent by the server to read a post on a board + /// + [AutoMappedType] + public class BoardTakeHandler : InGameOnlyPacketHandler + { + private readonly IBoardRepository _boardRepository; + + public override PacketFamily Family => PacketFamily.Board; + + public override PacketAction Action => PacketAction.Take; + + public BoardTakeHandler(IPlayerInfoProvider playerInfoProvider, + IBoardRepository boardRepository) + : base(playerInfoProvider) + { + _boardRepository = boardRepository; + } + + public override bool HandlePacket(IPacket packet) + { + var postId = packet.ReadShort(); + var message = packet.ReadEndString(); + + _boardRepository.ActivePost.MatchSome(post => + { + if (post.PostId == postId) + { + _boardRepository.ActivePostMessage = Option.Some(message); + } + }); + + return true; + } + } +} diff --git a/EndlessClient/HUD/ServerMessageActions.cs b/EndlessClient/HUD/ServerMessageActions.cs index 472b1ea70..135be9ea7 100644 --- a/EndlessClient/HUD/ServerMessageActions.cs +++ b/EndlessClient/HUD/ServerMessageActions.cs @@ -22,6 +22,7 @@ public void NotifyPacketDialog(PacketFamily packetFamily) { case PacketFamily.Locker: _inGameDialogActions.ShowLockerDialog(); break; case PacketFamily.Chest: _inGameDialogActions.ShowChestDialog(); break; + case PacketFamily.Board: _inGameDialogActions.ShowBoardDialog(); break; } } diff --git a/EndlessClient/Rendering/Map/ClickDispatcher.cs b/EndlessClient/Rendering/Map/ClickDispatcher.cs index 4f0cd4cec..aaec45ff0 100644 --- a/EndlessClient/Rendering/Map/ClickDispatcher.cs +++ b/EndlessClient/Rendering/Map/ClickDispatcher.cs @@ -1,13 +1,12 @@ -using AutomaticTypeMapper; -using EndlessClient.Controllers; +using EndlessClient.Controllers; using EndlessClient.ControlSets; using EndlessClient.HUD.Controls; using EndlessClient.HUD.Spells; using EndlessClient.Rendering.Character; using EndlessClient.Rendering.NPC; -using EOLib.Domain.Board; using EOLib.Domain.Character; using EOLib.Domain.Extensions; +using EOLib.Domain.Interact.Board; using EOLib.Domain.Map; using EOLib.IO.Map; using Microsoft.Xna.Framework; @@ -18,7 +17,6 @@ using System.Collections.Generic; using System.Linq; using XNAControls; - using DomainCharacter = EOLib.Domain.Character.Character; using DomainNPC = EOLib.Domain.NPC.NPC;