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