diff --git a/azalea-client/src/plugins/movement.rs b/azalea-client/src/plugins/movement.rs index e97d9ec1..e4778be1 100644 --- a/azalea-client/src/plugins/movement.rs +++ b/azalea-client/src/plugins/movement.rs @@ -1,6 +1,9 @@ use std::{backtrace::Backtrace, io}; -use azalea_core::{position::Vec3, tick::GameTick}; +use azalea_core::{ + position::{Vec2, Vec3}, + tick::GameTick, +}; use azalea_entity::{ Attributes, InLoadedChunk, Jumping, LastSentPosition, LookDirection, Physics, Position, metadata::Sprinting, @@ -137,8 +140,7 @@ pub struct PhysicsState { pub trying_to_sprint: bool, pub move_direction: WalkDirection, - pub forward_impulse: f32, - pub left_impulse: f32, + pub move_vector: Vec2, } #[allow(clippy::type_complexity)] @@ -308,12 +310,10 @@ pub fn send_sprinting_if_needed( } } -/// Update the impulse from self.move_direction. The multiplier is used for -/// sneaking. +/// Updates the [`PhysicsState::move_vector`] based on the +/// [`PhysicsState::move_direction`]. pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) { for mut physics_state in query.iter_mut() { - let multiplier: Option = None; - let mut forward_impulse: f32 = 0.; let mut left_impulse: f32 = 0.; let move_direction = physics_state.move_direction; @@ -337,13 +337,9 @@ pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) { } _ => {} }; - 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; - } + let move_vector = Vec2::new(left_impulse, forward_impulse).normalized(); + physics_state.move_vector = move_vector; } } @@ -357,8 +353,12 @@ pub fn local_player_ai_step( ) { 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: 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; // TODO: food data and abilities // let has_enough_food_to_sprint = self.food_data().food_level || @@ -385,6 +385,47 @@ pub fn local_player_ai_step( } } +// LocalPlayer.modifyInput +fn modify_input( + mut move_vector: Vec2, + is_using_item: bool, + is_passenger: bool, + moving_slowly: bool, + attributes: &Attributes, +) -> Vec2 { + if move_vector.length_squared() == 0. { + return move_vector; + } + + move_vector *= 0.98; + if is_using_item && !is_passenger { + move_vector *= 0.2; + } + + if moving_slowly { + let sneaking_speed = attributes.sneaking_speed.calculate() as f32; + move_vector *= sneaking_speed; + } + + modify_input_speed_for_square_movement(move_vector) +} +fn modify_input_speed_for_square_movement(move_vector: Vec2) -> Vec2 { + let length = move_vector.length(); + if length == 0. { + return move_vector; + } + let scaled_to_inverse_length = move_vector * (1. / length); + let dist = distance_to_unit_square(scaled_to_inverse_length); + let scale = (length * dist).min(1.); + scaled_to_inverse_length * scale +} +fn distance_to_unit_square(v: Vec2) -> f32 { + let x = v.x.abs(); + let y = v.y.abs(); + let ratio = if y > x { x / y } else { y / x }; + (1.0 + ratio * ratio).sqrt() +} + impl Client { /// Start walking in the given direction. To sprint, use /// [`Client::sprint`]. To stop walking, call walk with @@ -508,7 +549,7 @@ 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 + physics_state.move_vector.y > 0.8 // } } diff --git a/azalea-core/src/bitset.rs b/azalea-core/src/bitset.rs index 2a3e5b51..fbb12ee0 100644 --- a/azalea-core/src/bitset.rs +++ b/azalea-core/src/bitset.rs @@ -151,6 +151,10 @@ where } } + pub const fn new_with_data(data: [u8; bits_to_bytes(N)]) -> Self { + FixedBitSet { data } + } + #[inline] pub fn index(&self, index: usize) -> bool { (self.data[index / 8] & (1u8 << (index % 8))) != 0 diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index c0c25639..dd6a37e0 100644 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -818,6 +818,66 @@ impl fmt::Display for Vec3 { } } +/// A 2D vector. +#[derive(Clone, Copy, Debug, Default, PartialEq, AzBuf)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Vec2 { + pub x: f32, + pub y: f32, +} +impl Vec2 { + const ZERO: Vec2 = Vec2 { x: 0.0, y: 0.0 }; + + #[inline] + pub fn new(x: f32, y: f32) -> Self { + Vec2 { x, y } + } + #[inline] + pub fn scale(&self, amount: f32) -> Self { + Vec2 { + x: self.x * amount, + y: self.y * amount, + } + } + #[inline] + pub fn dot(&self, other: Vec2) -> f32 { + self.x * other.x + self.y * other.y + } + #[inline] + pub fn normalized(&self) -> Self { + let length = (self.x * self.x + self.y * self.y).sqrt(); + if length < 1e-4 { + return Vec2::ZERO; + } + Vec2 { + x: self.x / length, + y: self.y / length, + } + } + #[inline] + pub fn length_squared(&self) -> f32 { + self.x * self.x + self.y * self.y + } + #[inline] + pub fn length(&self) -> f32 { + self.length_squared().sqrt() + } +} +impl Mul for Vec2 { + type Output = Self; + + #[inline] + fn mul(self, rhs: f32) -> Self::Output { + self.scale(rhs) + } +} +impl MulAssign for Vec2 { + #[inline] + fn mul_assign(&mut self, rhs: f32) { + *self = self.scale(rhs); + } +} + const PACKED_X_LENGTH: u64 = 1 + 25; // minecraft does something a bit more complicated to get this 25 const PACKED_Z_LENGTH: u64 = PACKED_X_LENGTH; const PACKED_Y_LENGTH: u64 = 64 - PACKED_X_LENGTH - PACKED_Z_LENGTH; diff --git a/azalea-entity/src/attributes.rs b/azalea-entity/src/attributes.rs index 7af845f8..c2a22e9b 100644 --- a/azalea-entity/src/attributes.rs +++ b/azalea-entity/src/attributes.rs @@ -10,6 +10,7 @@ use thiserror::Error; #[derive(Clone, Debug, Component)] pub struct Attributes { pub speed: AttributeInstance, + pub sneaking_speed: AttributeInstance, pub attack_speed: AttributeInstance, pub water_movement_efficiency: AttributeInstance, @@ -92,7 +93,7 @@ pub enum AttributeModifierOperation { pub fn sprinting_modifier() -> AttributeModifier { AttributeModifier { id: ResourceLocation::new("sprinting"), - amount: 0.30000001192092896, + amount: 0.3f32 as f64, operation: AttributeModifierOperation::MultiplyTotal, } } diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index 340b6f25..18e9e1a1 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -50,10 +50,10 @@ pub fn move_relative( pub fn input_vector(direction: LookDirection, speed: f32, acceleration: Vec3) -> Vec3 { let distance = acceleration.length_squared(); - if distance < 1.0E-7 { + if distance < 1.0e-7 { return Vec3::ZERO; } - let acceleration = if distance > 1.0 { + let acceleration = if distance > 1. { acceleration.normalize() } else { acceleration @@ -492,6 +492,7 @@ pub fn default_attributes(_entity_kind: EntityKind) -> Attributes { // entities have different defaults Attributes { speed: AttributeInstance::new(0.1), + sneaking_speed: AttributeInstance::new(0.3), attack_speed: AttributeInstance::new(4.0), water_movement_efficiency: AttributeInstance::new(0.0), block_interaction_range: AttributeInstance::new(4.5), diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index 772b9b86..9481cef7 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -15,10 +15,10 @@ use azalea_core::{ tick::GameTick, }; use azalea_entity::{ - Attributes, InLoadedChunk, Jumping, LocalEntity, LookDirection, OnClimbable, Physics, Pose, - Position, metadata::Sprinting, move_relative, + Attributes, EntityKindComponent, InLoadedChunk, Jumping, LocalEntity, LookDirection, + OnClimbable, Physics, Pose, Position, metadata::Sprinting, move_relative, }; -use azalea_registry::Block; +use azalea_registry::{Block, EntityKind}; use azalea_world::{Instance, InstanceContainer, InstanceName}; use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; @@ -66,12 +66,17 @@ pub fn ai_step( &LookDirection, &Sprinting, &InstanceName, + &EntityKindComponent, ), (With, With), >, instance_container: Res, ) { - for (mut physics, jumping, position, look_direction, sprinting, instance_name) in &mut query { + for (mut physics, jumping, position, look_direction, sprinting, instance_name, entity_kind) in + &mut query + { + let is_player = **entity_kind == EntityKind::Player; + // vanilla does movement interpolation here, doesn't really matter much for a // bot though @@ -79,14 +84,29 @@ pub fn ai_step( physics.no_jump_delay -= 1; } - if physics.velocity.x.abs() < 0.003 { - physics.velocity.x = 0.; + if is_player { + if physics.velocity.horizontal_distance_squared() < 9.0e-6 { + physics.velocity.x = 0.; + physics.velocity.z = 0.; + } + } else { + if physics.velocity.x.abs() < 0.003 { + physics.velocity.x = 0.; + } + if physics.velocity.z.abs() < 0.003 { + physics.velocity.z = 0.; + } } + if physics.velocity.y.abs() < 0.003 { physics.velocity.y = 0.; } - if physics.velocity.z.abs() < 0.003 { - physics.velocity.z = 0.; + + if is_player { + // handled in local_player_ai_step + } else { + physics.x_acceleration *= 0.98; + physics.z_acceleration *= 0.98; } if jumping == Some(&Jumping(true)) { @@ -128,9 +148,6 @@ pub fn ai_step( physics.no_jump_delay = 0; } - physics.x_acceleration *= 0.98; - physics.z_acceleration *= 0.98; - // TODO: freezing, pushEntities, drowning damage (in their own systems, // after `travel`) } diff --git a/azalea-protocol/src/common/movements.rs b/azalea-protocol/src/common/movements.rs index e88fb87e..6475b46f 100644 --- a/azalea-protocol/src/common/movements.rs +++ b/azalea-protocol/src/common/movements.rs @@ -94,7 +94,8 @@ fn apply_change>(base: T, condition: bool, change: T) -> T { impl AzaleaRead for RelativeMovements { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { // yes minecraft seriously wastes that many bits, smh - let set = FixedBitSet::<32>::azalea_read(buf)?; + let set = u32::azalea_read(buf)?; + let set = FixedBitSet::<32>::new_with_data(set.swap_bytes().to_be_bytes()); Ok(RelativeMovements { x: set.index(0), y: set.index(1),