From 63b4406af2a7b025856a86521637c303effd6193 Mon Sep 17 00:00:00 2001 From: mat Date: Thu, 9 Jan 2025 11:08:09 +0000 Subject: [PATCH] different travel function in water --- azalea-client/src/movement.rs | 4 +- azalea-entity/src/attributes.rs | 1 + azalea-entity/src/lib.rs | 29 ++- azalea-physics/src/collision/mod.rs | 5 +- azalea-physics/src/fluids.rs | 15 -- azalea-physics/src/lib.rs | 308 ++++++++++++++++++++++------ azalea-world/src/world.rs | 8 +- azalea/src/pathfinder/simulation.rs | 1 + 8 files changed, 283 insertions(+), 88 deletions(-) diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs index 6e89b251b..ec5b751ed 100644 --- a/azalea-client/src/movement.rs +++ b/azalea-client/src/movement.rs @@ -325,8 +325,8 @@ pub fn local_player_ai_step( ) { for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() { // server ai step - physics.xxa = physics_state.left_impulse; - physics.zza = physics_state.forward_impulse; + 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 || diff --git a/azalea-entity/src/attributes.rs b/azalea-entity/src/attributes.rs index 797ea43c4..9af3bcaab 100644 --- a/azalea-entity/src/attributes.rs +++ b/azalea-entity/src/attributes.rs @@ -11,6 +11,7 @@ use thiserror::Error; pub struct Attributes { pub speed: AttributeInstance, pub attack_speed: AttributeInstance, + pub water_movement_efficiency: AttributeInstance, } #[derive(Clone, Debug)] diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index 1eff91a87..adef24ca7 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -257,12 +257,14 @@ pub struct Physics { pub velocity: Vec3, pub vec_delta_codec: VecDeltaCodec, - /// X acceleration. - pub xxa: f32, - /// Y acceleration. - pub yya: f32, - /// Z acceleration. - pub zza: f32, + /// The acceleration here is the force that will be attempted to be added to + /// the entity's velocity next tick. + /// + /// You should typically not set this yourself, since it's controlled by how + /// the entity is trying to move. + pub x_acceleration: f32, + pub y_acceleration: f32, + pub z_acceleration: f32, on_ground: bool, last_on_ground: bool, @@ -295,9 +297,9 @@ impl Physics { velocity: Vec3::default(), vec_delta_codec: VecDeltaCodec::new(pos), - xxa: 0., - yya: 0., - zza: 0., + x_acceleration: 0., + y_acceleration: 0., + z_acceleration: 0., on_ground: false, last_on_ground: false, @@ -345,6 +347,14 @@ impl Physics { pub fn clear_fire(&mut self) { self.remaining_fire_ticks = 0; } + + pub fn is_in_water(&self) -> bool { + self.was_touching_water + } + pub fn is_in_lava(&self) -> bool { + // TODO: also check `!this.firstTick &&` + self.lava_fluid_height > 0. + } } /// Marker component for entities that are dead. @@ -444,6 +454,7 @@ impl EntityBundle { // entities have different defaults speed: AttributeInstance::new(0.1), attack_speed: AttributeInstance::new(4.0), + water_movement_efficiency: AttributeInstance::new(0.0), }, jumping: Jumping(false), diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs index 6a39836b0..e4a5d42d7 100644 --- a/azalea-physics/src/collision/mod.rs +++ b/azalea-physics/src/collision/mod.rs @@ -22,6 +22,7 @@ use tracing::warn; use self::world_collisions::get_block_collisions; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MoverType { Own, Player, @@ -143,8 +144,10 @@ fn collide(movement: &Vec3, world: &Instance, physics: &azalea_entity::Physics) } /// Move an entity by a given delta, checking for collisions. +/// +/// In Mojmap, this is `Entity.move`. pub fn move_colliding( - _mover_type: &MoverType, + _mover_type: MoverType, movement: &Vec3, world: &Instance, position: &mut Mut, diff --git a/azalea-physics/src/fluids.rs b/azalea-physics/src/fluids.rs index 9c0d98a53..d03fcc3b5 100644 --- a/azalea-physics/src/fluids.rs +++ b/azalea-physics/src/fluids.rs @@ -25,15 +25,11 @@ pub fn update_in_water_state_and_do_fluid_pushing( .expect("All entities should be in a valid world"); let world = world_lock.read(); - println!("update_in_water_state_and_do_fluid_pushing"); - physics.water_fluid_height = 0.; physics.lava_fluid_height = 0.; update_in_water_state_and_do_water_current_pushing(&mut physics, &world, &position); - println!("physics.water_fluid_height: {}", physics.water_fluid_height); - // let lava_push_factor = world // .registries // .dimension_type() @@ -117,10 +113,6 @@ fn update_fluid_height_and_do_fluid_pushing( } let mut additional_player_delta_for_fluid = get_fluid_flow(&fluid_at_cur_pos, world, cur_pos); - println!( - "additional_player_delta_for_fluid: {}", - additional_player_delta_for_fluid - ); if min_height_touching < 0.4 { additional_player_delta_for_fluid *= min_height_touching; }; @@ -131,9 +123,6 @@ fn update_fluid_height_and_do_fluid_pushing( } } - println!("num_fluids_being_touched: {}", num_fluids_being_touched); - println!("additional_player_delta: {}", additional_player_delta); - if additional_player_delta.length() > 0. { additional_player_delta /= num_fluids_being_touched as f64; @@ -180,10 +169,6 @@ pub fn get_fluid_flow(fluid: &FluidState, world: &Instance, pos: BlockPos) -> Ve .get_fluid_state(&adjacent_block_pos) .unwrap_or_default(); if fluid.affects_flow(&adjacent_fluid_state) { - println!( - "affects flow {adjacent_block_pos} {:?}", - adjacent_fluid_state - ); let mut adjacent_fluid_height = adjacent_fluid_state.height(); let mut adjacent_height_difference: f32 = 0.; diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index 11583d254..e5379c0f2 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -7,6 +7,7 @@ pub mod fluids; use azalea_block::{Block, BlockState}; use azalea_core::{ + aabb::AABB, math, position::{BlockPos, Vec3}, tick::GameTick, @@ -84,74 +85,263 @@ fn travel( continue; }; let world = world_lock.read(); - // if !self.is_effective_ai() && !self.is_controlled_by_local_instance() { - // // this.calculateEntityAnimation(this, this instanceof FlyingAnimal); - // return; - // } - let gravity: f64 = 0.08; + let sprinting = *sprinting.unwrap_or(&Sprinting(false)); - // TODO: slow falling effect - // let is_falling = self.delta.y <= 0.; + // TODO: elytras - let block_position = BlockPos::from(**position); - // LivingEntity.travel - let fluid_state = world.chunks.get_fluid_state(&block_position); + if physics.is_in_water() || physics.is_in_lava() { + // minecraft also checks for `this.isAffectedByFluids() && + // !this.canStandOnFluid(fluidAtBlock)` here but it doesn't matter + // for players + travel_in_fluid( + &mut physics, + &direction, + position, + attributes, + sprinting, + on_climbable, + pose, + jumping, + &world, + ); + } else { + travel_in_air( + &mut physics, + &direction, + position, + &attributes, + sprinting, + &on_climbable, + pose, + &jumping, + &world, + ); + } + } +} - // TODO: fluids +/// The usual movement when we're not in water or using an elytra. +fn travel_in_air( + physics: &mut Physics, + direction: &LookDirection, + position: Mut, + attributes: &Attributes, + sprinting: Sprinting, + on_climbable: &OnClimbable, + pose: Option<&Pose>, + jumping: &Jumping, + world: &Instance, +) { + let gravity = get_effective_gravity(); - // TODO: elytra + // LivingEntity.travel starts here - let block_pos_below = get_block_pos_below_that_affects_movement(&position); + // TODO: fluids - let block_state_below = world - .chunks - .get_block_state(&block_pos_below) - .unwrap_or(BlockState::AIR); - let block_below: Box = block_state_below.into(); - let block_friction = block_below.behavior().friction; + // TODO: elytra - let inertia = if physics.on_ground() { - block_friction * 0.91 - } else { - 0.91 + let block_pos_below = get_block_pos_below_that_affects_movement(&position); + + let block_state_below = world + .chunks + .get_block_state(&block_pos_below) + .unwrap_or(BlockState::AIR); + let block_below: Box = block_state_below.into(); + let block_friction = block_below.behavior().friction; + + let inertia = if physics.on_ground() { + block_friction * 0.91 + } else { + 0.91 + }; + + // this applies the current delta + let mut movement = handle_relative_friction_and_calculate_movement( + HandleRelativeFrictionAndCalculateMovementOpts { + block_friction, + world: &world, + physics, + direction: &direction, + position, + attributes, + is_sprinting: *sprinting, + on_climbable, + pose, + jumping, + }, + ); + + movement.y -= gravity; + + // if (this.shouldDiscardFriction()) { + // this.setDeltaMovement(movement.x, yMovement, movement.z); + // } else { + // this.setDeltaMovement(movement.x * (double)inertia, yMovement * + // 0.9800000190734863D, movement.z * (double)inertia); } + + // if should_discard_friction(self) { + if false { + physics.velocity = movement; + } else { + physics.velocity = Vec3 { + x: movement.x * inertia as f64, + y: movement.y * 0.9800000190734863f64, + z: movement.z * inertia as f64, }; + } +} - // this applies the current delta - let mut movement = handle_relative_friction_and_calculate_movement( - HandleRelativeFrictionAndCalculateMovementOpts { - block_friction, - world: &world, - physics: &mut physics, - direction: &direction, - position, - attributes, - is_sprinting: sprinting.map(|s| **s).unwrap_or(false), - on_climbable, - pose, - jumping, - }, - ); +fn travel_in_fluid( + physics: &mut Physics, + direction: &LookDirection, + mut position: Mut, + attributes: &Attributes, + sprinting: Sprinting, + on_climbable: &OnClimbable, + pose: Option<&Pose>, + jumping: &Jumping, + world: &Instance, +) { + let moving_down = physics.velocity.y <= 0.; + let y = position.y; + let gravity = get_effective_gravity(); + + let acceleration = Vec3::new( + physics.x_acceleration as f64, + physics.y_acceleration as f64, + physics.z_acceleration as f64, + ); - movement.y -= gravity; + if physics.was_touching_water { + let mut water_movement_speed = if *sprinting { 0.9 } else { 0.8 }; + let mut speed = 0.02; + let mut water_efficiency_modifier = attributes.water_movement_efficiency.calculate() as f32; + if !physics.on_ground() { + water_efficiency_modifier *= 0.5; + } - // if (this.shouldDiscardFriction()) { - // this.setDeltaMovement(movement.x, yMovement, movement.z); - // } else { - // this.setDeltaMovement(movement.x * (double)inertia, yMovement * - // 0.9800000190734863D, movement.z * (double)inertia); } + if water_efficiency_modifier > 0. { + water_movement_speed += (0.54600006 - water_movement_speed) * water_efficiency_modifier; + speed += (attributes.speed.calculate() as f32 - speed) * water_efficiency_modifier; + } + + // if (this.hasEffect(MobEffects.DOLPHINS_GRACE)) { + // waterMovementSpeed = 0.96F; + // } - // if should_discard_friction(self) { - if false { - physics.velocity = movement; + move_relative(physics, direction, speed, &acceleration); + move_colliding( + MoverType::Own, + &physics.velocity.clone(), + world, + &mut position, + physics, + ) + .expect("Entity should exist"); + + let mut new_velocity = physics.velocity; + if physics.horizontal_collision && **on_climbable { + // underwater ladders + new_velocity.y = 0.2; + } + new_velocity.x *= water_movement_speed as f64; + new_velocity.y *= 0.8; + new_velocity.z *= water_movement_speed as f64; + physics.velocity = + get_fluid_falling_adjusted_movement(gravity, moving_down, new_velocity, sprinting); + } else { + move_relative(physics, direction, 0.02, &acceleration); + move_colliding( + MoverType::Own, + &physics.velocity.clone(), + world, + &mut position, + physics, + ) + .expect("Entity should exist"); + + if physics.lava_fluid_height <= fluid_jump_threshold() { + physics.velocity.x *= 0.5; + physics.velocity.y *= 0.8; + physics.velocity.z *= 0.5; + let new_velocity = get_fluid_falling_adjusted_movement( + gravity, + moving_down, + physics.velocity, + sprinting, + ); + physics.velocity = new_velocity; } else { - physics.velocity = Vec3 { - x: movement.x * inertia as f64, - y: movement.y * 0.9800000190734863f64, - z: movement.z * inertia as f64, - }; + physics.velocity *= 0.5; + } + + if gravity != 0.0 { + physics.velocity.y -= gravity / 4.0; } } + + let velocity = physics.velocity; + if physics.horizontal_collision + && is_free( + physics.bounding_box, + world, + velocity.x, + velocity.y + 0.6 - position.y + y, + velocity.z, + ) + { + physics.velocity.y = 0.3; + } +} + +fn get_fluid_falling_adjusted_movement( + gravity: f64, + moving_down: bool, + new_velocity: Vec3, + sprinting: Sprinting, +) -> Vec3 { + if gravity != 0. && !*sprinting { + let new_y_velocity; + if moving_down + && (new_velocity.y - 0.005).abs() >= 0.003 + && f64::abs(new_velocity.y - gravity / 16.0) < 0.003 + { + new_y_velocity = -0.003; + } else { + new_y_velocity = new_velocity.y - gravity / 16.0; + } + + Vec3 { + x: new_velocity.x, + y: new_y_velocity, + z: new_velocity.z, + } + } else { + new_velocity + } +} + +fn is_free(bounding_box: AABB, world: &Instance, x: f64, y: f64, z: f64) -> bool { + // let bounding_box = bounding_box.move_relative(Vec3::new(x, y, z)); + + let _ = (bounding_box, world, x, y, z); + + // TODO: implement this, see Entity.isFree + + true +} + +fn get_effective_gravity() -> f64 { + // TODO: slow falling effect + 0.08 +} + +fn fluid_jump_threshold() -> f64 { + // this is 0.0 for entities with an eye height lower than 0.4, but that's not + // implemented since it's usually not relevant for players (unless the player + // was shrunk) + 0.4 } /// applies air resistance, calls self.travel(), and some other random @@ -202,8 +392,8 @@ pub fn ai_step( } } - physics.xxa *= 0.98; - physics.zza *= 0.98; + physics.x_acceleration *= 0.98; + physics.z_acceleration *= 0.98; // TODO: freezing, pushEntities, drowning damage (in their own systems, // after `travel`) @@ -285,22 +475,22 @@ fn handle_relative_friction_and_calculate_movement( direction, get_friction_influenced_speed(physics, attributes, block_friction, is_sprinting), &Vec3 { - x: physics.xxa as f64, - y: physics.yya as f64, - z: physics.zza as f64, + x: physics.x_acceleration as f64, + y: physics.y_acceleration as f64, + z: physics.z_acceleration as f64, }, ); physics.velocity = handle_on_climbable(physics.velocity, on_climbable, &position, world, pose); move_colliding( - &MoverType::Own, + MoverType::Own, &physics.velocity.clone(), world, &mut position, physics, ) - .expect("Entity should exist."); + .expect("Entity should exist"); // let delta_movement = entity.delta; // ladders // if ((entity.horizontalCollision || entity.jumping) && (entity.onClimbable() diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs index dfce99061..298bd5985 100644 --- a/azalea-world/src/world.rs +++ b/azalea-world/src/world.rs @@ -89,8 +89,12 @@ pub struct Instance { /// An index of all the entities we know are in the chunks of the world pub entities_by_chunk: HashMap>, - /// An index of Minecraft entity IDs to Azalea ECS entities. You should - /// avoid using this and instead use `azalea_entity::EntityIdIndex` + /// An index of Minecraft entity IDs to Azalea ECS entities. + /// + /// You should avoid using this (particularly if you're using swarms) and + /// instead use `azalea_entity::EntityIdIndex`, since some servers may + /// give different entity IDs for the same entities to different + /// players. pub entity_by_id: IntMap, pub registries: RegistryHolder, diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs index 630dd5917..1f29ad240 100644 --- a/azalea/src/pathfinder/simulation.rs +++ b/azalea/src/pathfinder/simulation.rs @@ -38,6 +38,7 @@ impl SimulatedPlayerBundle { attributes: Attributes { speed: AttributeInstance::new(0.1), attack_speed: AttributeInstance::new(4.0), + water_movement_efficiency: AttributeInstance::new(0.0), }, inventory: Inventory::default(), }