This example demonstrates how to create a Minecraft client that connects to a server, spawns in the world, and handles incoming and outgoing packets.
Complete Example
The following code shows a complete client implementation:
package main
import (
"fmt"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/auth"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
func main() {
// Create a minecraft.Dialer with an auth.TokenSource to authenticate to the server.
dialer := minecraft.Dialer{
TokenSource: auth.TokenSource,
}
// Dial a new connection to the target server.
address := "mco.mineplex.com:19132"
conn, err := dialer.Dial("raknet", address)
if err != nil {
panic(err)
}
defer conn.Close()
// Make the client spawn in the world: This is a blocking operation that will return an error if the
// client times out while spawning.
if err := conn.DoSpawn(); err != nil {
panic(err)
}
// You will then want to start a for loop that reads packets from the connection until it is closed.
for {
// Read a packet from the connection: ReadPacket returns an error if the connection is closed or if
// a read timeout is set. You will generally want to return or break if this happens.
pk, err := conn.ReadPacket()
if err != nil {
break
}
// The pk variable is of type packet.Packet, which may be type asserted to gain access to the data
// they hold:
switch p := pk.(type) {
case *packet.Emote:
fmt.Printf("Emote packet received: %v\n", p.EmoteID)
case *packet.MovePlayer:
fmt.Printf("Player %v moved to %v\n", p.EntityRuntimeID, p.Position)
}
// Write a packet to the connection: Similarly to ReadPacket, WritePacket will (only) return an error
// if the connection is closed.
p := &packet.RequestChunkRadius{ChunkRadius: 32, MaxChunkRadius: 32}
if err := conn.WritePacket(p); err != nil {
break
}
}
}
Key Components
1. Creating a Dialer
The minecraft.Dialer is used to establish connections to servers:
dialer := minecraft.Dialer{
TokenSource: auth.TokenSource,
}
The auth.TokenSource handles Microsoft authentication automatically. It will prompt for login credentials when needed.
2. Connecting to a Server
Use the Dial method to connect to a server address:
conn, err := dialer.Dial("raknet", address)
if err != nil {
panic(err)
}
defer conn.Close()
The first parameter "raknet" specifies the network protocol (RakNet is the protocol used by Minecraft Bedrock).
3. Spawning in the World
Before you can interact with the game world, the client must spawn:
if err := conn.DoSpawn(); err != nil {
panic(err)
}
DoSpawn() is a blocking operation that handles the entire spawn sequence. It returns an error if the connection times out during spawning.
4. Reading Packets
Packets are read in a continuous loop:
for {
pk, err := conn.ReadPacket()
if err != nil {
break
}
// Handle packet...
}
ReadPacket() returns when:
- A packet is received
- The connection is closed
- A read timeout occurs (if configured)
5. Handling Packet Types
Use type assertions to access packet-specific data:
switch p := pk.(type) {
case *packet.Emote:
fmt.Printf("Emote packet received: %v\n", p.EmoteID)
case *packet.MovePlayer:
fmt.Printf("Player %v moved to %v\n", p.EntityRuntimeID, p.Position)
}
All packet types are defined in the github.com/sandertv/gophertunnel/minecraft/protocol/packet package.
6. Writing Packets
Send packets to the server using WritePacket():
p := &packet.RequestChunkRadius{ChunkRadius: 32, MaxChunkRadius: 32}
if err := conn.WritePacket(p); err != nil {
break
}
Next Steps