1pub mod partial;
2pub mod storage;
3
4use std::{
5 collections::HashMap,
6 fmt::Debug,
7 io,
8 io::{Cursor, Write},
9};
10
11use azalea_block::block_state::{BlockState, BlockStateIntegerRepr};
12use azalea_buf::{AzBuf, BufReadError};
13use azalea_core::{
14 heightmap_kind::HeightmapKind,
15 position::{ChunkBiomePos, ChunkBlockPos, ChunkSectionBiomePos, ChunkSectionBlockPos},
16};
17use azalea_registry::data::Biome;
18use tracing::warn;
19
20use crate::{heightmap::Heightmap, palette::PalettedContainer};
21
22const SECTION_HEIGHT: u32 = 16;
23
24#[derive(Debug)]
32pub struct Chunk {
33 pub sections: Box<[Section]>,
34 pub heightmaps: HashMap<HeightmapKind, Heightmap>,
38}
39
40#[derive(Clone, Debug, Default, PartialEq)]
42pub struct Section {
43 pub block_count: u16,
49 pub fluid_count: u16,
53 pub states: PalettedContainer<BlockState>,
54 pub biomes: PalettedContainer<Biome>,
55}
56
57pub fn calculate_chunk_storage_range(view_distance: u32) -> u32 {
61 u32::max(view_distance, 2) + 3
62}
63
64impl Default for Chunk {
65 fn default() -> Self {
66 Chunk {
67 sections: vec![Section::default(); (384 / 16) as usize].into(),
68 heightmaps: HashMap::new(),
69 }
70 }
71}
72
73impl Chunk {
74 pub fn read_with_dimension_height(
75 buf: &mut Cursor<&[u8]>,
76 dimension_height: u32,
77 min_y: i32,
78 heightmaps_data: &[(HeightmapKind, Box<[u64]>)],
79 ) -> Result<Self, BufReadError> {
80 let section_count = dimension_height / SECTION_HEIGHT;
81 let mut sections = Vec::with_capacity(section_count as usize);
82 for _ in 0..section_count {
83 let section = Section::azalea_read(buf)?;
84 sections.push(section);
85 }
86 let sections = sections.into_boxed_slice();
87
88 let mut heightmaps = HashMap::new();
89 for (kind, data) in heightmaps_data {
90 let data = data.clone();
91 let heightmap = Heightmap::new(*kind, dimension_height, min_y, data);
92 heightmaps.insert(*kind, heightmap);
93 }
94
95 Ok(Chunk {
96 sections,
97 heightmaps,
98 })
99 }
100
101 pub fn get_block_state(&self, pos: &ChunkBlockPos, min_y: i32) -> Option<BlockState> {
102 get_block_state_from_sections(&self.sections, pos, min_y)
103 }
104
105 #[must_use = "Use Chunk::set_block_state instead if you don't need the previous state"]
106 pub fn get_and_set_block_state(
107 &mut self,
108 pos: &ChunkBlockPos,
109 state: BlockState,
110 min_y: i32,
111 ) -> BlockState {
112 let section_index = section_index(pos.y, min_y);
113 let Some(section) = self.sections.get_mut(section_index as usize) else {
114 warn!(
115 "Tried to get and set block state {state:?} at out-of-bounds relative chunk position {pos:?}",
116 );
117 return BlockState::AIR;
118 };
119 let chunk_section_pos = ChunkSectionBlockPos::from(pos);
120 let previous_state = section.get_and_set_block_state(chunk_section_pos, state);
121
122 for heightmap in self.heightmaps.values_mut() {
123 heightmap.update(pos, state, &self.sections);
124 }
125
126 previous_state
127 }
128
129 pub fn set_block_state(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) {
130 let section_index = section_index(pos.y, min_y);
131 let Some(section) = self.sections.get_mut(section_index as usize) else {
132 warn!(
133 "Tried to set block state {state:?} at out-of-bounds relative chunk position {pos:?}",
134 );
135 return;
136 };
137 let chunk_section_pos = ChunkSectionBlockPos::from(pos);
138 section.get_and_set_block_state(chunk_section_pos, state);
139
140 for heightmap in self.heightmaps.values_mut() {
141 heightmap.update(pos, state, &self.sections);
142 }
143 }
144
145 pub fn get_biome(&self, pos: ChunkBiomePos, min_y: i32) -> Option<Biome> {
147 if pos.y < min_y {
148 return None;
150 }
151 let section_index = section_index(pos.y, min_y);
152 let Some(section) = self.sections.get(section_index as usize) else {
153 warn!("Tried to get biome at out-of-bounds relative chunk position {pos:?}",);
154 return None;
155 };
156 let chunk_section_pos = ChunkSectionBiomePos::from(pos);
157 Some(section.get_biome(chunk_section_pos))
158 }
159}
160
161#[inline]
164pub fn get_block_state_from_sections(
165 sections: &[Section],
166 pos: &ChunkBlockPos,
167 min_y: i32,
168) -> Option<BlockState> {
169 if pos.y < min_y {
170 return None;
172 }
173 let section_index = section_index(pos.y, min_y) as usize;
174 if section_index >= sections.len() {
175 return None;
177 };
178 let section = §ions[section_index];
179
180 let chunk_section_pos = ChunkSectionBlockPos::from(pos);
181 Some(section.get_block_state(chunk_section_pos))
182}
183
184impl AzBuf for Section {
185 fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
186 let block_count = u16::azalea_read(buf)?;
187 let fluid_count = u16::azalea_read(buf)?;
188
189 let states = PalettedContainer::<BlockState>::read(buf)?;
197
198 for i in 0..states.storage.size() {
199 if !BlockState::is_valid_state(states.storage.get(i) as BlockStateIntegerRepr) {
200 return Err(BufReadError::Custom(format!(
201 "Invalid block state {} (index {i}) found in section.",
202 states.storage.get(i)
203 )));
204 }
205 }
206
207 let biomes = PalettedContainer::<Biome>::read(buf)?;
208 Ok(Section {
209 block_count,
210 fluid_count,
211 states,
212 biomes,
213 })
214 }
215 fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
216 self.block_count.azalea_write(buf)?;
217 self.fluid_count.azalea_write(buf)?;
218 self.states.write(buf)?;
219 self.biomes.write(buf)?;
220 Ok(())
221 }
222}
223
224impl Section {
225 pub fn get_block_state(&self, pos: ChunkSectionBlockPos) -> BlockState {
226 self.states.get(pos)
227 }
228 pub fn get_and_set_block_state(
229 &mut self,
230 pos: ChunkSectionBlockPos,
231 state: BlockState,
232 ) -> BlockState {
233 let previous_state = self.states.get_and_set(pos, state);
234
235 if previous_state.is_air() && !state.is_air() {
236 self.block_count += 1;
237 } else if !previous_state.is_air() && state.is_air() {
238 self.block_count -= 1;
239 }
240
241 previous_state
242 }
243
244 pub fn get_biome(&self, pos: ChunkSectionBiomePos) -> Biome {
245 self.biomes.get(pos)
246 }
247 pub fn set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) {
248 self.biomes.set(pos, biome);
249 }
250 pub fn get_and_set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) -> Biome {
251 self.biomes.get_and_set(pos, biome)
252 }
253}
254
255#[inline]
258pub fn section_index(y: i32, min_y: i32) -> u32 {
259 if y < min_y {
260 #[cfg(debug_assertions)]
261 tracing::warn!("y ({y}) must be at least {min_y}");
262 #[cfg(not(debug_assertions))]
263 tracing::trace!("y ({y}) must be at least {min_y}")
264 };
265 let min_section_index = min_y >> 4;
266 ((y >> 4) - min_section_index) as u32
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use crate::palette::SectionPos;
273
274 #[test]
275 fn test_section_index() {
276 assert_eq!(section_index(0, 0), 0);
277 assert_eq!(section_index(128, 0), 8);
278 assert_eq!(section_index(127, 0), 7);
279 assert_eq!(section_index(0, -64), 4);
280 assert_eq!(section_index(-64, -64), 0);
281 assert_eq!(section_index(-49, -64), 0);
282 assert_eq!(section_index(-48, -64), 1);
283 assert_eq!(section_index(128, -64), 12);
284 }
285
286 #[test]
287 fn serialize_and_deserialize_section() {
288 let mut states = PalettedContainer::new();
289
290 states.set(
291 SectionPos::new(1, 2, 3),
292 BlockState::try_from(BlockState::MAX_STATE).unwrap(),
293 );
294 states.set(
295 SectionPos::new(4, 5, 6),
296 BlockState::try_from(BlockState::MAX_STATE).unwrap(),
297 );
298 let biomes = PalettedContainer::new();
299 let section = Section {
300 block_count: 2,
301 fluid_count: 0,
302 states,
303 biomes,
304 };
305
306 let mut buf = Vec::new();
307 section.azalea_write(&mut buf).unwrap();
308
309 let mut cur = Cursor::new(buf.as_slice());
310 let deserialized_section = Section::azalea_read(&mut cur).unwrap();
311 assert_eq!(cur.position(), buf.len() as u64);
312
313 assert_eq!(section, deserialized_section);
314 }
315}