From 30b0fd1bc9975ead6c91c0aee5bdff9c164a73fb Mon Sep 17 00:00:00 2001 From: Acentech Dev Date: Mon, 27 Jul 2026 01:19:53 +0300 Subject: [PATCH] Regenerate packet code from merged XML (TvT + chat-command packets) The ClientToServer/ServerToClient packet .cs/.md are XSL-generated from the .xml (Network.Packets PreBuild). Regenerated after hand-merging the packet XML so the generated structs contain BOTH the AdaMu custom TvT (HeykelSavasi FA/FB/FC/FD + TeamSelect) packets AND the upstream chat-command packets (F5/00 request, F5/01 AvailableChatCommand). Co-Authored-By: Claude Opus 4.8 (1M context) --- ...1-1B-MagicEffectCancelRequest_by-client.md | 2 +- .../ClientToServer/ClientToServerPackets.cs | 77 +++++++ .../ClientToServerPacketsRef.cs | 77 +++++++ .../ClientToServer/ConnectionExtensions.cs | 25 ++ .../ServerToClient/ServerToClientPackets.cs | 215 ++++++++++++++++++ .../ServerToClientPacketsRef.cs | 215 ++++++++++++++++++ 6 files changed, 610 insertions(+), 1 deletion(-) diff --git a/docs/Packets/C1-1B-MagicEffectCancelRequest_by-client.md b/docs/Packets/C1-1B-MagicEffectCancelRequest_by-client.md index abba606..06bbf52 100644 --- a/docs/Packets/C1-1B-MagicEffectCancelRequest_by-client.md +++ b/docs/Packets/C1-1B-MagicEffectCancelRequest_by-client.md @@ -2,7 +2,7 @@ ## Is sent when -A player cancels a specific magic effect of a skill, usually 'Infinity Arrow' and 'Wizardy Enhance'. +A player cancels a specific magic effect of a skill, usually 'Infinity Arrow' and 'Wizardry Enhance'. ## Causes the following actions on the server side diff --git a/src/Network/Packets/ClientToServer/ClientToServerPackets.cs b/src/Network/Packets/ClientToServer/ClientToServerPackets.cs index 9eaeee2..b22a6c7 100644 --- a/src/Network/Packets/ClientToServer/ClientToServerPackets.cs +++ b/src/Network/Packets/ClientToServer/ClientToServerPackets.cs @@ -17604,6 +17604,83 @@ public readonly struct HeykelSavasiTeamSelect /// The packet as byte span. public static implicit operator Memory(HeykelSavasiTeamSelect packet) => packet._data; } + + +/// +/// Is sent by the client when: A client which supports a user interface for chat commands requests the list of commands which are available to the player. It's usually sent after the character entered the game world. +/// Causes reaction on server side: The server sends an AvailableChatCommand message for each available chat command. +/// +public readonly struct ChatCommandListRequest +{ + private readonly Memory _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public ChatCommandListRequest(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 ChatCommandListRequest(Memory data, bool initialize) + { + this._data = data; + if (initialize) + { + var header = this.Header; + header.Type = HeaderType; + header.Code = Code; + header.Length = (byte)Math.Min(data.Length, Length); + header.SubCode = SubCode; + } + } + + /// + /// Gets the header type of this data packet. + /// + public static byte HeaderType => 0xC1; + + /// + /// Gets the operation code of this data packet. + /// + public static byte Code => 0xF5; + + /// + /// Gets the operation sub-code of this data packet. + /// The is used as a grouping key. + /// + public static byte SubCode => 0x00; + + /// + /// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed. + /// + public static int Length => 4; + + /// + /// Gets the header of this packet. + /// + public C1HeaderWithSubCode Header => new (this._data); + + /// + /// Performs an implicit conversion from a Memory of bytes to a . + /// + /// The packet as span. + /// The packet as struct. + public static implicit operator ChatCommandListRequest(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(ChatCommandListRequest packet) => packet._data; +} /// /// The state of the trade button. /// diff --git a/src/Network/Packets/ClientToServer/ClientToServerPacketsRef.cs b/src/Network/Packets/ClientToServer/ClientToServerPacketsRef.cs index 94a9a49..551379d 100644 --- a/src/Network/Packets/ClientToServer/ClientToServerPacketsRef.cs +++ b/src/Network/Packets/ClientToServer/ClientToServerPacketsRef.cs @@ -17397,3 +17397,80 @@ public readonly ref struct HeykelSavasiTeamSelectRef /// The packet as byte span. public static implicit operator Span(HeykelSavasiTeamSelectRef packet) => packet._data; } + + +/// +/// Is sent by the client when: A client which supports a user interface for chat commands requests the list of commands which are available to the player. It's usually sent after the character entered the game world. +/// Causes reaction on server side: The server sends an AvailableChatCommand message for each available chat command. +/// +public readonly ref struct ChatCommandListRequestRef +{ + private readonly Span _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public ChatCommandListRequestRef(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 ChatCommandListRequestRef(Span data, bool initialize) + { + this._data = data; + if (initialize) + { + var header = this.Header; + header.Type = HeaderType; + header.Code = Code; + header.Length = (byte)Math.Min(data.Length, Length); + header.SubCode = SubCode; + } + } + + /// + /// Gets the header type of this data packet. + /// + public static byte HeaderType => 0xC1; + + /// + /// Gets the operation code of this data packet. + /// + public static byte Code => 0xF5; + + /// + /// Gets the operation sub-code of this data packet. + /// The is used as a grouping key. + /// + public static byte SubCode => 0x00; + + /// + /// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed. + /// + public static int Length => 4; + + /// + /// Gets the header of this packet. + /// + public C1HeaderWithSubCodeRef Header => new (this._data); + + /// + /// Performs an implicit conversion from a Span of bytes to a . + /// + /// The packet as span. + /// The packet as struct. + public static implicit operator ChatCommandListRequestRef(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(ChatCommandListRequestRef packet) => packet._data; +} diff --git a/src/Network/Packets/ClientToServer/ConnectionExtensions.cs b/src/Network/Packets/ClientToServer/ConnectionExtensions.cs index 6f3f0d5..5814469 100644 --- a/src/Network/Packets/ClientToServer/ConnectionExtensions.cs +++ b/src/Network/Packets/ClientToServer/ConnectionExtensions.cs @@ -5574,5 +5574,30 @@ public static class ConnectionExtensions return packet.Header.Length; } + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + + /// + /// Sends a to this connection. + /// + /// The connection. + /// + /// Is sent by the client when: A client which supports a user interface for chat commands requests the list of commands which are available to the player. It's usually sent after the character entered the game world. + /// Causes reaction on server side: The server sends an AvailableChatCommand message for each available chat command. + /// + public static async ValueTask SendChatCommandListRequestAsync(this IConnection? connection) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = ChatCommandListRequestRef.Length; + var packet = new ChatCommandListRequestRef(connection.Output.GetSpan(length)[..length]); + return packet.Header.Length; + } + await connection.SendAsync(WritePacket).ConfigureAwait(false); }} \ No newline at end of file diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs index 054452e..0399bf2 100644 --- a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs +++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs @@ -31163,6 +31163,221 @@ public readonly struct ScoreEntry set => WriteUInt32BigEndian(this._data.Span[13..], value); } } +} + + +/// +/// Is sent by the server when: After the client requested the list of available chat commands. One message is sent for each command which is available to the player. +/// Causes reaction on client side: The client adds the command to its list of known commands, so that it can offer them to the player without requiring him to know or type them. +/// +public readonly struct AvailableChatCommand +{ + private readonly Memory _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public AvailableChatCommand(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 AvailableChatCommand(Memory data, bool initialize) + { + this._data = data; + if (initialize) + { + var header = this.Header; + header.Type = HeaderType; + header.Code = Code; + header.Length = (ushort)data.Length; + header.SubCode = SubCode; + } + } + + /// + /// Gets the header type of this data packet. + /// + public static byte HeaderType => 0xC2; + + /// + /// Gets the operation code of this data packet. + /// + public static byte Code => 0xF5; + + /// + /// Gets the operation sub-code of this data packet. + /// The is used as a grouping key. + /// + public static byte SubCode => 0x01; + + /// + /// Gets the header of this packet. + /// + public C2HeaderWithSubCode Header => new (this._data); + + /// + /// Gets or sets the index of this command within the list, starting at 0. + /// + public byte Index + { + get => this._data.Span[5]; + set => this._data.Span[5] = value; + } + + /// + /// Gets or sets the total number of commands which are available to the player, so that the client knows when it received all of them. + /// + public byte Count + { + get => this._data.Span[6]; + set => this._data.Span[6] = value; + } + + /// + /// Gets or sets the character status which is required to execute the command. + /// + public CharacterStatus MinimumCharacterStatus + { + get => (CharacterStatus)this._data.Span[7]; + set => this._data.Span[7] = (byte)value; + } + + /// + /// Gets or sets the parameter count. + /// + public byte ParameterCount + { + get => this._data.Span[8]; + set => this._data.Span[8] = value; + } + + /// + /// Gets or sets the command including its slash, e.g. '/item'. + /// + public string Command + { + get => this._data.Span.ExtractString(9, 32, System.Text.Encoding.UTF8); + set => this._data.Slice(9, 32).Span.WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Gets or sets the name of the command, in the language of the player. + /// + public string Name + { + get => this._data.Span.ExtractString(41, 48, System.Text.Encoding.UTF8); + set => this._data.Slice(41, 48).Span.WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Gets or sets the description of the command, in the language of the player. + /// + public string Description + { + get => this._data.Span.ExtractString(89, 256, System.Text.Encoding.UTF8); + set => this._data.Slice(89, 256).Span.WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Gets the of the specified index. + /// + public ChatCommandParameter this[int index] => new (this._data.Slice(345 + index * ChatCommandParameter.Length)); + + /// + /// Performs an implicit conversion from a Memory of bytes to a . + /// + /// The packet as span. + /// The packet as struct. + public static implicit operator AvailableChatCommand(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(AvailableChatCommand packet) => packet._data; + + /// + /// Calculates the size of the packet for the specified count of . + /// + /// The count of from which the size will be calculated. + + public static int GetRequiredSize(int parametersCount) => parametersCount * ChatCommandParameter.Length + 345; + + +/// +/// Describes one parameter of a chat command, so that a user interface can offer an input for it.. +/// +public readonly struct ChatCommandParameter +{ + private readonly Memory _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public ChatCommandParameter(Memory data) + { + this._data = data; + } + + /// + /// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed. + /// + public static int Length => 102; + + /// + /// Gets or sets defines if the parameter has to be specified to execute the command. + /// + public bool IsRequired + { + get => this._data.Span.GetBoolean(); + set => this._data.Span.SetBoolean(value); + } + + /// + /// Gets or sets the kind of value which is expected, so that a fitting input can be shown. + /// + public ChatCommandParameterType Type + { + get => (ChatCommandParameterType)this._data.Span[1]; + set => this._data.Span[1] = (byte)value; + } + + /// + /// Gets or sets the name. + /// + public string Name + { + get => this._data.Span.ExtractString(2, 32, System.Text.Encoding.UTF8); + set => this._data.Slice(2, 32).Span.WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Gets or sets the short name which is used in the 'shortName=value' notation. It's empty when the parameter can only be passed by its position. + /// + public string ShortName + { + get => this._data.Span.ExtractString(34, 20, System.Text.Encoding.UTF8); + set => this._data.Slice(34, 20).Span.WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Gets or sets the accepted values, separated by a pipe. It's empty when the parameter isn't limited to a set of values. + /// + public string ValidValues + { + get => this._data.Span.ExtractString(54, 48, System.Text.Encoding.UTF8); + set => this._data.Slice(54, 48).Span.WriteString(value, System.Text.Encoding.UTF8); + } +} } /// /// Defines the role of a guild member. diff --git a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs index f054c49..ad319f8 100644 --- a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs +++ b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs @@ -29527,3 +29527,218 @@ public readonly ref struct ScoreEntryRef } } } + + +/// +/// Is sent by the server when: After the client requested the list of available chat commands. One message is sent for each command which is available to the player. +/// Causes reaction on client side: The client adds the command to its list of known commands, so that it can offer them to the player without requiring him to know or type them. +/// +public readonly ref struct AvailableChatCommandRef +{ + private readonly Span _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public AvailableChatCommandRef(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 AvailableChatCommandRef(Span data, bool initialize) + { + this._data = data; + if (initialize) + { + var header = this.Header; + header.Type = HeaderType; + header.Code = Code; + header.Length = (ushort)data.Length; + header.SubCode = SubCode; + } + } + + /// + /// Gets the header type of this data packet. + /// + public static byte HeaderType => 0xC2; + + /// + /// Gets the operation code of this data packet. + /// + public static byte Code => 0xF5; + + /// + /// Gets the operation sub-code of this data packet. + /// The is used as a grouping key. + /// + public static byte SubCode => 0x01; + + /// + /// Gets the header of this packet. + /// + public C2HeaderWithSubCodeRef Header => new (this._data); + + /// + /// Gets or sets the index of this command within the list, starting at 0. + /// + public byte Index + { + get => this._data[5]; + set => this._data[5] = value; + } + + /// + /// Gets or sets the total number of commands which are available to the player, so that the client knows when it received all of them. + /// + public byte Count + { + get => this._data[6]; + set => this._data[6] = value; + } + + /// + /// Gets or sets the character status which is required to execute the command. + /// + public CharacterStatus MinimumCharacterStatus + { + get => (CharacterStatus)this._data[7]; + set => this._data[7] = (byte)value; + } + + /// + /// Gets or sets the parameter count. + /// + public byte ParameterCount + { + get => this._data[8]; + set => this._data[8] = value; + } + + /// + /// Gets or sets the command including its slash, e.g. '/item'. + /// + public string Command + { + get => this._data.ExtractString(9, 32, System.Text.Encoding.UTF8); + set => this._data.Slice(9, 32).WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Gets or sets the name of the command, in the language of the player. + /// + public string Name + { + get => this._data.ExtractString(41, 48, System.Text.Encoding.UTF8); + set => this._data.Slice(41, 48).WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Gets or sets the description of the command, in the language of the player. + /// + public string Description + { + get => this._data.ExtractString(89, 256, System.Text.Encoding.UTF8); + set => this._data.Slice(89, 256).WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Gets the of the specified index. + /// + public ChatCommandParameterRef this[int index] => new (this._data[(345 + index * ChatCommandParameterRef.Length)..]); + + /// + /// Performs an implicit conversion from a Span of bytes to a . + /// + /// The packet as span. + /// The packet as struct. + public static implicit operator AvailableChatCommandRef(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(AvailableChatCommandRef packet) => packet._data; + + /// + /// Calculates the size of the packet for the specified count of . + /// + /// The count of from which the size will be calculated. + + public static int GetRequiredSize(int parametersCount) => parametersCount * ChatCommandParameterRef.Length + 345; + + +/// +/// Describes one parameter of a chat command, so that a user interface can offer an input for it.. +/// +public readonly ref struct ChatCommandParameterRef +{ + private readonly Span _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public ChatCommandParameterRef(Span data) + { + this._data = data; + } + + /// + /// Gets the initial length of this data packet. When the size is dynamic, this value may be bigger than actually needed. + /// + public static int Length => 102; + + /// + /// Gets or sets defines if the parameter has to be specified to execute the command. + /// + public bool IsRequired + { + get => this._data.GetBoolean(); + set => this._data.SetBoolean(value); + } + + /// + /// Gets or sets the kind of value which is expected, so that a fitting input can be shown. + /// + public ChatCommandParameterType Type + { + get => (ChatCommandParameterType)this._data[1]; + set => this._data[1] = (byte)value; + } + + /// + /// Gets or sets the name. + /// + public string Name + { + get => this._data.ExtractString(2, 32, System.Text.Encoding.UTF8); + set => this._data.Slice(2, 32).WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Gets or sets the short name which is used in the 'shortName=value' notation. It's empty when the parameter can only be passed by its position. + /// + public string ShortName + { + get => this._data.ExtractString(34, 20, System.Text.Encoding.UTF8); + set => this._data.Slice(34, 20).WriteString(value, System.Text.Encoding.UTF8); + } + + /// + /// Gets or sets the accepted values, separated by a pipe. It's empty when the parameter isn't limited to a set of values. + /// + public string ValidValues + { + get => this._data.ExtractString(54, 48, System.Text.Encoding.UTF8); + set => this._data.Slice(54, 48).WriteString(value, System.Text.Encoding.UTF8); + } +} +}