Skip to main content

steel_core/entity/
living_entity.rs

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