1use valence_nbt::Compound;
2use valence_protocol::BlockState;
3use valence_registry::biome::BiomeId;
45use super::paletted_container::PalettedContainer;
67/// Common operations on chunks. Notable implementors are
8/// [`LoadedChunk`](super::loaded::LoadedChunk) and
9/// [`UnloadedChunk`](super::unloaded::UnloadedChunk).
10pub trait Chunk {
11/// Gets the height of this chunk in meters or blocks.
12fn height(&self) -> u32;
1314/// Gets the block at the provided position in this chunk. `x` and `z`
15 /// are in the range `0..16` while `y` is in the range `0..height`.
16 ///
17 /// # Panics
18 ///
19 /// May panic if the position is out of bounds.
20#[track_caller]
21fn block(&self, x: u32, y: u32, z: u32) -> BlockRef {
22 BlockRef {
23 state: self.block_state(x, y, z),
24 nbt: self.block_entity(x, y, z),
25 }
26 }
2728/// Sets the block at the provided position in this chunk. `x` and `z`
29 /// are in the range `0..16` while `y` is in the range `0..height`. The
30 /// previous block at the position is returned.
31 ///
32 /// # Panics
33 ///
34 /// May panic if the position is out of bounds.
35#[track_caller]
36fn set_block(&mut self, x: u32, y: u32, z: u32, block: impl IntoBlock) -> Block {
37let block = block.into_block();
38let state = self.set_block_state(x, y, z, block.state);
39let nbt = self.set_block_entity(x, y, z, block.nbt);
4041 Block { state, nbt }
42 }
4344/// Sets all the blocks in the entire chunk to the provided block.
45fn fill_blocks(&mut self, block: impl IntoBlock) {
46let block = block.into_block();
4748self.fill_block_states(block.state);
4950if block.nbt.is_some() {
51for x in 0..16 {
52for z in 0..16 {
53for y in 0..self.height() {
54self.set_block_entity(x, y, z, block.nbt.clone());
55 }
56 }
57 }
58 } else {
59self.clear_block_entities();
60 }
61 }
6263/// Gets the block state at the provided position in this chunk. `x` and `z`
64 /// are in the range `0..16` while `y` is in the range `0..height`.
65 ///
66 /// # Panics
67 ///
68 /// May panic if the position is out of bounds.
69#[track_caller]
70fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState;
7172/// Sets the block state at the provided position in this chunk. `x` and `z`
73 /// are in the range `0..16` while `y` is in the range `0..height`. The
74 /// previous block state at the position is returned.
75 ///
76 /// **NOTE:** This is a low-level function which may break expected
77 /// invariants for block entities. Prefer [`Self::set_block`] if performance
78 /// is not a concern.
79 ///
80 /// # Panics
81 ///
82 /// May panic if the position is out of bounds.
83#[track_caller]
84fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState;
8586/// Replaces all block states in the entire chunk with the provided block
87 /// state.
88 ///
89 /// **NOTE:** This is a low-level function which may break expected
90 /// invariants for block entities. Prefer [`Self::fill_blocks`] instead.
91fn fill_block_states(&mut self, block: BlockState) {
92for sect_y in 0..self.height() / 16 {
93self.fill_block_state_section(sect_y, block);
94 }
95 }
9697/// Replaces all the block states in a section with the provided block
98 /// state.
99 ///
100 /// **NOTE:** This is a low-level function which may break expected
101 /// invariants for block entities. Prefer [`Self::set_block`] if performance
102 /// is not a concern.
103 ///
104 /// # Panics
105 ///
106 /// May panic if the section offset is out of bounds.
107#[track_caller]
108fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState);
109110/// Gets the block entity at the provided position in this chunk. `x` and
111 /// `z` are in the range `0..16` while `y` is in the range `0..height`.
112 ///
113 /// # Panics
114 ///
115 /// May panic if the position is out of bounds.
116#[track_caller]
117fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound>;
118119/// Gets a mutable reference to the block entity at the provided position in
120 /// this chunk. `x` and `z` are in the range `0..16` while `y` is in the
121 /// range `0..height`.
122 ///
123 /// # Panics
124 ///
125 /// May panic if the position is out of bounds.
126#[track_caller]
127fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound>;
128129/// Sets the block entity at the provided position in this chunk. `x` and
130 /// `z` are in the range `0..16` while `y` is in the range `0..height`.
131 /// The previous block entity at the position is returned.
132 ///
133 /// **NOTE:** This is a low-level function which may break expected
134 /// invariants for block entities. Prefer [`Self::set_block`] if performance
135 /// is not a concern.
136 ///
137 /// # Panics
138 ///
139 /// May panic if the position is out of bounds.
140#[track_caller]
141fn set_block_entity(
142&mut self,
143 x: u32,
144 y: u32,
145 z: u32,
146 block_entity: Option<Compound>,
147 ) -> Option<Compound>;
148149/// Removes all block entities from the chunk.
150 ///
151 /// **NOTE:** This is a low-level function which may break expected
152 /// invariants for block entities. Prefer [`Self::set_block`] if performance
153 /// is not a concern.
154fn clear_block_entities(&mut self);
155156/// Gets the biome at the provided position in this chunk. `x` and `z` are
157 /// in the range `0..4` while `y` is in the range `0..height / 4`.
158 ///
159 /// Note that biomes are 4x4x4 segments of a chunk, so the xyz arguments to
160 /// this method differ from those to [`Self::block_state`] and
161 /// [`Self::block_entity`].
162 ///
163 /// # Panics
164 ///
165 /// May panic if the position is out of bounds.
166#[track_caller]
167fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId;
168169/// Sets the biome at the provided position in this chunk. The Previous
170 /// biome at the position is returned. `x` and `z` are in the range `0..4`
171 /// while `y` is in the range `0..height / 4`.
172 ///
173 /// Note that biomes are 4x4x4 segments of a chunk, so the xyz arguments to
174 /// this method differ from those to [`Self::block_state`] and
175 /// [`Self::block_entity`].
176 ///
177 /// # Panics
178 ///
179 /// May panic if the position is out of bounds.
180#[track_caller]
181fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId;
182183/// Sets all the biomes in the entire chunk to the provided biome.
184fn fill_biomes(&mut self, biome: BiomeId) {
185for sect_y in 0..self.height() / 16 {
186self.fill_biome_section(sect_y, biome);
187 }
188 }
189190/// Replaces all the biomes in a section with the provided biome.
191 ///
192 /// # Panics
193 ///
194 /// May panic if the section offset is out of bounds.
195#[track_caller]
196fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId);
197198/// Sets all blocks and biomes in this chunk to the default values. The
199 /// height of the chunk is not modified.
200fn clear(&mut self) {
201self.fill_block_states(BlockState::AIR);
202self.fill_biomes(BiomeId::default());
203self.clear_block_entities();
204 }
205206/// Attempts to optimize this chunk by reducing its memory usage or other
207 /// characteristics. This may be a relatively expensive operation.
208 ///
209 /// This method must not alter the semantics of the chunk in any observable
210 /// way.
211fn shrink_to_fit(&mut self);
212}
213214/// Represents a complete block, which is a pair of block state and optional NBT
215/// data for the block entity.
216#[derive(Clone, PartialEq, Default, Debug)]
217pub struct Block {
218pub state: BlockState,
219pub nbt: Option<Compound>,
220}
221222impl Block {
223pub const fn new(state: BlockState, nbt: Option<Compound>) -> Self {
224Self { state, nbt }
225 }
226}
227228/// Like [`Block`], but immutably referenced.
229#[derive(Copy, Clone, PartialEq, Default, Debug)]
230pub struct BlockRef<'a> {
231pub state: BlockState,
232pub nbt: Option<&'a Compound>,
233}
234235impl<'a> BlockRef<'a> {
236pub const fn new(state: BlockState, nbt: Option<&'a Compound>) -> Self {
237Self { state, nbt }
238 }
239}
240241pub trait IntoBlock {
242// TODO: parameterize this with block registry ref?
243fn into_block(self) -> Block;
244}
245246impl IntoBlock for Block {
247fn into_block(self) -> Block {
248self
249}
250}
251252impl IntoBlock for BlockRef<'_> {
253fn into_block(self) -> Block {
254 Block {
255 state: self.state,
256 nbt: self.nbt.cloned(),
257 }
258 }
259}
260261/// This will initialize the block with a new empty compound if the block state
262/// is associated with a block entity.
263impl IntoBlock for BlockState {
264fn into_block(self) -> Block {
265 Block {
266 state: self,
267 nbt: self.block_entity_kind().map(|_| Compound::new()),
268 }
269 }
270}
271272pub(super) const SECTION_BLOCK_COUNT: usize = 16 * 16 * 16;
273pub(super) const SECTION_BIOME_COUNT: usize = 4 * 4 * 4;
274275/// The maximum height of a chunk.
276pub const MAX_HEIGHT: u32 = 4096;
277278pub(super) type BlockStateContainer =
279 PalettedContainer<BlockState, SECTION_BLOCK_COUNT, { SECTION_BLOCK_COUNT / 2 }>;
280281pub(super) type BiomeContainer =
282 PalettedContainer<BiomeId, SECTION_BIOME_COUNT, { SECTION_BIOME_COUNT / 2 }>;
283284#[inline]
285#[track_caller]
286pub(super) fn check_block_oob(chunk: &impl Chunk, x: u32, y: u32, z: u32) {
287assert!(
288 x < 16 && y < chunk.height() && z < 16,
289"chunk block offsets of ({x}, {y}, {z}) are out of bounds"
290);
291}
292293#[inline]
294#[track_caller]
295pub(super) fn check_biome_oob(chunk: &impl Chunk, x: u32, y: u32, z: u32) {
296assert!(
297 x < 4 && y < chunk.height() / 4 && z < 4,
298"chunk biome offsets of ({x}, {y}, {z}) are out of bounds"
299);
300}
301302#[inline]
303#[track_caller]
304pub(super) fn check_section_oob(chunk: &impl Chunk, sect_y: u32) {
305assert!(
306 sect_y < chunk.height() / 16,
307"chunk section offset of {sect_y} is out of bounds"
308);
309}
310311/// Returns the minimum number of bits needed to represent the integer `n`.
312pub(super) const fn bit_width(n: usize) -> usize {
313 (usize::BITS - n.leading_zeros()) as usize
314}
315316#[cfg(test)]
317mod tests {
318use super::*;
319use crate::layer::chunk::{LoadedChunk, UnloadedChunk};
320321#[test]
322fn chunk_get_set() {
323fn check(mut chunk: impl Chunk) {
324assert_eq!(
325 chunk.set_block_state(1, 2, 3, BlockState::CHAIN),
326 BlockState::AIR
327 );
328assert_eq!(
329 chunk.set_block_state(1, 2, 3, BlockState::AIR),
330 BlockState::CHAIN
331 );
332333assert_eq!(chunk.set_block_entity(1, 2, 3, Some(Compound::new())), None);
334assert_eq!(chunk.set_block_entity(1, 2, 3, None), Some(Compound::new()));
335 }
336337let unloaded = UnloadedChunk::with_height(512);
338let loaded = LoadedChunk::new(512);
339340 check(unloaded);
341 check(loaded);
342 }
343344#[cfg(debug_assertions)]
345 #[test]
346 #[should_panic]
347fn chunk_debug_oob_0() {
348let mut chunk = UnloadedChunk::with_height(512);
349 chunk.set_block_state(0, 0, 16, BlockState::AIR);
350 }
351352#[cfg(debug_assertions)]
353 #[test]
354 #[should_panic]
355fn chunk_debug_oob_1() {
356let mut chunk = LoadedChunk::new(512);
357 chunk.set_block_state(0, 0, 16, BlockState::AIR);
358 }
359360#[cfg(debug_assertions)]
361 #[test]
362 #[should_panic]
363fn chunk_debug_oob_2() {
364let mut chunk = UnloadedChunk::with_height(512);
365 chunk.set_block_entity(0, 0, 16, None);
366 }
367368#[cfg(debug_assertions)]
369 #[test]
370 #[should_panic]
371fn chunk_debug_oob_3() {
372let mut chunk = LoadedChunk::new(512);
373 chunk.set_block_entity(0, 0, 16, None);
374 }
375376#[cfg(debug_assertions)]
377 #[test]
378 #[should_panic]
379fn chunk_debug_oob_4() {
380let mut chunk = UnloadedChunk::with_height(512);
381 chunk.set_biome(0, 0, 4, BiomeId::DEFAULT);
382 }
383384#[cfg(debug_assertions)]
385 #[test]
386 #[should_panic]
387fn chunk_debug_oob_5() {
388let mut chunk = LoadedChunk::new(512);
389 chunk.set_biome(0, 0, 4, BiomeId::DEFAULT);
390 }
391392#[cfg(debug_assertions)]
393 #[test]
394 #[should_panic]
395fn chunk_debug_oob_6() {
396let mut chunk = UnloadedChunk::with_height(512);
397 chunk.fill_block_state_section(chunk.height() / 16, BlockState::AIR);
398 }
399400#[cfg(debug_assertions)]
401 #[test]
402 #[should_panic]
403fn chunk_debug_oob_7() {
404let mut chunk = LoadedChunk::new(512);
405 chunk.fill_block_state_section(chunk.height() / 16, BlockState::AIR);
406 }
407408#[cfg(debug_assertions)]
409 #[test]
410 #[should_panic]
411fn chunk_debug_oob_8() {
412let mut chunk = UnloadedChunk::with_height(512);
413 chunk.fill_biome_section(chunk.height() / 16, BiomeId::DEFAULT);
414 }
415416#[cfg(debug_assertions)]
417 #[test]
418 #[should_panic]
419fn chunk_debug_oob_9() {
420let mut chunk = LoadedChunk::new(512);
421 chunk.fill_biome_section(chunk.height() / 16, BiomeId::DEFAULT);
422 }
423}