RegionIO/internal/network/handler.go
Master290 a7bb9496ae Initial commit: RegionIO Minecraft server core (26.1.2/protocol 775)
Vanilla-faithful overworld generator (final_density + multi-noise biomes),
full connection lifecycle (status/login/configuration/play), chunk streaming,
creative block editing, and the protocol/nbt/registry infrastructure.
2026-06-24 00:32:51 +03:00

133 lines
3.2 KiB
Go

package network
import (
"errors"
"io"
"log/slog"
"net"
"regionio/internal/protocol"
"regionio/internal/server"
)
// handler drives one connection through its state machine until it closes.
// It is owned by a single goroutine (the read loop), so the play fields below
// need no synchronization.
type handler struct {
conn *Conn
srv *server.Server
log *slog.Logger
// Play-phase chunk streaming state.
loaded map[[2]int32]bool // chunks currently sent to the client
centerX int32
centerZ int32
hasCenter bool
// Creative inventory state for block placement.
heldSlot int32 // selected hotbar index (0-8)
hotbar [9]int32 // item network IDs per hotbar slot (-1 = empty)
}
// serve runs the read/dispatch loop for a single connection.
func (h *handler) serve() {
defer h.conn.Close()
for {
pkt, err := h.conn.ReadPacket()
if err != nil {
if !errors.Is(err, io.EOF) && !errors.Is(err, net.ErrClosed) {
h.log.Debug("connection closed", "err", err)
}
return
}
if err := h.dispatch(pkt); err != nil {
h.log.Debug("dispatch error", "state", h.conn.State(), "id", pkt.ID, "err", err)
return
}
}
}
// dispatch routes a packet to the handler for the current state.
func (h *handler) dispatch(pkt protocol.Packet) error {
switch h.conn.State() {
case protocol.StateHandshaking:
return h.handleHandshake(pkt)
case protocol.StateStatus:
return h.handleStatus(pkt)
case protocol.StateLogin:
return h.handleLogin(pkt)
case protocol.StateConfiguration:
return h.handleConfiguration(pkt)
case protocol.StatePlay:
return h.handlePlay(pkt)
default:
return errors.New("no handler for state " + h.conn.State().String())
}
}
// handleHandshake reads the single handshake packet and transitions state.
func (h *handler) handleHandshake(pkt protocol.Packet) error {
if pkt.ID != protocol.HandshakeID {
return errors.New("unexpected packet in handshaking state")
}
r := pkt.Body()
protoVer, err := r.VarInt()
if err != nil {
return err
}
addr, err := r.String()
if err != nil {
return err
}
port, err := r.Uint16()
if err != nil {
return err
}
next, err := r.VarInt()
if err != nil {
return err
}
h.log.Debug("handshake",
"protocol", protoVer, "addr", addr, "port", port, "next", next)
switch next {
case protocol.NextStateStatus:
h.conn.SetState(protocol.StateStatus)
case protocol.NextStateLogin, protocol.NextStateTransfer:
h.conn.SetState(protocol.StateLogin)
default:
return errors.New("invalid next state in handshake")
}
return nil
}
// handleStatus answers the server-list ping: status request and ping/pong.
func (h *handler) handleStatus(pkt protocol.Packet) error {
switch pkt.ID {
case protocol.StatusRequestID:
jsonBytes, err := h.srv.StatusJSON(0)
if err != nil {
return err
}
w := protocol.NewWriter(len(jsonBytes) + 4)
w.String(string(jsonBytes))
return h.conn.SendWriter(protocol.StatusResponseID, w)
case protocol.PingRequestID:
// Echo the client's payload back verbatim for latency measurement.
payload, err := pkt.Body().Int64()
if err != nil {
return err
}
w := protocol.NewWriter(8)
w.Int64(payload)
return h.conn.SendWriter(protocol.PongResponseID, w)
default:
return errors.New("unexpected packet in status state")
}
}