mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
implement fluid_shape
This commit is contained in:
parent
04036b6e4a
commit
5693191b57
8 changed files with 144 additions and 32 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -463,6 +463,7 @@ dependencies = [
|
|||
"bevy_ecs",
|
||||
"bevy_time",
|
||||
"parking_lot",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
|
|
@ -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<BlockState> 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::<crate::properties::Waterlogged>()
|
||||
.unwrap_or_default()
|
||||
{
|
||||
Self {
|
||||
fluid: azalea_registry::Fluid::Water,
|
||||
height: 15,
|
||||
amount: 8,
|
||||
}
|
||||
} else {
|
||||
let block = Box::<dyn Block>::from(state);
|
||||
if let Some(water) = block.downcast_ref::<crate::blocks::Water>() {
|
||||
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::<crate::blocks::Lava>() {
|
||||
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<FluidState> for BlockState {
|
||||
fn from(state: FluidState) -> Self {
|
||||
match state.fluid {
|
||||
|
@ -185,14 +205,14 @@ impl From<FluidState> 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,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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<BlockHitResult> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,14 +4,21 @@ mod mergers;
|
|||
mod shape;
|
||||
mod world_collisions;
|
||||
|
||||
use std::ops::Add;
|
||||
use std::{ops::Add, sync::LazyLock};
|
||||
|
||||
use azalea_core::{aabb::AABB, direction::Axis, math::EPSILON, position::Vec3};
|
||||
use azalea_world::{Instance, MoveEntityError};
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -340,8 +340,6 @@ pub fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
|
|||
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<PathFoundEvent> {
|
|||
debug!(" {}", movement.target.apply(origin));
|
||||
}
|
||||
|
||||
path = movements.into_iter().collect::<VecDeque<_>>();
|
||||
let path = movements.into_iter().collect::<VecDeque<_>>();
|
||||
|
||||
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,
|
||||
|
|
Loading…
Add table
Reference in a new issue