Major update of chunk system for 1.16.2
This commit is contained in:
203
bot/ingame.go
203
bot/ingame.go
@ -64,32 +64,10 @@ func (c *Client) handlePacket(p pk.Packet) (disconnect bool, err error) {
|
|||||||
switch data.PktID(p.ID) {
|
switch data.PktID(p.ID) {
|
||||||
case data.Login:
|
case data.Login:
|
||||||
err = handleJoinGamePacket(c, p)
|
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:
|
case data.CustomPayloadClientbound:
|
||||||
err = handlePluginPacket(c, p)
|
err = handlePluginPacket(c, p)
|
||||||
case data.Difficulty:
|
case data.Difficulty:
|
||||||
err = handleServerDifficultyPacket(c, p)
|
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:
|
case data.SpawnPosition:
|
||||||
err = handleSpawnPositionPacket(c, p)
|
err = handleSpawnPositionPacket(c, p)
|
||||||
if err2 := c.Events.updateSeenPackets(seenSpawnPos); err == nil {
|
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:
|
case data.AbilitiesClientbound:
|
||||||
err = handlePlayerAbilitiesPacket(c, p)
|
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:
|
case data.HeldItemSlotClientbound:
|
||||||
err = handleHeldItemPacket(c, p)
|
err = handleHeldItemPacket(c, p)
|
||||||
case data.UpdateLight:
|
case data.UpdateLight:
|
||||||
err = c.Events.updateSeenPackets(seenUpdateLight)
|
err = c.Events.updateSeenPackets(seenUpdateLight)
|
||||||
case data.MapChunk:
|
case data.MapChunk:
|
||||||
err = handleChunkDataPacket(c, p)
|
err = handleChunkDataPacket(c, p)
|
||||||
if err2 := c.Events.updateSeenPackets(seenChunkData); err == nil {
|
|
||||||
err = err2
|
|
||||||
}
|
|
||||||
case data.PositionClientbound:
|
case data.PositionClientbound:
|
||||||
err = handlePlayerPositionAndLookPacket(c, p)
|
err = handlePlayerPositionAndLookPacket(c, p)
|
||||||
sendPlayerPositionAndLookPacket(c) // to confirm the position
|
sendPlayerPositionAndLookPacket(c) // to confirm the position
|
||||||
@ -142,9 +102,9 @@ func (c *Client) handlePacket(p pk.Packet) (disconnect bool, err error) {
|
|||||||
case data.ChatClientbound:
|
case data.ChatClientbound:
|
||||||
err = handleChatMessagePacket(c, p)
|
err = handleChatMessagePacket(c, p)
|
||||||
case data.BlockChange:
|
case data.BlockChange:
|
||||||
////err = handleBlockChangePacket(c, p)
|
err = handleBlockChangePacket(c, p)
|
||||||
case data.MultiBlockChange:
|
case data.MultiBlockChange:
|
||||||
////err = handleMultiBlockChangePacket(c, p)
|
err = handleMultiBlockChangePacket(c, p)
|
||||||
case data.KickDisconnect:
|
case data.KickDisconnect:
|
||||||
err = handleDisconnectPacket(c, p)
|
err = handleDisconnectPacket(c, p)
|
||||||
disconnect = true
|
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)
|
return c.Events.WindowsItemChange(byte(pkt.WindowID), int(pkt.Slot), pkt.SlotData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// func handleMultiBlockChangePacket(c *Client, p pk.Packet) error {
|
func handleMultiBlockChangePacket(c *Client, p pk.Packet) error {
|
||||||
// if !c.settings.ReceiveMap {
|
if !c.settings.ReceiveMap {
|
||||||
// return nil
|
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 := loc.Decode(r); err != nil {
|
||||||
// if err != nil {
|
return fmt.Errorf("packed location: %v", err)
|
||||||
// return 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)}]
|
packedBlocks := make([]pk.VarLong, int(sz))
|
||||||
// if c != nil {
|
for i := 0; i < int(sz); i++ {
|
||||||
// RecordCount, err := pk.UnpackVarInt(r)
|
if err := packedBlocks[i].Decode(r); err != nil {
|
||||||
// if err != nil {
|
return fmt.Errorf("block[%d]: %v", i, err)
|
||||||
// return err
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// for i := int32(0); i < RecordCount; i++ {
|
x, z, y := int((loc>>42)&((1<<22)-1)),
|
||||||
// xz, err := r.ReadByte()
|
int((loc>>20)&((1<<22)-1)),
|
||||||
// if err != nil {
|
int(loc&((1<<20)-1))
|
||||||
// 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
|
|
||||||
|
|
||||||
// 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 {
|
func handleBlockChangePacket(c *Client, p pk.Packet) error {
|
||||||
// if !c.settings.ReceiveMap {
|
if !c.settings.ReceiveMap {
|
||||||
// return nil
|
return nil
|
||||||
// }
|
}
|
||||||
// var pos pk.Position
|
var (
|
||||||
// err := p.Scan(&pos)
|
pos pk.Position
|
||||||
// if err != nil {
|
bID pk.VarInt
|
||||||
// return err
|
)
|
||||||
// }
|
|
||||||
|
|
||||||
// c := g.wd.chunks[chunkLoc{x >> 4, z >> 4}]
|
if err := p.Scan(&pos, &bID); err != nil {
|
||||||
// if c != nil {
|
return err
|
||||||
// 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)}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return nil
|
c.Wd.UnaryBlockUpdate(pos, world.BlockStatus(bID))
|
||||||
// }
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func handleChatMessagePacket(c *Client, p pk.Packet) (err error) {
|
func handleChatMessagePacket(c *Client, p pk.Packet) (err error) {
|
||||||
var msg ptypes.ChatMessageClientbound
|
var msg ptypes.ChatMessageClientbound
|
||||||
@ -338,6 +297,23 @@ func handleJoinGamePacket(c *Client, p pk.Packet) error {
|
|||||||
c.IsDebug = bool(pkt.IsDebug)
|
c.IsDebug = bool(pkt.IsDebug)
|
||||||
c.IsFlat = bool(pkt.IsFlat)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,12 +340,17 @@ func handlePluginPacket(c *Client, p pk.Packet) error {
|
|||||||
|
|
||||||
func handleServerDifficultyPacket(c *Client, p pk.Packet) error {
|
func handleServerDifficultyPacket(c *Client, p pk.Packet) error {
|
||||||
var difficulty pk.Byte
|
var difficulty pk.Byte
|
||||||
err := p.Scan(&difficulty)
|
if err := p.Scan(&difficulty); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.Difficulty = int(difficulty)
|
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 {
|
func handleSpawnPositionPacket(c *Client, p pk.Packet) error {
|
||||||
@ -383,7 +364,7 @@ func handleSpawnPositionPacket(c *Client, p pk.Packet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePlayerAbilitiesPacket(g *Client, p pk.Packet) error {
|
func handlePlayerAbilitiesPacket(c *Client, p pk.Packet) error {
|
||||||
var (
|
var (
|
||||||
flags pk.Byte
|
flags pk.Byte
|
||||||
flySpeed pk.Float
|
flySpeed pk.Float
|
||||||
@ -393,10 +374,23 @@ func handlePlayerAbilitiesPacket(g *Client, p pk.Packet) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
g.abilities.Flags = int8(flags)
|
c.abilities.Flags = int8(flags)
|
||||||
g.abilities.FlyingSpeed = float32(flySpeed)
|
c.abilities.FlyingSpeed = float32(flySpeed)
|
||||||
g.abilities.FieldofViewModifier = float32(viewMod)
|
c.abilities.FieldofViewModifier = float32(viewMod)
|
||||||
return nil
|
|
||||||
|
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 {
|
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 {
|
func handleChunkDataPacket(c *Client, p pk.Packet) error {
|
||||||
|
if err := c.Events.updateSeenPackets(seenChunkData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if !c.settings.ReceiveMap {
|
if !c.settings.ReceiveMap {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
38
bot/world/bitarray.go
Normal file
38
bot/world/bitarray.go
Normal file
@ -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<<b.width - 1) << startBit) // set for all bits except target
|
||||||
|
)
|
||||||
|
b.data[arrayIdx] = (b.data[arrayIdx] & mask) | uint64(val<<startBit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitArray) Get(idx uint) uint {
|
||||||
|
var (
|
||||||
|
arrayIdx = idx / b.valsPerElement
|
||||||
|
offset = (idx % b.valsPerElement) * b.width
|
||||||
|
mask = uint64((1<<b.width - 1) << offset) // set for just the target
|
||||||
|
)
|
||||||
|
return uint(b.data[arrayIdx]&mask) >> offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func valsPerBitArrayElement(bitsPerValue uint) uint {
|
||||||
|
return uint(64 / bitsPerValue)
|
||||||
|
}
|
50
bot/world/bitarray_test.go
Normal file
50
bot/world/bitarray_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,8 @@ import (
|
|||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const maxPaletteBits = 8
|
||||||
|
|
||||||
// DecodeChunkColumn decode the chunk data structure.
|
// DecodeChunkColumn decode the chunk data structure.
|
||||||
// If decoding went error, successful decoded data will be returned.
|
// If decoding went error, successful decoded data will be returned.
|
||||||
func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) {
|
func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) {
|
||||||
@ -27,35 +29,35 @@ func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) {
|
|||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func perBits(BitsPerBlock byte) int {
|
func perBits(bpb byte) uint {
|
||||||
switch {
|
switch {
|
||||||
case BitsPerBlock <= 4:
|
case bpb <= 4:
|
||||||
return 4
|
return 4
|
||||||
case BitsPerBlock < 9:
|
case bpb <= maxPaletteBits:
|
||||||
return int(BitsPerBlock)
|
return uint(bpb)
|
||||||
default:
|
default:
|
||||||
return block.BitsPerBlock
|
return uint(block.BitsPerBlock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readSection(data pk.DecodeReader) (s Section, err error) {
|
func readSection(data pk.DecodeReader) (s Section, err error) {
|
||||||
var BlockCount pk.Short
|
var nonAirBlockCount pk.Short
|
||||||
if err := BlockCount.Decode(data); err != nil {
|
if err := nonAirBlockCount.Decode(data); err != nil {
|
||||||
return nil, fmt.Errorf("read block count error: %w", err)
|
return nil, fmt.Errorf("block count: %w", err)
|
||||||
}
|
}
|
||||||
var bpb pk.UnsignedByte
|
var bpb pk.UnsignedByte
|
||||||
if err := bpb.Decode(data); err != nil {
|
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.
|
// If bpb values greater than or equal to 9, use directSection.
|
||||||
// Otherwise use paletteSection.
|
// Otherwise use paletteSection.
|
||||||
var palettes []BlockStatus
|
var palettes []BlockStatus
|
||||||
var palettesIndex map[BlockStatus]int
|
var palettesIndex map[BlockStatus]int
|
||||||
if bpb < 9 {
|
if bpb <= maxPaletteBits {
|
||||||
// read palettes
|
// read palettes
|
||||||
var length pk.VarInt
|
var length pk.VarInt
|
||||||
if err := length.Decode(data); err != nil {
|
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)
|
palettes = make([]BlockStatus, length)
|
||||||
palettesIndex = make(map[BlockStatus]int, length)
|
palettesIndex = make(map[BlockStatus]int, length)
|
||||||
@ -86,8 +88,15 @@ func readSection(data pk.DecodeReader) (s Section, err error) {
|
|||||||
dataArray[i] = uint64(v)
|
dataArray[i] = uint64(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
sec := directSection{bpb: perBits(byte(bpb)), data: dataArray}
|
width := perBits(byte(bpb))
|
||||||
if bpb < 9 {
|
sec := directSection{
|
||||||
|
bitArray{
|
||||||
|
width: width,
|
||||||
|
valsPerElement: valsPerBitArrayElement(width),
|
||||||
|
data: dataArray,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if bpb <= maxPaletteBits {
|
||||||
return &paletteSection{
|
return &paletteSection{
|
||||||
palette: palettes,
|
palette: palettes,
|
||||||
palettesIndex: palettesIndex,
|
palettesIndex: palettesIndex,
|
||||||
@ -99,46 +108,38 @@ func readSection(data pk.DecodeReader) (s Section, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type directSection struct {
|
type directSection struct {
|
||||||
bpb int
|
bitArray
|
||||||
data []uint64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *directSection) GetBlock(offset int) BlockStatus {
|
func (d *directSection) GetBlock(offset uint) BlockStatus {
|
||||||
offset *= d.bpb
|
return BlockStatus(d.Get(offset))
|
||||||
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<<d.bpb - 1)) // mask
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *directSection) SetBlock(offset int, s BlockStatus) {
|
func (d *directSection) SetBlock(offset uint, s BlockStatus) {
|
||||||
offset *= d.bpb
|
d.Set(offset, uint(s))
|
||||||
padding := offset % 64
|
|
||||||
mask := ^uint64((1<<d.bpb - 1) << padding)
|
|
||||||
d.data[offset/64] = d.data[offset/64]&mask | uint64(s)<<padding
|
|
||||||
if padding > 64-d.bpb {
|
|
||||||
l := padding - (64 - d.bpb)
|
|
||||||
const maxUint64 = 1<<64 - 1
|
|
||||||
d.data[offset/64+1] = d.data[offset/64+1]&(maxUint64<<l) | uint64(s)>>(64-padding)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *directSection) CanContain(s BlockStatus) bool {
|
func (d *directSection) CanContain(s BlockStatus) bool {
|
||||||
return s <= (1<<d.bpb - 1)
|
return s <= (1<<d.width - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *directSection) clone(bpb int) *directSection {
|
func (d *directSection) clone(bpb uint) *directSection {
|
||||||
newSection := &directSection{
|
out := newSectionWithSize(bpb)
|
||||||
bpb: bpb,
|
for offset := uint(0); offset < 16*16*16; offset++ {
|
||||||
data: make([]uint64, 16*16*16*bpb/64),
|
out.SetBlock(offset, d.GetBlock(offset))
|
||||||
}
|
}
|
||||||
for offset := 0; offset < 16*16*16; offset++ {
|
return out
|
||||||
newSection.SetBlock(offset, d.GetBlock(offset))
|
}
|
||||||
|
|
||||||
|
func newSectionWithSize(bpb uint) *directSection {
|
||||||
|
valsPerElement := valsPerBitArrayElement(bpb)
|
||||||
|
return &directSection{
|
||||||
|
bitArray{
|
||||||
|
width: bpb,
|
||||||
|
valsPerElement: valsPerElement,
|
||||||
|
data: make([]uint64, 16*16*16/valsPerElement),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return newSection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type paletteSection struct {
|
type paletteSection struct {
|
||||||
@ -147,12 +148,12 @@ type paletteSection struct {
|
|||||||
directSection
|
directSection
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *paletteSection) GetBlock(offset int) BlockStatus {
|
func (p *paletteSection) GetBlock(offset uint) BlockStatus {
|
||||||
v := p.directSection.GetBlock(offset)
|
v := p.directSection.GetBlock(offset)
|
||||||
return p.palette[v]
|
return p.palette[v]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *paletteSection) SetBlock(offset int, s BlockStatus) {
|
func (p *paletteSection) SetBlock(offset uint, s BlockStatus) {
|
||||||
if i, ok := p.palettesIndex[s]; ok {
|
if i, ok := p.palettesIndex[s]; ok {
|
||||||
p.directSection.SetBlock(offset, BlockStatus(i))
|
p.directSection.SetBlock(offset, BlockStatus(i))
|
||||||
return
|
return
|
||||||
@ -164,7 +165,7 @@ func (p *paletteSection) SetBlock(offset int, s BlockStatus) {
|
|||||||
// Increase the underlying directSection
|
// Increase the underlying directSection
|
||||||
// Suppose that old bpb fit len(p.palette) before it appended.
|
// Suppose that old bpb fit len(p.palette) before it appended.
|
||||||
// So bpb+1 must enough for new len(p.palette).
|
// So bpb+1 must enough for new len(p.palette).
|
||||||
p.directSection = *p.directSection.clone(p.bpb + 1)
|
p.directSection = *p.directSection.clone(p.width + 1)
|
||||||
}
|
}
|
||||||
p.directSection.SetBlock(offset, BlockStatus(i))
|
p.directSection.SetBlock(offset, BlockStatus(i))
|
||||||
}
|
}
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
package world
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/data/block"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newDirectSection(bpb int) Section {
|
|
||||||
return &directSection{
|
|
||||||
bpb: bpb,
|
|
||||||
data: make([]uint64, 16*16*16*bpb/64),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDirectSection(t *testing.T) {
|
|
||||||
for bpb := 4; bpb <= block.BitsPerBlock; bpb++ {
|
|
||||||
testSection(newDirectSection(bpb), bpb)(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDirectSection_clone(t *testing.T) {
|
|
||||||
s := newDirectSection(9)
|
|
||||||
dataset := randData(9)
|
|
||||||
for i := 0; i < 16*16*16; i++ {
|
|
||||||
s.SetBlock(i, dataset[i])
|
|
||||||
}
|
|
||||||
s = s.(*directSection).clone(block.BitsPerBlock)
|
|
||||||
for i := 0; i < 16*16*16; i++ {
|
|
||||||
if s := s.GetBlock(i); dataset[i] != s {
|
|
||||||
t.Fatalf("direct section error: want: %v, get %v", dataset[i], s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPaletteSection(t *testing.T) {
|
|
||||||
t.Run("Correctness", testSection(&paletteSection{
|
|
||||||
palettesIndex: make(map[BlockStatus]int),
|
|
||||||
directSection: *(newDirectSection(7).(*directSection)),
|
|
||||||
}, 7))
|
|
||||||
t.Run("AutomaticExpansion", testSection(&paletteSection{
|
|
||||||
palettesIndex: make(map[BlockStatus]int),
|
|
||||||
directSection: *(newDirectSection(4).(*directSection)),
|
|
||||||
}, 9))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSection(s Section, bpb int) func(t *testing.T) {
|
|
||||||
return func(t *testing.T) {
|
|
||||||
for _, dataset := range [][16 * 16 * 16]BlockStatus{secData(bpb), randData(bpb)} {
|
|
||||||
for i := 0; i < 16*16*16; i++ {
|
|
||||||
s.SetBlock(i, dataset[i])
|
|
||||||
}
|
|
||||||
for i := 0; i < 16*16*16; i++ {
|
|
||||||
if v := s.GetBlock(i); dataset[i] != v {
|
|
||||||
t.Fatalf("direct section error: want: %v, get %v", dataset[i], v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func secData(bpb int) (data [16 * 16 * 16]BlockStatus) {
|
|
||||||
mask := 1<<bpb - 1
|
|
||||||
var v int
|
|
||||||
for i := range data {
|
|
||||||
data[i] = BlockStatus(v)
|
|
||||||
v = (v + 1) & mask
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func randData(bpb int) (data [16 * 16 * 16]BlockStatus) {
|
|
||||||
data = secData(bpb)
|
|
||||||
rand.Shuffle(len(data), func(i, j int) {
|
|
||||||
data[i], data[j] = data[j], data[i]
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
@ -2,6 +2,8 @@ package world
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Tnze/go-mc/bot/world/entity"
|
"github.com/Tnze/go-mc/bot/world/entity"
|
||||||
|
"github.com/Tnze/go-mc/data/block"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// World record all of the things in the world where player at
|
// World record all of the things in the world where player at
|
||||||
@ -18,16 +20,16 @@ type Chunk struct {
|
|||||||
// Section store a 16*16*16 cube blocks
|
// Section store a 16*16*16 cube blocks
|
||||||
type Section interface {
|
type Section interface {
|
||||||
// GetBlock return block status, offset can be calculate by SectionOffset.
|
// GetBlock return block status, offset can be calculate by SectionOffset.
|
||||||
GetBlock(offset int) BlockStatus
|
GetBlock(offset uint) BlockStatus
|
||||||
// SetBlock is the reverse operation of GetBlock.
|
// SetBlock is the reverse operation of GetBlock.
|
||||||
SetBlock(offset int, s BlockStatus)
|
SetBlock(offset uint, s BlockStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SectionOffset(x, y, z int) (offset int) {
|
func SectionIdx(x, y, z int) uint {
|
||||||
// According to wiki.vg: Data Array is given for each block with increasing x coordinates,
|
// According to wiki.vg: Data Array is given for each block with increasing x coordinates,
|
||||||
// within rows of increasing z coordinates, within layers of increasing y coordinates.
|
// within rows of increasing z coordinates, within layers of increasing y coordinates.
|
||||||
// So offset equals to ( x*16^0 + z*16^1 + y*16^2 )*(bits per block).
|
// So offset equals to ( x*16^0 + z*16^1 + y*16^2 )*(bits per block).
|
||||||
return x + z*16 + y*16*16
|
return uint(((y & 15) << 8) | (z << 4) | x)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlockStatus uint32
|
type BlockStatus uint32
|
||||||
@ -59,14 +61,51 @@ func (w *World) GetBlockStatus(x, y, z int) BlockStatus {
|
|||||||
// Use n>>4 rather then n/16. It acts wrong if n<0.
|
// Use n>>4 rather then n/16. It acts wrong if n<0.
|
||||||
c := w.Chunks[ChunkLoc{x >> 4, z >> 4}]
|
c := w.Chunks[ChunkLoc{x >> 4, z >> 4}]
|
||||||
if c != nil {
|
if c != nil {
|
||||||
// (n&(16-1)) == (n<0 ? n%16+16 : n%16)
|
|
||||||
if sec := c.Sections[y>>4]; sec != nil {
|
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
|
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 {
|
// func (b Block) String() string {
|
||||||
// return blockNameByID[b.id]
|
// return blockNameByID[b.id]
|
||||||
// }
|
// }
|
||||||
|
Reference in New Issue
Block a user