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