1use super::{
2 AddEntityError, Arc, BLOCK_DROPS, BlockPos, ChunkPos, ChunkStatus, DVec3, Direction, Entity,
3 EntityChunkCallback, EntityLifecycleChanges, EntityOwnership, EntityTracker, EntityVisibility,
4 ExperienceOrbEntity, FxHashSet, GameEventContext, GameEventDispatcher, GameEventListenerCount,
5 GameEventListenerStorage, GameEventRef, InactiveEntityCallback, ItemEntity, ItemStack, Player,
6 RemovalReason, SectionPos, SharedEntity, SharedGameEventListener, SyncMutex, World, WorldAabb,
7 WorldChangeRequest, block_entity_ticker, mem, vanilla_entities,
8};
9
10use crate::entity::{EntityCollisionCandidates, selector::EntitySelector};
11
12pub(super) struct NavigatingMobTracker {
13 ids: SyncMutex<FxHashSet<i32>>,
14}
15
16impl NavigatingMobTracker {
17 pub(super) fn new() -> Self {
18 Self {
19 ids: SyncMutex::new(FxHashSet::default()),
20 }
21 }
22
23 pub(super) fn track(&self, entity: &SharedEntity) {
24 if entity.as_pathfinder_mob().is_some() {
25 self.ids.lock().insert(entity.id());
26 }
27 }
28
29 pub(super) fn untrack(&self, entity_id: i32) {
30 self.ids.lock().remove(&entity_id);
31 }
32
33 pub(super) fn ids(&self) -> Vec<i32> {
34 self.ids.lock().iter().copied().collect()
35 }
36}
37
38pub(super) fn nearest_player_distance_in_range(
39 distance_sqr: f64,
40 max_distance: f64,
41 max_distance_sqr: f64,
42) -> bool {
43 max_distance < 0.0 || distance_sqr < max_distance_sqr
44}
45
46impl World {
47 #[must_use]
49 pub(crate) const fn block_entity_tickers(
50 &self,
51 ) -> &block_entity_ticker::WorldBlockEntityTickers {
52 &self.block_entity_tickers
53 }
54
55 #[must_use]
57 pub(crate) fn game_event_listener_count(&self) -> Arc<GameEventListenerCount> {
58 Arc::clone(&self.game_event_listener_count)
59 }
60
61 #[must_use]
63 pub const fn entity_tracker(&self) -> &EntityTracker {
64 &self.entity_tracker
65 }
66
67 pub(super) fn attach_managed_entity_callback(self: &Arc<Self>, entity: &SharedEntity) {
68 let callback = Arc::new(EntityChunkCallback::new(entity.id(), Arc::downgrade(self)));
69 entity.set_level_callback(callback);
70 self.entity_manager.commit_bounding_box_change(entity.id());
71 }
72
73 pub(crate) fn add_entity_to_tracker(self: &Arc<Self>, entity: &SharedEntity) {
74 self.entity_tracker.add(
75 entity,
76 |chunk| self.get_packet_tracking_players(chunk),
77 |id| self.players.get_by_entity_id(id),
78 );
79 self.track_navigating_mob(entity);
80 }
81
82 pub(crate) fn remove_entity_from_tracker(&self, entity_id: i32) {
83 self.entity_tracker.remove(entity_id, |player_id| {
84 self.players.get_by_entity_id(player_id)
85 });
86 self.untrack_navigating_mob(entity_id);
87 }
88
89 pub(crate) fn apply_entity_lifecycle_changes(
90 self: &Arc<Self>,
91 changes: EntityLifecycleChanges,
92 ) {
93 for entity in changes.tracking_stopped {
94 self.remove_entity_from_tracker(entity.id());
95 }
96 for entity in changes.tracking_started {
97 self.add_entity_to_tracker(&entity);
98 }
99 }
100
101 pub(super) fn track_navigating_mob(&self, entity: &SharedEntity) {
102 self.navigating_mobs.track(entity);
103 }
104
105 pub(super) fn untrack_navigating_mob(&self, entity_id: i32) {
106 self.navigating_mobs.untrack(entity_id);
107 }
108
109 pub(crate) fn register_loaded_entity(
110 self: &Arc<Self>,
111 entity: SharedEntity,
112 ) -> Result<(), AddEntityError> {
113 let lifecycle = self
114 .entity_manager
115 .add_live_entity(Arc::clone(&entity), EntityOwnership::ManagerOwned)?;
116 self.attach_managed_entity_callback(&entity);
117 self.apply_entity_lifecycle_changes(lifecycle);
118 Ok(())
119 }
120
121 pub(crate) fn register_loaded_entity_tree(
122 self: &Arc<Self>,
123 entities: &[SharedEntity],
124 ) -> Result<(), AddEntityError> {
125 let lifecycle = self
126 .entity_manager
127 .add_live_entity_tree(entities, EntityOwnership::ManagerOwned)?;
128 for entity in entities {
129 self.attach_managed_entity_callback(entity);
130 }
131 self.apply_entity_lifecycle_changes(lifecycle);
132 Ok(())
133 }
134
135 pub(crate) fn register_loaded_chunk_entities(
136 self: &Arc<Self>,
137 source_chunk: ChunkPos,
138 persisted_status: ChunkStatus,
139 entities: Vec<SharedEntity>,
140 ) {
141 for tree in Self::loaded_entity_trees(entities) {
142 let Some(root) = tree.first() else {
143 continue;
144 };
145 let root_id = root.id();
146 let root_uuid = root.uuid();
147 let root_type = root.entity_type();
148 let root_pos = root.position();
149 let root_chunk = ChunkPos::from_entity_pos(root_pos);
150 let mut dirty_chunks = FxHashSet::default();
151 for entity in &tree {
152 let entity_chunk = ChunkPos::from_entity_pos(entity.position());
153 if entity_chunk != source_chunk {
154 dirty_chunks.insert(source_chunk);
155 dirty_chunks.insert(entity_chunk);
156 }
157 }
158
159 if let Err(error) = self.register_loaded_entity_tree(&tree) {
160 tracing::warn!(
161 source_chunk = ?source_chunk,
162 ?persisted_status,
163 root_id,
164 uuid = ?root_uuid,
165 entity_type = ?root_type.key,
166 position = ?root_pos,
167 entity_chunk = ?root_chunk,
168 entity_count = tree.len(),
169 "Discarding loaded chunk entity tree that could not be registered: {error}; source_chunk={source_chunk:?}, persisted_status={persisted_status:?}, root_id={root_id}, uuid={root_uuid}, entity_type={:?}, position={root_pos:?}, entity_chunk={root_chunk:?}, entity_count={}",
170 root_type.key,
171 tree.len(),
172 );
173 Self::discard_loaded_entity_tree(&tree);
174 self.mark_chunk_dirty(source_chunk);
175 continue;
176 }
177
178 for chunk in dirty_chunks {
179 self.mark_chunk_dirty(chunk);
180 }
181 }
182 }
183
184 pub(super) fn loaded_entity_trees(entities: Vec<SharedEntity>) -> Vec<Vec<SharedEntity>> {
185 let mut seen = FxHashSet::default();
186 let mut trees = Vec::new();
187
188 for entity in &entities {
189 if entity.is_passenger() {
190 continue;
191 }
192 let mut tree = Vec::new();
193 Self::collect_loaded_entity_tree(entity, &mut seen, &mut tree);
194 if !tree.is_empty() {
195 trees.push(tree);
196 }
197 }
198
199 for entity in &entities {
200 if seen.contains(&entity.id()) {
201 continue;
202 }
203 let mut tree = Vec::new();
204 Self::collect_loaded_entity_tree(entity, &mut seen, &mut tree);
205 if !tree.is_empty() {
206 trees.push(tree);
207 }
208 }
209
210 trees
211 }
212
213 pub(super) fn collect_loaded_entity_tree(
214 entity: &SharedEntity,
215 seen: &mut FxHashSet<i32>,
216 tree: &mut Vec<SharedEntity>,
217 ) {
218 if !seen.insert(entity.id()) {
219 return;
220 }
221 tree.push(Arc::clone(entity));
222 for passenger in entity.passengers() {
223 Self::collect_loaded_entity_tree(&passenger, seen, tree);
224 }
225 }
226
227 pub(super) fn discard_loaded_entity_tree(entities: &[SharedEntity]) {
228 for entity in entities {
229 entity.set_removed(RemovalReason::Discarded);
230 }
231 }
232
233 pub(crate) fn has_full_chunk(&self, chunk_pos: ChunkPos) -> bool {
234 self.chunk_map
235 .with_full_chunk(chunk_pos, |_| true)
236 .unwrap_or(false)
237 }
238
239 pub fn try_add_entity(self: &Arc<Self>, entity: SharedEntity) -> Result<(), AddEntityError> {
241 let chunk_pos = ChunkPos::from_entity_pos(entity.position());
242 if !self.has_full_chunk(chunk_pos) {
243 return Err(AddEntityError::ChunkNotLoaded {
244 entity_id: entity.id(),
245 chunk: chunk_pos,
246 });
247 }
248 self.register_loaded_entity(entity)?;
249 self.mark_chunk_dirty(chunk_pos);
250 Ok(())
251 }
252
253 pub(crate) fn try_add_fresh_entity_with_passengers(
256 self: &Arc<Self>,
257 root: SharedEntity,
258 ) -> Result<(), AddEntityError> {
259 let mut entities = Vec::new();
260 let mut seen_ids = FxHashSet::default();
261 Self::collect_fresh_entity_tree(&root, &mut seen_ids, &mut entities)?;
262
263 let mut dirty_chunks = FxHashSet::default();
264 for entity in &entities {
265 let chunk = ChunkPos::from_entity_pos(entity.position());
266 if !self.entity_manager.is_chunk_loaded(chunk) {
267 return Err(AddEntityError::ChunkNotLoaded {
268 entity_id: entity.id(),
269 chunk,
270 });
271 }
272 dirty_chunks.insert(chunk);
273 }
274
275 self.register_loaded_entity_tree(&entities)?;
276 for chunk in dirty_chunks {
277 self.mark_chunk_dirty(chunk);
278 }
279 Ok(())
280 }
281
282 fn collect_fresh_entity_tree(
283 entity: &SharedEntity,
284 seen_ids: &mut FxHashSet<i32>,
285 entities: &mut Vec<SharedEntity>,
286 ) -> Result<(), AddEntityError> {
287 if !seen_ids.insert(entity.id()) {
288 return Err(AddEntityError::DuplicateId {
289 entity_id: entity.id(),
290 });
291 }
292 entities.push(Arc::clone(entity));
293 for passenger in entity.passengers() {
294 Self::collect_fresh_entity_tree(&passenger, seen_ids, entities)?;
295 }
296 Ok(())
297 }
298
299 pub(crate) fn on_entity_chunk_loaded(self: &Arc<Self>, pos: ChunkPos) {
300 let result = self.entity_manager.on_chunk_loaded(pos);
305 if result.needs_save {
306 self.mark_chunk_dirty(pos);
307 }
308 for entity in result.restored {
309 self.attach_managed_entity_callback(&entity);
310 }
311 self.apply_entity_lifecycle_changes(EntityLifecycleChanges {
312 tracking_started: result.tracking_started,
313 tracking_stopped: Vec::new(),
314 ticking_started: result.ticking_started,
315 ticking_stopped: Vec::new(),
316 });
317 }
318
319 pub(crate) fn update_entity_chunk_visibility(
320 self: &Arc<Self>,
321 pos: ChunkPos,
322 visibility: EntityVisibility,
323 ) {
324 let changes = self.entity_manager.update_chunk_visibility(pos, visibility);
325 self.apply_entity_lifecycle_changes(changes);
326 }
327
328 pub(crate) fn on_entity_chunk_unload_start(self: &Arc<Self>, pos: ChunkPos) {
329 let result = self.entity_manager.begin_chunk_unload(pos);
330 self.apply_entity_lifecycle_changes(EntityLifecycleChanges {
331 tracking_started: Vec::new(),
332 tracking_stopped: result.tracking_stopped,
333 ticking_started: Vec::new(),
334 ticking_stopped: result.ticking_stopped,
335 });
336 for entity in result.retained {
337 let entity_id = entity.id();
338 entity.set_level_callback(Arc::new(InactiveEntityCallback::new(entity_id)));
339 }
340 }
341
342 pub(crate) fn on_entity_chunk_unload_finalized(&self, pos: ChunkPos) {
343 self.entity_manager.finalize_chunk_unload(pos);
344 }
345
346 pub fn spawn_item(self: &Arc<Self>, pos: DVec3, item: ItemStack) -> Option<Arc<ItemEntity>> {
352 self.spawn_item_with_velocity(pos, item, ItemEntity::default_spawn_velocity())
353 }
354
355 pub fn spawn_item_with_velocity(
359 self: &Arc<Self>,
360 pos: DVec3,
361 item: ItemStack,
362 velocity: DVec3,
363 ) -> Option<Arc<ItemEntity>> {
364 use crate::entity::next_entity_id;
365
366 if item.is_empty() {
367 return None;
368 }
369
370 let entity_id = next_entity_id();
371 let entity = Arc::new(ItemEntity::with_item_and_velocity(
372 &vanilla_entities::ITEM,
373 entity_id,
374 pos,
375 item,
376 velocity,
377 Arc::downgrade(self),
378 ));
379 if let Err(error) = self.try_add_entity(Arc::<ItemEntity>::clone(&entity)) {
380 log::warn!("Failed to spawn item entity: {error}");
381 return None;
382 }
383 Some(entity)
384 }
385
386 pub fn pop_resource(
392 self: &Arc<Self>,
393 pos: BlockPos,
394 item: ItemStack,
395 ) -> Option<Arc<ItemEntity>> {
396 use steel_registry::vanilla_entities;
397
398 if item.is_empty() {
399 return None;
400 }
401
402 if !self.get_game_rule(&BLOCK_DROPS) {
404 return None;
405 }
406
407 let half_height = f64::from(vanilla_entities::ITEM.dimensions.height) / 2.0;
409
410 let x = f64::from(pos.x()) + 0.5 + rand::random_range(-0.25..0.25);
412 let y = f64::from(pos.y()) + 0.5 + rand::random_range(-0.25..0.25) - half_height;
413 let z = f64::from(pos.z()) + 0.5 + rand::random_range(-0.25..0.25);
414
415 let entity = self.spawn_item(DVec3::new(x, y, z), item)?;
416 entity.set_default_pickup_delay();
417 Some(entity)
418 }
419
420 pub fn pop_experience(self: &Arc<Self>, pos: BlockPos, amount: i32) {
424 if amount <= 0 || !self.get_game_rule(&BLOCK_DROPS) {
425 return;
426 }
427
428 ExperienceOrbEntity::award(
429 self,
430 DVec3::new(
431 f64::from(pos.x()) + 0.5,
432 f64::from(pos.y()) + 0.5,
433 f64::from(pos.z()) + 0.5,
434 ),
435 amount,
436 );
437 }
438
439 pub fn pop_resource_from_face(
444 self: &Arc<Self>,
445 pos: BlockPos,
446 face: Direction,
447 item: ItemStack,
448 ) -> Option<Arc<ItemEntity>> {
449 use steel_registry::vanilla_entities;
450
451 if item.is_empty() {
452 return None;
453 }
454
455 if !self.get_game_rule(&BLOCK_DROPS) {
456 return None;
457 }
458
459 let half_width = f64::from(vanilla_entities::ITEM.dimensions.width) / 2.0;
460 let half_height = f64::from(vanilla_entities::ITEM.dimensions.height) / 2.0;
461
462 let (step_x, step_y, step_z) = face.offset();
463
464 let x = f64::from(pos.x())
466 + 0.5
467 + if step_x == 0 {
468 rand::random_range(-0.25..0.25)
469 } else {
470 f64::from(step_x) * (0.5 + half_width)
471 };
472 let y = f64::from(pos.y())
473 + 0.5
474 + if step_y == 0 {
475 rand::random_range(-0.25..0.25)
476 } else {
477 f64::from(step_y) * (0.5 + half_height)
478 }
479 - half_height;
480 let z = f64::from(pos.z())
481 + 0.5
482 + if step_z == 0 {
483 rand::random_range(-0.25..0.25)
484 } else {
485 f64::from(step_z) * (0.5 + half_width)
486 };
487
488 let delta_x = if step_x == 0 {
490 rand::random_range(-0.1..0.1)
491 } else {
492 f64::from(step_x) * 0.1
493 };
494 let delta_y = if step_y == 0 {
495 rand::random_range(0.0..0.1)
496 } else {
497 f64::from(step_y) * 0.1 + 0.1
498 };
499 let delta_z = if step_z == 0 {
500 rand::random_range(-0.1..0.1)
501 } else {
502 f64::from(step_z) * 0.1
503 };
504
505 let entity = self.spawn_item_with_velocity(
506 DVec3::new(x, y, z),
507 item,
508 DVec3::new(delta_x, delta_y, delta_z),
509 )?;
510 entity.set_default_pickup_delay();
511 Some(entity)
512 }
513
514 #[must_use]
518 pub fn get_entity_by_id(&self, id: i32) -> Option<SharedEntity> {
519 self.entity_manager.get_by_id(id)
520 }
521
522 pub(crate) fn contains_live_or_unloading_entity(&self, entity: &SharedEntity) -> bool {
524 self.entity_manager
525 .contains_live_or_unloading_entity(entity)
526 }
527
528 pub fn queue_world_change(&self, entity: SharedEntity, request: WorldChangeRequest) {
530 self.pending_world_changes.lock().push((entity, request));
531 }
532
533 pub(crate) fn drain_world_changes(&self) -> Vec<(SharedEntity, WorldChangeRequest)> {
534 mem::take(&mut *self.pending_world_changes.lock())
535 }
536
537 #[must_use]
541 pub fn get_accessible_entity_by_id(&self, id: i32) -> Option<SharedEntity> {
542 self.entity_manager.get_accessible_by_id(id)
543 }
544
545 #[must_use]
549 pub fn get_entity_by_uuid(&self, uuid: &uuid::Uuid) -> Option<SharedEntity> {
550 self.entity_manager.get_by_uuid(uuid)
551 }
552
553 #[must_use]
557 pub fn get_entities_in_aabb(&self, aabb: &WorldAabb) -> Vec<SharedEntity> {
558 self.entity_manager.get_entities_in_aabb(aabb)
559 }
560
561 #[must_use]
565 pub fn get_entities_in_aabb_matching(
566 &self,
567 aabb: &WorldAabb,
568 predicate: impl FnMut(&dyn Entity) -> bool,
569 ) -> Vec<SharedEntity> {
570 self.entity_manager
571 .get_entities_in_aabb_matching(aabb, predicate)
572 }
573
574 #[must_use]
578 pub fn has_entity_in_aabb_matching(
579 &self,
580 aabb: &WorldAabb,
581 predicate: impl FnMut(&dyn Entity) -> bool,
582 ) -> bool {
583 self.entity_manager
584 .has_entity_in_aabb_matching(aabb, predicate)
585 }
586
587 #[must_use]
591 pub fn get_entity_bounding_boxes_in_aabb_matching(
592 &self,
593 aabb: &WorldAabb,
594 predicate: impl FnMut(&dyn Entity) -> bool,
595 ) -> Vec<WorldAabb> {
596 self.entity_manager
597 .get_entity_bounding_boxes_in_aabb_matching(aabb, predicate)
598 }
599
600 #[must_use]
602 pub(crate) fn get_movement_collision_boxes_in_aabb_matching(
603 &self,
604 aabb: &WorldAabb,
605 candidates: EntityCollisionCandidates,
606 predicate: impl FnMut(&dyn Entity) -> bool,
607 ) -> Vec<WorldAabb> {
608 self.entity_manager
609 .get_movement_collision_boxes_in_aabb_matching(aabb, candidates, predicate)
610 }
611
612 #[must_use]
614 pub(crate) fn has_movement_collision_in_aabb_matching(
615 &self,
616 aabb: &WorldAabb,
617 candidates: EntityCollisionCandidates,
618 predicate: impl FnMut(&dyn Entity) -> bool,
619 ) -> bool {
620 self.entity_manager
621 .has_movement_collision_in_aabb_matching(aabb, candidates, predicate)
622 }
623
624 #[must_use]
628 pub fn nearest_entity_in_aabb_matching(
629 &self,
630 aabb: &WorldAabb,
631 origin: DVec3,
632 predicate: impl FnMut(&dyn Entity) -> bool,
633 ) -> Option<SharedEntity> {
634 self.entity_manager
635 .nearest_entity_in_aabb_matching(aabb, origin, predicate)
636 }
637
638 #[must_use]
640 pub fn nearest_player(
641 &self,
642 position: DVec3,
643 max_distance: f64,
644 mut predicate: impl FnMut(&Player) -> bool,
645 ) -> Option<Arc<Player>> {
646 let max_distance_sqr = max_distance * max_distance;
647 let mut nearest: Option<(Arc<Player>, f64)> = None;
648 self.players.iter_players(|_, player| {
649 if predicate(player) {
650 let distance_sqr = player.position().distance_squared(position);
651 if nearest_player_distance_in_range(distance_sqr, max_distance, max_distance_sqr)
652 && nearest
653 .as_ref()
654 .is_none_or(|(_, current)| distance_sqr < *current)
655 {
656 nearest = Some((Arc::clone(player), distance_sqr));
657 }
658 }
659 true
660 });
661 nearest.map(|(player, _)| player)
662 }
663
664 #[must_use]
666 pub fn nearest_player_distance_sqr(&self, position: DVec3) -> Option<f64> {
667 let mut nearest = None;
668 self.players.iter_players(|_, player| {
669 if player.is_spectator() {
670 return true;
671 }
672 let distance_sqr = player.position().distance_squared(position);
673 if nearest.is_none_or(|current| distance_sqr < current) {
674 nearest = Some(distance_sqr);
675 }
676 true
677 });
678 nearest
679 }
680
681 #[must_use]
686 pub fn get_pushable_entities(
687 &self,
688 pusher: &dyn Entity,
689 aabb: &WorldAabb,
690 ) -> Vec<SharedEntity> {
691 Self::filter_pushable_entities(pusher, self.get_entities_in_aabb(aabb))
692 }
693
694 pub(crate) fn filter_pushable_entities(
695 pusher: &dyn Entity,
696 entities: Vec<SharedEntity>,
697 ) -> Vec<SharedEntity> {
698 let predicate = EntitySelector::pushable_by(pusher);
699
700 entities
701 .into_iter()
702 .filter(|entity| predicate(entity.as_ref()))
703 .collect()
704 }
705
706 pub fn register_game_event_listener(
708 &self,
709 section_pos: SectionPos,
710 listener: SharedGameEventListener,
711 ) {
712 let chunk_pos = ChunkPos::new(section_pos.x(), section_pos.z());
713 let registry = self.game_event_listener_storage(chunk_pos);
714 if let Some(registry) = registry {
715 registry.register(section_pos.y(), listener);
716 }
717 }
718
719 pub fn unregister_game_event_listener(
721 &self,
722 section_pos: SectionPos,
723 listener: &SharedGameEventListener,
724 ) -> bool {
725 let chunk_pos = ChunkPos::new(section_pos.x(), section_pos.z());
726 let registry = self.game_event_listener_storage(chunk_pos);
727 registry.is_some_and(|registry| registry.unregister(section_pos.y(), listener))
728 }
729
730 pub(super) fn game_event_listener_storage(
732 &self,
733 chunk_pos: ChunkPos,
734 ) -> Option<Arc<GameEventListenerStorage>> {
735 self.chunk_map.with_full_chunk(chunk_pos, |chunk| {
736 Arc::clone(&chunk.game_event_listeners().registry)
737 })
738 }
739
740 pub fn game_event(
742 self: &Arc<Self>,
743 event: GameEventRef,
744 pos: BlockPos,
745 context: &GameEventContext,
746 ) {
747 self.game_event_at(
748 event,
749 DVec3::new(
750 f64::from(pos.x()) + 0.5,
751 f64::from(pos.y()) + 0.5,
752 f64::from(pos.z()) + 0.5,
753 ),
754 context,
755 );
756 }
757
758 pub fn game_event_at(
760 self: &Arc<Self>,
761 event: GameEventRef,
762 source_pos: DVec3,
763 context: &GameEventContext,
764 ) {
765 if !self.game_event_listener_count.has_any() {
766 return;
767 }
768 let radius = event.notification_radius.max(0);
769 let center = BlockPos::from(source_pos);
770 let section_min_x = SectionPos::block_to_section_coord(center.x() - radius);
771 let section_min_y = SectionPos::block_to_section_coord(center.y() - radius);
772 let section_min_z = SectionPos::block_to_section_coord(center.z() - radius);
773 let section_max_x = SectionPos::block_to_section_coord(center.x() + radius);
774 let section_max_y = SectionPos::block_to_section_coord(center.y() + radius);
775 let section_max_z = SectionPos::block_to_section_coord(center.z() + radius);
776 let mut dispatcher = GameEventDispatcher::new(self, event, source_pos, context);
777
778 for section_x in section_min_x..=section_max_x {
779 for section_z in section_min_z..=section_max_z {
780 let registry =
781 self.game_event_listener_storage(ChunkPos::new(section_x, section_z));
782 if let Some(registry) = registry {
783 dispatcher.visit_chunk(®istry, section_min_y, section_max_y);
784 }
785 }
786 }
787
788 dispatcher.finish();
789 }
790}