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