up3
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled

This commit is contained in:
2026-07-09 08:33:57 +08:00
commit 26ed99fda6
845 changed files with 75419 additions and 0 deletions
+254
View File
@@ -0,0 +1,254 @@
package session
import (
"bytes"
"github.com/cespare/xxhash/v2"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/chunk"
"github.com/sandertv/gophertunnel/minecraft/nbt"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// subChunkRequests is set to true to enable the sub-chunk request system. This can (likely) cause unexpected issues,
// but also solves issues with block entities such as item frames and lecterns as of v1.19.10.
const subChunkRequests = true
// ViewChunk ...
func (s *Session) ViewChunk(pos world.ChunkPos, dim world.Dimension, blockEntities map[cube.Pos]world.Block, c *chunk.Chunk) {
if !s.conn.ClientCacheEnabled() {
s.sendNetworkChunk(pos, dim, c, blockEntities)
return
}
s.sendBlobHashes(pos, dim, c, blockEntities)
}
// ViewSubChunks ...
func (s *Session) ViewSubChunks(centre world.SubChunkPos, offsets []protocol.SubChunkOffset, tx *world.Tx) {
r := tx.Range()
entries := make([]protocol.SubChunkEntry, 0, len(offsets))
transaction := make(map[uint64]struct{})
for _, offset := range offsets {
ind := int16(centre.Y()) + int16(offset[1]) - int16(r[0])>>4
if ind < 0 || ind > int16(r.Height()>>4) {
entries = append(entries, protocol.SubChunkEntry{Result: protocol.SubChunkResultIndexOutOfBounds, Offset: offset})
continue
}
col, ok := s.chunkLoader.Chunk(world.ChunkPos{
centre.X() + int32(offset[0]),
centre.Z() + int32(offset[2]),
})
if !ok {
entries = append(entries, protocol.SubChunkEntry{Result: protocol.SubChunkResultChunkNotFound, Offset: offset})
continue
}
entries = append(entries, s.subChunkEntry(offset, ind, col, transaction))
}
if s.conn.ClientCacheEnabled() && len(transaction) > 0 {
s.blobMu.Lock()
s.openChunkTransactions = append(s.openChunkTransactions, transaction)
s.blobMu.Unlock()
}
dim, _ := world.DimensionID(tx.World().Dimension())
s.writePacket(&packet.SubChunk{
Dimension: int32(dim),
Position: protocol.SubChunkPos(centre),
CacheEnabled: s.conn.ClientCacheEnabled(),
SubChunkEntries: entries,
})
}
func (s *Session) subChunkEntry(offset protocol.SubChunkOffset, ind int16, col *world.Column, transaction map[uint64]struct{}) protocol.SubChunkEntry {
chunkMap := col.HeightMap()
subMapType, subMap := byte(protocol.HeightMapDataHasData), make([]int8, 256)
higher, lower := true, true
for x := uint8(0); x < 16; x++ {
for z := uint8(0); z < 16; z++ {
y, i := chunkMap.At(x, z), (uint16(z)<<4)|uint16(x)
otherInd := col.SubIndex(y)
switch {
case otherInd > ind:
subMap[i], lower = 16, false
case otherInd < ind:
subMap[i], higher = -1, false
default:
subMap[i], lower, higher = int8(y-col.SubY(otherInd)), false, false
}
}
}
if higher {
subMapType, subMap = protocol.HeightMapDataTooHigh, nil
} else if lower {
subMapType, subMap = protocol.HeightMapDataTooLow, nil
}
sub := col.Sub()[ind]
if sub.Empty() {
return protocol.SubChunkEntry{
Result: protocol.SubChunkResultSuccessAllAir,
HeightMapType: subMapType,
HeightMapData: subMap,
RenderHeightMapType: subMapType,
RenderHeightMapData: subMap,
Offset: offset,
}
}
serialisedSubChunk := chunk.EncodeSubChunk(col.Chunk, chunk.NetworkEncoding, int(ind))
blockEntityBuf := bytes.NewBuffer(nil)
enc := nbt.NewEncoderWithEncoding(blockEntityBuf, nbt.NetworkLittleEndian)
for pos, b := range col.BlockEntities {
if n, ok := b.(world.NBTer); ok && col.SubIndex(int16(pos.Y())) == ind {
d := n.EncodeNBT()
d["x"], d["y"], d["z"] = int32(pos[0]), int32(pos[1]), int32(pos[2])
_ = enc.Encode(d)
}
}
entry := protocol.SubChunkEntry{
Result: protocol.SubChunkResultSuccess,
RawPayload: append(serialisedSubChunk, blockEntityBuf.Bytes()...),
HeightMapType: subMapType,
HeightMapData: subMap,
RenderHeightMapType: subMapType,
RenderHeightMapData: subMap,
Offset: offset,
}
if s.conn.ClientCacheEnabled() {
if hash := xxhash.Sum64(serialisedSubChunk); s.trackBlob(hash, serialisedSubChunk) {
transaction[hash] = struct{}{}
entry.BlobHash = hash
entry.RawPayload = blockEntityBuf.Bytes()
}
}
return entry
}
// dimensionID returns the dimension ID of the world that the session is in.
func (s *Session) dimensionID(dim world.Dimension) int32 {
d, _ := world.DimensionID(dim)
return int32(d)
}
// sendBlobHashes sends chunk blob hashes of the data of the chunk and stores the data in a map of blobs. Only
// data that the client doesn't yet have will be sent over the network.
func (s *Session) sendBlobHashes(pos world.ChunkPos, dim world.Dimension, c *chunk.Chunk, blockEntities map[cube.Pos]world.Block) {
if subChunkRequests {
biomes := chunk.EncodeBiomes(c, chunk.NetworkEncoding)
if hash := xxhash.Sum64(biomes); s.trackBlob(hash, biomes) {
s.writePacket(&packet.LevelChunk{
Dimension: s.dimensionID(dim),
SubChunkCount: protocol.SubChunkRequestModeLimited,
Position: protocol.ChunkPos(pos),
HighestSubChunk: c.HighestFilledSubChunk(),
BlobHashes: []uint64{hash},
RawPayload: []byte{0},
CacheEnabled: true,
})
return
}
}
var (
data = chunk.Encode(c, chunk.NetworkEncoding)
count = uint32(len(data.SubChunks))
blobs = append(data.SubChunks, data.Biomes)
hashes = make([]uint64, len(blobs))
m = make(map[uint64]struct{}, len(blobs))
)
for i, blob := range blobs {
h := xxhash.Sum64(blob)
hashes[i], m[h] = h, struct{}{}
}
s.blobMu.Lock()
s.openChunkTransactions = append(s.openChunkTransactions, m)
if l := len(s.blobs); l > 4096 {
s.blobMu.Unlock()
s.conf.Log.Error("too many blobs pending", "n", l)
return
}
for i := range hashes {
s.blobs[hashes[i]] = blobs[i]
}
s.blobMu.Unlock()
// Length of 1 byte for the border block count.
raw := bytes.NewBuffer(make([]byte, 1, 32))
enc := nbt.NewEncoderWithEncoding(raw, nbt.NetworkLittleEndian)
for bp, b := range blockEntities {
if n, ok := b.(world.NBTer); ok {
d := n.EncodeNBT()
d["x"], d["y"], d["z"] = int32(bp[0]), int32(bp[1]), int32(bp[2])
_ = enc.Encode(d)
}
}
s.writePacket(&packet.LevelChunk{
Dimension: s.dimensionID(dim),
Position: protocol.ChunkPos{pos.X(), pos.Z()},
SubChunkCount: count,
CacheEnabled: true,
BlobHashes: hashes,
RawPayload: raw.Bytes(),
})
}
// sendNetworkChunk sends a network encoded chunk to the client.
func (s *Session) sendNetworkChunk(pos world.ChunkPos, dim world.Dimension, c *chunk.Chunk, blockEntities map[cube.Pos]world.Block) {
if subChunkRequests {
s.writePacket(&packet.LevelChunk{
Dimension: s.dimensionID(dim),
SubChunkCount: protocol.SubChunkRequestModeLimited,
Position: protocol.ChunkPos(pos),
HighestSubChunk: c.HighestFilledSubChunk(),
RawPayload: append(chunk.EncodeBiomes(c, chunk.NetworkEncoding), 0),
})
return
}
data := chunk.Encode(c, chunk.NetworkEncoding)
chunkBuf := bytes.NewBuffer(nil)
for _, s := range data.SubChunks {
_, _ = chunkBuf.Write(s)
}
_, _ = chunkBuf.Write(data.Biomes)
// Length of 1 byte for the border block count.
chunkBuf.WriteByte(0)
enc := nbt.NewEncoderWithEncoding(chunkBuf, nbt.NetworkLittleEndian)
for bp, b := range blockEntities {
if n, ok := b.(world.NBTer); ok {
d := n.EncodeNBT()
d["x"], d["y"], d["z"] = int32(bp[0]), int32(bp[1]), int32(bp[2])
_ = enc.Encode(d)
}
}
s.writePacket(&packet.LevelChunk{
Dimension: s.dimensionID(dim),
Position: protocol.ChunkPos{pos.X(), pos.Z()},
SubChunkCount: uint32(len(data.SubChunks)),
RawPayload: append([]byte(nil), chunkBuf.Bytes()...),
})
}
// trackBlob attempts to track the given blob. If the player has too many pending blobs, it returns false and closes the
// connection.
func (s *Session) trackBlob(hash uint64, blob []byte) bool {
s.blobMu.Lock()
if l := len(s.blobs); l > 4096 {
s.blobMu.Unlock()
s.conf.Log.Error("too many blobs pending", "n", l)
return false
}
s.blobs[hash] = blob
s.blobMu.Unlock()
return true
}