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
10pub(super) struct NavigatingMobTracker {
11 ids: SyncMutex<FxHashSet<i32>>,
12}
13
14impl NavigatingMobTracker {
15 pub(super) fn new() -> Self {
16 Self {
17 ids: SyncMutex::new(FxHashSet::default()),
18 }
19 }
20
21 pub(super) fn track(&self, entity: &SharedEntity) {
22 if entity.as_pathfinder_mob().is_some() {
23 self.ids.lock().insert(entity.id());
24 }
25 }
26
27 pub(super) fn untrack(&self, entity_id: i32) {
28 self.ids.lock().remove(&entity_id);
29 }
30
31 pub(super) fn ids(&self) -> Vec<i32> {
32 self.ids.lock().iter().copied().collect()
33 }
34}
35
36pub(super) fn nearest_player_distance_in_range(
37 distance_sqr: f64,
38 max_distance: f64,
39 max_distance_sqr: f64,
40) -> bool {
41 max_distance < 0.0 || distance_sqr < max_distance_sqr
42}
43
44impl World {
45 #[must_use]
47 pub(crate) const fn block_entity_tickers(
48 &self,
49 ) -> &block_entity_ticker::WorldBlockEntityTickers {
50 &self.block_entity_tickers
51 }
52
53 #[must_use]
55 pub(crate) fn game_event_listener_count(&self) -> Arc<GameEventListenerCount> {
56 Arc::clone(&self.game_event_listener_count)
57 }
58
59 #[must_use]
61 pub const fn entity_tracker(&self) -> &EntityTracker {
62 &self.entity_tracker
63 }
64
65 pub(super) fn attach_managed_entity_callback(self: &Arc<Self>, entity: &SharedEntity) {
66 let callback = Arc::new(EntityChunkCallback::new(entity.id(), Arc::downgrade(self)));
67 entity.set_level_callback(callback);
68 self.entity_manager.commit_bounding_box_change(entity.id());
69 }
70
71 pub(crate) fn add_entity_to_tracker(self: &Arc<Self>, entity: &SharedEntity) {
72 self.entity_tracker.add(
73 entity,
74 |chunk| self.get_packet_tracking_players(chunk),
75 |id| self.players.get_by_entity_id(id),
76 );
77 self.track_navigating_mob(entity);
78 }
79
80 pub(crate) fn remove_entity_from_tracker(&self, entity_id: i32) {
81 self.entity_tracker.remove(entity_id, |player_id| {
82 self.players.get_by_entity_id(player_id)
83 });
84 self.untrack_navigating_mob(entity_id);
85 }
86
87 pub(crate) fn apply_entity_lifecycle_changes(
88 self: &Arc<Self>,
89 changes: EntityLifecycleChanges,
90 ) {
91 for entity in changes.tracking_stopped {
92 self.remove_entity_from_tracker(entity.id());
93 }
94 for entity in changes.tracking_started {
95 self.add_entity_to_tracker(&entity);
96 }
97 }
98
99 pub(super) fn track_navigating_mob(&self, entity: &SharedEntity) {
100 self.navigating_mobs.track(entity);
101 }
102
103 pub(super) fn untrack_navigating_mob(&self, entity_id: i32) {
104 self.navigating_mobs.untrack(entity_id);
105 }
106
107 pub(crate) fn register_loaded_entity(
108 self: &Arc<Self>,
109 entity: SharedEntity,
110 ) -> Result<(), AddEntityError> {
111 let lifecycle = self
112 .entity_manager
113 .add_live_entity(entity.clone(), EntityOwnership::ManagerOwned)?;
114 self.attach_managed_entity_callback(&entity);
115 self.apply_entity_lifecycle_changes(lifecycle);
116 Ok(())
117 }
118
119 pub(crate) fn register_loaded_entity_tree(
120 self: &Arc<Self>,
121 entities: &[SharedEntity],
122 ) -> Result<(), AddEntityError> {
123 let lifecycle = self
124 .entity_manager
125 .add_live_entity_tree(entities, EntityOwnership::ManagerOwned)?;
126 for entity in entities {
127 self.attach_managed_entity_callback(entity);
128 }
129 self.apply_entity_lifecycle_changes(lifecycle);
130 Ok(())
131 }
132
133 pub(crate) fn register_loaded_chunk_entities(
134 self: &Arc<Self>,
135 source_chunk: ChunkPos,
136 persisted_status: ChunkStatus,
137 entities: Vec<SharedEntity>,
138 ) {
139 for tree in Self::loaded_entity_trees(entities) {
140 let Some(root) = tree.first() else {
141 continue;
142 };
143 let root_id = root.id();
144 let root_uuid = root.uuid();
145 let root_type = root.entity_type();
146 let root_pos = root.position();
147 let root_chunk = ChunkPos::from_entity_pos(root_pos);
148 let mut dirty_chunks = FxHashSet::default();
149 for entity in &tree {
150 let entity_chunk = ChunkPos::from_entity_pos(entity.position());
151 if entity_chunk != source_chunk {
152 dirty_chunks.insert(source_chunk);
153 dirty_chunks.insert(entity_chunk);
154 }
155 }
156
157 if let Err(error) = self.register_loaded_entity_tree(&tree) {
158 tracing::warn!(
159 source_chunk = ?source_chunk,
160 ?persisted_status,
161 root_id,
162 uuid = ?root_uuid,
163 entity_type = ?root_type.key,
164 position = ?root_pos,
165 entity_chunk = ?root_chunk,
166 entity_count = tree.len(),
167 "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={}",
168 root_type.key,
169 tree.len(),
170 );
171 Self::discard_loaded_entity_tree(&tree);
172 self.mark_chunk_dirty(source_chunk);
173 continue;
174 }
175
176 for chunk in dirty_chunks {
177 self.mark_chunk_dirty(chunk);
178 }
179 }
180 }
181
182 pub(super) fn loaded_entity_trees(entities: Vec<SharedEntity>) -> Vec<Vec<SharedEntity>> {
183 let mut seen = FxHashSet::default();
184 let mut trees = Vec::new();
185
186 for entity in &entities {
187 if entity.is_passenger() {
188 continue;
189 }
190 let mut tree = Vec::new();
191 Self::collect_loaded_entity_tree(entity, &mut seen, &mut tree);
192 if !tree.is_empty() {
193 trees.push(tree);
194 }
195 }
196
197 for entity in &entities {
198 if seen.contains(&entity.id()) {
199 continue;
200 }
201 let mut tree = Vec::new();
202 Self::collect_loaded_entity_tree(entity, &mut seen, &mut tree);
203 if !tree.is_empty() {
204 trees.push(tree);
205 }
206 }
207
208 trees
209 }
210
211 pub(super) fn collect_loaded_entity_tree(
212 entity: &SharedEntity,
213 seen: &mut FxHashSet<i32>,
214 tree: &mut Vec<SharedEntity>,
215 ) {
216 if !seen.insert(entity.id()) {
217 return;
218 }
219 tree.push(Arc::clone(entity));
220 for passenger in entity.passengers() {
221 Self::collect_loaded_entity_tree(&passenger, seen, tree);
222 }
223 }
224
225 pub(super) fn discard_loaded_entity_tree(entities: &[SharedEntity]) {
226 for entity in entities {
227 entity.set_removed(RemovalReason::Discarded);
228 }
229 }
230
231 pub(crate) fn has_full_chunk(&self, chunk_pos: ChunkPos) -> bool {
232 self.chunk_map
233 .with_full_chunk(chunk_pos, |_| true)
234 .unwrap_or(false)
235 }
236
237 pub fn try_add_entity(self: &Arc<Self>, entity: SharedEntity) -> Result<(), AddEntityError> {
239 let chunk_pos = ChunkPos::from_entity_pos(entity.position());
240 if !self.has_full_chunk(chunk_pos) {
241 return Err(AddEntityError::ChunkNotLoaded {
242 entity_id: entity.id(),
243 chunk: chunk_pos,
244 });
245 }
246 self.register_loaded_entity(entity)?;
247 self.mark_chunk_dirty(chunk_pos);
248 Ok(())
249 }
250
251 pub(crate) fn on_entity_chunk_loaded(self: &Arc<Self>, pos: ChunkPos) {
252 let result = self.entity_manager.on_chunk_loaded(pos);
257 if result.needs_save {
258 self.mark_chunk_dirty(pos);
259 }
260 for entity in result.restored {
261 self.attach_managed_entity_callback(&entity);
262 }
263 self.apply_entity_lifecycle_changes(EntityLifecycleChanges {
264 tracking_started: result.tracking_started,
265 tracking_stopped: Vec::new(),
266 ticking_started: result.ticking_started,
267 ticking_stopped: Vec::new(),
268 });
269 }
270
271 pub(crate) fn update_entity_chunk_visibility(
272 self: &Arc<Self>,
273 pos: ChunkPos,
274 visibility: EntityVisibility,
275 ) {
276 let changes = self.entity_manager.update_chunk_visibility(pos, visibility);
277 self.apply_entity_lifecycle_changes(changes);
278 }
279
280 pub(crate) fn on_entity_chunk_unload_start(self: &Arc<Self>, pos: ChunkPos) {
281 let result = self.entity_manager.begin_chunk_unload(pos);
282 self.apply_entity_lifecycle_changes(EntityLifecycleChanges {
283 tracking_started: Vec::new(),
284 tracking_stopped: result.tracking_stopped,
285 ticking_started: Vec::new(),
286 ticking_stopped: result.ticking_stopped,
287 });
288 for entity in result.retained {
289 let entity_id = entity.id();
290 entity.set_level_callback(Arc::new(InactiveEntityCallback::new(entity_id)));
291 }
292 }
293
294 pub(crate) fn on_entity_chunk_unload_finalized(&self, pos: ChunkPos) {
295 self.entity_manager.finalize_chunk_unload(pos);
296 }
297
298 pub fn spawn_item(self: &Arc<Self>, pos: DVec3, item: ItemStack) -> Option<Arc<ItemEntity>> {
304 let vx = rand::random::<f64>() * 0.2 - 0.1;
306 let vy = 0.2;
307 let vz = rand::random::<f64>() * 0.2 - 0.1;
308 self.spawn_item_with_velocity(pos, item, DVec3::new(vx, vy, vz))
309 }
310
311 pub fn spawn_item_with_velocity(
315 self: &Arc<Self>,
316 pos: DVec3,
317 item: ItemStack,
318 velocity: DVec3,
319 ) -> Option<Arc<ItemEntity>> {
320 use crate::entity::next_entity_id;
321
322 if item.is_empty() {
323 return None;
324 }
325
326 let entity_id = next_entity_id();
327 let entity = Arc::new(ItemEntity::with_item_and_velocity(
328 &vanilla_entities::ITEM,
329 entity_id,
330 pos,
331 item,
332 velocity,
333 Arc::downgrade(self),
334 ));
335 if let Err(error) = self.try_add_entity(entity.clone()) {
336 log::warn!("Failed to spawn item entity: {error}");
337 return None;
338 }
339 Some(entity)
340 }
341
342 pub fn pop_resource(
348 self: &Arc<Self>,
349 pos: BlockPos,
350 item: ItemStack,
351 ) -> Option<Arc<ItemEntity>> {
352 use steel_registry::vanilla_entities;
353
354 if item.is_empty() {
355 return None;
356 }
357
358 if !self.get_game_rule(&BLOCK_DROPS) {
360 return None;
361 }
362
363 let half_height = f64::from(vanilla_entities::ITEM.dimensions.height) / 2.0;
365
366 let x = f64::from(pos.x()) + 0.5 + (rand::random::<f64>() - 0.5) * 0.5;
368 let y = f64::from(pos.y()) + 0.5 + (rand::random::<f64>() - 0.5) * 0.5 - half_height;
369 let z = f64::from(pos.z()) + 0.5 + (rand::random::<f64>() - 0.5) * 0.5;
370
371 let entity = self.spawn_item(DVec3::new(x, y, z), item)?;
372 entity.set_default_pickup_delay();
373 Some(entity)
374 }
375
376 pub fn pop_experience(self: &Arc<Self>, pos: BlockPos, amount: i32) {
380 if amount <= 0 || !self.get_game_rule(&BLOCK_DROPS) {
381 return;
382 }
383
384 ExperienceOrbEntity::award(
385 self,
386 DVec3::new(
387 f64::from(pos.x()) + 0.5,
388 f64::from(pos.y()) + 0.5,
389 f64::from(pos.z()) + 0.5,
390 ),
391 amount,
392 );
393 }
394
395 pub fn pop_resource_from_face(
400 self: &Arc<Self>,
401 pos: BlockPos,
402 face: Direction,
403 item: ItemStack,
404 ) -> Option<Arc<ItemEntity>> {
405 use steel_registry::vanilla_entities;
406
407 if item.is_empty() {
408 return None;
409 }
410
411 let half_width = f64::from(vanilla_entities::ITEM.dimensions.width) / 2.0;
412 let half_height = f64::from(vanilla_entities::ITEM.dimensions.height) / 2.0;
413
414 let (step_x, step_y, step_z) = face.offset();
415
416 let x = f64::from(pos.x())
418 + 0.5
419 + if step_x == 0 {
420 (rand::random::<f64>() - 0.5) * 0.5
421 } else {
422 f64::from(step_x) * (0.5 + half_width)
423 };
424 let y = f64::from(pos.y())
425 + 0.5
426 + if step_y == 0 {
427 (rand::random::<f64>() - 0.5) * 0.5
428 } else {
429 f64::from(step_y) * (0.5 + half_height)
430 }
431 - half_height;
432 let z = f64::from(pos.z())
433 + 0.5
434 + if step_z == 0 {
435 (rand::random::<f64>() - 0.5) * 0.5
436 } else {
437 f64::from(step_z) * (0.5 + half_width)
438 };
439
440 let delta_x = if step_x == 0 {
442 (rand::random::<f64>() - 0.5) * 0.2
443 } else {
444 f64::from(step_x) * 0.1
445 };
446 let delta_y = if step_y == 0 {
447 rand::random::<f64>() * 0.1
448 } else {
449 f64::from(step_y) * 0.1 + 0.1
450 };
451 let delta_z = if step_z == 0 {
452 (rand::random::<f64>() - 0.5) * 0.2
453 } else {
454 f64::from(step_z) * 0.1
455 };
456
457 let entity = self.spawn_item_with_velocity(
458 DVec3::new(x, y, z),
459 item,
460 DVec3::new(delta_x, delta_y, delta_z),
461 )?;
462 entity.set_default_pickup_delay();
463 Some(entity)
464 }
465
466 #[must_use]
470 pub fn get_entity_by_id(&self, id: i32) -> Option<SharedEntity> {
471 self.entity_manager.get_by_id(id)
472 }
473
474 pub(crate) fn contains_live_or_unloading_entity(&self, entity: &SharedEntity) -> bool {
476 self.entity_manager
477 .contains_live_or_unloading_entity(entity)
478 }
479
480 pub fn queue_world_change(&self, entity: SharedEntity, request: WorldChangeRequest) {
482 self.pending_world_changes.lock().push((entity, request));
483 }
484
485 pub(crate) fn drain_world_changes(&self) -> Vec<(SharedEntity, WorldChangeRequest)> {
486 mem::take(&mut *self.pending_world_changes.lock())
487 }
488
489 #[must_use]
493 pub fn get_accessible_entity_by_id(&self, id: i32) -> Option<SharedEntity> {
494 self.entity_manager.get_accessible_by_id(id)
495 }
496
497 #[must_use]
501 pub fn get_entity_by_uuid(&self, uuid: &uuid::Uuid) -> Option<SharedEntity> {
502 self.entity_manager.get_by_uuid(uuid)
503 }
504
505 #[must_use]
509 pub fn get_entities_in_aabb(&self, aabb: &WorldAabb) -> Vec<SharedEntity> {
510 self.entity_manager.get_entities_in_aabb(aabb)
511 }
512
513 #[must_use]
517 pub fn get_entities_in_aabb_matching(
518 &self,
519 aabb: &WorldAabb,
520 predicate: impl FnMut(&dyn Entity) -> bool,
521 ) -> Vec<SharedEntity> {
522 self.entity_manager
523 .get_entities_in_aabb_matching(aabb, predicate)
524 }
525
526 #[must_use]
530 pub fn has_entity_in_aabb_matching(
531 &self,
532 aabb: &WorldAabb,
533 predicate: impl FnMut(&dyn Entity) -> bool,
534 ) -> bool {
535 self.entity_manager
536 .has_entity_in_aabb_matching(aabb, predicate)
537 }
538
539 #[must_use]
543 pub fn get_entity_bounding_boxes_in_aabb_matching(
544 &self,
545 aabb: &WorldAabb,
546 predicate: impl FnMut(&dyn Entity) -> bool,
547 ) -> Vec<WorldAabb> {
548 self.entity_manager
549 .get_entity_bounding_boxes_in_aabb_matching(aabb, predicate)
550 }
551
552 #[must_use]
556 pub fn nearest_entity_in_aabb_matching(
557 &self,
558 aabb: &WorldAabb,
559 origin: DVec3,
560 predicate: impl FnMut(&dyn Entity) -> bool,
561 ) -> Option<SharedEntity> {
562 self.entity_manager
563 .nearest_entity_in_aabb_matching(aabb, origin, predicate)
564 }
565
566 #[must_use]
568 pub fn nearest_player(
569 &self,
570 position: DVec3,
571 max_distance: f64,
572 mut predicate: impl FnMut(&Player) -> bool,
573 ) -> Option<Arc<Player>> {
574 let max_distance_sqr = max_distance * max_distance;
575 let mut nearest: Option<(Arc<Player>, f64)> = None;
576 self.players.iter_players(|_, player| {
577 if predicate(player) {
578 let distance_sqr = player.position().distance_squared(position);
579 if nearest_player_distance_in_range(distance_sqr, max_distance, max_distance_sqr)
580 && nearest
581 .as_ref()
582 .is_none_or(|(_, current)| distance_sqr < *current)
583 {
584 nearest = Some((player.clone(), distance_sqr));
585 }
586 }
587 true
588 });
589 nearest.map(|(player, _)| player)
590 }
591
592 #[must_use]
594 pub fn nearest_player_distance_sqr(&self, position: DVec3) -> Option<f64> {
595 let mut nearest = None;
596 self.players.iter_players(|_, player| {
597 if player.is_spectator() {
598 return true;
599 }
600 let distance_sqr = player.position().distance_squared(position);
601 if nearest.is_none_or(|current| distance_sqr < current) {
602 nearest = Some(distance_sqr);
603 }
604 true
605 });
606 nearest
607 }
608
609 #[must_use]
614 pub fn get_pushable_entities(
615 &self,
616 pusher: &dyn Entity,
617 aabb: &WorldAabb,
618 ) -> Vec<SharedEntity> {
619 self.get_entities_in_aabb(aabb)
620 .into_iter()
621 .filter(|entity| entity.id() != pusher.id())
622 .filter(|entity| !entity.is_spectator())
623 .filter(|entity| entity.is_pushable())
624 .collect()
625 }
626
627 pub fn register_game_event_listener(
629 &self,
630 section_pos: SectionPos,
631 listener: SharedGameEventListener,
632 ) {
633 let chunk_pos = ChunkPos::new(section_pos.x(), section_pos.z());
634 let registry = self.game_event_listener_storage(chunk_pos);
635 if let Some(registry) = registry {
636 registry.register(section_pos.y(), listener);
637 }
638 }
639
640 pub fn unregister_game_event_listener(
642 &self,
643 section_pos: SectionPos,
644 listener: &SharedGameEventListener,
645 ) -> bool {
646 let chunk_pos = ChunkPos::new(section_pos.x(), section_pos.z());
647 let registry = self.game_event_listener_storage(chunk_pos);
648 registry.is_some_and(|registry| registry.unregister(section_pos.y(), listener))
649 }
650
651 pub(super) fn game_event_listener_storage(
653 &self,
654 chunk_pos: ChunkPos,
655 ) -> Option<Arc<GameEventListenerStorage>> {
656 self.chunk_map.with_full_chunk(chunk_pos, |chunk| {
657 Arc::clone(&chunk.game_event_listeners().registry)
658 })
659 }
660
661 pub fn game_event(
663 self: &Arc<Self>,
664 event: GameEventRef,
665 pos: BlockPos,
666 context: &GameEventContext,
667 ) {
668 self.game_event_at(
669 event,
670 DVec3::new(
671 f64::from(pos.x()) + 0.5,
672 f64::from(pos.y()) + 0.5,
673 f64::from(pos.z()) + 0.5,
674 ),
675 context,
676 );
677 }
678
679 pub fn game_event_at(
681 self: &Arc<Self>,
682 event: GameEventRef,
683 source_pos: DVec3,
684 context: &GameEventContext,
685 ) {
686 if !self.game_event_listener_count.has_any() {
687 return;
688 }
689 let radius = event.notification_radius.max(0);
690 let center = BlockPos::from(source_pos);
691 let section_min_x = SectionPos::block_to_section_coord(center.x() - radius);
692 let section_min_y = SectionPos::block_to_section_coord(center.y() - radius);
693 let section_min_z = SectionPos::block_to_section_coord(center.z() - radius);
694 let section_max_x = SectionPos::block_to_section_coord(center.x() + radius);
695 let section_max_y = SectionPos::block_to_section_coord(center.y() + radius);
696 let section_max_z = SectionPos::block_to_section_coord(center.z() + radius);
697 let mut dispatcher = GameEventDispatcher::new(self, event, source_pos, context);
698
699 for section_x in section_min_x..=section_max_x {
700 for section_z in section_min_z..=section_max_z {
701 let registry =
702 self.game_event_listener_storage(ChunkPos::new(section_x, section_z));
703 if let Some(registry) = registry {
704 dispatcher.visit_chunk(®istry, section_min_y, section_max_y);
705 }
706 }
707 }
708
709 dispatcher.finish();
710 }
711}