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