Skip to main content

steel_core/entity/
living_entity.rs

1use super::*;
2
3/// A trait for living entities that can take damage, heal, and die.
4///
5/// This trait provides the core functionality for entities that have health,
6/// can be damaged, and can die. It's based on Minecraft's `LivingEntity` class.
7///
8/// **Note:** All methods take `&self` (not `&mut self`) because living entities
9/// are shared via `Arc` and use interior mutability (`SyncMutex`, etc.).
10pub trait LivingEntity: Entity {
11    /// Returns a reference to the shared [`LivingEntityBase`] that holds
12    /// living runtime state such as attributes, cached movement speed,
13    /// damage cooldown, and death animation counters.
14    fn living_base(&self) -> &LivingEntityBase;
15
16    /// Returns vanilla living body/head rotation state.
17    fn living_rotation_state(&self) -> LivingRotationState {
18        self.living_base().rotation_state()
19    }
20
21    /// Returns vanilla `LivingEntity.yBodyRot`.
22    fn y_body_rot(&self) -> f32 {
23        self.living_base().y_body_rot()
24    }
25
26    /// Sets vanilla `LivingEntity.yBodyRot`.
27    fn set_y_body_rot(&self, y_body_rot: f32) {
28        self.living_base().set_y_body_rot(y_body_rot);
29    }
30
31    /// Returns vanilla `LivingEntity.yHeadRot`.
32    fn y_head_rot(&self) -> f32 {
33        self.living_base().y_head_rot()
34    }
35
36    /// Sets vanilla `LivingEntity.yHeadRot`.
37    fn set_y_head_rot(&self, y_head_rot: f32) {
38        self.living_base().set_y_head_rot(y_head_rot);
39    }
40
41    /// Copies current living body/head rotations to vanilla old-rotation state.
42    fn advance_living_rotation_for_base_tick(&self) {
43        self.living_base().advance_rotation_for_base_tick();
44    }
45
46    /// Copies current attack animation to vanilla old attack-animation state.
47    fn advance_attack_animation_for_base_tick(&self) {
48        self.living_base().advance_attack_animation_for_base_tick();
49    }
50
51    /// Runs vanilla `LivingEntity.baseTick`.
52    fn base_tick_living_entity(&self) {
53        self.advance_living_rotation_for_base_tick();
54        self.advance_attack_animation_for_base_tick();
55        self.entity_base_tick();
56        self.tick_living_environmental_damage();
57    }
58
59    /// Returns vanilla arm-swing animation state.
60    fn living_swing_state(&self) -> LivingSwingState {
61        self.living_base().swing_state()
62    }
63
64    /// Returns vanilla `LivingEntity.getCurrentSwingDuration`.
65    fn current_swing_duration(&self) -> i32 {
66        let hand = self
67            .living_swing_state()
68            .swinging_arm()
69            .unwrap_or(InteractionHand::MainHand);
70        let slot = match hand {
71            InteractionHand::MainHand => EquipmentSlot::MainHand,
72            InteractionHand::OffHand => EquipmentSlot::OffHand,
73        };
74        let mut swing_duration = SwingAnimation::DEFAULT.duration;
75        self.with_equipment_slot(slot, &mut |item_stack| {
76            swing_duration = item_stack
77                .get(SWING_ANIMATION)
78                .copied()
79                .unwrap_or(SwingAnimation::DEFAULT)
80                .duration;
81        });
82        if let Some(haste) = self.mob_effect(vanilla_mob_effects::HASTE) {
83            swing_duration - (1 + haste.amplifier())
84        } else if let Some(mining_fatigue) = self.mob_effect(vanilla_mob_effects::MINING_FATIGUE) {
85            swing_duration + (1 + mining_fatigue.amplifier()) * 2
86        } else {
87            swing_duration
88        }
89    }
90
91    /// Runs vanilla `LivingEntity.swing`.
92    fn swing(&self, hand: InteractionHand, update_self: bool) {
93        if !self
94            .living_base()
95            .start_swing(hand, self.current_swing_duration())
96        {
97            return;
98        }
99
100        let Some(world) = self.level() else {
101            return;
102        };
103        let action = match hand {
104            InteractionHand::MainHand => AnimateAction::SwingMainHand,
105            InteractionHand::OffHand => AnimateAction::SwingOffHand,
106        };
107        let packet = CAnimate::new(self.id(), action);
108        let exclude = if update_self { None } else { Some(self.id()) };
109        world.broadcast_to_entity_trackers(self.id(), packet.clone(), exclude);
110        if update_self && let Some(player) = self.as_player() {
111            player.send_packet(packet);
112        }
113    }
114
115    /// Runs vanilla `LivingEntity.updateSwingTime`.
116    fn update_swing_time(&self) {
117        self.living_base()
118            .update_swing_time(self.current_swing_duration());
119    }
120
121    /// Returns a reference to this entity's attribute map.
122    fn attributes(&self) -> &SyncMutex<AttributeMap> {
123        self.living_base().attributes()
124    }
125
126    /// Packs syncable attributes for initial spawn pairing.
127    ///
128    /// Mirrors vanilla `ServerEntity.sendPairingData`, which sends all syncable
129    /// living attributes after the add-entity and metadata packets.
130    fn pack_syncable_attributes(&self) -> Vec<AttributeSnapshot> {
131        self.attributes().lock().syncable_snapshots()
132    }
133
134    /// Drains syncable dirty attributes for per-tick tracking updates.
135    ///
136    /// Mirrors vanilla `ServerEntity.sendDirtyEntityData`, which sends dirty
137    /// living attributes after dirty entity data.
138    fn drain_dirty_syncable_attributes(&self) -> Vec<AttributeSnapshot> {
139        self.attributes().lock().drain_dirty_sync()
140    }
141
142    /// Drains dirty mob-effect packet changes for vanilla recipients.
143    fn drain_dirty_mob_effects(&self) -> Vec<MobEffectSyncChange> {
144        self.living_base().drain_dirty_mob_effects()
145    }
146
147    /// Packs non-empty equipment slots for initial spawn pairing.
148    fn pack_all_equipment(&self) -> Vec<EquipmentSlotItem> {
149        self.pack_living_equipment()
150    }
151
152    /// Drains equipment slots that changed since the last tracker sync.
153    fn drain_dirty_equipment(&self) -> Vec<EquipmentSlotItem> {
154        self.drain_dirty_living_equipment()
155    }
156
157    /// Appends vanilla-shaped living state used by command NBT predicates.
158    fn save_command_nbt(&self, nbt: &mut NbtCompound) {
159        nbt.insert("Health", self.get_health());
160        nbt.insert(
161            "DeathTime",
162            NbtTag::Short(self.living_base().death_time() as i16),
163        );
164        nbt.insert("AbsorptionAmount", self.get_absorption_amount());
165        nbt.insert(
166            "current_impulse_context_reset_grace_time",
167            self.living_base()
168                .current_impulse_context_reset_grace_time(),
169        );
170        if let Some(impact) = self.living_base().current_impulse_impact_pos() {
171            nbt.insert(
172                "current_explosion_impact_pos",
173                NbtList::Double(vec![impact.x, impact.y, impact.z]),
174            );
175        }
176        nbt.insert("attributes", self.attributes().lock().to_vanilla_nbt());
177
178        let mut effects = self.living_base().active_mob_effects();
179        effects.sort_by_key(|effect| effect.effect().try_id().unwrap_or(usize::MAX));
180        if !effects.is_empty() {
181            nbt.insert(
182                "active_effects",
183                NbtList::Compound(
184                    effects
185                        .iter()
186                        .map(ActiveMobEffect::to_vanilla_nbt)
187                        .collect(),
188                ),
189            );
190        }
191
192        nbt.insert("FallFlying", nbt_bool(self.is_fall_flying()));
193        if let Some(pos) = self.sleeping_pos() {
194            nbt.insert(
195                "sleeping_pos",
196                NbtTag::IntArray(vec![pos.x(), pos.y(), pos.z()]),
197            );
198        }
199        if let Some(uuid) = self.last_hurt_by_player_uuid() {
200            nbt.insert(
201                "last_hurt_by_player",
202                NbtTag::IntArray(uuid.to_int_array().to_vec()),
203            );
204            nbt.insert(
205                "last_hurt_by_player_memory_time",
206                self.last_hurt_by_player_memory_time(),
207            );
208        }
209        if let Some(entity) = self.last_hurt_by_mob() {
210            nbt.insert(
211                "last_hurt_by_mob",
212                NbtTag::IntArray(entity.uuid().to_int_array().to_vec()),
213            );
214            nbt.insert(
215                "ticks_since_last_hurt_by_mob",
216                self.tick_count()
217                    .wrapping_sub(self.last_hurt_by_mob_timestamp()),
218            );
219        }
220
221        let mut equipment = NbtCompound::new();
222        for slot in EquipmentSlot::ALL {
223            self.with_equipment_slot(slot, &mut |item| {
224                if !item.is_empty() {
225                    equipment.insert(slot.name(), item.to_nbt_tag_ref());
226                }
227            });
228        }
229        if !equipment.is_empty() {
230            nbt.insert("equipment", NbtTag::Compound(equipment));
231        }
232    }
233
234    /// Gets the current health of the entity.
235    fn get_health(&self) -> f32;
236
237    /// Sets the health of the entity, clamped between 0 and max health.
238    fn set_health(&self, health: f32);
239
240    /// Gets the maximum health from the attribute system.
241    fn get_max_health(&self) -> f32 {
242        self.attributes()
243            .lock()
244            .required_value(vanilla_attributes::MAX_HEALTH) as f32
245    }
246
247    /// Returns vanilla `LivingEntity.noActionTime`.
248    fn no_action_time(&self) -> i32 {
249        self.living_base().no_action_time()
250    }
251
252    /// Sets vanilla `LivingEntity.noActionTime`.
253    fn set_no_action_time(&self, no_action_time: i32) {
254        self.living_base().set_no_action_time(no_action_time);
255    }
256
257    /// Increments vanilla `LivingEntity.noActionTime`.
258    fn increment_no_action_time(&self) {
259        self.living_base().increment_no_action_time();
260    }
261
262    /// Heals the entity by the specified amount.
263    fn heal(&self, amount: f32) {
264        let current_health = self.get_health();
265        if current_health > 0.0 {
266            self.set_health(current_health + amount);
267        }
268    }
269
270    /// Returns true if the entity is dead or dying (health <= 0).
271    fn is_dead_or_dying(&self) -> bool {
272        self.get_health() <= 0.0
273    }
274
275    /// Returns vanilla `LivingEntity.isBaby()`.
276    fn is_baby(&self) -> bool {
277        self.as_ageable_mob().is_some_and(AgeableMob::is_baby)
278    }
279
280    /// Returns vanilla `LivingEntity.getSoundVolume`.
281    fn sound_volume(&self) -> f32 {
282        1.0
283    }
284
285    /// Returns vanilla `LivingEntity.getVoicePitch`.
286    fn voice_pitch(&self) -> f32 {
287        if self.is_baby() {
288            (rand::random::<f32>() - rand::random::<f32>()) * 0.2 + 1.5
289        } else {
290            (rand::random::<f32>() - rand::random::<f32>()) * 0.2 + 1.0
291        }
292    }
293
294    /// Returns vanilla `LivingEntity.getHurtSound`.
295    fn hurt_sound(&self, _source: &DamageSource) -> Option<SoundEventRef> {
296        Some(&sound_events::ENTITY_GENERIC_HURT)
297    }
298
299    /// Returns vanilla `LivingEntity.getDeathSound`.
300    fn death_sound(&self) -> Option<SoundEventRef> {
301        Some(&sound_events::ENTITY_GENERIC_DEATH)
302    }
303
304    /// Runs vanilla `LivingEntity.makeSound`.
305    fn make_sound(&self, sound: Option<SoundEventRef>) {
306        if let Some(sound) = sound {
307            self.play_sound(sound, self.sound_volume(), self.voice_pitch());
308        }
309    }
310
311    /// Runs vanilla `LivingEntity.playHurtSound`.
312    fn play_hurt_sound(&self, source: &DamageSource) {
313        if let Some(mob) = self.as_mob() {
314            mob.reset_ambient_sound_time();
315        }
316        self.make_sound(self.hurt_sound(source));
317    }
318
319    /// Plays vanilla's death sound for this living entity.
320    fn play_death_sound(&self) {
321        self.make_sound(self.death_sound());
322    }
323
324    /// Returns vanilla `LivingEntity.getAgeScale()`.
325    fn get_age_scale(&self) -> f32 {
326        if self.is_baby() { 0.5 } else { 1.0 }
327    }
328
329    /// Returns vanilla `LivingEntity.getScale()`.
330    fn get_scale(&self) -> f32 {
331        self.attributes()
332            .lock()
333            .get_value(vanilla_attributes::SCALE)
334            .unwrap_or(1.0) as f32
335    }
336
337    /// Returns true if the entity is alive (health > 0).
338    fn is_alive(&self) -> bool {
339        !self.is_dead_or_dying()
340    }
341
342    /// Returns vanilla `LivingEntity.getArmorCoverPercentage()`.
343    fn get_armor_cover_percentage(&self) -> f32 {
344        let mut covered_slots = 0;
345        for slot in EquipmentSlot::ARMOR_SLOTS {
346            self.with_equipment_slot(slot, &mut |item_stack| {
347                if !item_stack.is_empty() {
348                    covered_slots += 1;
349                }
350            });
351        }
352
353        covered_slots as f32 / EquipmentSlot::ARMOR_SLOTS.len() as f32
354    }
355
356    /// Returns vanilla `LivingEntity.getVisibilityPercent()`.
357    fn get_visibility_percent(&self, targeting_entity: Option<&dyn Entity>) -> f64 {
358        let mut visibility_percent = 1.0;
359        if self.is_discrete() {
360            visibility_percent *= 0.8;
361        }
362
363        if self.is_invisible() {
364            visibility_percent *= 0.7 * f64::from(self.get_armor_cover_percentage().max(0.1));
365        }
366
367        if self.disguise_head_matches_targeting_entity(targeting_entity) {
368            visibility_percent *= 0.5;
369        }
370
371        visibility_percent
372    }
373
374    /// Returns whether the equipped head item reduces visibility to `targeting_entity`.
375    fn disguise_head_matches_targeting_entity(
376        &self,
377        targeting_entity: Option<&dyn Entity>,
378    ) -> bool {
379        let Some(targeting_entity) = targeting_entity else {
380            return false;
381        };
382
383        let mut matches_target = false;
384        self.with_equipment_slot(EquipmentSlot::Head, &mut |item_stack| {
385            let target_type = targeting_entity.entity_type();
386            matches_target = target_type == &vanilla_entities::SKELETON
387                && item_stack.is(&vanilla_items::SKELETON_SKULL)
388                || target_type == &vanilla_entities::ZOMBIE
389                    && item_stack.is(&vanilla_items::ZOMBIE_HEAD)
390                || target_type == &vanilla_entities::PIGLIN
391                    && item_stack.is(&vanilla_items::PIGLIN_HEAD)
392                || target_type == &vanilla_entities::PIGLIN_BRUTE
393                    && item_stack.is(&vanilla_items::PIGLIN_HEAD)
394                || target_type == &vanilla_entities::CREEPER
395                    && item_stack.is(&vanilla_items::CREEPER_HEAD);
396        });
397        matches_target
398    }
399
400    /// Returns vanilla `LivingEntity.canBeSeenByAnyone()`.
401    fn can_be_seen_by_anyone(&self) -> bool {
402        !self.is_spectator() && Entity::is_alive(self)
403    }
404
405    /// Returns vanilla `LivingEntity.canBeSeenAsEnemy()`.
406    fn can_be_seen_as_enemy(&self) -> bool {
407        !self.is_invulnerable() && self.can_be_seen_by_anyone()
408    }
409
410    /// Returns vanilla `LivingEntity.canAttack()`.
411    fn can_attack(&self, target: &dyn LivingEntity) -> bool {
412        if target.entity_type() == &vanilla_entities::PLAYER
413            && self
414                .level()
415                .is_some_and(|world| world.difficulty() == Difficulty::Peaceful)
416        {
417            return false;
418        }
419
420        target.can_be_seen_as_enemy()
421    }
422
423    /// Returns vanilla `LivingEntity.getLastDamageSource()`.
424    fn last_damage_source(&self) -> Option<DamageSource> {
425        let game_time = self.level().map_or(0, |world| world.game_time());
426        self.living_base().last_damage_source(game_time)
427    }
428
429    /// Sets vanilla `LivingEntity.lastHurtByPlayer`.
430    fn set_last_hurt_by_player(&self, player_uuid: Uuid, time_to_remember: i32) {
431        self.living_base()
432            .set_last_hurt_by_player(player_uuid, time_to_remember);
433    }
434
435    /// Returns vanilla `LivingEntity.lastHurtByPlayerMemoryTime`.
436    fn last_hurt_by_player_memory_time(&self) -> i32 {
437        self.living_base().last_hurt_by_player_memory_time()
438    }
439
440    /// Returns vanilla `LivingEntity.lastHurtByPlayer`, if still remembered.
441    fn last_hurt_by_player_uuid(&self) -> Option<Uuid> {
442        self.living_base().last_hurt_by_player_uuid()
443    }
444
445    /// Returns vanilla `LivingEntity.lastHurtByMob`.
446    fn last_hurt_by_mob(&self) -> Option<SharedEntity> {
447        self.living_base().last_hurt_by_mob()
448    }
449
450    /// Returns vanilla `LivingEntity.lastHurtByMobTimestamp`.
451    fn last_hurt_by_mob_timestamp(&self) -> i32 {
452        self.living_base().last_hurt_by_mob_timestamp()
453    }
454
455    /// Sets vanilla `LivingEntity.lastHurtByMob`.
456    fn set_last_hurt_by_mob(&self, target: Option<&SharedEntity>) {
457        self.living_base()
458            .set_last_hurt_by_mob(target, self.tick_count());
459    }
460
461    /// Returns vanilla `LivingEntity.lastHurtMob`.
462    fn last_hurt_mob(&self) -> Option<SharedEntity> {
463        self.living_base().last_hurt_mob()
464    }
465
466    /// Returns vanilla `LivingEntity.lastHurtMobTimestamp`.
467    fn last_hurt_mob_timestamp(&self) -> i32 {
468        self.living_base().last_hurt_mob_timestamp()
469    }
470
471    /// Sets vanilla `LivingEntity.lastHurtMob`.
472    fn set_last_hurt_mob(&self, target: Option<&SharedEntity>) {
473        self.living_base()
474            .set_last_hurt_mob(target, self.tick_count());
475    }
476
477    /// Resolves vanilla `LivingEntity.resolveMobResponsibleForDamage`.
478    fn resolve_mob_responsible_for_damage(&self, world: &World, source: &DamageSource) {
479        if source.is(&vanilla_damage_type_tags::DamageTypeTag::NO_ANGER) {
480            return;
481        }
482        if source.damage_type == &vanilla_damage_types::WIND_CHARGE
483            && REGISTRY.entity_types.is_in_tag(
484                self.entity_type(),
485                &EntityTypeTag::NO_ANGER_FROM_WIND_CHARGE,
486            )
487        {
488            return;
489        }
490
491        let Some(entity_id) = source.causing_entity_id else {
492            return;
493        };
494        let Some(entity) = world.get_entity_by_id(entity_id) else {
495            return;
496        };
497        if entity.is_living_entity() {
498            self.set_last_hurt_by_mob(Some(&entity));
499        }
500    }
501
502    /// Resolves vanilla `LivingEntity.resolvePlayerResponsibleForDamage`.
503    fn resolve_player_responsible_for_damage(&self, world: &World, source: &DamageSource) {
504        let Some(entity_id) = source.causing_entity_id else {
505            return;
506        };
507        let Some(entity) = world.get_entity_by_id(entity_id) else {
508            return;
509        };
510        if entity.entity_type() == &vanilla_entities::PLAYER {
511            self.set_last_hurt_by_player(entity.uuid(), 100);
512        }
513    }
514
515    /// Returns vanilla `LivingEntity.hasLineOfSight()`.
516    fn has_line_of_sight(&self, target: &dyn Entity) -> bool {
517        self.has_line_of_sight_with(
518            target,
519            ClipBlockShape::Collider,
520            ClipFluid::None,
521            target.get_eye_y(),
522        )
523    }
524
525    /// Returns vanilla line-of-sight with explicit clip options.
526    fn has_line_of_sight_with(
527        &self,
528        target: &dyn Entity,
529        block_shape: ClipBlockShape,
530        fluid: ClipFluid,
531        target_eye_y: f64,
532    ) -> bool {
533        let Some(world) = self.level() else {
534            return false;
535        };
536        let Some(target_world) = target.level() else {
537            return false;
538        };
539        if !Arc::ptr_eq(&world, &target_world) {
540            return false;
541        }
542
543        let position = self.position();
544        let target_position = target.position();
545        let start = DVec3::new(position.x, self.get_eye_y(), position.z);
546        let end = DVec3::new(target_position.x, target_eye_y, target_position.z);
547        if start.distance_squared(end) > 128.0 * 128.0 {
548            return false;
549        }
550
551        world.clip(start, end, block_shape, fluid).is_miss()
552    }
553
554    /// Returns vanilla base living-entity invulnerability.
555    fn default_is_invulnerable_to(&self, source: &DamageSource) -> bool {
556        self.is_removed()
557            || self.is_invulnerable() && !source.bypasses_invulnerability()
558            || source.is(&vanilla_damage_type_tags::DamageTypeTag::IS_FIRE) && self.fire_immune()
559            || source.is(&vanilla_damage_type_tags::DamageTypeTag::IS_FALL)
560                && self.is_fall_damage_immune()
561    }
562
563    /// Returns whether this living entity ignores a damage source.
564    fn is_invulnerable_to(&self, world: &World, source: &DamageSource) -> bool {
565        self.default_is_invulnerable_to(source)
566            || enchantment_helper::is_immune_to_damage(world, self, source)
567    }
568
569    /// Main vanilla living-entity damage entry point.
570    ///
571    /// `world` is the `ServerLevel` supplied by the vanilla caller. It may
572    /// intentionally differ from the entity's attached world.
573    fn hurt_server(&self, world: &World, source: &DamageSource, amount: f32) -> bool {
574        if self.is_invulnerable_to(world, source) {
575            return false;
576        }
577        if self.is_dead_or_dying() {
578            return false;
579        }
580        if source.is(&vanilla_damage_type_tags::DamageTypeTag::IS_FIRE)
581            && self.has_mob_effect(vanilla_mob_effects::FIRE_RESISTANCE)
582        {
583            return false;
584        }
585        if self.is_sleeping() {
586            self.stop_sleeping();
587        }
588
589        self.set_no_action_time(0);
590
591        let mut damage = amount;
592        if damage < 0.0 {
593            damage = 0.0;
594        }
595
596        // TODO: apply item blocking before actually_hurt once shield/use-item hooks exist.
597        if source.is(&vanilla_damage_type_tags::DamageTypeTag::IS_FREEZING)
598            && REGISTRY
599                .entity_types
600                .is_in_tag(self.entity_type(), &EntityTypeTag::FREEZE_HURTS_EXTRA_TYPES)
601        {
602            damage *= 5.0;
603        }
604        // TODO: apply helmet damage once those equipment hooks exist.
605        if !damage.is_finite() {
606            damage = f32::MAX;
607        }
608
609        let Some((took_full_damage, effective_amount)) = self
610            .living_base()
611            .apply_damage_cooldown(damage, source.bypasses_cooldown())
612        else {
613            return false;
614        };
615
616        self.before_actually_hurt(source, effective_amount);
617        self.actually_hurt(world, source, effective_amount);
618        self.resolve_mob_responsible_for_damage(world, source);
619        self.resolve_player_responsible_for_damage(world, source);
620
621        if took_full_damage {
622            self.broadcast_damage_event(world, source);
623            if !source.is(&vanilla_damage_type_tags::DamageTypeTag::NO_IMPACT) {
624                self.mark_hurt();
625                self.broadcast_hurt_animation(world);
626            }
627            self.apply_damage_knockback(source);
628        }
629
630        if self.is_dead_or_dying() {
631            if took_full_damage {
632                self.play_death_sound();
633            }
634            self.die(source);
635        } else if took_full_damage {
636            self.play_hurt_sound(source);
637        }
638        // TODO: Play secondary hurt sounds once equipment effects expose them.
639
640        let game_time = self.level().map_or(0, |world| world.game_time());
641        self.living_base()
642            .record_last_damage_source(source, game_time);
643
644        true
645    }
646
647    /// Hook before applying damage after vanilla reductions.
648    fn before_actually_hurt(&self, _source: &DamageSource, _amount: f32) {
649        if let Some(animal) = self.as_animal() {
650            animal.reset_love();
651        }
652    }
653
654    /// Damages equipment that participates in vanilla armor absorption.
655    fn hurt_armor(&self, _source: &DamageSource, _damage: f32) {}
656
657    /// Mirrors vanilla `LivingEntity.doHurtEquipment`.
658    fn do_hurt_equipment(&self, source: &DamageSource, damage: f32, slots: &[EquipmentSlot]) {
659        if damage <= 0.0 {
660            return;
661        }
662
663        let durability_damage = (damage / 4.0).max(1.0) as i32;
664        for &slot in slots {
665            let mut item_broke = false;
666            self.with_equipment_slot_mut(slot, &mut |item| {
667                let damage_on_hurt = item
668                    .get_equippable()
669                    .is_some_and(|equippable| equippable.damage_on_hurt);
670                if damage_on_hurt
671                    && item.is_damageable_item()
672                    && item.can_be_hurt_by(source.damage_type)
673                {
674                    item_broke =
675                        item.hurt_and_break(durability_damage, self.has_infinite_materials());
676                }
677            });
678            if item_broke {
679                self.on_equipped_item_broken(slot);
680            }
681        }
682    }
683
684    /// Mirrors vanilla `LivingEntity.getDamageAfterArmorAbsorb`.
685    fn get_damage_after_armor_absorb(&self, source: &DamageSource, mut damage: f32) -> f32 {
686        if !source.is(&vanilla_damage_type_tags::DamageTypeTag::BYPASSES_ARMOR) {
687            self.hurt_armor(source, damage);
688            let armor_toughness =
689                self.attributes()
690                    .lock()
691                    .required_value(vanilla_attributes::ARMOR_TOUGHNESS) as f32;
692            damage = combat_rules::get_damage_after_absorb(
693                self,
694                damage,
695                source,
696                self.get_armor_value() as f32,
697                armor_toughness,
698            );
699        }
700        damage
701    }
702
703    /// Mirrors vanilla `LivingEntity.getDamageAfterMagicAbsorb`.
704    fn get_damage_after_magic_absorb(&self, source: &DamageSource, mut damage: f32) -> f32 {
705        if source.is(&vanilla_damage_type_tags::DamageTypeTag::BYPASSES_EFFECTS) {
706            return damage;
707        }
708
709        if !source.is(&vanilla_damage_type_tags::DamageTypeTag::BYPASSES_RESISTANCE)
710            && let Some(resistance) = self.mob_effect(vanilla_mob_effects::RESISTANCE)
711        {
712            let absorb_value = (resistance.amplifier() + 1) * 5;
713            let absorb = 25 - absorb_value;
714            damage = (damage * absorb as f32 / 25.0).max(0.0);
715        }
716
717        if damage <= 0.0 {
718            return 0.0;
719        }
720        if source.is(&vanilla_damage_type_tags::DamageTypeTag::BYPASSES_ENCHANTMENTS) {
721            return damage;
722        }
723
724        let enchantment_armor = self.level().map_or(0.0, |world| {
725            enchantment_helper::get_damage_protection(&world, self, source)
726        });
727        if enchantment_armor > 0.0 {
728            damage = combat_rules::get_damage_after_magic_absorb(damage, enchantment_armor);
729        }
730        damage
731    }
732
733    /// Applies damage after vanilla reductions.
734    fn actually_hurt(&self, world: &World, source: &DamageSource, amount: f32) {
735        if self.is_invulnerable_to(world, source) {
736            return;
737        }
738
739        let damage = self.get_damage_after_armor_absorb(source, amount);
740        let damage = self.get_damage_after_magic_absorb(source, damage);
741        let original_damage = damage;
742        let damage = (damage - self.get_absorption_amount()).max(0.0);
743        self.set_absorption_amount(self.get_absorption_amount() - (original_damage - damage));
744
745        if damage != 0.0 {
746            self.set_health(self.get_health() - damage);
747            self.set_absorption_amount(self.get_absorption_amount() - damage);
748            self.game_event(&vanilla_game_events::ENTITY_DAMAGE);
749        }
750    }
751
752    /// Applies vanilla hurt knockback for a damage source.
753    fn apply_damage_knockback(&self, source: &DamageSource) {
754        if source.is(&vanilla_damage_type_tags::DamageTypeTag::NO_KNOCKBACK) {
755            return;
756        }
757
758        let (xd, zd) = self.damage_knockback_direction(source);
759        self.knockback(DAMAGE_KNOCKBACK_POWER, xd, zd);
760        self.indicate_damage(xd, zd);
761    }
762
763    /// Returns the horizontal direction used by vanilla damage knockback.
764    fn damage_knockback_direction(&self, source: &DamageSource) -> (f64, f64) {
765        if let Some(direct_entity_id) = source.direct_entity_id
766            && let Some(world) = self.level()
767            && let Some(direct_entity) = world.get_entity_by_id(direct_entity_id)
768            && let Some(projectile) = direct_entity.as_projectile()
769            && let Some(hurt_entity) = self.as_living_entity()
770        {
771            let (xd, zd) =
772                projectile.calculate_horizontal_hurt_knockback_direction(hurt_entity, source);
773            return (-xd, -zd);
774        }
775
776        let Some(source_position) = source.source_position else {
777            return (0.0, 0.0);
778        };
779
780        let position = self.position();
781        (
782            source_position.x - position.x,
783            source_position.z - position.z,
784        )
785    }
786
787    /// Applies vanilla `LivingEntity.knockback`.
788    fn knockback(&self, mut power: f64, mut xd: f64, mut zd: f64) {
789        power *= 1.0 - self.knockback_resistance();
790        if power <= 0.0 {
791            return;
792        }
793
794        while xd * xd + zd * zd < KNOCKBACK_DIRECTION_EPSILON_SQ {
795            xd = (rand::random::<f64>() - rand::random::<f64>()) * 0.01;
796            zd = (rand::random::<f64>() - rand::random::<f64>()) * 0.01;
797        }
798
799        let old_velocity = self.velocity();
800        let delta_vector = DVec3::new(xd, 0.0, zd).normalize() * power;
801        self.set_velocity(DVec3::new(
802            old_velocity.x / 2.0 - delta_vector.x,
803            if self.on_ground() {
804                0.4_f64.min(old_velocity.y / 2.0 + power)
805            } else {
806                old_velocity.y
807            },
808            old_velocity.z / 2.0 - delta_vector.z,
809        ));
810        self.mark_velocity_sync();
811    }
812
813    /// Returns vanilla knockback resistance.
814    fn knockback_resistance(&self) -> f64 {
815        self.attributes()
816            .lock()
817            .required_value(vanilla_attributes::KNOCKBACK_RESISTANCE)
818    }
819
820    /// Mirrors vanilla `LivingEntity.indicateDamage`.
821    fn indicate_damage(&self, _xd: f64, _zd: f64) {}
822
823    /// Returns the chunk used for vanilla nearby hurt broadcasts.
824    fn hurt_broadcast_chunk(&self) -> ChunkPos {
825        ChunkPos::from_entity_pos(self.position())
826    }
827
828    /// Broadcasts vanilla damage-event metadata near this entity.
829    fn broadcast_damage_event(&self, world: &World, source: &DamageSource) {
830        world.broadcast_to_nearby(
831            self.hurt_broadcast_chunk(),
832            CDamageEvent {
833                entity_id: self.id(),
834                source_type_id: source.damage_type.id() as i32,
835                source_cause_id: source.causing_entity_id.map_or(0, |id| id + 1),
836                source_direct_id: source.direct_entity_id.map_or(0, |id| id + 1),
837                source_position: source.source_position,
838            },
839            None,
840        );
841    }
842
843    /// Broadcasts vanilla hurt animation near this entity.
844    fn broadcast_hurt_animation(&self, world: &World) {
845        let (yaw, _) = self.rotation();
846        world.broadcast_to_nearby(
847            self.hurt_broadcast_chunk(),
848            CHurtAnimation {
849                entity_id: self.id(),
850                yaw,
851            },
852            None,
853        );
854    }
855
856    /// Processes vanilla living death side effects.
857    fn die(&self, source: &DamageSource) {
858        if self.is_removed() {
859            return;
860        }
861        if !self.living_base().mark_death_processed() {
862            return;
863        }
864
865        self.game_event(&vanilla_game_events::ENTITY_DIE);
866        self.drop_all_death_loot(source);
867        self.broadcast_entity_event(EntityStatus::Death);
868        self.set_pose(EntityPose::Dying);
869    }
870
871    /// Returns vanilla `LivingEntity.shouldDropLoot`.
872    fn should_drop_loot(&self, world: &World) -> bool {
873        !self.is_baby() && world.get_game_rule(&MOB_DROPS)
874    }
875
876    /// Returns vanilla `LivingEntity.shouldDropExperience`.
877    fn should_drop_experience(&self) -> bool {
878        !self.is_baby()
879    }
880
881    /// Returns vanilla `LivingEntity.isAlwaysExperienceDropper`.
882    fn is_always_experience_dropper(&self) -> bool {
883        false
884    }
885
886    /// Runs vanilla `LivingEntity.skipDropExperience`.
887    fn skip_drop_experience(&self) {
888        self.living_base().skip_drop_experience();
889    }
890
891    /// Returns vanilla `LivingEntity.wasExperienceConsumed`.
892    fn was_experience_consumed(&self) -> bool {
893        self.living_base().was_experience_consumed()
894    }
895
896    /// Returns vanilla `LivingEntity.getBaseExperienceReward`.
897    fn base_experience_reward(&self) -> i32 {
898        if let Some(animal) = self.as_animal() {
899            return animal.base_experience_reward_animal();
900        }
901
902        self.as_mob().map_or(0, Mob::base_experience_reward_mob)
903    }
904
905    /// Returns vanilla `LivingEntity.getExperienceReward`.
906    fn experience_reward(&self, _world: &World, _killer_entity_id: Option<i32>) -> i32 {
907        // TODO: Apply EnchantmentHelper.processMobExperience once enchantment
908        // value-effect hooks can receive the killer/living-entity context.
909        self.base_experience_reward()
910    }
911
912    /// Runs the currently implemented subset of vanilla `LivingEntity.dropAllDeathLoot`.
913    fn drop_all_death_loot(&self, source: &DamageSource) {
914        let Some(world) = self.level() else {
915            return;
916        };
917        if self.should_drop_loot(world.as_ref()) {
918            let killed_by_player = self.last_hurt_by_player_memory_time() > 0;
919            self.drop_from_loot_table(source, killed_by_player);
920            self.drop_custom_death_loot(source, killed_by_player);
921            if let Some(mob) = self.as_mob() {
922                mob.drop_custom_death_loot_mob(source, killed_by_player);
923            }
924        }
925        self.drop_experience(&world, source.causing_entity_id);
926        // TODO: Drop non-mob equipment overrides once those foundations exist.
927    }
928
929    /// Runs vanilla `LivingEntity.dropExperience`.
930    fn drop_experience(&self, world: &Arc<World>, killer_entity_id: Option<i32>) {
931        if self.was_experience_consumed() {
932            return;
933        }
934
935        let should_drop = self.is_always_experience_dropper()
936            || self.last_hurt_by_player_memory_time() > 0
937                && self.should_drop_experience()
938                && world.get_game_rule(&MOB_DROPS);
939        if !should_drop {
940            return;
941        }
942
943        let reward = self.experience_reward(world, killer_entity_id);
944        if reward > 0 {
945            ExperienceOrbEntity::award(world, self.position(), reward);
946        }
947    }
948
949    /// Resolves the loot table used by vanilla `LivingEntity.dropFromLootTable`.
950    fn death_loot_table(&self) -> Option<LootTableRef> {
951        if let Some(mob) = self.as_mob()
952            && mob.has_custom_death_loot_table()
953        {
954            return mob.custom_death_loot_table();
955        }
956
957        let entity_type = self.entity_type();
958        let loot_key = Identifier::vanilla(format!("entities/{}", entity_type.key.path));
959        REGISTRY.loot_tables.by_key(&loot_key)
960    }
961
962    /// Returns vanilla `Entity.getLootTableSeed` for death loot.
963    fn death_loot_table_seed(&self) -> i64 {
964        self.as_mob().map_or(0, Mob::death_loot_table_seed)
965    }
966
967    /// Runs vanilla `LivingEntity.dropFromLootTable`.
968    fn drop_from_loot_table(&self, source: &DamageSource, killed_by_player: bool) {
969        let Some(world) = self.level() else {
970            return;
971        };
972        let has_custom_death_loot_table =
973            self.as_mob().is_some_and(Mob::has_custom_death_loot_table);
974        let Some(loot_table) = self.death_loot_table() else {
975            if has_custom_death_loot_table && let Some(mob) = self.as_mob() {
976                mob.clear_custom_death_loot_table();
977            }
978            return;
979        };
980
981        let seed = self.death_loot_table_seed();
982        let drops = if seed == 0 {
983            let mut rng = rand::rng();
984            death_loot_items_with_rng(
985                self,
986                loot_table,
987                world.as_ref(),
988                source,
989                killed_by_player,
990                &mut rng,
991            )
992        } else {
993            let mut rng = StdRng::seed_from_u64(seed as u64);
994            death_loot_items_with_rng(
995                self,
996                loot_table,
997                world.as_ref(),
998                source,
999                killed_by_player,
1000                &mut rng,
1001            )
1002        };
1003
1004        if has_custom_death_loot_table && let Some(mob) = self.as_mob() {
1005            mob.clear_custom_death_loot_table();
1006        }
1007
1008        for item_stack in drops {
1009            self.spawn_at_location(item_stack, 0.0);
1010        }
1011    }
1012
1013    /// Hook for non-mob custom death loot.
1014    fn drop_custom_death_loot(&self, _source: &DamageSource, _killed_by_player: bool) {}
1015
1016    /// Ticks the vanilla living death animation and removes the entity at completion.
1017    fn tick_death(&self) {
1018        let death_time = self.living_base().increment_death_time();
1019        if death_time >= DEATH_DURATION && !self.is_removed() {
1020            self.broadcast_entity_event(EntityStatus::Poof);
1021            self.set_removed(RemovalReason::Killed);
1022        }
1023    }
1024
1025    /// Gets the absorption amount (extra health from effects like absorption).
1026    fn get_absorption_amount(&self) -> f32 {
1027        self.living_base().absorption_amount()
1028    }
1029
1030    /// Sets the absorption amount.
1031    fn set_absorption_amount(&self, amount: f32) {
1032        self.living_base().set_absorption_amount(amount);
1033    }
1034
1035    /// Returns vanilla `LivingEntity.getFallDamageSound()`.
1036    fn fall_damage_sound(&self, damage: i32) -> SoundEventRef {
1037        let (small, big) = self.fall_sounds();
1038        if damage > 4 { big } else { small }
1039    }
1040
1041    /// Plays vanilla `LivingEntity.playBlockFallSound()`.
1042    fn play_block_fall_sound(&self) {
1043        let Some(world) = self.level() else {
1044            return;
1045        };
1046        let position = self.position();
1047        let pos = BlockPos::new(
1048            position.x.floor() as i32,
1049            (position.y - f64::from(0.2_f32)).floor() as i32,
1050            position.z.floor() as i32,
1051        );
1052        let state = world.get_block_state(pos);
1053        if state.is_air() {
1054            return;
1055        }
1056
1057        let sound_type = state.get_block().config.sound_type;
1058        self.play_sound(
1059            sound_type.fall_sound,
1060            sound_type.volume * 0.5,
1061            sound_type.pitch * 0.75,
1062        );
1063    }
1064
1065    /// Mirrors vanilla `LivingEntity.causeFallDamage`.
1066    fn cause_living_fall_damage(
1067        &self,
1068        fall_distance: f64,
1069        damage_modifier: f32,
1070        source: &DamageSource,
1071    ) -> bool {
1072        let effective_fall_distance =
1073            if let Some(impact_pos) = self.living_base().current_impulse_impact_pos() {
1074                let effective_fall_distance = fall_distance.min(impact_pos.y - self.position().y);
1075                if effective_fall_distance <= 0.0 {
1076                    self.reset_current_impulse_context();
1077                } else {
1078                    self.try_reset_current_impulse_context();
1079                }
1080                effective_fall_distance
1081            } else {
1082                fall_distance
1083            };
1084
1085        if self.is_fall_damage_immune() {
1086            return false;
1087        }
1088
1089        self.propagate_fall_to_passengers(effective_fall_distance, damage_modifier, source);
1090
1091        let attributes = self.attributes().lock();
1092        let safe_fall_distance = attributes
1093            .get_value(vanilla_attributes::SAFE_FALL_DISTANCE)
1094            .unwrap_or(vanilla_attributes::SAFE_FALL_DISTANCE.default_value);
1095        let fall_damage_multiplier = attributes
1096            .get_value(vanilla_attributes::FALL_DAMAGE_MULTIPLIER)
1097            .unwrap_or(vanilla_attributes::FALL_DAMAGE_MULTIPLIER.default_value);
1098        drop(attributes);
1099
1100        let damage = LivingEntityBase::calculate_fall_damage(
1101            effective_fall_distance,
1102            damage_modifier,
1103            safe_fall_distance,
1104            fall_damage_multiplier,
1105        );
1106        if damage <= 0 {
1107            return false;
1108        }
1109
1110        self.reset_current_impulse_context();
1111        self.play_sound(self.fall_damage_sound(damage), 1.0, 1.0);
1112        self.play_block_fall_sound();
1113        if let Some(world) = self.level() {
1114            self.hurt(&world, source, damage as f32);
1115        }
1116        true
1117    }
1118
1119    /// Gets the entity's armor value from the attribute system.
1120    fn get_armor_value(&self) -> i32 {
1121        self.attributes()
1122            .lock()
1123            .get_value(vanilla_attributes::ARMOR)
1124            .unwrap_or(0.0) as i32
1125    }
1126
1127    /// Gets the gravity value from the attribute system.
1128    fn get_attribute_gravity(&self) -> f64 {
1129        self.attributes()
1130            .lock()
1131            .required_value(vanilla_attributes::GRAVITY)
1132    }
1133
1134    /// Returns vanilla `LivingEntity.getEffectiveGravity()`.
1135    fn get_effective_gravity(&self) -> f64 {
1136        let gravity = self.get_gravity();
1137        if self.velocity().y <= 0.0 && self.has_mob_effect(vanilla_mob_effects::SLOW_FALLING) {
1138            gravity.min(0.01)
1139        } else {
1140            gravity
1141        }
1142    }
1143
1144    /// Checks if the entity can be affected by potions.
1145    fn is_affected_by_potions(&self) -> bool {
1146        !self.is_dead_or_dying()
1147    }
1148
1149    /// Returns vanilla base `LivingEntity.canBeAffected` eligibility.
1150    fn default_can_be_affected(&self, effect: &MobEffectInstance) -> bool {
1151        if REGISTRY
1152            .entity_types
1153            .is_in_tag(self.entity_type(), &EntityTypeTag::IMMUNE_TO_INFESTED)
1154        {
1155            return effect.effect() != vanilla_mob_effects::INFESTED;
1156        }
1157        if REGISTRY
1158            .entity_types
1159            .is_in_tag(self.entity_type(), &EntityTypeTag::IMMUNE_TO_OOZING)
1160        {
1161            return effect.effect() != vanilla_mob_effects::OOZING;
1162        }
1163        if REGISTRY
1164            .entity_types
1165            .is_in_tag(self.entity_type(), &EntityTypeTag::IGNORES_POISON_AND_REGEN)
1166        {
1167            return effect.effect() != vanilla_mob_effects::REGENERATION
1168                && effect.effect() != vanilla_mob_effects::POISON;
1169        }
1170
1171        true
1172    }
1173
1174    /// Returns whether this entity accepts a mob-effect instance.
1175    ///
1176    /// Concrete entities override this for vanilla class-specific immunities.
1177    fn can_be_affected(&self, effect: &MobEffectInstance) -> bool {
1178        self.default_can_be_affected(effect)
1179    }
1180
1181    /// Returns vanilla `LivingEntity.hasEffect()`.
1182    fn has_mob_effect(&self, effect: MobEffectRef) -> bool {
1183        self.living_base().has_mob_effect(effect)
1184    }
1185
1186    /// Returns vanilla `LivingEntity.getEffect()`.
1187    fn mob_effect(&self, effect: MobEffectRef) -> Option<ActiveMobEffect> {
1188        self.living_base().mob_effect(effect)
1189    }
1190
1191    /// Returns all active vanilla mob effects.
1192    fn active_mob_effects(&self) -> Vec<ActiveMobEffect> {
1193        self.living_base().active_mob_effects()
1194    }
1195
1196    /// Sets active vanilla mob-effect state.
1197    fn set_mob_effect(&self, effect: MobEffectRef, amplifier: i32) {
1198        self.add_mob_effect(MobEffectInstance::new(effect, amplifier));
1199    }
1200
1201    /// Adds or updates active vanilla mob-effect state.
1202    fn add_mob_effect(&self, effect: MobEffectInstance) -> bool {
1203        if !self.can_be_affected(&effect) {
1204            return false;
1205        }
1206        self.living_base().add_mob_effect(effect)
1207    }
1208
1209    /// Sets the presence of a vanilla mob effect.
1210    fn set_mob_effect_active(&self, effect: MobEffectRef, active: bool) {
1211        if active {
1212            self.set_mob_effect(effect, 0);
1213        } else {
1214            self.remove_mob_effect(effect);
1215        }
1216    }
1217
1218    /// Removes active vanilla mob-effect state.
1219    fn remove_mob_effect(&self, effect: MobEffectRef) -> bool {
1220        self.living_base().remove_mob_effect(effect)
1221    }
1222
1223    /// Ticks vanilla server-side mob-effect behavior and durations.
1224    fn tick_mob_effects(&self) {
1225        let world = self.level();
1226        for effect in self.active_mob_effects() {
1227            if !effect.has_remaining_duration() {
1228                self.living_base().tick_mob_effect_duration(effect.effect());
1229                continue;
1230            }
1231
1232            if effect.should_apply_effect_tick_this_tick(self.tick_count())
1233                && world
1234                    .as_deref()
1235                    .is_some_and(|world| !effect.apply_effect_tick(world, self))
1236            {
1237                self.remove_mob_effect(effect.effect());
1238                continue;
1239            }
1240
1241            self.living_base().tick_mob_effect_duration(effect.effect());
1242        }
1243    }
1244
1245    /// Returns whether vanilla effects keep this entity from drowning.
1246    fn has_water_breathing(&self) -> bool {
1247        self.has_mob_effect(vanilla_mob_effects::WATER_BREATHING)
1248            || self.has_mob_effect(vanilla_mob_effects::CONDUIT_POWER)
1249            || self.has_mob_effect(vanilla_mob_effects::BREATH_OF_THE_NAUTILUS)
1250    }
1251
1252    /// Returns whether active vanilla effects refill this entity's air supply.
1253    fn should_effects_refill_air_supply(&self) -> bool {
1254        !self.has_mob_effect(vanilla_mob_effects::BREATH_OF_THE_NAUTILUS)
1255            || self.has_mob_effect(vanilla_mob_effects::WATER_BREATHING)
1256            || self.has_mob_effect(vanilla_mob_effects::CONDUIT_POWER)
1257    }
1258
1259    /// Returns vanilla `LivingEntity.canBreatheUnderwater`.
1260    fn can_breathe_underwater(&self) -> bool {
1261        self.entity_type().flags.can_breathe_underwater
1262    }
1263
1264    /// Returns whether this entity can lose air and take drowning damage.
1265    fn can_drown_in_water(&self) -> bool {
1266        if self.can_breathe_underwater() || self.has_water_breathing() {
1267            return false;
1268        }
1269
1270        !self
1271            .as_player()
1272            .is_some_and(|player| player.abilities.lock().invulnerable)
1273    }
1274
1275    /// Returns whether the entity's eye block is a bubble column.
1276    fn is_eye_in_bubble_column(&self) -> bool {
1277        let Some(world) = self.level() else {
1278            return false;
1279        };
1280
1281        world
1282            .get_block_state(BlockPos::new(
1283                self.position().x.floor() as i32,
1284                self.get_eye_y().floor() as i32,
1285                self.position().z.floor() as i32,
1286            ))
1287            .get_block()
1288            == &vanilla_blocks::BUBBLE_COLUMN
1289    }
1290
1291    /// Mirrors vanilla `LivingEntity.decreaseAirSupply`.
1292    fn decrease_air_supply(&self, current_supply: i32) -> i32 {
1293        let oxygen_bonus = self
1294            .attributes()
1295            .lock()
1296            .get_value(vanilla_attributes::OXYGEN_BONUS)
1297            .unwrap_or(0.0);
1298        if oxygen_bonus > 0.0 && rand::random::<f64>() >= 1.0 / (oxygen_bonus + 1.0) {
1299            current_supply
1300        } else {
1301            current_supply - 1
1302        }
1303    }
1304
1305    /// Mirrors vanilla `LivingEntity.increaseAirSupply`.
1306    fn increase_air_supply(&self, current_supply: i32) -> i32 {
1307        (current_supply + 4).min(self.max_air_supply())
1308    }
1309
1310    /// Mirrors vanilla `LivingEntity.shouldTakeDrowningDamage`.
1311    fn should_take_drowning_damage(&self) -> bool {
1312        self.air_supply() <= -20
1313    }
1314
1315    /// Ticks vanilla living air-supply and drowning behavior from `baseTick`.
1316    fn tick_living_air_supply(&self) {
1317        if !LivingEntity::is_alive(self) {
1318            return;
1319        }
1320
1321        let eye_in_water = self.is_eye_in_water() && !self.is_eye_in_bubble_column();
1322        if eye_in_water {
1323            if self.can_drown_in_water() {
1324                self.set_air_supply(self.decrease_air_supply(self.air_supply()));
1325                if self.should_take_drowning_damage() {
1326                    self.set_air_supply(0);
1327                    self.broadcast_entity_event(EntityStatus::DrownParticles);
1328                    if let Some(world) = self.level() {
1329                        self.hurt(
1330                            &world,
1331                            &DamageSource::environment(&vanilla_damage_types::DROWN),
1332                            2.0,
1333                        );
1334                    }
1335                }
1336            } else if self.air_supply() < self.max_air_supply()
1337                && self.should_effects_refill_air_supply()
1338            {
1339                self.set_air_supply(self.increase_air_supply(self.air_supply()));
1340            }
1341
1342            if self
1343                .vehicle()
1344                .is_some_and(|vehicle| vehicle.dismounts_underwater())
1345            {
1346                self.stop_riding();
1347            }
1348            return;
1349        }
1350
1351        if self.air_supply() < self.max_air_supply() {
1352            self.set_air_supply(self.increase_air_supply(self.air_supply()));
1353        }
1354    }
1355
1356    /// Mirrors vanilla `LivingEntity.isInWall`.
1357    fn is_in_wall(&self) -> bool {
1358        !self.is_sleeping() && Entity::is_in_wall(self)
1359    }
1360
1361    /// Applies vanilla living in-wall damage from `baseTick`.
1362    fn tick_in_wall_damage(&self) {
1363        if !LivingEntity::is_alive(self) || !LivingEntity::is_in_wall(self) {
1364            return;
1365        }
1366
1367        if let Some(world) = self.level() {
1368            self.hurt(
1369                &world,
1370                &DamageSource::environment(&vanilla_damage_types::IN_WALL),
1371                1.0,
1372            );
1373        }
1374    }
1375
1376    /// Applies vanilla living environmental damage in `LivingEntity.baseTick` order.
1377    fn tick_living_environmental_damage(&self) {
1378        if !LivingEntity::is_alive(self) {
1379            return;
1380        }
1381
1382        if LivingEntity::is_in_wall(self) {
1383            self.tick_in_wall_damage();
1384        } else if self.as_player().is_some()
1385            && let Some(world) = self.level()
1386        {
1387            let border = world.world_border_snapshot();
1388            let position = self.position();
1389            if let Some(damage) =
1390                border.outside_damage_amount(position.x, position.z, self.bounding_box())
1391            {
1392                self.hurt(
1393                    &world,
1394                    &DamageSource::environment(&vanilla_damage_types::OUTSIDE_BORDER),
1395                    damage,
1396                );
1397            }
1398        }
1399
1400        self.tick_living_air_supply();
1401    }
1402
1403    /// Returns vanilla `LivingEntity.isAffectedByFluids()`.
1404    fn is_affected_by_fluids(&self) -> bool {
1405        true
1406    }
1407
1408    /// Returns vanilla `LivingEntity.canStandOnFluid()`.
1409    fn can_stand_on_fluid(&self, _fluid_state: FluidState) -> bool {
1410        false
1411    }
1412
1413    /// Checks if the entity is currently using an item.
1414    fn is_using_item(&self) -> bool {
1415        false
1416    }
1417
1418    /// Checks if the entity is blocking with a shield or similar item.
1419    fn is_blocking(&self) -> bool {
1420        false
1421    }
1422
1423    /// Checks if the entity is fall flying (using elytra).
1424    fn is_fall_flying(&self) -> bool {
1425        self.living_base().is_fall_flying()
1426    }
1427
1428    /// Sets whether this entity is fall flying.
1429    fn set_fall_flying(&self, fall_flying: bool) {
1430        self.set_shared_fall_flying(fall_flying);
1431        self.living_base().set_fall_flying(fall_flying);
1432    }
1433
1434    /// Returns vanilla `LivingEntity.getFallFlyingTicks()`.
1435    fn fall_flying_ticks(&self) -> i32 {
1436        self.living_base().fall_flying_ticks()
1437    }
1438
1439    /// Visits the item in a vanilla living-entity equipment slot.
1440    fn with_equipment_slot(&self, slot: EquipmentSlot, visitor: &mut dyn FnMut(&ItemStack)) {
1441        let equipment = self.living_base().equipment().lock();
1442        visitor(equipment.get_ref(slot));
1443    }
1444
1445    /// Returns vanilla `LivingEntity.isHolding`.
1446    fn is_holding(&self, predicate: &mut dyn FnMut(&ItemStack) -> bool) -> bool {
1447        let mut holding = false;
1448        self.with_equipment_slot(EquipmentSlot::MainHand, &mut |item_stack| {
1449            holding = predicate(item_stack);
1450        });
1451        if holding {
1452            return true;
1453        }
1454
1455        self.with_equipment_slot(EquipmentSlot::OffHand, &mut |item_stack| {
1456            holding = predicate(item_stack);
1457        });
1458        holding
1459    }
1460
1461    /// Mutates the item in a vanilla living-entity equipment slot.
1462    fn with_equipment_slot_mut(
1463        &self,
1464        slot: EquipmentSlot,
1465        visitor: &mut dyn FnMut(&mut ItemStack),
1466    ) {
1467        let mut equipment = self.living_base().equipment().lock();
1468        visitor(equipment.get_mut(slot));
1469    }
1470
1471    /// Returns whether this entity currently has an item in `slot`.
1472    fn has_item_in_slot(&self, slot: EquipmentSlot) -> bool {
1473        let mut has_item = false;
1474        self.with_equipment_slot(slot, &mut |item_stack| {
1475            has_item = !item_stack.is_empty();
1476        });
1477        has_item
1478    }
1479
1480    /// Returns whether vanilla allows this entity to use `slot`.
1481    fn can_use_slot(&self, _slot: EquipmentSlot) -> bool {
1482        true
1483    }
1484
1485    /// Returns the effective vanilla dispenser slot gate for living entities and mobs.
1486    fn can_dispenser_equip_into_slot(&self, _slot: EquipmentSlot) -> bool {
1487        self.as_mob().is_none_or(Mob::can_pick_up_loot)
1488    }
1489
1490    /// Returns vanilla `LivingEntity.canEquipWithDispenser`.
1491    fn can_equip_with_dispenser(&self, item_stack: &ItemStack) -> bool {
1492        if !Entity::is_alive(self) || self.is_spectator() {
1493            return false;
1494        }
1495
1496        let Some(equippable) = item_stack.get_equippable() else {
1497            return false;
1498        };
1499        if !equippable.dispensable {
1500            return false;
1501        }
1502
1503        let slot = equippable.slot;
1504        self.can_use_slot(slot)
1505            && equippable.can_be_equipped_by(self.entity_type())
1506            && !self.has_item_in_slot(slot)
1507            && self.can_dispenser_equip_into_slot(slot)
1508    }
1509
1510    /// Returns vanilla `LivingEntity.isEquippableInSlot`.
1511    fn is_equippable_in_slot(&self, item_stack: &ItemStack, slot: EquipmentSlot) -> bool {
1512        let Some(equippable) = item_stack.get_equippable() else {
1513            return slot == EquipmentSlot::MainHand && self.can_use_slot(EquipmentSlot::MainHand);
1514        };
1515
1516        slot == equippable.slot
1517            && self.can_use_slot(equippable.slot)
1518            && equippable.can_be_equipped_by(self.entity_type())
1519    }
1520
1521    /// Returns the equip sound Steel can currently resolve for this entity.
1522    fn equip_sound(&self, slot: EquipmentSlot, stack: &ItemStack) -> Option<SoundEventRef> {
1523        let equippable = stack.get_equippable()?;
1524        (slot == equippable.slot)
1525            .then(|| equippable.equip_sound.registry_ref())
1526            .flatten()
1527    }
1528
1529    /// Runs vanilla's equippable `ItemStack.interactLivingEntity` branch.
1530    fn interact_living_entity_with_equippable(
1531        &self,
1532        player: &Player,
1533        hand: InteractionHand,
1534    ) -> InteractionResult {
1535        let item_stack = {
1536            let inventory = player.inventory.lock();
1537            let item_stack = inventory.get_item_in_hand(hand);
1538            item_stack.copy_with_count(item_stack.count())
1539        };
1540        let Some(equippable) = item_stack.get_equippable() else {
1541            return InteractionResult::Pass;
1542        };
1543        if !equippable.equip_on_interact {
1544            return InteractionResult::Pass;
1545        }
1546
1547        let slot = equippable.slot;
1548        if !self.is_equippable_in_slot(&item_stack, slot) || !Entity::is_alive(self) {
1549            return InteractionResult::Pass;
1550        }
1551
1552        let equipped = {
1553            let mut equipment = self.living_base().equipment().lock();
1554            if !equipment.get_ref(slot).is_empty() {
1555                return InteractionResult::Pass;
1556            }
1557
1558            let mut inventory = player.inventory.lock();
1559            if !self.is_equippable_in_slot(inventory.get_item_in_hand(hand), slot) {
1560                return InteractionResult::Pass;
1561            }
1562
1563            let equipped = inventory.split_item_in_hand(hand, 1);
1564            if equipped.is_empty() {
1565                return InteractionResult::Pass;
1566            }
1567
1568            equipment.set(slot, equipped);
1569            equipment.get_ref(slot).copy_with_count(1)
1570        };
1571
1572        if let Some(sound) = self.equip_sound(slot, &equipped) {
1573            self.play_sound(sound, 1.0, 1.0);
1574        }
1575        if let Some(mob) = self.as_mob() {
1576            mob.set_guaranteed_drop(slot);
1577        }
1578        // TODO: Emit EQUIP game event once game-event dispatch is implemented.
1579        InteractionResult::Success
1580    }
1581
1582    /// Refreshes transient item attribute modifiers for one equipment slot.
1583    fn refresh_equipment_attribute_modifiers(&self, slot: EquipmentSlot) {
1584        self.with_equipment_slot(slot, &mut |item_stack| {
1585            self.living_base()
1586                .refresh_equipment_attribute_modifiers(slot, item_stack);
1587        });
1588    }
1589
1590    /// Detects and applies Vanilla living-equipment changes once per tick.
1591    fn detect_equipment_updates(&self) {
1592        let mut changes = self.living_base().collect_equipment_changes();
1593        if changes.is_empty() {
1594            return;
1595        }
1596
1597        for (slot, _, current) in &changes {
1598            self.living_base()
1599                .refresh_equipment_attribute_modifiers(*slot, current);
1600        }
1601
1602        let main_hand = changes
1603            .iter()
1604            .find(|(slot, _, _)| *slot == EquipmentSlot::MainHand);
1605        let offhand = changes
1606            .iter()
1607            .find(|(slot, _, _)| *slot == EquipmentSlot::OffHand);
1608        let hands_swapped = main_hand.zip(offhand).is_some_and(
1609            |((_, previous_main, current_main), (_, previous_off, current_off))| {
1610                ItemStack::matches(current_main, previous_off)
1611                    && ItemStack::matches(current_off, previous_main)
1612            },
1613        );
1614
1615        if hands_swapped {
1616            if let Some(world) = self.level() {
1617                world.broadcast_to_entity_trackers(
1618                    self.id(),
1619                    CEntityEvent {
1620                        entity_id: self.id(),
1621                        event: EntityStatus::SwapHands,
1622                    },
1623                    None,
1624                );
1625            }
1626            changes.retain(|(slot, _, _)| {
1627                !matches!(slot, EquipmentSlot::MainHand | EquipmentSlot::OffHand)
1628            });
1629        }
1630
1631        self.living_base().queue_equipment_changes(
1632            changes
1633                .into_iter()
1634                .map(|(slot, _, current)| (slot, current)),
1635        );
1636    }
1637
1638    /// Packs non-empty living equipment slots for initial spawn pairing.
1639    fn pack_living_equipment(&self) -> Vec<EquipmentSlotItem> {
1640        equipment_items_to_packet_items(self.living_base().equipment().lock().non_empty_items())
1641    }
1642
1643    /// Drains dirty living equipment slots for tracker sync.
1644    fn drain_dirty_living_equipment(&self) -> Vec<EquipmentSlotItem> {
1645        equipment_items_to_packet_items(self.living_base().drain_equipment_changes())
1646    }
1647
1648    /// Returns whether equipment durability should be skipped for this entity.
1649    fn has_infinite_materials(&self) -> bool {
1650        false
1651    }
1652
1653    /// Called after an equipped item breaks.
1654    fn on_equipped_item_broken(&self, slot: EquipmentSlot) {
1655        let event = match slot {
1656            EquipmentSlot::MainHand => EntityStatus::MainhandBreak,
1657            EquipmentSlot::OffHand => EntityStatus::OffhandBreak,
1658            EquipmentSlot::Head => EntityStatus::HeadBreak,
1659            EquipmentSlot::Chest => EntityStatus::ChestBreak,
1660            EquipmentSlot::Legs => EntityStatus::LegsBreak,
1661            EquipmentSlot::Feet => EntityStatus::FeetBreak,
1662            EquipmentSlot::Body => EntityStatus::BodyBreak,
1663            EquipmentSlot::Saddle => EntityStatus::SaddleBreak,
1664        };
1665        self.broadcast_entity_event(event);
1666        self.refresh_equipment_attribute_modifiers(slot);
1667    }
1668
1669    /// Returns vanilla `LivingEntity.canFreeze()` after concrete entity exemptions.
1670    ///
1671    /// Vanilla keeps the entity-type freeze immunity on `Entity` and the equipment
1672    /// immunity on `LivingEntity`. Steel keeps this helper separate so concrete
1673    /// `Entity::can_freeze` implementations can delegate without downcasting.
1674    fn default_living_can_freeze(&self) -> bool {
1675        for slot in EquipmentSlot::ALL {
1676            if !slot.is_armor() {
1677                continue;
1678            }
1679            let mut is_freeze_immune = false;
1680            self.with_equipment_slot(slot, &mut |item_stack| {
1681                is_freeze_immune = REGISTRY
1682                    .items
1683                    .is_in_tag(item_stack.item(), &ItemTag::FREEZE_IMMUNE_WEARABLES);
1684            });
1685
1686            if is_freeze_immune {
1687                return false;
1688            }
1689        }
1690
1691        self.default_can_freeze()
1692    }
1693
1694    /// Returns whether vanilla `tryAddFrost` sees a non-air block below.
1695    fn is_on_non_air_block_for_frost(&self) -> bool {
1696        let Some(world) = self.level() else {
1697            return false;
1698        };
1699        let Some(pos) = self.on_pos_legacy() else {
1700            return false;
1701        };
1702
1703        world.get_block_state(pos).get_block() != &vanilla_blocks::AIR
1704    }
1705
1706    /// Mirrors vanilla `LivingEntity.removeFrost`.
1707    fn remove_frost(&self) {
1708        self.attributes().lock().remove_modifier(
1709            vanilla_attributes::MOVEMENT_SPEED,
1710            &SPEED_MODIFIER_POWDER_SNOW_ID,
1711        );
1712    }
1713
1714    /// Mirrors vanilla `LivingEntity.tryAddFrost`.
1715    fn try_add_frost(&self) {
1716        if !self.is_on_non_air_block_for_frost() || self.ticks_frozen() <= 0 {
1717            return;
1718        }
1719
1720        self.attributes().lock().add_modifier(
1721            vanilla_attributes::MOVEMENT_SPEED,
1722            AttributeModifier {
1723                id: SPEED_MODIFIER_POWDER_SNOW_ID,
1724                amount: f64::from(-0.05_f32 * self.percent_frozen()),
1725                operation: AttributeModifierOperation::AddValue,
1726            },
1727            false,
1728        );
1729    }
1730
1731    /// Ticks vanilla `LivingEntity.aiStep` freezing effects.
1732    fn tick_freezing(&self) {
1733        if !self.is_in_powder_snow() || !self.can_freeze() {
1734            self.set_ticks_frozen((self.ticks_frozen() - 2).max(0));
1735        }
1736
1737        self.remove_frost();
1738        self.try_add_frost();
1739        if self.tick_count() % 40 == 0
1740            && self.is_fully_frozen()
1741            && self.can_freeze()
1742            && let Some(world) = self.level()
1743        {
1744            self.hurt(
1745                &world,
1746                &DamageSource::environment(&vanilla_damage_types::FREEZE),
1747                1.0,
1748            );
1749        }
1750    }
1751
1752    /// Runs vanilla `LivingEntity.tick`.
1753    ///
1754    /// The default `Entity::tick` dispatches living entities here.
1755    fn tick_living_entity(&self) {
1756        self.default_tick();
1757        self.living_base().decrement_invulnerable_time();
1758        self.tick_mob_effects();
1759        self.detect_equipment_updates();
1760
1761        if self.is_dead_or_dying() {
1762            self.tick_death();
1763            self.tick_living_state();
1764            return;
1765        }
1766
1767        if !self.is_removed() {
1768            self.ai_step();
1769        }
1770
1771        self.tick_living_state();
1772    }
1773
1774    /// Ticks living-entity counters after movement.
1775    fn tick_living_state(&self) {
1776        if let Some(mob) = self.as_mob() {
1777            mob.tick_body_rotation_control();
1778        }
1779        self.living_base()
1780            .tick_fall_flying_state(self.is_fall_flying());
1781        self.update_swing_time();
1782        self.refresh_dirty_attributes();
1783        self.living_base().tick_post_impulse_grace_time();
1784        self.living_base().tick_last_hurt_by_player_memory();
1785        self.living_base()
1786            .tick_living_combat_memory(self.tick_count());
1787    }
1788
1789    /// Mirrors vanilla `LivingEntity.canGlideUsing()`.
1790    fn can_glide_using(&self, item_stack: &ItemStack, slot: EquipmentSlot) -> bool {
1791        let Some(equippable) = item_stack.get_equippable() else {
1792            return false;
1793        };
1794
1795        item_stack.has(GLIDER) && equippable.slot == slot && !item_stack.next_damage_will_break()
1796    }
1797
1798    /// Returns whether the item in `slot` can be used for vanilla gliding.
1799    fn can_glide_using_equipment_slot(&self, slot: EquipmentSlot) -> bool {
1800        let mut can_glide = false;
1801        self.with_equipment_slot(slot, &mut |item_stack| {
1802            can_glide = self.can_glide_using(item_stack, slot);
1803        });
1804        can_glide
1805    }
1806
1807    /// Damages one random equipped glider like vanilla `LivingEntity.updateFallFlying()`.
1808    fn damage_random_glider(&self) {
1809        let mut slots_with_gliders = Vec::new();
1810        for slot in EquipmentSlot::ALL {
1811            if self.can_glide_using_equipment_slot(slot) {
1812                slots_with_gliders.push(slot);
1813            }
1814        }
1815
1816        let slot_count = slots_with_gliders.len();
1817        if slot_count == 0 {
1818            return;
1819        }
1820
1821        let slot_index = rand::random_range(0..slot_count);
1822        let slot_to_damage = slots_with_gliders[slot_index];
1823        let has_infinite_materials = self.has_infinite_materials();
1824        let mut item_broke = false;
1825        self.with_equipment_slot_mut(slot_to_damage, &mut |item_stack| {
1826            item_broke = item_stack.hurt_and_break(1, has_infinite_materials);
1827        });
1828        if item_broke {
1829            self.on_equipped_item_broken(slot_to_damage);
1830        }
1831    }
1832
1833    /// Default vanilla `LivingEntity.canGlide()` implementation for overrides.
1834    fn default_can_glide(&self) -> bool {
1835        !self.on_ground()
1836            && !self.is_passenger()
1837            && !self.has_mob_effect(vanilla_mob_effects::LEVITATION)
1838            && EquipmentSlot::ALL
1839                .iter()
1840                .any(|&slot| self.can_glide_using_equipment_slot(slot))
1841    }
1842
1843    /// Mirrors vanilla `LivingEntity.canGlide()`.
1844    fn can_glide(&self) -> bool {
1845        self.default_can_glide()
1846    }
1847
1848    /// Mirrors vanilla `Player.startFallFlying()`.
1849    fn start_fall_flying(&self) {
1850        self.set_fall_flying(true);
1851    }
1852
1853    /// Mirrors vanilla `Player.tryToStartFallFlying()`.
1854    fn try_to_start_fall_flying(&self) -> bool {
1855        if !self.is_fall_flying() && self.can_glide() && !self.is_in_water() {
1856            self.start_fall_flying();
1857            return true;
1858        }
1859
1860        false
1861    }
1862
1863    /// Returns the last climbable block position this living entity touched.
1864    fn last_climbable_pos(&self) -> Option<BlockPos> {
1865        self.living_base().last_climbable_pos()
1866    }
1867
1868    /// Records the last climbable block position this living entity touched.
1869    fn set_last_climbable_pos(&self, pos: BlockPos) {
1870        self.living_base().set_last_climbable_pos(pos);
1871    }
1872
1873    /// Returns vanilla `LivingEntity.onClimbable()` behavior.
1874    fn default_living_on_climbable(&self) -> bool {
1875        if self.is_spectator() {
1876            return false;
1877        }
1878
1879        let pos = self.block_position();
1880        let Some(world) = self.level() else {
1881            return false;
1882        };
1883        let state = world.get_block_state(pos);
1884        let block = state.get_block();
1885
1886        if self.is_fall_flying() && block.has_tag(&BlockTag::CAN_GLIDE_THROUGH) {
1887            return false;
1888        }
1889
1890        let climbable = block.has_tag(&BlockTag::CLIMBABLE)
1891            || block.has_tag(&BlockTag::TRAPDOORS)
1892                && trapdoor_usable_as_ladder_state(state, world.get_block_state(pos.below()));
1893
1894        if climbable {
1895            self.set_last_climbable_pos(pos);
1896        }
1897
1898        climbable
1899    }
1900
1901    /// Returns whether vanilla living travel should skip friction damping.
1902    fn should_discard_friction(&self) -> bool {
1903        self.living_base().should_discard_friction()
1904    }
1905
1906    /// Sets whether vanilla living travel should skip friction damping.
1907    fn set_discard_friction(&self, discard_friction: bool) {
1908        self.living_base().set_discard_friction(discard_friction);
1909    }
1910
1911    /// Returns whether this living entity is currently applying jump input.
1912    fn is_jumping(&self) -> bool {
1913        self.living_base().is_jumping()
1914    }
1915
1916    /// Sets whether this living entity is currently applying jump input.
1917    fn set_jumping(&self, jumping: bool) {
1918        self.living_base().set_jumping(jumping);
1919    }
1920
1921    /// Returns vanilla living travel input.
1922    fn travel_input(&self) -> LivingTravelInput {
1923        self.living_base().travel_input()
1924    }
1925
1926    /// Sets vanilla living travel input.
1927    fn set_travel_input(&self, input: LivingTravelInput) {
1928        self.living_base().set_travel_input(input);
1929    }
1930
1931    /// Applies vanilla `LivingEntity.applyInput()` damping.
1932    fn apply_input(&self) {
1933        self.living_base().dampen_travel_input();
1934    }
1935
1936    /// Returns vanilla jump cooldown ticks.
1937    fn no_jump_delay(&self) -> i32 {
1938        self.living_base().no_jump_delay()
1939    }
1940
1941    /// Sets vanilla jump cooldown ticks.
1942    fn set_no_jump_delay(&self, ticks: i32) {
1943        self.living_base().set_no_jump_delay(ticks);
1944    }
1945
1946    /// Decrements vanilla jump cooldown once per living AI step.
1947    fn tick_no_jump_delay(&self) {
1948        self.living_base().tick_no_jump_delay();
1949    }
1950
1951    /// Returns vanilla `LivingEntity.isImmobile()`.
1952    fn default_is_immobile(&self) -> bool {
1953        self.is_dead_or_dying()
1954    }
1955
1956    /// Returns vanilla `LivingEntity.isImmobile()`.
1957    fn is_immobile(&self) -> bool {
1958        self.default_is_immobile()
1959    }
1960
1961    /// Applies vanilla `LivingEntity.aiStep()` velocity thresholds.
1962    fn apply_living_velocity_thresholds(&self) {
1963        let movement = self.velocity();
1964        let mut dx = movement.x;
1965        let mut dy = movement.y;
1966        let mut dz = movement.z;
1967
1968        if self.entity_type() == &vanilla_entities::PLAYER {
1969            if movement.x.mul_add(movement.x, movement.z * movement.z) < 9.0E-6 {
1970                dx = 0.0;
1971                dz = 0.0;
1972            }
1973        } else {
1974            if movement.x.abs() < 0.003 {
1975                dx = 0.0;
1976            }
1977            if movement.z.abs() < 0.003 {
1978                dz = 0.0;
1979            }
1980        }
1981
1982        if movement.y.abs() < 0.003 {
1983            dy = 0.0;
1984        }
1985
1986        self.set_velocity(DVec3::new(dx, dy, dz));
1987    }
1988
1989    /// Server AI hook called from vanilla `LivingEntity.aiStep()`.
1990    fn server_ai_step(&self) {}
1991
1992    /// Returns vanilla `LivingEntity.getJumpBoostPower()`.
1993    fn get_jump_boost_power(&self) -> f32 {
1994        self.mob_effect(vanilla_mob_effects::JUMP_BOOST)
1995            .map_or(0.0, |effect| 0.1 * (effect.amplifier() as f32 + 1.0))
1996    }
1997
1998    /// Returns vanilla `LivingEntity.getJumpPower(float)`.
1999    fn get_jump_power_with_multiplier(&self, multiplier: f32) -> f32 {
2000        let jump_strength =
2001            self.attributes()
2002                .lock()
2003                .get_value(vanilla_attributes::JUMP_STRENGTH)
2004                .unwrap_or(vanilla_attributes::JUMP_STRENGTH.default_value) as f32;
2005        jump_strength * multiplier * self.block_jump_factor() + self.get_jump_boost_power()
2006    }
2007
2008    /// Returns vanilla `LivingEntity.getJumpPower()`.
2009    fn get_jump_power(&self) -> f32 {
2010        self.get_jump_power_with_multiplier(1.0)
2011    }
2012
2013    /// Default vanilla `LivingEntity.jumpFromGround()` implementation for overrides.
2014    fn default_jump_from_ground(&self) {
2015        let jump_power = self.get_jump_power();
2016        if jump_power <= 1.0E-5 {
2017            return;
2018        }
2019
2020        let movement = self.velocity();
2021        self.set_velocity(DVec3::new(
2022            movement.x,
2023            movement.y.max(f64::from(jump_power)),
2024            movement.z,
2025        ));
2026        if self.is_sprinting() {
2027            let angle = self.rotation().0.to_radians();
2028            self.set_velocity(
2029                self.velocity()
2030                    + DVec3::new(
2031                        f64::from(-angle.sin() * 0.2),
2032                        0.0,
2033                        f64::from(angle.cos() * 0.2),
2034                    ),
2035            );
2036        }
2037
2038        self.mark_velocity_sync();
2039    }
2040
2041    /// Mirrors vanilla `LivingEntity.jumpFromGround()`.
2042    fn jump_from_ground(&self) {
2043        self.default_jump_from_ground();
2044    }
2045
2046    /// Mirrors vanilla `LivingEntity.goDownInWater()`.
2047    fn go_down_in_water(&self) {
2048        self.set_velocity(self.velocity() + DVec3::new(0.0, f64::from(-0.04_f32), 0.0));
2049    }
2050
2051    /// Mirrors vanilla `LivingEntity.jumpInLiquid()`.
2052    fn jump_in_liquid(&self, _fluid_tag: &Identifier) {
2053        self.set_velocity(self.velocity() + DVec3::new(0.0, f64::from(0.04_f32), 0.0));
2054    }
2055
2056    /// Applies vanilla `LivingEntity.aiStep()` jump handling.
2057    fn handle_living_jump(&self) {
2058        if !self.is_jumping() || !self.is_affected_by_fluids() {
2059            self.set_no_jump_delay(0);
2060            return;
2061        }
2062
2063        let fluid_height = if self.is_in_lava() {
2064            self.fluid_contact().lava_height()
2065        } else {
2066            self.fluid_contact().water_height()
2067        };
2068        let in_water_and_has_fluid_height = self.is_in_water() && fluid_height > 0.0;
2069        let fluid_jump_threshold = self.get_fluid_jump_threshold();
2070        if !in_water_and_has_fluid_height
2071            || self.on_ground() && fluid_height <= fluid_jump_threshold
2072        {
2073            if !self.is_in_lava() || self.on_ground() && fluid_height <= fluid_jump_threshold {
2074                if (self.on_ground()
2075                    || in_water_and_has_fluid_height && fluid_height <= fluid_jump_threshold)
2076                    && self.no_jump_delay() == 0
2077                {
2078                    self.jump_from_ground();
2079                    self.set_no_jump_delay(10);
2080                }
2081            } else {
2082                self.jump_in_liquid(&vanilla_fluid_tags::FluidTag::LAVA);
2083            }
2084        } else {
2085            self.jump_in_liquid(&vanilla_fluid_tags::FluidTag::WATER);
2086        }
2087    }
2088
2089    /// Mirrors vanilla `LivingEntity.tickRidden()`.
2090    fn tick_ridden(&self, _controller: &Player, _ridden_input: DVec3) {}
2091
2092    /// Mirrors vanilla `LivingEntity.getRiddenInput()`.
2093    fn ridden_input(&self, _controller: &Player, self_input: DVec3) -> DVec3 {
2094        self_input
2095    }
2096
2097    /// Mirrors vanilla `LivingEntity.getRiddenSpeed()`.
2098    fn ridden_speed(&self, _controller: &Player) -> f32 {
2099        self.get_speed()
2100    }
2101
2102    /// Mirrors vanilla `LivingEntity.travelRidden()`.
2103    fn travel_ridden(&self, controller: &Player, self_input: DVec3) -> Option<MoveResult> {
2104        let ridden_input = self.ridden_input(controller, self_input);
2105        self.tick_ridden(controller, ridden_input);
2106        if self.can_simulate_movement() {
2107            self.set_speed(self.ridden_speed(controller));
2108            return self.travel(ridden_input);
2109        }
2110
2111        self.set_velocity(DVec3::ZERO);
2112        None
2113    }
2114
2115    /// Default vanilla-shaped `LivingEntity.aiStep()` movement foundation for overrides.
2116    ///
2117    /// This covers the shared travel state Steel currently has; mob AI and
2118    /// equipment ticking are still separate follow-up work.
2119    fn default_ai_step(&self) -> Option<MoveResult> {
2120        self.tick_no_jump_delay();
2121        if !self.can_simulate_movement() {
2122            self.set_velocity(self.velocity() * 0.98);
2123        }
2124
2125        self.apply_living_velocity_thresholds();
2126        self.apply_input();
2127        if self.is_immobile() {
2128            self.set_jumping(false);
2129            let input = self.travel_input();
2130            self.set_travel_input(LivingTravelInput::new(0.0, input.vertical(), 0.0));
2131        } else if self.is_effective_ai() {
2132            self.server_ai_step();
2133        }
2134
2135        self.handle_living_jump();
2136
2137        if self.is_fall_flying() {
2138            self.update_fall_flying();
2139        }
2140
2141        if self.has_mob_effect(vanilla_mob_effects::SLOW_FALLING)
2142            || self.has_mob_effect(vanilla_mob_effects::LEVITATION)
2143        {
2144            self.reset_fall_distance();
2145        }
2146
2147        let input = self.travel_input();
2148        let input = DVec3::new(
2149            f64::from(input.sideways()),
2150            f64::from(input.vertical()),
2151            f64::from(input.forward()),
2152        );
2153        let result = if Entity::is_alive(self)
2154            && let Some(controller_entity) = self.controlling_passenger()
2155            && let Some(controller) = controller_entity.as_player()
2156        {
2157            self.travel_ridden(controller, input)
2158        } else if self.can_simulate_movement() && self.is_effective_ai() {
2159            self.travel(input)
2160        } else {
2161            None
2162        };
2163
2164        self.apply_effects_from_blocks();
2165        self.tick_freezing();
2166        self.push_entities();
2167        result
2168    }
2169
2170    /// Mirrors vanilla `LivingEntity.aiStep()`.
2171    fn ai_step(&self) -> Option<MoveResult> {
2172        self.default_ai_step()
2173    }
2174
2175    /// Mirrors vanilla `LivingEntity.pushEntities()`.
2176    fn push_entities(&self) {
2177        let Some(world) = self.level() else {
2178            return;
2179        };
2180        if !world.tick_runs_normally() {
2181            return;
2182        }
2183
2184        let pusher = self.as_entity_event_source();
2185        let pushable_entities = world.get_pushable_entities(pusher, &self.bounding_box());
2186        if pushable_entities.is_empty() {
2187            return;
2188        }
2189
2190        self.apply_entity_cramming_damage(&world, &pushable_entities);
2191
2192        for entity in pushable_entities {
2193            entity.push_entity(pusher);
2194        }
2195    }
2196
2197    /// Applies vanilla max entity cramming damage from `LivingEntity.pushEntities()`.
2198    fn apply_entity_cramming_damage(&self, world: &World, pushable_entities: &[SharedEntity]) {
2199        let max_cramming = world.get_game_rule(&MAX_ENTITY_CRAMMING);
2200
2201        if max_cramming <= 0 || pushable_entities.len() <= (max_cramming - 1) as usize {
2202            return;
2203        }
2204
2205        let random_roll = rand::random_range(0..4);
2206        let non_passenger_count = pushable_entities
2207            .iter()
2208            .filter(|entity| !entity.is_passenger())
2209            .count();
2210
2211        if should_apply_entity_cramming_damage(
2212            max_cramming,
2213            pushable_entities.len(),
2214            non_passenger_count,
2215            random_roll,
2216        ) {
2217            self.hurt(
2218                world,
2219                &DamageSource::environment(&vanilla_damage_types::CRAMMING),
2220                6.0,
2221            );
2222        }
2223    }
2224
2225    /// Returns vanilla `LivingEntity.isSuppressingSlidingDownLadder()`.
2226    fn is_suppressing_sliding_down_ladder(&self) -> bool {
2227        self.is_suppressing_bounce()
2228    }
2229
2230    /// Returns a levitation velocity adjustment for `travelInAir`.
2231    fn levitation_travel_y_delta(&self, movement_y: f64) -> Option<f64> {
2232        self.mob_effect(vanilla_mob_effects::LEVITATION)
2233            .map(|effect| (0.05 * f64::from(effect.amplifier() + 1) - movement_y) * 0.2)
2234    }
2235
2236    /// Returns whether vanilla `LivingEntity.travel()` should use fluid movement.
2237    fn should_travel_in_fluid(&self, fluid_state: FluidState) -> bool {
2238        (self.is_in_water() || self.is_in_lava())
2239            && self.is_affected_by_fluids()
2240            && !self.can_stand_on_fluid(fluid_state)
2241    }
2242
2243    /// Returns vanilla `LivingEntity.getWaterSlowDown()`.
2244    fn get_water_slow_down(&self) -> f32 {
2245        0.8
2246    }
2247
2248    /// Returns the water movement efficiency attribute used by fluid travel.
2249    fn water_movement_efficiency(&self) -> f32 {
2250        self.attributes()
2251            .lock()
2252            .get_value(vanilla_attributes::WATER_MOVEMENT_EFFICIENCY)
2253            .unwrap_or(0.0) as f32
2254    }
2255
2256    /// Returns whether dolphin's grace should apply to water travel.
2257    fn has_dolphins_grace(&self) -> bool {
2258        self.has_mob_effect(vanilla_mob_effects::DOLPHINS_GRACE)
2259    }
2260
2261    /// Returns vanilla `LivingEntity.getFlyingSpeed()`.
2262    fn get_flying_speed(&self) -> f32 {
2263        if self
2264            .controlling_passenger()
2265            .is_some_and(|passenger| passenger.entity_type() == &vanilla_entities::PLAYER)
2266        {
2267            self.get_speed() * 0.1
2268        } else {
2269            0.02
2270        }
2271    }
2272
2273    /// Returns vanilla `LivingEntity.getFrictionInfluencedSpeed()`.
2274    fn get_friction_influenced_speed(&self, block_friction: f32) -> f32 {
2275        if self.on_ground() {
2276            self.get_speed() * (0.216_000_02 / (block_friction * block_friction * block_friction))
2277        } else {
2278            self.get_flying_speed()
2279        }
2280    }
2281
2282    /// Returns the vertical friction used by `travelInAir`.
2283    fn air_travel_vertical_friction(&self, _horizontal_friction: f32) -> f32 {
2284        // TODO: FlyingAnimal uses horizontal friction here once animal types exist.
2285        0.98
2286    }
2287
2288    /// Applies vanilla `LivingEntity.handleOnClimbable()`.
2289    fn handle_on_climbable(&self, movement: DVec3) -> DVec3 {
2290        if !self.on_climbable() {
2291            return movement;
2292        }
2293
2294        self.reset_fall_distance();
2295        let Some(world) = self.level() else {
2296            return movement;
2297        };
2298        let block_state = self.in_block_state(&world);
2299        let mut y = movement.y.max(-0.15);
2300        if y < 0.0
2301            && block_state.get_block() != &vanilla_blocks::SCAFFOLDING
2302            && self.is_suppressing_sliding_down_ladder()
2303            && self.entity_type() == &vanilla_entities::PLAYER
2304        {
2305            y = 0.0;
2306        }
2307
2308        DVec3::new(
2309            movement.x.clamp(-0.15, 0.15),
2310            y,
2311            movement.z.clamp(-0.15, 0.15),
2312        )
2313    }
2314
2315    /// Applies gravity using vanilla living-entity effective gravity.
2316    fn apply_living_travel_gravity(&self) {
2317        let gravity = self.get_effective_gravity();
2318        if gravity != 0.0 {
2319            let mut velocity = self.velocity();
2320            velocity.y -= gravity;
2321            self.set_velocity(velocity);
2322        }
2323    }
2324
2325    /// Mirrors vanilla `LivingEntity.handleRelativeFrictionAndCalculateMovement()`.
2326    fn handle_relative_friction_and_calculate_movement(
2327        &self,
2328        input: DVec3,
2329        block_friction: f32,
2330    ) -> Option<(DVec3, MoveResult)> {
2331        self.move_relative(self.get_friction_influenced_speed(block_friction), input);
2332        self.set_velocity(self.handle_on_climbable(self.velocity()));
2333        let result = self.move_entity(MoverType::SelfMovement, self.velocity())?;
2334        let mut movement = self.velocity();
2335        if (result.horizontal_collision || self.is_jumping())
2336            && (self.on_climbable()
2337                || self.was_in_powder_snow()
2338                    && PowderSnowBlock::can_entity_walk_on_powder_snow(self))
2339        {
2340            movement.y = 0.2;
2341        }
2342
2343        Some((movement, result))
2344    }
2345
2346    /// Mirrors vanilla `LivingEntity.travelInAir()`.
2347    fn travel_in_air(&self, input: DVec3) -> Option<MoveResult> {
2348        let world = self.level()?;
2349        let pos_below = self.block_pos_below_that_affects_movement()?;
2350        let block_friction = if self.on_ground() {
2351            world.get_block_state(pos_below).get_block().config.friction
2352        } else {
2353            1.0
2354        };
2355        let horizontal_friction = block_friction * 0.91;
2356        let (movement, result) =
2357            self.handle_relative_friction_and_calculate_movement(input, block_friction)?;
2358        let movement_y = if let Some(levitation_y) = self.levitation_travel_y_delta(movement.y) {
2359            movement.y + levitation_y
2360        } else {
2361            movement.y - self.get_effective_gravity()
2362        };
2363
2364        if self.should_discard_friction() {
2365            self.set_velocity(DVec3::new(movement.x, movement_y, movement.z));
2366        } else {
2367            let vertical_friction = self.air_travel_vertical_friction(horizontal_friction);
2368            self.set_velocity(DVec3::new(
2369                movement.x * f64::from(horizontal_friction),
2370                movement_y * f64::from(vertical_friction),
2371                movement.z * f64::from(horizontal_friction),
2372            ));
2373        }
2374
2375        Some(result)
2376    }
2377
2378    /// Mirrors vanilla `LivingEntity.getFluidFallingAdjustedMovement()`.
2379    fn get_fluid_falling_adjusted_movement(
2380        &self,
2381        base_gravity: f64,
2382        is_falling: bool,
2383        movement: DVec3,
2384    ) -> DVec3 {
2385        if base_gravity == 0.0 || self.is_sprinting() {
2386            return movement;
2387        }
2388
2389        let y = if is_falling
2390            && (movement.y - 0.005).abs() >= 0.003
2391            && (movement.y - base_gravity / 16.0).abs() < 0.003
2392        {
2393            -0.003
2394        } else {
2395            movement.y - base_gravity / 16.0
2396        };
2397
2398        DVec3::new(movement.x, y, movement.z)
2399    }
2400
2401    /// Mirrors vanilla `LivingEntity.jumpOutOfFluid()`.
2402    fn jump_out_of_fluid(&self, old_y: f64) {
2403        if !self.horizontal_collision() {
2404            return;
2405        }
2406
2407        let movement = self.velocity();
2408        let target_delta = DVec3::new(
2409            movement.x,
2410            movement.y + f64::from(0.6_f32) - self.position().y + old_y,
2411            movement.z,
2412        );
2413        if self.is_free(target_delta) {
2414            self.set_velocity(DVec3::new(movement.x, f64::from(0.3_f32), movement.z));
2415        }
2416    }
2417
2418    /// Mirrors vanilla `LivingEntity.floatInWaterWhileRidden()`.
2419    fn float_in_water_while_ridden(&self) {
2420        if !REGISTRY
2421            .entity_types
2422            .is_in_tag(self.entity_type(), &EntityTypeTag::CAN_FLOAT_WHILE_RIDDEN)
2423        {
2424            return;
2425        }
2426        if !self.is_vehicle()
2427            || self.fluid_contact().water_height() <= self.get_fluid_jump_threshold()
2428        {
2429            return;
2430        }
2431
2432        self.set_velocity(self.velocity() + DVec3::new(0.0, f64::from(0.04_f32), 0.0));
2433    }
2434
2435    /// Mirrors vanilla `LivingEntity.travelInWater()`.
2436    fn travel_in_water(
2437        &self,
2438        input: DVec3,
2439        base_gravity: f64,
2440        is_falling: bool,
2441        old_y: f64,
2442    ) -> Option<MoveResult> {
2443        let mut slow_down = if self.is_sprinting() {
2444            0.9
2445        } else {
2446            self.get_water_slow_down()
2447        };
2448        let mut speed = 0.02;
2449        let mut water_movement_efficiency = self.water_movement_efficiency();
2450        if !self.on_ground() {
2451            water_movement_efficiency *= 0.5;
2452        }
2453
2454        if water_movement_efficiency > 0.0 {
2455            slow_down += (0.546_000_06 - slow_down) * water_movement_efficiency;
2456            speed += (self.get_speed() - speed) * water_movement_efficiency;
2457        }
2458
2459        if self.has_dolphins_grace() {
2460            slow_down = 0.96;
2461        }
2462
2463        self.move_relative(speed, input);
2464        let result = self.move_entity(MoverType::SelfMovement, self.velocity())?;
2465        let mut movement = self.velocity();
2466        if result.horizontal_collision && self.on_climbable() {
2467            movement.y = 0.2;
2468        }
2469
2470        movement = DVec3::new(
2471            movement.x * f64::from(slow_down),
2472            movement.y * f64::from(0.8_f32),
2473            movement.z * f64::from(slow_down),
2474        );
2475        self.set_velocity(self.get_fluid_falling_adjusted_movement(
2476            base_gravity,
2477            is_falling,
2478            movement,
2479        ));
2480        self.jump_out_of_fluid(old_y);
2481
2482        Some(result)
2483    }
2484
2485    /// Mirrors vanilla `LivingEntity.travelInLava()`.
2486    fn travel_in_lava(
2487        &self,
2488        input: DVec3,
2489        base_gravity: f64,
2490        is_falling: bool,
2491        old_y: f64,
2492    ) -> Option<MoveResult> {
2493        self.move_relative(0.02, input);
2494        let result = self.move_entity(MoverType::SelfMovement, self.velocity())?;
2495        if self.fluid_contact().lava_height() <= self.get_fluid_jump_threshold() {
2496            let movement = self.velocity();
2497            self.set_velocity(DVec3::new(
2498                movement.x * 0.5,
2499                movement.y * f64::from(0.8_f32),
2500                movement.z * 0.5,
2501            ));
2502            self.set_velocity(self.get_fluid_falling_adjusted_movement(
2503                base_gravity,
2504                is_falling,
2505                self.velocity(),
2506            ));
2507        } else {
2508            self.set_velocity(self.velocity() * 0.5);
2509        }
2510
2511        if base_gravity != 0.0 {
2512            self.set_velocity(self.velocity() + DVec3::new(0.0, -base_gravity / 4.0, 0.0));
2513        }
2514
2515        self.jump_out_of_fluid(old_y);
2516
2517        Some(result)
2518    }
2519
2520    /// Mirrors vanilla `LivingEntity.travelInFluid()`.
2521    fn travel_in_fluid(&self, input: DVec3) -> Option<MoveResult> {
2522        let is_falling = self.velocity().y <= 0.0;
2523        let old_y = self.position().y;
2524        let base_gravity = self.get_effective_gravity();
2525        if self.is_in_water() {
2526            let result = self.travel_in_water(input, base_gravity, is_falling, old_y);
2527            self.float_in_water_while_ridden();
2528            return result;
2529        }
2530
2531        self.travel_in_lava(input, base_gravity, is_falling, old_y)
2532    }
2533
2534    /// Mirrors the validation part of vanilla `LivingEntity.updateFallFlying()`.
2535    fn update_fall_flying(&self) {
2536        self.check_fall_distance_accumulation();
2537        if self.can_glide() {
2538            if let Some(free_fall_interval) =
2539                fall_flying_free_fall_interval(self.fall_flying_ticks())
2540            {
2541                if free_fall_interval % 2 == 0 {
2542                    self.damage_random_glider();
2543                }
2544                if let Some(world) = self.level() {
2545                    world.game_event_at(
2546                        &vanilla_game_events::ELYTRA_GLIDE,
2547                        self.position(),
2548                        &GameEventContext::new(Some(self.as_entity_event_source()), None),
2549                    );
2550                }
2551            }
2552        } else {
2553            self.set_fall_flying(false);
2554        }
2555    }
2556
2557    /// Mirrors vanilla `LivingEntity.updateFallFlyingMovement()`.
2558    fn update_fall_flying_movement(&self, mut movement: DVec3) -> DVec3 {
2559        let look_angle = self.look_angle();
2560        let pitch_radians = self.rotation().1.to_radians();
2561        let look_horizontal_length = horizontal_distance(look_angle);
2562        let move_horizontal_length = horizontal_distance(movement);
2563        let gravity = self.get_effective_gravity();
2564        let lift_force = f64::from(pitch_radians).cos().powi(2);
2565        movement.y += gravity * (-1.0 + lift_force * 0.75);
2566
2567        if movement.y < 0.0 && look_horizontal_length > 0.0 {
2568            let convert = movement.y * -0.1 * lift_force;
2569            movement += DVec3::new(
2570                look_angle.x * convert / look_horizontal_length,
2571                convert,
2572                look_angle.z * convert / look_horizontal_length,
2573            );
2574        }
2575
2576        if pitch_radians < 0.0 && look_horizontal_length > 0.0 {
2577            let convert = move_horizontal_length * -f64::from(pitch_radians.sin()) * 0.04;
2578            movement += DVec3::new(
2579                -look_angle.x * convert / look_horizontal_length,
2580                convert * 3.2,
2581                -look_angle.z * convert / look_horizontal_length,
2582            );
2583        }
2584
2585        if look_horizontal_length > 0.0 {
2586            movement += DVec3::new(
2587                (look_angle.x / look_horizontal_length * move_horizontal_length - movement.x) * 0.1,
2588                0.0,
2589                (look_angle.z / look_horizontal_length * move_horizontal_length - movement.z) * 0.1,
2590            );
2591        }
2592
2593        DVec3::new(
2594            movement.x * f64::from(0.99_f32),
2595            movement.y * f64::from(0.98_f32),
2596            movement.z * f64::from(0.99_f32),
2597        )
2598    }
2599
2600    /// Mirrors vanilla `LivingEntity.stopFallFlying()`.
2601    fn stop_fall_flying(&self) {
2602        self.set_fall_flying(true);
2603        self.set_fall_flying(false);
2604    }
2605
2606    /// Mirrors vanilla `LivingEntity.handleFallFlyingCollisions()`.
2607    fn handle_fall_flying_collisions(
2608        &self,
2609        previous_horizontal_speed: f64,
2610        new_horizontal_speed: f64,
2611    ) {
2612        if !self.horizontal_collision() {
2613            return;
2614        }
2615
2616        let damage = fall_flying_collision_damage(previous_horizontal_speed, new_horizontal_speed);
2617        if damage <= 0.0 {
2618            return;
2619        }
2620
2621        self.play_sound(self.fall_damage_sound(damage as i32), 1.0, 1.0);
2622        if let Some(world) = self.level() {
2623            self.hurt(
2624                &world,
2625                &DamageSource::environment(&vanilla_damage_types::FLY_INTO_WALL),
2626                damage,
2627            );
2628        }
2629    }
2630
2631    /// Mirrors vanilla `LivingEntity.travelFallFlying()`.
2632    fn travel_fall_flying(&self, input: DVec3) -> Option<MoveResult> {
2633        if self.on_climbable() {
2634            let result = self.travel_in_air(input);
2635            self.stop_fall_flying();
2636            return result;
2637        }
2638
2639        let previous_movement = self.velocity();
2640        let previous_horizontal_speed = horizontal_distance(previous_movement);
2641        self.set_velocity(self.update_fall_flying_movement(previous_movement));
2642        let result = self.move_entity(MoverType::SelfMovement, self.velocity());
2643        let new_horizontal_speed = horizontal_distance(self.velocity());
2644        self.handle_fall_flying_collisions(previous_horizontal_speed, new_horizontal_speed);
2645        result
2646    }
2647
2648    /// Default vanilla `LivingEntity.travel()` implementation for overrides.
2649    fn default_travel(&self, input: DVec3) -> Option<MoveResult> {
2650        let world = self.level()?;
2651        let fluid_state = get_fluid_state(&world, self.block_position());
2652        if self.should_travel_in_fluid(fluid_state) {
2653            return self.travel_in_fluid(input);
2654        }
2655        if self.is_fall_flying() {
2656            return self.travel_fall_flying(input);
2657        }
2658
2659        self.travel_in_air(input)
2660    }
2661
2662    /// Mirrors vanilla `LivingEntity.travel()`.
2663    fn travel(&self, input: DVec3) -> Option<MoveResult> {
2664        self.default_travel(input)
2665    }
2666
2667    /// Returns the bed position that makes this living entity sleeping.
2668    fn sleeping_pos(&self) -> Option<BlockPos> {
2669        self.living_base().sleeping_pos()
2670    }
2671
2672    /// Sets the vanilla living-entity sleeping position.
2673    fn set_sleeping_pos(&self, bed_position: BlockPos) {
2674        self.living_base().set_sleeping_pos(bed_position);
2675    }
2676
2677    /// Clears the vanilla living-entity sleeping position.
2678    fn clear_sleeping_pos(&self) {
2679        self.living_base().clear_sleeping_pos();
2680    }
2681
2682    /// Checks if the entity is sleeping.
2683    fn is_sleeping(&self) -> bool {
2684        self.sleeping_pos().is_some()
2685    }
2686
2687    /// Stops the entity from sleeping.
2688    fn stop_sleeping(&self) {
2689        self.clear_sleeping_pos();
2690    }
2691
2692    /// Checks if the entity is sprinting.
2693    fn is_sprinting(&self) -> bool {
2694        self.living_base().is_sprinting()
2695    }
2696
2697    /// Sets whether the entity is sprinting.
2698    fn set_sprinting(&self, sprinting: bool) {
2699        self.set_shared_sprinting(sprinting);
2700        self.living_base().set_sprinting(sprinting);
2701    }
2702
2703    /// Gets the entity's cached movement speed.
2704    fn get_speed(&self) -> f32 {
2705        self.living_base().speed()
2706    }
2707
2708    /// Sets the entity's cached movement speed.
2709    fn set_speed(&self, speed: f32) {
2710        self.living_base().set_speed(speed);
2711    }
2712
2713    /// Applies vanilla post-impulse movement validation grace.
2714    fn apply_post_impulse_grace_time(&self, ticks: i32) {
2715        self.living_base().apply_post_impulse_grace_time(ticks);
2716    }
2717
2718    /// Mirrors vanilla `LivingEntity.setIgnoreFallDamageFromCurrentImpulse`.
2719    fn set_ignore_fall_damage_from_current_impulse(
2720        &self,
2721        ignore_fall_damage: bool,
2722        new_impulse_impact_pos: DVec3,
2723    ) {
2724        self.living_base()
2725            .set_ignore_fall_damage_from_current_impulse(
2726                ignore_fall_damage,
2727                new_impulse_impact_pos,
2728            );
2729    }
2730
2731    /// Returns vanilla `LivingEntity.isIgnoringFallDamageFromCurrentImpulse`.
2732    fn is_ignoring_fall_damage_from_current_impulse(&self) -> bool {
2733        self.living_base()
2734            .is_ignoring_fall_damage_from_current_impulse()
2735    }
2736
2737    /// Returns vanilla `LivingEntity.currentImpulseImpactPos`.
2738    fn current_impulse_impact_pos(&self) -> Option<DVec3> {
2739        self.living_base().current_impulse_impact_pos()
2740    }
2741
2742    /// Mirrors vanilla `LivingEntity.tryResetCurrentImpulseContext`.
2743    fn try_reset_current_impulse_context(&self) {
2744        self.living_base().try_reset_current_impulse_context();
2745    }
2746
2747    /// Mirrors vanilla `LivingEntity.resetCurrentImpulseContext`.
2748    fn reset_current_impulse_context(&self) {
2749        self.living_base().reset_current_impulse_context();
2750    }
2751
2752    /// Returns whether movement validation is inside post-impulse grace.
2753    fn is_in_post_impulse_grace_time(&self) -> bool {
2754        self.living_base().is_in_post_impulse_grace_time()
2755    }
2756
2757    /// Decrements post-impulse grace once per living-entity tick.
2758    fn tick_post_impulse_grace_time(&self) {
2759        self.living_base().tick_post_impulse_grace_time();
2760    }
2761
2762    /// Drains dirty attributes and applies server-side effects.
2763    fn refresh_dirty_attributes(&self) {
2764        let dirty = self.attributes().lock().drain_dirty_updates();
2765        for attr in dirty {
2766            if attr.key == vanilla_attributes::MAX_HEALTH.key {
2767                let max = self.get_max_health();
2768                if self.get_health() > max {
2769                    self.set_health(max);
2770                }
2771            } else if attr.key == vanilla_attributes::MAX_ABSORPTION.key {
2772                let max = self
2773                    .attributes()
2774                    .lock()
2775                    .get_value(vanilla_attributes::MAX_ABSORPTION)
2776                    .unwrap_or(0.0) as f32;
2777                if self.get_absorption_amount() > max {
2778                    self.set_absorption_amount(max);
2779                }
2780            } else if attr.key == vanilla_attributes::SCALE.key {
2781                self.refresh_dimensions();
2782            }
2783            // TODO: WAYPOINT_TRANSMIT_RANGE → waypoint manager
2784        }
2785    }
2786}
2787
2788fn death_loot_items_with_rng<R: rand::Rng, E: LivingEntity + ?Sized>(
2789    entity: &E,
2790    loot_table: LootTableRef,
2791    world: &World,
2792    source: &DamageSource,
2793    killed_by_player: bool,
2794    rng: &mut R,
2795) -> Vec<ItemStack> {
2796    let causing_entity = source
2797        .causing_entity_id
2798        .and_then(|entity_id| world.get_entity_by_id(entity_id));
2799    let direct_entity = source
2800        .direct_entity_id
2801        .and_then(|entity_id| world.get_entity_by_id(entity_id));
2802    let last_damage_player = if killed_by_player {
2803        entity
2804            .last_hurt_by_player_uuid()
2805            .and_then(|uuid| world.get_entity_by_uuid(&uuid))
2806    } else {
2807        None
2808    };
2809
2810    let position = entity.position();
2811    let this_entity = living_entity_loot_ref(entity);
2812    let causing_entity = causing_entity.as_deref().map(entity_loot_ref);
2813    let direct_entity = direct_entity.as_deref().map(entity_loot_ref);
2814    let last_damage_player = last_damage_player.as_deref().map(entity_loot_ref);
2815    let damage_source = DamageSourceInfo {
2816        damage_type: Some(&source.damage_type.key),
2817        tags: &[],
2818        is_direct: source.is_direct(),
2819    };
2820
2821    let mut context = LootContext::new(rng)
2822        .with_origin(position.x, position.y, position.z)
2823        .with_game_time(world.game_time())
2824        .with_killed_by_player(killed_by_player)
2825        .with_this_entity(this_entity)
2826        .with_damage_source(damage_source);
2827    if let Some(entity) = causing_entity {
2828        context = context.with_killer_entity(entity);
2829    }
2830    if let Some(entity) = direct_entity {
2831        context = context.with_direct_killer_entity(entity);
2832    }
2833    if let Some(entity) = last_damage_player {
2834        context = context.with_last_damage_player(entity);
2835    }
2836
2837    loot_table.get_random_items(&mut context)
2838}
2839
2840fn living_entity_loot_ref<E: LivingEntity + ?Sized>(entity: &E) -> EntityRef<'_> {
2841    EntityRef {
2842        entity_type: Some(&entity.entity_type().key),
2843        flags: EntityRefFlags {
2844            is_on_fire: entity.is_on_fire(),
2845            is_sneaking: entity.is_crouching(),
2846            is_sprinting: entity.is_sprinting(),
2847            is_swimming: entity.is_swimming(),
2848            is_baby: entity.is_baby(),
2849        },
2850        // TODO: Include equipment and custom name once loot contexts can snapshot entity data.
2851        equipment: None,
2852        custom_name: None,
2853    }
2854}