diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2025-02-22 21:45:26 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-22 21:45:26 -0600 |
| commit | e21e1b97bf9337e9f4747cd1b545b1b3a03e2ce7 (patch) | |
| tree | add6f8bfce40d0c07845d8aa4c9945a0b918444c /azalea-client/src/movement.rs | |
| parent | f8130c3c92946d2293634ba4e252d6bc93026c3c (diff) | |
| download | azalea-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.rs | 580 |
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, - } - } -} |
