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  }