1use 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
38pub const DEATH_DURATION: i32 = 20;
40pub const HURT_DURATION_TICKS: i32 = 10;
42pub 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#[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
64pub type ActiveMobEffect = MobEffectInstance;
66
67impl MobEffectInstance {
68 #[must_use]
70 pub const fn new(effect: MobEffectRef, amplifier: i32) -> Self {
71 Self::with_duration(effect, INFINITE_EFFECT_DURATION, amplifier)
72 }
73
74 #[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 #[must_use]
90 pub const fn with_ambient(mut self, ambient: bool) -> Self {
91 self.ambient = ambient;
92 self
93 }
94
95 #[must_use]
97 pub const fn with_visible(mut self, visible: bool) -> Self {
98 self.visible = visible;
99 self
100 }
101
102 #[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 #[must_use]
111 pub const fn effect(&self) -> MobEffectRef {
112 self.effect
113 }
114
115 #[must_use]
117 pub const fn duration(&self) -> i32 {
118 self.duration
119 }
120
121 #[must_use]
123 pub const fn amplifier(&self) -> i32 {
124 self.amplifier
125 }
126
127 #[must_use]
130 pub const fn is_ambient(&self) -> bool {
131 self.ambient
132 }
133
134 #[must_use]
136 pub const fn is_visible(&self) -> bool {
137 self.visible
138 }
139
140 #[must_use]
142 pub const fn show_icon(&self) -> bool {
143 self.show_icon
144 }
145
146 #[must_use]
148 pub const fn is_infinite_duration(&self) -> bool {
149 self.duration == INFINITE_EFFECT_DURATION
150 }
151
152 #[must_use]
154 pub const fn ends_within(&self, ticks: i32) -> bool {
155 !self.is_infinite_duration() && self.duration <= ticks
156 }
157
158 #[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 #[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 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#[derive(Debug, Clone, PartialEq, Eq)]
344pub enum MobEffectSyncChange {
345 Update {
347 effect: MobEffectInstance,
349 blend_for_self: bool,
351 },
352 Remove {
354 effect: MobEffectRef,
356 },
357}
358
359impl MobEffectSyncChange {
360 #[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#[derive(Debug, Clone)]
388pub enum MobEffectSyncPacket {
389 Update(CUpdateMobEffect),
391 Remove(CRemoveMobEffect),
393}
394
395#[derive(Debug, Clone, PartialEq)]
397pub struct MobEffectDisplayState {
398 pub particles: ParticleList,
400 pub ambient: bool,
402 pub invisible: bool,
404 pub glowing: bool,
406}
407
408#[derive(Debug, Clone, Copy, PartialEq)]
413pub struct LivingTravelInput {
414 sideways: f32,
415 vertical: f32,
416 forward: f32,
417}
418
419impl LivingTravelInput {
420 pub const ZERO: Self = Self::new(0.0, 0.0, 0.0);
422
423 #[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 #[must_use]
435 pub const fn sideways(self) -> f32 {
436 self.sideways
437 }
438
439 #[must_use]
441 pub const fn vertical(self) -> f32 {
442 self.vertical
443 }
444
445 #[must_use]
447 pub const fn forward(self) -> f32 {
448 self.forward
449 }
450
451 #[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#[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 #[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 #[must_use]
486 pub const fn y_body_rot(self) -> f32 {
487 self.y_body_rot
488 }
489
490 #[must_use]
492 pub const fn y_body_rot_o(self) -> f32 {
493 self.y_body_rot_o
494 }
495
496 #[must_use]
498 pub const fn y_head_rot(self) -> f32 {
499 self.y_head_rot
500 }
501
502 #[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#[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 #[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 #[must_use]
545 pub const fn swinging(self) -> bool {
546 self.current_swing.is_some()
547 }
548
549 #[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 #[must_use]
560 pub const fn swing_time(self) -> i32 {
561 self.swing_time
562 }
563
564 #[must_use]
566 pub const fn old_attack_anim(self) -> f32 {
567 self.old_attack_anim
568 }
569
570 #[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#[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 #[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 #[must_use]
606 pub const fn hand(&self) -> InteractionHand {
607 self.hand
608 }
609
610 #[must_use]
612 pub const fn item(&self) -> ItemRef {
613 self.item
614 }
615
616 #[must_use]
618 pub const fn duration(&self) -> i32 {
619 self.duration
620 }
621
622 #[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
713pub 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 #[must_use]
742 pub fn new(entity_type: EntityTypeRef) -> Self {
743 Self::with_attributes(AttributeMap::new_for_entity(entity_type))
744 }
745
746 #[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 #[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 #[inline]
782 pub const fn attributes(&self) -> &SyncMutex<AttributeMap> {
783 &self.attributes
784 }
785
786 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 #[inline]
800 pub const fn equipment(&self) -> &Shared<dyn EntityEquipment> {
801 &self.equipment
802 }
803
804 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], ¤t_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 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 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 #[must_use]
843 pub fn rotation_state(&self) -> LivingRotationState {
844 self.state.lock().rotation
845 }
846
847 #[must_use]
849 pub fn swing_state(&self) -> LivingSwingState {
850 self.state.lock().swing
851 }
852
853 #[must_use]
855 pub fn active_item_use(&self) -> Option<ActiveItemUseState> {
856 self.state.lock().active_item_use
857 }
858
859 #[must_use]
861 pub fn is_using_item(&self) -> bool {
862 self.state.lock().active_item_use.is_some()
863 }
864
865 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 pub fn stop_using_item(&self) -> Option<ActiveItemUseState> {
881 self.state.lock().active_item_use.take()
882 }
883
884 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 #[must_use]
894 pub fn y_body_rot(&self) -> f32 {
895 self.state.lock().rotation.y_body_rot
896 }
897
898 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 #[must_use]
905 pub fn y_head_rot(&self) -> f32 {
906 self.state.lock().rotation.y_head_rot
907 }
908
909 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 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 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 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 #[must_use]
973 pub fn absorption_amount(&self) -> f32 {
974 self.state.lock().absorption_amount
975 }
976
977 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 pub fn skip_drop_experience(&self) {
988 self.state.lock().skip_drop_experience = true;
989 }
990
991 #[must_use]
993 pub fn was_experience_consumed(&self) -> bool {
994 self.state.lock().skip_drop_experience
995 }
996
997 #[must_use]
999 pub fn no_action_time(&self) -> i32 {
1000 self.state.lock().no_action_time
1001 }
1002
1003 pub fn set_no_action_time(&self, no_action_time: i32) {
1005 self.state.lock().no_action_time = no_action_time;
1006 }
1007
1008 pub fn increment_no_action_time(&self) {
1010 self.state.lock().no_action_time += 1;
1011 }
1012
1013 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 #[must_use]
1067 pub fn has_mob_effect(&self, effect: MobEffectRef) -> bool {
1068 self.active_mob_effects.lock().contains_key(&effect)
1069 }
1070
1071 #[must_use]
1073 pub fn mob_effect(&self, effect: MobEffectRef) -> Option<ActiveMobEffect> {
1074 self.active_mob_effects.lock().get(&effect).cloned()
1075 }
1076
1077 #[must_use]
1079 pub fn active_mob_effects(&self) -> Vec<ActiveMobEffect> {
1080 self.active_mob_effects.lock().values().cloned().collect()
1081 }
1082
1083 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 pub fn set_mob_effect(&self, effect: MobEffectRef, amplifier: i32) {
1125 self.add_mob_effect(MobEffectInstance::new(effect, amplifier));
1126 }
1127
1128 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 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 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 pub fn drain_dirty_mob_effects(&self) -> Vec<MobEffectSyncChange> {
1187 self.dirty_mob_effects.lock().drain(..).collect()
1188 }
1189
1190 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 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 pub(crate) fn mark_effects_dirty(&self) {
1260 self.state.lock().effects_dirty = true;
1261 }
1262
1263 #[inline]
1265 pub fn speed(&self) -> f32 {
1266 self.state.lock().speed
1267 }
1268
1269 #[inline]
1271 pub fn set_speed(&self, speed: f32) {
1272 self.state.lock().speed = speed;
1273 }
1274
1275 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 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 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 #[must_use]
1312 pub fn current_impulse_impact_pos(&self) -> Option<DVec3> {
1313 self.state.lock().current_impulse_impact_pos
1314 }
1315
1316 #[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 #[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 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 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 #[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 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 #[must_use]
1360 pub fn is_fall_flying(&self) -> bool {
1361 self.state.lock().fall_flying
1362 }
1363
1364 pub fn set_fall_flying(&self, fall_flying: bool) {
1366 self.state.lock().fall_flying = fall_flying;
1367 }
1368
1369 #[must_use]
1371 pub fn fall_flying_ticks(&self) -> i32 {
1372 self.state.lock().fall_flying_ticks
1373 }
1374
1375 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 #[must_use]
1388 pub fn is_sprinting(&self) -> bool {
1389 self.state.lock().sprinting
1390 }
1391
1392 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 #[must_use]
1417 pub fn sleeping_pos(&self) -> Option<BlockPos> {
1418 self.state.lock().sleeping_pos
1419 }
1420
1421 pub fn set_sleeping_pos(&self, bed_position: BlockPos) {
1423 self.state.lock().sleeping_pos = Some(bed_position);
1424 }
1425
1426 pub fn clear_sleeping_pos(&self) {
1428 self.state.lock().sleeping_pos = None;
1429 }
1430
1431 #[must_use]
1433 pub fn is_sleeping(&self) -> bool {
1434 self.sleeping_pos().is_some()
1435 }
1436
1437 #[must_use]
1439 pub fn last_climbable_pos(&self) -> Option<BlockPos> {
1440 self.state.lock().last_climbable_pos
1441 }
1442
1443 pub fn set_last_climbable_pos(&self, pos: BlockPos) {
1445 self.state.lock().last_climbable_pos = Some(pos);
1446 }
1447
1448 #[must_use]
1450 pub fn should_discard_friction(&self) -> bool {
1451 self.state.lock().discard_friction
1452 }
1453
1454 pub fn set_discard_friction(&self, discard_friction: bool) {
1456 self.state.lock().discard_friction = discard_friction;
1457 }
1458
1459 #[must_use]
1461 pub fn is_jumping(&self) -> bool {
1462 self.state.lock().jumping
1463 }
1464
1465 pub fn set_jumping(&self, jumping: bool) {
1467 self.state.lock().jumping = jumping;
1468 }
1469
1470 #[must_use]
1472 pub fn travel_input(&self) -> LivingTravelInput {
1473 self.state.lock().travel_input
1474 }
1475
1476 pub fn set_travel_input(&self, input: LivingTravelInput) {
1478 self.state.lock().travel_input = input;
1479 }
1480
1481 pub fn dampen_travel_input(&self) {
1483 let mut state = self.state.lock();
1484 state.travel_input = state.travel_input.dampened();
1485 }
1486
1487 #[must_use]
1489 pub fn no_jump_delay(&self) -> i32 {
1490 self.state.lock().no_jump_delay
1491 }
1492
1493 pub fn set_no_jump_delay(&self, ticks: i32) {
1495 self.state.lock().no_jump_delay = ticks;
1496 }
1497
1498 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 #[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 pub fn invulnerable_time(&self) -> i32 {
1522 self.state.lock().invulnerable_time
1523 }
1524
1525 pub fn set_invulnerable_time(&self, invulnerable_time: i32) {
1527 self.state.lock().invulnerable_time = invulnerable_time;
1528 }
1529
1530 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 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 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 #[must_use]
1555 pub fn hurt_time(&self) -> i32 {
1556 self.state.lock().hurt_time
1557 }
1558
1559 #[must_use]
1561 pub fn hurt_duration(&self) -> i32 {
1562 self.state.lock().hurt_duration
1563 }
1564
1565 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 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 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 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 #[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 #[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 #[must_use]
1648 pub fn last_hurt_by_player_uuid(&self) -> Option<Uuid> {
1649 self.state.lock().last_hurt_by_player
1650 }
1651
1652 #[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 #[must_use]
1661 pub fn last_hurt_by_mob_timestamp(&self) -> i32 {
1662 self.state.lock().last_hurt_by_mob_timestamp
1663 }
1664
1665 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 #[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 #[must_use]
1681 pub fn last_hurt_mob_timestamp(&self) -> i32 {
1682 self.state.lock().last_hurt_mob_timestamp
1683 }
1684
1685 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 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 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 #[must_use]
1727 pub fn is_death_processed(&self) -> bool {
1728 self.state.lock().death_processed
1729 }
1730
1731 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 #[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 #[must_use]
1753 pub fn death_time(&self) -> i32 {
1754 self.state.lock().death_time
1755 }
1756
1757 #[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
1790pub 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}