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.
This commit is contained in:
commit
a7bb9496ae
146 changed files with 217621 additions and 0 deletions
133
internal/network/handler.go
Normal file
133
internal/network/handler.go
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
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")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue