1use super::*;
2use crate::chunk::Chunk;
3
4mod neighbor_updater;
5
6pub(in crate::world) use neighbor_updater::{CollectingNeighborUpdater, ShapeUpdate};
7
8static LARGE_BLOCK_REGION_WARNING_EMITTED: AtomicBool = AtomicBool::new(false);
9
10impl World {
11 #[must_use]
15 pub fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
16 if !self.is_in_valid_bounds(pos) {
17 return REGISTRY.blocks.get_base_state_id(&vanilla_blocks::VOID_AIR);
18 }
19
20 let chunk_pos = Self::chunk_pos_for_block(pos);
21 self.chunk_map
22 .with_full_chunk(chunk_pos, |chunk| chunk.get_block_state(pos))
23 .unwrap_or_else(|| REGISTRY.blocks.get_base_state_id(&vanilla_blocks::AIR))
24 }
25
26 pub fn light_value_at(&self, layer: LightLayer, pos: BlockPos) -> u8 {
28 if layer == LightLayer::Sky && !self.dimension_type.has_skylight {
29 return 0;
30 }
31 if !self.is_in_valid_bounds_horizontal(pos) {
32 return self.default_light_value(layer);
33 }
34
35 let chunk_pos = Self::chunk_pos_for_block(pos);
36 self.chunk_map
37 .with_chunk_at_status(chunk_pos, ChunkStatus::Light, |chunk| {
38 let light = chunk.light();
39 light.get_light_value(layer, pos)
40 })
41 .unwrap_or_else(|| self.default_light_value(layer))
42 }
43
44 pub(crate) fn is_entity_ticking_chunk_loaded(&self, pos: BlockPos) -> bool {
45 self.chunk_map
46 .is_entity_ticking_full_chunk_loaded(Self::chunk_pos_for_block(pos))
47 }
48
49 pub(crate) fn is_full_chunk_loaded_at(&self, pos: BlockPos) -> bool {
50 self.chunk_map
51 .with_full_chunk(Self::chunk_pos_for_block(pos), |_| ())
52 .is_some()
53 }
54
55 pub(crate) fn queue_light_change_after_block_set(
56 &self,
57 pos: BlockPos,
58 old_state: BlockStateId,
59 new_state: BlockStateId,
60 empty_section_change: Option<LightSectionEmptinessChange>,
61 ) {
62 let light_properties_changed = has_different_light_properties(old_state, new_state);
63 if !light_properties_changed && empty_section_change.is_none() {
64 return;
65 }
66
67 self.chunk_map
68 .queue_light_change(pos, light_properties_changed, empty_section_change);
69 }
70
71 pub(super) const fn default_light_value(&self, layer: LightLayer) -> u8 {
72 match layer {
73 LightLayer::Sky if self.dimension_type.has_skylight => MAX_LIGHT_LEVEL,
74 LightLayer::Sky | LightLayer::Block => 0,
75 }
76 }
77
78 #[must_use]
85 pub fn block_states_in_aabb_are_air(&self, aabb: WorldAabb) -> bool {
86 let min_x = aabb.min_x().floor() as i32;
87 let min_y = aabb.min_y().floor() as i32;
88 let min_z = aabb.min_z().floor() as i32;
89 let max_x = aabb.max_x().floor() as i32;
90 let max_y = aabb.max_y().floor() as i32;
91 let max_z = aabb.max_z().floor() as i32;
92
93 let bounds = BlockRegionBounds::from_corners(
94 BlockPos::new(min_x, min_y, min_z),
95 BlockPos::new(max_x, max_y, max_z),
96 );
97
98 let streaming_read = || {
99 for y in min_y..=max_y {
100 for z in min_z..=max_z {
101 for x in min_x..=max_x {
102 if !self.get_block_state(BlockPos::new(x, y, z)).is_air() {
103 return false;
104 }
105 }
106 }
107 }
108 true
109 };
110
111 let Some(all_air) = self.try_with_block_region(bounds, |region| {
112 for y in min_y..=max_y {
113 for z in min_z..=max_z {
114 for x in min_x..=max_x {
115 let Some(state) = region.get_block_state(BlockPos::new(x, y, z)) else {
116 return false;
117 };
118 if !state.is_air() {
119 return false;
120 }
121 }
122 }
123 }
124 true
125 }) else {
126 if !LARGE_BLOCK_REGION_WARNING_EMITTED.swap(true, Ordering::Relaxed) {
127 tracing::warn!(
128 min_x,
129 min_y,
130 min_z,
131 max_x,
132 max_y,
133 max_z,
134 max_workset_slots = MAX_BLOCK_REGION_WORKSET_SLOTS,
135 "Block-state AABB exceeds the bulk-read limit; using streaming reads"
136 );
137 }
138 return streaming_read();
139 };
140 all_air
141 }
142
143 pub fn set_block(
153 self: &Arc<Self>,
154 pos: BlockPos,
155 block_state: BlockStateId,
156 flags: UpdateFlags,
157 ) -> bool {
158 self.set_block_with_limit(pos, block_state, flags, 512)
159 }
160
161 pub fn set_block_with_limit(
169 self: &Arc<Self>,
170 pos: BlockPos,
171 block_state: BlockStateId,
172 flags: UpdateFlags,
173 update_limit: i32,
174 ) -> bool {
175 if !self.is_in_valid_bounds(pos) {
176 return false;
177 }
178
179 let chunk_pos = Self::chunk_pos_for_block(pos);
180 let Some(old_state) = self
181 .chunk_map
182 .with_full_chunk(chunk_pos, |chunk| {
183 chunk.set_block_state(pos, block_state, flags)
184 })
185 .flatten()
186 else {
187 return false;
188 };
189
190 self.finish_block_set(pos, old_state, block_state, flags, update_limit);
191 true
192 }
193
194 pub fn set_block_if_unchanged(
201 self: &Arc<Self>,
202 pos: BlockPos,
203 expected_state: BlockStateId,
204 new_state: BlockStateId,
205 flags: UpdateFlags,
206 ) -> ConditionalBlockSetResult {
207 self.set_block_if_unchanged_with_limit(pos, expected_state, new_state, flags, 512)
208 }
209
210 pub fn set_block_if_unchanged_with_limit(
212 self: &Arc<Self>,
213 pos: BlockPos,
214 expected_state: BlockStateId,
215 new_state: BlockStateId,
216 flags: UpdateFlags,
217 update_limit: i32,
218 ) -> ConditionalBlockSetResult {
219 if !self.is_in_valid_bounds(pos) {
220 return ConditionalBlockSetResult::Unavailable;
221 }
222
223 let chunk_pos = Self::chunk_pos_for_block(pos);
224 let Some(result) = self
225 .chunk_map
226 .with_full_chunk(chunk_pos, |chunk| {
227 chunk.set_block_state_if_unchanged(pos, expected_state, new_state, flags)
228 })
229 .flatten()
230 else {
231 return ConditionalBlockSetResult::Unavailable;
232 };
233
234 match result {
235 FullChunkBlockSetResult::Changed(old_state) => {
236 self.finish_block_set(pos, old_state, new_state, flags, update_limit);
237 ConditionalBlockSetResult::Changed
238 }
239 FullChunkBlockSetResult::Unchanged => ConditionalBlockSetResult::Unchanged,
240 FullChunkBlockSetResult::Stale(current_state) => {
241 ConditionalBlockSetResult::Stale(current_state)
242 }
243 }
244 }
245
246 pub(super) fn finish_block_set(
247 self: &Arc<Self>,
248 pos: BlockPos,
249 old_state: BlockStateId,
250 block_state: BlockStateId,
251 flags: UpdateFlags,
252 update_limit: i32,
253 ) {
254 let new_state = self.get_block_state(pos);
255 if new_state != block_state {
256 return;
257 }
258
259 let chunk_pos = Self::chunk_pos_for_block(pos);
260 if flags.contains(UpdateFlags::UPDATE_CLIENTS)
261 && self.chunk_map.is_block_ticking_full_chunk_loaded(chunk_pos)
262 {
263 self.chunk_map.block_changed(pos);
264 self.update_navigating_mobs_after_block_collision_change(pos, old_state, block_state);
265 }
266
267 if flags.contains(UpdateFlags::UPDATE_NEIGHBORS) {
268 self.update_neighbors_at(pos, old_state.get_block());
269 let behavior = BLOCK_BEHAVIORS.get_behavior(block_state.get_block());
270 if behavior.has_analog_output_signal(block_state) {
271 self.update_neighbor_for_output_signal(pos, block_state.get_block());
272 }
273 }
274
275 if !flags.contains(UpdateFlags::UPDATE_KNOWN_SHAPE) && update_limit > 0 {
276 let neighbor_flags =
277 flags & !(UpdateFlags::UPDATE_NEIGHBORS | UpdateFlags::UPDATE_SUPPRESS_DROPS);
278 let old_behavior = BLOCK_BEHAVIORS.get_behavior(old_state.get_block());
279 old_behavior.update_indirect_neighbour_shapes(
280 old_state,
281 self,
282 pos,
283 neighbor_flags,
284 update_limit - 1,
285 );
286 self.update_neighbour_shapes(block_state, pos, neighbor_flags, update_limit - 1);
287 let new_behavior = BLOCK_BEHAVIORS.get_behavior(block_state.get_block());
288 new_behavior.update_indirect_neighbour_shapes(
289 block_state,
290 self,
291 pos,
292 neighbor_flags,
293 update_limit - 1,
294 );
295 }
296
297 if REGISTRY.poi_types.type_id_for_state(old_state)
298 != REGISTRY.poi_types.type_id_for_state(new_state)
299 {
300 self.poi_storage
301 .lock()
302 .on_block_state_change(pos, old_state, new_state);
303 }
304 }
305
306 pub(super) fn update_navigating_mobs_after_block_collision_change(
307 self: &Arc<Self>,
308 pos: BlockPos,
309 old_state: BlockStateId,
310 new_state: BlockStateId,
311 ) {
312 let collision_shape_changed = self.block_collision_shape_changed(pos, old_state, new_state);
313 let game_time = self.game_time();
314 for entity_id in self.navigating_mob_ids() {
315 let Some(entity) = self.entity_manager.get_by_id(entity_id) else {
316 self.untrack_navigating_mob(entity_id);
317 continue;
318 };
319 let Some(pathfinder) = entity.as_pathfinder_mob() else {
320 self.untrack_navigating_mob(entity_id);
321 continue;
322 };
323 {
324 let mut navigation = pathfinder.mob_base().navigation().lock();
325 navigation.invalidate_path_type(pos);
326 }
327 if !collision_shape_changed {
328 continue;
329 }
330 if !pathfinder.is_path_finding() {
331 continue;
332 }
333
334 let should_recompute = {
335 let navigation = pathfinder.mob_base().navigation().lock();
336 navigation.should_recompute_path(pos, pathfinder.position())
337 };
338 if !should_recompute {
339 continue;
340 }
341
342 let request = {
343 let mut navigation = pathfinder.mob_base().navigation().lock();
344 navigation.request_recompute_path(game_time, pathfinder.can_update_path())
345 };
346 if let Some(request) = request {
347 pathfinder.recompute_path(request);
348 }
349 }
350 }
351
352 pub(super) fn navigating_mob_ids(&self) -> Vec<i32> {
353 self.navigating_mobs.ids()
354 }
355
356 pub(super) fn block_collision_shape_changed(
357 &self,
358 pos: BlockPos,
359 old_state: BlockStateId,
360 new_state: BlockStateId,
361 ) -> bool {
362 let old_shape = self.block_collision_shape(pos, old_state);
363 let new_shape = self.block_collision_shape(pos, new_state);
364 join_is_not_empty(old_shape, new_shape, BooleanOp::NotSame)
365 }
366
367 pub(super) fn block_collision_shape(&self, pos: BlockPos, state: BlockStateId) -> VoxelShape {
368 BLOCK_BEHAVIORS
369 .get_behavior(state.get_block())
370 .get_collision_shape(state, self, pos, BlockCollisionContext::empty())
371 }
372
373 pub fn update_neighbors_at(self: &Arc<Self>, pos: BlockPos, source_block: BlockRef) {
377 self.neighbor_updater
378 .update_neighbors_at_except_from_facing(self, pos, source_block, None);
379 }
380
381 pub fn update_neighbors_at_except_from_facing(
386 self: &Arc<Self>,
387 pos: BlockPos,
388 source_block: BlockRef,
389 skip_direction: Direction,
390 ) {
391 self.neighbor_updater
392 .update_neighbors_at_except_from_facing(self, pos, source_block, Some(skip_direction));
393 }
394
395 pub(crate) fn update_neighbor_for_output_signal(
402 self: &Arc<Self>,
403 pos: BlockPos,
404 changed_block: BlockRef,
405 ) {
406 for direction in Direction::HORIZONTAL {
407 let mut relative_pos = pos.relative(direction);
408 if !self.has_full_chunk(Self::chunk_pos_for_block(relative_pos)) {
409 continue;
410 }
411
412 let mut state = self.get_block_state(relative_pos);
413 if state.get_block() == &vanilla_blocks::COMPARATOR {
414 self.neighbor_changed_with_state(state, relative_pos, changed_block, false);
415 continue;
416 }
417
418 if !self.is_redstone_conductor(state, relative_pos) {
419 continue;
420 }
421
422 relative_pos = relative_pos.relative(direction);
423 if !self.has_full_chunk(Self::chunk_pos_for_block(relative_pos)) {
424 continue;
425 }
426 state = self.get_block_state(relative_pos);
427 if state.get_block() == &vanilla_blocks::COMPARATOR {
428 self.neighbor_changed_with_state(state, relative_pos, changed_block, false);
429 }
430 }
431 }
432
433 pub(crate) fn update_neighbour_shapes(
434 self: &Arc<Self>,
435 state: BlockStateId,
436 pos: BlockPos,
437 flags: UpdateFlags,
438 update_limit: i32,
439 ) {
440 for direction in Direction::UPDATE_SHAPE_ORDER {
441 let neighbor_pos = pos.relative(direction);
442 self.neighbor_shape_changed(
443 direction.opposite(),
444 neighbor_pos,
445 pos,
446 state,
447 flags,
448 update_limit,
449 );
450 }
451 }
452
453 pub(crate) fn update_from_neighbor_shapes(
455 self: &Arc<Self>,
456 state: BlockStateId,
457 pos: BlockPos,
458 ) -> BlockStateId {
459 let mut updated = state;
460 for direction in Direction::UPDATE_SHAPE_ORDER {
461 let neighbor_pos = pos.relative(direction);
462 let neighbor_state = self.get_block_state(neighbor_pos);
463 updated = BLOCK_BEHAVIORS
464 .get_behavior(updated.get_block())
465 .update_shape(updated, self, pos, direction, neighbor_pos, neighbor_state);
466 }
467 updated
468 }
469
470 pub(crate) fn neighbor_shape_changed(
474 self: &Arc<Self>,
475 direction: Direction,
476 pos: BlockPos,
477 neighbor_pos: BlockPos,
478 neighbor_state: BlockStateId,
479 flags: UpdateFlags,
480 update_limit: i32,
481 ) {
482 self.neighbor_updater.shape_update(
483 self,
484 ShapeUpdate::new(
485 direction,
486 neighbor_state,
487 pos,
488 neighbor_pos,
489 flags,
490 update_limit,
491 ),
492 );
493 }
494
495 pub(super) fn execute_neighbor_shape_update(
496 self: &Arc<Self>,
497 direction: Direction,
498 pos: BlockPos,
499 neighbor_pos: BlockPos,
500 neighbor_state: BlockStateId,
501 flags: UpdateFlags,
502 update_limit: i32,
503 ) {
504 if !self.is_in_valid_bounds(pos) {
505 return;
506 }
507
508 let current_state = self.get_block_state(pos);
509
510 if flags.contains(UpdateFlags::UPDATE_SKIP_SHAPE_UPDATE_ON_WIRE)
511 && current_state.get_block() == &vanilla_blocks::REDSTONE_WIRE
512 {
513 return;
514 }
515
516 let block_behaviors = &*BLOCK_BEHAVIORS;
517 let behavior = block_behaviors.get_behavior(current_state.get_block());
518 let new_state = behavior.update_shape(
519 current_state,
520 self,
521 pos,
522 direction,
523 neighbor_pos,
524 neighbor_state,
525 );
526
527 self.update_or_destroy(current_state, new_state, pos, flags, update_limit);
528 }
529
530 pub(crate) fn update_or_destroy(
531 self: &Arc<World>,
532 old_state: BlockStateId,
533 new_state: BlockStateId,
534 pos: BlockPos,
535 flags: UpdateFlags,
536 recursion_left: i32,
537 ) {
538 if new_state == old_state {
539 return;
540 }
541
542 if new_state.is_air() {
543 self.destroy_block_with_limit(
544 pos,
545 !flags.contains(UpdateFlags::UPDATE_SUPPRESS_DROPS),
546 recursion_left,
547 );
548 } else {
549 self.set_block_with_limit(
550 pos,
551 new_state,
552 flags & !UpdateFlags::UPDATE_SUPPRESS_DROPS,
553 recursion_left,
554 );
555 }
556 }
557
558 pub(crate) fn update_neighbour_on_block_set(
562 self: &Arc<Self>,
563 pos: BlockPos,
564 old_state: BlockStateId,
565 ) {
566 let block_state = self.get_block_state(pos);
567 let behavior = BLOCK_BEHAVIORS.get_behavior(old_state.get_block());
569
570 if old_state != block_state {
571 behavior.affect_neighbors_after_removal(old_state, self, pos, false);
572 }
573
574 self.update_neighbors_at(pos, block_state.get_block());
575
576 if behavior.has_analog_output_signal(block_state) {
577 self.update_neighbor_for_output_signal(pos, block_state.get_block());
578 }
579 }
580
581 pub(crate) fn neighbor_changed(self: &Arc<Self>, pos: BlockPos, source_block: BlockRef) {
585 self.neighbor_updater
586 .neighbor_changed(self, pos, source_block);
587 }
588
589 pub(crate) fn neighbor_changed_with_state(
590 self: &Arc<Self>,
591 state: BlockStateId,
592 pos: BlockPos,
593 source_block: BlockRef,
594 moved_by_piston: bool,
595 ) {
596 self.neighbor_updater.neighbor_changed_with_state(
597 self,
598 state,
599 pos,
600 source_block,
601 moved_by_piston,
602 );
603 }
604
605 pub(super) fn execute_neighbor_update(
606 self: &Arc<Self>,
607 state: BlockStateId,
608 pos: BlockPos,
609 source_block: BlockRef,
610 moved_by_piston: bool,
611 ) {
612 if !self.is_in_valid_bounds(pos) {
613 return;
614 }
615 let block_behaviors = &*BLOCK_BEHAVIORS;
616 let behavior = block_behaviors.get_behavior(state.get_block());
617 behavior.handle_neighbor_changed(state, self, pos, source_block, moved_by_piston);
618 }
619
620 pub(super) const fn chunk_pos_for_block(pos: BlockPos) -> ChunkPos {
621 ChunkPos::new(
622 SectionPos::block_to_section_coord(pos.0.x),
623 SectionPos::block_to_section_coord(pos.0.z),
624 )
625 }
626
627 #[must_use]
631 pub fn get_block_entity(&self, pos: BlockPos) -> Option<SharedBlockEntity> {
632 let chunk_pos = Self::chunk_pos_for_block(pos);
633 self.chunk_map
634 .with_full_chunk(chunk_pos, |chunk| chunk.get_block_entity_immediate(pos))
635 .flatten()
636 }
637
638 pub(crate) fn set_block_entity(&self, block_entity: SharedBlockEntity) -> bool {
640 let pos = block_entity.get_block_pos();
641 if !self.is_in_valid_bounds(pos) {
642 return false;
643 }
644
645 self.chunk_map
646 .with_full_chunk(Self::chunk_pos_for_block(pos), |chunk| {
647 chunk.add_and_register_block_entity(block_entity)
648 })
649 .unwrap_or(false)
650 }
651
652 pub(crate) fn remove_block_entity_if_same(&self, expected: &dyn BlockEntity) -> bool {
654 let pos = expected.get_block_pos();
655 if !self.is_in_valid_bounds(pos) {
656 return false;
657 }
658
659 self.chunk_map
660 .with_full_chunk(Self::chunk_pos_for_block(pos), |chunk| {
661 chunk.remove_block_entity_if_same(expected)
662 })
663 .unwrap_or(false)
664 }
665
666 pub fn block_entity_changed(&self, pos: BlockPos) {
670 let chunk_pos = Self::chunk_pos_for_block(pos);
671 self.chunk_map.packet_content_changed(chunk_pos);
672 self.mark_chunk_dirty(chunk_pos);
673 }
674
675 pub(crate) fn send_block_updated(&self, pos: BlockPos) {
680 self.chunk_map.block_changed(pos);
681 }
682
683 pub fn mark_chunk_dirty(&self, chunk_pos: ChunkPos) {
687 self.chunk_map
688 .with_chunk_at_status(chunk_pos, ChunkStatus::Empty, Chunk::mark_dirty);
689 }
690}