1use azalea_block::{BlockState, BlockStates};
2use azalea_core::position::{BlockPos, ChunkPos};
3
4use crate::{Chunk, ChunkStorage, Instance, iterators::ChunkIterator, palette::Palette};
5
6impl Instance {
7 pub fn find_block(
21 &self,
22 nearest_to: impl Into<BlockPos>,
23 block_states: &BlockStates,
24 ) -> Option<BlockPos> {
25 let nearest_to: BlockPos = nearest_to.into();
29 let start_chunk: ChunkPos = (&nearest_to).into();
30 let mut iter = ChunkIterator::new(start_chunk, 32);
31
32 let mut nearest_found_pos: Option<BlockPos> = None;
33 let mut nearest_found_distance = 0;
34
35 while let Some(chunk_pos) = iter.next() {
37 let Some(chunk) = self.chunks.get(&chunk_pos) else {
38 continue;
44 };
45
46 find_blocks_in_chunk(
47 block_states,
48 chunk_pos,
49 &chunk.read(),
50 self.chunks.min_y,
51 |this_block_pos| {
52 let this_block_distance = (nearest_to - this_block_pos).length_manhattan();
53 if nearest_found_pos.is_none() || this_block_distance < nearest_found_distance {
55 nearest_found_pos = Some(this_block_pos);
56 nearest_found_distance = this_block_distance;
57 }
58 },
59 );
60
61 if let Some(nearest_found_pos) = nearest_found_pos {
62 let required_chunk_distance = u32::max(
65 u32::max(
66 (chunk_pos.x - start_chunk.x).unsigned_abs(),
67 (chunk_pos.z - start_chunk.z).unsigned_abs(),
68 ),
69 (nearest_to.y - nearest_found_pos.y)
70 .unsigned_abs()
71 .div_ceil(16),
72 ) + 1;
73 let nearest_chunk_distance = iter.layer;
74
75 if nearest_chunk_distance > required_chunk_distance {
78 return Some(nearest_found_pos);
79 }
80 }
81 }
82
83 if nearest_found_pos.is_some() {
84 nearest_found_pos
85 } else {
86 None
87 }
88 }
89
90 pub fn find_blocks<'a>(
98 &'a self,
99 nearest_to: impl Into<BlockPos>,
100 block_states: &'a BlockStates,
101 ) -> FindBlocks<'a> {
102 FindBlocks::new(nearest_to.into(), &self.chunks, block_states)
103 }
104}
105
106pub struct FindBlocks<'a> {
107 nearest_to: BlockPos,
108 start_chunk: ChunkPos,
109 chunk_iterator: ChunkIterator,
110 chunks: &'a ChunkStorage,
111 block_states: &'a BlockStates,
112
113 queued: Vec<BlockPos>,
114}
115
116impl<'a> FindBlocks<'a> {
117 pub fn new(
118 nearest_to: BlockPos,
119 chunks: &'a ChunkStorage,
120 block_states: &'a BlockStates,
121 ) -> Self {
122 let start_chunk: ChunkPos = (&nearest_to).into();
123 Self {
124 nearest_to,
125 start_chunk,
126 chunk_iterator: ChunkIterator::new(start_chunk, 32),
127 chunks,
128 block_states,
129
130 queued: Vec::new(),
131 }
132 }
133}
134
135impl Iterator for FindBlocks<'_> {
136 type Item = BlockPos;
137
138 fn next(&mut self) -> Option<Self::Item> {
139 if let Some(queued) = self.queued.pop() {
140 return Some(queued);
141 }
142
143 let mut found = Vec::new();
144
145 let mut nearest_found_pos: Option<BlockPos> = None;
146 let mut nearest_found_distance = 0;
147
148 while let Some(chunk_pos) = self.chunk_iterator.next() {
149 let Some(chunk) = self.chunks.get(&chunk_pos) else {
150 continue;
156 };
157
158 find_blocks_in_chunk(
159 self.block_states,
160 chunk_pos,
161 &chunk.read(),
162 self.chunks.min_y,
163 |this_block_pos| {
164 let this_block_distance = (self.nearest_to - this_block_pos).length_manhattan();
165
166 found.push((this_block_pos, this_block_distance));
167
168 if nearest_found_pos.is_none() || this_block_distance < nearest_found_distance {
169 nearest_found_pos = Some(this_block_pos);
170 nearest_found_distance = this_block_distance;
171 }
172 },
173 );
174
175 if let Some(nearest_found_pos) = nearest_found_pos {
176 let required_chunk_distance = u32::max(
179 u32::max(
180 (chunk_pos.x - self.start_chunk.x).unsigned_abs(),
181 (chunk_pos.z - self.start_chunk.z).unsigned_abs(),
182 ),
183 (self.nearest_to.y - nearest_found_pos.y)
184 .unsigned_abs()
185 .div_ceil(16),
186 ) + 1;
187 let nearest_chunk_distance = self.chunk_iterator.layer;
188
189 if nearest_chunk_distance > required_chunk_distance {
192 found.sort_unstable_by_key(|(_, distance)| u32::MAX - distance);
194
195 self.queued = found.into_iter().map(|(pos, _)| pos).collect();
196 return self.queued.pop();
197 }
198 }
199 }
200
201 None
202 }
203}
204
205pub fn find_blocks_in_chunk(
211 block_states: &BlockStates,
212 chunk_pos: ChunkPos,
213 chunk: &Chunk,
214 min_y: i32,
215 mut cb: impl FnMut(BlockPos),
216) {
217 for (section_index, section) in chunk.sections.iter().enumerate() {
218 let maybe_has_block = palette_maybe_has_block(§ion.states.palette, block_states);
219 if !maybe_has_block {
220 continue;
221 }
222
223 for i in 0..4096 {
224 let block_state = section.states.get_at_index(i);
225
226 if block_states.contains(&block_state) {
227 let section_pos = section.states.coords_from_index(i);
228 let (x, y, z) = (
229 chunk_pos.x * 16 + (section_pos.x as i32),
230 min_y + (section_index * 16) as i32 + section_pos.y as i32,
231 chunk_pos.z * 16 + (section_pos.z as i32),
232 );
233 let this_block_pos = BlockPos { x, y, z };
234
235 cb(this_block_pos);
236 }
237 }
238 }
239}
240
241fn palette_maybe_has_block(palette: &Palette<BlockState>, block_states: &BlockStates) -> bool {
242 match &palette {
243 Palette::SingleValue(id) => block_states.contains(id),
244 Palette::Linear(ids) => ids.iter().any(|id| block_states.contains(id)),
245 Palette::Hashmap(ids) => ids.iter().any(|id| block_states.contains(id)),
246 Palette::Global => true,
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use azalea_registry::Block;
253
254 use super::*;
255 use crate::{Chunk, PartialChunkStorage};
256
257 #[test]
258 fn find_block() {
259 let mut instance = Instance::default();
260
261 let chunk_storage = &mut instance.chunks;
262 let mut partial_chunk_storage = PartialChunkStorage::default();
263
264 partial_chunk_storage.set(
267 &ChunkPos { x: 0, z: 0 },
268 Some(Chunk::default()),
269 chunk_storage,
270 );
271 partial_chunk_storage.set(
272 &ChunkPos { x: 1, z: 0 },
273 Some(Chunk::default()),
274 chunk_storage,
275 );
276
277 chunk_storage.set_block_state(&BlockPos { x: 17, y: 0, z: 0 }, Block::Stone.into());
278 chunk_storage.set_block_state(&BlockPos { x: 0, y: 18, z: 0 }, Block::Stone.into());
279
280 let pos = instance.find_block(BlockPos { x: 0, y: 0, z: 0 }, &Block::Stone.into());
281 assert_eq!(pos, Some(BlockPos { x: 17, y: 0, z: 0 }));
282 }
283
284 #[test]
285 fn find_block_next_to_chunk_border() {
286 let mut instance = Instance::default();
287
288 let chunk_storage = &mut instance.chunks;
289 let mut partial_chunk_storage = PartialChunkStorage::default();
290
291 partial_chunk_storage.set(
294 &ChunkPos { x: -1, z: 0 },
295 Some(Chunk::default()),
296 chunk_storage,
297 );
298 partial_chunk_storage.set(
299 &ChunkPos { x: 0, z: 0 },
300 Some(Chunk::default()),
301 chunk_storage,
302 );
303
304 chunk_storage.set_block_state(&BlockPos { x: -1, y: 0, z: 0 }, Block::Stone.into());
305 chunk_storage.set_block_state(&BlockPos { x: 15, y: 0, z: 0 }, Block::Stone.into());
306
307 let pos = instance.find_block(BlockPos { x: 0, y: 0, z: 0 }, &Block::Stone.into());
308 assert_eq!(pos, Some(BlockPos { x: -1, y: 0, z: 0 }));
309 }
310}