1mod fire_freeze;
7mod movement;
8mod persistence;
9mod relationships;
10
11pub use fire_freeze::EntityFireFreezeState;
12pub use movement::{
13 EntityGroundContact, EntityMovement, EntityMovementEmission, EntityMovementFlags,
14 EntityMovementProgress, EntityVerticalMovementStateUpdate,
15};
16pub use persistence::{EntityBaseLoad, EntityBaseSaveData};
17pub use relationships::PendingWorldChangeToken;
18use relationships::{EntityLifecycleState, EntityRelationshipState};
19
20use std::{
21 collections::VecDeque,
22 sync::{Arc, Weak},
23};
24
25use glam::DVec3;
26use simdnbt::owned::NbtCompound;
27use steel_math::{DEGREE_90, DEGREE_360};
28use steel_registry::entity_data::EntityPose;
29use steel_registry::entity_type::EntityDimensions;
30use steel_registry::vanilla_entities;
31use steel_utils::locks::SyncMutex;
32use steel_utils::{BlockPos, BlockStateId, WorldAabb};
33use text_components::TextComponent;
34use uuid::Uuid;
35
36use crate::entity::fluid_contact::EntityFluidContact;
37use crate::entity::{
38 EntityGeneration, EntityLevelCallback, EntityMoveError, InsideBlockEffectType,
39 NullEntityCallback, RemovalReason, SharedEntity,
40};
41use crate::physics::EntityPhysicsState;
42use crate::portal::{PortalKind, PortalProcessResult, PortalProcessor};
43use crate::world::World;
44
45const BOARDING_COOLDOWN: i32 = 60;
46const PISTON_MOVEMENT_LIMIT: f64 = 0.51;
47const PISTON_ZERO_MOVEMENT_EPSILON: f64 = 1.0e-7;
48const PISTON_APPLIED_MOVEMENT_EPSILON: f64 = 1.0e-5;
49const STUCK_SPEED_MULTIPLIER_EPSILON: f64 = 1.0e-7;
50const MOVEMENT_TRACE_LIMIT: usize = 100;
51const MOVEMENT_TRACE_POSITION_EPSILON_SQ: f64 = 9.999_999_4e-11;
52pub const DEFAULT_TICKS_REQUIRED_TO_FREEZE: i32 = 140;
54pub const DEFAULT_MAX_AIR_SUPPLY: i32 = 300;
56pub const MAX_ENTITY_TAGS: usize = 1024;
58const FIRE_IGNITE_TICKS: i32 = 8 * 20;
59const LAVA_IGNITE_TICKS: i32 = 15 * 20;
60
61fn require_finite_position(position: DVec3, field: &str) {
62 assert!(
63 position.is_finite(),
64 "entity {field} must be finite: {position:?}"
65 );
66}
67
68fn normalize_rotation(rotation: (f32, f32)) -> (f32, f32) {
69 assert!(
70 rotation.0.is_finite() && rotation.1.is_finite(),
71 "entity rotation must be finite: {rotation:?}"
72 );
73 (
74 rotation.0 % DEGREE_360,
75 rotation.1.clamp(-DEGREE_90, DEGREE_90) % DEGREE_360,
76 )
77}
78
79#[derive(Debug, Clone, Copy)]
80pub(crate) struct EntityPhysicsStateInput {
81 pub(crate) max_up_step: f32,
82 pub(crate) backs_off_from_edge: bool,
83 pub(crate) descending: bool,
84 pub(crate) can_walk_on_powder_snow: bool,
85 pub(crate) is_falling_block: bool,
86}
87
88#[derive(Debug, Default)]
89struct EntityMovementTrace {
90 movement_this_tick: VecDeque<EntityMovement>,
91 final_movements_this_tick: Vec<EntityMovement>,
92}
93
94impl EntityMovementTrace {
95 fn record(&mut self, movement: EntityMovement) {
96 if self.movement_this_tick.len() >= MOVEMENT_TRACE_LIMIT {
97 let first = self.movement_this_tick.pop_front();
98 let second = self.movement_this_tick.pop_front();
99 match (first, second) {
100 (Some(first), Some(second)) => self
101 .movement_this_tick
102 .push_front(EntityMovement::new(first.from(), second.to())),
103 (Some(first), None) => self.movement_this_tick.push_front(first),
104 (None, _) => {}
105 }
106 }
107
108 self.movement_this_tick.push_back(movement);
109 }
110
111 fn remove_latest_recording(&mut self) {
112 self.movement_this_tick.pop_back();
113 }
114
115 fn reset(&mut self) {
116 self.movement_this_tick.clear();
117 self.final_movements_this_tick.clear();
118 }
119
120 fn take_for_block_effects(
121 &mut self,
122 old_position: DVec3,
123 position: DVec3,
124 ) -> Vec<EntityMovement> {
125 self.final_movements_this_tick.clear();
126 self.final_movements_this_tick
127 .extend(self.movement_this_tick.drain(..));
128
129 if let Some(last_movement) = self.final_movements_this_tick.last().copied() {
130 if (last_movement.to() - position).length_squared() > MOVEMENT_TRACE_POSITION_EPSILON_SQ
131 {
132 self.final_movements_this_tick
133 .push(EntityMovement::new(last_movement.to(), position));
134 }
135 } else {
136 self.final_movements_this_tick
137 .push(EntityMovement::new(old_position, position));
138 }
139
140 self.final_movements_this_tick.as_slice().to_vec()
141 }
142
143 fn last_for_block_effects(&self) -> Vec<EntityMovement> {
144 self.final_movements_this_tick.as_slice().to_vec()
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq)]
150struct EntityPistonMovement {
151 deltas: [f64; 3],
152 game_time: i64,
153}
154
155impl EntityPistonMovement {
156 const fn new() -> Self {
157 Self {
158 deltas: [0.0; 3],
159 game_time: 0,
160 }
161 }
162
163 fn limit_movement(&mut self, movement: DVec3, current_game_time: i64) -> DVec3 {
164 if movement.length_squared() <= PISTON_ZERO_MOVEMENT_EPSILON {
165 return movement;
166 }
167
168 if current_game_time != self.game_time {
169 self.deltas = [0.0; 3];
170 self.game_time = current_game_time;
171 }
172
173 if movement.x != 0.0 {
174 return self.apply_axis_restriction(0, movement.x, DVec3::X);
175 }
176 if movement.y != 0.0 {
177 return self.apply_axis_restriction(1, movement.y, DVec3::Y);
178 }
179 if movement.z != 0.0 {
180 return self.apply_axis_restriction(2, movement.z, DVec3::Z);
181 }
182
183 DVec3::ZERO
184 }
185
186 fn apply_axis_restriction(&mut self, axis: usize, amount: f64, unit: DVec3) -> DVec3 {
187 let limited =
188 (amount + self.deltas[axis]).clamp(-PISTON_MOVEMENT_LIMIT, PISTON_MOVEMENT_LIMIT);
189 let applied = limited - self.deltas[axis];
190 self.deltas[axis] = limited;
191
192 if applied.abs() <= PISTON_APPLIED_MOVEMENT_EPSILON {
193 DVec3::ZERO
194 } else {
195 unit * applied
196 }
197 }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq)]
202pub struct EntityAmethystStepSound {
203 pub volume: f32,
205 pub pitch: f32,
207}
208
209#[derive(Debug, Clone, Copy, PartialEq)]
216pub struct EntityBaseState {
217 tick_count: i32,
218 first_tick: bool,
219 position: DVec3,
220 old_position: DVec3,
221 last_known_position: Option<DVec3>,
222 last_known_speed: DVec3,
223 velocity: DVec3,
224 rotation: (f32, f32),
225 old_rotation: (f32, f32),
226 pose: EntityPose,
227 dimensions: EntityDimensions,
228 bounding_box: WorldAabb,
229 movement_flags: EntityMovementFlags,
230 ground_contact: EntityGroundContact,
231 movement_progress: EntityMovementProgress,
232 fire_freeze: EntityFireFreezeState,
233 in_block_state: Option<BlockStateId>,
234 fluid_contact: EntityFluidContact,
235 was_eye_in_water: bool,
236 piston_movement: EntityPistonMovement,
237 fall_distance: f64,
238 stuck_speed_multiplier: DVec3,
239 no_physics: bool,
240 needs_velocity_sync: bool,
241 hurt_marked: bool,
242}
243
244impl EntityBaseState {
245 #[must_use]
247 pub fn new(position: DVec3, dimensions: EntityDimensions) -> Self {
248 require_finite_position(position, "position");
249 Self {
250 tick_count: 0,
251 first_tick: true,
252 position,
253 old_position: position,
254 last_known_position: None,
255 last_known_speed: DVec3::ZERO,
256 velocity: DVec3::ZERO,
257 rotation: (0.0, 0.0),
258 old_rotation: (0.0, 0.0),
259 pose: EntityPose::Standing,
260 dimensions,
261 bounding_box: Self::make_bounding_box(position, dimensions),
262 movement_flags: EntityMovementFlags::new(),
263 ground_contact: EntityGroundContact::airborne(),
264 movement_progress: EntityMovementProgress::new(),
265 fire_freeze: EntityFireFreezeState::new(),
266 in_block_state: None,
267 fluid_contact: EntityFluidContact::default(),
268 was_eye_in_water: false,
269 piston_movement: EntityPistonMovement::new(),
270 fall_distance: 0.0,
271 stuck_speed_multiplier: DVec3::ZERO,
272 no_physics: false,
273 needs_velocity_sync: false,
274 hurt_marked: false,
275 }
276 }
277
278 #[must_use]
283 pub fn new_with_bounding_box(
284 position: DVec3,
285 dimensions: EntityDimensions,
286 bounding_box: WorldAabb,
287 ) -> Self {
288 Self {
289 bounding_box,
290 ..Self::new(position, dimensions)
291 }
292 }
293
294 #[must_use]
295 fn make_bounding_box(position: DVec3, dimensions: EntityDimensions) -> WorldAabb {
296 WorldAabb::entity_box(
297 position.x,
298 position.y,
299 position.z,
300 f64::from(dimensions.half_width()),
301 f64::from(dimensions.height),
302 )
303 }
304
305 #[must_use]
307 pub fn with_velocity(mut self, velocity: DVec3) -> Self {
308 if velocity.is_finite() {
309 self.velocity = velocity;
310 }
311 self
312 }
313
314 #[must_use]
316 pub fn with_old_position(mut self, old_position: DVec3) -> Self {
317 require_finite_position(old_position, "old position");
318 self.old_position = old_position;
319 self
320 }
321
322 #[must_use]
324 pub fn with_rotation(mut self, rotation: (f32, f32)) -> Self {
325 let rotation = normalize_rotation(rotation);
326 self.rotation = rotation;
327 self.old_rotation = rotation;
328 self
329 }
330
331 #[must_use]
333 pub const fn with_fall_distance(mut self, fall_distance: f64) -> Self {
334 self.fall_distance = fall_distance;
335 self
336 }
337
338 #[must_use]
340 pub const fn with_fire_freeze_state(mut self, fire_freeze: EntityFireFreezeState) -> Self {
341 self.fire_freeze = fire_freeze;
342 self
343 }
344
345 #[must_use]
347 pub const fn with_on_ground(mut self, on_ground: bool) -> Self {
348 self.movement_flags = self.movement_flags.with_on_ground(on_ground);
349 self.ground_contact = if on_ground {
350 EntityGroundContact::on_ground(None)
351 } else {
352 EntityGroundContact::airborne()
353 };
354 self
355 }
356}
357
358pub struct EntityBase {
384 generation: EntityGeneration,
386 id: i32,
388 uuid: Uuid,
390 world: SyncMutex<Weak<World>>,
392 state: SyncMutex<EntityBaseState>,
394 save_data: SyncMutex<EntityBaseSaveData>,
396 movement_trace: SyncMutex<EntityMovementTrace>,
398 lifecycle: SyncMutex<EntityLifecycleState>,
400 relationships: SyncMutex<EntityRelationshipState>,
402 portal_process: SyncMutex<Option<PortalProcessor>>,
404 level_callback: SyncMutex<Arc<dyn EntityLevelCallback>>,
406}
407
408impl EntityBase {
409 #[must_use]
411 pub fn new(id: i32, position: DVec3, dimensions: EntityDimensions, world: Weak<World>) -> Self {
412 Self::new_with_state(id, EntityBaseState::new(position, dimensions), world)
413 }
414
415 #[must_use]
417 #[expect(
418 clippy::large_types_passed_by_value,
419 reason = "EntityBaseState is an owned construction snapshot built with with_* helpers"
420 )]
421 pub fn new_with_state(id: i32, state: EntityBaseState, world: Weak<World>) -> Self {
422 Self::with_uuid_and_state(id, Uuid::new_v4(), state, world)
423 }
424
425 #[must_use]
429 pub fn with_uuid(
430 id: i32,
431 uuid: Uuid,
432 position: DVec3,
433 dimensions: EntityDimensions,
434 world: Weak<World>,
435 ) -> Self {
436 Self::with_uuid_and_state(id, uuid, EntityBaseState::new(position, dimensions), world)
437 }
438
439 #[must_use]
444 #[expect(
445 clippy::large_types_passed_by_value,
446 reason = "EntityBaseState is an owned construction snapshot built with with_* helpers"
447 )]
448 pub fn with_uuid_and_state(
449 id: i32,
450 uuid: Uuid,
451 state: EntityBaseState,
452 world: Weak<World>,
453 ) -> Self {
454 Self {
455 generation: EntityGeneration::next(),
456 id,
457 uuid,
458 world: SyncMutex::new(world),
459 state: SyncMutex::new(state),
460 save_data: SyncMutex::new(EntityBaseSaveData::new()),
461 movement_trace: SyncMutex::new(EntityMovementTrace::default()),
462 lifecycle: SyncMutex::new(EntityLifecycleState::new()),
463 relationships: SyncMutex::new(EntityRelationshipState::default()),
464 portal_process: SyncMutex::new(None),
465 level_callback: SyncMutex::new(Arc::new(NullEntityCallback)),
466 }
467 }
468
469 #[must_use]
471 pub fn from_load(load: EntityBaseLoad, dimensions: EntityDimensions) -> Self {
472 let base = Self::with_uuid_and_state(
473 load.id,
474 load.uuid,
475 EntityBaseState::new(load.position, dimensions)
476 .with_velocity(load.velocity)
477 .with_rotation(load.rotation)
478 .with_fall_distance(load.fall_distance)
479 .with_fire_freeze_state(load.fire_freeze)
480 .with_on_ground(load.on_ground),
481 load.world,
482 );
483 base.replace_save_data(load.save_data);
484 base
485 }
486
487 #[inline]
489 pub const fn generation(&self) -> EntityGeneration {
490 self.generation
491 }
492
493 #[inline]
495 pub const fn id(&self) -> i32 {
496 self.id
497 }
498
499 #[inline]
501 pub const fn uuid(&self) -> Uuid {
502 self.uuid
503 }
504
505 #[inline]
507 pub fn position(&self) -> DVec3 {
508 self.state.lock().position
509 }
510
511 #[inline]
513 pub fn old_position(&self) -> DVec3 {
514 self.state.lock().old_position
515 }
516
517 #[inline]
519 pub fn known_speed(&self) -> DVec3 {
520 self.state.lock().last_known_speed
521 }
522
523 #[inline]
525 pub fn tick_count(&self) -> i32 {
526 self.state.lock().tick_count
527 }
528
529 #[inline]
531 pub fn is_first_tick(&self) -> bool {
532 self.state.lock().first_tick
533 }
534
535 #[inline]
537 pub fn set_first_tick(&self, first_tick: bool) {
538 self.state.lock().first_tick = first_tick;
539 }
540
541 #[inline]
543 pub fn bounding_box(&self) -> WorldAabb {
544 self.state.lock().bounding_box
545 }
546
547 pub(crate) fn physics_state(&self, input: EntityPhysicsStateInput) -> EntityPhysicsState {
549 let state = self.state.lock();
550 EntityPhysicsState::new(state.position, state.bounding_box, input.max_up_step)
551 .with_on_ground(state.movement_flags.on_ground())
552 .with_backs_off_from_edge(input.backs_off_from_edge)
553 .with_fall_distance(state.fall_distance)
554 .with_descending(input.descending)
555 .with_can_walk_on_powder_snow(input.can_walk_on_powder_snow)
556 .with_falling_block(input.is_falling_block)
557 }
558
559 #[inline]
561 pub fn pose(&self) -> EntityPose {
562 self.state.lock().pose
563 }
564
565 #[inline]
567 pub fn dimensions(&self) -> EntityDimensions {
568 self.state.lock().dimensions
569 }
570
571 #[inline]
573 pub fn velocity(&self) -> DVec3 {
574 self.state.lock().velocity
575 }
576
577 #[inline]
579 pub fn rotation(&self) -> (f32, f32) {
580 self.state.lock().rotation
581 }
582
583 #[inline]
585 pub fn old_rotation(&self) -> (f32, f32) {
586 self.state.lock().old_rotation
587 }
588
589 #[inline]
591 pub fn on_ground(&self) -> bool {
592 self.state.lock().movement_flags.on_ground()
593 }
594
595 #[inline]
597 pub fn movement_flags(&self) -> EntityMovementFlags {
598 self.state.lock().movement_flags
599 }
600
601 #[inline]
603 pub fn ground_contact(&self) -> EntityGroundContact {
604 self.state.lock().ground_contact
605 }
606
607 #[inline]
609 pub fn movement_progress(&self) -> EntityMovementProgress {
610 self.state.lock().movement_progress
611 }
612
613 #[inline]
615 pub fn fire_freeze_state(&self) -> EntityFireFreezeState {
616 self.state.lock().fire_freeze
617 }
618
619 pub fn save_data(&self) -> EntityBaseSaveData {
621 self.save_data.lock().clone()
622 }
623
624 pub fn replace_save_data(&self, save_data: EntityBaseSaveData) {
626 *self.save_data.lock() = save_data;
627 }
628
629 pub fn in_block_state(&self, world: &World) -> BlockStateId {
631 let mut state = self.state.lock();
632 if let Some(in_block_state) = state.in_block_state {
633 return in_block_state;
634 }
635
636 let position = state.position;
637 let block_pos = BlockPos::containing(position.x, position.y, position.z);
638 let in_block_state = world.get_block_state(block_pos);
639 state.in_block_state = Some(in_block_state);
640 in_block_state
641 }
642
643 pub fn set_fire_freeze_state(&self, fire_freeze: EntityFireFreezeState) {
645 self.state.lock().fire_freeze = fire_freeze;
646 }
647
648 #[inline]
650 pub fn horizontal_collision(&self) -> bool {
651 self.state.lock().movement_flags.horizontal_collision()
652 }
653
654 #[inline]
656 pub fn vertical_collision(&self) -> bool {
657 self.state.lock().movement_flags.vertical_collision()
658 }
659
660 #[inline]
662 pub fn vertical_collision_below(&self) -> bool {
663 self.state.lock().movement_flags.vertical_collision_below()
664 }
665
666 pub fn supporting_block(&self) -> Option<BlockPos> {
668 self.state.lock().ground_contact.supporting_block()
669 }
670
671 pub fn on_ground_no_blocks(&self) -> bool {
673 self.state.lock().ground_contact.on_ground_no_blocks()
674 }
675
676 pub fn fluid_contact(&self) -> EntityFluidContact {
678 self.state.lock().fluid_contact
679 }
680
681 pub fn was_eye_in_water(&self) -> bool {
683 self.state.lock().was_eye_in_water
684 }
685
686 #[inline]
688 pub fn fall_distance(&self) -> f64 {
689 self.state.lock().fall_distance
690 }
691
692 #[inline]
694 pub fn no_physics(&self) -> bool {
695 self.state.lock().no_physics
696 }
697
698 #[inline]
700 pub fn air_supply(&self) -> i32 {
701 self.save_data.lock().air_supply
702 }
703
704 #[inline]
706 pub fn portal_cooldown(&self) -> i32 {
707 self.save_data.lock().portal_cooldown
708 }
709
710 #[inline]
712 pub fn is_on_portal_cooldown(&self) -> bool {
713 self.portal_cooldown() > 0
714 }
715
716 #[inline]
718 pub fn portal_process(&self) -> Option<PortalProcessor> {
719 *self.portal_process.lock()
720 }
721
722 #[inline]
724 pub fn no_gravity(&self) -> bool {
725 self.save_data.lock().no_gravity
726 }
727
728 #[inline]
730 pub fn invulnerable(&self) -> bool {
731 self.save_data.lock().invulnerable
732 }
733
734 #[inline]
736 pub fn custom_name(&self) -> Option<TextComponent> {
737 self.save_data.lock().custom_name.clone()
738 }
739
740 #[inline]
742 pub fn custom_name_visible(&self) -> bool {
743 self.save_data.lock().custom_name_visible
744 }
745
746 #[inline]
748 pub fn silent(&self) -> bool {
749 self.save_data.lock().silent
750 }
751
752 #[inline]
754 pub fn glowing(&self) -> bool {
755 self.save_data.lock().glowing
756 }
757
758 pub fn tags(&self) -> Vec<String> {
760 self.save_data.lock().tags.iter().cloned().collect()
761 }
762
763 pub fn custom_data(&self) -> NbtCompound {
765 self.save_data.lock().custom_data.clone()
766 }
767
768 #[inline]
770 pub fn needs_velocity_sync(&self) -> bool {
771 self.state.lock().needs_velocity_sync
772 }
773
774 #[inline]
776 pub fn hurt_marked(&self) -> bool {
777 self.state.lock().hurt_marked
778 }
779
780 #[inline]
784 pub fn level(&self) -> Option<Arc<World>> {
785 self.world.lock().upgrade()
786 }
787
788 pub fn vehicle(&self) -> Option<SharedEntity> {
790 self.relationships.lock().vehicle()
791 }
792
793 pub fn passengers(&self) -> Vec<SharedEntity> {
795 self.relationships.lock().passengers()
796 }
797
798 pub fn first_passenger(&self) -> Option<SharedEntity> {
800 self.relationships.lock().first_passenger()
801 }
802
803 pub fn is_vehicle(&self) -> bool {
805 self.first_passenger().is_some()
806 }
807
808 pub fn has_passenger_id(&self, passenger_id: i32) -> bool {
810 self.relationships.lock().has_passenger_id(passenger_id)
811 }
812
813 pub fn boarding_cooldown(&self) -> i32 {
815 self.relationships.lock().boarding_cooldown
816 }
817
818 pub(crate) fn remove_passenger_id(&self, passenger_id: i32) -> bool {
820 self.relationships.lock().remove_passenger_id(passenger_id)
821 }
822
823 pub fn stop_riding(&self) {
825 self.stop_riding_relationship();
826 }
827
828 pub(crate) fn restore_passenger_relationship(vehicle: &SharedEntity, passenger: &SharedEntity) {
830 passenger.base().stop_riding_relationship();
831 Self::add_passenger_relationship(vehicle, passenger);
832 }
833
834 pub(crate) fn start_riding_relationship(vehicle: &SharedEntity, passenger: &SharedEntity) {
836 passenger.base().stop_riding_relationship();
837 Self::add_passenger_relationship(vehicle, passenger);
838 }
839
840 fn add_passenger_relationship(vehicle: &SharedEntity, passenger: &SharedEntity) {
841 if vehicle.base().has_passenger_id(passenger.id()) {
842 return;
843 }
844
845 passenger.base().relationships.lock().vehicle = Some(Arc::downgrade(vehicle));
846 let passenger_ref = Arc::downgrade(passenger);
847 let mut vehicle_relationships = vehicle.base().relationships.lock();
848 let first_passenger_is_player = vehicle_relationships
849 .first_passenger()
850 .is_some_and(|first| first.entity_type() == &vanilla_entities::PLAYER);
851 if passenger.entity_type() == &vanilla_entities::PLAYER && !first_passenger_is_player {
852 vehicle_relationships.passengers.insert(0, passenger_ref);
853 } else {
854 vehicle_relationships.passengers.push(passenger_ref);
855 }
856 }
857
858 pub(crate) fn set_boarding_cooldown(&self, boarding_cooldown: i32) {
860 self.relationships.lock().boarding_cooldown = boarding_cooldown;
861 }
862
863 pub fn advance_base_tick_state(&self) {
865 self.clear_in_block_state_for_base_tick();
866 self.set_old_rotation_to_current();
867 self.compute_known_speed();
868 self.decrement_boarding_cooldown();
869 }
870
871 fn clear_in_block_state_for_base_tick(&self) {
873 self.state.lock().in_block_state = None;
874 }
875
876 pub fn compute_known_speed(&self) {
878 let mut state = self.state.lock();
879 let previous_position = match state.last_known_position {
880 Some(position) => position,
881 None => state.position,
882 };
883 state.last_known_speed = state.position - previous_position;
884 state.last_known_position = Some(state.position);
885 }
886
887 fn decrement_boarding_cooldown(&self) {
888 let mut relationships = self.relationships.lock();
889 if relationships.boarding_cooldown > 0 {
890 relationships.boarding_cooldown -= 1;
891 }
892 }
893
894 pub fn process_portal_cooldown(&self) {
896 let mut save_data = self.save_data.lock();
897 if save_data.portal_cooldown > 0 {
898 save_data.portal_cooldown -= 1;
899 }
900 }
901
902 pub(crate) fn set_world(&self, world: Weak<World>) {
904 *self.world.lock() = world;
905 }
906
907 pub fn begin_pending_world_change(&self) -> Option<PendingWorldChangeToken> {
909 let mut lifecycle = self.lifecycle.lock();
910 if lifecycle.removal_reason.is_some() || lifecycle.pending_world_change.is_some() {
911 return None;
912 }
913 let token = lifecycle.next_world_change_token();
914 lifecycle.pending_world_change = Some(token);
915 Some(token)
916 }
917
918 pub(crate) fn begin_pending_player_respawn(&self) -> Option<PendingWorldChangeToken> {
923 let mut lifecycle = self.lifecycle.lock();
924 if !matches!(lifecycle.removal_reason, None | Some(RemovalReason::Killed))
925 || lifecycle.pending_world_change.is_some()
926 {
927 return None;
928 }
929 let token = lifecycle.next_world_change_token();
930 lifecycle.pending_world_change = Some(token);
931 Some(token)
932 }
933
934 pub fn finish_pending_world_change(&self, token: PendingWorldChangeToken) -> bool {
936 let mut lifecycle = self.lifecycle.lock();
937 if lifecycle.pending_world_change != Some(token) {
938 return false;
939 }
940 lifecycle.pending_world_change = None;
941 true
942 }
943
944 #[inline]
946 pub fn is_world_change_pending(&self) -> bool {
947 self.lifecycle.lock().pending_world_change.is_some()
948 }
949
950 #[inline]
952 pub fn is_world_change_token_pending(&self, token: PendingWorldChangeToken) -> bool {
953 self.lifecycle.lock().pending_world_change == Some(token)
954 }
955
956 #[inline]
958 pub fn is_removed(&self) -> bool {
959 self.lifecycle.lock().removal_reason.is_some()
960 }
961
962 #[inline]
964 pub fn removal_reason(&self) -> Option<RemovalReason> {
965 self.lifecycle.lock().removal_reason
966 }
967
968 pub fn set_removed(&self, reason: RemovalReason) {
972 let callback = {
973 let mut lifecycle = self.lifecycle.lock();
974 if lifecycle.removal_reason.is_some() {
975 None
976 } else {
977 lifecycle.removal_reason = Some(reason);
978 lifecycle.pending_world_change = None;
979 Some(self.level_callback.lock().clone())
980 }
981 };
982
983 if let Some(callback) = callback {
984 self.detach_from_relationships(reason);
985 callback.on_remove(reason);
986 *self.level_callback.lock() = Arc::new(NullEntityCallback);
987 }
988 }
989
990 fn detach_from_relationships(&self, reason: RemovalReason) {
991 if reason.should_destroy() {
992 self.stop_riding_relationship();
993 }
994 self.eject_passenger_relationships();
995 }
996
997 fn stop_riding_relationship(&self) {
998 let vehicle = {
999 let mut relationships = self.relationships.lock();
1000 let vehicle = relationships.vehicle();
1001 relationships.vehicle = None;
1002 vehicle
1003 };
1004
1005 if let Some(vehicle) = vehicle {
1006 vehicle.base().remove_passenger_id(self.id);
1007 self.set_boarding_cooldown(BOARDING_COOLDOWN);
1008 }
1009 }
1010
1011 fn eject_passenger_relationships(&self) {
1012 let passengers = {
1013 let mut relationships = self.relationships.lock();
1014 let passengers = relationships.passengers();
1015 relationships.passengers.clear();
1016 passengers
1017 };
1018
1019 for passenger in passengers {
1020 if passenger.base().clear_vehicle_if(self.id) {
1021 passenger.base().set_boarding_cooldown(BOARDING_COOLDOWN);
1022 }
1023 }
1024 }
1025
1026 fn clear_vehicle_if(&self, vehicle_id: i32) -> bool {
1027 {
1028 let mut relationships = self.relationships.lock();
1029 let Some(vehicle) = relationships.vehicle() else {
1030 return false;
1031 };
1032 if vehicle.id() != vehicle_id {
1033 return false;
1034 }
1035 }
1036
1037 if let Err(error) = self.try_set_position(self.position()) {
1038 log::warn!(
1039 "Failed to refresh passenger {} manager position before clearing vehicle {vehicle_id}: {error}",
1040 self.id
1041 );
1042 }
1043
1044 let mut relationships = self.relationships.lock();
1045 let Some(vehicle) = relationships.vehicle() else {
1046 return false;
1047 };
1048 if vehicle.id() != vehicle_id {
1049 return false;
1050 }
1051 relationships.vehicle = None;
1052 true
1053 }
1054
1055 pub fn clear_removed(&self) -> bool {
1059 let mut lifecycle = self.lifecycle.lock();
1060 let was_removed = lifecycle.removal_reason.is_some();
1061 lifecycle.removal_reason = None;
1062 lifecycle.pending_world_change = None;
1063 was_removed
1064 }
1065
1066 pub fn set_level_callback(&self, callback: Arc<dyn EntityLevelCallback>) {
1068 *self.level_callback.lock() = callback;
1069 }
1070
1071 #[must_use = "movement commits can fail when world entity state rejects the update"]
1073 pub fn try_set_position(&self, pos: DVec3) -> Result<(), EntityMoveError> {
1074 require_finite_position(pos, "position");
1075 let old_pos = self.state.lock().position;
1076 let callback = self.level_callback.lock().clone();
1077 callback.validate_move(old_pos, pos)?;
1078 self.set_position_local_unchecked(pos);
1079 if let Err(error) = callback.on_move_committed(old_pos, pos) {
1080 self.set_position_local_unchecked(old_pos);
1081 return Err(error);
1082 }
1083 Ok(())
1084 }
1085
1086 pub(crate) fn set_position_local(&self, pos: DVec3) {
1090 let callback = self.level_callback.lock().clone();
1091 assert!(
1092 callback.allows_local_position_update(),
1093 "entity {} local position update bypassed world entity manager",
1094 self.id
1095 );
1096 self.set_position_local_unchecked(pos);
1097 }
1098
1099 fn set_position_local_unchecked(&self, pos: DVec3) {
1100 require_finite_position(pos, "position");
1101 {
1102 let mut state = self.state.lock();
1103 let old = state.position;
1104 state.position = pos;
1105 state.bounding_box = EntityBaseState::make_bounding_box(pos, state.dimensions);
1106 if BlockPos::containing(old.x, old.y, old.z)
1107 != BlockPos::containing(pos.x, pos.y, pos.z)
1108 {
1109 state.in_block_state = None;
1110 }
1111 }
1112 }
1113
1114 pub fn set_old_position_to_current(&self) {
1116 let mut state = self.state.lock();
1117 state.old_position = state.position;
1118 }
1119
1120 pub fn set_old_position(&self, old_position: DVec3) {
1122 require_finite_position(old_position, "old position");
1123 self.state.lock().old_position = old_position;
1124 }
1125
1126 pub fn set_old_rotation_to_current(&self) {
1128 let mut state = self.state.lock();
1129 state.old_rotation = state.rotation;
1130 }
1131
1132 pub fn set_old_yaw_to_current(&self) {
1134 let mut state = self.state.lock();
1135 state.old_rotation.0 = state.rotation.0;
1136 }
1137
1138 pub fn set_old_rotation(&self, old_rotation: (f32, f32)) {
1140 self.state.lock().old_rotation = normalize_rotation(old_rotation);
1141 }
1142
1143 pub fn record_movement_this_tick(&self, movement: EntityMovement) {
1145 self.movement_trace.lock().record(movement);
1146 }
1147
1148 pub fn remove_latest_movement_recording(&self) {
1150 self.movement_trace.lock().remove_latest_recording();
1151 }
1152
1153 pub fn clear_movement_this_tick(&self) {
1155 self.movement_trace.lock().reset();
1156 }
1157
1158 pub fn take_movements_for_block_effects(&self) -> Vec<EntityMovement> {
1160 let (old_position, position) = {
1161 let state = self.state.lock();
1162 (state.old_position, state.position)
1163 };
1164
1165 self.movement_trace
1166 .lock()
1167 .take_for_block_effects(old_position, position)
1168 }
1169
1170 pub fn last_movements_for_block_effects(&self) -> Vec<EntityMovement> {
1172 self.movement_trace.lock().last_for_block_effects()
1173 }
1174
1175 pub fn set_bounding_box(&self, bounding_box: WorldAabb) {
1180 self.state.lock().bounding_box = bounding_box;
1181 self.notify_bounding_box_changed(bounding_box);
1182 }
1183
1184 pub fn set_pose_and_dimensions(&self, pose: EntityPose, dimensions: EntityDimensions) {
1186 let bounding_box = {
1187 let mut state = self.state.lock();
1188 state.pose = pose;
1189 state.dimensions = dimensions;
1190 state.bounding_box = EntityBaseState::make_bounding_box(state.position, dimensions);
1191 state.bounding_box
1192 };
1193 self.notify_bounding_box_changed(bounding_box);
1194 }
1195
1196 fn notify_bounding_box_changed(&self, bounding_box: WorldAabb) {
1197 let callback = Arc::clone(&self.level_callback.lock());
1198 callback.on_bounding_box_changed(bounding_box);
1199 }
1200
1201 pub fn set_velocity(&self, velocity: DVec3) {
1203 if velocity.is_finite() {
1204 self.state.lock().velocity = velocity;
1205 }
1206 }
1207
1208 #[inline]
1210 pub fn advance_tick_count(&self) {
1211 let mut state = self.state.lock();
1212 state.tick_count = state.tick_count.wrapping_add(1);
1213 }
1214
1215 pub fn record_movement_progress(
1217 &self,
1218 clipped_movement: DVec3,
1219 climbing: bool,
1220 ) -> EntityMovementProgress {
1221 let mut state = self.state.lock();
1222 state
1223 .movement_progress
1224 .add_movement(clipped_movement, climbing);
1225 state.movement_progress
1226 }
1227
1228 pub fn set_next_step(&self, next_step: f32) {
1230 self.state.lock().movement_progress.next_step = next_step;
1231 }
1232
1233 pub fn amethyst_step_sound(&self, tick_count: i32) -> Option<EntityAmethystStepSound> {
1235 let intensity = {
1236 let mut state = self.state.lock();
1237 let progress = &mut state.movement_progress;
1238 if tick_count < progress.last_crystal_sound_play_tick + 20 {
1239 return None;
1240 }
1241
1242 let tick_delta = tick_count - progress.last_crystal_sound_play_tick;
1243 progress.crystal_sound_intensity *= 0.997_f32.powi(tick_delta);
1244 progress.crystal_sound_intensity = (progress.crystal_sound_intensity + 0.07).min(1.0);
1245 progress.last_crystal_sound_play_tick = tick_count;
1246 progress.crystal_sound_intensity
1247 };
1248
1249 let pitch = rand::random_range(0.5..0.5 + intensity * 1.2);
1250 let volume = 0.1 + intensity * 1.2;
1251 Some(EntityAmethystStepSound { volume, pitch })
1252 }
1253
1254 pub fn set_rotation(&self, rotation: (f32, f32)) {
1256 self.state.lock().rotation = normalize_rotation(rotation);
1257 }
1258
1259 pub fn set_no_physics(&self, no_physics: bool) {
1261 self.state.lock().no_physics = no_physics;
1262 }
1263
1264 pub fn set_air_supply(&self, air_supply: i32) {
1266 self.save_data.lock().air_supply = air_supply;
1267 }
1268
1269 pub fn set_portal_cooldown(&self, portal_cooldown: i32) {
1271 self.save_data.lock().portal_cooldown = portal_cooldown;
1272 }
1273
1274 pub fn set_as_inside_portal(&self, portal: PortalKind, entry_position: BlockPos) {
1276 let mut portal_process = self.portal_process.lock();
1277 match portal_process.as_mut() {
1278 Some(process) if process.is_same_portal(portal) => {
1279 process.set_as_inside_portal(entry_position);
1280 }
1281 _ => {
1282 *portal_process = Some(PortalProcessor::new(portal, entry_position));
1283 }
1284 }
1285 }
1286
1287 pub fn process_portal_teleportation(
1289 &self,
1290 allowed_to_teleport: bool,
1291 transition_time: i32,
1292 ) -> Option<PortalProcessResult> {
1293 self.portal_process.lock().as_mut().map(|process| {
1294 process.process_portal_teleportation(allowed_to_teleport, transition_time)
1295 })
1296 }
1297
1298 pub(crate) fn set_portal_process(&self, portal_process: Option<PortalProcessor>) {
1300 *self.portal_process.lock() = portal_process;
1301 }
1302
1303 pub fn clear_portal_process(&self) {
1305 *self.portal_process.lock() = None;
1306 }
1307
1308 pub fn set_no_gravity(&self, no_gravity: bool) {
1310 self.save_data.lock().no_gravity = no_gravity;
1311 }
1312
1313 pub fn set_invulnerable(&self, invulnerable: bool) {
1315 self.save_data.lock().invulnerable = invulnerable;
1316 }
1317
1318 pub fn set_custom_name(&self, custom_name: Option<TextComponent>) {
1320 self.save_data.lock().custom_name = custom_name;
1321 }
1322
1323 pub fn set_custom_name_visible(&self, visible: bool) {
1325 self.save_data.lock().custom_name_visible = visible;
1326 }
1327
1328 pub fn set_silent(&self, silent: bool) {
1330 self.save_data.lock().silent = silent;
1331 }
1332
1333 pub fn set_glowing(&self, glowing: bool) {
1335 self.save_data.lock().glowing = glowing;
1336 }
1337
1338 pub fn add_tag(&self, tag: String) -> bool {
1340 self.save_data.lock().add_tag(tag)
1341 }
1342
1343 pub fn remove_tag(&self, tag: &str) -> bool {
1345 self.save_data.lock().tags.remove(tag)
1346 }
1347
1348 pub fn set_custom_data(&self, custom_data: NbtCompound) {
1350 self.save_data.lock().custom_data = custom_data;
1351 }
1352
1353 pub fn mark_velocity_sync(&self) {
1355 self.state.lock().needs_velocity_sync = true;
1356 }
1357
1358 pub fn clear_velocity_sync(&self) {
1360 self.state.lock().needs_velocity_sync = false;
1361 }
1362
1363 pub fn mark_hurt(&self) {
1365 self.state.lock().hurt_marked = true;
1366 }
1367
1368 pub fn clear_hurt_mark(&self) {
1370 self.state.lock().hurt_marked = false;
1371 }
1372
1373 pub fn set_fall_distance(&self, fall_distance: f64) {
1375 self.state.lock().fall_distance = fall_distance;
1376 }
1377
1378 pub fn accumulate_fall_distance(&self, vertical_movement: f64) {
1380 self.state.lock().fall_distance -= f64::from(vertical_movement as f32);
1381 }
1382
1383 pub fn reset_fall_distance(&self) {
1385 self.set_fall_distance(0.0);
1386 }
1387
1388 pub fn remaining_fire_ticks(&self) -> i32 {
1390 self.state.lock().fire_freeze.remaining_fire_ticks()
1391 }
1392
1393 pub fn set_remaining_fire_ticks(&self, remaining_fire_ticks: i32) {
1395 self.state.lock().fire_freeze.remaining_fire_ticks = remaining_fire_ticks;
1396 }
1397
1398 pub fn ticks_frozen(&self) -> i32 {
1400 self.state.lock().fire_freeze.ticks_frozen()
1401 }
1402
1403 pub fn set_ticks_frozen(&self, ticks_frozen: i32) {
1405 self.state.lock().fire_freeze.ticks_frozen = ticks_frozen;
1406 }
1407
1408 pub fn is_in_powder_snow(&self) -> bool {
1410 self.state.lock().fire_freeze.is_in_powder_snow()
1411 }
1412
1413 pub fn was_in_powder_snow(&self) -> bool {
1415 self.state.lock().fire_freeze.was_in_powder_snow()
1416 }
1417
1418 pub fn set_visual_fire(&self, has_visual_fire: bool) {
1420 self.state.lock().fire_freeze.has_visual_fire = has_visual_fire;
1421 }
1422
1423 pub fn has_visual_fire(&self) -> bool {
1425 self.state.lock().fire_freeze.has_visual_fire()
1426 }
1427
1428 pub fn is_on_fire(&self, fire_immune: bool) -> bool {
1430 !fire_immune && self.remaining_fire_ticks() > 0
1431 }
1432
1433 pub fn is_freezing(&self) -> bool {
1435 self.state.lock().fire_freeze.is_freezing()
1436 }
1437
1438 pub fn is_fully_frozen(&self, ticks_required_to_freeze: i32) -> bool {
1440 self.state
1441 .lock()
1442 .fire_freeze
1443 .is_fully_frozen(ticks_required_to_freeze)
1444 }
1445
1446 pub fn advance_powder_snow_contact_for_base_tick(&self) {
1448 let mut state = self.state.lock();
1449 state.fire_freeze.was_in_powder_snow = state.fire_freeze.is_in_powder_snow;
1450 state.fire_freeze.is_in_powder_snow = false;
1451 }
1452
1453 pub fn advance_fire_tick(&self, fire_immune: bool, in_lava: bool) -> bool {
1457 let mut state = self.state.lock();
1458 if state.fire_freeze.remaining_fire_ticks <= 0 {
1459 return false;
1460 }
1461
1462 if fire_immune {
1463 state.fire_freeze.remaining_fire_ticks = state.fire_freeze.remaining_fire_ticks.min(0);
1464 return false;
1465 }
1466
1467 let should_damage = state.fire_freeze.remaining_fire_ticks % 20 == 0 && !in_lava;
1468 state.fire_freeze.remaining_fire_ticks -= 1;
1469 should_damage
1470 }
1471
1472 pub fn clear_freeze(&self) {
1474 self.set_ticks_frozen(0);
1475 }
1476
1477 pub fn clear_fire(&self) {
1479 let mut state = self.state.lock();
1480 state.fire_freeze.remaining_fire_ticks = state.fire_freeze.remaining_fire_ticks.min(0);
1481 }
1482
1483 pub fn ignite_for_ticks(&self, number_of_ticks: i32, remaining_fire_ticks_cap: Option<i32>) {
1485 let mut state = self.state.lock();
1486 Self::ignite_for_ticks_in_state(
1487 &mut state.fire_freeze,
1488 number_of_ticks,
1489 remaining_fire_ticks_cap,
1490 );
1491 }
1492
1493 pub fn apply_inside_block_effect(
1495 &self,
1496 effect_type: InsideBlockEffectType,
1497 can_freeze: bool,
1498 fire_immune: bool,
1499 fire_ignite_extra_ticks: i32,
1500 ticks_required_to_freeze: i32,
1501 remaining_fire_ticks_cap: Option<i32>,
1502 ) {
1503 let mut state = self.state.lock();
1504 match effect_type {
1505 InsideBlockEffectType::Freeze => {
1506 state.fire_freeze.is_in_powder_snow = true;
1507 if can_freeze {
1508 state.fire_freeze.ticks_frozen =
1509 ticks_required_to_freeze.min(state.fire_freeze.ticks_frozen + 1);
1510 }
1511 }
1512 InsideBlockEffectType::ClearFreeze => {
1513 state.fire_freeze.ticks_frozen = 0;
1514 }
1515 InsideBlockEffectType::FireIgnite => {
1516 Self::apply_fire_ignite(
1517 &mut state.fire_freeze,
1518 fire_immune,
1519 fire_ignite_extra_ticks,
1520 remaining_fire_ticks_cap,
1521 );
1522 }
1523 InsideBlockEffectType::LavaIgnite => {
1524 if !fire_immune {
1525 Self::ignite_for_ticks_in_state(
1526 &mut state.fire_freeze,
1527 LAVA_IGNITE_TICKS,
1528 remaining_fire_ticks_cap,
1529 );
1530 }
1531 }
1532 InsideBlockEffectType::Extinguish => {
1533 state.fire_freeze.remaining_fire_ticks =
1534 state.fire_freeze.remaining_fire_ticks.min(0);
1535 }
1536 }
1537 }
1538
1539 fn apply_fire_ignite(
1540 fire_freeze: &mut EntityFireFreezeState,
1541 fire_immune: bool,
1542 fire_ignite_extra_ticks: i32,
1543 remaining_fire_ticks_cap: Option<i32>,
1544 ) {
1545 if fire_immune {
1546 return;
1547 }
1548
1549 if fire_freeze.remaining_fire_ticks < 0 {
1550 Self::set_remaining_fire_ticks_in_state(
1551 fire_freeze,
1552 fire_freeze.remaining_fire_ticks + 1,
1553 remaining_fire_ticks_cap,
1554 );
1555 } else if fire_ignite_extra_ticks > 0 {
1556 Self::set_remaining_fire_ticks_in_state(
1557 fire_freeze,
1558 fire_freeze.remaining_fire_ticks + fire_ignite_extra_ticks,
1559 remaining_fire_ticks_cap,
1560 );
1561 }
1562
1563 if fire_freeze.remaining_fire_ticks >= 0 {
1564 Self::ignite_for_ticks_in_state(
1565 fire_freeze,
1566 FIRE_IGNITE_TICKS,
1567 remaining_fire_ticks_cap,
1568 );
1569 }
1570 }
1571
1572 fn ignite_for_ticks_in_state(
1573 fire_freeze: &mut EntityFireFreezeState,
1574 number_of_ticks: i32,
1575 remaining_fire_ticks_cap: Option<i32>,
1576 ) {
1577 if fire_freeze.remaining_fire_ticks < number_of_ticks {
1578 Self::set_remaining_fire_ticks_in_state(
1579 fire_freeze,
1580 number_of_ticks,
1581 remaining_fire_ticks_cap,
1582 );
1583 }
1584 fire_freeze.ticks_frozen = 0;
1585 }
1586
1587 fn set_remaining_fire_ticks_in_state(
1588 fire_freeze: &mut EntityFireFreezeState,
1589 remaining_fire_ticks: i32,
1590 remaining_fire_ticks_cap: Option<i32>,
1591 ) {
1592 fire_freeze.remaining_fire_ticks =
1593 Self::cap_remaining_fire_ticks(remaining_fire_ticks, remaining_fire_ticks_cap);
1594 }
1595
1596 fn cap_remaining_fire_ticks(
1597 remaining_fire_ticks: i32,
1598 remaining_fire_ticks_cap: Option<i32>,
1599 ) -> i32 {
1600 remaining_fire_ticks_cap.map_or(remaining_fire_ticks, |cap| remaining_fire_ticks.min(cap))
1601 }
1602
1603 pub fn dampen_fall_distance_in_lava(&self) {
1605 let mut state = self.state.lock();
1606 if !state.first_tick && state.fluid_contact.lava_height() > 0.0 {
1607 state.fall_distance *= 0.5;
1608 }
1609 }
1610
1611 pub fn reset_fall_distance_in_water(&self) {
1613 let mut state = self.state.lock();
1614 if state.fluid_contact.water_height() > 0.0 {
1615 state.fall_distance = 0.0;
1616 }
1617 }
1618
1619 pub fn set_on_ground(&self, on_ground: bool) {
1621 let mut state = self.state.lock();
1622 state.movement_flags = state.movement_flags.with_on_ground(on_ground);
1623 if !on_ground {
1624 state.ground_contact = EntityGroundContact::airborne();
1625 }
1626 }
1627
1628 pub fn set_movement_flags(
1630 &self,
1631 movement_flags: EntityMovementFlags,
1632 ground_contact: EntityGroundContact,
1633 ) {
1634 let mut state = self.state.lock();
1635 state.movement_flags = movement_flags;
1636 state.ground_contact = ground_contact;
1637 }
1638
1639 pub fn set_ground_contact(&self, ground_contact: EntityGroundContact) {
1641 self.state.lock().ground_contact = ground_contact;
1642 }
1643
1644 pub fn set_fluid_contact(&self, fluid_contact: EntityFluidContact) {
1646 self.state.lock().fluid_contact = fluid_contact;
1647 }
1648
1649 #[inline]
1651 pub fn is_in_lava(&self) -> bool {
1652 let state = self.state.lock();
1653 !state.first_tick && state.fluid_contact.lava_height() > 0.0
1654 }
1655
1656 pub fn set_fluid_contact_for_base_tick(&self, fluid_contact: EntityFluidContact) {
1661 let mut state = self.state.lock();
1662 state.was_eye_in_water = state.fluid_contact.eye_in_water();
1663 state.fluid_contact = fluid_contact;
1664 }
1665
1666 pub fn set_on_ground_with_movement(
1668 &self,
1669 on_ground: bool,
1670 horizontal_collision: bool,
1671 ground_contact: EntityGroundContact,
1672 ) {
1673 let mut state = self.state.lock();
1674 state.movement_flags = state
1675 .movement_flags
1676 .with_on_ground(on_ground)
1677 .with_horizontal_collision(horizontal_collision);
1678 state.ground_contact = ground_contact;
1679 }
1680
1681 pub fn clear_collision_flags(&self) {
1683 let mut state = self.state.lock();
1684 state.movement_flags = state.movement_flags.without_collisions();
1685 }
1686
1687 pub fn limit_piston_movement(&self, movement: DVec3, current_game_time: i64) -> DVec3 {
1689 self.state
1690 .lock()
1691 .piston_movement
1692 .limit_movement(movement, current_game_time)
1693 }
1694
1695 pub fn make_stuck_in_block(&self, speed_multiplier: DVec3) {
1697 let mut state = self.state.lock();
1698 state.fall_distance = 0.0;
1699 state.stuck_speed_multiplier = speed_multiplier;
1700 }
1701
1702 #[must_use]
1704 pub fn consume_stuck_speed_multiplier(&self, movement: DVec3, apply_multiplier: bool) -> DVec3 {
1705 let mut state = self.state.lock();
1706 if state.stuck_speed_multiplier.length_squared() <= STUCK_SPEED_MULTIPLIER_EPSILON {
1707 return movement;
1708 }
1709
1710 let stuck_speed_multiplier = state.stuck_speed_multiplier;
1711 state.stuck_speed_multiplier = DVec3::ZERO;
1712 state.velocity = DVec3::ZERO;
1713
1714 if apply_multiplier {
1715 movement * stuck_speed_multiplier
1716 } else {
1717 movement
1718 }
1719 }
1720}
1721
1722#[cfg(test)]
1723mod tests;