diff --git a/Cargo.lock b/Cargo.lock index a6081a953..60b0a0df8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -463,6 +463,7 @@ dependencies = [ "bevy_ecs", "bevy_time", "parking_lot", + "tracing", "uuid", ] diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs index 65dd581e8..4719ef426 100755 --- a/azalea-block/src/lib.rs +++ b/azalea-block/src/lib.rs @@ -8,7 +8,8 @@ mod range; use core::fmt::Debug; use std::{ any::Any, - io::{Cursor, Write}, + fmt, + io::{self, Cursor, Write}, }; use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; @@ -115,13 +116,13 @@ impl AzaleaRead for BlockState { } } impl AzaleaWrite for BlockState { - fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> { u32::azalea_write_var(&(self.id as u32), buf) } } -impl std::fmt::Debug for BlockState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl Debug for BlockState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "BlockState(id: {}, {:?})", @@ -134,50 +135,69 @@ impl std::fmt::Debug for BlockState { #[derive(Clone, Debug)] pub struct FluidState { pub fluid: azalea_registry::Fluid, - pub height: u8, + /// 0 = empty, 8 = full, 9 = max. + /// + /// 9 is meant to be used when there's another fluid block of the same type + /// above it, but it's usually unused by this struct. + pub amount: u8, +} +impl FluidState { + /// A floating point number in between 0 and 1 representing the height (as a + /// percentage of a full block) of the fluid. + pub fn height(&self) -> f32 { + self.amount as f32 / 9. + } } impl Default for FluidState { fn default() -> Self { Self { fluid: azalea_registry::Fluid::Empty, - height: 0, + amount: 0, } } } impl From for FluidState { fn from(state: BlockState) -> Self { + // note that 8 here might be treated as 9 in some cases if there's another fluid + // block of the same type above it + if state .property::() .unwrap_or_default() { Self { fluid: azalea_registry::Fluid::Water, - height: 15, + amount: 8, } } else { let block = Box::::from(state); if let Some(water) = block.downcast_ref::() { Self { fluid: azalea_registry::Fluid::Water, - height: water.level as u8, + amount: to_or_from_legacy_fluid_level(water.level as u8), } } else if let Some(lava) = block.downcast_ref::() { Self { fluid: azalea_registry::Fluid::Lava, - height: lava.level as u8, + amount: to_or_from_legacy_fluid_level(lava.level as u8), } } else { Self { fluid: azalea_registry::Fluid::Empty, - height: 0, + amount: 0, } } } } } +// see FlowingFluid.getLegacyLevel +fn to_or_from_legacy_fluid_level(level: u8) -> u8 { + 8_u8.saturating_sub(level) +} + impl From for BlockState { fn from(state: FluidState) -> Self { match state.fluid { @@ -185,14 +205,14 @@ impl From for BlockState { azalea_registry::Fluid::Water | azalea_registry::Fluid::FlowingWater => { BlockState::from(crate::blocks::Water { level: crate::properties::WaterLevel::from( - state.height as BlockStateIntegerRepr, + state.amount as BlockStateIntegerRepr, ), }) } azalea_registry::Fluid::Lava | azalea_registry::Fluid::FlowingLava => { BlockState::from(crate::blocks::Lava { level: crate::properties::LavaLevel::from( - state.height as BlockStateIntegerRepr, + state.amount as BlockStateIntegerRepr, ), }) } diff --git a/azalea-entity/src/plugin/mod.rs b/azalea-entity/src/plugin/mod.rs index 90d7f1c57..92918c69b 100644 --- a/azalea-entity/src/plugin/mod.rs +++ b/azalea-entity/src/plugin/mod.rs @@ -104,7 +104,7 @@ pub fn update_fluid_on_eyes( .read() .get_fluid_state(&eye_block_pos) .unwrap_or_default(); - let fluid_cutoff_y = eye_block_pos.y as f64 + (fluid_at_eye.height as f64 / 16f64); + let fluid_cutoff_y = eye_block_pos.y as f64 + (fluid_at_eye.amount as f64 / 16f64); if fluid_cutoff_y > adjusted_eye_y { **fluid_on_eyes = fluid_at_eye.fluid; } else { diff --git a/azalea-physics/Cargo.toml b/azalea-physics/Cargo.toml index 8b963d06a..60d80787c 100644 --- a/azalea-physics/Cargo.toml +++ b/azalea-physics/Cargo.toml @@ -19,4 +19,5 @@ azalea-registry = { path = "../azalea-registry", version = "0.11.0" } azalea-world = { path = "../azalea-world", version = "0.11.0" } bevy_app = { workspace = true } bevy_ecs = { workspace = true } +tracing = { workspace = true } parking_lot = { workspace = true } diff --git a/azalea-physics/src/clip.rs b/azalea-physics/src/clip.rs index fd6bbf0b6..4a374f582 100644 --- a/azalea-physics/src/clip.rs +++ b/azalea-physics/src/clip.rs @@ -20,12 +20,11 @@ pub struct ClipContext { // pub collision_context: EntityCollisionContext, } impl ClipContext { - // minecraft passes in the world and blockpos here... but it doesn't actually - // seem necessary? - /// Get the shape of given block, using the type of shape set in /// [`Self::block_shape_type`]. pub fn block_shape(&self, block_state: BlockState) -> &VoxelShape { + // minecraft passes in the world and blockpos to this function but it's not + // actually necessary. it is for fluid_shape though match self.block_shape_type { BlockShapeType::Collider => block_state.collision_shape(), BlockShapeType::Outline => block_state.outline_shape(), @@ -41,6 +40,19 @@ impl ClipContext { } } } + + pub fn fluid_shape( + &self, + fluid_state: FluidState, + world: &ChunkStorage, + pos: &BlockPos, + ) -> &VoxelShape { + if self.fluid_pick_type.can_pick(&fluid_state) { + crate::collision::fluid_shape(&fluid_state, world, pos) + } else { + &EMPTY_SHAPE + } + } } #[derive(Debug, Copy, Clone)] @@ -63,6 +75,17 @@ pub enum FluidPickType { Any, Water, } +impl FluidPickType { + pub fn can_pick(&self, fluid_state: &FluidState) -> bool { + match self { + Self::None => false, + Self::SourceOnly => fluid_state.amount == 8, + Self::Any => fluid_state.fluid != azalea_registry::Fluid::Empty, + Self::Water => fluid_state.fluid == azalea_registry::Fluid::Water, + } + } +} + #[derive(Debug, Clone)] pub struct EntityCollisionContext { pub descending: bool, @@ -81,15 +104,29 @@ pub fn clip(chunk_storage: &ChunkStorage, context: ClipContext) -> BlockHitResul let block_state = chunk_storage.get_block_state(block_pos).unwrap_or_default(); let fluid_state = FluidState::from(block_state); - // TODO: add fluid stuff to this (see getFluidState in vanilla source) let block_shape = ctx.block_shape(block_state); + let interaction_clip = clip_with_interaction_override( + &ctx.from, + &ctx.to, + block_pos, + block_shape, + &block_state, + ); + let fluid_shape = ctx.fluid_shape(fluid_state, chunk_storage, block_pos); + let fluid_clip = fluid_shape.clip(&ctx.from, &ctx.to, block_pos); - clip_with_interaction_override(&ctx.from, &ctx.to, block_pos, block_shape, &block_state) - // let block_distance = if let Some(block_hit_result) = - // block_hit_result { context.from.distance_squared_to(& - // block_hit_result.location) } else { - // f64::INFINITY - // }; + let distance_to_interaction = interaction_clip + .map(|hit| ctx.from.distance_squared_to(&hit.location)) + .unwrap_or(f64::MAX); + let distance_to_fluid = fluid_clip + .map(|hit| ctx.from.distance_squared_to(&hit.location)) + .unwrap_or(f64::MAX); + + if distance_to_interaction <= distance_to_fluid { + interaction_clip + } else { + fluid_clip + } }, |context| { let vec = context.from - context.to; @@ -107,9 +144,10 @@ fn clip_with_interaction_override( to: &Vec3, block_pos: &BlockPos, block_shape: &VoxelShape, - block_state: &BlockState, + _block_state: &BlockState, ) -> Option { let block_hit_result = block_shape.clip(from, to, block_pos); + if let Some(block_hit_result) = block_hit_result { // TODO: minecraft calls .getInteractionShape here // getInteractionShape is empty for almost every shape except cauldons, @@ -123,9 +161,10 @@ fn clip_with_interaction_override( return Some(block_hit_result.with_direction(interaction_hit_result.direction)); } } + Some(block_hit_result) } else { - block_hit_result + None } } diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs index 913cedacd..bff9d6f83 100644 --- a/azalea-physics/src/collision/mod.rs +++ b/azalea-physics/src/collision/mod.rs @@ -4,14 +4,21 @@ mod mergers; mod shape; mod world_collisions; -use std::ops::Add; - -use azalea_core::{aabb::AABB, direction::Axis, math::EPSILON, position::Vec3}; -use azalea_world::{Instance, MoveEntityError}; +use std::{ops::Add, sync::LazyLock}; + +use azalea_block::FluidState; +use azalea_core::{ + aabb::AABB, + direction::Axis, + math::EPSILON, + position::{BlockPos, Vec3}, +}; +use azalea_world::{ChunkStorage, Instance, MoveEntityError}; use bevy_ecs::world::Mut; pub use blocks::BlockWithShape; pub use discrete_voxel_shape::*; pub use shape::*; +use tracing::warn; use self::world_collisions::get_block_collisions; @@ -333,3 +340,48 @@ fn collide_with_shapes( z: z_movement, } } + +/// Get the [`VoxelShape`] for the given fluid state. +/// +/// The instance and position are required so it can check if the block above is +/// also the same fluid type. +pub fn fluid_shape( + fluid: &FluidState, + world: &ChunkStorage, + pos: &BlockPos, +) -> &'static VoxelShape { + if fluid.amount == 9 { + let fluid_state_above = world.get_fluid_state(&pos.up(1)).unwrap_or_default(); + if fluid_state_above.fluid == fluid.fluid { + return &BLOCK_SHAPE; + } + } + + // pre-calculate these in a LazyLock so this function can return a + // reference instead + + static FLUID_SHAPES: LazyLock<[VoxelShape; 10]> = LazyLock::new(|| { + [ + calculate_shape_for_fluid(0), + calculate_shape_for_fluid(1), + calculate_shape_for_fluid(2), + calculate_shape_for_fluid(3), + calculate_shape_for_fluid(4), + calculate_shape_for_fluid(5), + calculate_shape_for_fluid(6), + calculate_shape_for_fluid(7), + calculate_shape_for_fluid(8), + calculate_shape_for_fluid(9), + ] + }); + + if fluid.amount > 9 { + warn!("Tried to calculate shape for fluid with height > 9: {fluid:?} at {pos}"); + return &EMPTY_SHAPE; + } + + &FLUID_SHAPES[fluid.amount as usize] +} +fn calculate_shape_for_fluid(amount: u8) -> VoxelShape { + box_shape(0.0, 0.0, 0.0, 1.0, (f32::from(amount) / 9.0) as f64, 1.0) +} diff --git a/azalea-physics/src/collision/shape.rs b/azalea-physics/src/collision/shape.rs index e7ac9c2ed..fb733cae3 100755 --- a/azalea-physics/src/collision/shape.rs +++ b/azalea-physics/src/collision/shape.rs @@ -413,7 +413,7 @@ impl VoxelShape { VoxelShape::Cube(s) => s.find_index(axis, coord), _ => { let upper_limit = (self.shape().size(axis) + 1) as i32; - binary_search(0, upper_limit, &|t| coord < self.get(axis, t as usize)) - 1 + binary_search(0, upper_limit, |t| coord < self.get(axis, t as usize)) - 1 } } } diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 9f8f90528..013755903 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -340,8 +340,6 @@ pub fn calculate_path(opts: CalculatePathOpts) -> Option { call_successors_fn(&cached_world, &opts.mining_cache, opts.successors_fn, pos) }; - let path; - let start_time = Instant::now(); let astar::Path { @@ -375,7 +373,7 @@ pub fn calculate_path(opts: CalculatePathOpts) -> Option { debug!(" {}", movement.target.apply(origin)); } - path = movements.into_iter().collect::>(); + let path = movements.into_iter().collect::>(); let goto_id_now = opts.goto_id_atomic.load(atomic::Ordering::SeqCst); if goto_id != goto_id_now { @@ -520,6 +518,7 @@ pub fn path_found_listener( } } +#[allow(clippy::type_complexity)] pub fn timeout_movement( mut query: Query<( Entity,