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 eid int32 } type Component interface { Run(ctx context.Context) AddPlayer(p *Player) RemovePlayer(p *Player) } //go:embed DimensionCodec.snbt var dimensionCodecSNBT string //go:embed Dimension.snbt var dimensionSNBT string func NewGame(dim Level, components ...Component) *Game { return &Game{ Dim: dim, components: components, } } 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, handlers: make(map[int32][]packetHandlerFunc), 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 { err := p.ReadPacket(&packet) if err != nil { return } for _, ph := range p.handlers[packet.ID] { err = ph(Packet757(packet)) if err != nil { return } } } } func (g *Game) newEID() int32 { return atomic.AddInt32(&g.eid, 1) }