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