github.com/df-mc/dragonfly@v0.9.13/server/world/chunk/chunk.go (about) 1 package chunk 2 3 import ( 4 "github.com/df-mc/dragonfly/server/block/cube" 5 ) 6 7 // Chunk is a segment in the world with a size of 16x16x256 blocks. A chunk contains multiple sub chunks 8 // and stores other information such as biomes. 9 // It is not safe to call methods on Chunk simultaneously from multiple goroutines. 10 type Chunk struct { 11 // r holds the (vertical) range of the Chunk. It includes both the minimum and maximum coordinates. 12 r cube.Range 13 // air is the runtime ID of air. 14 air uint32 15 // recalculateHeightMap is true if the chunk's height map should be recalculated on the next call to the HeightMap 16 // function. 17 recalculateHeightMap bool 18 // heightMap is the height map of the chunk. 19 heightMap HeightMap 20 // sub holds all sub chunks part of the chunk. The pointers held by the array are nil if no sub chunk is 21 // allocated at the indices. 22 sub []*SubChunk 23 // biomes is an array of biome IDs. There is one biome ID for every column in the chunk. 24 biomes []*PalettedStorage 25 } 26 27 // New initialises a new chunk and returns it, so that it may be used. 28 func New(air uint32, r cube.Range) *Chunk { 29 n := (r.Height() >> 4) + 1 30 sub, biomes := make([]*SubChunk, n), make([]*PalettedStorage, n) 31 for i := 0; i < n; i++ { 32 sub[i] = NewSubChunk(air) 33 biomes[i] = emptyStorage(0) 34 } 35 return &Chunk{ 36 r: r, 37 air: air, 38 sub: sub, 39 biomes: biomes, 40 recalculateHeightMap: true, 41 heightMap: make(HeightMap, 256), 42 } 43 } 44 45 // Range returns the cube.Range of the Chunk as passed to New. 46 func (chunk *Chunk) Range() cube.Range { 47 return chunk.r 48 } 49 50 // Sub returns a list of all sub chunks present in the chunk. 51 func (chunk *Chunk) Sub() []*SubChunk { 52 return chunk.sub 53 } 54 55 // Block returns the runtime ID of the block at a given x, y and z in a chunk at the given layer. If no 56 // sub chunk exists at the given y, the block is assumed to be air. 57 func (chunk *Chunk) Block(x uint8, y int16, z uint8, layer uint8) uint32 { 58 sub := chunk.SubChunk(y) 59 if sub.Empty() || uint8(len(sub.storages)) <= layer { 60 return chunk.air 61 } 62 return sub.storages[layer].At(x, uint8(y), z) 63 } 64 65 // SetBlock sets the runtime ID of a block at a given x, y and z in a chunk at the given layer. If no 66 // SubChunk exists at the given y, a new SubChunk is created and the block is set. 67 func (chunk *Chunk) SetBlock(x uint8, y int16, z uint8, layer uint8, block uint32) { 68 sub := chunk.sub[chunk.SubIndex(y)] 69 if uint8(len(sub.storages)) <= layer && block == chunk.air { 70 // Air was set at n layer, but there were less than n layers, so there already was air there. 71 // Don't do anything with this, just return. 72 return 73 } 74 sub.Layer(layer).Set(x, uint8(y), z, block) 75 chunk.recalculateHeightMap = true 76 } 77 78 // Biome returns the biome ID at a specific column in the chunk. 79 func (chunk *Chunk) Biome(x uint8, y int16, z uint8) uint32 { 80 return chunk.biomes[chunk.SubIndex(y)].At(x, uint8(y), z) 81 } 82 83 // SetBiome sets the biome ID at a specific column in the chunk. 84 func (chunk *Chunk) SetBiome(x uint8, y int16, z uint8, biome uint32) { 85 chunk.biomes[chunk.SubIndex(y)].Set(x, uint8(y), z, biome) 86 } 87 88 // Light returns the light level at a specific position in the chunk. 89 func (chunk *Chunk) Light(x uint8, y int16, z uint8) uint8 { 90 ux, uy, uz, sub := x&0xf, uint8(y&0xf), z&0xf, chunk.SubChunk(y) 91 sky := sub.SkyLight(ux, uy, uz) 92 if sky == 15 { 93 // The skylight was already on the maximum value, so return it without checking block light. 94 return sky 95 } 96 if block := sub.BlockLight(ux, uy, uz); block > sky { 97 return block 98 } 99 return sky 100 } 101 102 // SkyLight returns the skylight level at a specific position in the chunk. 103 func (chunk *Chunk) SkyLight(x uint8, y int16, z uint8) uint8 { 104 return chunk.SubChunk(y).SkyLight(x&15, uint8(y&15), z&15) 105 } 106 107 // HighestLightBlocker iterates from the highest non-empty sub chunk downwards to find the Y value of the 108 // highest block that completely blocks any light from going through. If none is found, the value returned is 109 // the minimum height. 110 func (chunk *Chunk) HighestLightBlocker(x, z uint8) int16 { 111 return chunk.highestLightBlocker(x, z, false) 112 } 113 114 // highestLightBlocker iterates from the highest non-empty sub chunk downwards 115 // to find the Y value of the highest block that completely blocks any light 116 // from going through. If none is found, the value returned is the minimum 117 // height. If addOne is true, one is added to the Y returned if a block was 118 // found. 119 func (chunk *Chunk) highestLightBlocker(x, z uint8, addOne bool) int16 { 120 var plus int16 121 if addOne { 122 plus++ 123 } 124 for index := int16(len(chunk.sub) - 1); index >= 0; index-- { 125 if sub := chunk.sub[index]; !sub.Empty() { 126 for y := 15; y >= 0; y-- { 127 if FilteringBlocks[sub.storages[0].At(x, uint8(y), z)] == 15 { 128 return int16(y) | chunk.SubY(index) + plus 129 } 130 } 131 } 132 } 133 return int16(chunk.r[0]) 134 } 135 136 // HighestBlock iterates from the highest non-empty sub chunk downwards to find the Y value of the highest 137 // non-air block at an x and z. If no blocks are present in the column, the minimum height is returned. 138 func (chunk *Chunk) HighestBlock(x, z uint8) int16 { 139 for index := int16(len(chunk.sub) - 1); index >= 0; index-- { 140 if sub := chunk.sub[index]; !sub.Empty() { 141 for y := 15; y >= 0; y-- { 142 if rid := sub.storages[0].At(x, uint8(y), z); rid != chunk.air { 143 return int16(y) | chunk.SubY(index) 144 } 145 } 146 } 147 } 148 return int16(chunk.r[0]) 149 } 150 151 // HeightMap returns the height map of the chunk. If the chunk is edited, the height map will be recalculated on the 152 // next call to this function. 153 func (chunk *Chunk) HeightMap() HeightMap { 154 if chunk.recalculateHeightMap { 155 for x := uint8(0); x < 16; x++ { 156 for z := uint8(0); z < 16; z++ { 157 chunk.heightMap.Set(x, z, chunk.highestLightBlocker(x, z, true)) 158 } 159 } 160 chunk.recalculateHeightMap = false 161 } 162 return chunk.heightMap 163 } 164 165 // Compact compacts the chunk as much as possible, getting rid of any sub chunks that are empty, and compacts 166 // all storages in the sub chunks to occupy as little space as possible. 167 // Compact should be called right before the chunk is saved in order to optimise the storage space. 168 func (chunk *Chunk) Compact() { 169 for i := range chunk.sub { 170 chunk.sub[i].compact() 171 } 172 } 173 174 // SubChunk finds the correct SubChunk in the Chunk by a Y value. 175 func (chunk *Chunk) SubChunk(y int16) *SubChunk { 176 return chunk.sub[chunk.SubIndex(y)] 177 } 178 179 // SubIndex returns the sub chunk Y index matching the y value passed. 180 func (chunk *Chunk) SubIndex(y int16) int16 { 181 return (y - int16(chunk.r[0])) >> 4 182 } 183 184 // SubY returns the sub chunk Y value matching the index passed. 185 func (chunk *Chunk) SubY(index int16) int16 { 186 return (index << 4) + int16(chunk.r[0]) 187 } 188 189 // HighestFilledSubChunk returns the index of the highest sub chunk in the chunk 190 // that has any blocks in it. 0 is returned if no subchunks have any blocks. 191 func (chunk *Chunk) HighestFilledSubChunk() uint16 { 192 highest := uint16(0) 193 for highest = uint16(len(chunk.sub) - 1); highest > 0; highest-- { 194 if !chunk.sub[highest].Empty() { 195 break 196 } 197 } 198 return highest 199 }