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