Skip to main content

steel_core/entity/projectile/
mod.rs

1//! Vanilla `Projectile` class hierarchy, mirrored as Rust traits + base structs.
2//!
3//! Mirrors `net.minecraft.world.entity.projectile`:
4//! `Entity → Projectile → ThrowableProjectile → ThrowableItemProjectile`.
5//! Concrete projectiles embed [`ProjectileBase`] (owner / left-owner / shot state)
6//! and implement the trait stack; the per-layer tick logic chains explicitly via
7//! [`Projectile::projectile_base_tick`] and
8//! [`ThrowableProjectile::throwable_projectile_tick`] (vanilla `super.tick()`).
9//!
10//! The block + entity move-vector raycast mirrors `ProjectileUtil`.
11
12mod throwable;
13mod throwable_item;
14
15use std::mem;
16use std::sync::{Arc, Weak};
17
18use glam::DVec3;
19use simdnbt::borrow::NbtCompound as BorrowedNbtCompoundView;
20use simdnbt::owned::{NbtCompound, NbtTag};
21use steel_math::{DEGREE_180, DEGREE_360};
22use steel_registry::blocks::block_state_ext::BlockStateExt as _;
23use steel_registry::item_stack::ItemStack;
24use steel_registry::vanilla_entity_type_tags::EntityTypeTag;
25use steel_registry::vanilla_game_rules::{MOB_GRIEFING, PROJECTILES_CAN_BREAK_BLOCKS};
26use steel_registry::{REGISTRY, TaggedRegistryExt as _, vanilla_game_events};
27use steel_utils::axis::Axis;
28use steel_utils::locks::SyncMutex;
29use steel_utils::{BlockPos, UuidExt, WorldAabb};
30use uuid::Uuid;
31
32use crate::behavior::BLOCK_BEHAVIORS;
33use crate::enchantment_helper;
34use crate::entity::damage::DamageSource;
35use crate::entity::{Entity, LivingEntity, SharedEntity};
36use crate::player::Player;
37use crate::world::game_event::GameEventContext;
38use crate::world::{ClipBlockShape, ClipFluid, ClipHitResult, World};
39
40use super::{
41    BUBBLE_COLUMN_ABOVE_UP_ACCELERATION, BUBBLE_COLUMN_DOWN_ACCELERATION,
42    BUBBLE_COLUMN_INSIDE_UP_ACCELERATION,
43};
44
45pub use throwable::ThrowableProjectile;
46pub use throwable_item::ThrowableItemProjectile;
47
48/// Vanilla `Projectile.shoot` per-axis spread scale (`0.0172275 * uncertainty`).
49const SHOOT_INACCURACY_SCALE: f64 = 0.0172_275;
50
51/// Vanilla `ProjectileUtil.DEFAULT_ENTITY_HIT_RESULT_MARGIN`.
52const MAX_ENTITY_HIT_MARGIN: f64 = 0.3;
53
54/// Vanilla `ThrowableItemProjectile` spawn offset below the shooter's eye.
55const THROWN_ITEM_SPAWN_EYE_OFFSET: f64 = 0.1;
56
57/// Mirrors vanilla `RandomSource.triangle(mode, deviation)`.
58#[must_use]
59pub fn triangle_random(mode: f64, deviation: f64) -> f64 {
60    mode + deviation * (rand::random::<f64>() - rand::random::<f64>())
61}
62
63/// Result of a projectile move-vector raycast (vanilla `HitResult`).
64pub enum ProjectileHit {
65    /// The pearl's path entered a block collider.
66    Block {
67        /// Exact entry location.
68        location: DVec3,
69        /// The underlying block clip result.
70        hit: ClipHitResult,
71    },
72    /// The pearl's path intersected an entity.
73    Entity(EntityHitResult),
74}
75
76impl ProjectileHit {
77    /// Returns the world-space hit location.
78    #[must_use]
79    pub const fn location(&self) -> DVec3 {
80        match self {
81            Self::Block { location, .. } => *location,
82            Self::Entity(hit) => hit.location,
83        }
84    }
85}
86
87/// A projectile-versus-entity raycast hit (vanilla `EntityHitResult`).
88pub struct EntityHitResult {
89    /// The entity that was hit.
90    pub entity: SharedEntity,
91    /// The world-space location of the hit.
92    pub location: DVec3,
93}
94
95/// Vanilla projectile deflection behaviors (`ProjectileDeflection`).
96#[derive(Clone, Copy, Debug, Eq, PartialEq)]
97pub enum ProjectileDeflection {
98    /// Continue with normal hit handling.
99    None,
100    /// Reverse the projectile with Vanilla's randomized yaw adjustment.
101    Reverse,
102    /// Redirect the projectile along the deflecting entity's look direction.
103    AimDeflect,
104    /// Redirect the projectile along the deflecting entity's movement direction.
105    MomentumDeflect,
106}
107
108impl ProjectileDeflection {
109    fn apply(self, projectile: &dyn Projectile, deflecting_entity: Option<&dyn Entity>) {
110        match self {
111            Self::None => {}
112            Self::Reverse => {
113                let rotation = rand::random_range(170.0..190.0);
114                projectile.set_velocity(projectile.velocity() * -0.5);
115                let (yaw, pitch) = projectile.rotation();
116                projectile.set_rotation((yaw + rotation, pitch));
117                let (old_yaw, old_pitch) = projectile.base().old_rotation();
118                projectile
119                    .base()
120                    .set_old_rotation((old_yaw + rotation, old_pitch));
121                projectile.mark_velocity_sync();
122            }
123            Self::AimDeflect => {
124                let Some(entity) = deflecting_entity else {
125                    return;
126                };
127                projectile.set_velocity(entity.look_angle());
128                projectile.mark_velocity_sync();
129            }
130            Self::MomentumDeflect => {
131                let Some(entity) = deflecting_entity else {
132                    return;
133                };
134                let movement = entity.velocity();
135                let length = movement.length();
136                let normalized = if length < 1.0e-5 {
137                    DVec3::ZERO
138                } else {
139                    movement / length
140                };
141                projectile.set_velocity(normalized);
142                projectile.mark_velocity_sync();
143            }
144        }
145    }
146}
147
148struct ProjectileState {
149    owner: Option<Uuid>,
150    owner_entity: Option<Weak<dyn Entity>>,
151    left_owner: bool,
152    left_owner_checked: bool,
153    has_been_shot: bool,
154    last_deflected_by: Option<Weak<dyn Entity>>,
155}
156
157/// Runtime fields shared by vanilla projectiles (vanilla `Projectile` fields).
158pub struct ProjectileBase {
159    state: SyncMutex<ProjectileState>,
160}
161
162impl ProjectileBase {
163    /// Creates default projectile runtime state.
164    #[must_use]
165    pub const fn new() -> Self {
166        Self {
167            state: SyncMutex::new(ProjectileState {
168                owner: None,
169                owner_entity: None,
170                left_owner: false,
171                left_owner_checked: false,
172                has_been_shot: false,
173                last_deflected_by: None,
174            }),
175        }
176    }
177}
178
179impl Default for ProjectileBase {
180    fn default() -> Self {
181        Self::new()
182    }
183}
184
185/// Object-safe access to a projectile trait object from default [`Projectile`] methods.
186pub trait ProjectileEventSource {
187    /// Returns this entity as a projectile.
188    fn as_projectile_event_source(&self) -> &dyn Projectile;
189}
190
191impl<T: Projectile> ProjectileEventSource for T {
192    fn as_projectile_event_source(&self) -> &dyn Projectile {
193        self
194    }
195}
196
197/// Vanilla-shaped behavior shared by entities that extend `Projectile`.
198pub trait Projectile: Entity + ProjectileEventSource {
199    /// Returns shared projectile runtime state.
200    fn projectile_base(&self) -> &ProjectileBase;
201
202    /// Returns the horizontal hurt-knockback vector used for this projectile.
203    fn calculate_horizontal_hurt_knockback_direction(
204        &self,
205        _hurt_entity: &dyn LivingEntity,
206        _damage_source: &DamageSource,
207    ) -> (f64, f64) {
208        let movement = self.velocity();
209        (movement.x, movement.z)
210    }
211
212    /// Applies bubble-column surface acceleration without clamping velocity.
213    fn on_above_bubble_column_projectile(&self, drag_down: bool, pos: BlockPos) {
214        let acceleration = if drag_down {
215            -BUBBLE_COLUMN_DOWN_ACCELERATION
216        } else {
217            BUBBLE_COLUMN_ABOVE_UP_ACCELERATION
218        };
219        self.set_velocity(self.velocity() + DVec3::new(0.0, acceleration, 0.0));
220
221        if let Some(world) = self.level() {
222            world.send_bubble_column_particles(pos);
223        }
224    }
225
226    /// Applies inside-bubble-column acceleration without clamping velocity.
227    fn on_inside_bubble_column_projectile(&self, drag_down: bool) {
228        let acceleration = if drag_down {
229            -BUBBLE_COLUMN_DOWN_ACCELERATION
230        } else {
231            BUBBLE_COLUMN_INSIDE_UP_ACCELERATION
232        };
233        self.set_velocity(self.velocity() + DVec3::new(0.0, acceleration, 0.0));
234        self.reset_fall_distance();
235    }
236
237    /// Sets the owner UUID. Vanilla stores an `EntityReference`; Steel stores the
238    /// UUID and resolves lazily.
239    // TODO: introduce an `EntityReference` type to cache the resolved owner.
240    fn set_owner_uuid(&self, owner: Option<Uuid>) {
241        let mut state = self.projectile_base().state.lock();
242        state.owner = owner;
243        state.owner_entity = None;
244    }
245
246    /// Sets the owning entity and caches its live reference.
247    fn set_owner_entity(&self, owner: Option<&SharedEntity>) {
248        let mut state = self.projectile_base().state.lock();
249        state.owner = owner.map(|owner| owner.uuid());
250        state.owner_entity = owner.map(Arc::downgrade);
251    }
252
253    /// Caches a live owner reference when it matches the saved owner UUID.
254    fn cache_owner_entity(&self, owner: &SharedEntity) {
255        let mut state = self.projectile_base().state.lock();
256        if state.owner == Some(owner.uuid()) {
257            state.owner_entity = Some(Arc::downgrade(owner));
258        }
259    }
260
261    /// Returns the owner UUID, if any.
262    fn owner_uuid(&self) -> Option<Uuid> {
263        self.projectile_base().state.lock().owner
264    }
265
266    /// Resolves the owning entity in the current world (vanilla `Projectile.getOwner`).
267    fn get_owner(&self) -> Option<SharedEntity> {
268        let uuid = self.owner_uuid()?;
269        if let Some(owner) = self
270            .projectile_base()
271            .state
272            .lock()
273            .owner_entity
274            .as_ref()
275            .and_then(Weak::upgrade)
276            && !owner.is_removed()
277            && owner.uuid() == uuid
278        {
279            return Some(owner);
280        }
281
282        let owner = self.level()?.get_entity_by_uuid(&uuid)?;
283        self.cache_owner_entity(&owner);
284        Some(owner)
285    }
286
287    /// Returns vanilla `Projectile.mayInteract` for a block position.
288    fn projectile_may_interact(&self, world: &World, pos: steel_utils::BlockPos) -> bool {
289        let Some(owner) = self.get_owner() else {
290            return true;
291        };
292        if owner.as_player().is_some() {
293            return owner.may_interact(world, pos);
294        }
295        world.get_game_rule(&MOB_GRIEFING)
296    }
297
298    /// Returns vanilla `Projectile.mayBreak`.
299    fn may_break(&self, world: &World) -> bool {
300        REGISTRY
301            .entity_types
302            .is_in_tag(self.entity_type(), &EntityTypeTag::IMPACT_PROJECTILES)
303            && world.get_game_rule(&PROJECTILES_CAN_BREAK_BLOCKS)
304    }
305
306    /// Returns vanilla `Projectile.ownedBy`.
307    fn owned_by(&self, entity: &dyn Entity) -> bool {
308        self.owner_uuid() == Some(entity.uuid())
309    }
310
311    /// Returns vanilla `Projectile.hasBeenShot`.
312    fn has_been_shot(&self) -> bool {
313        self.projectile_base().state.lock().has_been_shot
314    }
315
316    /// Sets vanilla `Projectile.hasBeenShot`.
317    fn set_has_been_shot(&self, value: bool) {
318        self.projectile_base().state.lock().has_been_shot = value;
319    }
320
321    /// Returns vanilla `Projectile.leftOwner`.
322    fn left_owner(&self) -> bool {
323        self.projectile_base().state.lock().left_owner
324    }
325
326    /// Runs vanilla `Projectile.checkLeftOwner`.
327    fn check_left_owner(&self) {
328        let mut state = self.projectile_base().state.lock();
329        if state.left_owner || state.left_owner_checked {
330            return;
331        }
332        state.left_owner_checked = true;
333        drop(state);
334
335        let left = self.is_outside_owner_collision_range();
336        self.projectile_base().state.lock().left_owner = left;
337    }
338
339    /// Resets the per-tick left-owner check flag (vanilla clears it after `tick`).
340    fn reset_left_owner_checked(&self) {
341        self.projectile_base().state.lock().left_owner_checked = false;
342    }
343
344    /// Returns vanilla `Projectile.isOutsideOwnerCollisionRange`.
345    fn is_outside_owner_collision_range(&self) -> bool {
346        let Some(owner) = self.get_owner() else {
347            return true;
348        };
349        let aabb = self
350            .bounding_box()
351            .expand_towards(self.velocity())
352            .inflate(1.0);
353        let root_vehicle = owner.root_vehicle().unwrap_or_else(|| owner.clone());
354        let mut to_check = vec![root_vehicle];
355        let mut visited = Vec::new();
356
357        while let Some(entity) = to_check.pop() {
358            let entity_id = entity.id();
359            if visited.contains(&entity_id) {
360                continue;
361            }
362            visited.push(entity_id);
363
364            if entity.is_pickable() && aabb.intersects(entity.bounding_box()) {
365                return false;
366            }
367            to_check.extend(entity.passengers());
368        }
369
370        true
371    }
372
373    /// Returns vanilla `Projectile.canHitEntity`.
374    /// Use this if you don't need to override.
375    fn can_hit_entity(&self, entity: &dyn Entity) -> bool {
376        self.base_can_hit_entity(entity)
377    }
378
379    /// Returns vanilla `Projectile.canHitEntity`.
380    /// We had to split this up, because Rust doesn't allow for calling super trait fns (without causing unbounded recursion) for overrides, so otherwise vanilla behavior would not be achievable.
381    fn base_can_hit_entity(&self, entity: &dyn Entity) -> bool {
382        if !entity.can_be_hit_by_projectile() {
383            return false;
384        }
385        let Some(owner) = self.get_owner() else {
386            return true;
387        };
388        self.left_owner() || !owner.is_passenger_of_same_vehicle(entity)
389    }
390
391    /// Returns vanilla `Projectile.getMovementToShoot`.
392    fn get_movement_to_shoot(&self, direction: DVec3, power: f32, uncertainty: f32) -> DVec3 {
393        let deviation = SHOOT_INACCURACY_SCALE * f64::from(uncertainty);
394        let jitter = DVec3::new(
395            triangle_random(0.0, deviation),
396            triangle_random(0.0, deviation),
397            triangle_random(0.0, deviation),
398        );
399        (direction.normalize_or_zero() + jitter) * f64::from(power)
400    }
401
402    /// Runs vanilla `Projectile.shoot`.
403    fn shoot(&self, direction: DVec3, power: f32, uncertainty: f32) {
404        let movement = self.get_movement_to_shoot(direction, power, uncertainty);
405        self.set_velocity(movement);
406        self.mark_velocity_sync();
407
408        let horizontal = (movement.x * movement.x + movement.z * movement.z).sqrt();
409        let yaw = movement.x.atan2(movement.z).to_degrees() as f32;
410        let pitch = movement.y.atan2(horizontal).to_degrees() as f32;
411        self.set_rotation((yaw, pitch));
412        self.base().set_old_rotation_to_current();
413    }
414
415    /// Runs vanilla `Projectile.shootFromRotation`.
416    fn shoot_from_rotation(
417        &self,
418        source: &dyn Entity,
419        x_rot: f32,
420        y_rot: f32,
421        y_offset: f32,
422        power: f32,
423        uncertainty: f32,
424    ) {
425        let yaw = y_rot.to_radians();
426        let pitch = x_rot.to_radians();
427        let pitch_offset = (x_rot + y_offset).to_radians();
428        let direction = DVec3::new(
429            f64::from(-yaw.sin() * pitch.cos()),
430            f64::from(-pitch_offset.sin()),
431            f64::from(yaw.cos() * pitch.cos()),
432        );
433        self.shoot(direction, power, uncertainty);
434
435        let source_movement = source.known_movement();
436        let added_y = if source.on_ground() {
437            0.0
438        } else {
439            source_movement.y
440        };
441        self.set_velocity(
442            self.velocity() + DVec3::new(source_movement.x, added_y, source_movement.z),
443        );
444    }
445
446    /// Runs vanilla `Projectile.updateRotation` (lerped toward the movement vector).
447    fn update_rotation(&self) {
448        let movement = self.velocity();
449        let horizontal = (movement.x * movement.x + movement.z * movement.z).sqrt();
450        let (yaw_old, pitch_old) = self.base().old_rotation();
451        let yaw = lerp_rotation(yaw_old, movement.x.atan2(movement.z).to_degrees() as f32);
452        let pitch = lerp_rotation(pitch_old, movement.y.atan2(horizontal).to_degrees() as f32);
453        self.set_rotation((yaw, pitch));
454    }
455
456    /// Applies a Vanilla projectile deflection.
457    fn deflect(
458        &self,
459        deflection: ProjectileDeflection,
460        deflecting_entity: Option<&dyn Entity>,
461        new_owner_uuid: Option<Uuid>,
462        new_owner_entity: Option<&SharedEntity>,
463        by_attack: bool,
464    ) -> bool {
465        deflection.apply(self.as_projectile_event_source(), deflecting_entity);
466        let mut state = self.projectile_base().state.lock();
467        state.owner = new_owner_uuid;
468        state.owner_entity = new_owner_entity.map(Arc::downgrade);
469        drop(state);
470        self.on_deflection(by_attack);
471        true
472    }
473
474    /// Called after a successful Vanilla projectile deflection.
475    fn on_deflection(&self, _by_attack: bool) {}
476
477    /// Returns whether this projectile bounces from the world border.
478    fn should_bounce_on_world_border(&self) -> bool {
479        false
480    }
481
482    /// Casts the move vector and returns the nearest block/entity hit (vanilla
483    /// `ProjectileUtil.getHitResultOnMoveVector` with `this::canHitEntity`).
484    fn get_hit_result_on_move_vector(&self) -> Option<ProjectileHit> {
485        const EPSILON: f64 = 1.0e-12;
486        let world = self.level()?;
487        let from = self.position();
488        let delta = self.velocity();
489        if delta.length_squared() < EPSILON {
490            return None;
491        }
492        let to = from + delta;
493
494        let block_hit =
495            world.clip_including_border(from, to, ClipBlockShape::Collider, ClipFluid::None);
496        let entity_end = if block_hit.is_miss() {
497            to
498        } else {
499            block_hit.location
500        };
501
502        let search_box = self.bounding_box().expand_towards(delta).inflate(1.0);
503        let margin = compute_margin(self.tick_count());
504        let self_id = self.id();
505        let entity_hit =
506            get_entity_hit_result(world.as_ref(), from, entity_end, search_box, margin, |e| {
507                e.id() != self_id && self.can_hit_entity(e)
508            });
509
510        if let Some(hit) = entity_hit {
511            return Some(ProjectileHit::Entity(hit));
512        }
513        if !block_hit.is_miss() {
514            return Some(ProjectileHit::Block {
515                location: block_hit.location,
516                hit: block_hit,
517            });
518        }
519        None
520    }
521
522    /// Vanilla `Projectile.hitTargetOrDeflectSelf`.
523    fn hit_target_or_deflect_self(&self, hit: &ProjectileHit) -> ProjectileDeflection {
524        if let ProjectileHit::Entity(entity_hit) = hit {
525            let deflection = entity_hit
526                .entity
527                .deflection(self.as_projectile_event_source());
528            if deflection != ProjectileDeflection::None {
529                let already_deflected = self
530                    .projectile_base()
531                    .state
532                    .lock()
533                    .last_deflected_by
534                    .as_ref()
535                    .and_then(Weak::upgrade)
536                    .is_some_and(|last| Arc::ptr_eq(&last, &entity_hit.entity));
537                let owner_uuid = self.owner_uuid();
538                let owner_entity = self.get_owner();
539                if !already_deflected
540                    && self.deflect(
541                        deflection,
542                        Some(entity_hit.entity.as_ref()),
543                        owner_uuid,
544                        owner_entity.as_ref(),
545                        false,
546                    )
547                {
548                    self.projectile_base().state.lock().last_deflected_by =
549                        Some(Arc::downgrade(&entity_hit.entity));
550                }
551                return deflection;
552            }
553        } else if let ProjectileHit::Block { hit, .. } = hit
554            && self.should_bounce_on_world_border()
555            && hit.world_border_hit
556        {
557            let deflection = ProjectileDeflection::Reverse;
558            let owner_uuid = self.owner_uuid();
559            let owner_entity = self.get_owner();
560            if self.deflect(deflection, None, owner_uuid, owner_entity.as_ref(), false) {
561                self.set_velocity(self.velocity() * 0.2);
562                return deflection;
563            }
564        }
565
566        self.on_hit(hit);
567        ProjectileDeflection::None
568    }
569
570    /// Vanilla `Projectile.onHit`. Subclasses override this and call
571    /// [`Projectile::projectile_on_hit`] for the base dispatch (`super.onHit()`).
572    fn on_hit(&self, hit: &ProjectileHit) {
573        self.projectile_on_hit(hit);
574    }
575
576    /// The base `Projectile.onHit` dispatch to block/entity handlers. Not meant to
577    /// be overridden — override [`Projectile::on_hit`] and delegate here instead.
578    fn projectile_on_hit(&self, hit: &ProjectileHit) {
579        let world = self.level();
580        match hit {
581            ProjectileHit::Entity(entity_hit) => {
582                if REGISTRY.entity_types.is_in_tag(
583                    entity_hit.entity.entity_type(),
584                    &EntityTypeTag::REDIRECTABLE_PROJECTILE,
585                ) && let Some(projectile) = entity_hit.entity.as_projectile()
586                {
587                    let owner_uuid = self.owner_uuid();
588                    let owner_entity = self.get_owner();
589                    projectile.deflect(
590                        ProjectileDeflection::AimDeflect,
591                        owner_entity.as_deref(),
592                        owner_uuid,
593                        owner_entity.as_ref(),
594                        true,
595                    );
596                }
597                self.on_hit_entity(&entity_hit.entity, entity_hit.location);
598                if let Some(world) = world {
599                    world.game_event_at(
600                        &vanilla_game_events::PROJECTILE_LAND,
601                        entity_hit.location,
602                        &GameEventContext::new(Some(self.as_entity_event_source()), None),
603                    );
604                }
605            }
606            ProjectileHit::Block { hit, .. } => {
607                self.on_hit_block(hit);
608                if let Some(world) = world {
609                    let state = world.get_block_state(hit.block_pos);
610                    world.game_event(
611                        &vanilla_game_events::PROJECTILE_LAND,
612                        hit.block_pos,
613                        &GameEventContext::new(Some(self.as_entity_event_source()), Some(state)),
614                    );
615                }
616            }
617        }
618    }
619
620    /// Vanilla `Projectile.onHitEntity` (no-op by default).
621    fn on_hit_entity(&self, _entity: &SharedEntity, _location: DVec3) {}
622
623    /// Vanilla `Projectile.onHitBlock`.
624    fn on_hit_block(&self, hit: &ClipHitResult) {
625        self.projectile_on_hit_block(hit);
626    }
627
628    /// The base `Projectile.onHitBlock` implementation. Concrete projectiles
629    /// that override the hook call this to preserve the Java `super` dispatch.
630    fn projectile_on_hit_block(&self, hit: &ClipHitResult) {
631        let Some(world) = self.level() else {
632            return;
633        };
634        let Some(projectile) = self.as_projectile() else {
635            return;
636        };
637        let state = world.get_block_state(hit.block_pos);
638        BLOCK_BEHAVIORS
639            .get_behavior(state.get_block())
640            .on_projectile_hit(state, &world, hit, projectile);
641    }
642
643    /// Vanilla `Projectile.hurtServer`: marks hurt unless invulnerable to the
644    /// base checks, but never takes damage.
645    fn hurt(&self, _world: &World, source: &DamageSource, _amount: f32) -> bool {
646        if !self.is_invulnerable_to_base(source) {
647            self.mark_hurt();
648        }
649        false
650    }
651
652    /// Vanilla `Projectile.tick` (the `super.tick()` reached from subclasses).
653    fn projectile_base_tick(&self) {
654        if !self.has_been_shot() {
655            let owner = self.get_owner();
656            self.game_event_with_source_entity(
657                &vanilla_game_events::PROJECTILE_SHOOT,
658                owner.as_deref(),
659            );
660            self.set_has_been_shot(true);
661        }
662        self.check_left_owner();
663        self.default_tick();
664        self.reset_left_owner_checked();
665    }
666
667    /// Saves vanilla `Projectile` fields (`Owner`, `LeftOwner`, `HasBeenShot`).
668    fn save_projectile(&self, nbt: &mut NbtCompound) {
669        let state = self.projectile_base().state.lock();
670        if let Some(owner) = state.owner {
671            nbt.insert("Owner", NbtTag::IntArray(owner.to_int_array().to_vec()));
672        }
673        if state.left_owner {
674            nbt.insert("LeftOwner", 1i8);
675        }
676        nbt.insert("HasBeenShot", i8::from(state.has_been_shot));
677    }
678
679    /// Loads vanilla `Projectile` fields.
680    fn load_projectile(&self, nbt: BorrowedNbtCompoundView<'_, '_>) {
681        let mut state = self.projectile_base().state.lock();
682        if let Some(owner_arr) = nbt.int_array("Owner")
683            && let Some(uuid) = Uuid::from_int_array(&owner_arr)
684        {
685            state.owner = Some(uuid);
686        }
687        state.left_owner = nbt.byte("LeftOwner").is_some_and(|value| value != 0);
688        state.has_been_shot = nbt.byte("HasBeenShot").is_some_and(|value| value != 0);
689    }
690}
691
692/// Spawns a throwable-item projectile from the source's eye along its look
693/// direction and registers it in the world.
694///
695/// Mirrors vanilla `Projectile.spawnProjectileFromRotation` + `spawnProjectile`:
696/// create the projectile at the eye minus 0.1, set its owner, shoot it with
697/// `shootFromRotation`, add it to the world, then run `applyOnProjectileSpawned`
698/// (the `minecraft:projectile_spawned` enchantment effects). `create` receives
699/// the spawn position and must return the concrete projectile type. Returns
700/// `None` when the world rejects the projectile so the caller can fail the use.
701#[must_use]
702pub fn spawn_throwable_item_projectile<E>(
703    world: &Arc<World>,
704    player: &Player,
705    item_stack: &mut ItemStack,
706    power: f32,
707    uncertainty: f32,
708    create: impl FnOnce(DVec3) -> E,
709) -> Option<SharedEntity>
710where
711    E: Projectile + ThrowableItemProjectile + Entity + 'static,
712{
713    let player_pos = player.position();
714    let spawn_pos = DVec3::new(
715        player_pos.x,
716        player.get_eye_y() - THROWN_ITEM_SPAWN_EYE_OFFSET,
717        player_pos.z,
718    );
719
720    let entity = create(spawn_pos);
721    if let Some(owner) = world.players.get_by_uuid(&player.gameprofile.id) {
722        let owner: SharedEntity = owner;
723        entity.set_owner_entity(Some(&owner));
724    } else {
725        entity.set_owner_uuid(Some(player.gameprofile.id));
726    }
727    entity.set_item_clamped(item_stack.clone());
728
729    let (yaw, player_pitch) = player.rotation();
730    entity.shoot_from_rotation(player, player_pitch, yaw, 0.0, power, uncertainty);
731
732    let entity: SharedEntity = Arc::new(entity);
733    if let Err(error) = world.try_add_entity(Arc::clone(&entity)) {
734        log::debug!("failed to spawn throwable item projectile: {error}");
735        return None;
736    }
737    enchantment_helper::on_projectile_spawned(world, item_stack, entity.as_ref(), Some(player));
738
739    Some(entity)
740}
741
742/// Vanilla `ProjectileUtil.computeMargin`: ramps the entity hit margin from 0 to
743/// 0.3 over the first ticks of flight.
744#[must_use]
745pub fn compute_margin(tick_count: i32) -> f64 {
746    (f64::from(tick_count - 2) / 20.0).clamp(0.0, MAX_ENTITY_HIT_MARGIN)
747}
748
749/// Result of vanilla `ProjectileUtil.getHitResultOnViewVector`.
750pub enum ViewVectorHitResult {
751    /// No block or matching entity was hit within range.
752    Miss,
753    /// Nearest hit was a block collider (or world border).
754    Block(ClipHitResult),
755    /// Nearest hit was a matching entity, closer than any block along the ray.
756    Entity(EntityHitResult),
757}
758
759/// Vanilla `ProjectileUtil.getHitResultOnViewVector`.
760///
761/// Casts from the source eye along the look vector for `distance` blocks using
762/// collider shapes, then prefers a matching entity hit over the block hit.
763#[must_use]
764pub fn get_hit_result_on_view_vector(
765    world: &World,
766    source: &dyn Entity,
767    distance: f64,
768    matching: impl Fn(&dyn Entity) -> bool,
769) -> ViewVectorHitResult {
770    let position = source.position();
771    let from = DVec3::new(position.x, source.get_eye_y(), position.z);
772    let delta = source.look_angle() * distance;
773    let to = from + delta;
774
775    let block_hit =
776        world.clip_including_border(from, to, ClipBlockShape::Collider, ClipFluid::None);
777    let entity_end = if block_hit.is_miss() {
778        to
779    } else {
780        block_hit.location
781    };
782
783    let search_box = source.bounding_box().expand_towards(delta).inflate(1.0);
784    let source_id = source.id();
785    let entity_hit = get_entity_hit_result(world, from, entity_end, search_box, 0.0, |entity| {
786        entity.id() != source_id && matching(entity)
787    });
788
789    if let Some(hit) = entity_hit {
790        return ViewVectorHitResult::Entity(hit);
791    }
792    if !block_hit.is_miss() {
793        return ViewVectorHitResult::Block(block_hit);
794    }
795    ViewVectorHitResult::Miss
796}
797
798/// Vanilla `ProjectileUtil.getEntityHitResult` (entity-margin overload): returns
799/// the nearest entity whose inflated box the segment `from -> to` enters.
800fn get_entity_hit_result(
801    world: &World,
802    from: DVec3,
803    to: DVec3,
804    search_box: WorldAabb,
805    margin: f64,
806    predicate: impl Fn(&dyn Entity) -> bool,
807) -> Option<EntityHitResult> {
808    let mut nearest: Option<EntityHitResult> = None;
809    let mut nearest_dist_sq = f64::MAX;
810
811    for entity in world.get_entities_in_aabb(&search_box) {
812        if !predicate(entity.as_ref()) {
813            continue;
814        }
815        let target_box = entity.bounding_box().inflate(margin);
816        let Some(location) = clip_segment(target_box, from, to) else {
817            continue;
818        };
819        let dist_sq = from.distance_squared(location);
820        if dist_sq < nearest_dist_sq {
821            nearest_dist_sq = dist_sq;
822            nearest = Some(EntityHitResult { entity, location });
823        }
824    }
825
826    nearest
827}
828
829/// Clips the segment `from -> to` against `aabb`, returning the entry point.
830///
831/// Returns `from` when the segment starts inside the box and `None` when it never
832/// intersects. Mirrors vanilla `AABB.clip` using the slab method.
833fn clip_segment(aabb: WorldAabb, from: DVec3, to: DVec3) -> Option<DVec3> {
834    const EPSILON: f64 = 1.0e-7;
835
836    let direction = to - from;
837    let mut t_min = 0.0_f64;
838    let mut t_max = 1.0_f64;
839
840    for axis in [Axis::X, Axis::Y, Axis::Z] {
841        let start = axis_component(from, axis);
842        let delta = axis_component(direction, axis);
843        let axis_min = aabb.min(axis);
844        let axis_max = aabb.max(axis);
845
846        if delta.abs() < EPSILON {
847            if start < axis_min || start > axis_max {
848                return None;
849            }
850            continue;
851        }
852
853        let inv_delta = 1.0 / delta;
854        let mut low = (axis_min - start) * inv_delta;
855        let mut high = (axis_max - start) * inv_delta;
856        if low > high {
857            mem::swap(&mut low, &mut high);
858        }
859
860        t_min = t_min.max(low);
861        t_max = t_max.min(high);
862        if t_min > t_max {
863            return None;
864        }
865    }
866
867    Some(from + direction * t_min)
868}
869
870const fn axis_component(vec: DVec3, axis: Axis) -> f64 {
871    match axis {
872        Axis::X => vec.x,
873        Axis::Y => vec.y,
874        Axis::Z => vec.z,
875    }
876}
877
878/// Vanilla `Mth.lerp(0.2, rotO, rot)` after wrapping the old angle into range.
879fn lerp_rotation(mut rot_old: f32, rot: f32) -> f32 {
880    while rot - rot_old < -DEGREE_180 {
881        rot_old -= DEGREE_360;
882    }
883    while rot - rot_old >= DEGREE_180 {
884        rot_old += DEGREE_360;
885    }
886    rot_old + 0.2 * (rot - rot_old)
887}
888
889#[cfg(test)]
890mod tests {
891    use super::*;
892    use steel_registry::{
893        blocks::properties::{BlockStateProperties, Tilt},
894        entity_type::EntityTypeRef,
895        init_vanilla_registry, vanilla_blocks, vanilla_entities,
896    };
897    use steel_utils::{BlockPos, ChunkPos, Direction, types::UpdateFlags};
898
899    use crate::{
900        behavior::init_behaviors,
901        entity::{EntityBase, entities::FireworkRocketEntity},
902        test_support::{test_world, world_border_projectile_test_world},
903    };
904
905    struct OwnerCollisionProjectile {
906        base: EntityBase,
907        projectile_base: ProjectileBase,
908    }
909
910    impl OwnerCollisionProjectile {
911        fn new(id: i32, position: DVec3) -> Self {
912            Self {
913                base: EntityBase::new(
914                    id,
915                    position,
916                    vanilla_entities::ENDER_PEARL.dimensions,
917                    Weak::new(),
918                ),
919                projectile_base: ProjectileBase::new(),
920            }
921        }
922    }
923
924    crate::entity::impl_test_downcast_type!(OwnerCollisionProjectile);
925
926    impl Entity for OwnerCollisionProjectile {
927        fn base(&self) -> &EntityBase {
928            &self.base
929        }
930
931        fn entity_type(&self) -> EntityTypeRef {
932            &vanilla_entities::ENDER_PEARL
933        }
934    }
935
936    impl Projectile for OwnerCollisionProjectile {
937        fn projectile_base(&self) -> &ProjectileBase {
938            &self.projectile_base
939        }
940    }
941
942    struct OwnerCollisionTestEntity {
943        base: EntityBase,
944        pickable: bool,
945        entity_type: EntityTypeRef,
946    }
947
948    impl OwnerCollisionTestEntity {
949        fn shared(id: i32, position: DVec3, pickable: bool) -> SharedEntity {
950            Self::shared_with_type(id, position, pickable, &vanilla_entities::PIG)
951        }
952
953        fn shared_with_type(
954            id: i32,
955            position: DVec3,
956            pickable: bool,
957            entity_type: EntityTypeRef,
958        ) -> SharedEntity {
959            Arc::new(Self {
960                base: EntityBase::new(id, position, entity_type.dimensions, Weak::new()),
961                pickable,
962                entity_type,
963            })
964        }
965    }
966
967    crate::entity::impl_test_downcast_type!(OwnerCollisionTestEntity);
968
969    impl Entity for OwnerCollisionTestEntity {
970        fn base(&self) -> &EntityBase {
971            &self.base
972        }
973
974        fn entity_type(&self) -> EntityTypeRef {
975            self.entity_type
976        }
977
978        fn is_pickable(&self) -> bool {
979            self.pickable && !self.is_removed()
980        }
981    }
982
983    #[test]
984    fn compute_margin_ramps_from_zero_to_cap() {
985        assert!((compute_margin(2) - 0.0).abs() < 1.0e-9);
986        assert!((compute_margin(7) - 0.25).abs() < 1.0e-9);
987        assert!((compute_margin(100) - 0.3).abs() < 1.0e-9);
988    }
989
990    #[test]
991    fn clip_segment_hits_box_in_path() {
992        let aabb = WorldAabb::new(4.0, -0.5, -0.5, 5.0, 0.5, 0.5);
993        let hit = clip_segment(aabb, DVec3::ZERO, DVec3::new(10.0, 0.0, 0.0))
994            .expect("ray along +x should enter the box");
995        assert!((hit.x - 4.0).abs() < 1.0e-6);
996    }
997
998    #[test]
999    fn clip_segment_returns_start_when_inside() {
1000        let aabb = WorldAabb::new(-1.0, -1.0, -1.0, 1.0, 1.0, 1.0);
1001        let hit = clip_segment(aabb, DVec3::ZERO, DVec3::new(0.0, 5.0, 0.0))
1002            .expect("a ray starting inside the box hits at its origin");
1003        assert_eq!(hit, DVec3::ZERO);
1004    }
1005
1006    #[test]
1007    fn may_break_requires_impact_projectile_tag() {
1008        init_vanilla_registry();
1009
1010        let firework = FireworkRocketEntity::new(
1011            &vanilla_entities::FIREWORK_ROCKET,
1012            1,
1013            DVec3::ZERO,
1014            Weak::new(),
1015        );
1016        let ender_pearl = OwnerCollisionProjectile::new(2, DVec3::ZERO);
1017
1018        assert!(firework.may_break(test_world()));
1019        assert!(!ender_pearl.may_break(test_world()));
1020    }
1021
1022    #[test]
1023    fn move_vector_synthesizes_world_border_block_hit() {
1024        init_vanilla_registry();
1025        init_behaviors();
1026
1027        let world = Arc::clone(world_border_projectile_test_world());
1028        let firework = FireworkRocketEntity::new(
1029            &vanilla_entities::FIREWORK_ROCKET,
1030            3,
1031            DVec3::new(4.5, 64.0, 0.0),
1032            Arc::downgrade(&world),
1033        );
1034        firework.set_velocity(DVec3::X);
1035
1036        let Some(ProjectileHit::Block { location, hit }) = firework.get_hit_result_on_move_vector()
1037        else {
1038            panic!("firework should hit the world border");
1039        };
1040
1041        assert!(hit.world_border_hit);
1042        assert!(!hit.inside);
1043        assert_eq!(hit.direction, Direction::East);
1044        assert_eq!(hit.block_pos, BlockPos::new(4, 64, 0));
1045        assert_eq!(location, hit.location);
1046        assert!(location.x < 5.0 && location.x > 4.999);
1047    }
1048
1049    #[test]
1050    fn firework_deflects_without_exploding_on_deflecting_entity() {
1051        init_vanilla_registry();
1052
1053        let world = Arc::clone(test_world());
1054        let firework = FireworkRocketEntity::new(
1055            &vanilla_entities::FIREWORK_ROCKET,
1056            4,
1057            DVec3::ZERO,
1058            Arc::downgrade(&world),
1059        );
1060        firework.set_velocity(DVec3::X);
1061        let deflector = OwnerCollisionTestEntity::shared_with_type(
1062            5,
1063            DVec3::X,
1064            true,
1065            &vanilla_entities::BREEZE,
1066        );
1067
1068        let deflection =
1069            firework.hit_target_or_deflect_self(&ProjectileHit::Entity(EntityHitResult {
1070                entity: deflector,
1071                location: DVec3::X,
1072            }));
1073
1074        assert_eq!(deflection, ProjectileDeflection::Reverse);
1075        assert_eq!(firework.velocity(), DVec3::new(-0.5, 0.0, 0.0));
1076        assert!(firework.needs_velocity_sync());
1077        assert!(!firework.is_removed());
1078    }
1079
1080    #[test]
1081    fn base_block_hit_dispatches_vanilla_block_callbacks() {
1082        init_vanilla_registry();
1083        init_behaviors();
1084
1085        let world = Arc::clone(test_world());
1086        let chunk_map = Arc::clone(&world.chunk_map);
1087        let pos = BlockPos::new(1_136, 64, 1_136);
1088        let block_callback_test =
1089            chunk_map.with_full_chunks_in_radius(ChunkPos::from_block_pos(pos), 0, || {
1090                let unlit = vanilla_blocks::CANDLE
1091                    .default_state()
1092                    .set_value(&BlockStateProperties::LIT, false);
1093                assert!(world.set_block(pos, unlit, UpdateFlags::UPDATE_ALL));
1094
1095                let firework = FireworkRocketEntity::new(
1096                    &vanilla_entities::FIREWORK_ROCKET,
1097                    3,
1098                    DVec3::new(1_136.5, 64.5, 1_136.5),
1099                    Arc::downgrade(&world),
1100                );
1101                firework.set_remaining_fire_ticks(1);
1102                firework.projectile_on_hit_block(&ClipHitResult {
1103                    location: firework.position(),
1104                    direction: Direction::Up,
1105                    block_pos: pos,
1106                    miss: false,
1107                    inside: false,
1108                    world_border_hit: false,
1109                });
1110
1111                assert!(
1112                    world
1113                        .get_block_state(pos)
1114                        .get_value(&BlockStateProperties::LIT)
1115                );
1116
1117                let campfire_pos = pos.offset(1, 0, 0);
1118                let unlit_campfire = vanilla_blocks::CAMPFIRE
1119                    .default_state()
1120                    .set_value(&BlockStateProperties::LIT, false)
1121                    .set_value(&BlockStateProperties::WATERLOGGED, false);
1122                assert!(
1123                    world.set_block(campfire_pos, unlit_campfire, UpdateFlags::UPDATE_CLIENTS,)
1124                );
1125                firework.projectile_on_hit_block(&ClipHitResult {
1126                    location: firework.position(),
1127                    direction: Direction::Up,
1128                    block_pos: campfire_pos,
1129                    miss: false,
1130                    inside: false,
1131                    world_border_hit: false,
1132                });
1133                assert!(
1134                    world
1135                        .get_block_state(campfire_pos)
1136                        .get_value(&BlockStateProperties::LIT)
1137                );
1138
1139                let dripleaf_pos = pos.offset(2, 0, 0);
1140                assert!(world.set_block(
1141                    dripleaf_pos,
1142                    vanilla_blocks::BIG_DRIPLEAF.default_state(),
1143                    UpdateFlags::UPDATE_CLIENTS,
1144                ));
1145                firework.projectile_on_hit_block(&ClipHitResult {
1146                    location: firework.position(),
1147                    direction: Direction::Up,
1148                    block_pos: dripleaf_pos,
1149                    miss: false,
1150                    inside: false,
1151                    world_border_hit: false,
1152                });
1153                assert_eq!(
1154                    world
1155                        .get_block_state(dripleaf_pos)
1156                        .get_value(&BlockStateProperties::TILT),
1157                    Tilt::Full
1158                );
1159
1160                let chorus_pos = pos.offset(3, 0, 0);
1161                assert!(world.set_block(
1162                    chorus_pos,
1163                    vanilla_blocks::CHORUS_FLOWER.default_state(),
1164                    UpdateFlags::UPDATE_CLIENTS,
1165                ));
1166                firework.projectile_on_hit_block(&ClipHitResult {
1167                    location: firework.position(),
1168                    direction: Direction::Up,
1169                    block_pos: chorus_pos,
1170                    miss: false,
1171                    inside: false,
1172                    world_border_hit: false,
1173                });
1174                assert!(world.get_block_state(chorus_pos).is_air());
1175            });
1176        let result = chunk_map.chunk_runtime.block_on(block_callback_test);
1177
1178        assert_eq!(result, Some(()));
1179    }
1180
1181    #[test]
1182    fn owner_collision_range_checks_root_vehicle_passengers() {
1183        init_vanilla_registry();
1184
1185        let projectile = OwnerCollisionProjectile::new(1, DVec3::ZERO);
1186        let owner = OwnerCollisionTestEntity::shared(2, DVec3::new(10.0, 0.0, 0.0), true);
1187        let vehicle = OwnerCollisionTestEntity::shared(3, DVec3::new(10.0, 0.0, 0.0), true);
1188        let passenger = OwnerCollisionTestEntity::shared(4, DVec3::ZERO, true);
1189        EntityBase::restore_passenger_relationship(&vehicle, &owner);
1190        EntityBase::restore_passenger_relationship(&vehicle, &passenger);
1191
1192        projectile.set_owner_entity(Some(&owner));
1193
1194        assert!(!projectile.is_outside_owner_collision_range());
1195    }
1196
1197    #[test]
1198    fn owner_collision_range_ignores_non_pickable_root_vehicle_passengers() {
1199        init_vanilla_registry();
1200
1201        let projectile = OwnerCollisionProjectile::new(1, DVec3::ZERO);
1202        let owner = OwnerCollisionTestEntity::shared(2, DVec3::new(10.0, 0.0, 0.0), true);
1203        let vehicle = OwnerCollisionTestEntity::shared(3, DVec3::new(10.0, 0.0, 0.0), true);
1204        let passenger = OwnerCollisionTestEntity::shared(4, DVec3::ZERO, false);
1205        EntityBase::restore_passenger_relationship(&vehicle, &owner);
1206        EntityBase::restore_passenger_relationship(&vehicle, &passenger);
1207
1208        projectile.set_owner_entity(Some(&owner));
1209
1210        assert!(projectile.is_outside_owner_collision_range());
1211    }
1212}