diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2025-08-14 20:40:13 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-14 20:40:13 -0500 |
| commit | e74ed047dbaf3877db4a89a2d589e992abd0bb11 (patch) | |
| tree | 0a728c8be167a1d59a5492ed9df666f41cf12e57 /azalea-client/src/plugins | |
| parent | 6695132ddb31780786c67b8b9ff5df8ab3891438 (diff) | |
| download | azalea-drasl-e74ed047dbaf3877db4a89a2d589e992abd0bb11.tar.xz | |
Sneaking (#237)
* start implementing sneaking
* fix horizontal_collision being inverted and cleanup
* clippy
* change dimensions and eye height based on pose
* proper support for automatically crouching in certain cases
* fix anticheat issues
* add line to changelog and update a comment
Diffstat (limited to 'azalea-client/src/plugins')
| -rw-r--r-- | azalea-client/src/plugins/attack.rs | 10 | ||||
| -rw-r--r-- | azalea-client/src/plugins/interact/mod.rs | 19 | ||||
| -rw-r--r-- | azalea-client/src/plugins/interact/pick.rs | 9 | ||||
| -rw-r--r-- | azalea-client/src/plugins/inventory.rs | 5 | ||||
| -rw-r--r-- | azalea-client/src/plugins/mining.rs | 5 | ||||
| -rw-r--r-- | azalea-client/src/plugins/movement.rs | 322 | ||||
| -rw-r--r-- | azalea-client/src/plugins/packet/game/mod.rs | 4 |
7 files changed, 272 insertions, 102 deletions
diff --git a/azalea-client/src/plugins/attack.rs b/azalea-client/src/plugins/attack.rs index ec4337e5..7d730bb7 100644 --- a/azalea-client/src/plugins/attack.rs +++ b/azalea-client/src/plugins/attack.rs @@ -1,8 +1,6 @@ use azalea_core::{game_type::GameMode, tick::GameTick}; use azalea_entity::{ - Attributes, Physics, - indexing::EntityIdIndex, - metadata::{ShiftKeyDown, Sprinting}, + Attributes, Crouching, Physics, indexing::EntityIdIndex, metadata::Sprinting, update_bounding_box, }; use azalea_physics::PhysicsSet; @@ -100,7 +98,7 @@ pub fn handle_attack_queued( &mut Sprinting, &AttackQueued, &LocalGameMode, - &ShiftKeyDown, + &Crouching, &EntityIdIndex, )>, ) { @@ -111,7 +109,7 @@ pub fn handle_attack_queued( mut sprinting, attack_queued, game_mode, - sneaking, + crouching, entity_id_index, ) in &mut query { @@ -128,7 +126,7 @@ pub fn handle_attack_queued( ServerboundInteract { entity_id: target_entity_id, action: s_interact::ActionType::Attack, - using_secondary_action: **sneaking, + using_secondary_action: **crouching, }, )); commands.trigger(SwingArmEvent { diff --git a/azalea-client/src/plugins/interact/mod.rs b/azalea-client/src/plugins/interact/mod.rs index 634d492c..0275ca97 100644 --- a/azalea-client/src/plugins/interact/mod.rs +++ b/azalea-client/src/plugins/interact/mod.rs @@ -11,7 +11,7 @@ use azalea_core::{ tick::GameTick, }; use azalea_entity::{ - Attributes, LocalEntity, LookDirection, + Attributes, Crouching, LocalEntity, LookDirection, PlayerAbilities, attributes::{ creative_block_interaction_range_modifier, creative_entity_interaction_range_modifier, }, @@ -36,7 +36,7 @@ use crate::{ attack::handle_attack_event, interact::pick::{HitResultComponent, update_hit_result_component}, inventory::{Inventory, InventorySet}, - local_player::{LocalGameMode, PermissionLevel, PlayerAbilities}, + local_player::{LocalGameMode, PermissionLevel}, movement::MoveEventsSet, packet::game::SendPacketEvent, respawn::perform_respawn, @@ -250,12 +250,20 @@ pub fn handle_start_use_item_queued( &mut BlockStatePredictionHandler, &HitResultComponent, &LookDirection, + &Crouching, Option<&Mining>, )>, entity_id_query: Query<&MinecraftEntityId>, ) { - for (entity, start_use_item, mut prediction_handler, hit_result, look_direction, mining) in - query + for ( + entity, + start_use_item, + mut prediction_handler, + hit_result, + look_direction, + crouching, + mining, + ) in query { commands.entity(entity).remove::<StartUseItemQueued>(); @@ -332,8 +340,7 @@ pub fn handle_start_use_item_queued( location: r.location, hand: InteractionHand::MainHand, }, - // TODO: sneaking - using_secondary_action: false, + using_secondary_action: **crouching, }, )); } diff --git a/azalea-client/src/plugins/interact/pick.rs b/azalea-client/src/plugins/interact/pick.rs index cebbf905..a0a75910 100644 --- a/azalea-client/src/plugins/interact/pick.rs +++ b/azalea-client/src/plugins/interact/pick.rs @@ -5,7 +5,8 @@ use azalea_core::{ position::Vec3, }; use azalea_entity::{ - Attributes, Dead, EyeHeight, LocalEntity, LookDirection, Physics, Position, + Attributes, Dead, LocalEntity, LookDirection, Physics, Position, + dimensions::EntityDimensions, metadata::{ArmorStandMarker, Marker}, view_vector, }; @@ -31,7 +32,7 @@ pub fn update_hit_result_component( Entity, Option<&mut HitResultComponent>, &Position, - &EyeHeight, + &EntityDimensions, &LookDirection, &InstanceName, &Physics, @@ -47,7 +48,7 @@ pub fn update_hit_result_component( entity, hit_result_ref, position, - eye_height, + dimensions, look_direction, world_name, physics, @@ -57,7 +58,7 @@ pub fn update_hit_result_component( let block_pick_range = attributes.block_interaction_range.calculate(); let entity_pick_range = attributes.entity_interaction_range.calculate(); - let eye_position = position.up(eye_height.into()); + let eye_position = position.up(dimensions.eye_height.into()); let Some(world_lock) = instance_container.get(world_name) else { continue; diff --git a/azalea-client/src/plugins/inventory.rs b/azalea-client/src/plugins/inventory.rs index 732e1154..ecc8e826 100644 --- a/azalea-client/src/plugins/inventory.rs +++ b/azalea-client/src/plugins/inventory.rs @@ -4,6 +4,7 @@ use std::{ }; use azalea_chat::FormattedText; +use azalea_entity::PlayerAbilities; pub use azalea_inventory::*; use azalea_inventory::{ item::MaxStackSizeExt, @@ -23,9 +24,7 @@ use bevy_app::{App, Plugin, Update}; use bevy_ecs::prelude::*; use tracing::{error, warn}; -use crate::{ - Client, local_player::PlayerAbilities, packet::game::SendPacketEvent, respawn::perform_respawn, -}; +use crate::{Client, packet::game::SendPacketEvent, respawn::perform_respawn}; pub struct InventoryPlugin; impl Plugin for InventoryPlugin { diff --git a/azalea-client/src/plugins/mining.rs b/azalea-client/src/plugins/mining.rs index f584bacb..1b7adadc 100644 --- a/azalea-client/src/plugins/mining.rs +++ b/azalea-client/src/plugins/mining.rs @@ -1,6 +1,6 @@ use azalea_block::{BlockState, BlockTrait, fluid_state::FluidState}; use azalea_core::{direction::Direction, game_type::GameMode, position::BlockPos, tick::GameTick}; -use azalea_entity::{FluidOnEyes, Physics, Position, mining::get_mine_progress}; +use azalea_entity::{FluidOnEyes, Physics, PlayerAbilities, Position, mining::get_mine_progress}; use azalea_inventory::ItemStack; use azalea_physics::{PhysicsSet, collision::BlockWithShape}; use azalea_protocol::packets::game::s_player_action::{self, ServerboundPlayerAction}; @@ -17,7 +17,7 @@ use crate::{ check_is_interaction_restricted, pick::HitResultComponent, }, inventory::{Inventory, InventorySet}, - local_player::{InstanceHolder, LocalGameMode, PermissionLevel, PlayerAbilities}, + local_player::{InstanceHolder, LocalGameMode, PermissionLevel}, movement::MoveEventsSet, packet::game::SendPacketEvent, }; @@ -55,7 +55,6 @@ impl Plugin for MiningPlugin { .in_set(MiningSet) .after(InventorySet) .after(MoveEventsSet) - .before(azalea_entity::update_bounding_box) .after(azalea_entity::update_fluid_on_eyes) .after(crate::interact::pick::update_hit_result_component) .after(crate::attack::handle_attack_event) diff --git a/azalea-client/src/plugins/movement.rs b/azalea-client/src/plugins/movement.rs index c9b3b070..ad6779fa 100644 --- a/azalea-client/src/plugins/movement.rs +++ b/azalea-client/src/plugins/movement.rs @@ -1,14 +1,23 @@ use std::{backtrace::Backtrace, io}; use azalea_core::{ + game_type::GameMode, position::{Vec2, Vec3}, tick::GameTick, }; use azalea_entity::{ - Attributes, HasClientLoaded, Jumping, LastSentPosition, LookDirection, Physics, Position, - metadata::Sprinting, + Attributes, Crouching, HasClientLoaded, Jumping, LastSentPosition, LocalEntity, LookDirection, + Physics, PlayerAbilities, Pose, Position, + dimensions::calculate_dimensions, + metadata::{self, Sprinting}, + update_bounding_box, +}; +use azalea_physics::{ + PhysicsSet, ai_step, + collision::entity_collisions::{CollidableEntityQuery, PhysicsQuery}, + local_player::{PhysicsState, SprintDirection, WalkDirection}, + travel::{no_collision, travel}, }; -use azalea_physics::{PhysicsSet, ai_step}; use azalea_protocol::{ common::movements::MoveFlags, packets::{ @@ -22,12 +31,17 @@ use azalea_protocol::{ }, }, }; -use azalea_world::{MinecraftEntityId, MoveEntityError}; +use azalea_registry::EntityKind; +use azalea_world::{Instance, MinecraftEntityId, MoveEntityError}; use bevy_app::{App, Plugin, Update}; use bevy_ecs::prelude::*; use thiserror::Error; -use crate::{client::Client, packet::game::SendPacketEvent}; +use crate::{ + client::Client, + local_player::{Hunger, InstanceHolder, LocalGameMode}, + packet::game::SendPacketEvent, +}; #[derive(Error, Debug)] pub enum MovePlayerError { @@ -58,18 +72,21 @@ impl Plugin for MovementPlugin { Update, (handle_sprint, handle_walk, handle_knockback) .chain() - .in_set(MoveEventsSet), + .in_set(MoveEventsSet) + .after(update_bounding_box), ) .add_systems( GameTick, ( - (tick_controls, local_player_ai_step) + (tick_controls, local_player_ai_step, update_pose) .chain() .in_set(PhysicsSet) .before(ai_step) .before(azalea_physics::fluids::update_in_water_state_and_do_fluid_pushing), send_player_input_packet, - send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk), + send_sprinting_if_needed + .after(azalea_entity::update_in_loaded_chunk) + .after(travel), send_position.after(PhysicsSet), ) .chain(), @@ -97,6 +114,21 @@ impl Client { *self.component::<Jumping>() } + pub fn set_crouching(&self, crouching: bool) { + let mut ecs = self.ecs.lock(); + let mut physics_state = self.query::<&mut PhysicsState>(&mut ecs); + physics_state.trying_to_crouch = crouching; + } + + /// Whether the client is currently trying to sneak. + /// + /// You may want to check the [`Pose`] instead. + pub fn crouching(&self) -> bool { + let mut ecs = self.ecs.lock(); + let physics_state = self.query::<&PhysicsState>(&mut ecs); + physics_state.trying_to_crouch + } + /// 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. @@ -125,24 +157,6 @@ pub struct LastSentLookDirection { 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 move_vector: Vec2, -} - #[allow(clippy::type_complexity)] pub fn send_position( mut query: Query< @@ -266,8 +280,7 @@ pub fn send_player_input_packet( 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, + shift: physics_state.trying_to_crouch, sprint: physics_state.trying_to_sprint, }; @@ -345,46 +358,144 @@ pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) { /// Makes the bot do one physics tick. Note that this is already handled /// automatically by the client. +#[allow(clippy::type_complexity)] pub fn local_player_ai_step( mut query: Query< - (&PhysicsState, &mut Physics, &mut Sprinting, &mut Attributes), - With<HasClientLoaded>, + ( + Entity, + &PhysicsState, + &PlayerAbilities, + &metadata::Swimming, + &metadata::SleepingPos, + &InstanceHolder, + &Position, + Option<&Hunger>, + Option<&LastSentInput>, + &mut Physics, + &mut Sprinting, + &mut Crouching, + &mut Attributes, + ), + (With<HasClientLoaded>, With<LocalEntity>), >, + physics_query: PhysicsQuery, + collidable_entity_query: CollidableEntityQuery, ) { - for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() { + for ( + entity, + physics_state, + abilities, + swimming, + sleeping_pos, + instance_holder, + position, + hunger, + last_sent_input, + mut physics, + mut sprinting, + mut crouching, + mut attributes, + ) in query.iter_mut() + { // server ai step - // TODO: replace those booleans when using items, passengers, and sneaking are - // properly implemented - let move_vector = modify_input(physics_state.move_vector, false, false, false, &attributes); - physics.x_acceleration = move_vector.x; - physics.z_acceleration = move_vector.y; + let is_swimming = **swimming; + // TODO: implement passengers + let is_passenger = false; + let is_sleeping = sleeping_pos.is_some(); + + let world = instance_holder.instance.read(); + let ctx = CanPlayerFitCtx { + world: &world, + entity, + position: *position, + physics_query: &physics_query, + collidable_entity_query: &collidable_entity_query, + physics: &physics, + }; + + let new_crouching = !abilities.flying + && !is_swimming + && !is_passenger + && can_player_fit_within_blocks_and_entities_when(&ctx, Pose::Crouching) + && (last_sent_input.is_some_and(|i| i.0.shift) + || !is_sleeping + && !can_player_fit_within_blocks_and_entities_when(&ctx, Pose::Standing)); + if **crouching != new_crouching { + **crouching = new_crouching; + } // 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; + let has_enough_food_to_sprint = hunger.is_none_or(Hunger::is_enough_to_sprint); // 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 - ) - { + // TODO: swimming + let is_underwater = false; + let is_in_water = physics.is_in_water(); + // TODO: elytra + let is_fall_flying = false; + // TODO: passenger + let is_passenger = false; + // TODO: using items + let using_item = false; + // TODO: status effects + let has_blindness = false; + + let has_enough_impulse = has_enough_impulse_to_start_sprinting(physics_state); + + // LocalPlayer.canStartSprinting + let can_start_sprinting = !**sprinting + && has_enough_impulse + && has_enough_food_to_sprint + && !using_item + && !has_blindness + && (!is_passenger || is_underwater) + && (!is_fall_flying || is_underwater) + && (!is_moving_slowly(&crouching) || is_underwater) + && (!is_in_water || is_underwater); + if trying_to_sprint && can_start_sprinting { set_sprinting(true, &mut sprinting, &mut attributes); } + + if **sprinting { + // TODO: swimming + + let vehicle_can_sprint = false; + // shouldStopRunSprinting + let should_stop_sprinting = has_blindness + || (is_passenger && !vehicle_can_sprint) + || !has_enough_impulse + || !has_enough_food_to_sprint + || (physics.horizontal_collision && !physics.minor_horizontal_collision) + || (is_in_water && !is_underwater); + if should_stop_sprinting { + set_sprinting(false, &mut sprinting, &mut attributes); + } + } + + // TODO: replace those booleans when using items and passengers are properly + // implemented + let move_vector = modify_input( + physics_state.move_vector, + false, + false, + **crouching, + &attributes, + ); + physics.x_acceleration = move_vector.x; + physics.z_acceleration = move_vector.y; } } +fn is_moving_slowly(crouching: &Crouching) -> bool { + **crouching +} + // LocalPlayer.modifyInput fn modify_input( mut move_vector: Vec2, @@ -523,8 +634,12 @@ pub fn handle_sprint( } /// 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. +/// player. +/// +/// You should use the [`Client::walk`] and [`Client::sprint`] functions +/// instead. +/// +/// Returns true if the operation was successful. fn set_sprinting( sprinting: bool, currently_sprinting: &mut Sprinting, @@ -533,12 +648,12 @@ fn set_sprinting( **currently_sprinting = sprinting; if sprinting { attributes - .speed + .movement_speed .try_insert(azalea_entity::attributes::sprinting_modifier()) .is_ok() } else { attributes - .speed + .movement_speed .remove(&azalea_entity::attributes::sprinting_modifier().id) .is_none() } @@ -583,34 +698,85 @@ pub fn handle_knockback(mut query: Query<&mut Physics>, mut events: EventReader< } } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub enum WalkDirection { - #[default] - None, - Forward, - Backward, - Left, - Right, - ForwardRight, - ForwardLeft, - BackwardRight, - BackwardLeft, -} +pub fn update_pose( + mut query: Query<( + Entity, + &mut Pose, + &Physics, + &PhysicsState, + &LocalGameMode, + &InstanceHolder, + &Position, + )>, + physics_query: PhysicsQuery, + collidable_entity_query: CollidableEntityQuery, +) { + for (entity, mut pose, physics, physics_state, game_mode, instance_holder, position) in + query.iter_mut() + { + let world = instance_holder.instance.read(); + let world = &*world; + let ctx = CanPlayerFitCtx { + world, + entity, + position: *position, + physics_query: &physics_query, + collidable_entity_query: &collidable_entity_query, + physics, + }; -/// The directions that we can sprint in. It's a subset of [`WalkDirection`]. -#[derive(Clone, Copy, Debug)] -pub enum SprintDirection { - Forward, - ForwardRight, - ForwardLeft, -} + if !can_player_fit_within_blocks_and_entities_when(&ctx, Pose::Swimming) { + continue; + } + + // TODO: implement everything else from getDesiredPose: sleeping, swimming, + // fallFlying, spinAttack + let desired_pose = if physics_state.trying_to_crouch { + Pose::Crouching + } else { + Pose::Standing + }; -impl From<SprintDirection> for WalkDirection { - fn from(d: SprintDirection) -> Self { - match d { - SprintDirection::Forward => WalkDirection::Forward, - SprintDirection::ForwardRight => WalkDirection::ForwardRight, - SprintDirection::ForwardLeft => WalkDirection::ForwardLeft, + // TODO: passengers + let is_passenger = false; + + // canPlayerFitWithinBlocksAndEntitiesWhen + let new_pose = if game_mode.current == GameMode::Spectator + || is_passenger + || can_player_fit_within_blocks_and_entities_when(&ctx, desired_pose) + { + desired_pose + } else if can_player_fit_within_blocks_and_entities_when(&ctx, Pose::Crouching) { + Pose::Crouching + } else { + Pose::Swimming + }; + + // avoid triggering change detection + if new_pose != *pose { + *pose = new_pose; } } } + +struct CanPlayerFitCtx<'world, 'state, 'a, 'b> { + world: &'a Instance, + entity: Entity, + position: Position, + physics_query: &'a PhysicsQuery<'world, 'state, 'b>, + collidable_entity_query: &'a CollidableEntityQuery<'world, 'state>, + physics: &'a Physics, +} +fn can_player_fit_within_blocks_and_entities_when(ctx: &CanPlayerFitCtx, pose: Pose) -> bool { + // return this.level().noCollision(this, + // this.getDimensions(var1).makeBoundingBox(this.position()).deflate(1.0E-7)); + no_collision( + ctx.world, + Some(ctx.entity), + ctx.physics_query, + ctx.collidable_entity_query, + ctx.physics, + &calculate_dimensions(EntityKind::Player, pose).make_bounding_box(*ctx.position), + false, + ) +} diff --git a/azalea-client/src/plugins/packet/game/mod.rs b/azalea-client/src/plugins/packet/game/mod.rs index 26d83195..49523002 100644 --- a/azalea-client/src/plugins/packet/game/mod.rs +++ b/azalea-client/src/plugins/packet/game/mod.rs @@ -8,7 +8,7 @@ use azalea_core::{ }; use azalea_entity::{ Dead, EntityBundle, EntityKindComponent, HasClientLoaded, LoadedBy, LocalEntity, LookDirection, - Physics, Position, RelativeEntityUpdate, + Physics, PlayerAbilities, Position, RelativeEntityUpdate, indexing::{EntityIdIndex, EntityUuidIndex}, metadata::{Health, apply_metadata}, }; @@ -33,7 +33,7 @@ use crate::{ inventory::{ ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent, }, - local_player::{Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList}, + local_player::{Hunger, InstanceHolder, LocalGameMode, TabList}, movement::{KnockbackEvent, KnockbackType}, packet::as_system, player::{GameProfileComponent, PlayerInfo}, |
