Skip to main content

steel_core/entity/living_base/
mod.rs

1//! Shared fields for all living entities.
2//!
3//! Mirrors the runtime fields that vanilla defines on `LivingEntity` (and
4//! `Entity` for `invulnerableTime`). Entities that implement `LivingEntity`
5//! embed this struct and expose it via `LivingEntity::living_base()`, just like
6//! `EntityBase` is used for core `Entity` fields.
7
8use std::{array, mem, sync::Arc};
9
10use glam::DVec3;
11use rustc_hash::FxHashMap;
12use simdnbt::owned::{NbtCompound, NbtTag};
13use steel_protocol::packets::game::{CRemoveMobEffect, CUpdateMobEffect, MobEffectPacketFlags};
14use steel_registry::RegistryEntry;
15use steel_registry::attribute::AttributeRef;
16use steel_registry::entity_data::ParticleList;
17use steel_registry::entity_type::EntityTypeRef;
18use steel_registry::item_stack::ItemStack;
19use steel_registry::items::ItemRef;
20use steel_registry::mob_effect::MobEffectRef;
21use steel_registry::vanilla_attributes;
22use steel_registry::vanilla_entity_data::VanillaLivingEntityData;
23use steel_registry::vanilla_mob_effects;
24use steel_utils::locks::{IntoShared, Shared, SyncMutex};
25use steel_utils::types::InteractionHand;
26use steel_utils::{BlockPos, Identifier};
27use uuid::Uuid;
28
29use crate::behavior::MOB_EFFECT_BEHAVIORS;
30use crate::entity::attribute::{AttributeMap, AttributeModifier, AttributeModifierOperation};
31use crate::entity::damage::DamageSource;
32use crate::entity::{LivingEntity, SharedEntity, WeakEntity};
33use crate::inventory::equipment::{EntityEquipment, EquipmentSlot, OwnedEntityEquipment};
34use crate::world::World;
35
36/// Duration in ticks of the death animation before entity removal.
37pub const DEATH_DURATION: i32 = 20;
38/// Vanilla default `SwingAnimation` duration in ticks.
39pub const DEFAULT_SWING_DURATION: i32 = 6;
40const INFINITE_EFFECT_DURATION: i32 = -1;
41const MIN_EFFECT_AMPLIFIER: i32 = 0;
42const MAX_EFFECT_AMPLIFIER: i32 = 255;
43const SPRINT_SPEED_MODIFIER_AMOUNT: f64 = 0.3;
44const POST_IMPULSE_GRACE_TICKS: i32 = 40;
45/// Time before the last damage source expires, in game ticks.
46const DAMAGE_SOURCE_TIMEOUT: i64 = 40;
47
48/// Runtime mob-effect state.
49///
50/// Mirrors vanilla `MobEffectInstance` state that affects server-side living
51/// physics and client synchronization.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct MobEffectInstance {
54    effect: MobEffectRef,
55    duration: i32,
56    amplifier: i32,
57    ambient: bool,
58    visible: bool,
59    show_icon: bool,
60    hidden_effect: Option<Box<MobEffectInstance>>,
61}
62
63/// Active mob-effect state stored on a living entity.
64pub type ActiveMobEffect = MobEffectInstance;
65
66impl MobEffectInstance {
67    /// Creates infinite active mob-effect state for internal physics tests and hooks.
68    #[must_use]
69    pub const fn new(effect: MobEffectRef, amplifier: i32) -> Self {
70        Self::with_duration(effect, INFINITE_EFFECT_DURATION, amplifier)
71    }
72
73    /// Creates active mob-effect state with vanilla default visibility flags.
74    #[must_use]
75    pub const fn with_duration(effect: MobEffectRef, duration: i32, amplifier: i32) -> Self {
76        Self {
77            effect,
78            duration,
79            amplifier: clamp_effect_amplifier(amplifier),
80            ambient: false,
81            visible: true,
82            show_icon: true,
83            hidden_effect: None,
84        }
85    }
86
87    /// Sets whether this effect is ambient.
88    #[must_use]
89    pub const fn with_ambient(mut self, ambient: bool) -> Self {
90        self.ambient = ambient;
91        self
92    }
93
94    /// Sets whether this effect should show particles.
95    #[must_use]
96    pub const fn with_visible(mut self, visible: bool) -> Self {
97        self.visible = visible;
98        self
99    }
100
101    /// Sets whether this effect should show its inventory icon.
102    #[must_use]
103    pub const fn with_show_icon(mut self, show_icon: bool) -> Self {
104        self.show_icon = show_icon;
105        self
106    }
107
108    /// Returns the mob effect.
109    #[must_use]
110    pub const fn effect(&self) -> MobEffectRef {
111        self.effect
112    }
113
114    /// Returns vanilla `MobEffectInstance.getDuration()`.
115    #[must_use]
116    pub const fn duration(&self) -> i32 {
117        self.duration
118    }
119
120    /// Returns vanilla `MobEffectInstance.getAmplifier()`.
121    #[must_use]
122    pub const fn amplifier(&self) -> i32 {
123        self.amplifier
124    }
125
126    /// Returns vanilla `MobEffectInstance.isAmbient()`.
127    #[must_use]
128    pub const fn is_ambient(&self) -> bool {
129        self.ambient
130    }
131
132    /// Returns vanilla `MobEffectInstance.isVisible()`.
133    #[must_use]
134    pub const fn is_visible(&self) -> bool {
135        self.visible
136    }
137
138    /// Returns vanilla `MobEffectInstance.showIcon()`.
139    #[must_use]
140    pub const fn show_icon(&self) -> bool {
141        self.show_icon
142    }
143
144    /// Returns whether this effect uses vanilla's infinite-duration sentinel.
145    #[must_use]
146    pub const fn is_infinite_duration(&self) -> bool {
147        self.duration == INFINITE_EFFECT_DURATION
148    }
149
150    /// Serializes this effect with vanilla's `MobEffectInstance.CODEC` shape.
151    #[must_use]
152    pub(crate) fn to_vanilla_nbt(&self) -> NbtCompound {
153        let mut nbt = self.details_to_vanilla_nbt();
154        nbt.insert("id", self.effect.key.to_string());
155        nbt
156    }
157
158    fn details_to_vanilla_nbt(&self) -> NbtCompound {
159        let mut nbt = NbtCompound::new();
160        if self.amplifier != 0 {
161            nbt.insert("amplifier", NbtTag::Byte(self.amplifier as i8));
162        }
163        if self.duration != 0 {
164            nbt.insert("duration", self.duration);
165        }
166        if self.ambient {
167            nbt.insert("ambient", NbtTag::Byte(1));
168        }
169        if !self.visible {
170            nbt.insert("show_particles", NbtTag::Byte(0));
171        }
172        nbt.insert("show_icon", NbtTag::Byte(i8::from(self.show_icon)));
173        if let Some(hidden_effect) = self.hidden_effect.as_deref() {
174            nbt.insert(
175                "hidden_effect",
176                NbtTag::Compound(hidden_effect.details_to_vanilla_nbt()),
177            );
178        }
179        nbt
180    }
181
182    #[must_use]
183    pub(crate) const fn has_remaining_duration(&self) -> bool {
184        self.is_infinite_duration() || self.duration > 0
185    }
186
187    #[must_use]
188    pub(crate) fn should_apply_effect_tick_this_tick(&self, entity_tick_count: i32) -> bool {
189        let tick_count = if self.is_infinite_duration() {
190            entity_tick_count
191        } else {
192            self.duration
193        };
194
195        MOB_EFFECT_BEHAVIORS
196            .get_behavior(self.effect)
197            .should_apply_effect_tick_this_tick(tick_count, self.amplifier)
198    }
199
200    pub(crate) fn apply_effect_tick(&self, world: &World, entity: &dyn LivingEntity) -> bool {
201        MOB_EFFECT_BEHAVIORS
202            .get_behavior(self.effect)
203            .apply_effect_tick(world, entity, self.amplifier)
204    }
205
206    #[must_use]
207    const fn is_shorter_duration_than(&self, other: &Self) -> bool {
208        !self.is_infinite_duration()
209            && (self.duration < other.duration || other.is_infinite_duration())
210    }
211
212    /// Merges another instance of the same effect into this instance.
213    ///
214    /// Mirrors vanilla `MobEffectInstance.update`.
215    pub fn update(&mut self, take_over: Self) -> bool {
216        let mut changed = false;
217        let take_over_ambient = take_over.ambient;
218        let take_over_visible = take_over.visible;
219        let take_over_show_icon = take_over.show_icon;
220        if take_over.amplifier > self.amplifier {
221            if take_over.is_shorter_duration_than(self) {
222                let previous_hidden_effect = self.hidden_effect.take();
223                let mut hidden = self.clone();
224                hidden.hidden_effect = previous_hidden_effect;
225                self.hidden_effect = Some(Box::new(hidden));
226            }
227
228            self.amplifier = take_over.amplifier;
229            self.duration = take_over.duration;
230            changed = true;
231        } else if self.is_shorter_duration_than(&take_over) {
232            if take_over.amplifier == self.amplifier {
233                self.duration = take_over.duration;
234                changed = true;
235            } else if let Some(hidden_effect) = &mut self.hidden_effect {
236                hidden_effect.update(take_over);
237            } else {
238                self.hidden_effect = Some(Box::new(take_over));
239            }
240        }
241
242        if (!take_over_ambient && self.ambient) || changed {
243            self.ambient = take_over_ambient;
244            changed = true;
245        }
246
247        if take_over_visible != self.visible {
248            self.visible = take_over_visible;
249            changed = true;
250        }
251
252        if take_over_show_icon != self.show_icon {
253            self.show_icon = take_over_show_icon;
254            changed = true;
255        }
256
257        changed
258    }
259
260    fn tick_duration(&mut self) -> MobEffectTickResult {
261        if !self.has_remaining_duration() {
262            return MobEffectTickResult::Expired;
263        }
264
265        self.tick_down_duration();
266        if self.downgrade_to_hidden_effect() {
267            return MobEffectTickResult::Active { downgraded: true };
268        }
269        if self.has_remaining_duration() {
270            MobEffectTickResult::Active { downgraded: false }
271        } else {
272            MobEffectTickResult::Expired
273        }
274    }
275
276    fn tick_down_duration(&mut self) {
277        if let Some(hidden_effect) = &mut self.hidden_effect {
278            hidden_effect.tick_down_duration();
279        }
280
281        if !self.is_infinite_duration() && self.duration != 0 {
282            self.duration -= 1;
283        }
284    }
285
286    fn downgrade_to_hidden_effect(&mut self) -> bool {
287        if self.duration != 0 {
288            return false;
289        }
290
291        let Some(hidden_effect) = self.hidden_effect.take() else {
292            return false;
293        };
294        let MobEffectInstance {
295            duration,
296            amplifier,
297            ambient,
298            visible,
299            show_icon,
300            hidden_effect,
301            ..
302        } = *hidden_effect;
303        self.duration = duration;
304        self.amplifier = amplifier;
305        self.ambient = ambient;
306        self.visible = visible;
307        self.show_icon = show_icon;
308        self.hidden_effect = hidden_effect;
309        true
310    }
311}
312
313const fn clamp_effect_amplifier(amplifier: i32) -> i32 {
314    if amplifier < MIN_EFFECT_AMPLIFIER {
315        MIN_EFFECT_AMPLIFIER
316    } else if amplifier > MAX_EFFECT_AMPLIFIER {
317        MAX_EFFECT_AMPLIFIER
318    } else {
319        amplifier
320    }
321}
322
323enum MobEffectTickResult {
324    Active { downgraded: bool },
325    Expired,
326}
327
328/// A queued mob-effect packet change produced by living effect state.
329#[derive(Debug, Clone, PartialEq, Eq)]
330pub enum MobEffectSyncChange {
331    /// Add or update a mob effect.
332    Update {
333        /// The active effect instance to encode.
334        effect: MobEffectInstance,
335        /// Whether the owner-client packet should use vanilla's blend flag.
336        blend_for_self: bool,
337    },
338    /// Remove a mob effect.
339    Remove {
340        /// The effect type to remove.
341        effect: MobEffectRef,
342    },
343}
344
345impl MobEffectSyncChange {
346    /// Builds the clientbound packet for a concrete recipient.
347    #[must_use]
348    pub fn packet(&self, entity_id: i32, is_self_recipient: bool) -> MobEffectSyncPacket {
349        match self {
350            Self::Update {
351                effect,
352                blend_for_self,
353            } => MobEffectSyncPacket::Update(CUpdateMobEffect::new(
354                entity_id,
355                effect.effect,
356                effect.amplifier,
357                effect.duration,
358                MobEffectPacketFlags {
359                    ambient: effect.ambient,
360                    visible: effect.visible,
361                    show_icon: effect.show_icon,
362                    blend: *blend_for_self && is_self_recipient,
363                },
364            )),
365            Self::Remove { effect } => {
366                MobEffectSyncPacket::Remove(CRemoveMobEffect::new(entity_id, effect))
367            }
368        }
369    }
370}
371
372/// Concrete mob-effect packet ready to send to a player connection.
373#[derive(Debug, Clone)]
374pub enum MobEffectSyncPacket {
375    /// Add/update mob-effect packet.
376    Update(CUpdateMobEffect),
377    /// Remove mob-effect packet.
378    Remove(CRemoveMobEffect),
379}
380
381/// Synchronized living entity-data values derived from active mob effects.
382#[derive(Debug, Clone, PartialEq)]
383pub struct MobEffectDisplayState {
384    /// Visible effect particles for `LivingEntity.DATA_EFFECT_PARTICLES`.
385    pub particles: ParticleList,
386    /// Whether all active effects are ambient.
387    pub ambient: bool,
388    /// Whether the shared invisible flag should be set by active effects.
389    pub invisible: bool,
390    /// Whether the shared glowing flag should be set by active effects.
391    pub glowing: bool,
392}
393
394/// Movement input stored on vanilla `LivingEntity`.
395///
396/// Vanilla names these fields `xxa`, `yya`, and `zza`; Steel uses axis names
397/// so AI/pathfinding code can set intent without carrying obfuscated names.
398#[derive(Debug, Clone, Copy, PartialEq)]
399pub struct LivingTravelInput {
400    sideways: f32,
401    vertical: f32,
402    forward: f32,
403}
404
405impl LivingTravelInput {
406    /// No travel input.
407    pub const ZERO: Self = Self::new(0.0, 0.0, 0.0);
408
409    /// Creates living travel input.
410    #[must_use]
411    pub const fn new(sideways: f32, vertical: f32, forward: f32) -> Self {
412        Self {
413            sideways,
414            vertical,
415            forward,
416        }
417    }
418
419    /// Returns sideways movement input.
420    #[must_use]
421    pub const fn sideways(self) -> f32 {
422        self.sideways
423    }
424
425    /// Returns vertical movement input.
426    #[must_use]
427    pub const fn vertical(self) -> f32 {
428        self.vertical
429    }
430
431    /// Returns forward movement input.
432    #[must_use]
433    pub const fn forward(self) -> f32 {
434        self.forward
435    }
436
437    /// Returns input after vanilla `LivingEntity.applyInput()` damping.
438    #[must_use]
439    pub const fn dampened(self) -> Self {
440        Self {
441            sideways: self.sideways * 0.98,
442            vertical: self.vertical,
443            forward: self.forward * 0.98,
444        }
445    }
446}
447
448/// Vanilla living-entity body/head rotation state.
449#[derive(Debug, Clone, Copy, PartialEq)]
450pub struct LivingRotationState {
451    y_body_rot: f32,
452    y_body_rot_o: f32,
453    y_head_rot: f32,
454    y_head_rot_o: f32,
455}
456
457impl LivingRotationState {
458    /// Creates default living rotation state.
459    #[must_use]
460    pub const fn new() -> Self {
461        Self {
462            y_body_rot: 0.0,
463            y_body_rot_o: 0.0,
464            y_head_rot: 0.0,
465            y_head_rot_o: 0.0,
466        }
467    }
468
469    /// Returns vanilla `yBodyRot`.
470    #[must_use]
471    pub const fn y_body_rot(self) -> f32 {
472        self.y_body_rot
473    }
474
475    /// Returns vanilla `yBodyRotO`.
476    #[must_use]
477    pub const fn y_body_rot_o(self) -> f32 {
478        self.y_body_rot_o
479    }
480
481    /// Returns vanilla `yHeadRot`.
482    #[must_use]
483    pub const fn y_head_rot(self) -> f32 {
484        self.y_head_rot
485    }
486
487    /// Returns vanilla `yHeadRotO`.
488    #[must_use]
489    pub const fn y_head_rot_o(self) -> f32 {
490        self.y_head_rot_o
491    }
492}
493
494impl Default for LivingRotationState {
495    fn default() -> Self {
496        Self::new()
497    }
498}
499
500/// Vanilla arm-swing animation state stored on `LivingEntity`.
501#[derive(Debug, Clone, Copy, PartialEq)]
502pub struct LivingSwingState {
503    swinging: bool,
504    swinging_arm: Option<InteractionHand>,
505    swing_time: i32,
506    old_attack_anim: f32,
507    attack_anim: f32,
508}
509
510impl LivingSwingState {
511    /// Creates empty vanilla swing animation state.
512    #[must_use]
513    pub const fn new() -> Self {
514        Self {
515            swinging: false,
516            swinging_arm: None,
517            swing_time: 0,
518            old_attack_anim: 0.0,
519            attack_anim: 0.0,
520        }
521    }
522
523    /// Returns vanilla `LivingEntity.swinging`.
524    #[must_use]
525    pub const fn swinging(self) -> bool {
526        self.swinging
527    }
528
529    /// Returns vanilla `LivingEntity.swingingArm`.
530    #[must_use]
531    pub const fn swinging_arm(self) -> Option<InteractionHand> {
532        self.swinging_arm
533    }
534
535    /// Returns vanilla `LivingEntity.swingTime`.
536    #[must_use]
537    pub const fn swing_time(self) -> i32 {
538        self.swing_time
539    }
540
541    /// Returns vanilla `LivingEntity.oAttackAnim`.
542    #[must_use]
543    pub const fn old_attack_anim(self) -> f32 {
544        self.old_attack_anim
545    }
546
547    /// Returns vanilla `LivingEntity.attackAnim`.
548    #[must_use]
549    pub const fn attack_anim(self) -> f32 {
550        self.attack_anim
551    }
552}
553
554impl Default for LivingSwingState {
555    fn default() -> Self {
556        Self::new()
557    }
558}
559
560/// Vanilla active item-use state stored on `LivingEntity`.
561#[derive(Debug, Clone, Copy, PartialEq)]
562pub struct ActiveItemUseState {
563    hand: InteractionHand,
564    item: ItemRef,
565    duration: i32,
566    remaining_ticks: i32,
567}
568
569impl ActiveItemUseState {
570    /// Creates active item-use state from the hand, stack snapshot, and duration.
571    #[must_use]
572    pub const fn new(hand: InteractionHand, item: ItemRef, duration: i32) -> Self {
573        Self {
574            hand,
575            item,
576            duration,
577            remaining_ticks: duration,
578        }
579    }
580
581    /// Returns the hand being used.
582    #[must_use]
583    pub const fn hand(&self) -> InteractionHand {
584        self.hand
585    }
586
587    /// Returns the item snapshot captured when use started.
588    #[must_use]
589    pub const fn item(&self) -> ItemRef {
590        self.item
591    }
592
593    /// Returns the original use duration.
594    #[must_use]
595    pub const fn duration(&self) -> i32 {
596        self.duration
597    }
598
599    /// Returns the remaining active-use ticks.
600    #[must_use]
601    pub const fn remaining_ticks(&self) -> i32 {
602        self.remaining_ticks
603    }
604}
605
606#[derive(Debug, Clone)]
607struct LivingEntityState {
608    effects_dirty: bool,
609    death_processed: bool,
610    invulnerable_time: i32,
611    last_hurt: f32,
612    last_hurt_by_player: Option<Uuid>,
613    last_hurt_by_player_memory_time: i32,
614    last_hurt_by_mob: Option<WeakEntity>,
615    last_hurt_by_mob_timestamp: i32,
616    last_hurt_mob: Option<WeakEntity>,
617    last_hurt_mob_timestamp: i32,
618    last_damage_source: Option<DamageSource>,
619    last_damage_stamp: i64,
620    absorption_amount: f32,
621    skip_drop_experience: bool,
622    death_time: i32,
623    speed: f32,
624    current_impulse_impact_pos: Option<DVec3>,
625    current_impulse_context_reset_grace_time: i32,
626    fall_flying: bool,
627    fall_flying_ticks: i32,
628    sprinting: bool,
629    sleeping_pos: Option<BlockPos>,
630    last_climbable_pos: Option<BlockPos>,
631    discard_friction: bool,
632    jumping: bool,
633    travel_input: LivingTravelInput,
634    rotation: LivingRotationState,
635    swing: LivingSwingState,
636    active_item_use: Option<ActiveItemUseState>,
637    no_jump_delay: i32,
638    no_action_time: i32,
639}
640
641impl LivingEntityState {
642    const fn new(speed: f32) -> Self {
643        Self {
644            effects_dirty: false,
645            death_processed: false,
646            invulnerable_time: 0,
647            last_hurt: 0.0,
648            last_hurt_by_player: None,
649            last_hurt_by_player_memory_time: 0,
650            last_hurt_by_mob: None,
651            last_hurt_by_mob_timestamp: 0,
652            last_hurt_mob: None,
653            last_hurt_mob_timestamp: 0,
654            last_damage_source: None,
655            last_damage_stamp: 0,
656            absorption_amount: 0.0,
657            skip_drop_experience: false,
658            death_time: 0,
659            speed,
660            current_impulse_impact_pos: None,
661            current_impulse_context_reset_grace_time: 0,
662            fall_flying: false,
663            fall_flying_ticks: 0,
664            sprinting: false,
665            sleeping_pos: None,
666            last_climbable_pos: None,
667            discard_friction: false,
668            jumping: false,
669            travel_input: LivingTravelInput::ZERO,
670            rotation: LivingRotationState::new(),
671            swing: LivingSwingState::new(),
672            active_item_use: None,
673            no_jump_delay: 0,
674            no_action_time: 0,
675        }
676    }
677
678    const fn reset_death_state(&mut self) {
679        self.death_processed = false;
680        self.death_time = 0;
681        self.invulnerable_time = 0;
682        self.last_hurt = 0.0;
683        self.absorption_amount = 0.0;
684        self.skip_drop_experience = false;
685    }
686}
687
688/// Common runtime fields shared by all living entities.
689///
690/// **Deviation from vanilla:** Vanilla calls this guard `LivingEntity.dead`,
691/// but it means death side effects have been processed, not health is zero.
692/// `ServerPlayer.die()` does NOT call `super.die()` and never sets that field.
693/// Steel uses this guard for players too so repeated callbacks cannot duplicate
694/// death side effects. Health remains the source of truth for dead-or-dying
695/// checks such as client respawn requests.
696pub struct LivingEntityBase {
697    state: SyncMutex<LivingEntityState>,
698    attributes: SyncMutex<AttributeMap>,
699    active_mob_effects: SyncMutex<FxHashMap<MobEffectRef, ActiveMobEffect>>,
700    dirty_mob_effects: SyncMutex<Vec<MobEffectSyncChange>>,
701    equipment: Shared<dyn EntityEquipment>,
702    last_equipment_items: SyncMutex<[ItemStack; EquipmentSlot::ALL.len()]>,
703    pending_equipment_changes: SyncMutex<[Option<ItemStack>; EquipmentSlot::ALL.len()]>,
704    equipment_attribute_modifiers:
705        SyncMutex<[Vec<EquipmentAttributeModifierKey>; EquipmentSlot::ALL.len()]>,
706}
707
708#[derive(Debug)]
709struct EquipmentAttributeModifierKey {
710    attribute: AttributeRef,
711    id: Identifier,
712}
713
714impl LivingEntityBase {
715    /// Creates living runtime state from an entity type's default attributes.
716    #[must_use]
717    pub fn new(entity_type: EntityTypeRef) -> Self {
718        Self::with_attributes(AttributeMap::new_for_entity(entity_type))
719    }
720
721    /// Creates living runtime state from an explicit attribute map.
722    #[must_use]
723    pub fn with_attributes(attributes: AttributeMap) -> Self {
724        let equipment: Shared<dyn EntityEquipment> = OwnedEntityEquipment::new().into_shared();
725        Self::with_attributes_and_equipment(attributes, equipment)
726    }
727
728    /// Creates living runtime state with an explicit canonical equipment backing.
729    #[must_use]
730    pub fn with_equipment(
731        entity_type: EntityTypeRef,
732        equipment: Shared<dyn EntityEquipment>,
733    ) -> Self {
734        Self::with_attributes_and_equipment(AttributeMap::new_for_entity(entity_type), equipment)
735    }
736
737    fn with_attributes_and_equipment(
738        attributes: AttributeMap,
739        equipment: Shared<dyn EntityEquipment>,
740    ) -> Self {
741        let speed = attributes.required_value(vanilla_attributes::MOVEMENT_SPEED) as f32;
742
743        Self {
744            state: SyncMutex::new(LivingEntityState::new(speed)),
745            attributes: SyncMutex::new(attributes),
746            active_mob_effects: SyncMutex::new(FxHashMap::default()),
747            dirty_mob_effects: SyncMutex::new(Vec::new()),
748            equipment,
749            last_equipment_items: SyncMutex::new(array::from_fn(|_| ItemStack::empty())),
750            pending_equipment_changes: SyncMutex::new(array::from_fn(|_| None)),
751            equipment_attribute_modifiers: SyncMutex::new(array::from_fn(|_| Vec::new())),
752        }
753    }
754
755    /// Returns this entity's attribute map.
756    #[inline]
757    pub const fn attributes(&self) -> &SyncMutex<AttributeMap> {
758        &self.attributes
759    }
760
761    /// Applies vanilla constructor-time synced-data mutations for living entities.
762    ///
763    /// Vanilla defines `DATA_HEALTH_ID` as `1.0F`, then `LivingEntity` constructs
764    /// its attribute map and calls `setHealth(getMaxHealth())`.
765    pub fn initialize_synced_data<T: VanillaLivingEntityData>(&self, entity_data: &mut T) {
766        let max_health = self
767            .attributes
768            .lock()
769            .required_value(vanilla_attributes::MAX_HEALTH) as f32;
770        entity_data.living_entity_mut().health.set(max_health);
771    }
772
773    /// Returns vanilla `LivingEntity.equipment` storage.
774    #[inline]
775    pub const fn equipment(&self) -> &Shared<dyn EntityEquipment> {
776        &self.equipment
777    }
778
779    /// Collects equipment changes against Vanilla's previous-tick snapshots.
780    pub fn collect_equipment_changes(&self) -> Vec<(EquipmentSlot, ItemStack, ItemStack)> {
781        let current_items: [ItemStack; EquipmentSlot::ALL.len()] = {
782            let equipment = self.equipment.lock();
783            array::from_fn(|index| equipment.get_ref(EquipmentSlot::ALL[index]).clone())
784        };
785        let mut last_items = self.last_equipment_items.lock();
786        let mut changes = Vec::new();
787
788        for slot in EquipmentSlot::ALL {
789            let index = slot.index();
790            if ItemStack::matches(&last_items[index], &current_items[index]) {
791                continue;
792            }
793            let previous = mem::replace(&mut last_items[index], current_items[index].clone());
794            changes.push((slot, previous, current_items[index].clone()));
795        }
796        changes
797    }
798
799    /// Coalesces detected equipment changes until entity tracking sends them.
800    pub fn queue_equipment_changes(
801        &self,
802        changes: impl IntoIterator<Item = (EquipmentSlot, ItemStack)>,
803    ) {
804        let mut pending = self.pending_equipment_changes.lock();
805        for (slot, item_stack) in changes {
806            pending[slot.index()] = Some(item_stack);
807        }
808    }
809
810    /// Drains equipment changes detected by the living tick.
811    pub fn drain_equipment_changes(&self) -> Vec<(EquipmentSlot, ItemStack)> {
812        let mut pending = self.pending_equipment_changes.lock();
813        EquipmentSlot::ALL
814            .into_iter()
815            .filter_map(|slot| pending[slot.index()].take().map(|item| (slot, item)))
816            .collect()
817    }
818
819    /// Returns vanilla living body/head rotation state.
820    #[must_use]
821    pub fn rotation_state(&self) -> LivingRotationState {
822        self.state.lock().rotation
823    }
824
825    /// Returns vanilla arm-swing animation state.
826    #[must_use]
827    pub fn swing_state(&self) -> LivingSwingState {
828        self.state.lock().swing
829    }
830
831    /// Returns a snapshot of vanilla active item-use state.
832    #[must_use]
833    pub fn active_item_use(&self) -> Option<ActiveItemUseState> {
834        self.state.lock().active_item_use
835    }
836
837    /// Returns whether this living entity is actively using an item.
838    #[must_use]
839    pub fn is_using_item(&self) -> bool {
840        self.state.lock().active_item_use.is_some()
841    }
842
843    /// Starts vanilla active item use.
844    pub fn start_using_item(&self, hand: InteractionHand, item: &ItemStack, duration: i32) -> bool {
845        if item.is_empty() || duration <= 0 {
846            return false;
847        }
848
849        let mut state = self.state.lock();
850        if state.active_item_use.is_some() {
851            return false;
852        }
853        state.active_item_use = Some(ActiveItemUseState::new(hand, item.item(), duration));
854        true
855    }
856
857    /// Stops vanilla active item use without calling item hooks.
858    pub fn stop_using_item(&self) -> Option<ActiveItemUseState> {
859        self.state.lock().active_item_use.take()
860    }
861
862    /// Decrements active-use remaining ticks and returns the updated state.
863    pub fn decrement_active_item_use(&self) -> Option<ActiveItemUseState> {
864        let mut state = self.state.lock();
865        let active = state.active_item_use.as_mut()?;
866        active.remaining_ticks -= 1;
867        Some(*active)
868    }
869
870    /// Returns vanilla `yBodyRot`.
871    #[must_use]
872    pub fn y_body_rot(&self) -> f32 {
873        self.state.lock().rotation.y_body_rot
874    }
875
876    /// Sets vanilla `yBodyRot`.
877    pub fn set_y_body_rot(&self, y_body_rot: f32) {
878        self.state.lock().rotation.y_body_rot = y_body_rot;
879    }
880
881    /// Returns vanilla `yHeadRot`.
882    #[must_use]
883    pub fn y_head_rot(&self) -> f32 {
884        self.state.lock().rotation.y_head_rot
885    }
886
887    /// Sets vanilla `yHeadRot`.
888    pub fn set_y_head_rot(&self, y_head_rot: f32) {
889        self.state.lock().rotation.y_head_rot = y_head_rot;
890    }
891
892    /// Copies current living head/body rotations to their old-rotation fields.
893    pub fn advance_rotation_for_base_tick(&self) {
894        let mut state = self.state.lock();
895        state.rotation.y_head_rot_o = state.rotation.y_head_rot;
896        state.rotation.y_body_rot_o = state.rotation.y_body_rot;
897    }
898
899    /// Copies current attack animation to vanilla `oAttackAnim`.
900    pub fn advance_attack_animation_for_base_tick(&self) {
901        let mut state = self.state.lock();
902        state.swing.old_attack_anim = state.swing.attack_anim;
903    }
904
905    /// Starts vanilla `LivingEntity.swing` state if the swing gate allows it.
906    pub fn start_swing(&self, hand: InteractionHand, current_swing_duration: i32) -> bool {
907        let mut state = self.state.lock();
908        let swing = &mut state.swing;
909        if swing.swinging && swing.swing_time < current_swing_duration / 2 && swing.swing_time >= 0
910        {
911            return false;
912        }
913
914        swing.swing_time = -1;
915        swing.swinging = true;
916        swing.swinging_arm = Some(hand);
917        true
918    }
919
920    /// Updates vanilla `LivingEntity.swingTime` and `attackAnim`.
921    pub fn update_swing_time(&self, current_swing_duration: i32) {
922        let mut state = self.state.lock();
923        let swing = &mut state.swing;
924        if swing.swinging {
925            swing.swing_time += 1;
926            if swing.swing_time >= current_swing_duration {
927                swing.swing_time = 0;
928                swing.swinging = false;
929            }
930        } else {
931            swing.swing_time = 0;
932        }
933
934        swing.attack_anim = swing.swing_time as f32 / current_swing_duration as f32;
935    }
936
937    /// Returns vanilla `LivingEntity.absorptionAmount` for non-player living entities.
938    #[must_use]
939    pub fn absorption_amount(&self) -> f32 {
940        self.state.lock().absorption_amount
941    }
942
943    /// Sets vanilla `LivingEntity.absorptionAmount` for non-player living entities.
944    pub fn set_absorption_amount(&self, amount: f32) {
945        let max_absorption = self
946            .attributes
947            .lock()
948            .required_value(vanilla_attributes::MAX_ABSORPTION) as f32;
949        self.state.lock().absorption_amount = amount.clamp(0.0, max_absorption);
950    }
951
952    /// Runs vanilla `LivingEntity.skipDropExperience`.
953    pub fn skip_drop_experience(&self) {
954        self.state.lock().skip_drop_experience = true;
955    }
956
957    /// Returns vanilla `LivingEntity.wasExperienceConsumed`.
958    #[must_use]
959    pub fn was_experience_consumed(&self) -> bool {
960        self.state.lock().skip_drop_experience
961    }
962
963    /// Returns vanilla `LivingEntity.noActionTime`.
964    #[must_use]
965    pub fn no_action_time(&self) -> i32 {
966        self.state.lock().no_action_time
967    }
968
969    /// Sets vanilla `LivingEntity.noActionTime`.
970    pub fn set_no_action_time(&self, no_action_time: i32) {
971        self.state.lock().no_action_time = no_action_time;
972    }
973
974    /// Increments vanilla `LivingEntity.noActionTime` by one tick.
975    pub fn increment_no_action_time(&self) {
976        self.state.lock().no_action_time += 1;
977    }
978
979    /// Refreshes transient item attribute modifiers for an equipment slot.
980    pub fn refresh_equipment_attribute_modifiers(
981        &self,
982        slot: EquipmentSlot,
983        item_stack: &ItemStack,
984    ) {
985        let slot_index = slot.index();
986        let mut attributes = self.attributes.lock();
987        let mut installed_modifiers = self.equipment_attribute_modifiers.lock();
988
989        for key in installed_modifiers[slot_index].drain(..) {
990            attributes.remove_modifier(key.attribute, &key.id);
991        }
992
993        if item_stack.is_empty() || item_stack.is_broken() {
994            return;
995        }
996
997        let Some(modifiers) = item_stack.get_attribute_modifiers() else {
998            return;
999        };
1000
1001        for entry in modifiers.for_slot(slot) {
1002            for (index, keys) in installed_modifiers.iter_mut().enumerate() {
1003                if index == slot_index {
1004                    continue;
1005                }
1006                keys.retain(|key| key.attribute.key != entry.attribute.key || key.id != entry.id);
1007            }
1008
1009            attributes.remove_modifier(entry.attribute, &entry.id);
1010            if attributes.add_modifier(
1011                entry.attribute,
1012                AttributeModifier {
1013                    id: entry.id.clone(),
1014                    amount: entry.amount,
1015                    operation: entry.operation,
1016                },
1017                false,
1018            ) {
1019                installed_modifiers[slot_index].push(EquipmentAttributeModifierKey {
1020                    attribute: entry.attribute,
1021                    id: entry.id.clone(),
1022                });
1023            }
1024        }
1025    }
1026
1027    /// Returns whether this living entity has an active vanilla mob effect.
1028    #[must_use]
1029    pub fn has_mob_effect(&self, effect: MobEffectRef) -> bool {
1030        self.active_mob_effects.lock().contains_key(&effect)
1031    }
1032
1033    /// Returns active vanilla mob-effect state.
1034    #[must_use]
1035    pub fn mob_effect(&self, effect: MobEffectRef) -> Option<ActiveMobEffect> {
1036        self.active_mob_effects.lock().get(&effect).cloned()
1037    }
1038
1039    /// Returns all active vanilla mob effects.
1040    #[must_use]
1041    pub fn active_mob_effects(&self) -> Vec<ActiveMobEffect> {
1042        self.active_mob_effects.lock().values().cloned().collect()
1043    }
1044
1045    /// Adds or updates active vanilla mob-effect state.
1046    pub fn add_mob_effect(&self, effect: MobEffectInstance) -> bool {
1047        let effect_key = effect.effect;
1048        let mut existing_effect = None;
1049        let mut changed_effect = None;
1050        {
1051            let mut effects = self.active_mob_effects.lock();
1052            if let Some(current) = effects.get_mut(&effect_key) {
1053                if current.update(effect) {
1054                    changed_effect = Some(current.clone());
1055                }
1056            } else {
1057                effects.insert(effect_key, effect.clone());
1058                existing_effect = Some(effect);
1059            }
1060        }
1061
1062        if let Some(effect) = existing_effect {
1063            self.add_effect_attribute_modifiers(&effect);
1064            self.mark_effects_dirty();
1065            self.queue_mob_effect_sync(MobEffectSyncChange::Update {
1066                effect,
1067                blend_for_self: true,
1068            });
1069            return true;
1070        }
1071
1072        if let Some(effect) = changed_effect {
1073            self.refresh_effect_attribute_modifiers(&effect);
1074            self.mark_effects_dirty();
1075            self.queue_mob_effect_sync(MobEffectSyncChange::Update {
1076                effect,
1077                blend_for_self: false,
1078            });
1079            return true;
1080        }
1081
1082        false
1083    }
1084
1085    /// Sets active vanilla mob-effect state.
1086    pub fn set_mob_effect(&self, effect: MobEffectRef, amplifier: i32) {
1087        self.add_mob_effect(MobEffectInstance::new(effect, amplifier));
1088    }
1089
1090    /// Sets the presence of a vanilla mob effect.
1091    pub fn set_mob_effect_active(&self, effect: MobEffectRef, active: bool) {
1092        if active {
1093            self.set_mob_effect(effect, 0);
1094        } else {
1095            self.remove_mob_effect(effect);
1096        }
1097    }
1098
1099    /// Removes active vanilla mob-effect state.
1100    pub fn remove_mob_effect(&self, effect: MobEffectRef) -> bool {
1101        let removed = self.active_mob_effects.lock().remove(&effect);
1102        let Some(removed) = removed else {
1103            return false;
1104        };
1105
1106        self.remove_effect_attribute_modifiers(removed.effect);
1107        self.mark_effects_dirty();
1108        self.queue_mob_effect_sync(MobEffectSyncChange::Remove { effect });
1109        true
1110    }
1111
1112    /// Ticks one active mob-effect duration after its server behavior has run.
1113    pub(super) fn tick_mob_effect_duration(&self, effect_key: MobEffectRef) {
1114        let (updated, removed) = {
1115            let mut effects = self.active_mob_effects.lock();
1116            let Some(effect) = effects.get_mut(&effect_key) else {
1117                return;
1118            };
1119            match effect.tick_duration() {
1120                MobEffectTickResult::Active { downgraded } => {
1121                    let updated =
1122                        (downgraded || effect.duration() % 600 == 0).then(|| effect.clone());
1123                    (updated, None)
1124                }
1125                MobEffectTickResult::Expired => (None, effects.remove(&effect_key)),
1126            }
1127        };
1128
1129        if let Some(effect) = updated {
1130            self.refresh_effect_attribute_modifiers(&effect);
1131            self.mark_effects_dirty();
1132            self.queue_mob_effect_sync(MobEffectSyncChange::Update {
1133                effect,
1134                blend_for_self: false,
1135            });
1136        }
1137
1138        if let Some(effect) = removed {
1139            self.remove_effect_attribute_modifiers(effect.effect);
1140            self.mark_effects_dirty();
1141            self.queue_mob_effect_sync(MobEffectSyncChange::Remove {
1142                effect: effect.effect,
1143            });
1144        }
1145    }
1146
1147    /// Drains pending mob-effect packet changes.
1148    pub fn drain_dirty_mob_effects(&self) -> Vec<MobEffectSyncChange> {
1149        self.dirty_mob_effects.lock().drain(..).collect()
1150    }
1151
1152    /// Returns whether synchronized effect entity data should be recomputed.
1153    pub fn take_effects_dirty(&self) -> bool {
1154        let mut state = self.state.lock();
1155        let dirty = state.effects_dirty;
1156        state.effects_dirty = false;
1157        dirty
1158    }
1159
1160    /// Builds the synchronized living effect particle/glow/invisibility state.
1161    pub fn mob_effect_display_state(&self) -> MobEffectDisplayState {
1162        let mut effects = self
1163            .active_mob_effects
1164            .lock()
1165            .values()
1166            .cloned()
1167            .collect::<Vec<_>>();
1168        effects.sort_by_key(|effect| effect.effect.try_id().unwrap_or(usize::MAX));
1169
1170        let particles = effects
1171            .iter()
1172            .filter(|effect| effect.is_visible())
1173            .map(|effect| effect.effect.create_particle_options(effect.ambient))
1174            .collect();
1175
1176        MobEffectDisplayState {
1177            particles: ParticleList { particles },
1178            ambient: !effects.is_empty() && effects.iter().all(MobEffectInstance::is_ambient),
1179            invisible: effects
1180                .iter()
1181                .any(|effect| effect.effect == vanilla_mob_effects::INVISIBILITY),
1182            glowing: effects
1183                .iter()
1184                .any(|effect| effect.effect == vanilla_mob_effects::GLOWING),
1185        }
1186    }
1187
1188    fn add_effect_attribute_modifiers(&self, effect: &MobEffectInstance) {
1189        let mut attributes = self.attributes.lock();
1190        for modifier in effect.effect.attribute_modifiers {
1191            attributes.remove_modifier(modifier.attribute, &modifier.id);
1192            attributes.add_modifier(
1193                modifier.attribute,
1194                AttributeModifier {
1195                    id: modifier.id.clone(),
1196                    amount: modifier.amount * f64::from(effect.amplifier + 1),
1197                    operation: modifier.operation,
1198                },
1199                false,
1200            );
1201        }
1202    }
1203
1204    fn refresh_effect_attribute_modifiers(&self, effect: &MobEffectInstance) {
1205        self.remove_effect_attribute_modifiers(effect.effect);
1206        self.add_effect_attribute_modifiers(effect);
1207    }
1208
1209    fn remove_effect_attribute_modifiers(&self, effect: MobEffectRef) {
1210        let mut attributes = self.attributes.lock();
1211        for modifier in effect.attribute_modifiers {
1212            attributes.remove_modifier(modifier.attribute, &modifier.id);
1213        }
1214    }
1215
1216    fn queue_mob_effect_sync(&self, change: MobEffectSyncChange) {
1217        self.dirty_mob_effects.lock().push(change);
1218    }
1219
1220    /// Marks synchronized effect visibility data for recomputation.
1221    pub(crate) fn mark_effects_dirty(&self) {
1222        self.state.lock().effects_dirty = true;
1223    }
1224
1225    /// Gets the cached movement speed used by living movement code.
1226    #[inline]
1227    pub fn speed(&self) -> f32 {
1228        self.state.lock().speed
1229    }
1230
1231    /// Sets the cached movement speed used by living movement code.
1232    #[inline]
1233    pub fn set_speed(&self, speed: f32) {
1234        self.state.lock().speed = speed;
1235    }
1236
1237    /// Refreshes the cached movement speed from the `MOVEMENT_SPEED` attribute.
1238    pub fn refresh_speed_from_attributes(&self) {
1239        if let Some(speed) = self
1240            .attributes
1241            .lock()
1242            .get_value(vanilla_attributes::MOVEMENT_SPEED)
1243        {
1244            self.state.lock().speed = speed as f32;
1245        }
1246    }
1247
1248    /// Applies vanilla post-impulse movement validation grace.
1249    pub fn apply_post_impulse_grace_time(&self, ticks: i32) {
1250        let mut state = self.state.lock();
1251        state.current_impulse_context_reset_grace_time =
1252            state.current_impulse_context_reset_grace_time.max(ticks);
1253    }
1254
1255    /// Mirrors vanilla `LivingEntity.setIgnoreFallDamageFromCurrentImpulse`.
1256    pub fn set_ignore_fall_damage_from_current_impulse(
1257        &self,
1258        ignore_fall_damage: bool,
1259        new_impulse_impact_pos: DVec3,
1260    ) {
1261        let mut state = self.state.lock();
1262        if ignore_fall_damage {
1263            state.current_impulse_context_reset_grace_time = state
1264                .current_impulse_context_reset_grace_time
1265                .max(POST_IMPULSE_GRACE_TICKS);
1266            state.current_impulse_impact_pos = Some(new_impulse_impact_pos);
1267        } else {
1268            state.current_impulse_context_reset_grace_time = 0;
1269        }
1270    }
1271
1272    /// Returns vanilla `LivingEntity.currentImpulseImpactPos`.
1273    #[must_use]
1274    pub fn current_impulse_impact_pos(&self) -> Option<DVec3> {
1275        self.state.lock().current_impulse_impact_pos
1276    }
1277
1278    /// Returns vanilla `LivingEntity.currentImpulseContextResetGraceTime`.
1279    #[must_use]
1280    pub fn current_impulse_context_reset_grace_time(&self) -> i32 {
1281        self.state.lock().current_impulse_context_reset_grace_time
1282    }
1283
1284    /// Returns vanilla `LivingEntity.isIgnoringFallDamageFromCurrentImpulse`.
1285    #[must_use]
1286    pub fn is_ignoring_fall_damage_from_current_impulse(&self) -> bool {
1287        self.state.lock().current_impulse_impact_pos.is_some()
1288    }
1289
1290    /// Mirrors vanilla `LivingEntity.tryResetCurrentImpulseContext`.
1291    pub fn try_reset_current_impulse_context(&self) {
1292        let mut state = self.state.lock();
1293        if state.current_impulse_context_reset_grace_time == 0 {
1294            state.current_impulse_impact_pos = None;
1295        }
1296    }
1297
1298    /// Mirrors vanilla `LivingEntity.resetCurrentImpulseContext`.
1299    pub fn reset_current_impulse_context(&self) {
1300        let mut state = self.state.lock();
1301        state.current_impulse_context_reset_grace_time = 0;
1302        state.current_impulse_impact_pos = None;
1303    }
1304
1305    /// Returns whether movement validation is inside post-impulse grace.
1306    #[must_use]
1307    pub fn is_in_post_impulse_grace_time(&self) -> bool {
1308        self.state.lock().current_impulse_context_reset_grace_time > 0
1309    }
1310
1311    /// Decrements post-impulse grace once per living-entity tick.
1312    pub fn tick_post_impulse_grace_time(&self) {
1313        let mut state = self.state.lock();
1314        if state.current_impulse_context_reset_grace_time > 0 {
1315            state.current_impulse_context_reset_grace_time -= 1;
1316        }
1317    }
1318
1319    /// Returns whether this living entity is currently fall flying.
1320    #[must_use]
1321    pub fn is_fall_flying(&self) -> bool {
1322        self.state.lock().fall_flying
1323    }
1324
1325    /// Sets the vanilla living-entity fall-flying state.
1326    pub fn set_fall_flying(&self, fall_flying: bool) {
1327        self.state.lock().fall_flying = fall_flying;
1328    }
1329
1330    /// Returns vanilla `LivingEntity.fallFlyTicks`.
1331    #[must_use]
1332    pub fn fall_flying_ticks(&self) -> i32 {
1333        self.state.lock().fall_flying_ticks
1334    }
1335
1336    /// Ticks vanilla `LivingEntity.fallFlyTicks`.
1337    pub fn tick_fall_flying_state(&self, fall_flying: bool) {
1338        let mut state = self.state.lock();
1339        if fall_flying {
1340            state.fall_flying_ticks = state.fall_flying_ticks.wrapping_add(1);
1341        } else {
1342            state.fall_flying_ticks = 0;
1343        }
1344    }
1345
1346    /// Returns whether this living entity is sprinting.
1347    #[must_use]
1348    pub fn is_sprinting(&self) -> bool {
1349        self.state.lock().sprinting
1350    }
1351
1352    /// Sets the vanilla living-entity sprinting state and movement-speed modifier.
1353    pub fn set_sprinting(&self, sprinting: bool) {
1354        self.state.lock().sprinting = sprinting;
1355
1356        let mut attributes = self.attributes.lock();
1357        if sprinting {
1358            attributes.add_modifier(
1359                vanilla_attributes::MOVEMENT_SPEED,
1360                AttributeModifier {
1361                    id: Identifier::vanilla_static("sprinting"),
1362                    amount: SPRINT_SPEED_MODIFIER_AMOUNT,
1363                    operation: AttributeModifierOperation::AddMultipliedTotal,
1364                },
1365                false,
1366            );
1367        } else {
1368            attributes.remove_modifier(
1369                vanilla_attributes::MOVEMENT_SPEED,
1370                &Identifier::vanilla_static("sprinting"),
1371            );
1372        }
1373    }
1374
1375    /// Returns the bed position that makes this living entity sleeping.
1376    #[must_use]
1377    pub fn sleeping_pos(&self) -> Option<BlockPos> {
1378        self.state.lock().sleeping_pos
1379    }
1380
1381    /// Sets the vanilla living-entity sleeping position.
1382    pub fn set_sleeping_pos(&self, bed_position: BlockPos) {
1383        self.state.lock().sleeping_pos = Some(bed_position);
1384    }
1385
1386    /// Clears the vanilla living-entity sleeping position.
1387    pub fn clear_sleeping_pos(&self) {
1388        self.state.lock().sleeping_pos = None;
1389    }
1390
1391    /// Returns whether this living entity has a sleeping position.
1392    #[must_use]
1393    pub fn is_sleeping(&self) -> bool {
1394        self.sleeping_pos().is_some()
1395    }
1396
1397    /// Returns the last climbable block position this living entity touched.
1398    #[must_use]
1399    pub fn last_climbable_pos(&self) -> Option<BlockPos> {
1400        self.state.lock().last_climbable_pos
1401    }
1402
1403    /// Records the last climbable block position this living entity touched.
1404    pub fn set_last_climbable_pos(&self, pos: BlockPos) {
1405        self.state.lock().last_climbable_pos = Some(pos);
1406    }
1407
1408    /// Returns whether vanilla living travel should skip friction damping.
1409    #[must_use]
1410    pub fn should_discard_friction(&self) -> bool {
1411        self.state.lock().discard_friction
1412    }
1413
1414    /// Sets whether vanilla living travel should skip friction damping.
1415    pub fn set_discard_friction(&self, discard_friction: bool) {
1416        self.state.lock().discard_friction = discard_friction;
1417    }
1418
1419    /// Returns whether this living entity is applying jump input.
1420    #[must_use]
1421    pub fn is_jumping(&self) -> bool {
1422        self.state.lock().jumping
1423    }
1424
1425    /// Sets whether this living entity is applying jump input.
1426    pub fn set_jumping(&self, jumping: bool) {
1427        self.state.lock().jumping = jumping;
1428    }
1429
1430    /// Returns vanilla living travel input.
1431    #[must_use]
1432    pub fn travel_input(&self) -> LivingTravelInput {
1433        self.state.lock().travel_input
1434    }
1435
1436    /// Sets vanilla living travel input.
1437    pub fn set_travel_input(&self, input: LivingTravelInput) {
1438        self.state.lock().travel_input = input;
1439    }
1440
1441    /// Applies vanilla `LivingEntity.applyInput()` damping to travel input.
1442    pub fn dampen_travel_input(&self) {
1443        let mut state = self.state.lock();
1444        state.travel_input = state.travel_input.dampened();
1445    }
1446
1447    /// Returns vanilla jump cooldown ticks.
1448    #[must_use]
1449    pub fn no_jump_delay(&self) -> i32 {
1450        self.state.lock().no_jump_delay
1451    }
1452
1453    /// Sets vanilla jump cooldown ticks.
1454    pub fn set_no_jump_delay(&self, ticks: i32) {
1455        self.state.lock().no_jump_delay = ticks;
1456    }
1457
1458    /// Decrements vanilla jump cooldown once per living AI step.
1459    pub fn tick_no_jump_delay(&self) {
1460        let mut state = self.state.lock();
1461        if state.no_jump_delay > 0 {
1462            state.no_jump_delay -= 1;
1463        }
1464    }
1465
1466    /// Calculates vanilla living-entity fall damage.
1467    #[must_use]
1468    pub fn calculate_fall_damage(
1469        fall_distance: f64,
1470        damage_modifier: f32,
1471        safe_fall_distance: f64,
1472        fall_damage_multiplier: f64,
1473    ) -> i32 {
1474        ((fall_distance + 1.0e-6 - safe_fall_distance)
1475            * f64::from(damage_modifier)
1476            * fall_damage_multiplier)
1477            .floor() as i32
1478    }
1479
1480    /// Decrements remaining invulnerability ticks by one if any are active.
1481    pub fn decrement_invulnerable_time(&self) {
1482        let mut state = self.state.lock();
1483        if state.invulnerable_time > 0 {
1484            state.invulnerable_time -= 1;
1485        }
1486    }
1487
1488    /// Applies vanilla hurt cooldown bookkeeping.
1489    ///
1490    /// Returns `None` when damage should be ignored because death was already
1491    /// processed or the amount did not exceed the active invulnerability frame.
1492    pub fn apply_damage_cooldown(
1493        &self,
1494        amount: f32,
1495        bypasses_cooldown: bool,
1496    ) -> Option<(bool, f32)> {
1497        let mut state = self.state.lock();
1498        if state.death_processed {
1499            return None;
1500        }
1501
1502        if state.invulnerable_time > 10 && !bypasses_cooldown {
1503            if amount <= state.last_hurt {
1504                return None;
1505            }
1506            let effective = amount - state.last_hurt;
1507            state.last_hurt = amount;
1508            Some((false, effective))
1509        } else {
1510            state.last_hurt = amount;
1511            state.invulnerable_time = 20;
1512            Some((true, amount))
1513        }
1514    }
1515
1516    /// Records vanilla `LivingEntity.lastDamageSource` after successful damage.
1517    pub fn record_last_damage_source(&self, source: &DamageSource, game_time: i64) {
1518        let mut state = self.state.lock();
1519        state.last_damage_source = Some(source.clone());
1520        state.last_damage_stamp = game_time;
1521    }
1522
1523    /// Drops transient damage history when target-domain player state is restored.
1524    pub(crate) fn clear_last_damage_source(&self) {
1525        let mut state = self.state.lock();
1526        state.last_damage_source = None;
1527        state.last_damage_stamp = 0;
1528    }
1529
1530    /// Returns vanilla `LivingEntity.getLastDamageSource()`.
1531    pub fn last_damage_source(&self, game_time: i64) -> Option<DamageSource> {
1532        let mut state = self.state.lock();
1533        if game_time.wrapping_sub(state.last_damage_stamp) > DAMAGE_SOURCE_TIMEOUT {
1534            state.last_damage_source = None;
1535        }
1536        state.last_damage_source.clone()
1537    }
1538
1539    /// Sets vanilla `LivingEntity.lastHurtByPlayer` and memory time.
1540    pub fn set_last_hurt_by_player(&self, player_uuid: Uuid, time_to_remember: i32) {
1541        let mut state = self.state.lock();
1542        state.last_hurt_by_player = Some(player_uuid);
1543        state.last_hurt_by_player_memory_time = time_to_remember;
1544    }
1545
1546    /// Returns vanilla `LivingEntity.lastHurtByPlayerMemoryTime`.
1547    #[must_use]
1548    pub fn last_hurt_by_player_memory_time(&self) -> i32 {
1549        self.state.lock().last_hurt_by_player_memory_time
1550    }
1551
1552    /// Returns the remembered player UUID, if present.
1553    #[must_use]
1554    pub fn last_hurt_by_player_uuid(&self) -> Option<Uuid> {
1555        self.state.lock().last_hurt_by_player
1556    }
1557
1558    /// Returns vanilla `LivingEntity.lastHurtByMob`, if still resolvable.
1559    #[must_use]
1560    pub fn last_hurt_by_mob(&self) -> Option<SharedEntity> {
1561        let mut state = self.state.lock();
1562        living_entity_from_weak(&mut state.last_hurt_by_mob)
1563    }
1564
1565    /// Returns vanilla `LivingEntity.lastHurtByMobTimestamp`.
1566    #[must_use]
1567    pub fn last_hurt_by_mob_timestamp(&self) -> i32 {
1568        self.state.lock().last_hurt_by_mob_timestamp
1569    }
1570
1571    /// Sets vanilla `LivingEntity.lastHurtByMob` and timestamp.
1572    pub fn set_last_hurt_by_mob(&self, target: Option<&SharedEntity>, tick_count: i32) {
1573        let mut state = self.state.lock();
1574        state.last_hurt_by_mob = weak_living_entity(target);
1575        state.last_hurt_by_mob_timestamp = tick_count;
1576    }
1577
1578    /// Returns vanilla `LivingEntity.lastHurtMob`, if still resolvable.
1579    #[must_use]
1580    pub fn last_hurt_mob(&self) -> Option<SharedEntity> {
1581        let mut state = self.state.lock();
1582        living_entity_from_weak(&mut state.last_hurt_mob)
1583    }
1584
1585    /// Returns vanilla `LivingEntity.lastHurtMobTimestamp`.
1586    #[must_use]
1587    pub fn last_hurt_mob_timestamp(&self) -> i32 {
1588        self.state.lock().last_hurt_mob_timestamp
1589    }
1590
1591    /// Sets vanilla `LivingEntity.lastHurtMob` and timestamp.
1592    pub fn set_last_hurt_mob(&self, target: Option<&SharedEntity>, tick_count: i32) {
1593        let mut state = self.state.lock();
1594        state.last_hurt_mob = weak_living_entity(target);
1595        state.last_hurt_mob_timestamp = tick_count;
1596    }
1597
1598    /// Ticks vanilla last-hurt-by-player memory.
1599    pub fn tick_last_hurt_by_player_memory(&self) {
1600        let mut state = self.state.lock();
1601        if state.last_hurt_by_player_memory_time > 0 {
1602            state.last_hurt_by_player_memory_time -= 1;
1603        } else {
1604            state.last_hurt_by_player = None;
1605        }
1606    }
1607
1608    /// Ticks vanilla living combat-memory cleanup.
1609    pub fn tick_living_combat_memory(&self, tick_count: i32) {
1610        if self
1611            .last_hurt_mob()
1612            .is_some_and(|target| living_is_dead(&target))
1613        {
1614            self.set_last_hurt_mob(None, tick_count);
1615        }
1616
1617        let Some(hurt_by) = self.last_hurt_by_mob() else {
1618            return;
1619        };
1620        if living_is_dead(&hurt_by) || tick_count - self.last_hurt_by_mob_timestamp() > 100 {
1621            self.set_last_hurt_by_mob(None, tick_count);
1622        }
1623    }
1624
1625    /// Marks death side effects as processed.
1626    ///
1627    /// Returns `false` if they were already processed.
1628    pub fn mark_death_processed(&self) -> bool {
1629        let mut state = self.state.lock();
1630        if state.death_processed {
1631            return false;
1632        }
1633        state.death_processed = true;
1634        true
1635    }
1636
1637    /// Increments death animation time by 1 and returns the new value.
1638    #[inline]
1639    pub fn increment_death_time(&self) -> i32 {
1640        let mut state = self.state.lock();
1641        state.death_time += 1;
1642        state.death_time
1643    }
1644
1645    /// Returns vanilla `LivingEntity.deathTime`.
1646    #[must_use]
1647    pub fn death_time(&self) -> i32 {
1648        self.state.lock().death_time
1649    }
1650
1651    /// Resets all death-related state back to alive defaults.
1652    #[inline]
1653    pub fn reset_death_state(&self) {
1654        self.state.lock().reset_death_state();
1655    }
1656}
1657
1658fn weak_living_entity(target: Option<&SharedEntity>) -> Option<WeakEntity> {
1659    let target = target?;
1660    target.is_living_entity().then(|| Arc::downgrade(target))
1661}
1662
1663fn living_entity_from_weak(entity: &mut Option<WeakEntity>) -> Option<SharedEntity> {
1664    let Some(upgraded) = entity.as_ref().and_then(WeakEntity::upgrade) else {
1665        *entity = None;
1666        return None;
1667    };
1668    if !upgraded.is_living_entity() {
1669        *entity = None;
1670        return None;
1671    }
1672    Some(upgraded)
1673}
1674
1675fn living_is_dead(entity: &SharedEntity) -> bool {
1676    entity
1677        .as_living_entity()
1678        .is_none_or(|living| !LivingEntity::is_alive(living))
1679}
1680
1681#[cfg(test)]
1682mod tests;