Skip to main content
This guide shows you how to create a Minecraft client that connects to servers using minecraft.Dial().

Overview

A Minecraft client connects to remote servers, spawns in the world, and can read/write packets to interact with the game. The client uses Microsoft authentication to connect to servers.

Basic Client Example

1

Import Required Packages

import (
    "fmt"
    "github.com/sandertv/gophertunnel/minecraft"
    "github.com/sandertv/gophertunnel/minecraft/auth"
    "github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
2

Create a Dialer with Authentication

The minecraft.Dialer requires a TokenSource for authentication:
dialer := minecraft.Dialer{
    TokenSource: auth.TokenSource,
}
The auth.TokenSource handles Microsoft account authentication automatically.
3

Connect to a Server

Use the dialer to connect to a server address:
address := "mco.mineplex.com:19132"
conn, err := dialer.Dial("raknet", address)
if err != nil {
    panic(err)
}
defer conn.Close()
4

Spawn in the World

After connecting, spawn the client in the world:
if err := conn.DoSpawn(); err != nil {
    panic(err)
}
This is a blocking operation that waits for the spawn sequence to complete.
5

Handle Packets

Read and write packets in a loop:
for {
    // Read incoming packets
    pk, err := conn.ReadPacket()
    if err != nil {
        break
    }

    // Handle specific packet types
    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 packets to the server
    p := &packet.RequestChunkRadius{ChunkRadius: 32, MaxChunkRadius: 32}
    if err := conn.WritePacket(p); err != nil {
        break
    }
}

Complete Example

Here’s a complete working client:
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
        }
    }
}

Authentication

Using Token Source

The default auth.TokenSource handles authentication automatically:
dialer := minecraft.Dialer{
    TokenSource: auth.TokenSource,
}

Using Custom Tokens

You can also use custom OAuth2 tokens:
token, err := auth.RequestLiveToken()
if err != nil {
    panic(err)
}

dialer := minecraft.Dialer{
    TokenSource: auth.RefreshTokenSource(token),
}

Connection Options

The minecraft.Dialer supports several configuration options:
dialer := minecraft.Dialer{
    // Required: Token source for authentication
    TokenSource: auth.TokenSource,
    
    // Optional: Custom client data
    ClientData: minecraft.ClientData{
        DeviceID:    "custom-device-id",
        DeviceModel: "Custom Device",
        GameVersion: "1.21.0",
    },
}

Error Handling

Connection Errors

conn, err := dialer.Dial("raknet", address)
if err != nil {
    // Handle connection failure
    fmt.Printf("Failed to connect: %v\n", err)
    return
}

Spawn Errors

if err := conn.DoSpawn(); err != nil {
    // Handle spawn timeout or rejection
    fmt.Printf("Failed to spawn: %v\n", err)
    return
}

Disconnection

import "github.com/sandertv/gophertunnel/minecraft"

for {
    pk, err := conn.ReadPacket()
    if err != nil {
        var disc minecraft.DisconnectError
        if errors.As(err, &disc) {
            fmt.Printf("Disconnected: %v\n", disc.Error())
        }
        break
    }
    // Process packet...
}

Next Steps