Skip to main content

steel_core/entity/projectile/
throwable.rs

1//! Vanilla `ThrowableProjectile` — the gravity/drag movement loop.
2
3use crate::entity::RemovalReason;
4use crate::entity::projectile::Projectile;
5
6/// Vanilla `ThrowableProjectile.getDefaultGravity`.
7const DEFAULT_GRAVITY: f64 = 0.03;
8
9/// Vanilla drag multiplier while submerged (`ThrowableProjectile.applyInertia`).
10const WATER_INERTIA: f64 = 0.8;
11
12/// Vanilla-shaped behavior shared by entities that extend `ThrowableProjectile`.
13pub trait ThrowableProjectile: Projectile {
14    /// Vanilla `ThrowableProjectile.getAirDrag`.
15    fn get_air_drag(&self) -> f32 {
16        0.99
17    }
18
19    /// Vanilla `ThrowableProjectile.getDefaultGravity` (0.03).
20    fn throwable_default_gravity(&self) -> f64 {
21        DEFAULT_GRAVITY
22    }
23
24    /// Vanilla `ThrowableProjectile.applyInertia` (water vs air drag).
25    fn apply_inertia(&self) {
26        let inertia = if self.is_in_water() {
27            // VANILLA CLIENT-LOCAL: `ThrowableProjectile.tick` creates the trailing bubbles.
28            WATER_INERTIA
29        } else {
30            f64::from(self.get_air_drag())
31        };
32        self.set_velocity(self.velocity() * inertia);
33    }
34
35    /// Vanilla `ThrowableProjectile.tick`.
36    ///
37    /// Reached from a subclass's `tick` as `super.tick()`. Applies gravity and
38    /// drag, raycasts the move vector, moves to the hit (or full move), updates
39    /// rotation, runs the `Projectile`/`Entity` base tick, then resolves the hit.
40    fn throwable_projectile_tick(&self) {
41        // Vanilla `Entity.setOldPosAndRot()` is run by the level before ticking;
42        // capture it here so `old_position()`/`old_rotation()` hold the pre-move
43        // state used by `onHit` (teleport target) and `updateRotation` (lerp base).
44        self.set_old_position_to_current();
45        self.base().set_old_rotation_to_current();
46
47        // TODO: handle_first_tick_bubble_column (bubble column shove on spawn).
48        self.apply_gravity();
49        self.apply_inertia();
50
51        let hit = self.get_hit_result_on_move_vector();
52        let new_position = match &hit {
53            Some(result) => result.location(),
54            None => self.position() + self.velocity(),
55        };
56
57        if let Err(error) = self.try_set_position(new_position) {
58            log::debug!("failed to advance projectile {}: {error}", self.id());
59            self.set_removed(RemovalReason::Discarded);
60            return;
61        }
62
63        self.update_rotation();
64        self.apply_effects_from_blocks();
65        self.projectile_base_tick();
66
67        if let Some(result) = hit
68            && self.is_alive()
69            && !self.is_world_change_pending()
70        {
71            self.hit_target_or_deflect_self(&result);
72        }
73    }
74}