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