azalea_client/
movement.rs

1use std::backtrace::Backtrace;
2
3use azalea_core::position::Vec3;
4use azalea_core::tick::GameTick;
5use azalea_entity::{metadata::Sprinting, Attributes, Jumping};
6use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position};
7use azalea_physics::{ai_step, PhysicsSet};
8use azalea_protocol::packets::game::{ServerboundPlayerCommand, ServerboundPlayerInput};
9use azalea_protocol::packets::{
10    game::{
11        s_move_player_pos::ServerboundMovePlayerPos,
12        s_move_player_pos_rot::ServerboundMovePlayerPosRot,
13        s_move_player_rot::ServerboundMovePlayerRot,
14        s_move_player_status_only::ServerboundMovePlayerStatusOnly,
15    },
16    Packet,
17};
18use azalea_world::{MinecraftEntityId, MoveEntityError};
19use bevy_app::{App, Plugin, Update};
20use bevy_ecs::prelude::{Event, EventWriter};
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_handling::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 PlayerMovePlugin;
51
52impl Plugin for PlayerMovePlugin {
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 send_packet_events: EventWriter<SendPacketEvent>,
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            send_packet_events.send(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 send_packet_events: EventWriter<SendPacketEvent>,
261    mut commands: Commands,
262) {
263    for (entity, physics_state, jumping, last_sent_input) in query.iter_mut() {
264        let dir = physics_state.move_direction;
265        type D = WalkDirection;
266        let input = ServerboundPlayerInput {
267            forward: matches!(dir, D::Forward | D::ForwardLeft | D::ForwardRight),
268            backward: matches!(dir, D::Backward | D::BackwardLeft | D::BackwardRight),
269            left: matches!(dir, D::Left | D::ForwardLeft | D::BackwardLeft),
270            right: matches!(dir, D::Right | D::ForwardRight | D::BackwardRight),
271            jump: **jumping,
272            // TODO: implement sneaking
273            shift: false,
274            sprint: physics_state.trying_to_sprint,
275        };
276
277        // if LastSentInput isn't present, we default to assuming we're not pressing any
278        // keys and insert it anyways every time it changes
279        let last_sent_input = last_sent_input.cloned().unwrap_or_default();
280
281        if input != last_sent_input.0 {
282            send_packet_events.send(SendPacketEvent {
283                sent_by: entity,
284                packet: input.clone().into_variant(),
285            });
286            commands.entity(entity).insert(LastSentInput(input));
287        }
288    }
289}
290
291fn send_sprinting_if_needed(
292    mut query: Query<(Entity, &MinecraftEntityId, &Sprinting, &mut PhysicsState)>,
293    mut send_packet_events: EventWriter<SendPacketEvent>,
294) {
295    for (entity, minecraft_entity_id, sprinting, mut physics_state) in query.iter_mut() {
296        let was_sprinting = physics_state.was_sprinting;
297        if **sprinting != was_sprinting {
298            let sprinting_action = if **sprinting {
299                azalea_protocol::packets::game::s_player_command::Action::StartSprinting
300            } else {
301                azalea_protocol::packets::game::s_player_command::Action::StopSprinting
302            };
303            send_packet_events.send(SendPacketEvent::new(
304                entity,
305                ServerboundPlayerCommand {
306                    id: *minecraft_entity_id,
307                    action: sprinting_action,
308                    data: 0,
309                },
310            ));
311            physics_state.was_sprinting = **sprinting;
312        }
313    }
314}
315
316/// Update the impulse from self.move_direction. The multiplier is used for
317/// sneaking.
318pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) {
319    for mut physics_state in query.iter_mut() {
320        let multiplier: Option<f32> = None;
321
322        let mut forward_impulse: f32 = 0.;
323        let mut left_impulse: f32 = 0.;
324        let move_direction = physics_state.move_direction;
325        match move_direction {
326            WalkDirection::Forward | WalkDirection::ForwardRight | WalkDirection::ForwardLeft => {
327                forward_impulse += 1.;
328            }
329            WalkDirection::Backward
330            | WalkDirection::BackwardRight
331            | WalkDirection::BackwardLeft => {
332                forward_impulse -= 1.;
333            }
334            _ => {}
335        };
336        match move_direction {
337            WalkDirection::Right | WalkDirection::ForwardRight | WalkDirection::BackwardRight => {
338                left_impulse += 1.;
339            }
340            WalkDirection::Left | WalkDirection::ForwardLeft | WalkDirection::BackwardLeft => {
341                left_impulse -= 1.;
342            }
343            _ => {}
344        };
345        physics_state.forward_impulse = forward_impulse;
346        physics_state.left_impulse = left_impulse;
347
348        if let Some(multiplier) = multiplier {
349            physics_state.forward_impulse *= multiplier;
350            physics_state.left_impulse *= multiplier;
351        }
352    }
353}
354
355/// Makes the bot do one physics tick. Note that this is already handled
356/// automatically by the client.
357pub fn local_player_ai_step(
358    mut query: Query<
359        (&PhysicsState, &mut Physics, &mut Sprinting, &mut Attributes),
360        With<InLoadedChunk>,
361    >,
362) {
363    for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
364        // server ai step
365        physics.x_acceleration = physics_state.left_impulse;
366        physics.z_acceleration = physics_state.forward_impulse;
367
368        // TODO: food data and abilities
369        // let has_enough_food_to_sprint = self.food_data().food_level ||
370        // self.abilities().may_fly;
371        let has_enough_food_to_sprint = true;
372
373        // TODO: double tapping w to sprint i think
374
375        let trying_to_sprint = physics_state.trying_to_sprint;
376
377        if !**sprinting
378            && (
379                // !self.is_in_water()
380                // || self.is_underwater() &&
381                has_enough_impulse_to_start_sprinting(physics_state)
382                    && has_enough_food_to_sprint
383                    // && !self.using_item()
384                    // && !self.has_effect(MobEffects.BLINDNESS)
385                    && trying_to_sprint
386            )
387        {
388            set_sprinting(true, &mut sprinting, &mut attributes);
389        }
390    }
391}
392
393impl Client {
394    /// Start walking in the given direction. To sprint, use
395    /// [`Client::sprint`]. To stop walking, call walk with
396    /// `WalkDirection::None`.
397    ///
398    /// # Examples
399    ///
400    /// Walk for 1 second
401    /// ```rust,no_run
402    /// # use azalea_client::{Client, WalkDirection};
403    /// # use std::time::Duration;
404    /// # async fn example(mut bot: Client) {
405    /// bot.walk(WalkDirection::Forward);
406    /// tokio::time::sleep(Duration::from_secs(1)).await;
407    /// bot.walk(WalkDirection::None);
408    /// # }
409    /// ```
410    pub fn walk(&mut self, direction: WalkDirection) {
411        let mut ecs = self.ecs.lock();
412        ecs.send_event(StartWalkEvent {
413            entity: self.entity,
414            direction,
415        });
416    }
417
418    /// Start sprinting in the given direction. To stop moving, call
419    /// [`Client::walk(WalkDirection::None)`]
420    ///
421    /// # Examples
422    ///
423    /// Sprint for 1 second
424    /// ```rust,no_run
425    /// # use azalea_client::{Client, WalkDirection, SprintDirection};
426    /// # use std::time::Duration;
427    /// # async fn example(mut bot: Client) {
428    /// bot.sprint(SprintDirection::Forward);
429    /// tokio::time::sleep(Duration::from_secs(1)).await;
430    /// bot.walk(WalkDirection::None);
431    /// # }
432    /// ```
433    pub fn sprint(&mut self, direction: SprintDirection) {
434        let mut ecs = self.ecs.lock();
435        ecs.send_event(StartSprintEvent {
436            entity: self.entity,
437            direction,
438        });
439    }
440}
441
442/// An event sent when the client starts walking. This does not get sent for
443/// non-local entities.
444///
445/// To stop walking or sprinting, send this event with `WalkDirection::None`.
446#[derive(Event, Debug)]
447pub struct StartWalkEvent {
448    pub entity: Entity,
449    pub direction: WalkDirection,
450}
451
452/// The system that makes the player start walking when they receive a
453/// [`StartWalkEvent`].
454pub fn handle_walk(
455    mut events: EventReader<StartWalkEvent>,
456    mut query: Query<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>,
457) {
458    for event in events.read() {
459        if let Ok((mut physics_state, mut sprinting, mut attributes)) = query.get_mut(event.entity)
460        {
461            physics_state.move_direction = event.direction;
462            physics_state.trying_to_sprint = false;
463            set_sprinting(false, &mut sprinting, &mut attributes);
464        }
465    }
466}
467
468/// An event sent when the client starts sprinting. This does not get sent for
469/// non-local entities.
470#[derive(Event)]
471pub struct StartSprintEvent {
472    pub entity: Entity,
473    pub direction: SprintDirection,
474}
475/// The system that makes the player start sprinting when they receive a
476/// [`StartSprintEvent`].
477pub fn handle_sprint(
478    mut query: Query<&mut PhysicsState>,
479    mut events: EventReader<StartSprintEvent>,
480) {
481    for event in events.read() {
482        if let Ok(mut physics_state) = query.get_mut(event.entity) {
483            physics_state.move_direction = WalkDirection::from(event.direction);
484            physics_state.trying_to_sprint = true;
485        }
486    }
487}
488
489/// Change whether we're sprinting by adding an attribute modifier to the
490/// player. You should use the [`walk`] and [`sprint`] methods instead.
491/// Returns if the operation was successful.
492fn set_sprinting(
493    sprinting: bool,
494    currently_sprinting: &mut Sprinting,
495    attributes: &mut Attributes,
496) -> bool {
497    **currently_sprinting = sprinting;
498    if sprinting {
499        attributes
500            .speed
501            .try_insert(azalea_entity::attributes::sprinting_modifier())
502            .is_ok()
503    } else {
504        attributes
505            .speed
506            .remove(&azalea_entity::attributes::sprinting_modifier().id)
507            .is_none()
508    }
509}
510
511// Whether the player is moving fast enough to be able to start sprinting.
512fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool {
513    // if self.underwater() {
514    //     self.has_forward_impulse()
515    // } else {
516    physics_state.forward_impulse > 0.8
517    // }
518}
519
520/// An event sent by the server that sets or adds to our velocity. Usually
521/// `KnockbackKind::Set` is used for normal knockback and `KnockbackKind::Add`
522/// is used for explosions, but some servers (notably Hypixel) use explosions
523/// for knockback.
524#[derive(Event)]
525pub struct KnockbackEvent {
526    pub entity: Entity,
527    pub knockback: KnockbackType,
528}
529
530pub enum KnockbackType {
531    Set(Vec3),
532    Add(Vec3),
533}
534
535pub fn handle_knockback(mut query: Query<&mut Physics>, mut events: EventReader<KnockbackEvent>) {
536    for event in events.read() {
537        if let Ok(mut physics) = query.get_mut(event.entity) {
538            match event.knockback {
539                KnockbackType::Set(velocity) => {
540                    physics.velocity = velocity;
541                }
542                KnockbackType::Add(velocity) => {
543                    physics.velocity += velocity;
544                }
545            }
546        }
547    }
548}
549
550#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
551pub enum WalkDirection {
552    #[default]
553    None,
554    Forward,
555    Backward,
556    Left,
557    Right,
558    ForwardRight,
559    ForwardLeft,
560    BackwardRight,
561    BackwardLeft,
562}
563
564/// The directions that we can sprint in. It's a subset of [`WalkDirection`].
565#[derive(Clone, Copy, Debug)]
566pub enum SprintDirection {
567    Forward,
568    ForwardRight,
569    ForwardLeft,
570}
571
572impl From<SprintDirection> for WalkDirection {
573    fn from(d: SprintDirection) -> Self {
574        match d {
575            SprintDirection::Forward => WalkDirection::Forward,
576            SprintDirection::ForwardRight => WalkDirection::ForwardRight,
577            SprintDirection::ForwardLeft => WalkDirection::ForwardLeft,
578        }
579    }
580}