Skip to main content

steel_core/entity/
living_entity.rs

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