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