Merge pull request #152 from Tnze/blocks
[Update & Bug Fixes] Update to Minecraft 1.18.2
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
# Go-MC
|
# Go-MC
|
||||||
|
|
||||||

|

|
||||||
[](https://pkg.go.dev/github.com/Tnze/go-mc)
|
[](https://pkg.go.dev/github.com/Tnze/go-mc)
|
||||||
[](https://goreportcard.com/report/github.com/Tnze/go-mc)
|
[](https://goreportcard.com/report/github.com/Tnze/go-mc)
|
||||||
[](https://travis-ci.org/Tnze/go-mc)
|
[](https://travis-ci.org/Tnze/go-mc)
|
||||||
|
@ -10,11 +10,8 @@ import (
|
|||||||
|
|
||||||
// WorldInfo content player info in server.
|
// WorldInfo content player info in server.
|
||||||
type WorldInfo struct {
|
type WorldInfo struct {
|
||||||
DimensionCodec struct {
|
DimensionCodec nbt.StringifiedMessage
|
||||||
DimensionType interface{} `nbt:"minecraft:dimension_type"`
|
Dimension nbt.StringifiedMessage
|
||||||
WorldgenBiome interface{} `nbt:"minecraft:worldgen/biome"`
|
|
||||||
}
|
|
||||||
Dimension interface{}
|
|
||||||
WorldNames []string // Identifiers for all worlds on the server.
|
WorldNames []string // Identifiers for all worlds on the server.
|
||||||
WorldName string // Name of the world being spawned into.
|
WorldName string // Name of the world being spawned into.
|
||||||
HashedSeed int64 // First 8 bytes of the SHA-256 hash of the world's seed. Used client side for biome noise
|
HashedSeed int64 // First 8 bytes of the SHA-256 hash of the world's seed. Used client side for biome noise
|
||||||
@ -49,8 +46,8 @@ func (p *Player) handleLoginPacket(packet pk.Packet) error {
|
|||||||
(*pk.Byte)(&p.PrevGamemode),
|
(*pk.Byte)(&p.PrevGamemode),
|
||||||
&WorldCount,
|
&WorldCount,
|
||||||
pk.Ary{Len: &WorldCount, Ary: &WorldNames},
|
pk.Ary{Len: &WorldCount, Ary: &WorldNames},
|
||||||
pk.NBT(new(nbt.RawMessage)),
|
pk.NBT(&p.WorldInfo.DimensionCodec),
|
||||||
pk.NBT(new(nbt.RawMessage)),
|
pk.NBT(&p.WorldInfo.Dimension),
|
||||||
(*pk.Identifier)(&p.WorldName),
|
(*pk.Identifier)(&p.WorldName),
|
||||||
(*pk.Long)(&p.HashedSeed),
|
(*pk.Long)(&p.HashedSeed),
|
||||||
(*pk.VarInt)(&p.MaxPlayers),
|
(*pk.VarInt)(&p.MaxPlayers),
|
||||||
|
28
bot/mcbot.go
28
bot/mcbot.go
@ -6,6 +6,7 @@ package bot
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ProtocolVersion is the protocol version number of minecraft net protocol
|
// ProtocolVersion is the protocol version number of minecraft net protocol
|
||||||
const ProtocolVersion = 757
|
const ProtocolVersion = 758
|
||||||
const DefaultPort = mcnet.DefaultPort
|
const DefaultPort = mcnet.DefaultPort
|
||||||
|
|
||||||
// JoinServer connect a Minecraft server for playing the game.
|
// JoinServer connect a Minecraft server for playing the game.
|
||||||
@ -27,20 +28,31 @@ func (c *Client) JoinServer(addr string) (err error) {
|
|||||||
|
|
||||||
// JoinServerWithDialer is similar to JoinServer but using a Dialer.
|
// JoinServerWithDialer is similar to JoinServer but using a Dialer.
|
||||||
func (c *Client) JoinServerWithDialer(d *net.Dialer, addr string) (err error) {
|
func (c *Client) JoinServerWithDialer(d *net.Dialer, addr string) (err error) {
|
||||||
return c.join(context.Background(), &mcnet.Dialer{Dialer: d}, addr)
|
var dialer *mcnet.Dialer
|
||||||
|
if d != nil {
|
||||||
|
dialer = &mcnet.Dialer{Dialer: *d}
|
||||||
|
}
|
||||||
|
return c.join(context.Background(), dialer, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) join(ctx context.Context, d *mcnet.Dialer, addr string) error {
|
func (c *Client) join(ctx context.Context, d *mcnet.Dialer, addr string) error {
|
||||||
const Handshake = 0x00
|
const Handshake = 0x00
|
||||||
|
|
||||||
// Split Host and Port
|
// Split Host and Port
|
||||||
host, portStr, err := net.SplitHostPort(addr)
|
host, portStr, err := net.SplitHostPort(addr)
|
||||||
|
var port uint64
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return LoginErr{"split address", err}
|
var addrErr *net.AddrError
|
||||||
}
|
const missingPort = "missing port in address"
|
||||||
port, err := strconv.ParseUint(portStr, 0, 16)
|
if errors.As(err, &addrErr) && addrErr.Err == missingPort {
|
||||||
if err != nil {
|
port = 25565
|
||||||
return LoginErr{"parse port", err}
|
} else {
|
||||||
|
return LoginErr{"split address", err}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
port, err = strconv.ParseUint(portStr, 0, 16)
|
||||||
|
if err != nil {
|
||||||
|
return LoginErr{"parse port", err}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial connection
|
// Dial connection
|
||||||
|
33857
data/block/block.go
33857
data/block/block.go
File diff suppressed because it is too large
Load Diff
@ -1,152 +0,0 @@
|
|||||||
//go:build generate
|
|
||||||
// +build generate
|
|
||||||
|
|
||||||
// gen_blocks.go generates block information.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/iancoleman/strcase"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
version = "1.17"
|
|
||||||
infoURL = "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/pc/" + version + "/blocks.json"
|
|
||||||
//language=gohtml
|
|
||||||
blockTmpl = `// Code generated by gen_block.go; DO NOT EDIT.
|
|
||||||
|
|
||||||
// Package block stores information about blocks in Minecraft.
|
|
||||||
package block
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/bits"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BitsPerBlock indicates how many bits are needed to represent all possible
|
|
||||||
// block states. This value is used to determine the size of the global palette.
|
|
||||||
var BitsPerBlock = bits.Len(uint(len(StateID)))
|
|
||||||
|
|
||||||
// ID describes the numeric ID of a block.
|
|
||||||
type ID uint32
|
|
||||||
|
|
||||||
// Block describes information about a type of block.
|
|
||||||
type Block struct {
|
|
||||||
ID ID
|
|
||||||
DisplayName string
|
|
||||||
Name string
|
|
||||||
|
|
||||||
Hardness float64
|
|
||||||
Diggable bool
|
|
||||||
DropIDs []uint32
|
|
||||||
NeedsTools map[uint32]bool
|
|
||||||
|
|
||||||
MinStateID uint32
|
|
||||||
MaxStateID uint32
|
|
||||||
|
|
||||||
Transparent bool
|
|
||||||
FilterLightLevel int
|
|
||||||
EmitLightLevel int
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
{{- range .}}
|
|
||||||
{{.CamelName}} = Block{
|
|
||||||
ID: {{.ID}},
|
|
||||||
DisplayName: "{{.DisplayName}}",
|
|
||||||
Name: "{{.Name}}",
|
|
||||||
Hardness: {{.Hardness}},
|
|
||||||
Diggable: {{.Diggable}},
|
|
||||||
DropIDs: []uint32{ {{- range .DropIDs}}{{.}},{{end -}} },
|
|
||||||
NeedsTools: map[uint32]bool{ {{- range $key, $val := .NeedsTools}}{{$key}}: {{$val}},{{end -}} },
|
|
||||||
MinStateID: {{.MinStateID}},
|
|
||||||
MaxStateID: {{.MaxStateID}},
|
|
||||||
Transparent: {{.Transparent}},
|
|
||||||
FilterLightLevel: {{.FilterLightLevel}},
|
|
||||||
EmitLightLevel: {{.EmitLightLevel}},
|
|
||||||
}{{end}}
|
|
||||||
)
|
|
||||||
|
|
||||||
// ByID is an index of minecraft blocks by their ID.
|
|
||||||
var ByID = map[ID]*Block{ {{range .}}
|
|
||||||
{{.ID}}: &{{.CamelName}},{{end}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateID maps all possible state IDs to a corresponding block ID.
|
|
||||||
var StateID = map[uint32]ID{ {{range $block := .}}
|
|
||||||
{{- range .States}}
|
|
||||||
{{.}}: {{$block.ID}},
|
|
||||||
{{- end}}{{end}}
|
|
||||||
}`
|
|
||||||
)
|
|
||||||
|
|
||||||
type Block struct {
|
|
||||||
ID uint32 `json:"id"`
|
|
||||||
CamelName string `json:"-"`
|
|
||||||
DisplayName string `json:"displayName"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
Hardness float64 `json:"hardness"`
|
|
||||||
Diggable bool `json:"diggable"`
|
|
||||||
DropIDs []uint32 `json:"drops"`
|
|
||||||
NeedsTools map[uint32]bool `json:"harvestTools"`
|
|
||||||
|
|
||||||
MinStateID uint32 `json:"minStateId"`
|
|
||||||
MaxStateID uint32 `json:"maxStateId"`
|
|
||||||
|
|
||||||
Transparent bool `json:"transparent"`
|
|
||||||
FilterLightLevel int `json:"filterLight"`
|
|
||||||
EmitLightLevel int `json:"emitLight"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Block) States() []uint32 {
|
|
||||||
if b.MinStateID == b.MaxStateID {
|
|
||||||
return []uint32{b.MinStateID}
|
|
||||||
}
|
|
||||||
states := make([]uint32, 0)
|
|
||||||
for i := b.MinStateID; i <= b.MaxStateID; i++ {
|
|
||||||
states = append(states, i)
|
|
||||||
}
|
|
||||||
return states
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadInfo() ([]*Block, error) {
|
|
||||||
resp, err := http.Get(infoURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var data []*Block
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, d := range data {
|
|
||||||
d.CamelName = strcase.ToCamel(d.Name)
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:generate go run $GOFILE
|
|
||||||
//go:generate go fmt block.go
|
|
||||||
func main() {
|
|
||||||
fmt.Println("generating block.go")
|
|
||||||
blocks, err := downloadInfo()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Create("block.go")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
if err := template.Must(template.New("").Parse(blockTmpl)).Execute(f, blocks); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
@ -1,219 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/draw"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/data/block"
|
|
||||||
"github.com/Tnze/go-mc/level"
|
|
||||||
"github.com/Tnze/go-mc/save"
|
|
||||||
"github.com/Tnze/go-mc/save/region"
|
|
||||||
)
|
|
||||||
|
|
||||||
var colors []color.RGBA64
|
|
||||||
var sectionWorkerNum = 1
|
|
||||||
|
|
||||||
var (
|
|
||||||
regionWorkerNum = flag.Int("workers", runtime.NumCPU(), "worker numbers")
|
|
||||||
regionsFold = flag.String("region", filepath.Join(os.Getenv("AppData"), ".minecraft", "saves", "World", "region"), "region directory path")
|
|
||||||
drawBigPicture = flag.Bool("bigmap", true, "draw the big map")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
de, err := os.ReadDir(*regionsFold)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var min, max [2]int
|
|
||||||
updateMinMax := func(pos [2]int) {
|
|
||||||
mkmax(&max[0], &pos[0])
|
|
||||||
mkmax(&max[1], &pos[1])
|
|
||||||
mkmin(&min[0], &pos[0])
|
|
||||||
mkmin(&min[1], &pos[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
if *drawBigPicture {
|
|
||||||
for _, dir := range de {
|
|
||||||
name := dir.Name()
|
|
||||||
var pos [2]int // {x, z}
|
|
||||||
if _, err := fmt.Sscanf(name, "r.%d.%d.mca", &pos[0], &pos[1]); err != nil {
|
|
||||||
log.Printf("Error parsing file name of %s: %v, ignoring", name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
updateMinMax(pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type regions struct {
|
|
||||||
pos [2]int
|
|
||||||
*region.Region
|
|
||||||
}
|
|
||||||
// Open mca files
|
|
||||||
var rs = make(chan regions, *regionWorkerNum)
|
|
||||||
go func() {
|
|
||||||
for _, dir := range de {
|
|
||||||
name := dir.Name()
|
|
||||||
path := filepath.Join(*regionsFold, name)
|
|
||||||
var pos [2]int // {x, z}
|
|
||||||
if _, err := fmt.Sscanf(name, "r.%d.%d.mca", &pos[0], &pos[1]); err != nil {
|
|
||||||
log.Printf("Error parsing file name of %s: %v, ignoring", name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := region.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error when opening %s: %v, ignoring", name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rs <- regions{pos: pos, Region: r}
|
|
||||||
}
|
|
||||||
close(rs)
|
|
||||||
}()
|
|
||||||
var bigPicture *image.RGBA
|
|
||||||
if *drawBigPicture {
|
|
||||||
bigPicture = image.NewRGBA(image.Rect(min[0]*512, min[1]*512, max[0]*512+512, max[1]*512+512))
|
|
||||||
}
|
|
||||||
var bigWg sync.WaitGroup
|
|
||||||
// draw columns
|
|
||||||
for r := range rs {
|
|
||||||
img := image.NewRGBA(image.Rect(0, 0, 32*16, 32*16))
|
|
||||||
type task struct {
|
|
||||||
data []byte
|
|
||||||
pos [2]int
|
|
||||||
}
|
|
||||||
c := make(chan task)
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for i := 0; i < *regionWorkerNum; i++ {
|
|
||||||
go func() {
|
|
||||||
var column save.Chunk
|
|
||||||
for task := range c {
|
|
||||||
if err := column.Load(task.data); err != nil {
|
|
||||||
log.Printf("Decode column (%d.%d) error: %v", task.pos[0], task.pos[1], err)
|
|
||||||
}
|
|
||||||
//pos := [2]int{int(column.Level.PosX), int(column.Level.PosZ)}
|
|
||||||
//if pos != task.pos {
|
|
||||||
// fmt.Printf("chunk position not match: want %v, get %v\n", task.pos, pos)
|
|
||||||
//}
|
|
||||||
draw.Draw(
|
|
||||||
img, image.Rect(task.pos[0]*16, task.pos[1]*16, task.pos[0]*16+16, task.pos[1]*16+16),
|
|
||||||
drawColumn(&column), image.Pt(0, 0),
|
|
||||||
draw.Over,
|
|
||||||
)
|
|
||||||
wg.Done()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for x := 0; x < 32; x++ {
|
|
||||||
for z := 0; z < 32; z++ {
|
|
||||||
if !r.ExistSector(x, z) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
data, err := r.ReadSector(x, z)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Read sector (%d.%d) error: %v", x, z, err)
|
|
||||||
}
|
|
||||||
wg.Add(1)
|
|
||||||
c <- task{data: data, pos: [2]int{x, z}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(c)
|
|
||||||
wg.Wait()
|
|
||||||
// Save pictures
|
|
||||||
bigWg.Add(1)
|
|
||||||
log.Print("Draw: ", r.pos)
|
|
||||||
go func(img image.Image, pos [2]int) {
|
|
||||||
savePng(img, fmt.Sprintf("r.%d.%d.png", pos[0], pos[1]))
|
|
||||||
if *drawBigPicture {
|
|
||||||
draw.Draw(
|
|
||||||
bigPicture, image.Rect(pos[0]*512, pos[1]*512, pos[0]*512+512, pos[1]*512+512),
|
|
||||||
img, image.Pt(0, 0), draw.Src,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
bigWg.Done()
|
|
||||||
}(img, r.pos)
|
|
||||||
// To close mca files
|
|
||||||
if err := r.Close(); err != nil {
|
|
||||||
log.Printf("Close r.%d.%d.mca error: %v", r.pos[0], r.pos[1], err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bigWg.Wait()
|
|
||||||
if *drawBigPicture {
|
|
||||||
savePng(bigPicture, "maps.png")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawColumn(column *save.Chunk) (img *image.RGBA) {
|
|
||||||
img = image.NewRGBA(image.Rect(0, 0, 16, 16))
|
|
||||||
s := column.Sections
|
|
||||||
c := make(chan *save.Section)
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for i := 0; i < sectionWorkerNum; i++ {
|
|
||||||
go func() {
|
|
||||||
for s := range c {
|
|
||||||
drawSection(s, img)
|
|
||||||
wg.Done()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
defer close(c)
|
|
||||||
|
|
||||||
wg.Add(len(s))
|
|
||||||
for i := range s {
|
|
||||||
c <- &s[i]
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawSection(s *save.Section, img *image.RGBA) {
|
|
||||||
data := *(*[]uint64)((unsafe.Pointer)(&s.BlockStates.Data))
|
|
||||||
palette := s.BlockStates.Palette
|
|
||||||
rawPalette := make([]int, len(palette))
|
|
||||||
for i, v := range palette {
|
|
||||||
// TODO: Consider the properties of block, not only index the block name
|
|
||||||
rawPalette[i] = int(stateIDs[strings.TrimPrefix(v.Name, "minecraft:")])
|
|
||||||
}
|
|
||||||
c := level.NewStatesPaletteContainerWithData(16*16*16, data, rawPalette)
|
|
||||||
for y := 0; y < 16; y++ {
|
|
||||||
layerImg := image.NewRGBA(image.Rect(0, 0, 16, 16))
|
|
||||||
for i := 16*16 - 1; i >= 0; i-- {
|
|
||||||
var bid block.ID
|
|
||||||
bid = block.ID(c.Get(i))
|
|
||||||
c := colors[block.ByID[bid].ID]
|
|
||||||
layerImg.Set(i%16, i/16, c)
|
|
||||||
}
|
|
||||||
draw.Draw(
|
|
||||||
img, image.Rect(0, 0, 16, 16),
|
|
||||||
layerImg, image.Pt(0, 0),
|
|
||||||
draw.Over,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This map should be moved to data/block.
|
|
||||||
var stateIDs map[string]uint32
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
for i, v := range block.StateID {
|
|
||||||
name := block.ByID[v].Name
|
|
||||||
if _, ok := stateIDs[name]; !ok {
|
|
||||||
stateIDs[name] = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
_ "embed"
|
|
||||||
"encoding/gob"
|
|
||||||
"github.com/Tnze/go-mc/data/block"
|
|
||||||
"image"
|
|
||||||
"image/png"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func savePng(img image.Image, name string) {
|
|
||||||
f, err := os.Create(name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := png.Encode(f, img); err != nil {
|
|
||||||
f.Close()
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:embed colors.gob
|
|
||||||
var colorsBin []byte // gob([]color.RGBA64)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if err := gob.NewDecoder(bytes.NewReader(colorsBin)).Decode(&colors); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mkmax(c, n *int) {
|
|
||||||
if *c < *n {
|
|
||||||
*c = *n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func mkmin(c, n *int) {
|
|
||||||
if *c > *n {
|
|
||||||
*c = *n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var idByName = make(map[string]uint32, len(block.ByID))
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
for _, v := range block.ByID {
|
|
||||||
idByName["minecraft:"+v.Name] = uint32(v.ID)
|
|
||||||
}
|
|
||||||
}
|
|
79
level/block/block.go
Normal file
79
level/block/block.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package block
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"math/bits"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/nbt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Block interface {
|
||||||
|
ID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// This file stores all possible block states into a TAG_List with zlib compressed.
|
||||||
|
//go:embed block_states.nbt
|
||||||
|
var blockStates []byte
|
||||||
|
|
||||||
|
var toStateID map[Block]int
|
||||||
|
var fromStateID []Block
|
||||||
|
|
||||||
|
// BitsPerBlock indicates how many bits are needed to represent all possible
|
||||||
|
// block states. This value is used to determine the size of the global palette.
|
||||||
|
var BitsPerBlock int
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
Name string
|
||||||
|
Properties nbt.RawMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var states []State
|
||||||
|
// decompress
|
||||||
|
z, err := zlib.NewReader(bytes.NewReader(blockStates))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// decode all states
|
||||||
|
if _, err = nbt.NewDecoder(z).Decode(&states); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
toStateID = make(map[Block]int, len(states))
|
||||||
|
fromStateID = make([]Block, 0, len(states))
|
||||||
|
for _, state := range states {
|
||||||
|
block := fromID[state.Name]
|
||||||
|
if state.Properties.Type != nbt.TagEnd {
|
||||||
|
err := state.Properties.Unmarshal(&block)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := toStateID[block]; ok {
|
||||||
|
panic(fmt.Errorf("state %#v already exist", block))
|
||||||
|
}
|
||||||
|
toStateID[block] = len(fromStateID)
|
||||||
|
fromStateID = append(fromStateID, block)
|
||||||
|
}
|
||||||
|
BitsPerBlock = bits.Len(uint(len(fromStateID)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromStateID(stateID int) (b Block, ok bool) {
|
||||||
|
if stateID >= 0 && stateID < len(fromStateID) {
|
||||||
|
b = fromStateID[stateID]
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultBlock(id string) (b Block, ok bool) {
|
||||||
|
b, ok = fromID[id]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToStateID(b Block) (i int, ok bool) {
|
||||||
|
i, ok = toStateID[b]
|
||||||
|
return
|
||||||
|
}
|
BIN
level/block/block_states.nbt
Normal file
BIN
level/block/block_states.nbt
Normal file
Binary file not shown.
6358
level/block/blocks.go
Normal file
6358
level/block/blocks.go
Normal file
File diff suppressed because it is too large
Load Diff
BIN
level/block/blocks.nbt
Normal file
BIN
level/block/blocks.nbt
Normal file
Binary file not shown.
12
level/block/generator/blocks.go.tmpl
Normal file
12
level/block/generator/blocks.go.tmpl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Code generated by {{Generator}}; DO NOT EDIT.
|
||||||
|
package block
|
||||||
|
{{range .}}
|
||||||
|
type {{.Name | ToGoTypeName}} struct { {{- range $key, $elem := .Meta}}
|
||||||
|
{{$key | UpperTheFirst}} {{$elem | GetType}} `nbt:"{{$key}}"`{{end}} {{- if ne 0 (len .Meta)}}
|
||||||
|
{{end -}} }
|
||||||
|
|
||||||
|
func ({{.Name | ToGoTypeName}}) ID() string { return {{.Name | printf "%q"}} }
|
||||||
|
{{end}}
|
||||||
|
var fromID = map[string]Block { {{- range .}}
|
||||||
|
{{.Name | printf "%#v"}}: {{.Name | ToGoTypeName}}{},{{end}}
|
||||||
|
}
|
99
level/block/generator/main.go
Normal file
99
level/block/generator/main.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/nbt"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed blocks.go.tmpl
|
||||||
|
var tempSource string
|
||||||
|
|
||||||
|
var temp = template.Must(template.
|
||||||
|
New("block_template").
|
||||||
|
Funcs(template.FuncMap{
|
||||||
|
"UpperTheFirst": UpperTheFirst,
|
||||||
|
"ToGoTypeName": ToGoTypeName,
|
||||||
|
"GetType": GetType,
|
||||||
|
"Generator": func() string { return "generator/main.go" },
|
||||||
|
}).
|
||||||
|
Parse(tempSource))
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
Name string
|
||||||
|
Meta map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var states []State
|
||||||
|
readBlockStates(&states)
|
||||||
|
|
||||||
|
// generate go source file
|
||||||
|
genSourceFile(states)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readBlockStates(states *[]State) {
|
||||||
|
// open block_states data file
|
||||||
|
f, err := os.Open("blocks.nbt")
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// parse the nbt format
|
||||||
|
if _, err := nbt.NewDecoder(f).Decode(states); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func genSourceFile(states []State) {
|
||||||
|
file, err := os.Create("blocks.go")
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
// clean up the file
|
||||||
|
if err := file.Truncate(0); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := temp.Execute(file, states); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToGoTypeName(name string) string {
|
||||||
|
name = strings.TrimPrefix(name, "minecraft:")
|
||||||
|
words := strings.Split(name, "_")
|
||||||
|
for i := range words {
|
||||||
|
words[i] = UpperTheFirst(words[i])
|
||||||
|
}
|
||||||
|
return strings.Join(words, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeMaps = map[string]string{
|
||||||
|
"BooleanProperty": "Boolean",
|
||||||
|
"DirectionProperty": "Direction",
|
||||||
|
"EnumProperty": "string",
|
||||||
|
"IntegerProperty": "Integer",
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetType(v string) string {
|
||||||
|
if mapped, ok := typeMaps[v]; ok {
|
||||||
|
return mapped
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpperTheFirst(word string) string {
|
||||||
|
runes := []rune(word)
|
||||||
|
if len(runes) > 0 {
|
||||||
|
runes[0] = unicode.ToUpper(runes[0])
|
||||||
|
}
|
||||||
|
return string(runes)
|
||||||
|
}
|
38
level/block/properties.go
Normal file
38
level/block/properties.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package block
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Boolean bool
|
||||||
|
Direction string
|
||||||
|
Integer int
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b Boolean) MarshalText() (text []byte, err error) {
|
||||||
|
return []byte(strconv.FormatBool(bool(b))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Boolean) UnmarshalText(text []byte) (err error) {
|
||||||
|
*((*bool)(b)), err = strconv.ParseBool(string(text))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Direction) MarshalText() (text []byte, err error) {
|
||||||
|
return []byte(d), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Direction) UnmarshalText(text []byte) error {
|
||||||
|
*d = Direction(text)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Integer) MarshalText() (text []byte, err error) {
|
||||||
|
return []byte(strconv.Itoa(int(i))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Integer) UnmarshalText(text []byte) (err error) {
|
||||||
|
*((*int)(i)), err = strconv.Atoi(string(text))
|
||||||
|
return
|
||||||
|
}
|
@ -2,13 +2,14 @@ package level
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/data/block"
|
"github.com/Tnze/go-mc/level/block"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
"github.com/Tnze/go-mc/save"
|
"github.com/Tnze/go-mc/save"
|
||||||
)
|
)
|
||||||
@ -113,11 +114,18 @@ func ChunkFromSave(c *save.Chunk, secs int) *Chunk {
|
|||||||
statePalette := v.BlockStates.Palette
|
statePalette := v.BlockStates.Palette
|
||||||
stateRawPalette := make([]int, len(statePalette))
|
stateRawPalette := make([]int, len(statePalette))
|
||||||
for i, v := range statePalette {
|
for i, v := range statePalette {
|
||||||
// TODO: Consider the properties of block, not only index the block name
|
b := v.Block()
|
||||||
stateRawPalette[i] = int(stateIDs[strings.TrimPrefix(v.Name, "minecraft:")])
|
if b == nil {
|
||||||
if v.Name != "minecraft:air" {
|
panic(fmt.Errorf("block not found: %#v", v))
|
||||||
|
}
|
||||||
|
if !isAir(b) {
|
||||||
blockCount++
|
blockCount++
|
||||||
}
|
}
|
||||||
|
var ok bool
|
||||||
|
stateRawPalette[i], ok = block.ToStateID(b)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Errorf("state id not found: %#v", b))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
biomesData := *(*[]uint64)((unsafe.Pointer)(&v.Biomes.Data))
|
biomesData := *(*[]uint64)((unsafe.Pointer)(&v.Biomes.Data))
|
||||||
@ -153,18 +161,6 @@ func ChunkFromSave(c *save.Chunk, secs int) *Chunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This map should be moved to data/block.
|
|
||||||
var stateIDs = make(map[string]uint32)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
for i, v := range block.StateID {
|
|
||||||
name := block.ByID[v].Name
|
|
||||||
if _, ok := stateIDs[name]; !ok {
|
|
||||||
stateIDs[name] = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chunk) WriteTo(w io.Writer) (int64, error) {
|
func (c *Chunk) WriteTo(w io.Writer) (int64, error) {
|
||||||
data, err := c.Data()
|
data, err := c.Data()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -211,8 +207,8 @@ func (s *Section) GetBlock(i int) int {
|
|||||||
return s.States.Get(i)
|
return s.States.Get(i)
|
||||||
}
|
}
|
||||||
func (s *Section) SetBlock(i int, v int) {
|
func (s *Section) SetBlock(i int, v int) {
|
||||||
// TODO: Handle cave air and void air
|
b, _ := block.FromStateID(s.States.Get(i))
|
||||||
if s.States.Get(i) != 0 {
|
if isAir(b) {
|
||||||
s.blockCount--
|
s.blockCount--
|
||||||
}
|
}
|
||||||
if v != 0 {
|
if v != 0 {
|
||||||
@ -263,3 +259,12 @@ func (l *lightData) WriteTo(w io.Writer) (int64, error) {
|
|||||||
pk.Array(l.BlockLight),
|
pk.Array(l.BlockLight),
|
||||||
}.WriteTo(w)
|
}.WriteTo(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isAir(b block.Block) bool {
|
||||||
|
switch b.(type) {
|
||||||
|
case block.Air, block.CaveAir, block.VoidAir:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package nbt
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -23,7 +24,7 @@ func Unmarshal(data []byte, v interface{}) error {
|
|||||||
// into a struct or map, but not a string.
|
// into a struct or map, but not a string.
|
||||||
//
|
//
|
||||||
// This method also return tag name of the root tag.
|
// This method also return tag name of the root tag.
|
||||||
// In real world, it is often empty, but the API should allows you to get it when ever you want.
|
// In real world, it is often empty, but the API should allow you to get it when ever you want.
|
||||||
func (d *Decoder) Decode(v interface{}) (string, error) {
|
func (d *Decoder) Decode(v interface{}) (string, error) {
|
||||||
val := reflect.ValueOf(v)
|
val := reflect.ValueOf(v)
|
||||||
if val.Kind() != reflect.Ptr {
|
if val.Kind() != reflect.Ptr {
|
||||||
@ -39,7 +40,7 @@ func (d *Decoder) Decode(v interface{}) (string, error) {
|
|||||||
return tagName, fmt.Errorf("nbt: unknown Tag, maybe compressed by %s, uncompress it first", c)
|
return tagName, fmt.Errorf("nbt: unknown Tag, maybe compressed by %s, uncompress it first", c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We decode val not val.Elem because the NBTDecoder interface
|
// We decode val not val.Elem because the Unmarshaler interface
|
||||||
// test must be applied at the top level of the value.
|
// test must be applied at the top level of the value.
|
||||||
err = d.unmarshal(val, tagType)
|
err = d.unmarshal(val, tagType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -64,9 +65,12 @@ func (d *Decoder) checkCompressed(head byte) (compress string) {
|
|||||||
var ErrEND = errors.New("unexpected TAG_End")
|
var ErrEND = errors.New("unexpected TAG_End")
|
||||||
|
|
||||||
func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error {
|
func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error {
|
||||||
u, val := indirect(val, tagType == TagEnd)
|
u, t, val, assign := indirect(val, tagType == TagEnd)
|
||||||
|
if assign != nil {
|
||||||
|
defer assign()
|
||||||
|
}
|
||||||
if u != nil {
|
if u != nil {
|
||||||
return u.Decode(tagType, d.r)
|
return u.UnmarshalNBT(tagType, d.r)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch tagType {
|
switch tagType {
|
||||||
@ -177,13 +181,20 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch vk := val.Kind(); vk {
|
if t != nil {
|
||||||
default:
|
err := t.UnmarshalText([]byte(s))
|
||||||
return errors.New("cannot parse TagString as " + vk.String())
|
if err != nil {
|
||||||
case reflect.String:
|
return err
|
||||||
val.SetString(s)
|
}
|
||||||
case reflect.Interface:
|
} else {
|
||||||
val.Set(reflect.ValueOf(s))
|
switch vk := val.Kind(); vk {
|
||||||
|
default:
|
||||||
|
return errors.New("cannot parse TagString as " + vk.String())
|
||||||
|
case reflect.String:
|
||||||
|
val.SetString(s)
|
||||||
|
case reflect.Interface:
|
||||||
|
val.Set(reflect.ValueOf(s))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case TagByteArray:
|
case TagByteArray:
|
||||||
@ -270,7 +281,7 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we need parse TAG_List into slice, make a new with right length.
|
// If we need parse TAG_List into slice, make a new with right length.
|
||||||
// Otherwise if we need parse into array, we check if len(array) are enough.
|
// Otherwise, if we need parse into array, we check if len(array) are enough.
|
||||||
var buf reflect.Value
|
var buf reflect.Value
|
||||||
vk := val.Kind()
|
vk := val.Kind()
|
||||||
switch vk {
|
switch vk {
|
||||||
@ -370,14 +381,15 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error {
|
|||||||
|
|
||||||
// indirect walks down v allocating pointers as needed,
|
// indirect walks down v allocating pointers as needed,
|
||||||
// until it gets to a non-pointer.
|
// until it gets to a non-pointer.
|
||||||
// If it encounters an NBTDecoder, indirect stops and returns that.
|
// If it encounters an Unmarshaler, indirect stops and returns that.
|
||||||
// If decodingNull is true, indirect stops at the first settable pointer so it
|
// If decodingNull is true, indirect stops at the first settable pointer, so it
|
||||||
// can be set to nil.
|
// can be set to nil.
|
||||||
//
|
//
|
||||||
// This function is copied and modified from encoding/json
|
// This function is copied and modified from encoding/json
|
||||||
func indirect(v reflect.Value, decodingNull bool) (NBTDecoder, reflect.Value) {
|
func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value, func()) {
|
||||||
v0 := v
|
v0 := v
|
||||||
haveAddr := false
|
haveAddr := false
|
||||||
|
var assign func()
|
||||||
|
|
||||||
// If v is a named type and is addressable,
|
// If v is a named type and is addressable,
|
||||||
// start with its address, so that if the type has pointer methods,
|
// start with its address, so that if the type has pointer methods,
|
||||||
@ -389,12 +401,19 @@ func indirect(v reflect.Value, decodingNull bool) (NBTDecoder, reflect.Value) {
|
|||||||
for {
|
for {
|
||||||
// Load value from interface, but only if the result will be
|
// Load value from interface, but only if the result will be
|
||||||
// usefully addressable.
|
// usefully addressable.
|
||||||
|
// Otherwise, try init a new value
|
||||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||||
e := v.Elem()
|
e := v.Elem()
|
||||||
if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
|
if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
|
||||||
haveAddr = false
|
haveAddr = false
|
||||||
v = e
|
v = e
|
||||||
continue
|
continue
|
||||||
|
} else if v.CanSet() {
|
||||||
|
e = reflect.New(e.Type())
|
||||||
|
cv := v
|
||||||
|
assign = func() { cv.Set(e.Elem()) }
|
||||||
|
v = e
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,8 +436,12 @@ func indirect(v reflect.Value, decodingNull bool) (NBTDecoder, reflect.Value) {
|
|||||||
v.Set(reflect.New(v.Type().Elem()))
|
v.Set(reflect.New(v.Type().Elem()))
|
||||||
}
|
}
|
||||||
if v.Type().NumMethod() > 0 && v.CanInterface() {
|
if v.Type().NumMethod() > 0 && v.CanInterface() {
|
||||||
if u, ok := v.Interface().(NBTDecoder); ok {
|
i := v.Interface()
|
||||||
return u, reflect.Value{}
|
if u, ok := i.(Unmarshaler); ok {
|
||||||
|
return u, nil, reflect.Value{}, assign
|
||||||
|
}
|
||||||
|
if u, ok := i.(encoding.TextUnmarshaler); ok {
|
||||||
|
return nil, u, v, assign
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,7 +452,7 @@ func indirect(v reflect.Value, decodingNull bool) (NBTDecoder, reflect.Value) {
|
|||||||
v = v.Elem()
|
v = v.Elem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, v
|
return nil, nil, v, assign
|
||||||
}
|
}
|
||||||
|
|
||||||
// rawRead read and discard a value
|
// rawRead read and discard a value
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -380,6 +381,31 @@ func TestDecoder_Decode_ErrorString(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TextBool bool
|
||||||
|
|
||||||
|
func (b TextBool) MarshalText() (text []byte, err error) {
|
||||||
|
return []byte(strconv.FormatBool(bool(b))), nil
|
||||||
|
}
|
||||||
|
func (b *TextBool) UnmarshalText(text []byte) (err error) {
|
||||||
|
*((*bool)(b)), err = strconv.ParseBool(string(text))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecoder_Decode_textUnmarshaler(t *testing.T) {
|
||||||
|
var b TextBool
|
||||||
|
data := []byte{
|
||||||
|
TagString, 0, 0,
|
||||||
|
0, 4, 't', 'r', 'u', 'e',
|
||||||
|
}
|
||||||
|
_, err := NewDecoder(bytes.NewReader(data)).Decode(&b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if b != true {
|
||||||
|
t.Errorf("b should be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRawMessage_Decode(t *testing.T) {
|
func TestRawMessage_Decode(t *testing.T) {
|
||||||
data := []byte{
|
data := []byte{
|
||||||
TagCompound, 0, 2, 'a', 'b',
|
TagCompound, 0, 2, 'a', 'b',
|
||||||
@ -435,16 +461,16 @@ func TestStringifiedMessage_Decode(t *testing.T) {
|
|||||||
t.Fatal(tag, err)
|
t.Fatal(tag, err)
|
||||||
} else {
|
} else {
|
||||||
if tag != "ab" {
|
if tag != "ab" {
|
||||||
t.Fatalf("Decode tag name error: want %s, get: %s", "ab", tag)
|
t.Fatalf("UnmarshalNBT tag name error: want %s, get: %s", "ab", tag)
|
||||||
}
|
}
|
||||||
if container.Key != 12 {
|
if container.Key != 12 {
|
||||||
t.Fatalf("Decode Key error: want %v, get: %v", 12, container.Key)
|
t.Fatalf("UnmarshalNBT Key error: want %v, get: %v", 12, container.Key)
|
||||||
}
|
}
|
||||||
if container.Value != `"Tn ze"` {
|
if container.Value != `"Tn ze"` {
|
||||||
t.Fatalf("Decode Key error: get: %v", container.Value)
|
t.Fatalf("UnmarshalNBT Key error: get: %v", container.Value)
|
||||||
}
|
}
|
||||||
if container.List != "[{},{}]" {
|
if container.List != "[{},{}]" {
|
||||||
t.Fatalf("Decode List error: get: %v", container.List)
|
t.Fatalf("UnmarshalNBT List error: get: %v", container.List)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,8 +49,8 @@ func (e *Encoder) marshal(val reflect.Value, tagType byte, tagName string) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if val.CanInterface() {
|
if val.CanInterface() {
|
||||||
if encoder, ok := val.Interface().(NBTEncoder); ok {
|
if encoder, ok := val.Interface().(Marshaler); ok {
|
||||||
return encoder.Encode(e.w)
|
return encoder.MarshalNBT(e.w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return e.writeValue(val, tagType)
|
return e.writeValue(val, tagType)
|
||||||
@ -205,7 +205,7 @@ func getTagType(v reflect.Value) (byte, reflect.Value) {
|
|||||||
v.Set(reflect.New(v.Type().Elem()))
|
v.Set(reflect.New(v.Type().Elem()))
|
||||||
}
|
}
|
||||||
if v.Type().NumMethod() > 0 && v.CanInterface() {
|
if v.Type().NumMethod() > 0 && v.CanInterface() {
|
||||||
if u, ok := v.Interface().(NBTEncoder); ok {
|
if u, ok := v.Interface().(Marshaler); ok {
|
||||||
return u.TagType(), v
|
return u.TagType(), v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,7 +214,7 @@ func getTagType(v reflect.Value) (byte, reflect.Value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if v.CanInterface() {
|
if v.CanInterface() {
|
||||||
if encoder, ok := v.Interface().(NBTEncoder); ok {
|
if encoder, ok := v.Interface().(Marshaler); ok {
|
||||||
return encoder.TagType(), v
|
return encoder.TagType(), v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ package nbt
|
|||||||
|
|
||||||
import "io"
|
import "io"
|
||||||
|
|
||||||
type NBTDecoder interface {
|
type Unmarshaler interface {
|
||||||
Decode(tagType byte, r DecoderReader) error
|
UnmarshalNBT(tagType byte, r DecoderReader) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type NBTEncoder interface {
|
type Marshaler interface {
|
||||||
TagType() byte
|
TagType() byte
|
||||||
Encode(w io.Writer) error
|
MarshalNBT(w io.Writer) error
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,12 @@ func (m RawMessage) TagType() byte {
|
|||||||
return m.Type
|
return m.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m RawMessage) Encode(w io.Writer) error {
|
func (m RawMessage) MarshalNBT(w io.Writer) error {
|
||||||
_, err := w.Write(m.Data)
|
_, err := w.Write(m.Data)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RawMessage) Decode(tagType byte, r DecoderReader) error {
|
func (m *RawMessage) UnmarshalNBT(tagType byte, r DecoderReader) error {
|
||||||
if tagType == TagEnd {
|
if tagType == TagEnd {
|
||||||
return ErrEND
|
return ErrEND
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ func (m RawMessage) Unmarshal(v interface{}) error {
|
|||||||
d := NewDecoder(bytes.NewReader(m.Data))
|
d := NewDecoder(bytes.NewReader(m.Data))
|
||||||
val := reflect.ValueOf(v)
|
val := reflect.ValueOf(v)
|
||||||
if val.Kind() != reflect.Ptr {
|
if val.Kind() != reflect.Ptr {
|
||||||
return errors.New("nbt: non-pointer passed to Decode")
|
return errors.New("nbt: non-pointer passed to UnmarshalNBT")
|
||||||
}
|
}
|
||||||
return d.unmarshal(val, m.Type)
|
return d.unmarshal(val, m.Type)
|
||||||
}
|
}
|
||||||
|
@ -56,13 +56,13 @@ func (m StringifiedMessage) TagType() byte {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m StringifiedMessage) Encode(w io.Writer) error {
|
func (m StringifiedMessage) MarshalNBT(w io.Writer) error {
|
||||||
d := decodeState{data: []byte(m)}
|
d := decodeState{data: []byte(m)}
|
||||||
d.scan.reset()
|
d.scan.reset()
|
||||||
return writeValue(NewEncoder(w), &d, false, "")
|
return writeValue(NewEncoder(w), &d, false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *StringifiedMessage) Decode(tagType byte, r DecoderReader) error {
|
func (m *StringifiedMessage) UnmarshalNBT(tagType byte, r DecoderReader) error {
|
||||||
if tagType == TagEnd {
|
if tagType == TagEnd {
|
||||||
return ErrEND
|
return ErrEND
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ func (m *StringifiedMessage) encode(d *Decoder, sb *strings.Builder, tagType byt
|
|||||||
return err
|
return err
|
||||||
case TagInt:
|
case TagInt:
|
||||||
i, err := d.readInt32()
|
i, err := d.readInt32()
|
||||||
sb.WriteString(strconv.FormatInt(int64(i), 10) + "I")
|
sb.WriteString(strconv.FormatInt(int64(i), 10))
|
||||||
return err
|
return err
|
||||||
case TagFloat:
|
case TagFloat:
|
||||||
i, err := d.readInt32()
|
i, err := d.readInt32()
|
||||||
|
@ -159,7 +159,7 @@ func TestStringifiedMessage_Encode(t *testing.T) {
|
|||||||
{`[B; 1B, 2B, 3B]`, []byte{0, 0, 0, 3, 1, 2, 3}},
|
{`[B; 1B, 2B, 3B]`, []byte{0, 0, 0, 3, 1, 2, 3}},
|
||||||
{`[{},{}]`, []byte{TagCompound, 0, 0, 0, 2, 0, 0}},
|
{`[{},{}]`, []byte{TagCompound, 0, 0, 0, 2, 0, 0}},
|
||||||
} {
|
} {
|
||||||
if err := StringifiedMessage(v.snbt).Encode(&buff); err != nil {
|
if err := StringifiedMessage(v.snbt).MarshalNBT(&buff); err != nil {
|
||||||
t.Errorf("Encode SNBT error: %v", err)
|
t.Errorf("Encode SNBT error: %v", err)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(buff.Bytes(), v.data) {
|
if !bytes.Equal(buff.Bytes(), v.data) {
|
||||||
|
3
nbt/snbt_encode_test.go
Normal file
3
nbt/snbt_encode_test.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package nbt
|
||||||
|
|
||||||
|
//TODO: Test SNBT encode
|
@ -63,11 +63,11 @@ func DialMCTimeout(addr string, timeout time.Duration) (*Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Dialer struct {
|
type Dialer struct {
|
||||||
*net.Dialer
|
net.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialer) resolver() *net.Resolver {
|
func (d *Dialer) resolver() *net.Resolver {
|
||||||
if d.Resolver != nil {
|
if d != nil && d.Resolver != nil {
|
||||||
return d.Resolver
|
return d.Resolver
|
||||||
}
|
}
|
||||||
return net.DefaultResolver
|
return net.DefaultResolver
|
||||||
|
@ -5,8 +5,10 @@ import (
|
|||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/Tnze/go-mc/nbt"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/level/block"
|
||||||
|
"github.com/Tnze/go-mc/nbt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Chunk is 16* chunk
|
// Chunk is 16* chunk
|
||||||
@ -45,6 +47,19 @@ type BlockState struct {
|
|||||||
Properties nbt.RawMessage
|
Properties nbt.RawMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *BlockState) Block() block.Block {
|
||||||
|
b, ok := block.DefaultBlock(s.Name)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s.Properties.Type != nbt.TagEnd {
|
||||||
|
if err := s.Properties.Unmarshal(&b); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// Load read column data from []byte
|
// Load read column data from []byte
|
||||||
func (c *Chunk) Load(data []byte) (err error) {
|
func (c *Chunk) Load(data []byte) (err error) {
|
||||||
var r io.Reader = bytes.NewReader(data[1:])
|
var r io.Reader = bytes.NewReader(data[1:])
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
piglin_safe: 0b,
|
piglin_safe:0B,
|
||||||
natural: 1b,
|
natural:1B,
|
||||||
ambient_light: 0.0f,
|
ambient_light:0.0000000000F,
|
||||||
infiniburn: "minecraft:infiniburn_overworld",
|
infiniburn:"#minecraft:infiniburn_overworld",
|
||||||
respawn_anchor_works: 0b,
|
respawn_anchor_works:0B,
|
||||||
has_skylight: 1b,
|
has_skylight:1B,
|
||||||
bed_works: 1b,
|
bed_works:1B,
|
||||||
effects: "minecraft:overworld",
|
effects:"minecraft:overworld",
|
||||||
has_raids: 1b,
|
has_raids:1B,
|
||||||
min_y: 0,
|
logical_height:384,
|
||||||
height: 256,
|
coordinate_scale:1.0000000000D,
|
||||||
logical_height: 256,
|
min_y:-64,
|
||||||
coordinate_scale: 1.0d,
|
has_ceiling:0B,
|
||||||
ultrawarm: 0b,
|
ultrawarm:0B,
|
||||||
has_ceiling: 0b
|
height:384
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
@ -35,7 +35,7 @@ func NewGlobalChat() *GlobalChat {
|
|||||||
func (g *GlobalChat) Init(game *Game) {
|
func (g *GlobalChat) Init(game *Game) {
|
||||||
game.AddHandler(&PacketHandler{
|
game.AddHandler(&PacketHandler{
|
||||||
ID: packetid.ServerboundChat,
|
ID: packetid.ServerboundChat,
|
||||||
F: func(player *Player, packet Packet757) error {
|
F: func(player *Player, packet Packet758) error {
|
||||||
var msg pk.String
|
var msg pk.String
|
||||||
if err := pk.Packet(packet).Scan(&msg); err != nil {
|
if err := pk.Packet(packet).Scan(&msg); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -59,14 +59,14 @@ func (g *GlobalChat) Run(ctx context.Context) {
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case item := <-g.msg:
|
case item := <-g.msg:
|
||||||
g.broadcast(Packet757(pk.Marshal(
|
g.broadcast(Packet758(pk.Marshal(
|
||||||
packetid.ClientboundChat,
|
packetid.ClientboundChat,
|
||||||
item.toMessage(),
|
item.toMessage(),
|
||||||
pk.Byte(chatPosChat),
|
pk.Byte(chatPosChat),
|
||||||
pk.UUID(item.p.UUID),
|
pk.UUID(item.p.UUID),
|
||||||
)))
|
)))
|
||||||
case p := <-g.join:
|
case p := <-g.join:
|
||||||
g.broadcast(Packet757(pk.Marshal(
|
g.broadcast(Packet758(pk.Marshal(
|
||||||
packetid.ClientboundChat,
|
packetid.ClientboundChat,
|
||||||
chat.TranslateMsg("multiplayer.player.joined", chat.Text(p.Name)).SetColor(chat.Yellow),
|
chat.TranslateMsg("multiplayer.player.joined", chat.Text(p.Name)).SetColor(chat.Yellow),
|
||||||
pk.Byte(chatPosSystem),
|
pk.Byte(chatPosSystem),
|
||||||
@ -74,7 +74,7 @@ func (g *GlobalChat) Run(ctx context.Context) {
|
|||||||
)))
|
)))
|
||||||
g.players[p.UUID] = p
|
g.players[p.UUID] = p
|
||||||
case p := <-g.quit:
|
case p := <-g.quit:
|
||||||
g.broadcast(Packet757(pk.Marshal(
|
g.broadcast(Packet758(pk.Marshal(
|
||||||
packetid.ClientboundChat,
|
packetid.ClientboundChat,
|
||||||
chat.TranslateMsg("multiplayer.player.left", chat.Text(p.Name)).SetColor(chat.Yellow),
|
chat.TranslateMsg("multiplayer.player.left", chat.Text(p.Name)).SetColor(chat.Yellow),
|
||||||
pk.Byte(chatPosSystem),
|
pk.Byte(chatPosSystem),
|
||||||
@ -85,7 +85,7 @@ func (g *GlobalChat) Run(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GlobalChat) broadcast(packet Packet757) {
|
func (g *GlobalChat) broadcast(packet Packet758) {
|
||||||
for _, p := range g.players {
|
for _, p := range g.players {
|
||||||
p.WritePacket(packet)
|
p.WritePacket(packet)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
func (g *Graph) Init(game *server.Game) {
|
func (g *Graph) Init(game *server.Game) {
|
||||||
game.AddHandler(&server.PacketHandler{
|
game.AddHandler(&server.PacketHandler{
|
||||||
ID: packetid.ServerboundChat,
|
ID: packetid.ServerboundChat,
|
||||||
F: func(player *server.Player, packet server.Packet757) error {
|
F: func(player *server.Player, packet server.Packet758) error {
|
||||||
var msg pk.String
|
var msg pk.String
|
||||||
if err := pk.Packet(packet).Scan(&msg); err != nil {
|
if err := pk.Packet(packet).Scan(&msg); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -35,7 +35,7 @@ func (g *Graph) Run(ctx context.Context) {}
|
|||||||
|
|
||||||
// AddPlayer implement server.Component for Graph
|
// AddPlayer implement server.Component for Graph
|
||||||
func (g *Graph) AddPlayer(p *server.Player) {
|
func (g *Graph) AddPlayer(p *server.Player) {
|
||||||
p.WritePacket(server.Packet757(pk.Marshal(
|
p.WritePacket(server.Packet758(pk.Marshal(
|
||||||
packetid.ClientboundCommands, g,
|
packetid.ClientboundCommands, g,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
@ -50,10 +50,10 @@ func (s *SimpleDim) PlayerJoin(p *Player) {
|
|||||||
)
|
)
|
||||||
column.Unlock()
|
column.Unlock()
|
||||||
|
|
||||||
p.WritePacket(Packet757(packet))
|
p.WritePacket(Packet758(packet))
|
||||||
}
|
}
|
||||||
|
|
||||||
p.WritePacket(Packet757(pk.Marshal(
|
p.WritePacket(Packet758(pk.Marshal(
|
||||||
packetid.ClientboundPlayerPosition,
|
packetid.ClientboundPlayerPosition,
|
||||||
pk.Double(0), pk.Double(143), pk.Double(0),
|
pk.Double(0), pk.Double(143), pk.Double(0),
|
||||||
pk.Float(0), pk.Float(0),
|
pk.Float(0), pk.Float(0),
|
||||||
|
@ -42,7 +42,7 @@ type PacketHandler struct {
|
|||||||
F packetHandlerFunc
|
F packetHandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
type packetHandlerFunc func(player *Player, packet Packet757) error
|
type packetHandlerFunc func(player *Player, packet Packet758) error
|
||||||
|
|
||||||
//go:embed DimensionCodec.snbt
|
//go:embed DimensionCodec.snbt
|
||||||
var dimensionCodecSNBT string
|
var dimensionCodecSNBT string
|
||||||
@ -95,7 +95,9 @@ func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net
|
|||||||
pk.Boolean(false), // Is hardcore
|
pk.Boolean(false), // Is hardcore
|
||||||
pk.Byte(p.Gamemode), // Gamemode
|
pk.Byte(p.Gamemode), // Gamemode
|
||||||
pk.Byte(-1), // Prev Gamemode
|
pk.Byte(-1), // Prev Gamemode
|
||||||
pk.Array([]pk.Identifier{pk.Identifier(dimInfo.Name)}),
|
pk.Array([]pk.Identifier{
|
||||||
|
pk.Identifier(dimInfo.Name),
|
||||||
|
}),
|
||||||
pk.NBT(nbt.StringifiedMessage(dimensionCodecSNBT)),
|
pk.NBT(nbt.StringifiedMessage(dimensionCodecSNBT)),
|
||||||
pk.NBT(nbt.StringifiedMessage(dimensionSNBT)),
|
pk.NBT(nbt.StringifiedMessage(dimensionSNBT)),
|
||||||
pk.Identifier(dimInfo.Name), // World Name
|
pk.Identifier(dimInfo.Name), // World Name
|
||||||
@ -145,7 +147,7 @@ func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, ph := range g.handlers[packet.ID] {
|
for _, ph := range g.handlers[packet.ID] {
|
||||||
if err := ph.F(p, Packet757(packet)); err != nil {
|
if err := ph.F(p, Packet758(packet)); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := p.GetErr(); err != nil {
|
if err := p.GetErr(); err != nil {
|
||||||
|
@ -60,7 +60,7 @@ func (k *KeepAlive) AddPlayerDelayUpdateHandler(f func(p *Player, delay time.Dur
|
|||||||
func (k *KeepAlive) Init(g *Game) {
|
func (k *KeepAlive) Init(g *Game) {
|
||||||
g.AddHandler(&PacketHandler{
|
g.AddHandler(&PacketHandler{
|
||||||
ID: packetid.ServerboundKeepAlive,
|
ID: packetid.ServerboundKeepAlive,
|
||||||
F: func(player *Player, packet Packet757) error {
|
F: func(player *Player, packet Packet758) error {
|
||||||
var KeepAliveID pk.Long
|
var KeepAliveID pk.Long
|
||||||
if err := pk.Packet(packet).Scan(&KeepAliveID); err != nil {
|
if err := pk.Packet(packet).Scan(&KeepAliveID); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -121,7 +121,7 @@ func (k *KeepAlive) pingPlayer(now time.Time) {
|
|||||||
if elem := k.pingList.Front(); elem != nil {
|
if elem := k.pingList.Front(); elem != nil {
|
||||||
p := k.pingList.Remove(elem).(keepAliveItem).player
|
p := k.pingList.Remove(elem).(keepAliveItem).player
|
||||||
// Send Clientbound KeepAlive packet.
|
// Send Clientbound KeepAlive packet.
|
||||||
p.WritePacket(Packet757(pk.Marshal(
|
p.WritePacket(Packet758(pk.Marshal(
|
||||||
packetid.ClientboundKeepAlive,
|
packetid.ClientboundKeepAlive,
|
||||||
pk.Long(k.keepAliveID),
|
pk.Long(k.keepAliveID),
|
||||||
)))
|
)))
|
||||||
|
@ -22,12 +22,13 @@ type Player struct {
|
|||||||
errChan chan error
|
errChan chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Packet757 is a packet in protocol 757.
|
// Packet758 is a packet in protocol 757.
|
||||||
// We are using type system to force programmers to update packets.
|
// We are using type system to force programmers to update packets.
|
||||||
|
type Packet758 pk.Packet
|
||||||
type Packet757 pk.Packet
|
type Packet757 pk.Packet
|
||||||
|
|
||||||
// WritePacket to player client. The type of parameter will update per version.
|
// WritePacket to player client. The type of parameter will update per version.
|
||||||
func (p *Player) WritePacket(packet Packet757) {
|
func (p *Player) WritePacket(packet Packet758) {
|
||||||
p.packetQueue.Push(pk.Packet(packet))
|
p.packetQueue.Push(pk.Packet(packet))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ func (p *PlayerList) AddPlayer(player *Player) {
|
|||||||
defer p.playersLock.Unlock()
|
defer p.playersLock.Unlock()
|
||||||
|
|
||||||
if len(p.players) >= p.maxPlayer {
|
if len(p.players) >= p.maxPlayer {
|
||||||
player.WritePacket(Packet757(pk.Marshal(
|
player.WritePacket(Packet758(pk.Marshal(
|
||||||
packetid.ClientboundDisconnect,
|
packetid.ClientboundDisconnect,
|
||||||
chat.TranslateMsg("multiplayer.disconnect.server_full"),
|
chat.TranslateMsg("multiplayer.disconnect.server_full"),
|
||||||
)))
|
)))
|
||||||
|
@ -30,8 +30,8 @@ import (
|
|||||||
"github.com/Tnze/go-mc/net"
|
"github.com/Tnze/go-mc/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ProtocolName = "1.18.1"
|
const ProtocolName = "1.18.2"
|
||||||
const ProtocolVersion = 757
|
const ProtocolVersion = 758
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
ListPingHandler
|
ListPingHandler
|
||||||
|
Reference in New Issue
Block a user