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