Skip to content

Packets (Messages)

Minecraft Packets (called Messages in Void) describe the data that is sent between the player and server. These messages contain information about player actions, world events, and other game mechanics.

Typically, you describe existing packets in game, so you can read, modify, or cancel them.
However you are not limited to this, you can also define your own packets for your own minecraft mod or plugin.

Packets can be defined with IMinecraftClientboundPacket<TPacket> or IMinecraftServerboundPacket<TPacket> interface. If your packet is same for both client and server, you can use IMinecraftPacket<TPacket> interface.

Your packet definition must specify how to Decode and Encode the packet data.
In this example, we will define a Set Held Item (clientbound) packet.

This packet contains one integer property - slot, which is the index of the item in the player hotbar.

ExamplePlugin/Packets/Clientbound/SetHeldItemClientboundPacket.cs
public class SetHeldItemClientboundPacket : IMinecraftClientboundPacket<SetHeldItemClientboundPacket>
{
public required int Slot { get; set; }
public void Encode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion)
{
buffer.WriteVarInt(Slot);
}
public static SetHeldItemClientboundPacket Decode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion)
{
int slot = buffer.ReadVarInt();
return new SetHeldItemClientboundPacket
{
Slot = slot
};
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}

Before receiving or sending packets, you need to register them specifying packet ids for each game protocol version. Packets registrations are made for each game phase, so you need to register them in the correct phase. Common phases are Handshake, Login, Configuration and Play.

[Subscribe]
public void OnPhaseChanged(PhaseChangedEvent @event)
{
if (@event.Phase is not Phase.Play)
return;
@event.Player.RegisterPacket<SetHeldItemClientboundPacket>([
new(0x4F, ProtocolVersion.MINECRAFT_1_20_2),
new(0x51, ProtocolVersion.MINECRAFT_1_20_3),
new(0x53, ProtocolVersion.MINECRAFT_1_20_5),
new(0x63, ProtocolVersion.MINECRAFT_1_21_2),
new(0x62, ProtocolVersion.MINECRAFT_1_21_5)
]);
}

Now that we have defined our packet, we can receive it with MessageReceivedEvent event.

[Subscribe]
public void OnMessageReceived(MessageReceivedEvent @event)
{
// Check if the message is a SetHeldItemClientboundPacket
if (@event.Message is not SetHeldItemClientboundPacket packet)
return;
// Print the slot value sent by server
Console.WriteLine($"Received {nameof(SetHeldItemClientboundPacket)} with Slot: {packet.Slot}");
}

There is also a MessageSentEvent event that is triggered when the packet is already sent to the receiver.

[Subscribe]
public void OnMessageSent(MessageSentEvent @event)
{
// Check if the message is a SetHeldItemClientboundPacket
if (@event.Message is not SetHeldItemClientboundPacket packet)
return;
// Print the slot value sent to the player
Console.WriteLine($"Sent {nameof(SetHeldItemClientboundPacket)} with Slot: {packet.Slot}");
}

There are 3 ways to send packets in Void:

  • Directly to the IPlayer instance
  • To the INetworkChannel of server or player
  • To the ILink connection between the server and player

You can send packets to the player with SendPacketAsync method on IPlayer instance.

await player.SendPacketAsync(new SetHeldItemClientboundPacket { Slot = slot }, cancellationToken);

You can send packets to the server with ILink.ServerChannel instance.

await player.GetLink().ServerChannel.SendPacketAsync(new SetHeldItemClientboundPacket { Slot = slot }, cancellationToken);

You can send packets to the link with ILink.SendPacketAsync method. ILink will automatically determine the destination of the packet based on the packet interface.

  • If the packet has IMinecraftClientboundPacket<TPacket> interface, it will be sent to the client.
  • If the packet has IMinecraftServerboundPacket<TPacket> interface, it will be sent to the server.
  • If the packet has both interfaces, it will be sent only to the client.
  • If the packet has neither interface, InvalidOperationException will be thrown.
await player.GetLink().SendPacketAsync(new SetHeldItemClientboundPacket { Slot = slot }, cancellationToken);

When you want to explicitly send a packet to the server or client, SendPacketAsync has overload that specifies the Side of destination.

await player.GetLink().SendPacketAsync(Side.Client, new SetHeldItemClientboundPacket { Slot = slot }, cancellationToken);

Check out complete example for inventory service plugin that includes both clientbound and serverbound set held item packets.

You can cancel packets in MessageReceivedEvent event.
Set IEvent.Result value to true to prevent sending packet to receiver.

[Subscribe]
public void OnMessageReceived(MessageReceivedEvent @event)
{
if (@event.Message is not SetHeldItemClientboundPacket packet)
return;
// Cancel the SetHeldItemClientboundPacket packet
@event.Result = true;
}