azalea_client/plugins/
movement.rs

1use std::{backtrace::Backtrace, io};
2
3use azalea_core::{position::Vec3, tick::GameTick};
4use azalea_entity::{
5    Attributes, InLoadedChunk, Jumping, LastSentPosition, LookDirection, Physics, Position,
6    metadata::Sprinting,
7};
8use azalea_physics::{PhysicsSet, ai_step};
9use azalea_protocol::{
10    common::movements::MoveFlags,
11    packets::{
12        Packet,
13        game::{
14            ServerboundPlayerCommand, ServerboundPlayerInput,
15            s_move_player_pos::ServerboundMovePlayerPos,
16            s_move_player_pos_rot::ServerboundMovePlayerPosRot,
17            s_move_player_rot::ServerboundMovePlayerRot,
18            s_move_player_status_only::ServerboundMovePlayerStatusOnly,
19        },
20    },
21};
22use azalea_world::{MinecraftEntityId, MoveEntityError};
23use bevy_app::{App, Plugin, Update};
24use bevy_ecs::prelude::*;
25use thiserror::Error;
26
27use crate::{client::Client, packet::game::SendPacketEvent};
28
29#[derive(Error, Debug)]
30pub enum MovePlayerError {
31    #[error("Player is not in world")]
32    PlayerNotInWorld(Backtrace),
33    #[error("{0}")]
34    Io(#[from] io::Error),
35}
36
37impl From<MoveEntityError> for MovePlayerError {
38    fn from(err: MoveEntityError) -> Self {
39        match err {
40            MoveEntityError::EntityDoesNotExist(backtrace) => {
41                MovePlayerError::PlayerNotInWorld(backtrace)
42            }
43        }
44    }
45}
46
47pub struct MovementPlugin;
48
49impl Plugin for MovementPlugin {
50    fn build(&self, app: &mut App) {
51        app.add_event::<StartWalkEvent>()
52            .add_event::<StartSprintEvent>()
53            .add_event::<KnockbackEvent>()
54            .add_systems(
55                Update,
56                (handle_sprint, handle_walk, handle_knockback)
57                    .chain()
58                    .in_set(MoveEventsSet),
59            )
60            .add_systems(
61                GameTick,
62                (
63                    (tick_controls, local_player_ai_step)
64                        .chain()
65                        .in_set(PhysicsSet)
66                        .before(ai_step)
67                        .before(azalea_physics::fluids::update_in_water_state_and_do_fluid_pushing),
68                    send_player_input_packet,
69                    send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk),
70                    send_position.after(PhysicsSet),
71                )
72                    .chain(),
73            );
74    }
75}
76
77#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
78pub struct MoveEventsSet;
79
80impl Client {
81    /// Set whether we're jumping. This acts as if you held space in
82    /// vanilla. If you want to jump once, use the `jump` function.
83    ///
84    /// If you're making a realistic client, calling this function every tick is
85    /// recommended.
86    pub fn set_jumping(&self, jumping: bool) {
87        let mut ecs = self.ecs.lock();
88        let mut jumping_mut = self.query::<&mut Jumping>(&mut ecs);
89        **jumping_mut = jumping;
90    }
91
92    /// Returns whether the player will try to jump next tick.
93    pub fn jumping(&self) -> bool {
94        *self.component::<Jumping>()
95    }
96
97    /// Sets the direction the client is looking. `y_rot` is yaw (looking to the
98    /// side), `x_rot` is pitch (looking up and down). You can get these
99    /// numbers from the vanilla f3 screen.
100    /// `y_rot` goes from -180 to 180, and `x_rot` goes from -90 to 90.
101    pub fn set_direction(&self, y_rot: f32, x_rot: f32) {
102        let mut ecs = self.ecs.lock();
103        let mut look_direction = self.query::<&mut LookDirection>(&mut ecs);
104
105        (look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot);
106    }
107
108    /// Returns the direction the client is looking. The first value is the y
109    /// rotation (ie. yaw, looking to the side) and the second value is the x
110    /// rotation (ie. pitch, looking up and down).
111    pub fn direction(&self) -> (f32, f32) {
112        let look_direction = self.component::<LookDirection>();
113        (look_direction.y_rot, look_direction.x_rot)
114    }
115}
116
117/// A component that contains the look direction that was last sent over the
118/// network.
119#[derive(Debug, Component, Clone, Default)]
120pub struct LastSentLookDirection {
121    pub x_rot: f32,
122    pub y_rot: f32,
123}
124
125/// Component for entities that can move and sprint. Usually only in
126/// [`LocalEntity`]s.
127///
128/// [`LocalEntity`]: azalea_entity::LocalEntity
129#[derive(Default, Component, Clone)]
130pub struct PhysicsState {
131    /// Minecraft only sends a movement packet either after 20 ticks or if the
132    /// player moved enough. This is that tick counter.
133    pub position_remainder: u32,
134    pub was_sprinting: bool,
135    // Whether we're going to try to start sprinting this tick. Equivalent to
136    // holding down ctrl for a tick.
137    pub trying_to_sprint: bool,
138
139    pub move_direction: WalkDirection,
140    pub forward_impulse: f32,
141    pub left_impulse: f32,
142}
143
144#[allow(clippy::type_complexity)]
145pub fn send_position(
146    mut query: Query<
147        (
148            Entity,
149            &Position,
150            &LookDirection,
151            &mut PhysicsState,
152            &mut LastSentPosition,
153            &mut Physics,
154            &mut LastSentLookDirection,
155        ),
156        With<InLoadedChunk>,
157    >,
158    mut commands: Commands,
159) {
160    for (
161        entity,
162        position,
163        direction,
164        mut physics_state,
165        mut last_sent_position,
166        mut physics,
167        mut last_direction,
168    ) in query.iter_mut()
169    {
170        let packet = {
171            // TODO: the camera being able to be controlled by other entities isn't
172            // implemented yet if !self.is_controlled_camera() { return };
173
174            let x_delta = position.x - last_sent_position.x;
175            let y_delta = position.y - last_sent_position.y;
176            let z_delta = position.z - last_sent_position.z;
177            let y_rot_delta = (direction.y_rot - last_direction.y_rot) as f64;
178            let x_rot_delta = (direction.x_rot - last_direction.x_rot) as f64;
179
180            physics_state.position_remainder += 1;
181
182            // boolean sendingPosition = Mth.lengthSquared(xDelta, yDelta, zDelta) >
183            // Mth.square(2.0E-4D) || this.positionReminder >= 20;
184            let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
185                > 2.0e-4f64.powi(2))
186                || physics_state.position_remainder >= 20;
187            let sending_direction = y_rot_delta != 0.0 || x_rot_delta != 0.0;
188
189            // if self.is_passenger() {
190            //   TODO: posrot packet for being a passenger
191            // }
192            let flags = MoveFlags {
193                on_ground: physics.on_ground(),
194                horizontal_collision: physics.horizontal_collision,
195            };
196            let packet = if sending_position && sending_direction {
197                Some(
198                    ServerboundMovePlayerPosRot {
199                        pos: **position,
200                        look_direction: *direction,
201                        flags,
202                    }
203                    .into_variant(),
204                )
205            } else if sending_position {
206                Some(
207                    ServerboundMovePlayerPos {
208                        pos: **position,
209                        flags,
210                    }
211                    .into_variant(),
212                )
213            } else if sending_direction {
214                Some(
215                    ServerboundMovePlayerRot {
216                        look_direction: *direction,
217                        flags,
218                    }
219                    .into_variant(),
220                )
221            } else if physics.last_on_ground() != physics.on_ground() {
222                Some(ServerboundMovePlayerStatusOnly { flags }.into_variant())
223            } else {
224                None
225            };
226
227            if sending_position {
228                **last_sent_position = **position;
229                physics_state.position_remainder = 0;
230            }
231            if sending_direction {
232                last_direction.y_rot = direction.y_rot;
233                last_direction.x_rot = direction.x_rot;
234            }
235
236            let on_ground = physics.on_ground();
237            physics.set_last_on_ground(on_ground);
238            // minecraft checks for autojump here, but also autojump is bad so
239
240            packet
241        };
242
243        if let Some(packet) = packet {
244            commands.trigger(SendPacketEvent {
245                sent_by: entity,
246                packet,
247            });
248        }
249    }
250}
251
252#[derive(Debug, Default, Component, Clone, PartialEq, Eq)]
253pub struct LastSentInput(pub ServerboundPlayerInput);
254pub fn send_player_input_packet(
255    mut query: Query<(Entity, &PhysicsState, &Jumping, Option<&LastSentInput>)>,
256    mut commands: Commands,
257) {
258    for (entity, physics_state, jumping, last_sent_input) in query.iter_mut() {
259        let dir = physics_state.move_direction;
260        type D = WalkDirection;
261        let input = ServerboundPlayerInput {
262            forward: matches!(dir, D::Forward | D::ForwardLeft | D::ForwardRight),
263            backward: matches!(dir, D::Backward | D::BackwardLeft | D::BackwardRight),
264            left: matches!(dir, D::Left | D::ForwardLeft | D::BackwardLeft),
265            right: matches!(dir, D::Right | D::ForwardRight | D::BackwardRight),
266            jump: **jumping,
267            // TODO: implement sneaking
268            shift: false,
269            sprint: physics_state.trying_to_sprint,
270        };
271
272        // if LastSentInput isn't present, we default to assuming we're not pressing any
273        // keys and insert it anyways every time it changes
274        let last_sent_input = last_sent_input.cloned().unwrap_or_default();
275
276        if input != last_sent_input.0 {
277            commands.trigger(SendPacketEvent {
278                sent_by: entity,
279                packet: input.clone().into_variant(),
280            });
281            commands.entity(entity).insert(LastSentInput(input));
282        }
283    }
284}
285
286pub fn send_sprinting_if_needed(
287    mut query: Query<(Entity, &MinecraftEntityId, &Sprinting, &mut PhysicsState)>,
288    mut commands: Commands,
289) {
290    for (entity, minecraft_entity_id, sprinting, mut physics_state) in query.iter_mut() {
291        let was_sprinting = physics_state.was_sprinting;
292        if **sprinting != was_sprinting {
293            let sprinting_action = if **sprinting {
294                azalea_protocol::packets::game::s_player_command::Action::StartSprinting
295            } else {
296                azalea_protocol::packets::game::s_player_command::Action::StopSprinting
297            };
298            commands.trigger(SendPacketEvent::new(
299                entity,
300                ServerboundPlayerCommand {
301                    id: *minecraft_entity_id,
302                    action: sprinting_action,
303                    data: 0,
304                },
305            ));
306            physics_state.was_sprinting = **sprinting;
307        }
308    }
309}
310
311/// Update the impulse from self.move_direction. The multiplier is used for
312/// sneaking.
313pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) {
314    for mut physics_state in query.iter_mut() {
315        let multiplier: Option<f32> = None;
316
317        let mut forward_impulse: f32 = 0.;
318        let mut left_impulse: f32 = 0.;
319        let move_direction = physics_state.move_direction;
320        match move_direction {
321            WalkDirection::Forward | WalkDirection::ForwardRight | WalkDirection::ForwardLeft => {
322                forward_impulse += 1.;
323            }
324            WalkDirection::Backward
325            | WalkDirection::BackwardRight
326            | WalkDirection::BackwardLeft => {
327                forward_impulse -= 1.;
328            }
329            _ => {}
330        };
331        match move_direction {
332            WalkDirection::Right | WalkDirection::ForwardRight | WalkDirection::BackwardRight => {
333                left_impulse += 1.;
334            }
335            WalkDirection::Left | WalkDirection::ForwardLeft | WalkDirection::BackwardLeft => {
336                left_impulse -= 1.;
337            }
338            _ => {}
339        };
340        physics_state.forward_impulse = forward_impulse;
341        physics_state.left_impulse = left_impulse;
342
343        if let Some(multiplier) = multiplier {
344            physics_state.forward_impulse *= multiplier;
345            physics_state.left_impulse *= multiplier;
346        }
347    }
348}
349
350/// Makes the bot do one physics tick. Note that this is already handled
351/// automatically by the client.
352pub fn local_player_ai_step(
353    mut query: Query<
354        (&PhysicsState, &mut Physics, &mut Sprinting, &mut Attributes),
355        With<InLoadedChunk>,
356    >,
357) {
358    for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
359        // server ai step
360        physics.x_acceleration = physics_state.left_impulse;
361        physics.z_acceleration = physics_state.forward_impulse;
362
363        // TODO: food data and abilities
364        // let has_enough_food_to_sprint = self.food_data().food_level ||
365        // self.abilities().may_fly;
366        let has_enough_food_to_sprint = true;
367
368        // TODO: double tapping w to sprint i think
369
370        let trying_to_sprint = physics_state.trying_to_sprint;
371
372        if !**sprinting
373            && (
374                // !self.is_in_water()
375                // || self.is_underwater() &&
376                has_enough_impulse_to_start_sprinting(physics_state)
377                    && has_enough_food_to_sprint
378                    // && !self.using_item()
379                    // && !self.has_effect(MobEffects.BLINDNESS)
380                    && trying_to_sprint
381            )
382        {
383            set_sprinting(true, &mut sprinting, &mut attributes);
384        }
385    }
386}
387
388impl Client {
389    /// Start walking in the given direction. To sprint, use
390    /// [`Client::sprint`]. To stop walking, call walk with
391    /// `WalkDirection::None`.
392    ///
393    /// # Examples
394    ///
395    /// Walk for 1 second
396    /// ```rust,no_run
397    /// # use azalea_client::{Client, WalkDirection};
398    /// # use std::time::Duration;
399    /// # async fn example(mut bot: Client) {
400    /// bot.walk(WalkDirection::Forward);
401    /// tokio::time::sleep(Duration::from_secs(1)).await;
402    /// bot.walk(WalkDirection::None);
403    /// # }
404    /// ```
405    pub fn walk(&self, direction: WalkDirection) {
406        let mut ecs = self.ecs.lock();
407        ecs.send_event(StartWalkEvent {
408            entity: self.entity,
409            direction,
410        });
411    }
412
413    /// Start sprinting in the given direction. To stop moving, call
414    /// [`Client::walk(WalkDirection::None)`]
415    ///
416    /// # Examples
417    ///
418    /// Sprint for 1 second
419    /// ```rust,no_run
420    /// # use azalea_client::{Client, WalkDirection, SprintDirection};
421    /// # use std::time::Duration;
422    /// # async fn example(mut bot: Client) {
423    /// bot.sprint(SprintDirection::Forward);
424    /// tokio::time::sleep(Duration::from_secs(1)).await;
425    /// bot.walk(WalkDirection::None);
426    /// # }
427    /// ```
428    pub fn sprint(&self, direction: SprintDirection) {
429        let mut ecs = self.ecs.lock();
430        ecs.send_event(StartSprintEvent {
431            entity: self.entity,
432            direction,
433        });
434    }
435}
436
437/// An event sent when the client starts walking. This does not get sent for
438/// non-local entities.
439///
440/// To stop walking or sprinting, send this event with `WalkDirection::None`.
441#[derive(Event, Debug)]
442pub struct StartWalkEvent {
443    pub entity: Entity,
444    pub direction: WalkDirection,
445}
446
447/// The system that makes the player start walking when they receive a
448/// [`StartWalkEvent`].
449pub fn handle_walk(
450    mut events: EventReader<StartWalkEvent>,
451    mut query: Query<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>,
452) {
453    for event in events.read() {
454        if let Ok((mut physics_state, mut sprinting, mut attributes)) = query.get_mut(event.entity)
455        {
456            physics_state.move_direction = event.direction;
457            physics_state.trying_to_sprint = false;
458            set_sprinting(false, &mut sprinting, &mut attributes);
459        }
460    }
461}
462
463/// An event sent when the client starts sprinting. This does not get sent for
464/// non-local entities.
465#[derive(Event)]
466pub struct StartSprintEvent {
467    pub entity: Entity,
468    pub direction: SprintDirection,
469}
470/// The system that makes the player start sprinting when they receive a
471/// [`StartSprintEvent`].
472pub fn handle_sprint(
473    mut query: Query<&mut PhysicsState>,
474    mut events: EventReader<StartSprintEvent>,
475) {
476    for event in events.read() {
477        if let Ok(mut physics_state) = query.get_mut(event.entity) {
478            physics_state.move_direction = WalkDirection::from(event.direction);
479            physics_state.trying_to_sprint = true;
480        }
481    }
482}
483
484/// Change whether we're sprinting by adding an attribute modifier to the
485/// player. You should use the [`walk`] and [`sprint`] methods instead.
486/// Returns if the operation was successful.
487fn set_sprinting(
488    sprinting: bool,
489    currently_sprinting: &mut Sprinting,
490    attributes: &mut Attributes,
491) -> bool {
492    **currently_sprinting = sprinting;
493    if sprinting {
494        attributes
495            .speed
496            .try_insert(azalea_entity::attributes::sprinting_modifier())
497            .is_ok()
498    } else {
499        attributes
500            .speed
501            .remove(&azalea_entity::attributes::sprinting_modifier().id)
502            .is_none()
503    }
504}
505
506// Whether the player is moving fast enough to be able to start sprinting.
507fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool {
508    // if self.underwater() {
509    //     self.has_forward_impulse()
510    // } else {
511    physics_state.forward_impulse > 0.8
512    // }
513}
514
515/// An event sent by the server that sets or adds to our velocity. Usually
516/// `KnockbackKind::Set` is used for normal knockback and `KnockbackKind::Add`
517/// is used for explosions, but some servers (notably Hypixel) use explosions
518/// for knockback.
519#[derive(Event)]
520pub struct KnockbackEvent {
521    pub entity: Entity,
522    pub knockback: KnockbackType,
523}
524
525pub enum KnockbackType {
526    Set(Vec3),
527    Add(Vec3),
528}
529
530pub fn handle_knockback(mut query: Query<&mut Physics>, mut events: EventReader<KnockbackEvent>) {
531    for event in events.read() {
532        if let Ok(mut physics) = query.get_mut(event.entity) {
533            match event.knockback {
534                KnockbackType::Set(velocity) => {
535                    physics.velocity = velocity;
536                }
537                KnockbackType::Add(velocity) => {
538                    physics.velocity += velocity;
539                }
540            }
541        }
542    }
543}
544
545#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
546pub enum WalkDirection {
547    #[default]
548    None,
549    Forward,
550    Backward,
551    Left,
552    Right,
553    ForwardRight,
554    ForwardLeft,
555    BackwardRight,
556    BackwardLeft,
557}
558
559/// The directions that we can sprint in. It's a subset of [`WalkDirection`].
560#[derive(Clone, Copy, Debug)]
561pub enum SprintDirection {
562    Forward,
563    ForwardRight,
564    ForwardLeft,
565}
566
567impl From<SprintDirection> for WalkDirection {
568    fn from(d: SprintDirection) -> Self {
569        match d {
570            SprintDirection::Forward => WalkDirection::Forward,
571            SprintDirection::ForwardRight => WalkDirection::ForwardRight,
572            SprintDirection::ForwardLeft => WalkDirection::ForwardLeft,
573        }
574    }
575}