aboutsummaryrefslogtreecommitdiff
path: root/azalea-client/src/movement.rs
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2025-02-22 21:45:26 -0600
committerGitHub <noreply@github.com>2025-02-22 21:45:26 -0600
commite21e1b97bf9337e9f4747cd1b545b1b3a03e2ce7 (patch)
treeadd6f8bfce40d0c07845d8aa4c9945a0b918444c /azalea-client/src/movement.rs
parentf8130c3c92946d2293634ba4e252d6bc93026c3c (diff)
downloadazalea-drasl-e21e1b97bf9337e9f4747cd1b545b1b3a03e2ce7.tar.xz
Refactor azalea-client (#205)
* start organizing packet_handling more by moving packet handlers into their own functions * finish writing all the handler functions for packets * use macro for generating match statement for packet handler functions * fix set_entity_data * update config state to also use handler functions * organize az-client file structure by moving things into plugins directory * fix merge issues
Diffstat (limited to 'azalea-client/src/movement.rs')
-rw-r--r--azalea-client/src/movement.rs580
1 files changed, 0 insertions, 580 deletions
diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs
deleted file mode 100644
index b0ff70f4..00000000
--- a/azalea-client/src/movement.rs
+++ /dev/null
@@ -1,580 +0,0 @@
-use std::backtrace::Backtrace;
-
-use azalea_core::position::Vec3;
-use azalea_core::tick::GameTick;
-use azalea_entity::{Attributes, Jumping, metadata::Sprinting};
-use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position};
-use azalea_physics::{PhysicsSet, ai_step};
-use azalea_protocol::packets::game::{ServerboundPlayerCommand, ServerboundPlayerInput};
-use azalea_protocol::packets::{
- Packet,
- game::{
- s_move_player_pos::ServerboundMovePlayerPos,
- s_move_player_pos_rot::ServerboundMovePlayerPosRot,
- s_move_player_rot::ServerboundMovePlayerRot,
- s_move_player_status_only::ServerboundMovePlayerStatusOnly,
- },
-};
-use azalea_world::{MinecraftEntityId, MoveEntityError};
-use bevy_app::{App, Plugin, Update};
-use bevy_ecs::prelude::{Event, EventWriter};
-use bevy_ecs::schedule::SystemSet;
-use bevy_ecs::system::Commands;
-use bevy_ecs::{
- component::Component, entity::Entity, event::EventReader, query::With,
- schedule::IntoSystemConfigs, system::Query,
-};
-use thiserror::Error;
-
-use crate::client::Client;
-use crate::packet_handling::game::SendPacketEvent;
-
-#[derive(Error, Debug)]
-pub enum MovePlayerError {
- #[error("Player is not in world")]
- PlayerNotInWorld(Backtrace),
- #[error("{0}")]
- Io(#[from] std::io::Error),
-}
-
-impl From<MoveEntityError> for MovePlayerError {
- fn from(err: MoveEntityError) -> Self {
- match err {
- MoveEntityError::EntityDoesNotExist(backtrace) => {
- MovePlayerError::PlayerNotInWorld(backtrace)
- }
- }
- }
-}
-
-pub struct PlayerMovePlugin;
-
-impl Plugin for PlayerMovePlugin {
- fn build(&self, app: &mut App) {
- app.add_event::<StartWalkEvent>()
- .add_event::<StartSprintEvent>()
- .add_event::<KnockbackEvent>()
- .add_systems(
- Update,
- (handle_sprint, handle_walk, handle_knockback)
- .chain()
- .in_set(MoveEventsSet),
- )
- .add_systems(
- GameTick,
- (
- (tick_controls, local_player_ai_step)
- .chain()
- .in_set(PhysicsSet)
- .before(ai_step)
- .before(azalea_physics::fluids::update_in_water_state_and_do_fluid_pushing),
- send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk),
- send_player_input_packet,
- send_position.after(PhysicsSet),
- )
- .chain(),
- );
- }
-}
-
-#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
-pub struct MoveEventsSet;
-
-impl Client {
- /// Set whether we're jumping. This acts as if you held space in
- /// vanilla. If you want to jump once, use the `jump` function.
- ///
- /// If you're making a realistic client, calling this function every tick is
- /// recommended.
- pub fn set_jumping(&mut self, jumping: bool) {
- let mut ecs = self.ecs.lock();
- let mut jumping_mut = self.query::<&mut Jumping>(&mut ecs);
- **jumping_mut = jumping;
- }
-
- /// Returns whether the player will try to jump next tick.
- pub fn jumping(&self) -> bool {
- *self.component::<Jumping>()
- }
-
- /// Sets the direction the client is looking. `y_rot` is yaw (looking to the
- /// side), `x_rot` is pitch (looking up and down). You can get these
- /// numbers from the vanilla f3 screen.
- /// `y_rot` goes from -180 to 180, and `x_rot` goes from -90 to 90.
- pub fn set_direction(&mut self, y_rot: f32, x_rot: f32) {
- let mut ecs = self.ecs.lock();
- let mut look_direction = self.query::<&mut LookDirection>(&mut ecs);
-
- (look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot);
- }
-
- /// Returns the direction the client is looking. The first value is the y
- /// rotation (ie. yaw, looking to the side) and the second value is the x
- /// rotation (ie. pitch, looking up and down).
- pub fn direction(&self) -> (f32, f32) {
- let look_direction = self.component::<LookDirection>();
- (look_direction.y_rot, look_direction.x_rot)
- }
-}
-
-/// A component that contains the look direction that was last sent over the
-/// network.
-#[derive(Debug, Component, Clone, Default)]
-pub struct LastSentLookDirection {
- pub x_rot: f32,
- pub y_rot: f32,
-}
-
-/// Component for entities that can move and sprint. Usually only in
-/// [`LocalEntity`]s.
-///
-/// [`LocalEntity`]: azalea_entity::LocalEntity
-#[derive(Default, Component, Clone)]
-pub struct PhysicsState {
- /// Minecraft only sends a movement packet either after 20 ticks or if the
- /// player moved enough. This is that tick counter.
- pub position_remainder: u32,
- pub was_sprinting: bool,
- // Whether we're going to try to start sprinting this tick. Equivalent to
- // holding down ctrl for a tick.
- pub trying_to_sprint: bool,
-
- pub move_direction: WalkDirection,
- pub forward_impulse: f32,
- pub left_impulse: f32,
-}
-
-#[allow(clippy::type_complexity)]
-pub fn send_position(
- mut query: Query<
- (
- Entity,
- &Position,
- &LookDirection,
- &mut PhysicsState,
- &mut LastSentPosition,
- &mut Physics,
- &mut LastSentLookDirection,
- ),
- With<InLoadedChunk>,
- >,
- mut send_packet_events: EventWriter<SendPacketEvent>,
-) {
- for (
- entity,
- position,
- direction,
- mut physics_state,
- mut last_sent_position,
- mut physics,
- mut last_direction,
- ) in query.iter_mut()
- {
- let packet = {
- // TODO: the camera being able to be controlled by other entities isn't
- // implemented yet if !self.is_controlled_camera() { return };
-
- let x_delta = position.x - last_sent_position.x;
- let y_delta = position.y - last_sent_position.y;
- let z_delta = position.z - last_sent_position.z;
- let y_rot_delta = (direction.y_rot - last_direction.y_rot) as f64;
- let x_rot_delta = (direction.x_rot - last_direction.x_rot) as f64;
-
- physics_state.position_remainder += 1;
-
- // boolean sendingPosition = Mth.lengthSquared(xDelta, yDelta, zDelta) >
- // Mth.square(2.0E-4D) || this.positionReminder >= 20;
- let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
- > 2.0e-4f64.powi(2))
- || physics_state.position_remainder >= 20;
- let sending_direction = y_rot_delta != 0.0 || x_rot_delta != 0.0;
-
- // if self.is_passenger() {
- // TODO: posrot packet for being a passenger
- // }
- let packet = if sending_position && sending_direction {
- Some(
- ServerboundMovePlayerPosRot {
- pos: **position,
- look_direction: *direction,
- on_ground: physics.on_ground(),
- }
- .into_variant(),
- )
- } else if sending_position {
- Some(
- ServerboundMovePlayerPos {
- pos: **position,
- on_ground: physics.on_ground(),
- }
- .into_variant(),
- )
- } else if sending_direction {
- Some(
- ServerboundMovePlayerRot {
- look_direction: *direction,
- on_ground: physics.on_ground(),
- }
- .into_variant(),
- )
- } else if physics.last_on_ground() != physics.on_ground() {
- Some(
- ServerboundMovePlayerStatusOnly {
- on_ground: physics.on_ground(),
- }
- .into_variant(),
- )
- } else {
- None
- };
-
- if sending_position {
- **last_sent_position = **position;
- physics_state.position_remainder = 0;
- }
- if sending_direction {
- last_direction.y_rot = direction.y_rot;
- last_direction.x_rot = direction.x_rot;
- }
-
- let on_ground = physics.on_ground();
- physics.set_last_on_ground(on_ground);
- // minecraft checks for autojump here, but also autojump is bad so
-
- packet
- };
-
- if let Some(packet) = packet {
- send_packet_events.send(SendPacketEvent {
- sent_by: entity,
- packet,
- });
- }
- }
-}
-
-#[derive(Debug, Default, Component, Clone, PartialEq, Eq)]
-pub struct LastSentInput(pub ServerboundPlayerInput);
-pub fn send_player_input_packet(
- mut query: Query<(Entity, &PhysicsState, &Jumping, Option<&LastSentInput>)>,
- mut send_packet_events: EventWriter<SendPacketEvent>,
- mut commands: Commands,
-) {
- for (entity, physics_state, jumping, last_sent_input) in query.iter_mut() {
- let dir = physics_state.move_direction;
- type D = WalkDirection;
- let input = ServerboundPlayerInput {
- forward: matches!(dir, D::Forward | D::ForwardLeft | D::ForwardRight),
- backward: matches!(dir, D::Backward | D::BackwardLeft | D::BackwardRight),
- left: matches!(dir, D::Left | D::ForwardLeft | D::BackwardLeft),
- right: matches!(dir, D::Right | D::ForwardRight | D::BackwardRight),
- jump: **jumping,
- // TODO: implement sneaking
- shift: false,
- sprint: physics_state.trying_to_sprint,
- };
-
- // if LastSentInput isn't present, we default to assuming we're not pressing any
- // keys and insert it anyways every time it changes
- let last_sent_input = last_sent_input.cloned().unwrap_or_default();
-
- if input != last_sent_input.0 {
- send_packet_events.send(SendPacketEvent {
- sent_by: entity,
- packet: input.clone().into_variant(),
- });
- commands.entity(entity).insert(LastSentInput(input));
- }
- }
-}
-
-fn send_sprinting_if_needed(
- mut query: Query<(Entity, &MinecraftEntityId, &Sprinting, &mut PhysicsState)>,
- mut send_packet_events: EventWriter<SendPacketEvent>,
-) {
- for (entity, minecraft_entity_id, sprinting, mut physics_state) in query.iter_mut() {
- let was_sprinting = physics_state.was_sprinting;
- if **sprinting != was_sprinting {
- let sprinting_action = if **sprinting {
- azalea_protocol::packets::game::s_player_command::Action::StartSprinting
- } else {
- azalea_protocol::packets::game::s_player_command::Action::StopSprinting
- };
- send_packet_events.send(SendPacketEvent::new(
- entity,
- ServerboundPlayerCommand {
- id: *minecraft_entity_id,
- action: sprinting_action,
- data: 0,
- },
- ));
- physics_state.was_sprinting = **sprinting;
- }
- }
-}
-
-/// Update the impulse from self.move_direction. The multiplier is used for
-/// sneaking.
-pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) {
- for mut physics_state in query.iter_mut() {
- let multiplier: Option<f32> = None;
-
- let mut forward_impulse: f32 = 0.;
- let mut left_impulse: f32 = 0.;
- let move_direction = physics_state.move_direction;
- match move_direction {
- WalkDirection::Forward | WalkDirection::ForwardRight | WalkDirection::ForwardLeft => {
- forward_impulse += 1.;
- }
- WalkDirection::Backward
- | WalkDirection::BackwardRight
- | WalkDirection::BackwardLeft => {
- forward_impulse -= 1.;
- }
- _ => {}
- };
- match move_direction {
- WalkDirection::Right | WalkDirection::ForwardRight | WalkDirection::BackwardRight => {
- left_impulse += 1.;
- }
- WalkDirection::Left | WalkDirection::ForwardLeft | WalkDirection::BackwardLeft => {
- left_impulse -= 1.;
- }
- _ => {}
- };
- physics_state.forward_impulse = forward_impulse;
- physics_state.left_impulse = left_impulse;
-
- if let Some(multiplier) = multiplier {
- physics_state.forward_impulse *= multiplier;
- physics_state.left_impulse *= multiplier;
- }
- }
-}
-
-/// Makes the bot do one physics tick. Note that this is already handled
-/// automatically by the client.
-pub fn local_player_ai_step(
- mut query: Query<
- (&PhysicsState, &mut Physics, &mut Sprinting, &mut Attributes),
- With<InLoadedChunk>,
- >,
-) {
- for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
- // server ai step
- physics.x_acceleration = physics_state.left_impulse;
- physics.z_acceleration = physics_state.forward_impulse;
-
- // TODO: food data and abilities
- // let has_enough_food_to_sprint = self.food_data().food_level ||
- // self.abilities().may_fly;
- let has_enough_food_to_sprint = true;
-
- // TODO: double tapping w to sprint i think
-
- let trying_to_sprint = physics_state.trying_to_sprint;
-
- if !**sprinting
- && (
- // !self.is_in_water()
- // || self.is_underwater() &&
- has_enough_impulse_to_start_sprinting(physics_state)
- && has_enough_food_to_sprint
- // && !self.using_item()
- // && !self.has_effect(MobEffects.BLINDNESS)
- && trying_to_sprint
- )
- {
- set_sprinting(true, &mut sprinting, &mut attributes);
- }
- }
-}
-
-impl Client {
- /// Start walking in the given direction. To sprint, use
- /// [`Client::sprint`]. To stop walking, call walk with
- /// `WalkDirection::None`.
- ///
- /// # Examples
- ///
- /// Walk for 1 second
- /// ```rust,no_run
- /// # use azalea_client::{Client, WalkDirection};
- /// # use std::time::Duration;
- /// # async fn example(mut bot: Client) {
- /// bot.walk(WalkDirection::Forward);
- /// tokio::time::sleep(Duration::from_secs(1)).await;
- /// bot.walk(WalkDirection::None);
- /// # }
- /// ```
- pub fn walk(&mut self, direction: WalkDirection) {
- let mut ecs = self.ecs.lock();
- ecs.send_event(StartWalkEvent {
- entity: self.entity,
- direction,
- });
- }
-
- /// Start sprinting in the given direction. To stop moving, call
- /// [`Client::walk(WalkDirection::None)`]
- ///
- /// # Examples
- ///
- /// Sprint for 1 second
- /// ```rust,no_run
- /// # use azalea_client::{Client, WalkDirection, SprintDirection};
- /// # use std::time::Duration;
- /// # async fn example(mut bot: Client) {
- /// bot.sprint(SprintDirection::Forward);
- /// tokio::time::sleep(Duration::from_secs(1)).await;
- /// bot.walk(WalkDirection::None);
- /// # }
- /// ```
- pub fn sprint(&mut self, direction: SprintDirection) {
- let mut ecs = self.ecs.lock();
- ecs.send_event(StartSprintEvent {
- entity: self.entity,
- direction,
- });
- }
-}
-
-/// An event sent when the client starts walking. This does not get sent for
-/// non-local entities.
-///
-/// To stop walking or sprinting, send this event with `WalkDirection::None`.
-#[derive(Event, Debug)]
-pub struct StartWalkEvent {
- pub entity: Entity,
- pub direction: WalkDirection,
-}
-
-/// The system that makes the player start walking when they receive a
-/// [`StartWalkEvent`].
-pub fn handle_walk(
- mut events: EventReader<StartWalkEvent>,
- mut query: Query<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>,
-) {
- for event in events.read() {
- if let Ok((mut physics_state, mut sprinting, mut attributes)) = query.get_mut(event.entity)
- {
- physics_state.move_direction = event.direction;
- physics_state.trying_to_sprint = false;
- set_sprinting(false, &mut sprinting, &mut attributes);
- }
- }
-}
-
-/// An event sent when the client starts sprinting. This does not get sent for
-/// non-local entities.
-#[derive(Event)]
-pub struct StartSprintEvent {
- pub entity: Entity,
- pub direction: SprintDirection,
-}
-/// The system that makes the player start sprinting when they receive a
-/// [`StartSprintEvent`].
-pub fn handle_sprint(
- mut query: Query<&mut PhysicsState>,
- mut events: EventReader<StartSprintEvent>,
-) {
- for event in events.read() {
- if let Ok(mut physics_state) = query.get_mut(event.entity) {
- physics_state.move_direction = WalkDirection::from(event.direction);
- physics_state.trying_to_sprint = true;
- }
- }
-}
-
-/// Change whether we're sprinting by adding an attribute modifier to the
-/// player. You should use the [`walk`] and [`sprint`] methods instead.
-/// Returns if the operation was successful.
-fn set_sprinting(
- sprinting: bool,
- currently_sprinting: &mut Sprinting,
- attributes: &mut Attributes,
-) -> bool {
- **currently_sprinting = sprinting;
- if sprinting {
- attributes
- .speed
- .try_insert(azalea_entity::attributes::sprinting_modifier())
- .is_ok()
- } else {
- attributes
- .speed
- .remove(&azalea_entity::attributes::sprinting_modifier().id)
- .is_none()
- }
-}
-
-// Whether the player is moving fast enough to be able to start sprinting.
-fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool {
- // if self.underwater() {
- // self.has_forward_impulse()
- // } else {
- physics_state.forward_impulse > 0.8
- // }
-}
-
-/// An event sent by the server that sets or adds to our velocity. Usually
-/// `KnockbackKind::Set` is used for normal knockback and `KnockbackKind::Add`
-/// is used for explosions, but some servers (notably Hypixel) use explosions
-/// for knockback.
-#[derive(Event)]
-pub struct KnockbackEvent {
- pub entity: Entity,
- pub knockback: KnockbackType,
-}
-
-pub enum KnockbackType {
- Set(Vec3),
- Add(Vec3),
-}
-
-pub fn handle_knockback(mut query: Query<&mut Physics>, mut events: EventReader<KnockbackEvent>) {
- for event in events.read() {
- if let Ok(mut physics) = query.get_mut(event.entity) {
- match event.knockback {
- KnockbackType::Set(velocity) => {
- physics.velocity = velocity;
- }
- KnockbackType::Add(velocity) => {
- physics.velocity += velocity;
- }
- }
- }
- }
-}
-
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
-pub enum WalkDirection {
- #[default]
- None,
- Forward,
- Backward,
- Left,
- Right,
- ForwardRight,
- ForwardLeft,
- BackwardRight,
- BackwardLeft,
-}
-
-/// The directions that we can sprint in. It's a subset of [`WalkDirection`].
-#[derive(Clone, Copy, Debug)]
-pub enum SprintDirection {
- Forward,
- ForwardRight,
- ForwardLeft,
-}
-
-impl From<SprintDirection> for WalkDirection {
- fn from(d: SprintDirection) -> Self {
- match d {
- SprintDirection::Forward => WalkDirection::Forward,
- SprintDirection::ForwardRight => WalkDirection::ForwardRight,
- SprintDirection::ForwardLeft => WalkDirection::ForwardLeft,
- }
- }
-}