Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Sandertv/gophertunnel/llms.txt
Use this file to discover all available pages before exploring further.
This guide shows you how to create a Minecraft server that accepts client connections using minecraft.Listen().
Overview
A Minecraft server listens for incoming connections, accepts clients, spawns them in a world, and handles packet communication. Servers can be customized with status providers, resource packs, and custom game data.
Basic Server Example
Import Required Packages
import (
"fmt"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
Configure the Listener
Create a minecraft.ListenConfig with a status provider:name := "MOTD of this server"
cfg := minecraft.ListenConfig{
StatusProvider: minecraft.NewStatusProvider(name, "Gophertunnel"),
}
The status provider defines what players see in the server list. Start Listening
Listen on a specific address and port:address := ":19132"
listener, err := cfg.Listen("raknet", address)
if err != nil {
panic(err)
}
defer listener.Close()
Accept Connections
Accept incoming connections in a loop:for {
c, err := listener.Accept()
if err != nil {
return
}
conn := c.(*minecraft.Conn)
// Handle each connection in a goroutine
go handleConnection(conn)
}
Handle Client Connection
For each client, start the game and process packets:func handleConnection(conn *minecraft.Conn) {
defer conn.Close()
// Make the client spawn in the world
worldData := minecraft.GameData{}
if err := conn.StartGame(worldData); err != nil {
return
}
// Read and handle packets
for {
pk, err := conn.ReadPacket()
if err != nil {
break
}
// Process packets...
switch p := pk.(type) {
case *packet.Emote:
fmt.Printf("Emote packet: %v\n", p.EmoteID)
}
}
}
Complete Example
Here’s a complete working server:
package main
import (
"fmt"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
func main() {
// Create a minecraft.Listener with a specific name to be displayed as MOTD in the server list.
name := "MOTD of this server"
cfg := minecraft.ListenConfig{
StatusProvider: minecraft.NewStatusProvider(name, "Gophertunnel"),
}
// Listen on the address with port 19132.
address := ":19132"
listener, err := cfg.Listen("raknet", address)
if err != nil {
panic(err)
}
for {
// Accept connections in a for loop. Accept will only return an error if the minecraft.Listener is
// closed. (So never unexpectedly.)
c, err := listener.Accept()
if err != nil {
return
}
conn := c.(*minecraft.Conn)
go func() {
// Process the connection on another goroutine as you would with TCP connections.
defer conn.Close()
// Make the client spawn in the world using conn.StartGame. An error is returned if the client
// times out during the connection.
worldData := minecraft.GameData{}
if err := conn.StartGame(worldData); err != nil {
return
}
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.ChunkRadiusUpdated{ChunkRadius: 32}
if err := conn.WritePacket(p); err != nil {
break
}
}
}()
}
}
Configuration Options
Status Provider
The status provider controls what appears in the server list:
cfg := minecraft.ListenConfig{
StatusProvider: minecraft.NewStatusProvider("My Server", "Gophertunnel"),
}
Foreign Status Provider
Proxy the status from another server:
p, err := minecraft.NewForeignStatusProvider("another-server.com:19132")
if err != nil {
panic(err)
}
cfg := minecraft.ListenConfig{
StatusProvider: p,
}
Authentication
Enable Xbox Live authentication:
cfg := minecraft.ListenConfig{
StatusProvider: minecraft.NewStatusProvider("My Server", "Gophertunnel"),
AuthenticationDisabled: false, // Require authentication (default)
}
Resource Packs
Add resource packs to your server:
import "github.com/sandertv/gophertunnel/minecraft/resource"
pack, err := resource.ReadPath("path/to/pack.mcpack")
if err != nil {
panic(err)
}
cfg := minecraft.ListenConfig{
StatusProvider: minecraft.NewStatusProvider("My Server", "Gophertunnel"),
ResourcePacks: []*resource.Pack{pack},
}
See the resource packs guide for more details.
Game Data
The minecraft.GameData struct defines the world the player spawns into:
worldData := minecraft.GameData{
WorldName: "My World",
WorldSpawn: protocol.BlockPos{X: 0, Y: 64, Z: 0},
PlayerPosition: mgl32.Vec3{0, 65, 0},
Difficulty: 1,
GameMode: 1, // Creative
Time: 6000,
PlayerGameMode: 1,
}
if err := conn.StartGame(worldData); err != nil {
return
}
Managing Connections
Disconnecting Players
err := listener.Disconnect(conn, "You have been kicked")
if err != nil {
fmt.Printf("Failed to disconnect: %v\n", err)
}
Graceful Shutdown
import "context"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// In another goroutine, close the listener on signal
go func() {
<-ctx.Done()
listener.Close()
}()
for {
c, err := listener.Accept()
if err != nil {
// Listener was closed
return
}
// Handle connection...
}
Error Handling
Accept Errors
c, err := listener.Accept()
if err != nil {
// Listener was closed
fmt.Printf("Accept error: %v\n", err)
return
}
StartGame Errors
if err := conn.StartGame(worldData); err != nil {
// Client timed out or rejected
fmt.Printf("StartGame error: %v\n", err)
return
}
Best Practices
- Use Goroutines: Handle each connection in a separate goroutine to support multiple clients
- Close Connections: Always defer
conn.Close() to clean up resources
- Handle Errors: Check errors from
ReadPacket() and WritePacket() to detect disconnections
- Validate Packets: Validate packet data before processing to prevent crashes
Next Steps