144 lines
3.2 KiB
Go
144 lines
3.2 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/Tnze/go-mc/data/packetid"
|
|
"github.com/Tnze/go-mc/nbt"
|
|
"github.com/Tnze/go-mc/net"
|
|
pk "github.com/Tnze/go-mc/net/packet"
|
|
)
|
|
|
|
type GamePlay interface {
|
|
// AcceptPlayer handle everything after "LoginSuccess" is sent.
|
|
//
|
|
// Note: the connection will be closed after this function returned.
|
|
// You don't need to close the connection, but to keep not returning while the player is playing.
|
|
AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn)
|
|
}
|
|
|
|
type Game struct {
|
|
Dim Level
|
|
components []Component
|
|
handlers map[int32][]*PacketHandler
|
|
|
|
eid int32
|
|
}
|
|
|
|
type Component interface {
|
|
Init(g *Game)
|
|
Run(ctx context.Context)
|
|
AddPlayer(p *Player)
|
|
RemovePlayer(p *Player)
|
|
}
|
|
|
|
type PacketHandler struct {
|
|
ID int32
|
|
F packetHandlerFunc
|
|
}
|
|
|
|
type packetHandlerFunc func(player *Player, packet Packet757) error
|
|
|
|
//go:embed DimensionCodec.snbt
|
|
var dimensionCodecSNBT string
|
|
|
|
//go:embed Dimension.snbt
|
|
var dimensionSNBT string
|
|
|
|
func NewGame(dim Level, components ...Component) *Game {
|
|
g := &Game{
|
|
Dim: dim,
|
|
components: components,
|
|
handlers: make(map[int32][]*PacketHandler),
|
|
}
|
|
for _, v := range components {
|
|
v.Init(g)
|
|
}
|
|
return g
|
|
}
|
|
|
|
func (g *Game) AddHandler(ph *PacketHandler) {
|
|
g.handlers[ph.ID] = append(g.handlers[ph.ID], ph)
|
|
}
|
|
|
|
func (g *Game) Run(ctx context.Context) {
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(g.components))
|
|
for _, c := range g.components {
|
|
go func(c Component) {
|
|
defer wg.Done()
|
|
c.Run(ctx)
|
|
}(c)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) {
|
|
p := &Player{
|
|
Conn: conn,
|
|
Name: name,
|
|
UUID: id,
|
|
EntityID: g.newEID(),
|
|
Gamemode: 1,
|
|
errChan: make(chan error, 1),
|
|
}
|
|
dimInfo := g.Dim.Info()
|
|
err := p.WritePacket(Packet757(pk.Marshal(
|
|
packetid.ClientboundLogin,
|
|
pk.Int(p.EntityID), // Entity ID
|
|
pk.Boolean(false), // Is hardcore
|
|
pk.Byte(p.Gamemode), // Gamemode
|
|
pk.Byte(-1), // Prev Gamemode
|
|
pk.Array([]pk.Identifier{pk.Identifier(dimInfo.Name)}),
|
|
pk.NBT(nbt.StringifiedMessage(dimensionCodecSNBT)),
|
|
pk.NBT(nbt.StringifiedMessage(dimensionSNBT)),
|
|
pk.Identifier(dimInfo.Name), // World Name
|
|
pk.Long(dimInfo.HashedSeed), // Hashed seed
|
|
pk.VarInt(0), // Max Players (Ignored by client)
|
|
pk.VarInt(15), // View Distance
|
|
pk.VarInt(15), // Simulation Distance
|
|
pk.Boolean(false), // Reduced Debug Info
|
|
pk.Boolean(true), // Enable respawn screen
|
|
pk.Boolean(false), // Is Debug
|
|
pk.Boolean(true), // Is Flat
|
|
)))
|
|
if err != nil {
|
|
return
|
|
}
|
|
g.Dim.PlayerJoin(p)
|
|
defer g.Dim.PlayerQuit(p)
|
|
|
|
for _, c := range g.components {
|
|
c.AddPlayer(p)
|
|
if err := p.GetErr(); err != nil {
|
|
return
|
|
}
|
|
//goland:noinspection GoDeferInLoop
|
|
defer c.RemovePlayer(p)
|
|
}
|
|
|
|
var packet pk.Packet
|
|
for {
|
|
if err := p.ReadPacket(&packet); err != nil {
|
|
return
|
|
}
|
|
for _, ph := range g.handlers[packet.ID] {
|
|
if err := ph.F(p, Packet757(packet)); err != nil {
|
|
return
|
|
}
|
|
if err := p.GetErr(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *Game) newEID() int32 {
|
|
return atomic.AddInt32(&g.eid, 1)
|
|
}
|