Skip to main content

yog_body/
movement.rs

1//! The local player's movement: a mod may replace a travel step (what the
2//! player moves by this tick) — gliding, flying, dashing. Runs on the
3//! client, which moves its own player; the server takes the result.
4
5use crate::math::Vec3;
6
7/// The local player before a travel step.
8#[derive(yog_data::Schema, Debug, Clone, PartialEq, Default)]
9pub struct TravelFrame {
10    /// The local player.
11    pub uuid: String,
12    /// Blocks per tick, as it is now.
13    pub velocity: Vec3,
14    /// Unit view direction.
15    pub look: Vec3,
16    /// Degrees.
17    pub yaw: f32,
18    pub pitch: f32,
19    /// Movement keys: forward (−1–1), strafe left (−1–1), jump, sneak.
20    pub forward: f32,
21    pub strafe: f32,
22    pub jumping: bool,
23    pub sneaking: bool,
24    /// Pull per tick (0.08, 0.01 with slow falling).
25    pub gravity: f32,
26    /// `entity_flags` bits.
27    pub flags: u32,
28}
29
30/// Vanilla's elytra numbers (`LivingEntity.travel` while fall-flying).
31mod glide {
32    /// A look this long (vertical included) lifts fully.
33    pub const FULL_LIFT_LOOK: f64 = 0.4;
34    /// How much of gravity a level glide cancels.
35    pub const LIFT: f64 = 0.75;
36    /// Falling turned into forward speed.
37    pub const FALL_TO_SPEED: f64 = 0.1;
38    /// Speed turned into climb when looking up.
39    pub const CLIMB: f64 = 0.04;
40    /// Climb's vertical gain.
41    pub const CLIMB_UP: f64 = 3.2;
42    /// How fast the velocity turns toward the look.
43    pub const STEER: f64 = 0.1;
44    /// Drag a tick: across and down.
45    pub const DRAG: f64 = 0.99;
46    pub const DRAG_Y: f64 = 0.98;
47}
48
49/// Vanilla's elytra step (`LivingEntity.travel` while fall-flying): the
50/// velocity to move by this tick, drag applied.
51pub fn elytra(f: &TravelFrame) -> Vec3 {
52    let v = f.velocity;
53    let look = f.look;
54    let pitch = f.pitch.to_radians();
55    let g = f.gravity as f64;
56    let (lx, ly, lz) = (look.x as f64, look.y as f64, look.z as f64);
57    let (mut vx, mut vy, mut vz) = (v.x as f64, v.y as f64, v.z as f64);
58    let d1 = (lx * lx + lz * lz).sqrt();
59    let d3 = (vx * vx + vz * vz).sqrt();
60    let d4 = (lx * lx + ly * ly + lz * lz).sqrt();
61    let mut d5 = (pitch as f64).cos();
62    d5 = d5 * d5 * (d4 / glide::FULL_LIFT_LOOK).min(1.0);
63    vy += g * (-1.0 + d5 * glide::LIFT);
64    if vy < 0.0 && d1 > 0.0 {
65        let d6 = vy * -glide::FALL_TO_SPEED * d5;
66        vx += lx * d6 / d1;
67        vy += d6;
68        vz += lz * d6 / d1;
69    }
70    if pitch < 0.0 && d1 > 0.0 {
71        let d10 = d3 * (-(pitch.sin()) as f64) * glide::CLIMB;
72        vx += -lx * d10 / d1;
73        vy += d10 * glide::CLIMB_UP;
74        vz += -lz * d10 / d1;
75    }
76    if d1 > 0.0 {
77        vx += (lx / d1 * d3 - vx) * glide::STEER;
78        vz += (lz / d1 * d3 - vz) * glide::STEER;
79    }
80    Vec3::new(
81        (vx * glide::DRAG) as f32,
82        (vy * glide::DRAG_Y) as f32,
83        (vz * glide::DRAG) as f32,
84    )
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn gliding_level_falls_slowly_and_keeps_going() {
93        let f = TravelFrame {
94            velocity: Vec3::new(0.0, 0.0, 1.0),
95            look: Vec3::new(0.0, 0.0, 1.0),
96            gravity: 0.08,
97            ..Default::default()
98        };
99        let v = elytra(&f);
100        assert!(v.y < 0.0 && v.y > -0.08, "{v:?}");
101        assert!(v.z > 0.9);
102    }
103}