From 8c1a52dc1acd39b01e7853f3ec0f60288740de1f Mon Sep 17 00:00:00 2001 From: sven-n Date: Mon, 20 Jan 2025 17:48:35 +0100 Subject: [PATCH] Added extended message for mails The old structure doesn't have enough space for the larger appearance data. I made it even larger than needed for future extensions. --- .../C4-C7-OpenLetterExtended_by-server.md | 22 ++++ docs/Packets/ServerToClient.md | 1 + .../Messenger/ShowLetterExtendedPlugIn.cs | 64 +++++++++ .../ServerToClient/ConnectionExtensions.cs | 36 ++++++ .../ServerToClient/ServerToClientPackets.cs | 121 ++++++++++++++++++ .../ServerToClient/ServerToClientPackets.xml | 36 ++++++ .../ServerToClientPacketsRef.cs | 121 ++++++++++++++++++ 7 files changed, 401 insertions(+) create mode 100644 docs/Packets/C4-C7-OpenLetterExtended_by-server.md create mode 100644 src/GameServer/RemoteView/Messenger/ShowLetterExtendedPlugIn.cs diff --git a/docs/Packets/C4-C7-OpenLetterExtended_by-server.md b/docs/Packets/C4-C7-OpenLetterExtended_by-server.md new file mode 100644 index 000000000..f4135ab72 --- /dev/null +++ b/docs/Packets/C4-C7-OpenLetterExtended_by-server.md @@ -0,0 +1,22 @@ +# C4 C7 - OpenLetterExtended (by server) + +## Is sent when + +After the player requested to read a letter. + +## Causes the following actions on the client side + +The letter is opened in a new dialog. + +## Structure + +| Index | Length | Data Type | Value | Description | +|-------|--------|-----------|-------|-------------| +| 0 | 1 | Byte | 0xC4 | [Packet type](PacketTypes.md) | +| 1 | 2 | Short | | Packet header - length of the packet | +| 3 | 1 | Byte | 0xC7 | Packet header - packet type identifier | +| 4 | 2 | ShortLittleEndian | | LetterIndex | +| 6 | 42 | Binary | | SenderAppearance | +| 48 | 1 | Byte | | Rotation | +| 49 | 1 | Byte | | Animation | +| 50 | | String | | Message | \ No newline at end of file diff --git a/docs/Packets/ServerToClient.md b/docs/Packets/ServerToClient.md index 6811d291a..c8d9facdc 100644 --- a/docs/Packets/ServerToClient.md +++ b/docs/Packets/ServerToClient.md @@ -168,6 +168,7 @@ * [C1 C5 - LetterSendResponse (by server)](C1-C5-LetterSendResponse_by-server.md) * [C3 C6 - AddLetter (by server)](C3-C6-AddLetter_by-server.md) * [C4 C7 - OpenLetter (by server)](C4-C7-OpenLetter_by-server.md) + * [C4 C7 - OpenLetterExtended (by server)](C4-C7-OpenLetterExtended_by-server.md) * [C1 C8 - RemoveLetter (by server)](C1-C8-RemoveLetter_by-server.md) * [C3 CA - ChatRoomConnectionInfo (by server)](C3-CA-ChatRoomConnectionInfo_by-server.md) * [C3 CB - FriendInvitationResult (by server)](C3-CB-FriendInvitationResult_by-server.md) diff --git a/src/GameServer/RemoteView/Messenger/ShowLetterExtendedPlugIn.cs b/src/GameServer/RemoteView/Messenger/ShowLetterExtendedPlugIn.cs new file mode 100644 index 000000000..ef6a84b7f --- /dev/null +++ b/src/GameServer/RemoteView/Messenger/ShowLetterExtendedPlugIn.cs @@ -0,0 +1,64 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.GameServer.RemoteView.Messenger; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.DataModel.Entities; +using MUnique.OpenMU.GameLogic.Views.Messenger; +using MUnique.OpenMU.Network; +using MUnique.OpenMU.Network.Packets.ServerToClient; +using MUnique.OpenMU.Network.PlugIns; +using MUnique.OpenMU.PlugIns; + +/// +/// The default implementation of the which is forwarding everything to the game client with specific data packets. +/// +[PlugIn(nameof(ShowLetterExtendedPlugIn), "The extended implementation of the IShowLetterPlugIn which is forwarding everything to the game client with specific data packets.")] +[Guid("9FC6D771-FCCE-4780-B68E-5FBFF3FE1EB0")] +[MinimumClient(106, 3, ClientLanguage.Invariant)] +public class ShowLetterExtendedPlugIn : IShowLetterPlugIn +{ + private readonly RemotePlayer _player; + + /// + /// Initializes a new instance of the class. + /// + /// The player. + public ShowLetterExtendedPlugIn(RemotePlayer player) => this._player = player; + + /// + public async ValueTask ShowLetterAsync(LetterBody letter) + { + var connection = this._player.Connection; + if (connection is null || this._player.SelectedCharacter is null) + { + return; + } + + var appearanceSerializer = this._player.AppearanceSerializer; + var letterIndex = this._player.SelectedCharacter.Letters.IndexOf(letter.Header!); + int Write() + { + var size = OpenLetterExtendedRef.GetRequiredSize(letter.Message); + var span = connection.Output.GetSpan(size)[..size]; + var result = new OpenLetterExtendedRef(span) + { + LetterIndex = (ushort)letterIndex, + Animation = letter.Animation, + Rotation = letter.Rotation, + Message = letter.Message, + }; + + if (letter.SenderAppearance is not null) + { + appearanceSerializer.WriteAppearanceData(result.SenderAppearance, letter.SenderAppearance, false); + } + + return size; + } + + await connection.SendAsync(Write).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs index 63d7cc837..668e822af 100644 --- a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs +++ b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs @@ -5546,6 +5546,42 @@ int WritePacket() await connection.SendAsync(WritePacket).ConfigureAwait(false); } + /// + /// Sends a to this connection. + /// + /// The connection. + /// The letter index. + /// The sender appearance. + /// The rotation. + /// The animation. + /// The message. + /// + /// Is sent by the server when: After the player requested to read a letter. + /// Causes reaction on client side: The letter is opened in a new dialog. + /// + public static async ValueTask SendOpenLetterExtendedAsync(this IConnection? connection, ushort @letterIndex, Memory @senderAppearance, byte @rotation, byte @animation, string @message) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = OpenLetterExtendedRef.GetRequiredSize(message); + var packet = new OpenLetterExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.LetterIndex = @letterIndex; + @senderAppearance.Span.CopyTo(packet.SenderAppearance); + packet.Rotation = @rotation; + packet.Animation = @animation; + packet.Message = @message; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + /// /// Sends a to this connection. /// diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs index d9eb82efd..f01611cf6 100644 --- a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs +++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs @@ -26790,6 +26790,127 @@ public string Message } +/// +/// Is sent by the server when: After the player requested to read a letter. +/// Causes reaction on client side: The letter is opened in a new dialog. +/// +public readonly struct OpenLetterExtended +{ + private readonly Memory _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public OpenLetterExtended(Memory data) + : this(data, true) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + /// If set to true, the header data is automatically initialized and written to the underlying span. + private OpenLetterExtended(Memory data, bool initialize) + { + this._data = data; + if (initialize) + { + var header = this.Header; + header.Type = HeaderType; + header.Code = Code; + header.Length = (ushort)data.Length; + } + } + + /// + /// Gets the header type of this data packet. + /// + public static byte HeaderType => 0xC4; + + /// + /// Gets the operation code of this data packet. + /// + public static byte Code => 0xC7; + + /// + /// Gets the header of this packet. + /// + public C4Header Header => new (this._data); + + /// + /// Gets or sets the letter index. + /// + public ushort LetterIndex + { + get => ReadUInt16LittleEndian(this._data.Span[4..]); + set => WriteUInt16LittleEndian(this._data.Span[4..], value); + } + + /// + /// Gets or sets the sender appearance. + /// + public Span SenderAppearance + { + get => this._data.Slice(6, 42).Span; + } + + /// + /// Gets or sets the rotation. + /// + public byte Rotation + { + get => this._data.Span[48]; + set => this._data.Span[48] = value; + } + + /// + /// Gets or sets the animation. + /// + public byte Animation + { + get => this._data.Span[49]; + set => this._data.Span[49] = value; + } + + /// + /// Gets or sets the message. + /// + public string Message + { + get => this._data.Span.ExtractString(50, this._data.Length - 50, System.Text.Encoding.UTF8); + set => this._data.Slice(50).Span.WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Performs an implicit conversion from a Memory of bytes to a . + /// + /// The packet as span. + /// The packet as struct. + public static implicit operator OpenLetterExtended(Memory packet) => new (packet, false); + + /// + /// Performs an implicit conversion from to a Memory of bytes. + /// + /// The packet as struct. + /// The packet as byte span. + public static implicit operator Memory(OpenLetterExtended packet) => packet._data; + + /// + /// Calculates the size of the packet for the specified field content. + /// + /// The content of the variable 'Message' field from which the size will be calculated. + public static int GetRequiredSize(string content) => System.Text.Encoding.UTF8.GetByteCount(content) + 1 + 50; + + /// + /// Calculates the size of the packet for the specified field content. + /// + /// The content length in bytes of the variable 'Message' field from which the size will be calculated. + public static int GetRequiredSize(int contentLength) => contentLength + 1 + 50; +} + + /// /// Is sent by the server when: After a letter has been deleted by the request of the player. /// Causes reaction on client side: The letter is removed from the letter list. diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml index d8064082f..25b9e48b7 100644 --- a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml +++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml @@ -9596,6 +9596,42 @@ + + C4Header + C7 + OpenLetterExtended + ServerToClient + After the player requested to read a letter. + The letter is opened in a new dialog. + + + 4 + ShortLittleEndian + LetterIndex + + + 6 + Binary + SenderAppearance + 42 + + + 48 + Byte + Rotation + + + 49 + Byte + Animation + + + 50 + String + Message + + + C1Header C8 diff --git a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs index 3f73cb01b..dedc95a75 100644 --- a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs +++ b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs @@ -25510,6 +25510,127 @@ public string Message } +/// +/// Is sent by the server when: After the player requested to read a letter. +/// Causes reaction on client side: The letter is opened in a new dialog. +/// +public readonly ref struct OpenLetterExtendedRef +{ + private readonly Span _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public OpenLetterExtendedRef(Span data) + : this(data, true) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + /// If set to true, the header data is automatically initialized and written to the underlying span. + private OpenLetterExtendedRef(Span data, bool initialize) + { + this._data = data; + if (initialize) + { + var header = this.Header; + header.Type = HeaderType; + header.Code = Code; + header.Length = (ushort)data.Length; + } + } + + /// + /// Gets the header type of this data packet. + /// + public static byte HeaderType => 0xC4; + + /// + /// Gets the operation code of this data packet. + /// + public static byte Code => 0xC7; + + /// + /// Gets the header of this packet. + /// + public C4HeaderRef Header => new (this._data); + + /// + /// Gets or sets the letter index. + /// + public ushort LetterIndex + { + get => ReadUInt16LittleEndian(this._data[4..]); + set => WriteUInt16LittleEndian(this._data[4..], value); + } + + /// + /// Gets or sets the sender appearance. + /// + public Span SenderAppearance + { + get => this._data.Slice(6, 42); + } + + /// + /// Gets or sets the rotation. + /// + public byte Rotation + { + get => this._data[48]; + set => this._data[48] = value; + } + + /// + /// Gets or sets the animation. + /// + public byte Animation + { + get => this._data[49]; + set => this._data[49] = value; + } + + /// + /// Gets or sets the message. + /// + public string Message + { + get => this._data.ExtractString(50, this._data.Length - 50, System.Text.Encoding.UTF8); + set => this._data.Slice(50).WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Performs an implicit conversion from a Span of bytes to a . + /// + /// The packet as span. + /// The packet as struct. + public static implicit operator OpenLetterExtendedRef(Span packet) => new (packet, false); + + /// + /// Performs an implicit conversion from to a Span of bytes. + /// + /// The packet as struct. + /// The packet as byte span. + public static implicit operator Span(OpenLetterExtendedRef packet) => packet._data; + + /// + /// Calculates the size of the packet for the specified field content. + /// + /// The content of the variable 'Message' field from which the size will be calculated. + public static int GetRequiredSize(string content) => System.Text.Encoding.UTF8.GetByteCount(content) + 1 + 50; + + /// + /// Calculates the size of the packet for the specified field content. + /// + /// The content length in bytes of the variable 'Message' field from which the size will be calculated. + public static int GetRequiredSize(int contentLength) => contentLength + 1 + 50; +} + + /// /// Is sent by the server when: After a letter has been deleted by the request of the player. /// Causes reaction on client side: The letter is removed from the letter list.