diff --git a/docs/Packets/C1-3F-06-PlayerShopBuyResultExtended_by-server.md b/docs/Packets/C1-3F-06-PlayerShopBuyResultExtended_by-server.md index 51b1e1ed9d..8543eda9d7 100644 --- a/docs/Packets/C1-3F-06-PlayerShopBuyResultExtended_by-server.md +++ b/docs/Packets/C1-3F-06-PlayerShopBuyResultExtended_by-server.md @@ -36,4 +36,5 @@ The kind of result. | 6 | NameMismatchOrPriceMissing | The requested player with the specified id has a different name or price is missing. | | 7 | LackOfMoney | The player has not enough money to buy the item from the seller. | | 8 | MoneyOverflowOrNotEnoughSpace | The selling player cannot sell the item, because the sale would overflow his money amount in the inventory. Another possibility is that the inventory of the buyer cannot take the item. | -| 9 | ItemBlock | The requested player has item block active. | \ No newline at end of file +| 9 | ItemBlock | The requested player has item block active. | +| 10 | LackOfPriceCurrency | The player has not enough of the currency (jewels or account coins) in which the price of the item is defined. | \ No newline at end of file diff --git a/docs/Packets/C1-3F-0A-PlayerShopPriceTypesExtended_by-server.md b/docs/Packets/C1-3F-0A-PlayerShopPriceTypesExtended_by-server.md new file mode 100644 index 0000000000..87d23475c7 --- /dev/null +++ b/docs/Packets/C1-3F-0A-PlayerShopPriceTypesExtended_by-server.md @@ -0,0 +1,30 @@ +# C1 3F 0A - PlayerShopPriceTypesExtended (by server) + +## Is sent when + +After entering the game world, and before the player shop of another player is shown, when the server allows prices in player shops which are not paid in zen. + +## Causes the following actions on the client side + +The client offers the allowed price types, in addition to zen, when the player sets an item price in his player shop. Without this message, only zen prices are possible. + +## Structure + +| Index | Length | Data Type | Value | Description | +|-------|--------|-----------|-------|-------------| +| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) | +| 1 | 1 | Byte | | Packet header - length of the packet | +| 2 | 1 | Byte | 0x3F | Packet header - packet type identifier | +| 3 | 1 | Byte | 0x0A | Packet header - sub packet type identifier | +| 4 | 1 | Byte | | Count | +| 5 | PlayerShopPriceType.Length * Count | Array of PlayerShopPriceType | | PriceTypes | + +### PlayerShopPriceType Structure + +A price type which is allowed for prices in player shops, in addition to zen. + +Length: 2 Bytes + +| Index | Length | Data Type | Value | Description | +|-------|--------|-----------|-------|-------------| +| 0 | 2 | ShortLittleEndian | | PriceItemType; The value of PriceItemType of a price of this type, like in PlayerShopItemExtended: the item (group in the highest 4 bits, number in the remaining ones) with which the price is paid, or 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P), 0xFFF2 = Goblin Points. | \ No newline at end of file diff --git a/docs/Packets/C2-3F-05-PlayerShopItemListExtended_by-server.md b/docs/Packets/C2-3F-05-PlayerShopItemListExtended_by-server.md index 07a3e36801..0941492a83 100644 --- a/docs/Packets/C2-3F-05-PlayerShopItemListExtended_by-server.md +++ b/docs/Packets/C2-3F-05-PlayerShopItemListExtended_by-server.md @@ -30,9 +30,9 @@ Data of an item in a player shop, which allows for dynamic item sizes and trades | Index | Length | Data Type | Value | Description | |-------|--------|-----------|-------|-------------| -| 0 | 4 | IntegerLittleEndian | | MoneyPrice | -| 4 | 2 | ShortLittleEndian | | PriceItemType; Contains the item group in the highest 4 bits, and the item number in the remaining ones. | -| 6 | 2 | ShortLittleEndian | | RequiredItemAmount | +| 0 | 4 | IntegerLittleEndian | | MoneyPrice; The price in zen, or in account coins when PriceItemType is one of the account coin values (see there). It's 0 when the price is defined in items. | +| 4 | 2 | ShortLittleEndian | | PriceItemType; When RequiredItemAmount is greater than 0, it contains the item group in the highest 4 bits, and the item number in the remaining ones, of the item (e.g. a jewel) which is required to buy the item. When RequiredItemAmount is 0, the following reserved values define an account coin as currency, with the amount in MoneyPrice: 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P), 0xFFF2 = Goblin Points. Any other value means that MoneyPrice is the price in zen. | +| 6 | 2 | ShortLittleEndian | | RequiredItemAmount; The number of pieces of the item which is defined by PriceItemType, which is required to buy the item. 0, if the price is not defined in items. | | 8 | 1 | Byte | | ItemSlot | | 9 | | Binary | | ItemData | diff --git a/docs/Packets/C3-3F-09-PlayerShopSetItemPriceExtended_by-client.md b/docs/Packets/C3-3F-09-PlayerShopSetItemPriceExtended_by-client.md new file mode 100644 index 0000000000..59e95bb275 --- /dev/null +++ b/docs/Packets/C3-3F-09-PlayerShopSetItemPriceExtended_by-client.md @@ -0,0 +1,22 @@ +# C3 3F 09 - PlayerShopSetItemPriceExtended (by client) + +## Is sent when + +The player wants to set a price of an item which is inside his personal item shop, in zen or in one of the price types which are allowed by the server (see PlayerShopPriceTypesExtended). + +## Causes the following actions on the server side + +The price is set for the specified item, if its price type is allowed and the amount is within the configured limits. Works only if the shop is currently closed. The server responds with a PlayerShopSetItemPriceResponse. + +## Structure + +| Index | Length | Data Type | Value | Description | +|-------|--------|-----------|-------|-------------| +| 0 | 1 | Byte | 0xC3 | [Packet type](PacketTypes.md) | +| 1 | 1 | Byte | 13 | Packet header - length of the packet | +| 2 | 1 | Byte | 0x3F | Packet header - packet type identifier | +| 3 | 1 | Byte | 0x09 | Packet header - sub packet type identifier | +| 4 | 4 | IntegerLittleEndian | | MoneyPrice; The price in zen, or in account coins when PriceItemType is one of the account coin values. Encoded like the price of PlayerShopItemExtended. | +| 8 | 2 | ShortLittleEndian | | PriceItemType; The item (group in the highest 4 bits, number in the remaining ones) with which the price is paid, when RequiredItemAmount is greater than 0. Otherwise, 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P) and 0xFFF2 = Goblin Points define an account coin price; any other value a zen price. Encoded like the price of PlayerShopItemExtended. | +| 10 | 2 | ShortLittleEndian | | RequiredItemAmount; The number of pieces of the item which is defined by PriceItemType; 0, if the price is not paid with items. Encoded like the price of PlayerShopItemExtended. | +| 12 | 1 | Byte | | ItemSlot | \ No newline at end of file diff --git a/docs/Packets/ClientToServer.md b/docs/Packets/ClientToServer.md index 741eaf2412..ecb090c8c2 100644 --- a/docs/Packets/ClientToServer.md +++ b/docs/Packets/ClientToServer.md @@ -43,6 +43,7 @@ * [C3 3F 05 - PlayerShopItemListRequest (by client)](C3-3F-05-PlayerShopItemListRequest_by-client.md) * [C3 3F 06 - PlayerShopItemBuyRequest (by client)](C3-3F-06-PlayerShopItemBuyRequest_by-client.md) * [C3 3F 07 - PlayerShopCloseOther (by client)](C3-3F-07-PlayerShopCloseOther_by-client.md) + * [C3 3F 09 - PlayerShopSetItemPriceExtended (by client)](C3-3F-09-PlayerShopSetItemPriceExtended_by-client.md) * [C1 40 - PartyInviteRequest (by client)](C1-40-PartyInviteRequest_by-client.md) * [C1 41 - PartyInviteResponse (by client)](C1-41-PartyInviteResponse_by-client.md) * [C1 42 - PartyListRequest (by client)](C1-42-PartyListRequest_by-client.md) diff --git a/docs/Packets/ServerToClient.md b/docs/Packets/ServerToClient.md index bb41254ab6..e894f8d921 100644 --- a/docs/Packets/ServerToClient.md +++ b/docs/Packets/ServerToClient.md @@ -81,6 +81,7 @@ * [C1 3F 06 - PlayerShopBuyResult (by server)](C1-3F-06-PlayerShopBuyResult_by-server.md) * [C1 3F 06 - PlayerShopBuyResultExtended (by server)](C1-3F-06-PlayerShopBuyResultExtended_by-server.md) * [C1 3F 08 - PlayerShopItemSoldToPlayer (by server)](C1-3F-08-PlayerShopItemSoldToPlayer_by-server.md) + * [C1 3F 0A - PlayerShopPriceTypesExtended (by server)](C1-3F-0A-PlayerShopPriceTypesExtended_by-server.md) * [C1 3F 12 - ClosePlayerShopDialog (by server)](C1-3F-12-ClosePlayerShopDialog_by-server.md) * [C1 3F 2 - PlayerShopOpenSuccessful (by server)](C1-3F-2-PlayerShopOpenSuccessful_by-server.md) * [C1 3F 3 - PlayerShopClosed (by server)](C1-3F-3-PlayerShopClosed_by-server.md) diff --git a/src/Network/Packets/ClientToServer/ClientToServerPackets.cs b/src/Network/Packets/ClientToServer/ClientToServerPackets.cs index e1e09e34a3..48208f1143 100644 --- a/src/Network/Packets/ClientToServer/ClientToServerPackets.cs +++ b/src/Network/Packets/ClientToServer/ClientToServerPackets.cs @@ -1544,6 +1544,119 @@ public string PlayerName } +/// +/// Is sent by the client when: The player wants to set a price of an item which is inside his personal item shop, in zen or in one of the price types which are allowed by the server (see PlayerShopPriceTypesExtended). +/// Causes reaction on server side: The price is set for the specified item, if its price type is allowed and the amount is within the configured limits. Works only if the shop is currently closed. The server responds with a PlayerShopSetItemPriceResponse. +/// +public readonly struct PlayerShopSetItemPriceExtended +{ + private readonly Memory _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public PlayerShopSetItemPriceExtended(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 PlayerShopSetItemPriceExtended(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 => 0xC3; + + /// + /// Gets the operation code of this data packet. + /// + public static byte Code => 0x3F; + + /// + /// Gets the operation sub-code of this data packet. + /// The is used as a grouping key. + /// + public static byte SubCode => 0x09; + + /// + /// 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 => 13; + + /// + /// Gets the header of this packet. + /// + public C3HeaderWithSubCode Header => new (this._data); + + /// + /// Gets or sets the price in zen, or in account coins when PriceItemType is one of the account coin values. Encoded like the price of PlayerShopItemExtended. + /// + public uint MoneyPrice + { + get => ReadUInt32LittleEndian(this._data.Span[4..]); + set => WriteUInt32LittleEndian(this._data.Span[4..], value); + } + + /// + /// Gets or sets the item (group in the highest 4 bits, number in the remaining ones) with which the price is paid, when RequiredItemAmount is greater than 0. Otherwise, 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P) and 0xFFF2 = Goblin Points define an account coin price; any other value a zen price. Encoded like the price of PlayerShopItemExtended. + /// + public ushort PriceItemType + { + get => ReadUInt16LittleEndian(this._data.Span[8..]); + set => WriteUInt16LittleEndian(this._data.Span[8..], value); + } + + /// + /// Gets or sets the number of pieces of the item which is defined by PriceItemType; 0, if the price is not paid with items. Encoded like the price of PlayerShopItemExtended. + /// + public ushort RequiredItemAmount + { + get => ReadUInt16LittleEndian(this._data.Span[10..]); + set => WriteUInt16LittleEndian(this._data.Span[10..], value); + } + + /// + /// Gets or sets the item slot. + /// + public byte ItemSlot + { + get => this._data.Span[12]; + set => this._data.Span[12] = value; + } + + /// + /// Performs an implicit conversion from a Memory of bytes to a . + /// + /// The packet as span. + /// The packet as struct. + public static implicit operator PlayerShopSetItemPriceExtended(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(PlayerShopSetItemPriceExtended packet) => packet._data; +} + + /// /// Is sent by the client when: A player requests to pick up an item which is laying on the ground in the near of the players character. /// Causes reaction on server side: If the player is allowed to pick the item up, and is the first player which tried that, it tries to add the item to the inventory. The server sends a response about the result of the request. diff --git a/src/Network/Packets/ClientToServer/ClientToServerPackets.xml b/src/Network/Packets/ClientToServer/ClientToServerPackets.xml index 33e6a50ee7..965d93352a 100644 --- a/src/Network/Packets/ClientToServer/ClientToServerPackets.xml +++ b/src/Network/Packets/ClientToServer/ClientToServerPackets.xml @@ -397,6 +397,42 @@ + + C3HeaderWithSubCode + 3F + 09 + PlayerShopSetItemPriceExtended + Player Shop - Set Item Price (extended) + 13 + ClientToServer + The player wants to set a price of an item which is inside his personal item shop, in zen or in one of the price types which are allowed by the server (see PlayerShopPriceTypesExtended). + The price is set for the specified item, if its price type is allowed and the amount is within the configured limits. Works only if the shop is currently closed. The server responds with a PlayerShopSetItemPriceResponse. + + + 4 + IntegerLittleEndian + MoneyPrice + The price in zen, or in account coins when PriceItemType is one of the account coin values. Encoded like the price of PlayerShopItemExtended. + + + 8 + ShortLittleEndian + PriceItemType + The item (group in the highest 4 bits, number in the remaining ones) with which the price is paid, when RequiredItemAmount is greater than 0. Otherwise, 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P) and 0xFFF2 = Goblin Points define an account coin price; any other value a zen price. Encoded like the price of PlayerShopItemExtended. + + + 10 + ShortLittleEndian + RequiredItemAmount + The number of pieces of the item which is defined by PriceItemType; 0, if the price is not paid with items. Encoded like the price of PlayerShopItemExtended. + + + 12 + Byte + ItemSlot + + + C3Header 22 diff --git a/src/Network/Packets/ClientToServer/ClientToServerPacketsRef.cs b/src/Network/Packets/ClientToServer/ClientToServerPacketsRef.cs index 8bb115ffab..62a8ff59db 100644 --- a/src/Network/Packets/ClientToServer/ClientToServerPacketsRef.cs +++ b/src/Network/Packets/ClientToServer/ClientToServerPacketsRef.cs @@ -1544,6 +1544,119 @@ public string PlayerName } +/// +/// Is sent by the client when: The player wants to set a price of an item which is inside his personal item shop, in zen or in one of the price types which are allowed by the server (see PlayerShopPriceTypesExtended). +/// Causes reaction on server side: The price is set for the specified item, if its price type is allowed and the amount is within the configured limits. Works only if the shop is currently closed. The server responds with a PlayerShopSetItemPriceResponse. +/// +public readonly ref struct PlayerShopSetItemPriceExtendedRef +{ + private readonly Span _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public PlayerShopSetItemPriceExtendedRef(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 PlayerShopSetItemPriceExtendedRef(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 => 0xC3; + + /// + /// Gets the operation code of this data packet. + /// + public static byte Code => 0x3F; + + /// + /// Gets the operation sub-code of this data packet. + /// The is used as a grouping key. + /// + public static byte SubCode => 0x09; + + /// + /// 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 => 13; + + /// + /// Gets the header of this packet. + /// + public C3HeaderWithSubCodeRef Header => new (this._data); + + /// + /// Gets or sets the price in zen, or in account coins when PriceItemType is one of the account coin values. Encoded like the price of PlayerShopItemExtended. + /// + public uint MoneyPrice + { + get => ReadUInt32LittleEndian(this._data[4..]); + set => WriteUInt32LittleEndian(this._data[4..], value); + } + + /// + /// Gets or sets the item (group in the highest 4 bits, number in the remaining ones) with which the price is paid, when RequiredItemAmount is greater than 0. Otherwise, 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P) and 0xFFF2 = Goblin Points define an account coin price; any other value a zen price. Encoded like the price of PlayerShopItemExtended. + /// + public ushort PriceItemType + { + get => ReadUInt16LittleEndian(this._data[8..]); + set => WriteUInt16LittleEndian(this._data[8..], value); + } + + /// + /// Gets or sets the number of pieces of the item which is defined by PriceItemType; 0, if the price is not paid with items. Encoded like the price of PlayerShopItemExtended. + /// + public ushort RequiredItemAmount + { + get => ReadUInt16LittleEndian(this._data[10..]); + set => WriteUInt16LittleEndian(this._data[10..], value); + } + + /// + /// Gets or sets the item slot. + /// + public byte ItemSlot + { + get => this._data[12]; + set => this._data[12] = value; + } + + /// + /// Performs an implicit conversion from a Span of bytes to a . + /// + /// The packet as span. + /// The packet as struct. + public static implicit operator PlayerShopSetItemPriceExtendedRef(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(PlayerShopSetItemPriceExtendedRef packet) => packet._data; +} + + /// /// Is sent by the client when: A player requests to pick up an item which is laying on the ground in the near of the players character. /// Causes reaction on server side: If the player is allowed to pick the item up, and is the first player which tried that, it tries to add the item to the inventory. The server sends a response about the result of the request. diff --git a/src/Network/Packets/ClientToServer/ConnectionExtensions.cs b/src/Network/Packets/ClientToServer/ConnectionExtensions.cs index ce289c8f9f..d7e7126b63 100644 --- a/src/Network/Packets/ClientToServer/ConnectionExtensions.cs +++ b/src/Network/Packets/ClientToServer/ConnectionExtensions.cs @@ -512,6 +512,40 @@ int WritePacket() await connection.SendAsync(WritePacket).ConfigureAwait(false); } + /// + /// Sends a to this connection. + /// + /// The connection. + /// The price in zen, or in account coins when PriceItemType is one of the account coin values. Encoded like the price of PlayerShopItemExtended. + /// The item (group in the highest 4 bits, number in the remaining ones) with which the price is paid, when RequiredItemAmount is greater than 0. Otherwise, 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P) and 0xFFF2 = Goblin Points define an account coin price; any other value a zen price. Encoded like the price of PlayerShopItemExtended. + /// The number of pieces of the item which is defined by PriceItemType; 0, if the price is not paid with items. Encoded like the price of PlayerShopItemExtended. + /// The item slot. + /// + /// Is sent by the client when: The player wants to set a price of an item which is inside his personal item shop, in zen or in one of the price types which are allowed by the server (see PlayerShopPriceTypesExtended). + /// Causes reaction on server side: The price is set for the specified item, if its price type is allowed and the amount is within the configured limits. Works only if the shop is currently closed. The server responds with a PlayerShopSetItemPriceResponse. + /// + public static async ValueTask SendPlayerShopSetItemPriceExtendedAsync(this IConnection? connection, uint @moneyPrice, ushort @priceItemType, ushort @requiredItemAmount, byte @itemSlot) + { + if (connection is null) + { + return; + } + + int WritePacket() + { + var length = PlayerShopSetItemPriceExtendedRef.Length; + var packet = new PlayerShopSetItemPriceExtendedRef(connection.Output.GetSpan(length)[..length]); + packet.MoneyPrice = @moneyPrice; + packet.PriceItemType = @priceItemType; + packet.RequiredItemAmount = @requiredItemAmount; + packet.ItemSlot = @itemSlot; + + return packet.Header.Length; + } + + await connection.SendAsync(WritePacket).ConfigureAwait(false); + } + /// /// Sends a to this connection. /// diff --git a/src/Network/Packets/MUnique.OpenMU.Network.Packets.csproj b/src/Network/Packets/MUnique.OpenMU.Network.Packets.csproj index ec0f00af74..add339226a 100644 --- a/src/Network/Packets/MUnique.OpenMU.Network.Packets.csproj +++ b/src/Network/Packets/MUnique.OpenMU.Network.Packets.csproj @@ -16,8 +16,8 @@ MUnique OpenMU MUOnline Network Packets MUnique.OpenMU.Network.Packets This package contains message structs for the MMORPG "MU Online", which are primarily focused on the english version of Season 6 Episode 3. - 0.9.10 - 0.9.10 + 0.9.11 + 0.9.11 diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs index f9286bd864..3bca8dbae1 100644 --- a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs +++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs @@ -123,7 +123,7 @@ public PlayerShopItemExtended(Memory data) } /// - /// Gets or sets the money price. + /// Gets or sets the price in zen, or in account coins when PriceItemType is one of the account coin values (see there). It's 0 when the price is defined in items. /// public uint MoneyPrice { @@ -132,7 +132,7 @@ public uint MoneyPrice } /// - /// Gets or sets contains the item group in the highest 4 bits, and the item number in the remaining ones. + /// Gets or sets when RequiredItemAmount is greater than 0, it contains the item group in the highest 4 bits, and the item number in the remaining ones, of the item (e.g. a jewel) which is required to buy the item. When RequiredItemAmount is 0, the following reserved values define an account coin as currency, with the amount in MoneyPrice: 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P), 0xFFF2 = Goblin Points. Any other value means that MoneyPrice is the price in zen. /// public ushort PriceItemType { @@ -141,7 +141,7 @@ public ushort PriceItemType } /// - /// Gets or sets the required item amount. + /// Gets or sets the number of pieces of the item which is defined by PriceItemType, which is required to buy the item. 0, if the price is not defined in items. /// public ushort RequiredItemAmount { @@ -12001,6 +12001,11 @@ public enum ResultKind /// The requested player has item block active. /// ItemBlock = 9, + + /// + /// The player has not enough of the currency (jewels or account coins) in which the price of the item is defined. + /// + LackOfPriceCurrency = 10, } private readonly Memory _data; @@ -12267,6 +12272,131 @@ public byte ItemCount } +/// +/// Is sent by the server when: After entering the game world, and before the player shop of another player is shown, when the server allows prices in player shops which are not paid in zen. +/// Causes reaction on client side: The client offers the allowed price types, in addition to zen, when the player sets an item price in his player shop. Without this message, only zen prices are possible. +/// +public readonly struct PlayerShopPriceTypesExtended +{ + private readonly Memory _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public PlayerShopPriceTypesExtended(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 PlayerShopPriceTypesExtended(Memory data, bool initialize) + { + this._data = data; + if (initialize) + { + var header = this.Header; + header.Type = HeaderType; + header.Code = Code; + header.Length = (byte)data.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 => 0x3F; + + /// + /// Gets the operation sub-code of this data packet. + /// The is used as a grouping key. + /// + public static byte SubCode => 0x0A; + + /// + /// Gets the header of this packet. + /// + public C1HeaderWithSubCode Header => new (this._data); + + /// + /// Gets or sets the count. + /// + public byte Count + { + get => this._data.Span[4]; + set => this._data.Span[4] = value; + } + + /// + /// Gets the of the specified index. + /// + public PlayerShopPriceType this[int index] => new (this._data.Slice(5 + index * PlayerShopPriceType.Length)); + + /// + /// Performs an implicit conversion from a Memory of bytes to a . + /// + /// The packet as span. + /// The packet as struct. + public static implicit operator PlayerShopPriceTypesExtended(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(PlayerShopPriceTypesExtended 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 priceTypesCount) => priceTypesCount * PlayerShopPriceType.Length + 5; + + +/// +/// A price type which is allowed for prices in player shops, in addition to zen.. +/// +public readonly struct PlayerShopPriceType +{ + private readonly Memory _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public PlayerShopPriceType(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 => 2; + + /// + /// Gets or sets the value of PriceItemType of a price of this type, like in PlayerShopItemExtended: the item (group in the highest 4 bits, number in the remaining ones) with which the price is paid, or 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P), 0xFFF2 = Goblin Points. + /// + public ushort PriceItemType + { + get => ReadUInt16LittleEndian(this._data.Span); + set => WriteUInt16LittleEndian(this._data.Span, value); + } +} +} + + /// /// Is sent by the server when: After the player gets into scope of a player with an opened shop. /// Causes reaction on client side: The player shop title is shown at the specified players. diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml index 8378cc7cba..f3396b3919 100644 --- a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml +++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml @@ -49,17 +49,19 @@ 0 IntegerLittleEndian MoneyPrice + The price in zen, or in account coins when PriceItemType is one of the account coin values (see there). It's 0 when the price is defined in items. 4 ShortLittleEndian PriceItemType - Contains the item group in the highest 4 bits, and the item number in the remaining ones. + When RequiredItemAmount is greater than 0, it contains the item group in the highest 4 bits, and the item number in the remaining ones, of the item (e.g. a jewel) which is required to buy the item. When RequiredItemAmount is 0, the following reserved values define an account coin as currency, with the amount in MoneyPrice: 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P), 0xFFF2 = Goblin Points. Any other value means that MoneyPrice is the price in zen. 6 ShortLittleEndian RequiredItemAmount + The number of pieces of the item which is defined by PriceItemType, which is required to buy the item. 0, if the price is not defined in items. 8 @@ -4481,6 +4483,11 @@ The requested player has item block active. 9 + + LackOfPriceCurrency + The player has not enough of the currency (jewels or account coins) in which the price of the item is defined. + 10 + @@ -4555,6 +4562,44 @@ + + C1HeaderWithSubCode + 3F + 0A + PlayerShopPriceTypesExtended + ServerToClient + After entering the game world, and before the player shop of another player is shown, when the server allows prices in player shops which are not paid in zen. + The client offers the allowed price types, in addition to zen, when the player sets an item price in his player shop. Without this message, only zen prices are possible. + + + 4 + Byte + Count + + + 5 + Structure[] + PlayerShopPriceType + PriceTypes + Count + + + + + PlayerShopPriceType + A price type which is allowed for prices in player shops, in addition to zen. + 2 + + + 0 + ShortLittleEndian + PriceItemType + The value of PriceItemType of a price of this type, like in PlayerShopItemExtended: the item (group in the highest 4 bits, number in the remaining ones) with which the price is paid, or 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P), 0xFFF2 = Goblin Points. + + + + + C2HeaderWithSubCode 3F diff --git a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs index c1af132e94..4f1268e957 100644 --- a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs +++ b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs @@ -123,7 +123,7 @@ public PlayerShopItemExtendedRef(Span data) } /// - /// Gets or sets the money price. + /// Gets or sets the price in zen, or in account coins when PriceItemType is one of the account coin values (see there). It's 0 when the price is defined in items. /// public uint MoneyPrice { @@ -132,7 +132,7 @@ public uint MoneyPrice } /// - /// Gets or sets contains the item group in the highest 4 bits, and the item number in the remaining ones. + /// Gets or sets when RequiredItemAmount is greater than 0, it contains the item group in the highest 4 bits, and the item number in the remaining ones, of the item (e.g. a jewel) which is required to buy the item. When RequiredItemAmount is 0, the following reserved values define an account coin as currency, with the amount in MoneyPrice: 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P), 0xFFF2 = Goblin Points. Any other value means that MoneyPrice is the price in zen. /// public ushort PriceItemType { @@ -141,7 +141,7 @@ public ushort PriceItemType } /// - /// Gets or sets the required item amount. + /// Gets or sets the number of pieces of the item which is defined by PriceItemType, which is required to buy the item. 0, if the price is not defined in items. /// public ushort RequiredItemAmount { @@ -11488,6 +11488,131 @@ public byte ItemCount } +/// +/// Is sent by the server when: After entering the game world, and before the player shop of another player is shown, when the server allows prices in player shops which are not paid in zen. +/// Causes reaction on client side: The client offers the allowed price types, in addition to zen, when the player sets an item price in his player shop. Without this message, only zen prices are possible. +/// +public readonly ref struct PlayerShopPriceTypesExtendedRef +{ + private readonly Span _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public PlayerShopPriceTypesExtendedRef(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 PlayerShopPriceTypesExtendedRef(Span data, bool initialize) + { + this._data = data; + if (initialize) + { + var header = this.Header; + header.Type = HeaderType; + header.Code = Code; + header.Length = (byte)data.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 => 0x3F; + + /// + /// Gets the operation sub-code of this data packet. + /// The is used as a grouping key. + /// + public static byte SubCode => 0x0A; + + /// + /// Gets the header of this packet. + /// + public C1HeaderWithSubCodeRef Header => new (this._data); + + /// + /// Gets or sets the count. + /// + public byte Count + { + get => this._data[4]; + set => this._data[4] = value; + } + + /// + /// Gets the of the specified index. + /// + public PlayerShopPriceTypeRef this[int index] => new (this._data[(5 + index * PlayerShopPriceTypeRef.Length)..]); + + /// + /// Performs an implicit conversion from a Span of bytes to a . + /// + /// The packet as span. + /// The packet as struct. + public static implicit operator PlayerShopPriceTypesExtendedRef(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(PlayerShopPriceTypesExtendedRef 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 priceTypesCount) => priceTypesCount * PlayerShopPriceTypeRef.Length + 5; + + +/// +/// A price type which is allowed for prices in player shops, in addition to zen.. +/// +public readonly ref struct PlayerShopPriceTypeRef +{ + private readonly Span _data; + + /// + /// Initializes a new instance of the struct. + /// + /// The underlying data. + public PlayerShopPriceTypeRef(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 => 2; + + /// + /// Gets or sets the value of PriceItemType of a price of this type, like in PlayerShopItemExtended: the item (group in the highest 4 bits, number in the remaining ones) with which the price is paid, or 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P), 0xFFF2 = Goblin Points. + /// + public ushort PriceItemType + { + get => ReadUInt16LittleEndian(this._data); + set => WriteUInt16LittleEndian(this._data, value); + } +} +} + + /// /// Is sent by the server when: After the player gets into scope of a player with an opened shop. /// Causes reaction on client side: The player shop title is shown at the specified players. diff --git a/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs b/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs index 96b5119c73..c2b1c4652f 100644 --- a/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs +++ b/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs @@ -376,6 +376,36 @@ public void PlayerShopCloseOther_PacketSizeValidation() "Field 'PlayerName' exceeds packet boundary"); } + /// + /// Tests the packet size calculation for PlayerShopSetItemPriceExtended. + /// + [Test] + public void PlayerShopSetItemPriceExtended_PacketSizeValidation() + { + // Fixed-length packet validation + const int expectedLength = 13; + var actualLength = PlayerShopSetItemPriceExtendedRef.Length; + + Assert.That(actualLength, Is.EqualTo(expectedLength), + "Packet length mismatch: declared length does not match calculated size"); + + // Validate field 'MoneyPrice' boundary + Assert.That(4 + 4, Is.LessThanOrEqualTo(expectedLength), + "Field 'MoneyPrice' exceeds packet boundary"); + + // Validate field 'PriceItemType' boundary + Assert.That(8 + 2, Is.LessThanOrEqualTo(expectedLength), + "Field 'PriceItemType' exceeds packet boundary"); + + // Validate field 'RequiredItemAmount' boundary + Assert.That(10 + 2, Is.LessThanOrEqualTo(expectedLength), + "Field 'RequiredItemAmount' exceeds packet boundary"); + + // Validate field 'ItemSlot' boundary + Assert.That(12 + 1, Is.LessThanOrEqualTo(expectedLength), + "Field 'ItemSlot' exceeds packet boundary"); + } + /// /// Tests the packet size calculation for PickupItemRequest. /// diff --git a/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs b/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs index 045072327e..c3d0744ad2 100644 --- a/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs +++ b/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs @@ -2424,6 +2424,20 @@ public void PlayerShopItemListExtended_PacketSizeValidation() "Field 'ItemCount' has invalid negative index"); } + /// + /// Tests the packet size calculation for PlayerShopPriceTypesExtended. + /// + [Test] + public void PlayerShopPriceTypesExtended_PacketSizeValidation() + { + // Basic packet validation + // Validate header type and field boundaries + + // Field 'Count' starts at index 4 with size 1 + Assert.That(4, Is.GreaterThanOrEqualTo(0), + "Field 'Count' has invalid negative index"); + } + /// /// Tests the packet size calculation for PlayerShops. ///