From eec9d3079514c4bfb617f04a6781396807f2f1ab Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 14 Sep 2020 20:03:57 -0700 Subject: [PATCH] Major update of chunk system for 1.16.2 --- bot/ingame.go | 203 ++++++++++++++++++------------------- bot/world/bitarray.go | 38 +++++++ bot/world/bitarray_test.go | 50 +++++++++ bot/world/chunk.go | 91 +++++++++-------- bot/world/chunk_test.go | 79 --------------- bot/world/world.go | 51 ++++++++-- 6 files changed, 279 insertions(+), 233 deletions(-) create mode 100644 bot/world/bitarray.go create mode 100644 bot/world/bitarray_test.go delete mode 100644 bot/world/chunk_test.go diff --git a/bot/ingame.go b/bot/ingame.go index 2dbc917..79d2e14 100644 --- a/bot/ingame.go +++ b/bot/ingame.go @@ -64,32 +64,10 @@ func (c *Client) handlePacket(p pk.Packet) (disconnect bool, err error) { switch data.PktID(p.ID) { case data.Login: err = handleJoinGamePacket(c, p) - - if err == nil && c.Events.GameStart != nil { - err = c.Events.GameStart() - } - - _ = c.conn.WritePacket( - //PluginMessage packet (serverbound) - sending minecraft brand. - pk.Marshal( - data.CustomPayloadServerbound, - pk.Identifier("minecraft:brand"), - pk.String(c.settings.Brand), - ), - ) - if err2 := c.Events.updateSeenPackets(seenJoinGame); err == nil { - err = err2 - } case data.CustomPayloadClientbound: err = handlePluginPacket(c, p) case data.Difficulty: err = handleServerDifficultyPacket(c, p) - if err == nil && c.Events.ServerDifficultyChange != nil { - err = c.Events.ServerDifficultyChange(c.Difficulty) - } - if err2 := c.Events.updateSeenPackets(seenServerDifficulty); err == nil { - err = err2 - } case data.SpawnPosition: err = handleSpawnPositionPacket(c, p) if err2 := c.Events.updateSeenPackets(seenSpawnPos); err == nil { @@ -97,30 +75,12 @@ func (c *Client) handlePacket(p pk.Packet) (disconnect bool, err error) { } case data.AbilitiesClientbound: err = handlePlayerAbilitiesPacket(c, p) - _ = c.conn.WritePacket( - //ClientSettings packet (serverbound) - pk.Marshal( - data.Settings, - pk.String(c.settings.Locale), - pk.Byte(c.settings.ViewDistance), - pk.VarInt(c.settings.ChatMode), - pk.Boolean(c.settings.ChatColors), - pk.UnsignedByte(c.settings.DisplayedSkinParts), - pk.VarInt(c.settings.MainHand), - ), - ) - if err2 := c.Events.updateSeenPackets(seenPlayerAbilities); err == nil { - err = err2 - } case data.HeldItemSlotClientbound: err = handleHeldItemPacket(c, p) case data.UpdateLight: err = c.Events.updateSeenPackets(seenUpdateLight) case data.MapChunk: err = handleChunkDataPacket(c, p) - if err2 := c.Events.updateSeenPackets(seenChunkData); err == nil { - err = err2 - } case data.PositionClientbound: err = handlePlayerPositionAndLookPacket(c, p) sendPlayerPositionAndLookPacket(c) // to confirm the position @@ -142,9 +102,9 @@ func (c *Client) handlePacket(p pk.Packet) (disconnect bool, err error) { case data.ChatClientbound: err = handleChatMessagePacket(c, p) case data.BlockChange: - ////err = handleBlockChangePacket(c, p) + err = handleBlockChangePacket(c, p) case data.MultiBlockChange: - ////err = handleMultiBlockChangePacket(c, p) + err = handleMultiBlockChangePacket(c, p) case data.KickDisconnect: err = handleDisconnectPacket(c, p) disconnect = true @@ -220,68 +180,67 @@ func handleSetSlotPacket(c *Client, p pk.Packet) error { return c.Events.WindowsItemChange(byte(pkt.WindowID), int(pkt.Slot), pkt.SlotData) } -// func handleMultiBlockChangePacket(c *Client, p pk.Packet) error { -// if !c.settings.ReceiveMap { -// return nil -// } +func handleMultiBlockChangePacket(c *Client, p pk.Packet) error { + if !c.settings.ReceiveMap { + return nil + } + r := bytes.NewReader(p.Data) -// var cX, cY pk.Int + var ( + loc pk.Long + dontTrustEdges pk.Boolean + sz pk.VarInt + ) -// err := p.Scan(&cX, &cY) -// if err != nil { -// return err -// } + if err := loc.Decode(r); err != nil { + return fmt.Errorf("packed location: %v", err) + } + if err := dontTrustEdges.Decode(r); err != nil { + return fmt.Errorf("unknown 1: %v", err) + } + if err := sz.Decode(r); err != nil { + return fmt.Errorf("array size: %v", err) + } -// c := g.wd.chunks[chunkLoc{int(cX), int(cY)}] -// if c != nil { -// RecordCount, err := pk.UnpackVarInt(r) -// if err != nil { -// return err -// } + packedBlocks := make([]pk.VarLong, int(sz)) + for i := 0; i < int(sz); i++ { + if err := packedBlocks[i].Decode(r); err != nil { + return fmt.Errorf("block[%d]: %v", i, err) + } + } -// for i := int32(0); i < RecordCount; i++ { -// xz, err := r.ReadByte() -// if err != nil { -// return err -// } -// y, err := r.ReadByte() -// if err != nil { -// return err -// } -// BlockID, err := pk.UnpackVarInt(r) -// if err != nil { -// return err -// } -// x, z := xz>>4, xz&0x0F + x, z, y := int((loc>>42)&((1<<22)-1)), + int((loc>>20)&((1<<22)-1)), + int(loc&((1<<20)-1)) -// c.sections[y/16].blocks[x][y%16][z] = Block{id: uint(BlockID)} -// } -// } + // Apply transform into negative (these numbers are signed) + if x >= 1<<21 { + x -= 1 << 22 + } + if z >= 1<<21 { + z -= 1 << 22 + } -// return nil -// } + c.Wd.MultiBlockUpdate(world.ChunkLoc{X: x, Z: z}, y, packedBlocks) + return nil +} -// func handleBlockChangePacket(c *Client, p pk.Packet) error { -// if !c.settings.ReceiveMap { -// return nil -// } -// var pos pk.Position -// err := p.Scan(&pos) -// if err != nil { -// return err -// } +func handleBlockChangePacket(c *Client, p pk.Packet) error { + if !c.settings.ReceiveMap { + return nil + } + var ( + pos pk.Position + bID pk.VarInt + ) -// c := g.wd.chunks[chunkLoc{x >> 4, z >> 4}] -// if c != nil { -// id, err := pk.UnpackVarInt(r) -// if err != nil { -// return err -// } -// c.sections[y/16].blocks[x&15][y&15][z&15] = Block{id: uint(id)} -// } + if err := p.Scan(&pos, &bID); err != nil { + return err + } -// return nil -// } + c.Wd.UnaryBlockUpdate(pos, world.BlockStatus(bID)) + return nil +} func handleChatMessagePacket(c *Client, p pk.Packet) (err error) { var msg ptypes.ChatMessageClientbound @@ -338,6 +297,23 @@ func handleJoinGamePacket(c *Client, p pk.Packet) error { c.IsDebug = bool(pkt.IsDebug) c.IsFlat = bool(pkt.IsFlat) + if c.Events.GameStart != nil { + if err := c.Events.GameStart(); err != nil { + return err + } + } + + c.conn.WritePacket( + //PluginMessage packet (serverbound) - sending minecraft brand. + pk.Marshal( + data.CustomPayloadServerbound, + pk.Identifier("minecraft:brand"), + pk.String(c.settings.Brand), + ), + ) + if err := c.Events.updateSeenPackets(seenJoinGame); err != nil { + return err + } return nil } @@ -364,12 +340,17 @@ func handlePluginPacket(c *Client, p pk.Packet) error { func handleServerDifficultyPacket(c *Client, p pk.Packet) error { var difficulty pk.Byte - err := p.Scan(&difficulty) - if err != nil { + if err := p.Scan(&difficulty); err != nil { return err } c.Difficulty = int(difficulty) - return nil + + if c.Events.ServerDifficultyChange != nil { + if err := c.Events.ServerDifficultyChange(c.Difficulty); err != nil { + return err + } + } + return c.Events.updateSeenPackets(seenServerDifficulty) } func handleSpawnPositionPacket(c *Client, p pk.Packet) error { @@ -383,7 +364,7 @@ func handleSpawnPositionPacket(c *Client, p pk.Packet) error { return nil } -func handlePlayerAbilitiesPacket(g *Client, p pk.Packet) error { +func handlePlayerAbilitiesPacket(c *Client, p pk.Packet) error { var ( flags pk.Byte flySpeed pk.Float @@ -393,10 +374,23 @@ func handlePlayerAbilitiesPacket(g *Client, p pk.Packet) error { if err != nil { return err } - g.abilities.Flags = int8(flags) - g.abilities.FlyingSpeed = float32(flySpeed) - g.abilities.FieldofViewModifier = float32(viewMod) - return nil + c.abilities.Flags = int8(flags) + c.abilities.FlyingSpeed = float32(flySpeed) + c.abilities.FieldofViewModifier = float32(viewMod) + + c.conn.WritePacket( + //ClientSettings packet (serverbound) + pk.Marshal( + data.Settings, + pk.String(c.settings.Locale), + pk.Byte(c.settings.ViewDistance), + pk.VarInt(c.settings.ChatMode), + pk.Boolean(c.settings.ChatColors), + pk.UnsignedByte(c.settings.DisplayedSkinParts), + pk.VarInt(c.settings.MainHand), + ), + ) + return c.Events.updateSeenPackets(seenPlayerAbilities) } func handleHeldItemPacket(c *Client, p pk.Packet) error { @@ -413,6 +407,9 @@ func handleHeldItemPacket(c *Client, p pk.Packet) error { } func handleChunkDataPacket(c *Client, p pk.Packet) error { + if err := c.Events.updateSeenPackets(seenChunkData); err != nil { + return err + } if !c.settings.ReceiveMap { return nil } diff --git a/bot/world/bitarray.go b/bot/world/bitarray.go new file mode 100644 index 0000000..cd8f9af --- /dev/null +++ b/bot/world/bitarray.go @@ -0,0 +1,38 @@ +package world + +// bitArray implements a bitfield array where values are packed into uint64 +// values. If the next value does not fit into remaining space, the remaining +// space of a uint64 is unused. +type bitArray struct { + width uint // bit width of each value + valsPerElement uint // number of values which fit into a single uint64. + + data []uint64 +} + +// Size returns the number of elements that can fit into the bit array. +func (b *bitArray) Size() int { + return int(b.valsPerElement) * len(b.data) +} + +func (b *bitArray) Set(idx, val uint) { + var ( + arrayIdx = idx / b.valsPerElement + startBit = (idx % b.valsPerElement) * b.width + mask = ^uint64((1<> offset +} + +func valsPerBitArrayElement(bitsPerValue uint) uint { + return uint(64 / bitsPerValue) +} diff --git a/bot/world/bitarray_test.go b/bot/world/bitarray_test.go new file mode 100644 index 0000000..cb45060 --- /dev/null +++ b/bot/world/bitarray_test.go @@ -0,0 +1,50 @@ +package world + +import ( + "encoding/binary" + "encoding/hex" + "testing" +) + +func TestBitArrayBasic(t *testing.T) { + a := bitArray{ + width: 5, + valsPerElement: valsPerBitArrayElement(5), + data: make([]uint64, 5), + } + + if got, want := a.Size(), 12*5; got != want { + t.Errorf("size = %d, want %d", got, want) + } + + a.Set(0, 4) + if v := a.Get(0); v != 4 { + t.Errorf("v[0] = %d, want 4", v) + } + a.Set(12, 8) + if v := a.Get(12); v != 8 { + t.Errorf("v[12] = %d, want 8", v) + } +} + +func TestBitArrayHardcoded(t *testing.T) { + d1, _ := hex.DecodeString("0020863148418841") + d2, _ := hex.DecodeString("01018A7260F68C87") + + a := bitArray{ + width: 5, + valsPerElement: valsPerBitArrayElement(5), + data: []uint64{binary.BigEndian.Uint64(d1), binary.BigEndian.Uint64(d2)}, + } + + if got, want := a.Size(), 12*2; got != want { + t.Errorf("size = %d, want %d", got, want) + } + + want := []uint{1, 2, 2, 3, 4, 4, 5, 6, 6, 4, 8, 0, 7, 4, 3, 13, 15, 16, 9, 14, 10, 12, 0, 2} + for idx, want := range want { + if got := a.Get(uint(idx)); got != want { + t.Errorf("v[%d] = %d, want %d", idx, got, want) + } + } +} diff --git a/bot/world/chunk.go b/bot/world/chunk.go index e347108..e01e3ce 100644 --- a/bot/world/chunk.go +++ b/bot/world/chunk.go @@ -8,6 +8,8 @@ import ( pk "github.com/Tnze/go-mc/net/packet" ) +const maxPaletteBits = 8 + // DecodeChunkColumn decode the chunk data structure. // If decoding went error, successful decoded data will be returned. func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) { @@ -27,35 +29,35 @@ func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) { return &c, nil } -func perBits(BitsPerBlock byte) int { +func perBits(bpb byte) uint { switch { - case BitsPerBlock <= 4: + case bpb <= 4: return 4 - case BitsPerBlock < 9: - return int(BitsPerBlock) + case bpb <= maxPaletteBits: + return uint(bpb) default: - return block.BitsPerBlock + return uint(block.BitsPerBlock) } } func readSection(data pk.DecodeReader) (s Section, err error) { - var BlockCount pk.Short - if err := BlockCount.Decode(data); err != nil { - return nil, fmt.Errorf("read block count error: %w", err) + var nonAirBlockCount pk.Short + if err := nonAirBlockCount.Decode(data); err != nil { + return nil, fmt.Errorf("block count: %w", err) } var bpb pk.UnsignedByte if err := bpb.Decode(data); err != nil { - return nil, fmt.Errorf("read bits per block error: %w", err) + return nil, fmt.Errorf("bits per block: %w", err) } // If bpb values greater than or equal to 9, use directSection. // Otherwise use paletteSection. var palettes []BlockStatus var palettesIndex map[BlockStatus]int - if bpb < 9 { + if bpb <= maxPaletteBits { // read palettes var length pk.VarInt if err := length.Decode(data); err != nil { - return nil, fmt.Errorf("read palettes length error: %w", err) + return nil, fmt.Errorf("palette length: %w", err) } palettes = make([]BlockStatus, length) palettesIndex = make(map[BlockStatus]int, length) @@ -86,8 +88,15 @@ func readSection(data pk.DecodeReader) (s Section, err error) { dataArray[i] = uint64(v) } - sec := directSection{bpb: perBits(byte(bpb)), data: dataArray} - if bpb < 9 { + width := perBits(byte(bpb)) + sec := directSection{ + bitArray{ + width: width, + valsPerElement: valsPerBitArrayElement(width), + data: dataArray, + }, + } + if bpb <= maxPaletteBits { return &paletteSection{ palette: palettes, palettesIndex: palettesIndex, @@ -99,46 +108,38 @@ func readSection(data pk.DecodeReader) (s Section, err error) { } type directSection struct { - bpb int - data []uint64 + bitArray } -func (d *directSection) GetBlock(offset int) BlockStatus { - offset *= d.bpb - padding := offset % 64 - block := uint32(d.data[offset/64] >> padding) - if padding > 64-d.bpb { - l := 64 - padding - block |= uint32(d.data[offset/64+1] << l) - } - return BlockStatus(block & (1< 64-d.bpb { - l := padding - (64 - d.bpb) - const maxUint64 = 1<<64 - 1 - d.data[offset/64+1] = d.data[offset/64+1]&(maxUint64<>(64-padding) - } +func (d *directSection) SetBlock(offset uint, s BlockStatus) { + d.Set(offset, uint(s)) } func (d *directSection) CanContain(s BlockStatus) bool { - return s <= (1<>4 rather then n/16. It acts wrong if n<0. c := w.Chunks[ChunkLoc{x >> 4, z >> 4}] if c != nil { - // (n&(16-1)) == (n<0 ? n%16+16 : n%16) if sec := c.Sections[y>>4]; sec != nil { - return sec.GetBlock(SectionOffset(x&15, y&15, z&15)) + return sec.GetBlock(SectionIdx(x&15, y&15, z&15)) } } return 0 } +func (w *World) UnaryBlockUpdate(pos pk.Position, bStateID BlockStatus) bool { + c := w.Chunks[ChunkLoc{X: pos.X >> 4, Z: pos.Z >> 4}] + if c == nil { + return false + } + sIdx, bIdx := pos.Y>>4, SectionIdx(pos.X&15, pos.Y&15, pos.Z&15) + + if sec := c.Sections[sIdx]; sec == nil { + sec = newSectionWithSize(uint(block.BitsPerBlock)) + sec.SetBlock(bIdx, bStateID) + c.Sections[sIdx] = sec + } else { + sec.SetBlock(bIdx, bStateID) + } + return true +} + +func (w *World) MultiBlockUpdate(loc ChunkLoc, sectionY int, blocks []pk.VarLong) bool { + c := w.Chunks[loc] + if c == nil { + return false // not loaded + } + + sec := c.Sections[sectionY] + if sec == nil { + sec = newSectionWithSize(uint(block.BitsPerBlock)) + c.Sections[sectionY] = sec + } + + for _, b := range blocks { + bStateID := b >> 12 + x, z, y := (b>>8)&0xf, (b>>4)&0xf, b&0xf + sec.SetBlock(SectionIdx(int(x&15), int(y&15), int(z&15)), BlockStatus(bStateID)) + } + + return true +} + // func (b Block) String() string { // return blockNameByID[b.id] // }