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::sync::{Arc, Weak};
21
22use glam::DVec3;
23use simdnbt::owned::NbtCompound;
24use smallvec::SmallVec;
25use steel_math::{DEGREE_90, DEGREE_360};
26use steel_registry::entity_data::EntityPose;
27use steel_registry::entity_type::EntityDimensions;
28use steel_registry::vanilla_entities;
29use steel_utils::locks::SyncMutex;
30use steel_utils::{BlockPos, BlockStateId, WorldAabb};
31use text_components::TextComponent;
32use uuid::Uuid;
33
34use crate::entity::damage::DamageHistoryBinding;
35use crate::entity::fluid_contact::EntityFluidContact;
36use crate::entity::{
37 EntityGeneration, EntityLevelCallback, EntityMoveError, InsideBlockEffectType,
38 NullEntityCallback, RemovalReason, SharedEntity,
39};
40use crate::physics::EntityPhysicsState;
41use crate::portal::{PortalKind, PortalProcessResult, PortalProcessor};
42use crate::world::World;
43
44const BOARDING_COOLDOWN: i32 = 60;
45const PISTON_MOVEMENT_LIMIT: f64 = 0.51;
46const PISTON_ZERO_MOVEMENT_EPSILON: f64 = 1.0e-7;
47const PISTON_APPLIED_MOVEMENT_EPSILON: f64 = 1.0e-5;
48const STUCK_SPEED_MULTIPLIER_EPSILON: f64 = 1.0e-7;
49const MOVEMENT_TRACE_LIMIT: usize = 100;
50const MOVEMENT_TRACE_POSITION_EPSILON_SQ: f64 = 9.999_999_4e-11;
51pub 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
88pub(crate) struct EntityPushState {
89 pub(crate) position: DVec3,
90 pub(crate) no_physics: bool,
91 pub(crate) is_vehicle: bool,
92}
93
94#[derive(Debug, Default)]
95struct EntityMovementTrace {
96 movement_this_tick: SmallVec<[EntityMovement; 1]>,
100 final_movements_this_tick: SmallVec<[EntityMovement; 1]>,
101}
102
103impl EntityMovementTrace {
104 fn record(&mut self, movement: EntityMovement) {
105 if self.movement_this_tick.len() >= MOVEMENT_TRACE_LIMIT {
106 let first = self.movement_this_tick.remove(0);
107 let second = self.movement_this_tick.remove(0);
108 self.movement_this_tick
109 .insert(0, EntityMovement::new(first.from(), second.to()));
110 }
111
112 self.movement_this_tick.push(movement);
113 }
114
115 fn remove_latest_recording(&mut self) {
116 self.movement_this_tick.pop();
117 }
118
119 fn reset(&mut self) {
120 self.movement_this_tick.clear();
121 self.final_movements_this_tick.clear();
122 }
123
124 fn take_for_block_effects(
125 &mut self,
126 old_position: DVec3,
127 position: DVec3,
128 ) -> SmallVec<[EntityMovement; 1]> {
129 self.final_movements_this_tick.clear();
130 self.final_movements_this_tick
131 .extend(self.movement_this_tick.drain(..));
132
133 if let Some(last_movement) = self.final_movements_this_tick.last().copied() {
134 if (last_movement.to() - position).length_squared() > MOVEMENT_TRACE_POSITION_EPSILON_SQ
135 {
136 self.final_movements_this_tick
137 .push(EntityMovement::new(last_movement.to(), position));
138 }
139 } else {
140 self.final_movements_this_tick
141 .push(EntityMovement::new(old_position, position));
142 }
143
144 self.final_movements_this_tick.clone()
145 }
146
147 fn last_for_block_effects(&self) -> SmallVec<[EntityMovement; 1]> {
148 self.final_movements_this_tick.clone()
149 }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq)]
154struct EntityPistonMovement {
155 deltas: [f64; 3],
156 game_time: i64,
157}
158
159impl EntityPistonMovement {
160 const fn new() -> Self {
161 Self {
162 deltas: [0.0; 3],
163 game_time: 0,
164 }
165 }
166
167 fn limit_movement(&mut self, movement: DVec3, current_game_time: i64) -> DVec3 {
168 if movement.length_squared() <= PISTON_ZERO_MOVEMENT_EPSILON {
169 return movement;
170 }
171
172 if current_game_time != self.game_time {
173 self.deltas = [0.0; 3];
174 self.game_time = current_game_time;
175 }
176
177 if movement.x != 0.0 {
178 return self.apply_axis_restriction(0, movement.x, DVec3::X);
179 }
180 if movement.y != 0.0 {
181 return self.apply_axis_restriction(1, movement.y, DVec3::Y);
182 }
183 if movement.z != 0.0 {
184 return self.apply_axis_restriction(2, movement.z, DVec3::Z);
185 }
186
187 DVec3::ZERO
188 }
189
190 fn apply_axis_restriction(&mut self, axis: usize, amount: f64, unit: DVec3) -> DVec3 {
191 let limited =
192 (amount + self.deltas[axis]).clamp(-PISTON_MOVEMENT_LIMIT, PISTON_MOVEMENT_LIMIT);
193 let applied = limited - self.deltas[axis];
194 self.deltas[axis] = limited;
195
196 if applied.abs() <= PISTON_APPLIED_MOVEMENT_EPSILON {
197 DVec3::ZERO
198 } else {
199 unit * applied
200 }
201 }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq)]
206pub struct EntityAmethystStepSound {
207 pub volume: f32,
209 pub pitch: f32,
211}
212
213#[derive(Debug, Clone, Copy, PartialEq)]
220pub struct EntityBaseState {
221 tick_count: i32,
222 first_tick: bool,
223 position: DVec3,
224 old_position: DVec3,
225 last_known_position: Option<DVec3>,
226 last_known_speed: DVec3,
227 velocity: DVec3,
228 rotation: (f32, f32),
229 old_rotation: (f32, f32),
230 pose: EntityPose,
231 dimensions: EntityDimensions,
232 bounding_box: WorldAabb,
233 movement_flags: EntityMovementFlags,
234 ground_contact: EntityGroundContact,
235 movement_progress: EntityMovementProgress,
236 fire_freeze: EntityFireFreezeState,
237 in_block_state: Option<BlockStateId>,
238 fluid_contact: EntityFluidContact,
239 was_eye_in_water: bool,
240 was_touching_water: bool,
241 piston_movement: EntityPistonMovement,
242 fall_distance: f64,
243 stuck_speed_multiplier: DVec3,
244 no_physics: bool,
245 needs_velocity_sync: bool,
246 hurt_marked: bool,
247 requires_precise_position: bool,
248}
249
250impl EntityBaseState {
251 #[must_use]
253 pub fn new(position: DVec3, dimensions: EntityDimensions) -> Self {
254 require_finite_position(position, "position");
255 Self {
256 tick_count: 0,
257 first_tick: true,
258 position,
259 old_position: position,
260 last_known_position: None,
261 last_known_speed: DVec3::ZERO,
262 velocity: DVec3::ZERO,
263 rotation: (0.0, 0.0),
264 old_rotation: (0.0, 0.0),
265 pose: EntityPose::Standing,
266 dimensions,
267 bounding_box: Self::make_bounding_box(position, dimensions),
268 movement_flags: EntityMovementFlags::new(),
269 ground_contact: EntityGroundContact::airborne(),
270 movement_progress: EntityMovementProgress::new(),
271 fire_freeze: EntityFireFreezeState::new(),
272 in_block_state: None,
273 fluid_contact: EntityFluidContact::default(),
274 was_eye_in_water: false,
275 was_touching_water: false,
276 piston_movement: EntityPistonMovement::new(),
277 fall_distance: 0.0,
278 stuck_speed_multiplier: DVec3::ZERO,
279 no_physics: false,
280 needs_velocity_sync: false,
281 hurt_marked: false,
282 requires_precise_position: false,
283 }
284 }
285
286 #[must_use]
291 pub fn new_with_bounding_box(
292 position: DVec3,
293 dimensions: EntityDimensions,
294 bounding_box: WorldAabb,
295 ) -> Self {
296 Self {
297 bounding_box,
298 ..Self::new(position, dimensions)
299 }
300 }
301
302 #[must_use]
303 fn make_bounding_box(position: DVec3, dimensions: EntityDimensions) -> WorldAabb {
304 WorldAabb::entity_box(
305 position.x,
306 position.y,
307 position.z,
308 f64::from(dimensions.half_width()),
309 f64::from(dimensions.height),
310 )
311 }
312
313 #[must_use]
315 pub fn with_velocity(mut self, velocity: DVec3) -> Self {
316 if velocity.is_finite() {
317 self.velocity = velocity;
318 }
319 self
320 }
321
322 #[must_use]
324 pub fn with_old_position(mut self, old_position: DVec3) -> Self {
325 require_finite_position(old_position, "old position");
326 self.old_position = old_position;
327 self
328 }
329
330 #[must_use]
332 pub fn with_rotation(mut self, rotation: (f32, f32)) -> Self {
333 let rotation = normalize_rotation(rotation);
334 self.rotation = rotation;
335 self.old_rotation = rotation;
336 self
337 }
338
339 #[must_use]
341 pub const fn with_fall_distance(mut self, fall_distance: f64) -> Self {
342 self.fall_distance = fall_distance;
343 self
344 }
345
346 #[must_use]
348 pub const fn with_fire_freeze_state(mut self, fire_freeze: EntityFireFreezeState) -> Self {
349 self.fire_freeze = fire_freeze;
350 self
351 }
352
353 #[must_use]
355 pub const fn with_on_ground(mut self, on_ground: bool) -> Self {
356 self.movement_flags = self.movement_flags.with_on_ground(on_ground);
357 self.ground_contact = if on_ground {
358 EntityGroundContact::on_ground(None)
359 } else {
360 EntityGroundContact::airborne()
361 };
362 self
363 }
364}
365
366pub struct EntityBase {
392 damage_history: DamageHistoryBinding,
393 generation: EntityGeneration,
395 id: i32,
397 uuid: Uuid,
399 world: SyncMutex<Weak<World>>,
401 state: SyncMutex<EntityBaseState>,
403 save_data: SyncMutex<EntityBaseSaveData>,
405 movement_trace: SyncMutex<EntityMovementTrace>,
407 lifecycle: SyncMutex<EntityLifecycleState>,
409 relationships: SyncMutex<EntityRelationshipState>,
411 portal_process: SyncMutex<Option<PortalProcessor>>,
413 level_callback: SyncMutex<Arc<dyn EntityLevelCallback>>,
415}
416
417impl EntityBase {
418 #[must_use]
420 pub fn new(id: i32, position: DVec3, dimensions: EntityDimensions, world: Weak<World>) -> Self {
421 Self::new_with_state(id, EntityBaseState::new(position, dimensions), world)
422 }
423
424 #[must_use]
426 #[expect(
427 clippy::large_types_passed_by_value,
428 reason = "EntityBaseState is an owned construction snapshot built with with_* helpers"
429 )]
430 pub fn new_with_state(id: i32, state: EntityBaseState, world: Weak<World>) -> Self {
431 Self::with_uuid_and_state(id, Uuid::new_v4(), state, world)
432 }
433
434 #[must_use]
438 pub fn with_uuid(
439 id: i32,
440 uuid: Uuid,
441 position: DVec3,
442 dimensions: EntityDimensions,
443 world: Weak<World>,
444 ) -> Self {
445 Self::with_uuid_and_state(id, uuid, EntityBaseState::new(position, dimensions), world)
446 }
447
448 #[must_use]
453 #[expect(
454 clippy::large_types_passed_by_value,
455 reason = "EntityBaseState is an owned construction snapshot built with with_* helpers"
456 )]
457 pub fn with_uuid_and_state(
458 id: i32,
459 uuid: Uuid,
460 state: EntityBaseState,
461 world: Weak<World>,
462 ) -> Self {
463 Self {
464 damage_history: DamageHistoryBinding::default(),
465 generation: EntityGeneration::next(),
466 id,
467 uuid,
468 world: SyncMutex::new(world),
469 state: SyncMutex::new(state),
470 save_data: SyncMutex::new(EntityBaseSaveData::new()),
471 movement_trace: SyncMutex::new(EntityMovementTrace::default()),
472 lifecycle: SyncMutex::new(EntityLifecycleState::new()),
473 relationships: SyncMutex::new(EntityRelationshipState::default()),
474 portal_process: SyncMutex::new(None),
475 level_callback: SyncMutex::new(Arc::new(NullEntityCallback)),
476 }
477 }
478
479 #[must_use]
481 pub fn from_load(load: EntityBaseLoad, dimensions: EntityDimensions) -> Self {
482 let base = Self::with_uuid_and_state(
483 load.id,
484 load.uuid,
485 EntityBaseState::new(load.position, dimensions)
486 .with_velocity(load.velocity)
487 .with_rotation(load.rotation)
488 .with_fall_distance(load.fall_distance)
489 .with_fire_freeze_state(load.fire_freeze)
490 .with_on_ground(load.on_ground),
491 load.world,
492 );
493 base.replace_save_data(load.save_data);
494 base
495 }
496
497 pub(crate) const fn damage_history(&self) -> &DamageHistoryBinding {
498 &self.damage_history
499 }
500
501 #[inline]
503 pub const fn generation(&self) -> EntityGeneration {
504 self.generation
505 }
506
507 #[inline]
509 pub const fn id(&self) -> i32 {
510 self.id
511 }
512
513 #[inline]
515 pub const fn uuid(&self) -> Uuid {
516 self.uuid
517 }
518
519 #[inline]
521 pub fn position(&self) -> DVec3 {
522 self.state.lock().position
523 }
524
525 #[inline]
527 pub fn old_position(&self) -> DVec3 {
528 self.state.lock().old_position
529 }
530
531 #[inline]
533 pub fn known_speed(&self) -> DVec3 {
534 self.state.lock().last_known_speed
535 }
536
537 #[inline]
539 pub fn tick_count(&self) -> i32 {
540 self.state.lock().tick_count
541 }
542
543 #[inline]
545 pub fn is_first_tick(&self) -> bool {
546 self.state.lock().first_tick
547 }
548
549 #[inline]
551 pub fn set_first_tick(&self, first_tick: bool) {
552 self.state.lock().first_tick = first_tick;
553 }
554
555 #[inline]
557 pub fn bounding_box(&self) -> WorldAabb {
558 self.state.lock().bounding_box
559 }
560
561 pub(crate) fn physics_state(&self, input: EntityPhysicsStateInput) -> EntityPhysicsState {
563 let state = self.state.lock();
564 EntityPhysicsState::new(state.position, state.bounding_box, input.max_up_step)
565 .with_on_ground(state.movement_flags.on_ground())
566 .with_backs_off_from_edge(input.backs_off_from_edge)
567 .with_fall_distance(state.fall_distance)
568 .with_descending(input.descending)
569 .with_can_walk_on_powder_snow(input.can_walk_on_powder_snow)
570 .with_falling_block(input.is_falling_block)
571 }
572
573 #[inline]
575 pub fn pose(&self) -> EntityPose {
576 self.state.lock().pose
577 }
578
579 #[inline]
581 pub fn dimensions(&self) -> EntityDimensions {
582 self.state.lock().dimensions
583 }
584
585 #[inline]
587 pub fn velocity(&self) -> DVec3 {
588 self.state.lock().velocity
589 }
590
591 #[inline]
593 pub fn rotation(&self) -> (f32, f32) {
594 self.state.lock().rotation
595 }
596
597 #[inline]
599 pub fn old_rotation(&self) -> (f32, f32) {
600 self.state.lock().old_rotation
601 }
602
603 #[inline]
605 pub fn on_ground(&self) -> bool {
606 self.state.lock().movement_flags.on_ground()
607 }
608
609 #[inline]
611 pub fn movement_flags(&self) -> EntityMovementFlags {
612 self.state.lock().movement_flags
613 }
614
615 #[inline]
617 pub fn ground_contact(&self) -> EntityGroundContact {
618 self.state.lock().ground_contact
619 }
620
621 #[inline]
623 pub fn movement_progress(&self) -> EntityMovementProgress {
624 self.state.lock().movement_progress
625 }
626
627 #[inline]
629 pub fn fire_freeze_state(&self) -> EntityFireFreezeState {
630 self.state.lock().fire_freeze
631 }
632
633 pub fn save_data(&self) -> EntityBaseSaveData {
635 self.save_data.lock().clone()
636 }
637
638 pub fn replace_save_data(&self, save_data: EntityBaseSaveData) {
640 *self.save_data.lock() = save_data;
641 }
642
643 pub fn in_block_state(&self, world: &World) -> BlockStateId {
645 let mut state = self.state.lock();
646 if let Some(in_block_state) = state.in_block_state {
647 return in_block_state;
648 }
649
650 let position = state.position;
651 let block_pos = BlockPos::containing(position.x, position.y, position.z);
652 let in_block_state = world.get_block_state(block_pos);
653 state.in_block_state = Some(in_block_state);
654 in_block_state
655 }
656
657 pub fn set_fire_freeze_state(&self, fire_freeze: EntityFireFreezeState) {
659 self.state.lock().fire_freeze = fire_freeze;
660 }
661
662 #[inline]
664 pub fn horizontal_collision(&self) -> bool {
665 self.state.lock().movement_flags.horizontal_collision()
666 }
667
668 #[inline]
670 pub fn vertical_collision(&self) -> bool {
671 self.state.lock().movement_flags.vertical_collision()
672 }
673
674 #[inline]
676 pub fn vertical_collision_below(&self) -> bool {
677 self.state.lock().movement_flags.vertical_collision_below()
678 }
679
680 pub fn supporting_block(&self) -> Option<BlockPos> {
682 self.state.lock().ground_contact.supporting_block()
683 }
684
685 pub fn on_ground_no_blocks(&self) -> bool {
687 self.state.lock().ground_contact.on_ground_no_blocks()
688 }
689
690 pub fn fluid_contact(&self) -> EntityFluidContact {
692 self.state.lock().fluid_contact
693 }
694
695 pub fn was_eye_in_water(&self) -> bool {
697 self.state.lock().was_eye_in_water
698 }
699
700 pub fn was_touching_water(&self) -> bool {
702 self.state.lock().was_touching_water
703 }
704
705 #[inline]
707 pub fn fall_distance(&self) -> f64 {
708 self.state.lock().fall_distance
709 }
710
711 #[inline]
713 pub fn no_physics(&self) -> bool {
714 self.state.lock().no_physics
715 }
716
717 pub(crate) fn unmounted_push_state(&self) -> Option<EntityPushState> {
720 let mut relationships = self.relationships.lock();
721
722 if relationships.vehicle().is_some() {
723 return None;
724 }
725
726 let is_vehicle = relationships.first_passenger().is_some();
727 drop(relationships);
728
729 let state = self.state.lock();
730
731 Some(EntityPushState {
732 position: state.position,
733 no_physics: state.no_physics,
734 is_vehicle,
735 })
736 }
737
738 #[inline]
740 pub fn air_supply(&self) -> i32 {
741 self.save_data.lock().air_supply
742 }
743
744 #[inline]
746 pub fn portal_cooldown(&self) -> i32 {
747 self.save_data.lock().portal_cooldown
748 }
749
750 #[inline]
752 pub fn is_on_portal_cooldown(&self) -> bool {
753 self.portal_cooldown() > 0
754 }
755
756 #[inline]
758 pub fn portal_process(&self) -> Option<PortalProcessor> {
759 *self.portal_process.lock()
760 }
761
762 #[inline]
764 pub fn no_gravity(&self) -> bool {
765 self.save_data.lock().no_gravity
766 }
767
768 #[inline]
770 pub fn invulnerable(&self) -> bool {
771 self.save_data.lock().invulnerable
772 }
773
774 #[inline]
776 pub fn custom_name(&self) -> Option<TextComponent> {
777 self.save_data.lock().custom_name.clone()
778 }
779
780 #[inline]
782 pub fn custom_name_visible(&self) -> bool {
783 self.save_data.lock().custom_name_visible
784 }
785
786 #[inline]
788 pub fn silent(&self) -> bool {
789 self.save_data.lock().silent
790 }
791
792 #[inline]
794 pub fn glowing(&self) -> bool {
795 self.save_data.lock().glowing
796 }
797
798 pub fn tags(&self) -> Vec<String> {
800 self.save_data.lock().tags.iter().cloned().collect()
801 }
802
803 pub fn custom_data(&self) -> NbtCompound {
805 self.save_data.lock().custom_data.clone()
806 }
807
808 #[inline]
811 pub fn needs_velocity_sync(&self) -> bool {
812 self.state.lock().needs_velocity_sync
813 }
814
815 #[inline]
817 pub fn hurt_marked(&self) -> bool {
818 self.state.lock().hurt_marked
819 }
820
821 #[inline]
823 pub fn requires_precise_position(&self) -> bool {
824 self.state.lock().requires_precise_position
825 }
826
827 pub fn set_requires_precise_position(&self, requires_precise_position: bool) {
829 self.state.lock().requires_precise_position = requires_precise_position;
830 }
831
832 #[inline]
836 pub fn level(&self) -> Option<Arc<World>> {
837 self.world.lock().upgrade()
838 }
839
840 pub fn vehicle(&self) -> Option<SharedEntity> {
842 self.relationships.lock().vehicle()
843 }
844
845 pub fn passengers(&self) -> Vec<SharedEntity> {
847 self.relationships.lock().passengers()
848 }
849
850 pub fn first_passenger(&self) -> Option<SharedEntity> {
852 self.relationships.lock().first_passenger()
853 }
854
855 pub fn is_vehicle(&self) -> bool {
857 self.first_passenger().is_some()
858 }
859
860 pub fn has_passenger_id(&self, passenger_id: i32) -> bool {
862 self.relationships.lock().has_passenger_id(passenger_id)
863 }
864
865 pub fn boarding_cooldown(&self) -> i32 {
867 self.relationships.lock().boarding_cooldown
868 }
869
870 pub(crate) fn remove_passenger_id(&self, passenger_id: i32) -> bool {
872 self.relationships.lock().remove_passenger_id(passenger_id)
873 }
874
875 pub fn stop_riding(&self) {
877 self.stop_riding_relationship();
878 }
879
880 pub(crate) fn restore_passenger_relationship(vehicle: &SharedEntity, passenger: &SharedEntity) {
882 passenger.base().stop_riding_relationship();
883 Self::add_passenger_relationship(vehicle, passenger);
884 }
885
886 pub(crate) fn start_riding_relationship(vehicle: &SharedEntity, passenger: &SharedEntity) {
888 passenger.base().stop_riding_relationship();
889 Self::add_passenger_relationship(vehicle, passenger);
890 }
891
892 fn add_passenger_relationship(vehicle: &SharedEntity, passenger: &SharedEntity) {
893 if vehicle.base().has_passenger_id(passenger.id()) {
894 return;
895 }
896
897 passenger.base().relationships.lock().vehicle = Some(Arc::downgrade(vehicle));
898 let passenger_ref = Arc::downgrade(passenger);
899 let mut vehicle_relationships = vehicle.base().relationships.lock();
900 let first_passenger_is_player = vehicle_relationships
901 .first_passenger()
902 .is_some_and(|first| first.entity_type() == &vanilla_entities::PLAYER);
903 if passenger.entity_type() == &vanilla_entities::PLAYER && !first_passenger_is_player {
904 vehicle_relationships.passengers.insert(0, passenger_ref);
905 } else {
906 vehicle_relationships.passengers.push(passenger_ref);
907 }
908 }
909
910 pub(crate) fn set_boarding_cooldown(&self, boarding_cooldown: i32) {
912 self.relationships.lock().boarding_cooldown = boarding_cooldown;
913 }
914
915 pub fn advance_base_tick_state(&self) {
917 self.clear_in_block_state_for_base_tick();
918 self.set_old_rotation_to_current();
919 self.compute_known_speed();
920 self.decrement_boarding_cooldown();
921 }
922
923 fn clear_in_block_state_for_base_tick(&self) {
925 self.state.lock().in_block_state = None;
926 }
927
928 pub fn compute_known_speed(&self) {
930 let mut state = self.state.lock();
931 let previous_position = match state.last_known_position {
932 Some(position) => position,
933 None => state.position,
934 };
935 state.last_known_speed = state.position - previous_position;
936 state.last_known_position = Some(state.position);
937 }
938
939 fn decrement_boarding_cooldown(&self) {
940 let mut relationships = self.relationships.lock();
941 if relationships.boarding_cooldown > 0 {
942 relationships.boarding_cooldown -= 1;
943 }
944 }
945
946 pub fn process_portal_cooldown(&self) {
948 let mut save_data = self.save_data.lock();
949 if save_data.portal_cooldown > 0 {
950 save_data.portal_cooldown -= 1;
951 }
952 }
953
954 pub(crate) fn set_world(&self, world: Weak<World>) {
956 *self.world.lock() = world;
957 }
958
959 pub fn begin_pending_world_change(&self) -> Option<PendingWorldChangeToken> {
961 let mut lifecycle = self.lifecycle.lock();
962 if lifecycle.removal_reason.is_some() || lifecycle.pending_world_change.is_some() {
963 return None;
964 }
965 let token = lifecycle.next_world_change_token();
966 lifecycle.pending_world_change = Some(token);
967 Some(token)
968 }
969
970 pub(crate) fn begin_pending_player_respawn(&self) -> Option<PendingWorldChangeToken> {
975 let mut lifecycle = self.lifecycle.lock();
976 if !matches!(lifecycle.removal_reason, None | Some(RemovalReason::Killed))
977 || lifecycle.pending_world_change.is_some()
978 {
979 return None;
980 }
981 let token = lifecycle.next_world_change_token();
982 lifecycle.pending_world_change = Some(token);
983 Some(token)
984 }
985
986 pub fn finish_pending_world_change(&self, token: PendingWorldChangeToken) -> bool {
988 let mut lifecycle = self.lifecycle.lock();
989 if lifecycle.pending_world_change != Some(token) {
990 return false;
991 }
992 lifecycle.pending_world_change = None;
993 true
994 }
995
996 #[inline]
998 pub fn is_world_change_pending(&self) -> bool {
999 self.lifecycle.lock().pending_world_change.is_some()
1000 }
1001
1002 #[inline]
1004 pub fn is_world_change_token_pending(&self, token: PendingWorldChangeToken) -> bool {
1005 self.lifecycle.lock().pending_world_change == Some(token)
1006 }
1007
1008 #[inline]
1010 pub fn is_removed(&self) -> bool {
1011 self.lifecycle.lock().removal_reason.is_some()
1012 }
1013
1014 #[inline]
1016 pub fn removal_reason(&self) -> Option<RemovalReason> {
1017 self.lifecycle.lock().removal_reason
1018 }
1019
1020 pub fn set_removed(&self, reason: RemovalReason) {
1024 let (callback, released) = {
1025 let mut lifecycle = self.lifecycle.lock();
1026 if lifecycle.removal_reason.is_some() {
1027 (None, None)
1028 } else {
1029 lifecycle.removal_reason = Some(reason);
1030 lifecycle.pending_world_change = None;
1031 (
1032 Some(self.level_callback.lock().clone()),
1033 self.damage_history.release_on_removal(),
1034 )
1035 }
1036 };
1037 drop(released);
1038
1039 if let Some(callback) = callback {
1040 self.detach_from_relationships(reason);
1041 callback.on_remove(reason);
1042 *self.level_callback.lock() = Arc::new(NullEntityCallback);
1043 }
1044 }
1045
1046 fn detach_from_relationships(&self, reason: RemovalReason) {
1047 if reason.should_destroy() {
1048 self.stop_riding_relationship();
1049 }
1050 self.eject_passenger_relationships();
1051 }
1052
1053 fn stop_riding_relationship(&self) {
1054 let vehicle = {
1055 let mut relationships = self.relationships.lock();
1056 let vehicle = relationships.vehicle();
1057 relationships.vehicle = None;
1058 vehicle
1059 };
1060
1061 if let Some(vehicle) = vehicle {
1062 vehicle.base().remove_passenger_id(self.id);
1063 self.set_boarding_cooldown(BOARDING_COOLDOWN);
1064 }
1065 }
1066
1067 fn eject_passenger_relationships(&self) {
1068 let passengers = {
1069 let mut relationships = self.relationships.lock();
1070 let passengers = relationships.passengers();
1071 relationships.passengers.clear();
1072 passengers
1073 };
1074
1075 for passenger in passengers {
1076 if passenger.base().clear_vehicle_if(self.id) {
1077 passenger.base().set_boarding_cooldown(BOARDING_COOLDOWN);
1078 }
1079 }
1080 }
1081
1082 fn clear_vehicle_if(&self, vehicle_id: i32) -> bool {
1083 {
1084 let mut relationships = self.relationships.lock();
1085 let Some(vehicle) = relationships.vehicle() else {
1086 return false;
1087 };
1088 if vehicle.id() != vehicle_id {
1089 return false;
1090 }
1091 }
1092
1093 if let Err(error) = self.try_set_position(self.position()) {
1094 log::warn!(
1095 "Failed to refresh passenger {} manager position before clearing vehicle {vehicle_id}: {error}",
1096 self.id
1097 );
1098 }
1099
1100 let mut relationships = self.relationships.lock();
1101 let Some(vehicle) = relationships.vehicle() else {
1102 return false;
1103 };
1104 if vehicle.id() != vehicle_id {
1105 return false;
1106 }
1107 relationships.vehicle = None;
1108 true
1109 }
1110
1111 pub fn clear_removed(&self) -> bool {
1116 let mut lifecycle = self.lifecycle.lock();
1117 let was_removed = lifecycle.removal_reason.is_some();
1118 lifecycle.removal_reason = None;
1119 lifecycle.pending_world_change = None;
1120 self.damage_history.clear_removed();
1121 was_removed
1122 }
1123
1124 pub fn set_level_callback(&self, callback: Arc<dyn EntityLevelCallback>) {
1126 *self.level_callback.lock() = callback;
1127 }
1128
1129 #[must_use = "movement commits can fail when world entity state rejects the update"]
1131 pub fn try_set_position(&self, pos: DVec3) -> Result<(), EntityMoveError> {
1132 require_finite_position(pos, "position");
1133 let old_pos = self.state.lock().position;
1134 let callback = self.level_callback.lock().clone();
1135 callback.validate_move(old_pos, pos)?;
1136 self.set_position_local_unchecked(pos);
1137 if let Err(error) = callback.on_move_committed(old_pos, pos) {
1138 self.set_position_local_unchecked(old_pos);
1139 return Err(error);
1140 }
1141 Ok(())
1142 }
1143
1144 pub(crate) fn set_position_local(&self, pos: DVec3) {
1148 let callback = self.level_callback.lock().clone();
1149 assert!(
1150 callback.allows_local_position_update(),
1151 "entity {} local position update bypassed world entity manager",
1152 self.id
1153 );
1154 self.set_position_local_unchecked(pos);
1155 }
1156
1157 fn set_position_local_unchecked(&self, pos: DVec3) {
1158 require_finite_position(pos, "position");
1159 {
1160 let mut state = self.state.lock();
1161 let old = state.position;
1162 state.position = pos;
1163 state.bounding_box = EntityBaseState::make_bounding_box(pos, state.dimensions);
1164 if BlockPos::containing(old.x, old.y, old.z)
1165 != BlockPos::containing(pos.x, pos.y, pos.z)
1166 {
1167 state.in_block_state = None;
1168 }
1169 }
1170 }
1171
1172 pub fn set_old_position_to_current(&self) {
1174 let mut state = self.state.lock();
1175 state.old_position = state.position;
1176 }
1177
1178 pub fn set_old_position(&self, old_position: DVec3) {
1180 require_finite_position(old_position, "old position");
1181 self.state.lock().old_position = old_position;
1182 }
1183
1184 pub fn set_old_rotation_to_current(&self) {
1186 let mut state = self.state.lock();
1187 state.old_rotation = state.rotation;
1188 }
1189
1190 pub fn set_old_yaw_to_current(&self) {
1192 let mut state = self.state.lock();
1193 state.old_rotation.0 = state.rotation.0;
1194 }
1195
1196 pub fn set_old_rotation(&self, old_rotation: (f32, f32)) {
1198 self.state.lock().old_rotation = normalize_rotation(old_rotation);
1199 }
1200
1201 pub fn record_movement_this_tick(&self, movement: EntityMovement) {
1203 self.movement_trace.lock().record(movement);
1204 }
1205
1206 pub fn remove_latest_movement_recording(&self) {
1208 self.movement_trace.lock().remove_latest_recording();
1209 }
1210
1211 pub fn clear_movement_this_tick(&self) {
1213 self.movement_trace.lock().reset();
1214 }
1215
1216 pub fn take_movements_for_block_effects(&self) -> SmallVec<[EntityMovement; 1]> {
1218 let (old_position, position) = {
1219 let state = self.state.lock();
1220 (state.old_position, state.position)
1221 };
1222
1223 self.movement_trace
1224 .lock()
1225 .take_for_block_effects(old_position, position)
1226 }
1227
1228 pub fn last_movements_for_block_effects(&self) -> SmallVec<[EntityMovement; 1]> {
1230 self.movement_trace.lock().last_for_block_effects()
1231 }
1232
1233 pub fn set_bounding_box(&self, bounding_box: WorldAabb) {
1238 self.state.lock().bounding_box = bounding_box;
1239 self.notify_bounding_box_changed(bounding_box);
1240 }
1241
1242 pub fn set_pose_and_dimensions(&self, pose: EntityPose, dimensions: EntityDimensions) {
1244 let bounding_box = {
1245 let mut state = self.state.lock();
1246 state.pose = pose;
1247 state.dimensions = dimensions;
1248 state.bounding_box = EntityBaseState::make_bounding_box(state.position, dimensions);
1249 state.bounding_box
1250 };
1251 self.notify_bounding_box_changed(bounding_box);
1252 }
1253
1254 fn notify_bounding_box_changed(&self, bounding_box: WorldAabb) {
1255 let callback = self.level_callback.lock().clone();
1256 callback.on_bounding_box_changed(bounding_box);
1257 }
1258
1259 pub fn set_velocity(&self, velocity: DVec3) {
1261 if velocity.is_finite() {
1262 self.state.lock().velocity = velocity;
1263 }
1264 }
1265
1266 pub(crate) fn push_impulse(&self, impulse: DVec3) {
1270 if !impulse.is_finite() {
1271 return;
1272 }
1273
1274 let mut state = self.state.lock();
1275 let velocity = state.velocity + impulse;
1276
1277 if velocity.is_finite() {
1278 state.velocity = velocity;
1279 }
1280
1281 state.needs_velocity_sync = true;
1282 }
1283
1284 #[inline]
1286 pub fn advance_tick_count(&self) {
1287 let mut state = self.state.lock();
1288 state.tick_count = state.tick_count.wrapping_add(1);
1289 }
1290
1291 #[inline]
1293 pub fn set_tick_count(&self, tick_count: i32) {
1294 self.state.lock().tick_count = tick_count;
1295 }
1296
1297 pub fn record_movement_progress(
1299 &self,
1300 clipped_movement: DVec3,
1301 climbing: bool,
1302 ) -> EntityMovementProgress {
1303 let mut state = self.state.lock();
1304 state
1305 .movement_progress
1306 .add_movement(clipped_movement, climbing);
1307 state.movement_progress
1308 }
1309
1310 pub fn set_next_step(&self, next_step: f32) {
1312 self.state.lock().movement_progress.next_step = next_step;
1313 }
1314
1315 pub fn amethyst_step_sound(&self, tick_count: i32) -> Option<EntityAmethystStepSound> {
1317 let intensity = {
1318 let mut state = self.state.lock();
1319 let progress = &mut state.movement_progress;
1320 if tick_count < progress.last_crystal_sound_play_tick + 20 {
1321 return None;
1322 }
1323
1324 let tick_delta = tick_count - progress.last_crystal_sound_play_tick;
1325 progress.crystal_sound_intensity *= 0.997_f32.powi(tick_delta);
1326 progress.crystal_sound_intensity = (progress.crystal_sound_intensity + 0.07).min(1.0);
1327 progress.last_crystal_sound_play_tick = tick_count;
1328 progress.crystal_sound_intensity
1329 };
1330
1331 let pitch = rand::random_range(0.5..0.5 + intensity * 1.2);
1332 let volume = 0.1 + intensity * 1.2;
1333 Some(EntityAmethystStepSound { volume, pitch })
1334 }
1335
1336 pub fn set_rotation(&self, rotation: (f32, f32)) {
1338 self.state.lock().rotation = normalize_rotation(rotation);
1339 }
1340
1341 pub fn set_no_physics(&self, no_physics: bool) {
1343 self.state.lock().no_physics = no_physics;
1344 }
1345
1346 pub fn set_air_supply(&self, air_supply: i32) {
1348 self.save_data.lock().air_supply = air_supply;
1349 }
1350
1351 pub fn set_portal_cooldown(&self, portal_cooldown: i32) {
1353 self.save_data.lock().portal_cooldown = portal_cooldown;
1354 }
1355
1356 pub fn set_as_inside_portal(&self, portal: PortalKind, entry_position: BlockPos) {
1358 let mut portal_process = self.portal_process.lock();
1359 match portal_process.as_mut() {
1360 Some(process) if process.is_same_portal(portal) => {
1361 process.set_as_inside_portal(entry_position);
1362 }
1363 _ => {
1364 *portal_process = Some(PortalProcessor::new(portal, entry_position));
1365 }
1366 }
1367 }
1368
1369 pub fn process_portal_teleportation(
1371 &self,
1372 allowed_to_teleport: bool,
1373 transition_time: i32,
1374 ) -> Option<PortalProcessResult> {
1375 self.portal_process.lock().as_mut().map(|process| {
1376 process.process_portal_teleportation(allowed_to_teleport, transition_time)
1377 })
1378 }
1379
1380 pub(crate) fn set_portal_process(&self, portal_process: Option<PortalProcessor>) {
1382 *self.portal_process.lock() = portal_process;
1383 }
1384
1385 pub fn clear_portal_process(&self) {
1387 *self.portal_process.lock() = None;
1388 }
1389
1390 pub fn set_no_gravity(&self, no_gravity: bool) {
1392 self.save_data.lock().no_gravity = no_gravity;
1393 }
1394
1395 pub fn set_invulnerable(&self, invulnerable: bool) {
1397 self.save_data.lock().invulnerable = invulnerable;
1398 }
1399
1400 pub fn set_custom_name(&self, custom_name: Option<TextComponent>) {
1402 self.save_data.lock().custom_name = custom_name;
1403 }
1404
1405 pub fn set_custom_name_visible(&self, visible: bool) {
1407 self.save_data.lock().custom_name_visible = visible;
1408 }
1409
1410 pub fn set_silent(&self, silent: bool) {
1412 self.save_data.lock().silent = silent;
1413 }
1414
1415 pub fn set_glowing(&self, glowing: bool) {
1417 self.save_data.lock().glowing = glowing;
1418 }
1419
1420 pub fn add_tag(&self, tag: String) -> bool {
1422 self.save_data.lock().add_tag(tag)
1423 }
1424
1425 pub fn remove_tag(&self, tag: &str) -> bool {
1427 self.save_data.lock().tags.remove(tag)
1428 }
1429
1430 pub fn set_custom_data(&self, custom_data: NbtCompound) {
1432 self.save_data.lock().custom_data = custom_data;
1433 }
1434
1435 pub fn mark_velocity_sync(&self) {
1437 self.state.lock().needs_velocity_sync = true;
1438 }
1439
1440 pub fn clear_velocity_sync(&self) {
1442 self.state.lock().needs_velocity_sync = false;
1443 }
1444
1445 pub fn mark_hurt(&self) {
1447 self.state.lock().hurt_marked = true;
1448 }
1449
1450 pub fn clear_hurt_mark(&self) {
1452 self.state.lock().hurt_marked = false;
1453 }
1454
1455 pub fn set_fall_distance(&self, fall_distance: f64) {
1457 self.state.lock().fall_distance = fall_distance;
1458 }
1459
1460 pub fn accumulate_fall_distance(&self, vertical_movement: f64) {
1462 self.state.lock().fall_distance -= f64::from(vertical_movement as f32);
1463 }
1464
1465 pub fn reset_fall_distance(&self) {
1467 self.set_fall_distance(0.0);
1468 }
1469
1470 pub fn remaining_fire_ticks(&self) -> i32 {
1472 self.state.lock().fire_freeze.remaining_fire_ticks()
1473 }
1474
1475 pub fn set_remaining_fire_ticks(&self, remaining_fire_ticks: i32) {
1477 self.state.lock().fire_freeze.remaining_fire_ticks = remaining_fire_ticks;
1478 }
1479
1480 pub fn ticks_frozen(&self) -> i32 {
1482 self.state.lock().fire_freeze.ticks_frozen()
1483 }
1484
1485 pub fn set_ticks_frozen(&self, ticks_frozen: i32) {
1487 self.state.lock().fire_freeze.ticks_frozen = ticks_frozen;
1488 }
1489
1490 pub fn is_in_powder_snow(&self) -> bool {
1492 self.state.lock().fire_freeze.is_in_powder_snow()
1493 }
1494
1495 pub fn was_in_powder_snow(&self) -> bool {
1497 self.state.lock().fire_freeze.was_in_powder_snow()
1498 }
1499
1500 pub fn set_visual_fire(&self, has_visual_fire: bool) {
1502 self.state.lock().fire_freeze.has_visual_fire = has_visual_fire;
1503 }
1504
1505 pub fn has_visual_fire(&self) -> bool {
1507 self.state.lock().fire_freeze.has_visual_fire()
1508 }
1509
1510 pub fn is_on_fire(&self, fire_immune: bool) -> bool {
1512 !fire_immune && self.remaining_fire_ticks() > 0
1513 }
1514
1515 pub fn is_freezing(&self) -> bool {
1517 self.state.lock().fire_freeze.is_freezing()
1518 }
1519
1520 pub fn is_fully_frozen(&self, ticks_required_to_freeze: i32) -> bool {
1522 self.state
1523 .lock()
1524 .fire_freeze
1525 .is_fully_frozen(ticks_required_to_freeze)
1526 }
1527
1528 pub fn advance_powder_snow_contact_for_base_tick(&self) {
1530 let mut state = self.state.lock();
1531 state.fire_freeze.was_in_powder_snow = state.fire_freeze.is_in_powder_snow;
1532 state.fire_freeze.is_in_powder_snow = false;
1533 }
1534
1535 pub fn advance_fire_tick(&self, fire_immune: bool, in_lava: bool) -> bool {
1539 let mut state = self.state.lock();
1540 if state.fire_freeze.remaining_fire_ticks <= 0 {
1541 return false;
1542 }
1543
1544 if fire_immune {
1545 state.fire_freeze.remaining_fire_ticks = state.fire_freeze.remaining_fire_ticks.min(0);
1546 return false;
1547 }
1548
1549 let should_damage = state.fire_freeze.remaining_fire_ticks % 20 == 0 && !in_lava;
1550 state.fire_freeze.remaining_fire_ticks -= 1;
1551 should_damage
1552 }
1553
1554 pub fn clear_freeze(&self) {
1556 self.set_ticks_frozen(0);
1557 }
1558
1559 pub fn clear_fire(&self) {
1561 let mut state = self.state.lock();
1562 state.fire_freeze.remaining_fire_ticks = state.fire_freeze.remaining_fire_ticks.min(0);
1563 }
1564
1565 pub fn ignite_for_ticks(&self, number_of_ticks: i32, remaining_fire_ticks_cap: Option<i32>) {
1567 let mut state = self.state.lock();
1568 Self::ignite_for_ticks_in_state(
1569 &mut state.fire_freeze,
1570 number_of_ticks,
1571 remaining_fire_ticks_cap,
1572 );
1573 }
1574
1575 pub fn apply_inside_block_effect(
1577 &self,
1578 effect_type: InsideBlockEffectType,
1579 can_freeze: bool,
1580 fire_immune: bool,
1581 fire_ignite_extra_ticks: i32,
1582 ticks_required_to_freeze: i32,
1583 remaining_fire_ticks_cap: Option<i32>,
1584 ) {
1585 let mut state = self.state.lock();
1586 match effect_type {
1587 InsideBlockEffectType::Freeze => {
1588 state.fire_freeze.is_in_powder_snow = true;
1589 if can_freeze {
1590 state.fire_freeze.ticks_frozen =
1591 ticks_required_to_freeze.min(state.fire_freeze.ticks_frozen + 1);
1592 }
1593 }
1594 InsideBlockEffectType::ClearFreeze => {
1595 state.fire_freeze.ticks_frozen = 0;
1596 }
1597 InsideBlockEffectType::FireIgnite => {
1598 Self::apply_fire_ignite(
1599 &mut state.fire_freeze,
1600 fire_immune,
1601 fire_ignite_extra_ticks,
1602 remaining_fire_ticks_cap,
1603 );
1604 }
1605 InsideBlockEffectType::LavaIgnite => {
1606 if !fire_immune {
1607 Self::ignite_for_ticks_in_state(
1608 &mut state.fire_freeze,
1609 LAVA_IGNITE_TICKS,
1610 remaining_fire_ticks_cap,
1611 );
1612 }
1613 }
1614 InsideBlockEffectType::Extinguish => {
1615 state.fire_freeze.remaining_fire_ticks =
1616 state.fire_freeze.remaining_fire_ticks.min(0);
1617 }
1618 }
1619 }
1620
1621 fn apply_fire_ignite(
1622 fire_freeze: &mut EntityFireFreezeState,
1623 fire_immune: bool,
1624 fire_ignite_extra_ticks: i32,
1625 remaining_fire_ticks_cap: Option<i32>,
1626 ) {
1627 if fire_immune {
1628 return;
1629 }
1630
1631 if fire_freeze.remaining_fire_ticks < 0 {
1632 Self::set_remaining_fire_ticks_in_state(
1633 fire_freeze,
1634 fire_freeze.remaining_fire_ticks + 1,
1635 remaining_fire_ticks_cap,
1636 );
1637 } else if fire_ignite_extra_ticks > 0 {
1638 Self::set_remaining_fire_ticks_in_state(
1639 fire_freeze,
1640 fire_freeze.remaining_fire_ticks + fire_ignite_extra_ticks,
1641 remaining_fire_ticks_cap,
1642 );
1643 }
1644
1645 if fire_freeze.remaining_fire_ticks >= 0 {
1646 Self::ignite_for_ticks_in_state(
1647 fire_freeze,
1648 FIRE_IGNITE_TICKS,
1649 remaining_fire_ticks_cap,
1650 );
1651 }
1652 }
1653
1654 fn ignite_for_ticks_in_state(
1655 fire_freeze: &mut EntityFireFreezeState,
1656 number_of_ticks: i32,
1657 remaining_fire_ticks_cap: Option<i32>,
1658 ) {
1659 if fire_freeze.remaining_fire_ticks < number_of_ticks {
1660 Self::set_remaining_fire_ticks_in_state(
1661 fire_freeze,
1662 number_of_ticks,
1663 remaining_fire_ticks_cap,
1664 );
1665 }
1666 fire_freeze.ticks_frozen = 0;
1667 }
1668
1669 fn set_remaining_fire_ticks_in_state(
1670 fire_freeze: &mut EntityFireFreezeState,
1671 remaining_fire_ticks: i32,
1672 remaining_fire_ticks_cap: Option<i32>,
1673 ) {
1674 fire_freeze.remaining_fire_ticks =
1675 Self::cap_remaining_fire_ticks(remaining_fire_ticks, remaining_fire_ticks_cap);
1676 }
1677
1678 fn cap_remaining_fire_ticks(
1679 remaining_fire_ticks: i32,
1680 remaining_fire_ticks_cap: Option<i32>,
1681 ) -> i32 {
1682 remaining_fire_ticks_cap.map_or(remaining_fire_ticks, |cap| remaining_fire_ticks.min(cap))
1683 }
1684
1685 pub fn dampen_fall_distance_in_lava(&self) {
1687 let mut state = self.state.lock();
1688 if !state.first_tick && state.fluid_contact.lava_height() > 0.0 {
1689 state.fall_distance *= 0.5;
1690 }
1691 }
1692
1693 pub fn reset_fall_distance_in_water(&self) {
1695 let mut state = self.state.lock();
1696 if state.fluid_contact.water_height() > 0.0 {
1697 state.fall_distance = 0.0;
1698 }
1699 }
1700
1701 pub fn set_on_ground(&self, on_ground: bool) {
1703 let mut state = self.state.lock();
1704 state.movement_flags = state.movement_flags.with_on_ground(on_ground);
1705 if !on_ground {
1706 state.ground_contact = EntityGroundContact::airborne();
1707 }
1708 }
1709
1710 pub fn set_movement_flags(
1712 &self,
1713 movement_flags: EntityMovementFlags,
1714 ground_contact: EntityGroundContact,
1715 ) {
1716 let mut state = self.state.lock();
1717 state.movement_flags = movement_flags;
1718 state.ground_contact = ground_contact;
1719 }
1720
1721 pub fn set_ground_contact(&self, ground_contact: EntityGroundContact) {
1723 self.state.lock().ground_contact = ground_contact;
1724 }
1725
1726 pub fn set_fluid_contact(&self, fluid_contact: EntityFluidContact) {
1728 self.state.lock().fluid_contact = fluid_contact;
1729 }
1730
1731 #[inline]
1733 pub fn is_in_lava(&self) -> bool {
1734 let state = self.state.lock();
1735 !state.first_tick && state.fluid_contact.lava_height() > 0.0
1736 }
1737
1738 pub fn update_was_eye_in_water(&self) {
1741 let mut state = self.state.lock();
1742 state.was_eye_in_water = state.fluid_contact.eye_in_water();
1743 }
1744
1745 pub fn set_fluid_interaction(&self, fluid_contact: EntityFluidContact) {
1748 let mut state = self.state.lock();
1749 state.was_touching_water = fluid_contact.water_height() > 0.0;
1750 state.fluid_contact = fluid_contact;
1751 }
1752
1753 pub fn set_on_ground_with_movement(
1755 &self,
1756 on_ground: bool,
1757 horizontal_collision: bool,
1758 ground_contact: EntityGroundContact,
1759 ) {
1760 let mut state = self.state.lock();
1761 state.movement_flags = state
1762 .movement_flags
1763 .with_on_ground(on_ground)
1764 .with_horizontal_collision(horizontal_collision);
1765 state.ground_contact = ground_contact;
1766 }
1767
1768 pub fn clear_collision_flags(&self) {
1770 let mut state = self.state.lock();
1771 state.movement_flags = state.movement_flags.without_collisions();
1772 }
1773
1774 pub fn limit_piston_movement(&self, movement: DVec3, current_game_time: i64) -> DVec3 {
1776 self.state
1777 .lock()
1778 .piston_movement
1779 .limit_movement(movement, current_game_time)
1780 }
1781
1782 pub fn make_stuck_in_block(&self, speed_multiplier: DVec3) {
1784 let mut state = self.state.lock();
1785 state.fall_distance = 0.0;
1786 state.stuck_speed_multiplier = speed_multiplier;
1787 }
1788
1789 #[must_use]
1791 pub fn consume_stuck_speed_multiplier(&self, movement: DVec3, apply_multiplier: bool) -> DVec3 {
1792 let mut state = self.state.lock();
1793 if state.stuck_speed_multiplier.length_squared() <= STUCK_SPEED_MULTIPLIER_EPSILON {
1794 return movement;
1795 }
1796
1797 let stuck_speed_multiplier = state.stuck_speed_multiplier;
1798 state.stuck_speed_multiplier = DVec3::ZERO;
1799 state.velocity = DVec3::ZERO;
1800
1801 if apply_multiplier {
1802 movement * stuck_speed_multiplier
1803 } else {
1804 movement
1805 }
1806 }
1807}
1808
1809#[cfg(test)]
1810mod tests;