1use std::sync::Arc;
8
9use glam::DVec3;
10use rustc_hash::FxHashSet;
11use steel_protocol::packets::game::{
12 AttributeSnapshot, CAddEntity, CRemoveEntities, CSetEntityData, CSetEntityLink,
13 CSetEntityMotion, CSetEquipment, CSetPassengers, CUpdateAttributes, EquipmentSlotItem,
14 to_angle_byte,
15};
16use steel_registry::entity_data::DataValue;
17use steel_registry::{RegistryEntry, vanilla_entities};
18use steel_utils::ChunkPos;
19use steel_utils::locks::{SyncMutex, SyncRwLock};
20
21use crate::chunk::player_chunk_view::PlayerChunkView;
22use crate::entity::leash::Leashable;
23use crate::entity::{
24 Entity, EntityMovementSyncPacket, MobEffectSyncPacket, ServerEntityMovementSyncState,
25 ServerEntityMovementSyncUpdate, SharedEntity, WeakEntity,
26};
27use crate::player::Player;
28
29const BLOCKS_PER_CHUNK: f64 = 16.0;
30
31pub struct EntityTracker {
33 entities: scc::HashMap<i32, TrackedEntity>,
35}
36
37pub struct EntityChangeSenders<
39 Movement,
40 SelfMovement,
41 EntityData,
42 Attributes,
43 MobEffects,
44 Equipment,
45 Passengers,
46 EntityLink,
47> {
48 pub movement: Movement,
50 pub self_movement: SelfMovement,
52 pub entity_data: EntityData,
54 pub attributes: Attributes,
56 pub mob_effects: MobEffects,
58 pub equipment: Equipment,
60 pub passengers: Passengers,
62 pub entity_link: EntityLink,
64}
65
66struct TrackedEntity {
68 entity: WeakEntity,
70 server_entity: SyncMutex<ServerEntityMovementSyncState>,
72 last_passenger_ids: SyncMutex<Vec<i32>>,
74 last_leash_holder_id: SyncMutex<Option<i32>>,
76 tracking_range: EntityTrackingRange,
78 registered_chunk: ChunkPos,
80 seen_by: SyncRwLock<FxHashSet<i32>>,
82}
83
84#[derive(Debug, Clone, Copy)]
85struct EntityTrackingRange {
86 block_radius: f64,
87}
88
89impl EntityTrackingRange {
90 fn from_client_chunk_range(client_chunk_range: i32) -> Self {
91 Self {
92 block_radius: f64::from(client_chunk_range) * BLOCKS_PER_CHUNK,
93 }
94 }
95
96 fn is_disabled(self) -> bool {
97 self.block_radius <= 0.0
98 }
99
100 fn visible_radius(self, player_view_distance: u8) -> f64 {
101 self.block_radius
102 .min(f64::from(player_view_distance) * BLOCKS_PER_CHUNK)
103 }
104}
105
106impl Default for EntityTracker {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112impl EntityTracker {
113 #[must_use]
115 pub fn new() -> Self {
116 Self {
117 entities: scc::HashMap::new(),
118 }
119 }
120
121 pub fn add(
133 &self,
134 entity: &SharedEntity,
135 get_players_in_chunk: impl Fn(ChunkPos) -> Vec<i32>,
136 get_player: impl Fn(i32) -> Option<Arc<Player>>,
137 ) {
138 assert!(
139 !entity.is_removed(),
140 "cannot add removed entity {} to tracker",
141 entity.id()
142 );
143
144 let entity_id = entity.id();
145 let tracking_range = EntityTrackingRange::from_client_chunk_range(
146 entity.entity_type().client_tracking_range,
147 );
148 if tracking_range.is_disabled() {
149 return;
150 }
151
152 let pos = entity.position();
153 let registered_chunk = ChunkPos::from_entity_pos(pos);
154
155 let players_to_notify = Self::visible_players_for_entity(
156 entity_id,
157 entity.as_ref(),
158 registered_chunk,
159 tracking_range,
160 &get_players_in_chunk,
161 &get_player,
162 );
163 let player_ids_to_notify: Vec<i32> = players_to_notify.iter().copied().collect();
164
165 let tracked_entity = TrackedEntity {
166 entity: Arc::downgrade(entity),
167 server_entity: SyncMutex::new(ServerEntityMovementSyncState::new(
168 pos,
169 entity.velocity(),
170 entity.on_ground(),
171 entity.rotation(),
172 entity.head_yaw(),
173 entity.entity_type().update_interval,
174 entity.entity_type().track_deltas,
175 )),
176 last_passenger_ids: SyncMutex::new(self.direct_tracked_passenger_ids(entity.as_ref())),
177 last_leash_holder_id: SyncMutex::new(leash_holder_id(entity.as_ref())),
178 tracking_range,
179 registered_chunk,
180 seen_by: SyncRwLock::new(players_to_notify),
181 };
182
183 assert!(
184 self.entities.insert_sync(entity_id, tracked_entity).is_ok(),
185 "entity {entity_id} is already tracked"
186 );
187
188 for player_id in player_ids_to_notify {
190 if let Some(player) = get_player(player_id) {
191 self.send_spawn_packets(entity, &player);
192 }
193 }
194 }
195
196 pub fn remove(&self, entity_id: i32, get_player: impl Fn(i32) -> Option<Arc<Player>>) {
198 if let Some((_, tracked)) = self.entities.remove_sync(&entity_id) {
199 let entity = tracked.entity.upgrade();
200 for player_id in tracked.seen_by.read().iter() {
202 if let Some(player) = get_player(*player_id) {
203 if let Some(entity) = &entity
204 && let Some(packet) =
205 self.vehicle_passenger_packet_for_player(entity.as_ref(), *player_id)
206 {
207 player.send_packet(packet);
208 }
209 player.send_packet(CRemoveEntities::single(entity_id));
210 }
211 }
212 }
213 }
214
215 pub fn update_player(
221 &self,
222 player: &Player,
223 view: &PlayerChunkView,
224 is_chunk_sent: impl Fn(ChunkPos) -> bool,
225 ) {
226 let player_id = player.id();
227 let player_pos = player.position();
228 let player_view_distance = view.view_distance;
229
230 let mut entities_to_despawn = Vec::new();
231 let mut entities_to_spawn = Vec::new();
232 let mut dead_entities = Vec::new();
233
234 self.entities.iter_sync(|entity_id, tracked| {
235 let entity_id = *entity_id;
236 let Some(entity) = tracked.entity.upgrade() else {
237 dead_entities.push(entity_id);
238 return true;
239 };
240
241 let visible = !entity.is_removed()
242 && entity_id != player_id
243 && view.contains(tracked.registered_chunk)
244 && is_chunk_sent(tracked.registered_chunk)
245 && entity.broadcast_to_player(player)
246 && is_within_tracking_distance(
247 entity.position(),
248 player_pos,
249 effective_tracking_range(entity.as_ref(), tracked.tracking_range),
250 player_view_distance,
251 );
252
253 let mut despawn = false;
254 {
255 let mut seen_by = tracked.seen_by.write();
256 if visible {
257 if seen_by.insert(player_id) {
258 entities_to_spawn.push(Arc::clone(&entity));
259 }
260 } else if seen_by.remove(&player_id) {
261 despawn = true;
262 }
263 }
264
265 if despawn {
266 let vehicle_packet =
267 self.vehicle_passenger_packet_for_player(entity.as_ref(), player_id);
268 entities_to_despawn.push((entity_id, vehicle_packet));
269 }
270
271 true
272 });
273
274 for (entity_id, vehicle_packet) in entities_to_despawn {
275 if let Some(packet) = vehicle_packet {
276 player.send_packet(packet);
277 }
278 player.send_packet(CRemoveEntities::single(entity_id));
279 }
280
281 for entity in entities_to_spawn {
282 self.send_spawn_packets(&entity, player);
283 }
284
285 for entity_id in dead_entities {
287 self.remove_dead_entity(entity_id);
288 }
289 }
290
291 #[expect(
295 clippy::too_many_lines,
296 reason = "entity tracker fanout mirrors vanilla sendChanges ordering"
297 )]
298 pub fn send_changes<
299 Movement,
300 SelfMovement,
301 EntityData,
302 Attributes,
303 MobEffects,
304 Equipment,
305 Passengers,
306 EntityLink,
307 >(
308 &self,
309 get_players_in_chunk: impl Fn(ChunkPos) -> Vec<i32>,
310 get_player: impl Fn(i32) -> Option<Arc<Player>>,
311 mut senders: EntityChangeSenders<
312 Movement,
313 SelfMovement,
314 EntityData,
315 Attributes,
316 MobEffects,
317 Equipment,
318 Passengers,
319 EntityLink,
320 >,
321 ) where
322 Movement: FnMut(i32, EntityMovementSyncPacket),
323 SelfMovement: FnMut(i32, EntityMovementSyncPacket),
324 EntityData: FnMut(i32, Vec<DataValue>),
325 Attributes: FnMut(i32, Vec<AttributeSnapshot>),
326 MobEffects: FnMut(i32, MobEffectSyncPacket),
327 Equipment: FnMut(i32, CSetEquipment),
328 Passengers: FnMut(i32, CSetPassengers),
329 EntityLink: FnMut(i32, CSetEntityLink),
330 {
331 let mut dead_entities = Vec::new();
332 let mut entities_to_refresh = Vec::new();
333 let mut passenger_packets_to_send = Vec::new();
334 let mut packets_to_broadcast = Vec::new();
335 let mut self_movement_packets = Vec::new();
336 let mut entity_data_to_broadcast = Vec::new();
337 let mut attributes_to_broadcast = Vec::new();
338 let mut mob_effect_packets_to_send = Vec::new();
339 let mut equipment_to_broadcast = Vec::new();
340 let mut entity_links_to_broadcast = Vec::new();
341
342 self.entities.iter_sync(|entity_id, tracked| {
343 let entity_id = *entity_id;
344 let Some(entity) = tracked.entity.upgrade() else {
345 dead_entities.push(entity_id);
346 return true;
347 };
348
349 if entity.is_removed() {
350 return true;
351 }
352
353 entity.update_data_before_sync();
354
355 let passenger_ids = self.direct_tracked_passenger_ids(entity.as_ref());
356 {
357 let mut last_passenger_ids = tracked.last_passenger_ids.lock();
358 if *last_passenger_ids != passenger_ids {
359 let changed_player_passenger_ids = direct_player_passenger_delta(
360 &last_passenger_ids,
361 &passenger_ids,
362 &get_player,
363 );
364 let seen_by = tracked.seen_by.read();
365 for player_id in seen_by.iter() {
366 if changed_player_passenger_ids.contains(player_id) {
367 continue;
368 }
369 passenger_packets_to_send.push((
370 *player_id,
371 CSetPassengers::new(
372 entity_id,
373 self.direct_passenger_ids_seen_by_player(
374 entity.as_ref(),
375 *player_id,
376 ),
377 ),
378 ));
379 }
380 *last_passenger_ids = passenger_ids;
381 entities_to_refresh.push(entity_id);
382 }
383 }
384
385 let dirty_entity_data = entity.pack_dirty_entity_data();
386 let has_dirty_entity_data = dirty_entity_data.is_some();
387 let result =
388 tracked
389 .server_entity
390 .lock()
391 .record_send_changes(ServerEntityMovementSyncUpdate {
392 entity_type: entity.entity_type(),
393 vertical_collision: entity.vertical_collision(),
394 horizontal_collision: entity.horizontal_collision(),
395 requires_precise_position: entity.base().requires_precise_position(),
396 entity_id,
397 is_passenger: entity.is_passenger(),
398 position: entity.position(),
399 velocity: entity.velocity(),
400 body_rotation: entity.rotation(),
401 head_yaw: entity.head_yaw(),
402 on_ground: entity.on_ground(),
403 needs_velocity_sync: entity.needs_velocity_sync(),
404 has_dirty_entity_data,
405 force_velocity_sync: entity.forces_fall_flying_velocity_sync(),
406 });
407 if result.should_clear_velocity_sync() {
408 entity.clear_velocity_sync();
409 }
410 result.for_each_packet(|packet| packets_to_broadcast.push((entity_id, packet)));
411 if entity.hurt_marked() {
412 let velocity = entity.velocity();
413 packets_to_broadcast.push((
414 entity_id,
415 EntityMovementSyncPacket::from(CSetEntityMotion::new(entity_id, velocity)),
416 ));
417 if entity.entity_type() == &vanilla_entities::PLAYER {
418 self_movement_packets.push((
419 entity_id,
420 EntityMovementSyncPacket::from(CSetEntityMotion::new(entity_id, velocity)),
421 ));
422 }
423 entity.clear_hurt_mark();
424 }
425 if let Some(dirty_entity_data) = dirty_entity_data {
426 entity_data_to_broadcast.push((entity_id, dirty_entity_data));
427 }
428 if let Some(living) = entity.as_living_entity() {
429 let dirty_attributes = living.drain_dirty_syncable_attributes();
430 if !dirty_attributes.is_empty() {
431 attributes_to_broadcast.push((entity_id, dirty_attributes));
432 }
433 let dirty_mob_effects = living.drain_dirty_mob_effects();
434 if !dirty_mob_effects.is_empty() {
435 let mut recipient_ids = FxHashSet::default();
436 if entity.entity_type() == &vanilla_entities::PLAYER
437 && get_player(entity_id).is_some()
438 {
439 recipient_ids.insert(entity_id);
440 }
441 for passenger in entity.passengers() {
442 let passenger_id = passenger.id();
443 if passenger.entity_type() == &vanilla_entities::PLAYER
444 && get_player(passenger_id).is_some()
445 {
446 recipient_ids.insert(passenger_id);
447 }
448 }
449 for recipient_id in recipient_ids {
450 for change in &dirty_mob_effects {
451 mob_effect_packets_to_send.push((
452 recipient_id,
453 change.packet(entity_id, recipient_id == entity_id),
454 ));
455 }
456 }
457 }
458 let dirty_equipment = living.drain_dirty_equipment();
459 if !dirty_equipment.is_empty() {
460 equipment_to_broadcast.push((entity_id, dirty_equipment));
461 }
462 }
463
464 let leash_holder_id = leash_holder_id(entity.as_ref());
465 {
466 let mut last_leash_holder_id = tracked.last_leash_holder_id.lock();
467 if *last_leash_holder_id != leash_holder_id {
468 entity_links_to_broadcast.push((
469 entity_id,
470 CSetEntityLink::new(entity_id, leash_holder_id.unwrap_or_default()),
471 ));
472 *last_leash_holder_id = leash_holder_id;
473 }
474 }
475
476 true
477 });
478
479 for entity_id in dead_entities {
480 self.remove_dead_entity(entity_id);
481 }
482
483 for (player_id, packet) in passenger_packets_to_send {
484 (senders.passengers)(player_id, packet);
485 }
486
487 for entity_id in entities_to_refresh {
488 self.refresh_entity_players(entity_id, &get_players_in_chunk, &get_player);
489 }
490
491 for (entity_id, packet) in packets_to_broadcast {
492 (senders.movement)(entity_id, packet);
493 }
494
495 for (player_id, packet) in self_movement_packets {
496 (senders.self_movement)(player_id, packet);
497 }
498
499 for (entity_id, dirty_entity_data) in entity_data_to_broadcast {
500 (senders.entity_data)(entity_id, dirty_entity_data);
501 }
502
503 for (entity_id, dirty_equipment) in equipment_to_broadcast {
504 (senders.equipment)(entity_id, CSetEquipment::new(entity_id, dirty_equipment));
505 }
506
507 for (entity_id, dirty_attributes) in attributes_to_broadcast {
508 (senders.attributes)(entity_id, dirty_attributes);
509 }
510
511 for (player_id, packet) in mob_effect_packets_to_send {
512 (senders.mob_effects)(player_id, packet);
513 }
514
515 for (entity_id, packet) in entity_links_to_broadcast {
516 (senders.entity_link)(entity_id, packet);
517 }
518 }
519
520 pub fn on_player_leave(&self, player: &Player) {
525 let player_id = player.id();
526 let mut entities_to_despawn = Vec::new();
527 let mut dead_entities = Vec::new();
528
529 self.entities.iter_sync(|entity_id, tracked| {
530 if tracked.seen_by.write().remove(&player_id) {
531 entities_to_despawn.push(*entity_id);
532 }
533 if tracked.entity.strong_count() == 0 {
534 dead_entities.push(*entity_id);
535 }
536 true
537 });
538
539 for entity_id in entities_to_despawn {
540 player.send_packet(CRemoveEntities::single(entity_id));
541 }
542
543 for entity_id in dead_entities {
544 self.remove_dead_entity(entity_id);
545 }
546 }
547
548 pub fn on_entity_section_change(
553 &self,
554 entity_id: i32,
555 old_chunk: ChunkPos,
556 new_chunk: ChunkPos,
557 get_players_in_chunk: impl Fn(ChunkPos) -> Vec<i32>,
558 get_player: impl Fn(i32) -> Option<Arc<Player>>,
559 ) {
560 let mut players_to_remove = Vec::new();
561 let mut players_to_add = Vec::new();
562 let mut entity_to_spawn = None;
563
564 self.entities.update_sync(&entity_id, |_, tracked| {
565 if old_chunk != new_chunk {
566 tracked.registered_chunk = new_chunk;
567 }
568
569 let Some(entity) = tracked.entity.upgrade() else {
570 return;
571 };
572
573 let new_seen_by = Self::visible_players_for_entity(
574 entity_id,
575 entity.as_ref(),
576 new_chunk,
577 tracked.tracking_range,
578 &get_players_in_chunk,
579 &get_player,
580 );
581
582 let mut seen_by = tracked.seen_by.write();
583 players_to_remove.extend(seen_by.difference(&new_seen_by).copied());
584 players_to_add.extend(new_seen_by.difference(&seen_by).copied());
585 *seen_by = new_seen_by;
586 entity_to_spawn = Some(entity);
587 });
588
589 let Some(entity) = entity_to_spawn else {
590 return;
591 };
592
593 for player_id in players_to_remove {
594 if let Some(player) = get_player(player_id) {
595 if let Some(packet) =
596 self.vehicle_passenger_packet_for_player(entity.as_ref(), player_id)
597 {
598 player.send_packet(packet);
599 }
600 player.send_packet(CRemoveEntities::single(entity_id));
601 }
602 }
603
604 for player_id in players_to_add {
605 if let Some(player) = get_player(player_id) {
606 self.send_spawn_packets(&entity, &player);
607 }
608 }
609 }
610
611 fn refresh_entity_players(
612 &self,
613 entity_id: i32,
614 get_players_in_chunk: &impl Fn(ChunkPos) -> Vec<i32>,
615 get_player: &impl Fn(i32) -> Option<Arc<Player>>,
616 ) {
617 let mut players_to_remove = Vec::new();
618 let mut players_to_add = Vec::new();
619 let mut entity_to_spawn = None;
620
621 self.entities.update_sync(&entity_id, |_, tracked| {
622 let Some(entity) = tracked.entity.upgrade() else {
623 return;
624 };
625
626 let new_seen_by = Self::visible_players_for_entity(
627 entity_id,
628 entity.as_ref(),
629 tracked.registered_chunk,
630 tracked.tracking_range,
631 get_players_in_chunk,
632 get_player,
633 );
634
635 let mut seen_by = tracked.seen_by.write();
636 players_to_remove.extend(seen_by.difference(&new_seen_by).copied());
637 players_to_add.extend(new_seen_by.difference(&seen_by).copied());
638 *seen_by = new_seen_by;
639 entity_to_spawn = Some(entity);
640 });
641
642 let Some(entity) = entity_to_spawn else {
643 return;
644 };
645
646 for player_id in players_to_remove {
647 if let Some(player) = get_player(player_id) {
648 if let Some(packet) =
649 self.vehicle_passenger_packet_for_player(entity.as_ref(), player_id)
650 {
651 player.send_packet(packet);
652 }
653 player.send_packet(CRemoveEntities::single(entity_id));
654 }
655 }
656
657 for player_id in players_to_add {
658 if let Some(player) = get_player(player_id) {
659 self.send_spawn_packets(&entity, &player);
660 }
661 }
662 }
663
664 #[must_use]
666 pub fn count(&self) -> usize {
667 self.entities.len()
668 }
669
670 #[must_use]
672 pub fn tracking_player_ids(&self, entity_id: i32) -> Vec<i32> {
673 self.entities
674 .read_sync(&entity_id, |_, tracked| {
675 tracked.seen_by.read().iter().copied().collect()
676 })
677 .unwrap_or_default()
678 }
679
680 fn remove_dead_entity(&self, entity_id: i32) {
681 let _ = self.entities.remove_sync(&entity_id);
684 }
685
686 fn visible_players_for_entity(
687 entity_id: i32,
688 entity: &dyn Entity,
689 entity_chunk: ChunkPos,
690 tracking_range: EntityTrackingRange,
691 get_players_in_chunk: &impl Fn(ChunkPos) -> Vec<i32>,
692 get_player: &impl Fn(i32) -> Option<Arc<Player>>,
693 ) -> FxHashSet<i32> {
694 let entity_pos = entity.position();
695 let tracking_range = effective_tracking_range(entity, tracking_range);
696 let mut players = FxHashSet::default();
697 if entity.is_removed() {
698 return players;
699 }
700
701 for player_id in get_players_in_chunk(entity_chunk) {
702 if player_id == entity_id {
703 continue;
704 }
705
706 let Some(player) = get_player(player_id) else {
707 continue;
708 };
709
710 if entity.broadcast_to_player(&player)
711 && is_within_tracking_distance(
712 entity_pos,
713 player.position(),
714 tracking_range,
715 player.view_distance(),
716 )
717 {
718 players.insert(player_id);
719 }
720 }
721
722 players
723 }
724
725 fn send_spawn_packets(&self, entity: &SharedEntity, player: &Player) {
726 let entity_id = entity.id();
727 self.spawn_pairing(entity, player.id())
728 .send_to(entity_id, player);
729 }
730
731 fn spawn_pairing(&self, entity: &SharedEntity, player_id: i32) -> EntitySpawnPairing {
732 EntitySpawnPairing::from_entity(
733 entity,
734 self.passenger_pairing_packets_for_player(entity.as_ref(), player_id),
735 )
736 }
737
738 fn passenger_pairing_packets_for_player(
739 &self,
740 entity: &dyn Entity,
741 player_id: i32,
742 ) -> Vec<CSetPassengers> {
743 let mut packets = Vec::new();
744 let passenger_ids = self.direct_passenger_ids_seen_by_player(entity, player_id);
745 if !passenger_ids.is_empty() {
746 packets.push(CSetPassengers::new(entity.id(), passenger_ids));
747 }
748
749 if let Some(vehicle) = entity.vehicle()
750 && self.entity_seen_by_player(vehicle.id(), player_id)
751 {
752 packets.push(CSetPassengers::new(
753 vehicle.id(),
754 self.direct_passenger_ids_seen_by_player(vehicle.as_ref(), player_id),
755 ));
756 }
757
758 packets
759 }
760
761 fn vehicle_passenger_packet_for_player(
762 &self,
763 entity: &dyn Entity,
764 player_id: i32,
765 ) -> Option<CSetPassengers> {
766 let vehicle = entity.vehicle()?;
767 if !self.entity_seen_by_player(vehicle.id(), player_id) {
768 return None;
769 }
770 Some(CSetPassengers::new(
771 vehicle.id(),
772 self.direct_passenger_ids_seen_by_player(vehicle.as_ref(), player_id),
773 ))
774 }
775
776 fn direct_tracked_passenger_ids(&self, entity: &dyn Entity) -> Vec<i32> {
777 entity
778 .passengers()
779 .into_iter()
780 .filter(|passenger| self.is_entity_tracked(passenger.id()))
781 .map(|passenger| passenger.id())
782 .collect()
783 }
784
785 fn direct_passenger_ids_seen_by_player(&self, entity: &dyn Entity, player_id: i32) -> Vec<i32> {
786 entity
787 .passengers()
788 .into_iter()
789 .filter(|passenger| {
790 passenger.id() == player_id || self.entity_seen_by_player(passenger.id(), player_id)
791 })
792 .map(|passenger| passenger.id())
793 .collect()
794 }
795
796 fn is_entity_tracked(&self, entity_id: i32) -> bool {
797 self.entities.read_sync(&entity_id, |_, _| ()).is_some()
798 }
799
800 fn entity_seen_by_player(&self, entity_id: i32, player_id: i32) -> bool {
801 self.entities
802 .read_sync(&entity_id, |_, tracked| {
803 tracked.seen_by.read().contains(&player_id)
804 })
805 .unwrap_or(false)
806 }
807}
808
809fn leash_holder_id(entity: &dyn Entity) -> Option<i32> {
810 entity
811 .as_leashable()
812 .and_then(Leashable::leash_holder)
813 .map(|holder| holder.id())
814}
815
816fn is_within_tracking_distance(
817 entity_pos: DVec3,
818 player_pos: DVec3,
819 tracking_range: EntityTrackingRange,
820 player_view_distance: u8,
821) -> bool {
822 let visible_radius = tracking_range.visible_radius(player_view_distance);
823 let x = player_pos.x - entity_pos.x;
824 let z = player_pos.z - entity_pos.z;
825 x * x + z * z <= visible_radius * visible_radius
826}
827
828fn effective_tracking_range(
829 entity: &dyn Entity,
830 base_range: EntityTrackingRange,
831) -> EntityTrackingRange {
832 let mut range = base_range;
833 let mut visited = FxHashSet::default();
834 visited.insert(entity.id());
835 add_passenger_tracking_ranges(entity, &mut range, &mut visited);
836 range
837}
838
839fn add_passenger_tracking_ranges(
840 entity: &dyn Entity,
841 range: &mut EntityTrackingRange,
842 visited: &mut FxHashSet<i32>,
843) {
844 for passenger in entity.passengers() {
845 if !visited.insert(passenger.id()) {
846 continue;
847 }
848 let passenger_range = EntityTrackingRange::from_client_chunk_range(
849 passenger.entity_type().client_tracking_range,
850 );
851 range.block_radius = range.block_radius.max(passenger_range.block_radius);
852 add_passenger_tracking_ranges(passenger.as_ref(), range, visited);
853 }
854}
855
856struct EntitySpawnPairing {
857 spawn_packet: CAddEntity,
858 entity_data: Vec<DataValue>,
859 attributes: Vec<AttributeSnapshot>,
860 equipment: Vec<EquipmentSlotItem>,
861 passenger_packets: Vec<CSetPassengers>,
862 entity_link_packet: Option<CSetEntityLink>,
863}
864
865impl EntitySpawnPairing {
866 fn from_entity(entity: &SharedEntity, passenger_packets: Vec<CSetPassengers>) -> Self {
867 entity.update_data_before_sync();
868
869 let pos = entity.spawn_position();
870 let vel = entity.velocity();
871 let (yaw, pitch) = entity.rotation();
872 let head_yaw = entity.head_yaw();
873 let entity_type_id = entity.entity_type().id() as i32;
874
875 let x_rot = to_angle_byte(pitch);
878 let y_rot = to_angle_byte(yaw);
879 let head_y_rot = to_angle_byte(head_yaw);
880
881 let (attributes, equipment) = entity.as_living_entity().map_or_else(
882 || (Vec::new(), Vec::new()),
883 |living| {
884 (
885 living.pack_syncable_attributes(),
886 living.pack_all_equipment(),
887 )
888 },
889 );
890
891 Self {
892 spawn_packet: CAddEntity {
893 id: entity.id(),
894 uuid: entity.uuid(),
895 entity_type: entity_type_id,
896 position: pos,
897 velocity: vel,
898 x_rot,
899 y_rot,
900 head_y_rot,
901 data: entity.spawn_data(),
902 },
903 entity_data: entity.pack_all_entity_data(),
904 attributes,
905 equipment,
906 passenger_packets,
907 entity_link_packet: leash_holder_id(entity.as_ref())
908 .map(|holder_id| CSetEntityLink::new(entity.id(), holder_id)),
909 }
910 }
911
912 fn send_to(self, entity_id: i32, player: &Player) {
913 player.send_bundle(|bundle| {
914 bundle.add(self.spawn_packet);
915
916 if !self.entity_data.is_empty() {
917 bundle.add(CSetEntityData::new(entity_id, self.entity_data));
918 }
919
920 if !self.attributes.is_empty() {
921 bundle.add(CUpdateAttributes::new(entity_id, self.attributes));
922 }
923
924 if !self.equipment.is_empty() {
925 bundle.add(CSetEquipment::new(entity_id, self.equipment));
926 }
927
928 for packet in self.passenger_packets {
929 bundle.add(packet);
930 }
931
932 if let Some(packet) = self.entity_link_packet {
933 bundle.add(packet);
934 }
935 });
936 }
937}
938
939fn direct_player_passenger_delta(
940 old_passenger_ids: &[i32],
941 new_passenger_ids: &[i32],
942 get_player: &impl Fn(i32) -> Option<Arc<Player>>,
943) -> Vec<i32> {
944 let old_passenger_ids = old_passenger_ids.iter().copied().collect::<FxHashSet<_>>();
945 let new_passenger_ids = new_passenger_ids.iter().copied().collect::<FxHashSet<_>>();
946 old_passenger_ids
947 .symmetric_difference(&new_passenger_ids)
948 .copied()
949 .filter(|entity_id| get_player(*entity_id).is_some())
950 .collect()
951}
952
953#[cfg(test)]
954mod tests;