Skip to main content

steel_core/entity/base/
mod.rs

1//! Common base functionality shared by all entities.
2//!
3//! `EntityBase` contains the core fields and methods that every entity needs.
4//! Entities embed this struct and delegate common `Entity` trait methods to it.
5
6mod 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    mem,
23    sync::{Arc, Weak},
24};
25
26use glam::DVec3;
27use simdnbt::owned::NbtCompound;
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    EntityLevelCallback, EntityMoveError, InsideBlockEffectType, NullEntityCallback, RemovalReason,
39    SharedEntity,
40};
41use crate::physics::EntityPhysicsState;
42use crate::portal::{PortalKind, PortalProcessResult, PortalProcessor};
43use crate::world::World;
44
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;
51/// Default vanilla `Entity.getTicksRequiredToFreeze` value.
52pub const DEFAULT_TICKS_REQUIRED_TO_FREEZE: i32 = 140;
53/// Default vanilla `Entity.getMaxAirSupply` value.
54pub const DEFAULT_MAX_AIR_SUPPLY: i32 = 300;
55/// Vanilla scoreboard tag limit for a single entity.
56pub const MAX_ENTITY_TAGS: usize = 1024;
57const FIRE_IGNITE_TICKS: i32 = 8 * 20;
58const LAVA_IGNITE_TICKS: i32 = 15 * 20;
59
60fn require_finite_position(position: DVec3, field: &str) {
61    assert!(
62        position.is_finite(),
63        "entity {field} must be finite: {position:?}"
64    );
65}
66
67fn normalize_rotation(rotation: (f32, f32)) -> (f32, f32) {
68    assert!(
69        rotation.0.is_finite() && rotation.1.is_finite(),
70        "entity rotation must be finite: {rotation:?}"
71    );
72    (rotation.0 % 360.0, rotation.1.clamp(-90.0, 90.0) % 360.0)
73}
74
75#[derive(Debug, Clone, Copy)]
76pub(crate) struct EntityPhysicsStateInput {
77    pub(crate) max_up_step: f32,
78    pub(crate) backs_off_from_edge: bool,
79    pub(crate) descending: bool,
80    pub(crate) can_walk_on_powder_snow: bool,
81    pub(crate) is_falling_block: bool,
82}
83
84#[derive(Debug, Default)]
85struct EntityMovementTrace {
86    movement_this_tick: VecDeque<EntityMovement>,
87    final_movements_this_tick: Vec<EntityMovement>,
88}
89
90impl EntityMovementTrace {
91    fn record(&mut self, movement: EntityMovement) {
92        if self.movement_this_tick.len() >= MOVEMENT_TRACE_LIMIT {
93            let first = self.movement_this_tick.pop_front();
94            let second = self.movement_this_tick.pop_front();
95            match (first, second) {
96                (Some(first), Some(second)) => self
97                    .movement_this_tick
98                    .push_front(EntityMovement::new(first.from(), second.to())),
99                (Some(first), None) => self.movement_this_tick.push_front(first),
100                (None, _) => {}
101            }
102        }
103
104        self.movement_this_tick.push_back(movement);
105    }
106
107    fn remove_latest_recording(&mut self) {
108        self.movement_this_tick.pop_back();
109    }
110
111    fn reset(&mut self) {
112        self.movement_this_tick.clear();
113        self.final_movements_this_tick.clear();
114    }
115
116    fn take_for_block_effects(
117        &mut self,
118        old_position: DVec3,
119        position: DVec3,
120    ) -> Vec<EntityMovement> {
121        self.final_movements_this_tick.clear();
122        self.final_movements_this_tick
123            .extend(self.movement_this_tick.drain(..));
124
125        if let Some(last_movement) = self.final_movements_this_tick.last().copied() {
126            if (last_movement.to() - position).length_squared() > MOVEMENT_TRACE_POSITION_EPSILON_SQ
127            {
128                self.final_movements_this_tick
129                    .push(EntityMovement::new(last_movement.to(), position));
130            }
131        } else {
132            self.final_movements_this_tick
133                .push(EntityMovement::new(old_position, position));
134        }
135
136        self.final_movements_this_tick.as_slice().to_vec()
137    }
138
139    fn last_for_block_effects(&self) -> Vec<EntityMovement> {
140        self.final_movements_this_tick.as_slice().to_vec()
141    }
142}
143
144/// Per-tick piston movement accumulated by vanilla `Entity.limitPistonMovement`.
145#[derive(Debug, Clone, Copy, PartialEq)]
146struct EntityPistonMovement {
147    deltas: [f64; 3],
148    game_time: i64,
149}
150
151impl EntityPistonMovement {
152    const fn new() -> Self {
153        Self {
154            deltas: [0.0; 3],
155            game_time: 0,
156        }
157    }
158
159    fn limit_movement(&mut self, movement: DVec3, current_game_time: i64) -> DVec3 {
160        if movement.length_squared() <= PISTON_ZERO_MOVEMENT_EPSILON {
161            return movement;
162        }
163
164        if current_game_time != self.game_time {
165            self.deltas = [0.0; 3];
166            self.game_time = current_game_time;
167        }
168
169        if movement.x != 0.0 {
170            return self.apply_axis_restriction(0, movement.x, DVec3::X);
171        }
172        if movement.y != 0.0 {
173            return self.apply_axis_restriction(1, movement.y, DVec3::Y);
174        }
175        if movement.z != 0.0 {
176            return self.apply_axis_restriction(2, movement.z, DVec3::Z);
177        }
178
179        DVec3::ZERO
180    }
181
182    fn apply_axis_restriction(&mut self, axis: usize, amount: f64, unit: DVec3) -> DVec3 {
183        let limited =
184            (amount + self.deltas[axis]).clamp(-PISTON_MOVEMENT_LIMIT, PISTON_MOVEMENT_LIMIT);
185        let applied = limited - self.deltas[axis];
186        self.deltas[axis] = limited;
187
188        if applied.abs() <= PISTON_APPLIED_MOVEMENT_EPSILON {
189            DVec3::ZERO
190        } else {
191            unit * applied
192        }
193    }
194}
195
196/// Sound parameters for vanilla amethyst step chimes.
197#[derive(Debug, Clone, Copy, PartialEq)]
198pub struct EntityAmethystStepSound {
199    /// Chime volume.
200    pub volume: f32,
201    /// Chime pitch.
202    pub pitch: f32,
203}
204
205/// Vanilla `Entity` movement state stored as one locked snapshot.
206///
207/// Position, velocity, rotation, and ground contact are commonly read together
208/// by physics, saving, and future navigation code. Keeping them in one struct
209/// makes those ownership boundaries explicit while still exposing focused
210/// accessors through [`EntityBase`].
211#[derive(Debug, Clone, Copy, PartialEq)]
212pub struct EntityBaseState {
213    tick_count: i32,
214    position: DVec3,
215    old_position: DVec3,
216    last_known_position: Option<DVec3>,
217    last_known_speed: DVec3,
218    velocity: DVec3,
219    rotation: (f32, f32),
220    old_rotation: (f32, f32),
221    pose: EntityPose,
222    dimensions: EntityDimensions,
223    bounding_box: WorldAabb,
224    movement_flags: EntityMovementFlags,
225    ground_contact: EntityGroundContact,
226    movement_progress: EntityMovementProgress,
227    fire_freeze: EntityFireFreezeState,
228    in_block_state: Option<BlockStateId>,
229    fluid_contact: EntityFluidContact,
230    was_eye_in_water: bool,
231    piston_movement: EntityPistonMovement,
232    fall_distance: f64,
233    stuck_speed_multiplier: DVec3,
234    no_physics: bool,
235    needs_velocity_sync: bool,
236    hurt_marked: bool,
237}
238
239impl EntityBaseState {
240    /// Creates base state for a freshly spawned entity.
241    #[must_use]
242    pub fn new(position: DVec3, dimensions: EntityDimensions) -> Self {
243        require_finite_position(position, "position");
244        Self {
245            tick_count: 0,
246            position,
247            old_position: position,
248            last_known_position: None,
249            last_known_speed: DVec3::ZERO,
250            velocity: DVec3::ZERO,
251            rotation: (0.0, 0.0),
252            old_rotation: (0.0, 0.0),
253            pose: EntityPose::Standing,
254            dimensions,
255            bounding_box: Self::make_bounding_box(position, dimensions),
256            movement_flags: EntityMovementFlags::new(),
257            ground_contact: EntityGroundContact::airborne(),
258            movement_progress: EntityMovementProgress::new(),
259            fire_freeze: EntityFireFreezeState::new(),
260            in_block_state: None,
261            fluid_contact: EntityFluidContact::default(),
262            was_eye_in_water: false,
263            piston_movement: EntityPistonMovement::new(),
264            fall_distance: 0.0,
265            stuck_speed_multiplier: DVec3::ZERO,
266            no_physics: false,
267            needs_velocity_sync: false,
268            hurt_marked: false,
269        }
270    }
271
272    /// Creates base state with an explicit bounding box.
273    ///
274    /// Hanging entities and other special cases do not use the default
275    /// dimensions-centered box.
276    #[must_use]
277    pub fn new_with_bounding_box(
278        position: DVec3,
279        dimensions: EntityDimensions,
280        bounding_box: WorldAabb,
281    ) -> Self {
282        Self {
283            bounding_box,
284            ..Self::new(position, dimensions)
285        }
286    }
287
288    #[must_use]
289    fn make_bounding_box(position: DVec3, dimensions: EntityDimensions) -> WorldAabb {
290        WorldAabb::entity_box(
291            position.x,
292            position.y,
293            position.z,
294            f64::from(dimensions.half_width()),
295            f64::from(dimensions.height),
296        )
297    }
298
299    /// Sets velocity on this state snapshot.
300    #[must_use]
301    pub fn with_velocity(mut self, velocity: DVec3) -> Self {
302        if velocity.is_finite() {
303            self.velocity = velocity;
304        }
305        self
306    }
307
308    /// Sets previous position on this state snapshot.
309    #[must_use]
310    pub fn with_old_position(mut self, old_position: DVec3) -> Self {
311        require_finite_position(old_position, "old position");
312        self.old_position = old_position;
313        self
314    }
315
316    /// Sets rotation on this state snapshot.
317    #[must_use]
318    pub fn with_rotation(mut self, rotation: (f32, f32)) -> Self {
319        let rotation = normalize_rotation(rotation);
320        self.rotation = rotation;
321        self.old_rotation = rotation;
322        self
323    }
324
325    /// Sets accumulated fall distance on this state snapshot.
326    #[must_use]
327    pub const fn with_fall_distance(mut self, fall_distance: f64) -> Self {
328        self.fall_distance = fall_distance;
329        self
330    }
331
332    /// Sets base fire/freeze state on this construction snapshot.
333    #[must_use]
334    pub const fn with_fire_freeze_state(mut self, fire_freeze: EntityFireFreezeState) -> Self {
335        self.fire_freeze = fire_freeze;
336        self
337    }
338
339    /// Sets the ground-contact flag on this state snapshot.
340    #[must_use]
341    pub const fn with_on_ground(mut self, on_ground: bool) -> Self {
342        self.movement_flags = self.movement_flags.with_on_ground(on_ground);
343        self.ground_contact = if on_ground {
344            EntityGroundContact::on_ground(None)
345        } else {
346            EntityGroundContact::airborne()
347        };
348        self
349    }
350}
351
352/// Common fields and methods shared by all entities.
353///
354/// Entities embed this struct to avoid duplicating core identity, position,
355/// and lifecycle management code. The `Entity` trait implementation can then
356/// delegate to `EntityBase` methods for common functionality.
357///
358/// # Example
359///
360/// ```ignore
361/// pub struct MyEntity {
362///     base: EntityBase,
363///     // Entity-specific fields...
364/// }
365///
366/// impl Entity for MyEntity {
367///     fn id(&self) -> i32 { self.base.id() }
368///     fn uuid(&self) -> Uuid { self.base.uuid() }
369///     fn position(&self) -> DVec3 { self.base.position() }
370///     // ... delegate other common methods ...
371///
372///     // Entity-specific implementations:
373///     fn entity_type(&self) -> EntityTypeRef { vanilla_entities::MY_ENTITY }
374///     fn tick(&self) { /* custom tick logic */ }
375/// }
376/// ```
377pub struct EntityBase {
378    /// Unique network ID for this entity (session-local).
379    id: i32,
380    /// Persistent UUID for this entity.
381    uuid: Uuid,
382    /// The world this entity is in.
383    world: SyncMutex<Weak<World>>,
384    /// Current vanilla movement state.
385    state: SyncMutex<EntityBaseState>,
386    /// Shared vanilla save data outside the movement snapshot.
387    save_data: SyncMutex<EntityBaseSaveData>,
388    /// Per-tick movement segments used by vanilla block-contact effects.
389    movement_trace: SyncMutex<EntityMovementTrace>,
390    /// Removal and tick bookkeeping.
391    lifecycle: SyncMutex<EntityLifecycleState>,
392    /// Passenger, vehicle, and boarding-cooldown state.
393    relationships: SyncMutex<EntityRelationshipState>,
394    /// Active vanilla portal timing state.
395    portal_process: SyncMutex<Option<PortalProcessor>>,
396    /// Callback for entity lifecycle events.
397    level_callback: SyncMutex<Arc<dyn EntityLevelCallback>>,
398}
399
400impl EntityBase {
401    /// Creates a new `EntityBase` with a randomly generated UUID.
402    #[must_use]
403    pub fn new(id: i32, position: DVec3, dimensions: EntityDimensions, world: Weak<World>) -> Self {
404        Self::new_with_state(id, EntityBaseState::new(position, dimensions), world)
405    }
406
407    /// Creates a new `EntityBase` with a randomly generated UUID and explicit state.
408    #[must_use]
409    #[expect(
410        clippy::large_types_passed_by_value,
411        reason = "EntityBaseState is an owned construction snapshot built with with_* helpers"
412    )]
413    pub fn new_with_state(id: i32, state: EntityBaseState, world: Weak<World>) -> Self {
414        Self::with_uuid_and_state(id, Uuid::new_v4(), state, world)
415    }
416
417    /// Creates a new `EntityBase` with the specified UUID.
418    ///
419    /// Use this when loading entities from disk or when the UUID is known.
420    #[must_use]
421    pub fn with_uuid(
422        id: i32,
423        uuid: Uuid,
424        position: DVec3,
425        dimensions: EntityDimensions,
426        world: Weak<World>,
427    ) -> Self {
428        Self::with_uuid_and_state(id, uuid, EntityBaseState::new(position, dimensions), world)
429    }
430
431    /// Creates a new `EntityBase` with the specified UUID and restored movement state.
432    ///
433    /// Use this when loading entities from disk so the vanilla base fields are
434    /// reconstructed in one place.
435    #[must_use]
436    #[expect(
437        clippy::large_types_passed_by_value,
438        reason = "EntityBaseState is an owned construction snapshot built with with_* helpers"
439    )]
440    pub fn with_uuid_and_state(
441        id: i32,
442        uuid: Uuid,
443        state: EntityBaseState,
444        world: Weak<World>,
445    ) -> Self {
446        Self {
447            id,
448            uuid,
449            world: SyncMutex::new(world),
450            state: SyncMutex::new(state),
451            save_data: SyncMutex::new(EntityBaseSaveData::new()),
452            movement_trace: SyncMutex::new(EntityMovementTrace::default()),
453            lifecycle: SyncMutex::new(EntityLifecycleState::new()),
454            relationships: SyncMutex::new(EntityRelationshipState::default()),
455            portal_process: SyncMutex::new(None),
456            level_callback: SyncMutex::new(Arc::new(NullEntityCallback)),
457        }
458    }
459
460    /// Creates a base from persistent vanilla entity fields.
461    #[must_use]
462    pub fn from_load(load: EntityBaseLoad, dimensions: EntityDimensions) -> Self {
463        let base = Self::with_uuid_and_state(
464            load.id,
465            load.uuid,
466            EntityBaseState::new(load.position, dimensions)
467                .with_velocity(load.velocity)
468                .with_rotation(load.rotation)
469                .with_fall_distance(load.fall_distance)
470                .with_fire_freeze_state(load.fire_freeze)
471                .with_on_ground(load.on_ground),
472            load.world,
473        );
474        base.replace_save_data(load.save_data);
475        base
476    }
477
478    /// Gets the entity's unique network ID.
479    #[inline]
480    pub const fn id(&self) -> i32 {
481        self.id
482    }
483
484    /// Gets the entity's UUID.
485    #[inline]
486    pub const fn uuid(&self) -> Uuid {
487        self.uuid
488    }
489
490    /// Gets the entity's current position.
491    #[inline]
492    pub fn position(&self) -> DVec3 {
493        self.state.lock().position
494    }
495
496    /// Gets the entity position used by vanilla movement traces.
497    #[inline]
498    pub fn old_position(&self) -> DVec3 {
499        self.state.lock().old_position
500    }
501
502    /// Returns vanilla `lastKnownSpeed`, the displacement computed at base-tick start.
503    #[inline]
504    pub fn known_speed(&self) -> DVec3 {
505        self.state.lock().last_known_speed
506    }
507
508    /// Returns vanilla `Entity.tickCount`.
509    #[inline]
510    pub fn tick_count(&self) -> i32 {
511        self.state.lock().tick_count
512    }
513
514    /// Gets the entity's current bounding box.
515    #[inline]
516    pub fn bounding_box(&self) -> WorldAabb {
517        self.state.lock().bounding_box
518    }
519
520    /// Returns the vanilla movement physics snapshot from the current base state.
521    pub(crate) fn physics_state(&self, input: EntityPhysicsStateInput) -> EntityPhysicsState {
522        let state = self.state.lock();
523        EntityPhysicsState::new(state.position, state.bounding_box, input.max_up_step)
524            .with_on_ground(state.movement_flags.on_ground())
525            .with_backs_off_from_edge(input.backs_off_from_edge)
526            .with_fall_distance(state.fall_distance)
527            .with_descending(input.descending)
528            .with_can_walk_on_powder_snow(input.can_walk_on_powder_snow)
529            .with_falling_block(input.is_falling_block)
530    }
531
532    /// Gets the entity's current pose.
533    #[inline]
534    pub fn pose(&self) -> EntityPose {
535        self.state.lock().pose
536    }
537
538    /// Gets the entity's current dimensions.
539    #[inline]
540    pub fn dimensions(&self) -> EntityDimensions {
541        self.state.lock().dimensions
542    }
543
544    /// Gets the entity's current velocity in blocks per tick.
545    #[inline]
546    pub fn velocity(&self) -> DVec3 {
547        self.state.lock().velocity
548    }
549
550    /// Gets the entity's rotation as (yaw, pitch) in degrees.
551    #[inline]
552    pub fn rotation(&self) -> (f32, f32) {
553        self.state.lock().rotation
554    }
555
556    /// Gets vanilla `yRotO`/`xRotO` as (yaw, pitch) in degrees.
557    #[inline]
558    pub fn old_rotation(&self) -> (f32, f32) {
559        self.state.lock().old_rotation
560    }
561
562    /// Returns true if the entity is touching the ground.
563    #[inline]
564    pub fn on_ground(&self) -> bool {
565        self.state.lock().movement_flags.on_ground()
566    }
567
568    /// Returns the current vanilla movement flag snapshot.
569    #[inline]
570    pub fn movement_flags(&self) -> EntityMovementFlags {
571        self.state.lock().movement_flags
572    }
573
574    /// Returns the current vanilla ground-contact snapshot.
575    #[inline]
576    pub fn ground_contact(&self) -> EntityGroundContact {
577        self.state.lock().ground_contact
578    }
579
580    /// Returns vanilla movement side-effect progress counters.
581    #[inline]
582    pub fn movement_progress(&self) -> EntityMovementProgress {
583        self.state.lock().movement_progress
584    }
585
586    /// Returns the current vanilla fire/freeze state.
587    #[inline]
588    pub fn fire_freeze_state(&self) -> EntityFireFreezeState {
589        self.state.lock().fire_freeze
590    }
591
592    /// Returns a snapshot of shared vanilla save data.
593    pub fn save_data(&self) -> EntityBaseSaveData {
594        self.save_data.lock().clone()
595    }
596
597    /// Replaces shared vanilla save data.
598    pub fn replace_save_data(&self, save_data: EntityBaseSaveData) {
599        *self.save_data.lock() = save_data;
600    }
601
602    /// Returns vanilla `Entity.getInBlockState`, cached until base tick or block-position change.
603    pub fn in_block_state(&self, world: &World) -> BlockStateId {
604        let mut state = self.state.lock();
605        if let Some(in_block_state) = state.in_block_state {
606            return in_block_state;
607        }
608
609        let position = state.position;
610        let block_pos = BlockPos::containing(position.x, position.y, position.z);
611        let in_block_state = world.get_block_state(block_pos);
612        state.in_block_state = Some(in_block_state);
613        in_block_state
614    }
615
616    /// Replaces the current vanilla fire/freeze state.
617    pub fn set_fire_freeze_state(&self, fire_freeze: EntityFireFreezeState) {
618        self.state.lock().fire_freeze = fire_freeze;
619    }
620
621    /// Returns true if the last movement was clipped horizontally.
622    #[inline]
623    pub fn horizontal_collision(&self) -> bool {
624        self.state.lock().movement_flags.horizontal_collision()
625    }
626
627    /// Returns true if the last movement was clipped vertically.
628    #[inline]
629    pub fn vertical_collision(&self) -> bool {
630        self.state.lock().movement_flags.vertical_collision()
631    }
632
633    /// Returns true if the last vertical collision was below the entity.
634    #[inline]
635    pub fn vertical_collision_below(&self) -> bool {
636        self.state.lock().movement_flags.vertical_collision_below()
637    }
638
639    /// Returns the block currently supporting this entity, if known.
640    pub fn supporting_block(&self) -> Option<BlockPos> {
641        self.state.lock().ground_contact.supporting_block()
642    }
643
644    /// Returns true when the entity is grounded but no supporting block was found.
645    pub fn on_ground_no_blocks(&self) -> bool {
646        self.state.lock().ground_contact.on_ground_no_blocks()
647    }
648
649    /// Returns cached fluid contact from the last entity fluid refresh.
650    pub fn fluid_contact(&self) -> EntityFluidContact {
651        self.state.lock().fluid_contact
652    }
653
654    /// Returns vanilla `wasEyeInWater` from the previous fluid refresh.
655    pub fn was_eye_in_water(&self) -> bool {
656        self.state.lock().was_eye_in_water
657    }
658
659    /// Returns accumulated vanilla fall distance.
660    #[inline]
661    pub fn fall_distance(&self) -> f64 {
662        self.state.lock().fall_distance
663    }
664
665    /// Returns true when movement bypasses collision physics.
666    #[inline]
667    pub fn no_physics(&self) -> bool {
668        self.state.lock().no_physics
669    }
670
671    /// Returns the synchronized vanilla `Air` value.
672    #[inline]
673    pub fn air_supply(&self) -> i32 {
674        self.save_data.lock().air_supply
675    }
676
677    /// Returns the vanilla portal cooldown in ticks.
678    #[inline]
679    pub fn portal_cooldown(&self) -> i32 {
680        self.save_data.lock().portal_cooldown
681    }
682
683    /// Returns whether the entity is on vanilla portal cooldown.
684    #[inline]
685    pub fn is_on_portal_cooldown(&self) -> bool {
686        self.portal_cooldown() > 0
687    }
688
689    /// Returns the active vanilla portal process, if the entity is charging a portal.
690    #[inline]
691    pub fn portal_process(&self) -> Option<PortalProcessor> {
692        *self.portal_process.lock()
693    }
694
695    /// Returns the shared vanilla `NoGravity` flag.
696    #[inline]
697    pub fn no_gravity(&self) -> bool {
698        self.save_data.lock().no_gravity
699    }
700
701    /// Returns the shared vanilla `Invulnerable` flag.
702    #[inline]
703    pub fn invulnerable(&self) -> bool {
704        self.save_data.lock().invulnerable
705    }
706
707    /// Returns the optional vanilla custom name.
708    #[inline]
709    pub fn custom_name(&self) -> Option<TextComponent> {
710        self.save_data.lock().custom_name.clone()
711    }
712
713    /// Returns the vanilla custom-name visibility flag.
714    #[inline]
715    pub fn custom_name_visible(&self) -> bool {
716        self.save_data.lock().custom_name_visible
717    }
718
719    /// Returns the synchronized vanilla silent flag.
720    #[inline]
721    pub fn silent(&self) -> bool {
722        self.save_data.lock().silent
723    }
724
725    /// Returns the server-owned vanilla glowing tag flag.
726    #[inline]
727    pub fn glowing(&self) -> bool {
728        self.save_data.lock().glowing
729    }
730
731    /// Returns a sorted snapshot of vanilla scoreboard tags.
732    pub fn tags(&self) -> Vec<String> {
733        self.save_data.lock().tags.iter().cloned().collect()
734    }
735
736    /// Returns a snapshot of vanilla custom data.
737    pub fn custom_data(&self) -> NbtCompound {
738        self.save_data.lock().custom_data.clone()
739    }
740
741    /// Returns true when vanilla `ServerEntity` should consider a velocity sync.
742    #[inline]
743    pub fn needs_velocity_sync(&self) -> bool {
744        self.state.lock().needs_velocity_sync
745    }
746
747    /// Returns true when vanilla hurt-marked velocity sync is pending.
748    #[inline]
749    pub fn hurt_marked(&self) -> bool {
750        self.state.lock().hurt_marked
751    }
752
753    /// Gets the world this entity is in.
754    ///
755    /// Returns `None` if the world has been dropped.
756    #[inline]
757    pub fn level(&self) -> Option<Arc<World>> {
758        self.world.lock().upgrade()
759    }
760
761    /// Gets the vehicle this entity is riding, if it is still loaded.
762    pub fn vehicle(&self) -> Option<SharedEntity> {
763        self.relationships.lock().vehicle()
764    }
765
766    /// Gets this entity's direct passengers, pruning stale weak references.
767    pub fn passengers(&self) -> Vec<SharedEntity> {
768        self.relationships.lock().passengers()
769    }
770
771    /// Gets this entity's first direct passenger, if present.
772    pub fn first_passenger(&self) -> Option<SharedEntity> {
773        self.relationships.lock().first_passenger()
774    }
775
776    /// Returns true when this entity has at least one direct passenger.
777    pub fn is_vehicle(&self) -> bool {
778        self.first_passenger().is_some()
779    }
780
781    /// Returns true when the entity ID is a direct passenger.
782    pub fn has_passenger_id(&self, passenger_id: i32) -> bool {
783        self.relationships.lock().has_passenger_id(passenger_id)
784    }
785
786    /// Returns the vanilla boarding cooldown in ticks.
787    pub fn boarding_cooldown(&self) -> i32 {
788        self.relationships.lock().boarding_cooldown
789    }
790
791    /// Removes a direct passenger by entity ID.
792    pub(crate) fn remove_passenger_id(&self, passenger_id: i32) -> bool {
793        self.relationships.lock().remove_passenger_id(passenger_id)
794    }
795
796    /// Stops riding the current vehicle, if any.
797    pub fn stop_riding(&self) {
798        self.stop_riding_relationship();
799    }
800
801    /// Restores a persisted passenger relationship without applying gameplay boarding rules.
802    pub(crate) fn restore_passenger_relationship(vehicle: &SharedEntity, passenger: &SharedEntity) {
803        passenger.base().stop_riding_relationship();
804        Self::add_passenger_relationship(vehicle, passenger);
805    }
806
807    /// Starts a gameplay passenger relationship after vanilla boarding rules pass.
808    pub(crate) fn start_riding_relationship(vehicle: &SharedEntity, passenger: &SharedEntity) {
809        passenger.base().stop_riding_relationship();
810        Self::add_passenger_relationship(vehicle, passenger);
811    }
812
813    fn add_passenger_relationship(vehicle: &SharedEntity, passenger: &SharedEntity) {
814        if vehicle.base().has_passenger_id(passenger.id()) {
815            return;
816        }
817
818        passenger.base().relationships.lock().vehicle = Some(Arc::downgrade(vehicle));
819        let passenger_ref = Arc::downgrade(passenger);
820        let mut vehicle_relationships = vehicle.base().relationships.lock();
821        let first_passenger_is_player = vehicle_relationships
822            .first_passenger()
823            .is_some_and(|first| first.entity_type() == &vanilla_entities::PLAYER);
824        if passenger.entity_type() == &vanilla_entities::PLAYER && !first_passenger_is_player {
825            vehicle_relationships.passengers.insert(0, passenger_ref);
826        } else {
827            vehicle_relationships.passengers.push(passenger_ref);
828        }
829    }
830
831    /// Sets the vanilla boarding cooldown in ticks.
832    pub(crate) fn set_boarding_cooldown(&self, boarding_cooldown: i32) {
833        self.relationships.lock().boarding_cooldown = boarding_cooldown;
834    }
835
836    /// Advances the base-tick movement and relationship state Steel currently implements.
837    pub fn advance_base_tick_state(&self) {
838        self.clear_in_block_state_for_base_tick();
839        self.set_old_rotation_to_current();
840        self.compute_known_speed();
841        self.decrement_boarding_cooldown();
842    }
843
844    /// Clears vanilla `inBlockState` at the start of base tick.
845    fn clear_in_block_state_for_base_tick(&self) {
846        self.state.lock().in_block_state = None;
847    }
848
849    /// Computes vanilla `lastKnownSpeed` from the previous base-tick position.
850    pub fn compute_known_speed(&self) {
851        let mut state = self.state.lock();
852        let previous_position = match state.last_known_position {
853            Some(position) => position,
854            None => state.position,
855        };
856        state.last_known_speed = state.position - previous_position;
857        state.last_known_position = Some(state.position);
858    }
859
860    fn decrement_boarding_cooldown(&self) {
861        let mut relationships = self.relationships.lock();
862        if relationships.boarding_cooldown > 0 {
863            relationships.boarding_cooldown -= 1;
864        }
865    }
866
867    /// Advances vanilla portal cooldown by one server tick.
868    pub fn process_portal_cooldown(&self) {
869        let mut save_data = self.save_data.lock();
870        if save_data.portal_cooldown > 0 {
871            save_data.portal_cooldown -= 1;
872        }
873    }
874
875    /// Resets state that vanilla gets from constructing a fresh player entity for death respawn.
876    pub fn reset_for_player_respawn(&self, dimensions: EntityDimensions) {
877        self.reset_for_player_respawn_inner(dimensions, None);
878    }
879
880    /// Resets death-respawn state while retaining the relocation that owns admission.
881    pub(crate) fn reset_for_player_respawn_during_world_change(
882        &self,
883        dimensions: EntityDimensions,
884        pending_token: PendingWorldChangeToken,
885    ) {
886        self.reset_for_player_respawn_inner(dimensions, Some(pending_token));
887    }
888
889    fn reset_for_player_respawn_inner(
890        &self,
891        dimensions: EntityDimensions,
892        pending_world_change: Option<PendingWorldChangeToken>,
893    ) {
894        let bounding_box = {
895            let mut state = self.state.lock();
896            let position = state.position;
897            state.old_position = position;
898            state.last_known_position = None;
899            state.last_known_speed = DVec3::ZERO;
900            state.velocity = DVec3::ZERO;
901            state.old_rotation = state.rotation;
902            state.pose = EntityPose::Standing;
903            state.dimensions = dimensions;
904            state.bounding_box = EntityBaseState::make_bounding_box(position, dimensions);
905            state.movement_flags = EntityMovementFlags::new();
906            state.ground_contact = EntityGroundContact::airborne();
907            state.movement_progress = EntityMovementProgress::new();
908            state.fire_freeze = EntityFireFreezeState::new();
909            state.in_block_state = None;
910            state.fluid_contact = EntityFluidContact::default();
911            state.was_eye_in_water = false;
912            state.piston_movement = EntityPistonMovement::new();
913            state.fall_distance = 0.0;
914            state.stuck_speed_multiplier = DVec3::ZERO;
915            state.no_physics = false;
916            state.needs_velocity_sync = false;
917            state.hurt_marked = false;
918            state.bounding_box
919        };
920        self.notify_bounding_box_changed(bounding_box);
921
922        self.movement_trace.lock().reset();
923        *self.portal_process.lock() = None;
924        self.lifecycle.lock().pending_world_change = pending_world_change;
925
926        let mut save_data = self.save_data.lock();
927        let tags = mem::take(&mut save_data.tags);
928        *save_data = EntityBaseSaveData::new();
929        save_data.tags = tags;
930    }
931
932    /// Updates the world reference used by this entity.
933    pub(crate) fn set_world(&self, world: Weak<World>) {
934        *self.world.lock() = world;
935    }
936
937    /// Marks this entity as waiting for a prepared world change.
938    pub fn begin_pending_world_change(&self) -> Option<PendingWorldChangeToken> {
939        let mut lifecycle = self.lifecycle.lock();
940        if lifecycle.removal_reason.is_some() || lifecycle.pending_world_change.is_some() {
941            return None;
942        }
943        let token = lifecycle.next_world_change_token();
944        lifecycle.pending_world_change = Some(token);
945        Some(token)
946    }
947
948    /// Marks a live or killed player as waiting for respawn preparation.
949    ///
950    /// Killed players remain eligible because their async spawn search may need
951    /// to be retried after the death animation removes their live entity.
952    pub(crate) fn begin_pending_player_respawn(&self) -> Option<PendingWorldChangeToken> {
953        let mut lifecycle = self.lifecycle.lock();
954        if !matches!(lifecycle.removal_reason, None | Some(RemovalReason::Killed))
955            || lifecycle.pending_world_change.is_some()
956        {
957            return None;
958        }
959        let token = lifecycle.next_world_change_token();
960        lifecycle.pending_world_change = Some(token);
961        Some(token)
962    }
963
964    /// Clears a pending world change if it still matches the provided token.
965    pub fn finish_pending_world_change(&self, token: PendingWorldChangeToken) -> bool {
966        let mut lifecycle = self.lifecycle.lock();
967        if lifecycle.pending_world_change != Some(token) {
968            return false;
969        }
970        lifecycle.pending_world_change = None;
971        true
972    }
973
974    /// Returns true while this entity is waiting for a prepared world change.
975    #[inline]
976    pub fn is_world_change_pending(&self) -> bool {
977        self.lifecycle.lock().pending_world_change.is_some()
978    }
979
980    /// Returns true if the given world-change token is still pending.
981    #[inline]
982    pub fn is_world_change_token_pending(&self, token: PendingWorldChangeToken) -> bool {
983        self.lifecycle.lock().pending_world_change == Some(token)
984    }
985
986    /// Returns true if the entity has been marked for removal.
987    #[inline]
988    pub fn is_removed(&self) -> bool {
989        self.lifecycle.lock().removal_reason.is_some()
990    }
991
992    /// Returns the reason this entity was removed, if it has been removed.
993    #[inline]
994    pub fn removal_reason(&self) -> Option<RemovalReason> {
995        self.lifecycle.lock().removal_reason
996    }
997
998    /// Marks the entity as removed with the given reason.
999    ///
1000    /// Notifies the level callback on first removal.
1001    pub fn set_removed(&self, reason: RemovalReason) {
1002        let callback = {
1003            let mut lifecycle = self.lifecycle.lock();
1004            if lifecycle.removal_reason.is_some() {
1005                None
1006            } else {
1007                lifecycle.removal_reason = Some(reason);
1008                lifecycle.pending_world_change = None;
1009                Some(self.level_callback.lock().clone())
1010            }
1011        };
1012
1013        if let Some(callback) = callback {
1014            self.detach_from_relationships(reason);
1015            callback.on_remove(reason);
1016            *self.level_callback.lock() = Arc::new(NullEntityCallback);
1017        }
1018    }
1019
1020    fn detach_from_relationships(&self, reason: RemovalReason) {
1021        if reason.should_destroy() {
1022            self.stop_riding_relationship();
1023        }
1024        self.eject_passenger_relationships();
1025    }
1026
1027    fn stop_riding_relationship(&self) {
1028        let vehicle = {
1029            let mut relationships = self.relationships.lock();
1030            let vehicle = relationships.vehicle();
1031            relationships.vehicle = None;
1032            vehicle
1033        };
1034
1035        if let Some(vehicle) = vehicle {
1036            vehicle.base().remove_passenger_id(self.id);
1037            self.set_boarding_cooldown(60);
1038        }
1039    }
1040
1041    fn eject_passenger_relationships(&self) {
1042        let passengers = {
1043            let mut relationships = self.relationships.lock();
1044            let passengers = relationships.passengers();
1045            relationships.passengers.clear();
1046            passengers
1047        };
1048
1049        for passenger in passengers {
1050            if passenger.base().clear_vehicle_if(self.id) {
1051                passenger.base().set_boarding_cooldown(60);
1052            }
1053        }
1054    }
1055
1056    fn clear_vehicle_if(&self, vehicle_id: i32) -> bool {
1057        {
1058            let mut relationships = self.relationships.lock();
1059            let Some(vehicle) = relationships.vehicle() else {
1060                return false;
1061            };
1062            if vehicle.id() != vehicle_id {
1063                return false;
1064            }
1065        }
1066
1067        if let Err(error) = self.try_set_position(self.position()) {
1068            log::warn!(
1069                "Failed to refresh passenger {} manager position before clearing vehicle {vehicle_id}: {error}",
1070                self.id
1071            );
1072        }
1073
1074        let mut relationships = self.relationships.lock();
1075        let Some(vehicle) = relationships.vehicle() else {
1076            return false;
1077        };
1078        if vehicle.id() != vehicle_id {
1079            return false;
1080        }
1081        relationships.vehicle = None;
1082        true
1083    }
1084
1085    /// Clears the removed flag and returns whether the entity had been removed.
1086    ///
1087    /// Steel reuses the same `Player` instance across respawn while vanilla
1088    /// constructs a fresh `ServerPlayer`, so player respawn needs an explicit
1089    /// way to reset this base lifecycle flag.
1090    pub fn clear_removed(&self) -> bool {
1091        let mut lifecycle = self.lifecycle.lock();
1092        let was_removed = lifecycle.removal_reason.is_some();
1093        lifecycle.removal_reason = None;
1094        lifecycle.pending_world_change = None;
1095        was_removed
1096    }
1097
1098    /// Sets the level callback for lifecycle events.
1099    pub fn set_level_callback(&self, callback: Arc<dyn EntityLevelCallback>) {
1100        *self.level_callback.lock() = callback;
1101    }
1102
1103    /// Sets the entity's position through the active level callback.
1104    #[must_use = "movement commits can fail when world entity state rejects the update"]
1105    pub fn try_set_position(&self, pos: DVec3) -> Result<(), EntityMoveError> {
1106        require_finite_position(pos, "position");
1107        let old_pos = self.state.lock().position;
1108        let callback = self.level_callback.lock().clone();
1109        callback.validate_move(old_pos, pos)?;
1110        self.set_position_local_unchecked(pos);
1111        if let Err(error) = callback.on_move_committed(old_pos, pos) {
1112            self.set_position_local_unchecked(old_pos);
1113            return Err(error);
1114        }
1115        Ok(())
1116    }
1117
1118    /// Sets position without consulting world lifecycle callbacks.
1119    ///
1120    /// Use this for construction, loading, proto-staged entities, and tests.
1121    pub(crate) fn set_position_local(&self, pos: DVec3) {
1122        let callback = self.level_callback.lock().clone();
1123        assert!(
1124            callback.allows_local_position_update(),
1125            "entity {} local position update bypassed world entity manager",
1126            self.id
1127        );
1128        self.set_position_local_unchecked(pos);
1129    }
1130
1131    fn set_position_local_unchecked(&self, pos: DVec3) {
1132        require_finite_position(pos, "position");
1133        {
1134            let mut state = self.state.lock();
1135            let old = state.position;
1136            state.position = pos;
1137            state.bounding_box = EntityBaseState::make_bounding_box(pos, state.dimensions);
1138            if BlockPos::containing(old.x, old.y, old.z)
1139                != BlockPos::containing(pos.x, pos.y, pos.z)
1140            {
1141                state.in_block_state = None;
1142            }
1143        }
1144    }
1145
1146    /// Sets the vanilla movement-trace old position to the current position.
1147    pub fn set_old_position_to_current(&self) {
1148        let mut state = self.state.lock();
1149        state.old_position = state.position;
1150    }
1151
1152    /// Sets the vanilla movement-trace old position explicitly.
1153    pub fn set_old_position(&self, old_position: DVec3) {
1154        require_finite_position(old_position, "old position");
1155        self.state.lock().old_position = old_position;
1156    }
1157
1158    /// Sets vanilla `yRotO`/`xRotO` to the current rotation.
1159    pub fn set_old_rotation_to_current(&self) {
1160        let mut state = self.state.lock();
1161        state.old_rotation = state.rotation;
1162    }
1163
1164    /// Sets vanilla `yRotO` to the current yaw without changing `xRotO`.
1165    pub fn set_old_yaw_to_current(&self) {
1166        let mut state = self.state.lock();
1167        state.old_rotation.0 = state.rotation.0;
1168    }
1169
1170    /// Sets vanilla `yRotO`/`xRotO` explicitly.
1171    pub fn set_old_rotation(&self, old_rotation: (f32, f32)) {
1172        self.state.lock().old_rotation = normalize_rotation(old_rotation);
1173    }
1174
1175    /// Records a movement segment for vanilla block-contact effects.
1176    pub fn record_movement_this_tick(&self, movement: EntityMovement) {
1177        self.movement_trace.lock().record(movement);
1178    }
1179
1180    /// Removes the latest movement segment recorded this tick.
1181    pub fn remove_latest_movement_recording(&self) {
1182        self.movement_trace.lock().remove_latest_recording();
1183    }
1184
1185    /// Clears movement segments recorded for the current tick.
1186    pub fn clear_movement_this_tick(&self) {
1187        self.movement_trace.lock().reset();
1188    }
1189
1190    /// Takes and finalizes this tick's movement segments for block-contact effects.
1191    pub fn take_movements_for_block_effects(&self) -> Vec<EntityMovement> {
1192        let (old_position, position) = {
1193            let state = self.state.lock();
1194            (state.old_position, state.position)
1195        };
1196
1197        self.movement_trace
1198            .lock()
1199            .take_for_block_effects(old_position, position)
1200    }
1201
1202    /// Returns the last finalized movement segments for vanilla block-contact effects.
1203    pub fn last_movements_for_block_effects(&self) -> Vec<EntityMovement> {
1204        self.movement_trace.lock().last_for_block_effects()
1205    }
1206
1207    /// Sets the entity's bounding box directly.
1208    ///
1209    /// Use this for vanilla entities whose box is not simply dimensions centered
1210    /// on the entity position.
1211    pub fn set_bounding_box(&self, bounding_box: WorldAabb) {
1212        self.state.lock().bounding_box = bounding_box;
1213        self.notify_bounding_box_changed(bounding_box);
1214    }
1215
1216    /// Sets pose and dimensions, then rebuilds the default position-centered box.
1217    pub fn set_pose_and_dimensions(&self, pose: EntityPose, dimensions: EntityDimensions) {
1218        let bounding_box = {
1219            let mut state = self.state.lock();
1220            state.pose = pose;
1221            state.dimensions = dimensions;
1222            state.bounding_box = EntityBaseState::make_bounding_box(state.position, dimensions);
1223            state.bounding_box
1224        };
1225        self.notify_bounding_box_changed(bounding_box);
1226    }
1227
1228    fn notify_bounding_box_changed(&self, bounding_box: WorldAabb) {
1229        let callback = Arc::clone(&self.level_callback.lock());
1230        callback.on_bounding_box_changed(bounding_box);
1231    }
1232
1233    /// Sets the entity's velocity in blocks per tick.
1234    pub fn set_velocity(&self, velocity: DVec3) {
1235        if velocity.is_finite() {
1236            self.state.lock().velocity = velocity;
1237        }
1238    }
1239
1240    /// Advances vanilla `Entity.tickCount` by one tick.
1241    #[inline]
1242    pub fn advance_tick_count(&self) {
1243        let mut state = self.state.lock();
1244        state.tick_count = state.tick_count.wrapping_add(1);
1245    }
1246
1247    /// Records movement distance used by vanilla step, swim, and flap effects.
1248    pub fn record_movement_progress(
1249        &self,
1250        clipped_movement: DVec3,
1251        climbing: bool,
1252    ) -> EntityMovementProgress {
1253        let mut state = self.state.lock();
1254        state
1255            .movement_progress
1256            .add_movement(clipped_movement, climbing);
1257        state.movement_progress
1258    }
1259
1260    /// Stores vanilla `nextStep` after a produced movement side effect.
1261    pub fn set_next_step(&self, next_step: f32) {
1262        self.state.lock().movement_progress.next_step = next_step;
1263    }
1264
1265    /// Returns vanilla amethyst-step chime parameters when the cooldown allows it.
1266    pub fn amethyst_step_sound(&self, tick_count: i32) -> Option<EntityAmethystStepSound> {
1267        let intensity = {
1268            let mut state = self.state.lock();
1269            let progress = &mut state.movement_progress;
1270            if tick_count < progress.last_crystal_sound_play_tick + 20 {
1271                return None;
1272            }
1273
1274            let tick_delta = tick_count - progress.last_crystal_sound_play_tick;
1275            progress.crystal_sound_intensity *= 0.997_f32.powi(tick_delta);
1276            progress.crystal_sound_intensity = (progress.crystal_sound_intensity + 0.07).min(1.0);
1277            progress.last_crystal_sound_play_tick = tick_count;
1278            progress.crystal_sound_intensity
1279        };
1280
1281        let pitch = 0.5 + intensity * rand::random::<f32>() * 1.2;
1282        let volume = 0.1 + intensity * 1.2;
1283        Some(EntityAmethystStepSound { volume, pitch })
1284    }
1285
1286    /// Sets the entity's rotation as (yaw, pitch) in degrees.
1287    pub fn set_rotation(&self, rotation: (f32, f32)) {
1288        self.state.lock().rotation = normalize_rotation(rotation);
1289    }
1290
1291    /// Sets whether this entity bypasses collision physics.
1292    pub fn set_no_physics(&self, no_physics: bool) {
1293        self.state.lock().no_physics = no_physics;
1294    }
1295
1296    /// Sets the synchronized vanilla `Air` value.
1297    pub fn set_air_supply(&self, air_supply: i32) {
1298        self.save_data.lock().air_supply = air_supply;
1299    }
1300
1301    /// Sets the vanilla portal cooldown in ticks.
1302    pub fn set_portal_cooldown(&self, portal_cooldown: i32) {
1303        self.save_data.lock().portal_cooldown = portal_cooldown;
1304    }
1305
1306    /// Marks this entity as inside a vanilla portal during the current tick.
1307    pub fn set_as_inside_portal(&self, portal: PortalKind, entry_position: BlockPos) {
1308        let mut portal_process = self.portal_process.lock();
1309        match portal_process.as_mut() {
1310            Some(process) if process.is_same_portal(portal) => {
1311                process.set_as_inside_portal(entry_position);
1312            }
1313            _ => {
1314                *portal_process = Some(PortalProcessor::new(portal, entry_position));
1315            }
1316        }
1317    }
1318
1319    /// Advances the active vanilla portal process, if one exists.
1320    pub fn process_portal_teleportation(
1321        &self,
1322        allowed_to_teleport: bool,
1323        transition_time: i32,
1324    ) -> Option<PortalProcessResult> {
1325        self.portal_process.lock().as_mut().map(|process| {
1326            process.process_portal_teleportation(allowed_to_teleport, transition_time)
1327        })
1328    }
1329
1330    /// Clears the active vanilla portal process.
1331    pub fn clear_portal_process(&self) {
1332        *self.portal_process.lock() = None;
1333    }
1334
1335    /// Sets the shared vanilla `NoGravity` flag.
1336    pub fn set_no_gravity(&self, no_gravity: bool) {
1337        self.save_data.lock().no_gravity = no_gravity;
1338    }
1339
1340    /// Sets the shared vanilla `Invulnerable` flag.
1341    pub fn set_invulnerable(&self, invulnerable: bool) {
1342        self.save_data.lock().invulnerable = invulnerable;
1343    }
1344
1345    /// Sets the optional vanilla custom name.
1346    pub fn set_custom_name(&self, custom_name: Option<TextComponent>) {
1347        self.save_data.lock().custom_name = custom_name;
1348    }
1349
1350    /// Sets the vanilla custom-name visibility flag.
1351    pub fn set_custom_name_visible(&self, visible: bool) {
1352        self.save_data.lock().custom_name_visible = visible;
1353    }
1354
1355    /// Sets the synchronized vanilla silent flag.
1356    pub fn set_silent(&self, silent: bool) {
1357        self.save_data.lock().silent = silent;
1358    }
1359
1360    /// Sets the server-owned vanilla glowing tag flag.
1361    pub fn set_glowing(&self, glowing: bool) {
1362        self.save_data.lock().glowing = glowing;
1363    }
1364
1365    /// Adds a vanilla scoreboard tag.
1366    pub fn add_tag(&self, tag: String) -> bool {
1367        self.save_data.lock().add_tag(tag)
1368    }
1369
1370    /// Removes a vanilla scoreboard tag.
1371    pub fn remove_tag(&self, tag: &str) -> bool {
1372        self.save_data.lock().tags.remove(tag)
1373    }
1374
1375    /// Replaces vanilla custom data.
1376    pub fn set_custom_data(&self, custom_data: NbtCompound) {
1377        self.save_data.lock().custom_data = custom_data;
1378    }
1379
1380    /// Marks velocity for vanilla `ServerEntity` synchronization.
1381    pub fn mark_velocity_sync(&self) {
1382        self.state.lock().needs_velocity_sync = true;
1383    }
1384
1385    /// Clears the vanilla velocity sync marker after send processing.
1386    pub fn clear_velocity_sync(&self) {
1387        self.state.lock().needs_velocity_sync = false;
1388    }
1389
1390    /// Marks this entity as hurt for vanilla self-inclusive motion sync.
1391    pub fn mark_hurt(&self) {
1392        self.state.lock().hurt_marked = true;
1393    }
1394
1395    /// Clears the vanilla hurt-marked motion sync flag.
1396    pub fn clear_hurt_mark(&self) {
1397        self.state.lock().hurt_marked = false;
1398    }
1399
1400    /// Sets accumulated vanilla fall distance.
1401    pub fn set_fall_distance(&self, fall_distance: f64) {
1402        self.state.lock().fall_distance = fall_distance;
1403    }
1404
1405    /// Adds vertical movement to accumulated fall distance using vanilla precision.
1406    pub fn accumulate_fall_distance(&self, vertical_movement: f64) {
1407        self.state.lock().fall_distance -= f64::from(vertical_movement as f32);
1408    }
1409
1410    /// Resets accumulated vanilla fall distance.
1411    pub fn reset_fall_distance(&self) {
1412        self.set_fall_distance(0.0);
1413    }
1414
1415    /// Returns vanilla `remainingFireTicks`.
1416    pub fn remaining_fire_ticks(&self) -> i32 {
1417        self.state.lock().fire_freeze.remaining_fire_ticks()
1418    }
1419
1420    /// Sets vanilla `remainingFireTicks`.
1421    pub fn set_remaining_fire_ticks(&self, remaining_fire_ticks: i32) {
1422        self.state.lock().fire_freeze.remaining_fire_ticks = remaining_fire_ticks;
1423    }
1424
1425    /// Returns synchronized vanilla `TicksFrozen`.
1426    pub fn ticks_frozen(&self) -> i32 {
1427        self.state.lock().fire_freeze.ticks_frozen()
1428    }
1429
1430    /// Sets synchronized vanilla `TicksFrozen`.
1431    pub fn set_ticks_frozen(&self, ticks_frozen: i32) {
1432        self.state.lock().fire_freeze.ticks_frozen = ticks_frozen;
1433    }
1434
1435    /// Returns whether the entity touched powder snow during the current tick.
1436    pub fn is_in_powder_snow(&self) -> bool {
1437        self.state.lock().fire_freeze.is_in_powder_snow()
1438    }
1439
1440    /// Returns whether the entity touched powder snow during the previous tick.
1441    pub fn was_in_powder_snow(&self) -> bool {
1442        self.state.lock().fire_freeze.was_in_powder_snow()
1443    }
1444
1445    /// Sets vanilla `hasVisualFire`.
1446    pub fn set_visual_fire(&self, has_visual_fire: bool) {
1447        self.state.lock().fire_freeze.has_visual_fire = has_visual_fire;
1448    }
1449
1450    /// Returns vanilla `hasVisualFire`.
1451    pub fn has_visual_fire(&self) -> bool {
1452        self.state.lock().fire_freeze.has_visual_fire()
1453    }
1454
1455    /// Returns whether the entity is on fire on the server.
1456    pub fn is_on_fire(&self, fire_immune: bool) -> bool {
1457        !fire_immune && self.remaining_fire_ticks() > 0
1458    }
1459
1460    /// Returns whether the entity is freezing.
1461    pub fn is_freezing(&self) -> bool {
1462        self.state.lock().fire_freeze.is_freezing()
1463    }
1464
1465    /// Returns whether the entity has reached full-freeze duration.
1466    pub fn is_fully_frozen(&self, ticks_required_to_freeze: i32) -> bool {
1467        self.state
1468            .lock()
1469            .fire_freeze
1470            .is_fully_frozen(ticks_required_to_freeze)
1471    }
1472
1473    /// Advances vanilla powder-snow contact at the start of base tick.
1474    pub fn advance_powder_snow_contact_for_base_tick(&self) {
1475        let mut state = self.state.lock();
1476        state.fire_freeze.was_in_powder_snow = state.fire_freeze.is_in_powder_snow;
1477        state.fire_freeze.is_in_powder_snow = false;
1478    }
1479
1480    /// Advances vanilla server-side fire tick state.
1481    ///
1482    /// Returns true when the caller should apply one tick of on-fire damage.
1483    pub fn advance_fire_tick(&self, fire_immune: bool, in_lava: bool) -> bool {
1484        let mut state = self.state.lock();
1485        if state.fire_freeze.remaining_fire_ticks <= 0 {
1486            return false;
1487        }
1488
1489        if fire_immune {
1490            state.fire_freeze.remaining_fire_ticks = state.fire_freeze.remaining_fire_ticks.min(0);
1491            return false;
1492        }
1493
1494        let should_damage = state.fire_freeze.remaining_fire_ticks % 20 == 0 && !in_lava;
1495        state.fire_freeze.remaining_fire_ticks -= 1;
1496        should_damage
1497    }
1498
1499    /// Clears accumulated freezing.
1500    pub fn clear_freeze(&self) {
1501        self.set_ticks_frozen(0);
1502    }
1503
1504    /// Clears fire without resetting the vanilla fire immunity cooldown.
1505    pub fn clear_fire(&self) {
1506        let mut state = self.state.lock();
1507        state.fire_freeze.remaining_fire_ticks = state.fire_freeze.remaining_fire_ticks.min(0);
1508    }
1509
1510    /// Ignites this entity for a vanilla tick duration.
1511    pub fn ignite_for_ticks(&self, number_of_ticks: i32, remaining_fire_ticks_cap: Option<i32>) {
1512        let mut state = self.state.lock();
1513        Self::ignite_for_ticks_in_state(
1514            &mut state.fire_freeze,
1515            number_of_ticks,
1516            remaining_fire_ticks_cap,
1517        );
1518    }
1519
1520    /// Applies a vanilla inside-block effect to base fire/freeze state.
1521    pub fn apply_inside_block_effect(
1522        &self,
1523        effect_type: InsideBlockEffectType,
1524        can_freeze: bool,
1525        fire_immune: bool,
1526        fire_ignite_extra_ticks: i32,
1527        ticks_required_to_freeze: i32,
1528        remaining_fire_ticks_cap: Option<i32>,
1529    ) {
1530        let mut state = self.state.lock();
1531        match effect_type {
1532            InsideBlockEffectType::Freeze => {
1533                state.fire_freeze.is_in_powder_snow = true;
1534                if can_freeze {
1535                    state.fire_freeze.ticks_frozen =
1536                        ticks_required_to_freeze.min(state.fire_freeze.ticks_frozen + 1);
1537                }
1538            }
1539            InsideBlockEffectType::ClearFreeze => {
1540                state.fire_freeze.ticks_frozen = 0;
1541            }
1542            InsideBlockEffectType::FireIgnite => {
1543                Self::apply_fire_ignite(
1544                    &mut state.fire_freeze,
1545                    fire_immune,
1546                    fire_ignite_extra_ticks,
1547                    remaining_fire_ticks_cap,
1548                );
1549            }
1550            InsideBlockEffectType::LavaIgnite => {
1551                if !fire_immune {
1552                    Self::ignite_for_ticks_in_state(
1553                        &mut state.fire_freeze,
1554                        LAVA_IGNITE_TICKS,
1555                        remaining_fire_ticks_cap,
1556                    );
1557                }
1558            }
1559            InsideBlockEffectType::Extinguish => {
1560                state.fire_freeze.remaining_fire_ticks =
1561                    state.fire_freeze.remaining_fire_ticks.min(0);
1562            }
1563        }
1564    }
1565
1566    fn apply_fire_ignite(
1567        fire_freeze: &mut EntityFireFreezeState,
1568        fire_immune: bool,
1569        fire_ignite_extra_ticks: i32,
1570        remaining_fire_ticks_cap: Option<i32>,
1571    ) {
1572        if fire_immune {
1573            return;
1574        }
1575
1576        if fire_freeze.remaining_fire_ticks < 0 {
1577            Self::set_remaining_fire_ticks_in_state(
1578                fire_freeze,
1579                fire_freeze.remaining_fire_ticks + 1,
1580                remaining_fire_ticks_cap,
1581            );
1582        } else if fire_ignite_extra_ticks > 0 {
1583            Self::set_remaining_fire_ticks_in_state(
1584                fire_freeze,
1585                fire_freeze.remaining_fire_ticks + fire_ignite_extra_ticks,
1586                remaining_fire_ticks_cap,
1587            );
1588        }
1589
1590        if fire_freeze.remaining_fire_ticks >= 0 {
1591            Self::ignite_for_ticks_in_state(
1592                fire_freeze,
1593                FIRE_IGNITE_TICKS,
1594                remaining_fire_ticks_cap,
1595            );
1596        }
1597    }
1598
1599    fn ignite_for_ticks_in_state(
1600        fire_freeze: &mut EntityFireFreezeState,
1601        number_of_ticks: i32,
1602        remaining_fire_ticks_cap: Option<i32>,
1603    ) {
1604        if fire_freeze.remaining_fire_ticks < number_of_ticks {
1605            Self::set_remaining_fire_ticks_in_state(
1606                fire_freeze,
1607                number_of_ticks,
1608                remaining_fire_ticks_cap,
1609            );
1610        }
1611        fire_freeze.ticks_frozen = 0;
1612    }
1613
1614    fn set_remaining_fire_ticks_in_state(
1615        fire_freeze: &mut EntityFireFreezeState,
1616        remaining_fire_ticks: i32,
1617        remaining_fire_ticks_cap: Option<i32>,
1618    ) {
1619        fire_freeze.remaining_fire_ticks =
1620            Self::cap_remaining_fire_ticks(remaining_fire_ticks, remaining_fire_ticks_cap);
1621    }
1622
1623    fn cap_remaining_fire_ticks(
1624        remaining_fire_ticks: i32,
1625        remaining_fire_ticks_cap: Option<i32>,
1626    ) -> i32 {
1627        remaining_fire_ticks_cap.map_or(remaining_fire_ticks, |cap| remaining_fire_ticks.min(cap))
1628    }
1629
1630    /// Applies vanilla base-tick fall-distance damping while touching lava.
1631    pub fn dampen_fall_distance_in_lava(&self) {
1632        let mut state = self.state.lock();
1633        if state.fluid_contact.lava_height() > 0.0 {
1634            state.fall_distance *= 0.5;
1635        }
1636    }
1637
1638    /// Applies vanilla fluid-interaction fall-distance reset while touching water.
1639    pub fn reset_fall_distance_in_water(&self) {
1640        let mut state = self.state.lock();
1641        if state.fluid_contact.water_height() > 0.0 {
1642            state.fall_distance = 0.0;
1643        }
1644    }
1645
1646    /// Sets whether this entity is touching the ground.
1647    pub fn set_on_ground(&self, on_ground: bool) {
1648        let mut state = self.state.lock();
1649        state.movement_flags = state.movement_flags.with_on_ground(on_ground);
1650        if !on_ground {
1651            state.ground_contact = EntityGroundContact::airborne();
1652        }
1653    }
1654
1655    /// Sets all vanilla movement flags after `Entity.move`.
1656    pub fn set_movement_flags(
1657        &self,
1658        movement_flags: EntityMovementFlags,
1659        ground_contact: EntityGroundContact,
1660    ) {
1661        let mut state = self.state.lock();
1662        state.movement_flags = movement_flags;
1663        state.ground_contact = ground_contact;
1664    }
1665
1666    /// Stores the current vanilla supporting-block snapshot.
1667    pub fn set_ground_contact(&self, ground_contact: EntityGroundContact) {
1668        self.state.lock().ground_contact = ground_contact;
1669    }
1670
1671    /// Stores the current vanilla fluid contact snapshot.
1672    pub fn set_fluid_contact(&self, fluid_contact: EntityFluidContact) {
1673        self.state.lock().fluid_contact = fluid_contact;
1674    }
1675
1676    /// Stores fluid contact for a vanilla base-tick refresh.
1677    ///
1678    /// Vanilla updates `wasEyeInWater` from the previous fluid interaction
1679    /// before scanning the current one.
1680    pub fn set_fluid_contact_for_base_tick(&self, fluid_contact: EntityFluidContact) {
1681        let mut state = self.state.lock();
1682        state.was_eye_in_water = state.fluid_contact.eye_in_water();
1683        state.fluid_contact = fluid_contact;
1684    }
1685
1686    /// Sets ground and horizontal collision flags from an accepted client move.
1687    pub fn set_on_ground_with_movement(
1688        &self,
1689        on_ground: bool,
1690        horizontal_collision: bool,
1691        ground_contact: EntityGroundContact,
1692    ) {
1693        let mut state = self.state.lock();
1694        state.movement_flags = state
1695            .movement_flags
1696            .with_on_ground(on_ground)
1697            .with_horizontal_collision(horizontal_collision);
1698        state.ground_contact = ground_contact;
1699    }
1700
1701    /// Clears collision flags after a no-physics move.
1702    pub fn clear_collision_flags(&self) {
1703        let mut state = self.state.lock();
1704        state.movement_flags = state.movement_flags.without_collisions();
1705    }
1706
1707    /// Applies vanilla per-tick piston movement accumulation.
1708    pub fn limit_piston_movement(&self, movement: DVec3, current_game_time: i64) -> DVec3 {
1709        self.state
1710            .lock()
1711            .piston_movement
1712            .limit_movement(movement, current_game_time)
1713    }
1714
1715    /// Sets the speed multiplier used for the next stuck-in-block movement pass.
1716    pub fn make_stuck_in_block(&self, speed_multiplier: DVec3) {
1717        let mut state = self.state.lock();
1718        state.fall_distance = 0.0;
1719        state.stuck_speed_multiplier = speed_multiplier;
1720    }
1721
1722    /// Applies and clears vanilla stuck-in-block speed state.
1723    #[must_use]
1724    pub fn consume_stuck_speed_multiplier(&self, movement: DVec3, apply_multiplier: bool) -> DVec3 {
1725        let mut state = self.state.lock();
1726        if state.stuck_speed_multiplier.length_squared() <= STUCK_SPEED_MULTIPLIER_EPSILON {
1727            return movement;
1728        }
1729
1730        let stuck_speed_multiplier = state.stuck_speed_multiplier;
1731        state.stuck_speed_multiplier = DVec3::ZERO;
1732        state.velocity = DVec3::ZERO;
1733
1734        if apply_multiplier {
1735            movement * stuck_speed_multiplier
1736        } else {
1737            movement
1738        }
1739    }
1740}
1741
1742#[cfg(test)]
1743mod tests;