Skip to content

Networking

Most of the Networking code in Pumpkin, can be found at Pumpkin-Protocol

Serverbound: Client->Server

Clientbound: Server->Client

Structure

Packets in the Pumpkin protocol are organized by functionality and state.

server: Contains definitions for serverbound packets.

client: Contains definitions for clientbound packets.

States

Handshake: Always the first packet being send from the Client. This begins also determins the next state, usally to indicate if the player thans perform a Status Request, Join the Server or wants to be transfered.

Status: Indicates the Client wants to see a Status response (MOTD).

Login: The Login sequence. Indicates the Client wants to join to the Server

Config: A sequence of Configuration packets beining mostly send from the Server to the Client. (Features, Resource Pack, Server Links...)

Play: The final state which indicate the Player is now ready to Join in also used to handle all other Gameplay packets.

Minecraft Protocol

You can find all Minecraft Java packets at https://wiki.vg/Protocol. There you also can see in which State they are. You also can see all the information the Packets has which we can either Write or Read depending if its Serverbound or Clientbound

Adding a Clientbound Packet

  1. Adding a Packet is easy. First you have to dereive serde Serialize for packets.
rust
#[derive(Serialize)]
  1. Next you have set the packet id using the packet macro
rust
#[packet(0x1D)]
  1. Now you can create the Struct.

IMPORTANT

Please start the Packet name with "C" for Clientbound. Also please add the State to the packet if its a Packet sended in multiple States, For example there are 3 Disconnect Packets.

  • CLoginDisconnect
  • CConfigDisconnect
  • CPlayDisconnect

Create fields within your packet structure to represent the data that will be sent to the client.

IMPORTANT

Use descriptive field names and appropriate data types.

Example:

rust
pub struct CPlayDisconnect {
    reason: TextComponent,
    more fields...
}
  1. Also don't forgot to impl a new function for Clientbound Packets so we can actaully send then by putting in the values

Example:

rust
impl CPlayDisconnect {
    pub fn new(reason: TextComponent) -> Self {
        Self { reason }
    }
}
  1. At the End everything should come together,
rust
#[derive(Serialize)]
#[packet(0x1D)]
pub struct CPlayDisconnect {
    reason: TextComponent,
}

impl CPlayDisconnect {
    pub fn new(reason: TextComponent) -> Self {
        Self { reason }
    }
}
  1. You can also Serialize the Packet manually, Which can be usefull if the Packet is more complex
diff
-#[derive(Serialize)]

+ impl ClientPacket for CPlayDisconnect {
+    fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) {
+       bytebuf.put_slice(&self.reason.encode());
+    }
  1. You can now send the Packet. See Sending Packets

Adding a Serverbound Packet

  1. Adding a Packet is easy. First you have to dereive serde Deserialize for packets.
rust
#[derive(Deserialize)]
  1. Next you have set the packet id using the packet macro
rust
#[packet(0x1A)]
  1. Now you can create the Struct.

IMPORTANT

Please start the Packet name with "S" for Serverbound. Also please add the State to the packet if its a Packet sended in multiple States.

Create fields within your packet structure to represent the data that will be sent to the client.

IMPORTANT

Use descriptive field names and appropriate data types.

Example:

rust
pub struct SPlayerPosition {
    pub x: f64,
    pub feet_y: f64,
    pub z: f64,
    pub ground: bool,
}
  1. At the End everything should come together,
rust
#[derive(Deserialize)]
#[packet(0x1A)]
pub struct SPlayerPosition {
    pub x: f64,
    pub feet_y: f64,
    pub z: f64,
    pub ground: bool,
}
  1. You can also Deserialize the Packet manually, Which can be usefull if the Packet is more complex
diff
-#[derive(Deserialize)]

+ impl ServerPacket for SPlayerPosition {
+    fn read(bytebuf: &mut ByteBuffer) -> Result<Self, DeserializerError> {
+       Ok(Self {
+           x: bytebuf.get_f64()?,
+           feet_y: bytebuf.get_f64()?,
+           z: bytebuf.get_f64()?,
+           ground: bytebuf.get_bool()?,
+       })
+    }
  1. You can listen for the Packet. See Receive Packets

Client

Pumpkin has stores Client and Players seperatly, Everything what is not reached the Play State is a Simple Client. Here are the Differences

Client

  • Can only be in Status/Login/Transfer/Config State
  • Is not a living entity
  • Has small resource consumption

Player

  • Can only be in Play State
  • Is a living entity in a world
  • Has more data, Consumes more resources

Sending Packets

Example:

rust
// Works only in Status State
client.send_packet(&CStatusResponse::new("{ description: "A Description"}"));

Receiving Packets

For Clients: src/client/mod.rs

diff
// Put the Packet into the right State
 fn handle_mystate_packet(
  &self,
    server: &Arc<Server>,
    packet: &mut RawPacket,
) -> Result<(), DeserializerError> {
    let bytebuf = &mut packet.bytebuf;
    match packet.id.0 {
        SHandShake::PACKET_ID => {
            self.handle_handshake(server, SHandShake::read(bytebuf)?);
            Ok(())
        }
+       MyPacket::PACKET_ID => {
+           self.handle_mypacket(server, MyPacket::read(bytebuf)?);
+           Ok(())
+       }
        _ => {
            log::error!(
                "Failed to handle packet id {} while in ... state",
                packet.id.0
            );
            Ok(())
        }
    }
}

For Players: src/entity/player.rs

diff
// Players only have Play State
 fn handle_play_packet(
  &self,
    server: &Arc<Server>,
    packet: &mut RawPacket,
) -> Result<(), DeserializerError> {
    let bytebuf = &mut packet.bytebuf;
    match packet.id.0 {
        SHandShake::PACKET_ID => {
            self.handle_handshake(server, SHandShake::read(bytebuf)?);
            Ok(())
        }
+       MyPacket::PACKET_ID => {
+           self.handle_mypacket(server, MyPacket::read(bytebuf)?);
+           Ok(())
+       }
        _ => {
            log::error!(
                "Failed to handle packet id {} while in ... state",
                packet.id.0
            );
            Ok(())
        }
    }
}

Compression

Minecraft Packets can use the ZLib compression for decoding/encoding there is usally a threshold set when compression is applied, This most often affects Chunk Packets.

Porting

To port to a new Minecraft version, You can compare difference in Protocol on wiki.vg https://wiki.vg/index.php?title=Protocol&action=history Also change the CURRENT_MC_PROTOCOL in src/lib.rs

Released under the MIT License.