azalea_client/plugins/
movement.rs

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