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