azalea_client/plugins/
movement.rs

1use std::{backtrace::Backtrace, io};
2
3use azalea_core::{
4    game_type::GameMode,
5    position::{Vec2, Vec3},
6    tick::GameTick,
7};
8use azalea_entity::{
9    Attributes, Crouching, HasClientLoaded, Jumping, LastSentPosition, LocalEntity, LookDirection,
10    Physics, PlayerAbilities, Pose, Position,
11    dimensions::calculate_dimensions,
12    metadata::{self, Sprinting},
13    update_bounding_box,
14};
15use azalea_physics::{
16    PhysicsSystems, ai_step,
17    collision::entity_collisions::{AabbQuery, CollidableEntityQuery, update_last_bounding_box},
18    local_player::{PhysicsState, SprintDirection, WalkDirection},
19    travel::{no_collision, travel},
20};
21use azalea_protocol::{
22    common::movements::MoveFlags,
23    packets::{
24        Packet,
25        game::{
26            ServerboundPlayerCommand, ServerboundPlayerInput,
27            s_move_player_pos::ServerboundMovePlayerPos,
28            s_move_player_pos_rot::ServerboundMovePlayerPosRot,
29            s_move_player_rot::ServerboundMovePlayerRot,
30            s_move_player_status_only::ServerboundMovePlayerStatusOnly,
31        },
32    },
33};
34use azalea_registry::EntityKind;
35use azalea_world::{Instance, MinecraftEntityId, MoveEntityError};
36use bevy_app::{App, Plugin, Update};
37use bevy_ecs::prelude::*;
38use thiserror::Error;
39
40use crate::{
41    client::Client,
42    local_player::{Hunger, InstanceHolder, LocalGameMode},
43    packet::game::SendGamePacketEvent,
44};
45
46#[derive(Error, Debug)]
47pub enum MovePlayerError {
48    #[error("Player is not in world")]
49    PlayerNotInWorld(Backtrace),
50    #[error("{0}")]
51    Io(#[from] io::Error),
52}
53
54impl From<MoveEntityError> for MovePlayerError {
55    fn from(err: MoveEntityError) -> Self {
56        match err {
57            MoveEntityError::EntityDoesNotExist(backtrace) => {
58                MovePlayerError::PlayerNotInWorld(backtrace)
59            }
60        }
61    }
62}
63
64pub struct MovementPlugin;
65
66impl Plugin for MovementPlugin {
67    fn build(&self, app: &mut App) {
68        app.add_message::<StartWalkEvent>()
69            .add_message::<StartSprintEvent>()
70            .add_message::<KnockbackEvent>()
71            .add_systems(
72                Update,
73                (handle_sprint, handle_walk, handle_knockback)
74                    .chain()
75                    .in_set(MoveEventsSystems)
76                    .after(update_bounding_box)
77                    .after(update_last_bounding_box),
78            )
79            .add_systems(
80                GameTick,
81                (
82                    (tick_controls, local_player_ai_step, update_pose)
83                        .chain()
84                        .in_set(PhysicsSystems)
85                        .before(ai_step)
86                        .before(azalea_physics::fluids::update_in_water_state_and_do_fluid_pushing),
87                    send_player_input_packet,
88                    send_sprinting_if_needed
89                        .after(azalea_entity::update_in_loaded_chunk)
90                        .after(travel),
91                    send_position.after(PhysicsSystems),
92                )
93                    .chain(),
94            );
95    }
96}
97
98#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
99pub struct MoveEventsSystems;
100
101impl Client {
102    /// Set whether we're jumping. This acts as if you held space in
103    /// vanilla.
104    ///
105    /// If you want to jump once, use the `jump` function in `azalea`.
106    ///
107    /// If you're making a realistic client, calling this function every tick is
108    /// recommended.
109    pub fn set_jumping(&self, jumping: bool) {
110        self.query_self::<&mut Jumping, _>(|mut j| **j = jumping);
111    }
112
113    /// Returns whether the player will try to jump next tick.
114    pub fn jumping(&self) -> bool {
115        *self.component::<Jumping>()
116    }
117
118    pub fn set_crouching(&self, crouching: bool) {
119        self.query_self::<&mut PhysicsState, _>(|mut p| p.trying_to_crouch = crouching);
120    }
121
122    /// Whether the client is currently trying to sneak.
123    ///
124    /// You may want to check the [`Pose`] instead.
125    pub fn crouching(&self) -> bool {
126        self.query_self::<&PhysicsState, _>(|p| p.trying_to_crouch)
127    }
128
129    /// Sets the direction the client is looking.
130    ///
131    /// `y_rot` is yaw (looking to the side, between -180 to 180), and `x_rot`
132    /// is pitch (looking up and down, between -90 to 90).
133    ///
134    /// You can get these numbers from the vanilla f3 screen.
135    pub fn set_direction(&self, y_rot: f32, x_rot: f32) {
136        self.query_self::<&mut LookDirection, _>(|mut ld| {
137            ld.update(LookDirection::new(y_rot, x_rot));
138        });
139    }
140
141    /// Returns the direction the client is looking.
142    ///
143    /// See [`Self::set_direction`] for more details.
144    pub fn direction(&self) -> (f32, f32) {
145        let look_direction: LookDirection = self.component::<LookDirection>();
146        (look_direction.y_rot(), look_direction.x_rot())
147    }
148}
149
150/// A component that contains the look direction that was last sent over the
151/// network.
152#[derive(Debug, Component, Clone, Default)]
153pub struct LastSentLookDirection {
154    pub x_rot: f32,
155    pub y_rot: f32,
156}
157
158#[allow(clippy::type_complexity)]
159pub fn send_position(
160    mut query: Query<
161        (
162            Entity,
163            &Position,
164            &LookDirection,
165            &mut PhysicsState,
166            &mut LastSentPosition,
167            &mut Physics,
168            &mut LastSentLookDirection,
169        ),
170        With<HasClientLoaded>,
171    >,
172    mut commands: Commands,
173) {
174    for (
175        entity,
176        position,
177        direction,
178        mut physics_state,
179        mut last_sent_position,
180        mut physics,
181        mut last_direction,
182    ) in query.iter_mut()
183    {
184        let packet = {
185            // TODO: the camera being able to be controlled by other entities isn't
186            // implemented yet if !self.is_controlled_camera() { return };
187
188            let x_delta = position.x - last_sent_position.x;
189            let y_delta = position.y - last_sent_position.y;
190            let z_delta = position.z - last_sent_position.z;
191            let y_rot_delta = (direction.y_rot() - last_direction.y_rot) as f64;
192            let x_rot_delta = (direction.x_rot() - last_direction.x_rot) as f64;
193
194            physics_state.position_remainder += 1;
195
196            // boolean sendingPosition = Mth.lengthSquared(xDelta, yDelta, zDelta) >
197            // Mth.square(2.0E-4D) || this.positionReminder >= 20;
198            let is_delta_large_enough =
199                (x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2)) > 2.0e-4f64.powi(2);
200            let sending_position = is_delta_large_enough || physics_state.position_remainder >= 20;
201            let sending_direction = y_rot_delta != 0.0 || x_rot_delta != 0.0;
202
203            // if self.is_passenger() {
204            //   TODO: posrot packet for being a passenger
205            // }
206            let flags = MoveFlags {
207                on_ground: physics.on_ground(),
208                horizontal_collision: physics.horizontal_collision,
209            };
210            let packet = if sending_position && sending_direction {
211                Some(
212                    ServerboundMovePlayerPosRot {
213                        pos: **position,
214                        look_direction: *direction,
215                        flags,
216                    }
217                    .into_variant(),
218                )
219            } else if sending_position {
220                Some(
221                    ServerboundMovePlayerPos {
222                        pos: **position,
223                        flags,
224                    }
225                    .into_variant(),
226                )
227            } else if sending_direction {
228                Some(
229                    ServerboundMovePlayerRot {
230                        look_direction: *direction,
231                        flags,
232                    }
233                    .into_variant(),
234                )
235            } else if physics.last_on_ground() != physics.on_ground() {
236                Some(ServerboundMovePlayerStatusOnly { flags }.into_variant())
237            } else {
238                None
239            };
240
241            if sending_position {
242                **last_sent_position = **position;
243                physics_state.position_remainder = 0;
244            }
245            if sending_direction {
246                last_direction.y_rot = direction.y_rot();
247                last_direction.x_rot = direction.x_rot();
248            }
249
250            let on_ground = physics.on_ground();
251            physics.set_last_on_ground(on_ground);
252            // minecraft checks for autojump here, but also autojump is bad so
253
254            packet
255        };
256
257        if let Some(packet) = packet {
258            commands.trigger(SendGamePacketEvent {
259                sent_by: entity,
260                packet,
261            });
262        }
263    }
264}
265
266#[derive(Debug, Default, Component, Clone, PartialEq, Eq)]
267pub struct LastSentInput(pub ServerboundPlayerInput);
268pub fn send_player_input_packet(
269    mut query: Query<(Entity, &PhysicsState, &Jumping, Option<&LastSentInput>)>,
270    mut commands: Commands,
271) {
272    for (entity, physics_state, jumping, last_sent_input) in query.iter_mut() {
273        let dir = physics_state.move_direction;
274        type D = WalkDirection;
275        let input = ServerboundPlayerInput {
276            forward: matches!(dir, D::Forward | D::ForwardLeft | D::ForwardRight),
277            backward: matches!(dir, D::Backward | D::BackwardLeft | D::BackwardRight),
278            left: matches!(dir, D::Left | D::ForwardLeft | D::BackwardLeft),
279            right: matches!(dir, D::Right | D::ForwardRight | D::BackwardRight),
280            jump: **jumping,
281            shift: physics_state.trying_to_crouch,
282            sprint: physics_state.trying_to_sprint,
283        };
284
285        // if LastSentInput isn't present, we default to assuming we're not pressing any
286        // keys and insert it anyways every time it changes
287        let last_sent_input = last_sent_input.cloned().unwrap_or_default();
288
289        if input != last_sent_input.0 {
290            commands.trigger(SendGamePacketEvent {
291                sent_by: entity,
292                packet: input.clone().into_variant(),
293            });
294            commands.entity(entity).insert(LastSentInput(input));
295        }
296    }
297}
298
299pub fn send_sprinting_if_needed(
300    mut query: Query<(Entity, &MinecraftEntityId, &Sprinting, &mut PhysicsState)>,
301    mut commands: Commands,
302) {
303    for (entity, minecraft_entity_id, sprinting, mut physics_state) in query.iter_mut() {
304        let was_sprinting = physics_state.was_sprinting;
305        if **sprinting != was_sprinting {
306            let sprinting_action = if **sprinting {
307                azalea_protocol::packets::game::s_player_command::Action::StartSprinting
308            } else {
309                azalea_protocol::packets::game::s_player_command::Action::StopSprinting
310            };
311            commands.trigger(SendGamePacketEvent::new(
312                entity,
313                ServerboundPlayerCommand {
314                    id: *minecraft_entity_id,
315                    action: sprinting_action,
316                    data: 0,
317                },
318            ));
319            physics_state.was_sprinting = **sprinting;
320        }
321    }
322}
323
324/// Updates the [`PhysicsState::move_vector`] based on the
325/// [`PhysicsState::move_direction`].
326pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) {
327    for mut physics_state in query.iter_mut() {
328        let mut forward_impulse: f32 = 0.;
329        let mut left_impulse: f32 = 0.;
330        let move_direction = physics_state.move_direction;
331        match move_direction {
332            WalkDirection::Forward | WalkDirection::ForwardRight | WalkDirection::ForwardLeft => {
333                forward_impulse += 1.;
334            }
335            WalkDirection::Backward
336            | WalkDirection::BackwardRight
337            | WalkDirection::BackwardLeft => {
338                forward_impulse -= 1.;
339            }
340            _ => {}
341        };
342        match move_direction {
343            WalkDirection::Right | WalkDirection::ForwardRight | WalkDirection::BackwardRight => {
344                left_impulse += 1.;
345            }
346            WalkDirection::Left | WalkDirection::ForwardLeft | WalkDirection::BackwardLeft => {
347                left_impulse -= 1.;
348            }
349            _ => {}
350        };
351
352        let move_vector = Vec2::new(left_impulse, forward_impulse).normalized();
353        physics_state.move_vector = move_vector;
354    }
355}
356
357/// Makes the bot do one physics tick.
358///
359/// This is handled automatically by the client.
360#[allow(clippy::type_complexity)]
361pub fn local_player_ai_step(
362    mut query: Query<
363        (
364            Entity,
365            &PhysicsState,
366            &PlayerAbilities,
367            &metadata::Swimming,
368            &metadata::SleepingPos,
369            &InstanceHolder,
370            &Position,
371            Option<&Hunger>,
372            Option<&LastSentInput>,
373            &mut Physics,
374            &mut Sprinting,
375            &mut Crouching,
376            &mut Attributes,
377        ),
378        (With<HasClientLoaded>, With<LocalEntity>),
379    >,
380    aabb_query: AabbQuery,
381    collidable_entity_query: CollidableEntityQuery,
382) {
383    for (
384        entity,
385        physics_state,
386        abilities,
387        swimming,
388        sleeping_pos,
389        instance_holder,
390        position,
391        hunger,
392        last_sent_input,
393        mut physics,
394        mut sprinting,
395        mut crouching,
396        mut attributes,
397    ) in query.iter_mut()
398    {
399        // server ai step
400
401        let is_swimming = **swimming;
402        // TODO: implement passengers
403        let is_passenger = false;
404        let is_sleeping = sleeping_pos.is_some();
405
406        let world = instance_holder.instance.read();
407        let ctx = CanPlayerFitCtx {
408            world: &world,
409            entity,
410            position: *position,
411            aabb_query: &aabb_query,
412            collidable_entity_query: &collidable_entity_query,
413            physics: &physics,
414        };
415
416        let new_crouching = !abilities.flying
417            && !is_swimming
418            && !is_passenger
419            && can_player_fit_within_blocks_and_entities_when(&ctx, Pose::Crouching)
420            && (last_sent_input.is_some_and(|i| i.0.shift)
421                || !is_sleeping
422                    && !can_player_fit_within_blocks_and_entities_when(&ctx, Pose::Standing));
423        if **crouching != new_crouching {
424            **crouching = new_crouching;
425        }
426
427        // TODO: food data and abilities
428        // let has_enough_food_to_sprint = self.food_data().food_level ||
429        // self.abilities().may_fly;
430        let has_enough_food_to_sprint = hunger.is_none_or(Hunger::is_enough_to_sprint);
431
432        // TODO: double tapping w to sprint i think
433
434        let trying_to_sprint = physics_state.trying_to_sprint;
435
436        // TODO: swimming
437        let is_underwater = false;
438        let is_in_water = physics.is_in_water();
439        // TODO: elytra
440        let is_fall_flying = false;
441        // TODO: passenger
442        let is_passenger = false;
443        // TODO: using items
444        let using_item = false;
445        // TODO: status effects
446        let has_blindness = false;
447
448        let has_enough_impulse = has_enough_impulse_to_start_sprinting(physics_state);
449
450        // LocalPlayer.canStartSprinting
451        let can_start_sprinting = !**sprinting
452            && has_enough_impulse
453            && has_enough_food_to_sprint
454            && !using_item
455            && !has_blindness
456            && (!is_passenger || is_underwater)
457            && (!is_fall_flying || is_underwater)
458            && (!is_moving_slowly(&crouching) || is_underwater)
459            && (!is_in_water || is_underwater);
460        if trying_to_sprint && can_start_sprinting {
461            set_sprinting(true, &mut sprinting, &mut attributes);
462        }
463
464        if **sprinting {
465            // TODO: swimming
466
467            let vehicle_can_sprint = false;
468            // shouldStopRunSprinting
469            let should_stop_sprinting = has_blindness
470                || (is_passenger && !vehicle_can_sprint)
471                || !has_enough_impulse
472                || !has_enough_food_to_sprint
473                || (physics.horizontal_collision && !physics.minor_horizontal_collision)
474                || (is_in_water && !is_underwater);
475            if should_stop_sprinting {
476                set_sprinting(false, &mut sprinting, &mut attributes);
477            }
478        }
479
480        // TODO: replace those booleans when using items and passengers are properly
481        // implemented
482        let move_vector = modify_input(
483            physics_state.move_vector,
484            false,
485            false,
486            **crouching,
487            &attributes,
488        );
489        physics.x_acceleration = move_vector.x;
490        physics.z_acceleration = move_vector.y;
491    }
492}
493
494fn is_moving_slowly(crouching: &Crouching) -> bool {
495    **crouching
496}
497
498// LocalPlayer.modifyInput
499fn modify_input(
500    mut move_vector: Vec2,
501    is_using_item: bool,
502    is_passenger: bool,
503    moving_slowly: bool,
504    attributes: &Attributes,
505) -> Vec2 {
506    if move_vector.length_squared() == 0. {
507        return move_vector;
508    }
509
510    move_vector *= 0.98;
511    if is_using_item && !is_passenger {
512        move_vector *= 0.2;
513    }
514
515    if moving_slowly {
516        let sneaking_speed = attributes.sneaking_speed.calculate() as f32;
517        move_vector *= sneaking_speed;
518    }
519
520    modify_input_speed_for_square_movement(move_vector)
521}
522fn modify_input_speed_for_square_movement(move_vector: Vec2) -> Vec2 {
523    let length = move_vector.length();
524    if length == 0. {
525        return move_vector;
526    }
527    let scaled_to_inverse_length = move_vector * (1. / length);
528    let dist = distance_to_unit_square(scaled_to_inverse_length);
529    let scale = (length * dist).min(1.);
530    scaled_to_inverse_length * scale
531}
532fn distance_to_unit_square(v: Vec2) -> f32 {
533    let x = v.x.abs();
534    let y = v.y.abs();
535    let ratio = if y > x { x / y } else { y / x };
536    (1. + ratio * ratio).sqrt()
537}
538
539impl Client {
540    /// Start walking in the given direction.
541    ///
542    /// To sprint, use [`Client::sprint`]. To stop walking, call walk with
543    /// [`WalkDirection::None`].
544    ///
545    /// # Example
546    ///
547    /// ```rust,no_run
548    /// # use azalea_client::{Client, WalkDirection};
549    /// # use std::time::Duration;
550    /// # async fn example(mut bot: Client) {
551    /// // walk for one second
552    /// bot.walk(WalkDirection::Forward);
553    /// tokio::time::sleep(Duration::from_secs(1)).await;
554    /// bot.walk(WalkDirection::None);
555    /// # }
556    /// ```
557    pub fn walk(&self, direction: WalkDirection) {
558        let mut ecs = self.ecs.lock();
559        ecs.write_message(StartWalkEvent {
560            entity: self.entity,
561            direction,
562        });
563    }
564
565    /// Start sprinting in the given direction.
566    ///
567    /// o stop moving, call [`bot.walk(WalkDirection::None)`](Self::walk)
568    ///
569    /// # Example
570    ///
571    /// ```rust,no_run
572    /// # use azalea_client::{Client, WalkDirection, SprintDirection};
573    /// # use std::time::Duration;
574    /// # async fn example(mut bot: Client) {
575    /// // sprint for one second
576    /// bot.sprint(SprintDirection::Forward);
577    /// tokio::time::sleep(Duration::from_secs(1)).await;
578    /// bot.walk(WalkDirection::None);
579    /// # }
580    /// ```
581    pub fn sprint(&self, direction: SprintDirection) {
582        let mut ecs = self.ecs.lock();
583        ecs.write_message(StartSprintEvent {
584            entity: self.entity,
585            direction,
586        });
587    }
588}
589
590/// An event sent when the client starts walking.
591///
592/// This does not get sent for non-local entities.
593///
594/// To stop walking or sprinting, send this event with `WalkDirection::None`.
595#[derive(Message, Debug)]
596pub struct StartWalkEvent {
597    pub entity: Entity,
598    pub direction: WalkDirection,
599}
600
601/// The system that makes the player start walking when they receive a
602/// [`StartWalkEvent`].
603pub fn handle_walk(
604    mut events: MessageReader<StartWalkEvent>,
605    mut query: Query<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>,
606) {
607    for event in events.read() {
608        if let Ok((mut physics_state, mut sprinting, mut attributes)) = query.get_mut(event.entity)
609        {
610            physics_state.move_direction = event.direction;
611            physics_state.trying_to_sprint = false;
612            set_sprinting(false, &mut sprinting, &mut attributes);
613        }
614    }
615}
616
617/// An event sent when the client starts sprinting.
618///
619/// This does not get sent for non-local entities.
620#[derive(Message)]
621pub struct StartSprintEvent {
622    pub entity: Entity,
623    pub direction: SprintDirection,
624}
625/// The system that makes the player start sprinting when they receive a
626/// [`StartSprintEvent`].
627pub fn handle_sprint(
628    mut query: Query<&mut PhysicsState>,
629    mut events: MessageReader<StartSprintEvent>,
630) {
631    for event in events.read() {
632        if let Ok(mut physics_state) = query.get_mut(event.entity) {
633            physics_state.move_direction = WalkDirection::from(event.direction);
634            physics_state.trying_to_sprint = true;
635        }
636    }
637}
638
639/// Change whether we're sprinting by adding an attribute modifier to the
640/// player.
641///
642/// You should use the [`Client::walk`] and [`Client::sprint`] functions
643/// instead.
644///
645/// Returns true if the operation was successful.
646fn set_sprinting(
647    sprinting: bool,
648    currently_sprinting: &mut Sprinting,
649    attributes: &mut Attributes,
650) -> bool {
651    **currently_sprinting = sprinting;
652    if sprinting {
653        attributes
654            .movement_speed
655            .try_insert(azalea_entity::attributes::sprinting_modifier())
656            .is_ok()
657    } else {
658        attributes
659            .movement_speed
660            .remove(&azalea_entity::attributes::sprinting_modifier().id)
661            .is_none()
662    }
663}
664
665// Whether the player is moving fast enough to be able to start sprinting.
666fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool {
667    // if self.underwater() {
668    //     self.has_forward_impulse()
669    // } else {
670    physics_state.move_vector.y > 0.8
671    // }
672}
673
674/// An event sent by the server that sets or adds to our velocity.
675///
676/// Usually `KnockbackKind::Set` is used for normal knockback and
677/// `KnockbackKind::Add` is used for explosions, but some servers (notably
678/// Hypixel) use explosions for knockback.
679#[derive(Message)]
680pub struct KnockbackEvent {
681    pub entity: Entity,
682    pub knockback: KnockbackType,
683}
684
685pub enum KnockbackType {
686    Set(Vec3),
687    Add(Vec3),
688}
689
690pub fn handle_knockback(mut query: Query<&mut Physics>, mut events: MessageReader<KnockbackEvent>) {
691    for event in events.read() {
692        if let Ok(mut physics) = query.get_mut(event.entity) {
693            match event.knockback {
694                KnockbackType::Set(velocity) => {
695                    physics.velocity = velocity;
696                }
697                KnockbackType::Add(velocity) => {
698                    physics.velocity += velocity;
699                }
700            }
701        }
702    }
703}
704
705pub fn update_pose(
706    mut query: Query<(
707        Entity,
708        &mut Pose,
709        &Physics,
710        &PhysicsState,
711        &LocalGameMode,
712        &InstanceHolder,
713        &Position,
714    )>,
715    aabb_query: AabbQuery,
716    collidable_entity_query: CollidableEntityQuery,
717) {
718    for (entity, mut pose, physics, physics_state, game_mode, instance_holder, position) in
719        query.iter_mut()
720    {
721        let world = instance_holder.instance.read();
722        let world = &*world;
723        let ctx = CanPlayerFitCtx {
724            world,
725            entity,
726            position: *position,
727            aabb_query: &aabb_query,
728            collidable_entity_query: &collidable_entity_query,
729            physics,
730        };
731
732        if !can_player_fit_within_blocks_and_entities_when(&ctx, Pose::Swimming) {
733            continue;
734        }
735
736        // TODO: implement everything else from getDesiredPose: sleeping, swimming,
737        // fallFlying, spinAttack
738        let desired_pose = if physics_state.trying_to_crouch {
739            Pose::Crouching
740        } else {
741            Pose::Standing
742        };
743
744        // TODO: passengers
745        let is_passenger = false;
746
747        // canPlayerFitWithinBlocksAndEntitiesWhen
748        let new_pose = if game_mode.current == GameMode::Spectator
749            || is_passenger
750            || can_player_fit_within_blocks_and_entities_when(&ctx, desired_pose)
751        {
752            desired_pose
753        } else if can_player_fit_within_blocks_and_entities_when(&ctx, Pose::Crouching) {
754            Pose::Crouching
755        } else {
756            Pose::Swimming
757        };
758
759        // avoid triggering change detection
760        if new_pose != *pose {
761            *pose = new_pose;
762        }
763    }
764}
765
766struct CanPlayerFitCtx<'world, 'state, 'a, 'b> {
767    world: &'a Instance,
768    entity: Entity,
769    position: Position,
770    aabb_query: &'a AabbQuery<'world, 'state, 'b>,
771    collidable_entity_query: &'a CollidableEntityQuery<'world, 'state>,
772    physics: &'a Physics,
773}
774fn can_player_fit_within_blocks_and_entities_when(ctx: &CanPlayerFitCtx, pose: Pose) -> bool {
775    // return this.level().noCollision(this,
776    // this.getDimensions(var1).makeBoundingBox(this.position()).deflate(1.0E-7));
777    no_collision(
778        ctx.world,
779        Some(ctx.entity),
780        ctx.aabb_query,
781        ctx.collidable_entity_query,
782        ctx.physics,
783        &calculate_dimensions(EntityKind::Player, pose).make_bounding_box(*ctx.position),
784        false,
785    )
786}