1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

start fixing code related to fluid physics

This commit is contained in:
mat 2025-03-16 03:06:40 +00:00
parent aeff08f5fa
commit 90ecbf160a
14 changed files with 549 additions and 162 deletions

View file

@ -24,7 +24,7 @@ impl Plugin for BrandPlugin {
}
}
fn handle_end_login_state(
pub fn handle_end_login_state(
mut removed: RemovedComponents<InLoginState>,
query: Query<&ClientInformation>,
mut send_packet_events: EventWriter<SendConfigPacketEvent>,

View file

@ -26,26 +26,26 @@ pub struct ClipPointOpts<'a> {
}
impl AABB {
pub fn contract(&self, x: f64, y: f64, z: f64) -> AABB {
pub fn contract(&self, amount: Vec3) -> AABB {
let mut min = self.min;
let mut max = self.max;
if x < 0.0 {
min.x -= x;
} else if x > 0.0 {
max.x -= x;
if amount.x < 0.0 {
min.x -= amount.x;
} else if amount.x > 0.0 {
max.x -= amount.x;
}
if y < 0.0 {
min.y -= y;
} else if y > 0.0 {
max.y -= y;
if amount.y < 0.0 {
min.y -= amount.y;
} else if amount.y > 0.0 {
max.y -= amount.y;
}
if z < 0.0 {
min.z -= z;
} else if z > 0.0 {
max.z -= z;
if amount.z < 0.0 {
min.z -= amount.z;
} else if amount.z > 0.0 {
max.z -= amount.z;
}
AABB { min, max }
@ -84,20 +84,23 @@ impl AABB {
}
}
pub fn inflate(&self, x: f64, y: f64, z: f64) -> AABB {
let min_x = self.min.x - x;
let min_y = self.min.y - y;
let min_z = self.min.z - z;
pub fn inflate(&self, amount: Vec3) -> AABB {
let min_x = self.min.x - amount.x;
let min_y = self.min.y - amount.y;
let min_z = self.min.z - amount.z;
let max_x = self.max.x + x;
let max_y = self.max.y + y;
let max_z = self.max.z + z;
let max_x = self.max.x + amount.x;
let max_y = self.max.y + amount.y;
let max_z = self.max.z + amount.z;
AABB {
min: Vec3::new(min_x, min_y, min_z),
max: Vec3::new(max_x, max_y, max_z),
}
}
pub fn inflate_all(&self, amount: f64) -> AABB {
self.inflate(Vec3::new(amount, amount, amount))
}
pub fn intersect(&self, other: &AABB) -> AABB {
let min_x = self.min.x.max(other.min.x);
@ -144,17 +147,17 @@ impl AABB {
&& self.min.z < other.max.z
&& self.max.z > other.min.z
}
pub fn intersects_vec3(&self, other: &Vec3, other2: &Vec3) -> bool {
pub fn intersects_vec3(&self, corner1: &Vec3, corner2: &Vec3) -> bool {
self.intersects_aabb(&AABB {
min: Vec3::new(
other.x.min(other2.x),
other.y.min(other2.y),
other.z.min(other2.z),
corner1.x.min(corner2.x),
corner1.y.min(corner2.y),
corner1.z.min(corner2.z),
),
max: Vec3::new(
other.x.max(other2.x),
other.y.max(other2.y),
other.z.max(other2.z),
corner1.x.max(corner2.x),
corner1.y.max(corner2.y),
corner1.z.max(corner2.z),
),
})
}
@ -183,12 +186,11 @@ impl AABB {
)
}
pub fn deflate(&mut self, x: f64, y: f64, z: f64) -> AABB {
self.inflate(-x, -y, -z)
pub fn deflate(&self, amount: Vec3) -> AABB {
self.inflate(Vec3::new(-amount.x, -amount.y, -amount.z))
}
pub fn deflate_all(&mut self, amount: f64) -> AABB {
self.deflate(amount, amount, amount)
pub fn deflate_all(&self, amount: f64) -> AABB {
self.deflate(Vec3::new(amount, amount, amount))
}
pub fn clip(&self, min: &Vec3, max: &Vec3) -> Option<Vec3> {
@ -434,11 +436,11 @@ impl AABB {
let new_center = center + vector;
for aabb in boxes {
let inflated = aabb.inflate(
let inflated = aabb.inflate(Vec3::new(
self.get_size(Axis::X) * 0.5,
self.get_size(Axis::Y) * 0.5,
self.get_size(Axis::Z) * 0.5,
);
));
if inflated.contains(&new_center) || inflated.contains(&center) {
return true;
}

View file

@ -52,7 +52,7 @@ impl Vec3 {
pub fn normalize(&self) -> Vec3 {
let length = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
if length < 1e-4 {
return Vec3::default();
return Vec3::ZERO;
}
Vec3 {
x: self.x / length,

View file

@ -309,6 +309,21 @@ impl Vec3 {
let z = self.z * (x_delta as f64) - self.x * (y_delta as f64);
Vec3 { x, y, z }
}
pub fn to_block_pos_floor(&self) -> BlockPos {
BlockPos {
x: self.x.floor() as i32,
y: self.y.floor() as i32,
z: self.z.floor() as i32,
}
}
pub fn to_block_pos_ceil(&self) -> BlockPos {
BlockPos {
x: self.x.ceil() as i32,
y: self.y.ceil() as i32,
z: self.z.ceil() as i32,
}
}
}
/// The coordinates of a block in the world. For entities (if the coordinate
@ -600,6 +615,16 @@ impl From<ChunkSectionPos> for ChunkPos {
ChunkPos { x: pos.x, z: pos.z }
}
}
impl From<&Vec3> for ChunkSectionPos {
fn from(pos: &Vec3) -> Self {
ChunkSectionPos::from(&BlockPos::from(pos))
}
}
impl From<Vec3> for ChunkSectionPos {
fn from(pos: Vec3) -> Self {
ChunkSectionPos::from(&pos)
}
}
impl From<&BlockPos> for ChunkBlockPos {
#[inline]

View file

@ -209,8 +209,8 @@ impl From<&LastSentPosition> for BlockPos {
///
/// If this is true, the entity will try to jump every tick. It's equivalent to
/// the space key being held in vanilla.
#[derive(Debug, Component, Copy, Clone, Deref, DerefMut, Default)]
pub struct Jumping(bool);
#[derive(Debug, Component, Copy, Clone, Deref, DerefMut, Default, PartialEq, Eq)]
pub struct Jumping(pub bool);
/// A component that contains the direction an entity is looking.
#[derive(Debug, Component, Copy, Clone, Default, PartialEq, AzBuf)]

View file

@ -11,9 +11,7 @@ use azalea_core::{
math::{self, EPSILON, lerp},
position::{BlockPos, Vec3},
};
use azalea_inventory::ItemStack;
use azalea_world::ChunkStorage;
use bevy_ecs::entity::Entity;
use crate::collision::{BlockWithShape, EMPTY_SHAPE, VoxelShape};
@ -92,15 +90,6 @@ impl FluidPickType {
}
}
#[derive(Debug, Clone)]
pub struct EntityCollisionContext {
pub descending: bool,
pub entity_bottom: f64,
pub held_item: ItemStack,
// pub can_stand_on_fluid: Box<dyn Fn(&FluidState) -> bool>,
pub entity: Entity,
}
pub fn clip(chunk_storage: &ChunkStorage, context: ClipContext) -> BlockHitResult {
traverse_blocks(
context.from,

View file

@ -0,0 +1,118 @@
// default List<VoxelShape> getEntityCollisions(@Nullable Entity entity, AABB
// aabb) { if (aabb.getSize() < 1.0E-7) {
// return List.of();
// } else {
// Predicate var3 = entity == null ? EntitySelector.CAN_BE_COLLIDED_WITH
// : EntitySelector.NO_SPECTATORS.and(entity::canCollideWith);
// List var4 = this.getEntities(entity, aabb.inflate(1.0E-7), var3);
// if (var4.isEmpty()) {
// return List.of();
// } else {
// Builder var5 = ImmutableList.builderWithExpectedSize(var4.size());
// for (Entity var7 : var4) {
// var5.add(Shapes.create(var7.getBoundingBox()));
// }
// return var5.build();
// }
// }
// }
use azalea_core::aabb::AABB;
use azalea_entity::{
LocalEntity, Physics,
metadata::{AbstractBoat, Shulker},
};
use azalea_world::Instance;
use bevy_ecs::{
entity::Entity,
query::{Or, With, Without},
system::Query,
};
use tracing::error;
use super::VoxelShape;
/// This query matches on entities that we can collide with. That is, boats and
/// shulkers.
///
/// If you want to use this in a more complex query, use
/// [`CollidableEntityFilter`] as a filter instead.
pub type CollidableEntityQuery<'world, 'state> = Query<'world, 'state, (), CollidableEntityFilter>;
/// This filter matches on entities that we can collide with (boats and
/// shulkers).
///
/// Use [`CollidableEntityQuery`] if you want an empty query that matches with
/// this.
pub type CollidableEntityFilter = Or<(With<AbstractBoat>, With<Shulker>)>;
pub type PhysicsQuery<'world, 'state, 'a> =
Query<'world, 'state, &'a Physics, Without<LocalEntity>>;
pub fn get_entity_collisions(
world: &Instance,
aabb: &AABB,
source_entity: Option<Entity>,
physics_query: &PhysicsQuery,
collidable_entity_query: &CollidableEntityQuery,
) -> Vec<VoxelShape> {
if aabb.size() < 1.0E-7 {
return vec![];
}
let collision_predicate = |entity| collidable_entity_query.get(entity).is_ok();
let collidable_entities = get_entities(
world,
source_entity,
&aabb.inflate_all(1.0E-7),
&collision_predicate,
physics_query,
);
collidable_entities
.into_iter()
.map(|(_entity, aabb)| VoxelShape::from(aabb))
.collect()
}
/// Return all entities that are colliding with the given bounding box and match
/// the given predicate.
///
/// `source_entity` is the entity that the bounding box belongs to, and won't be
/// one of the returned entities.
pub fn get_entities(
world: &Instance,
source_entity: Option<Entity>,
aabb: &AABB,
predicate: &dyn Fn(Entity) -> bool,
physics_query: &PhysicsQuery,
) -> Vec<(Entity, AABB)> {
let mut matches = Vec::new();
super::world_collisions::for_entities_in_chunks_colliding_with(
world,
aabb,
|_chunk_pos, entities_in_chunk| {
// now check if the entity itself collides
for &candidate in entities_in_chunk {
if Some(candidate) != source_entity && predicate(candidate) {
let Ok(physics) = physics_query.get(candidate) else {
error!(
"Entity {candidate} (found from for_entities_in_chunks_colliding_with) is missing required components."
);
continue;
};
let candidate_aabb = physics.bounding_box;
if aabb.intersects_aabb(&candidate_aabb) {
matches.push((candidate, physics.bounding_box));
}
}
}
},
);
matches
}

View file

@ -1,8 +1,9 @@
mod blocks;
mod discrete_voxel_shape;
pub mod entity_collisions;
mod mergers;
mod shape;
mod world_collisions;
pub mod world_collisions;
use std::{ops::Add, sync::LazyLock};
@ -279,7 +280,7 @@ fn collide_bounding_box(
// TODO: world border
let block_collisions =
get_block_collisions(world, entity_bounding_box.expand_towards(movement));
get_block_collisions(world, &entity_bounding_box.expand_towards(movement));
collision_boxes.extend(block_collisions);
collide_with_shapes(movement, *entity_bounding_box, &collision_boxes)
}
@ -392,6 +393,11 @@ fn calculate_shape_for_fluid(amount: u8) -> VoxelShape {
///
/// This is marked as deprecated in Minecraft.
pub fn legacy_blocks_motion(block: BlockState) -> bool {
if block == BlockState::AIR {
// fast path
return false;
}
let registry_block = azalea_registry::Block::from(block);
legacy_calculate_solid(block)
&& registry_block != azalea_registry::Block::Cobweb

View file

@ -194,7 +194,7 @@ impl Shapes {
}
/// Check if the op is true anywhere when joining the two shapes
/// vanilla calls this joinIsNotEmpty
/// vanilla calls this joinIsNotEmpty (join_is_not_empty).
pub fn matches_anywhere(
a: &VoxelShape,
b: &VoxelShape,
@ -574,13 +574,18 @@ impl VoxelShape {
}
}
impl From<AABB> for VoxelShape {
fn from(aabb: AABB) -> Self {
impl From<&AABB> for VoxelShape {
fn from(aabb: &AABB) -> Self {
box_shape(
aabb.min.x, aabb.min.y, aabb.min.z, aabb.max.x, aabb.max.y, aabb.max.z,
)
}
}
impl From<AABB> for VoxelShape {
fn from(aabb: AABB) -> Self {
VoxelShape::from(&aabb)
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct ArrayVoxelShape {

View file

@ -1,19 +1,21 @@
use std::sync::Arc;
use std::{collections::HashSet, sync::Arc};
use azalea_block::BlockState;
use azalea_block::{BlockState, fluid_state::FluidState};
use azalea_core::{
cursor3d::{Cursor3d, CursorIterationType},
cursor3d::{Cursor3d, CursorIteration, CursorIterationType},
math::EPSILON,
position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos},
position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos, Vec3},
};
use azalea_inventory::ItemStack;
use azalea_world::{Chunk, Instance};
use bevy_ecs::entity::Entity;
use parking_lot::RwLock;
use super::{BLOCK_SHAPE, Shapes};
use crate::collision::{AABB, BlockWithShape, VoxelShape};
pub fn get_block_collisions(world: &Instance, aabb: AABB) -> Vec<VoxelShape> {
let mut state = BlockCollisionsState::new(world, aabb);
pub fn get_block_collisions(world: &Instance, aabb: &AABB) -> Vec<VoxelShape> {
let mut state = BlockCollisionsState::new(world, aabb, EntityCollisionContext::of(None));
let mut block_collisions = Vec::new();
let initial_chunk_pos = ChunkPos::from(state.cursor.origin());
@ -21,52 +23,36 @@ pub fn get_block_collisions(world: &Instance, aabb: AABB) -> Vec<VoxelShape> {
let initial_chunk = initial_chunk.as_deref().map(RwLock::read);
while let Some(item) = state.cursor.next() {
if item.iteration_type == CursorIterationType::Corner {
continue;
}
state.compute_next(
item,
&mut block_collisions,
initial_chunk_pos,
initial_chunk.as_deref(),
);
}
let item_chunk_pos = ChunkPos::from(item.pos);
let block_state: BlockState = if item_chunk_pos == initial_chunk_pos {
match &initial_chunk {
Some(initial_chunk) => initial_chunk
.get(&ChunkBlockPos::from(item.pos), state.world.chunks.min_y)
.unwrap_or(BlockState::AIR),
_ => BlockState::AIR,
}
} else {
state.get_block_state(item.pos)
};
block_collisions
}
if block_state.is_air() {
// fast path since we can't collide with air
continue;
}
pub fn get_block_and_liquid_collisions(world: &Instance, aabb: &AABB) -> Vec<VoxelShape> {
let mut state = BlockCollisionsState::new(
world,
aabb,
EntityCollisionContext::of(None).with_include_liquids(true),
);
let mut block_collisions = Vec::new();
// TODO: continue if self.only_suffocating_blocks and the block is not
// suffocating
let initial_chunk_pos = ChunkPos::from(state.cursor.origin());
let initial_chunk = world.chunks.get(&initial_chunk_pos);
let initial_chunk = initial_chunk.as_deref().map(RwLock::read);
// if it's a full block do a faster collision check
if block_state.is_collision_shape_full() {
if !state.aabb.intersects_aabb(&AABB {
min: item.pos.to_vec3_floored(),
max: (item.pos + 1).to_vec3_floored(),
}) {
continue;
}
block_collisions.push(BLOCK_SHAPE.move_relative(item.pos.to_vec3_floored()));
continue;
}
let block_shape = state.get_block_shape(block_state);
let block_shape = block_shape.move_relative(item.pos.to_vec3_floored());
// if the entity shape and block shape don't collide, continue
if !Shapes::matches_anywhere(&block_shape, &state.entity_shape, |a, b| a && b) {
continue;
}
block_collisions.push(block_shape);
while let Some(item) = state.cursor.next() {
state.compute_next(
item,
&mut block_collisions,
initial_chunk_pos,
initial_chunk.as_deref(),
);
}
block_collisions
@ -74,16 +60,73 @@ pub fn get_block_collisions(world: &Instance, aabb: AABB) -> Vec<VoxelShape> {
pub struct BlockCollisionsState<'a> {
pub world: &'a Instance,
pub aabb: AABB,
pub aabb: &'a AABB,
pub entity_shape: VoxelShape,
pub cursor: Cursor3d,
_context: EntityCollisionContext,
cached_sections: Vec<(ChunkSectionPos, azalea_world::Section)>,
cached_block_shapes: Vec<(BlockState, &'static VoxelShape)>,
}
impl<'a> BlockCollisionsState<'a> {
pub fn new(world: &'a Instance, aabb: AABB) -> Self {
fn compute_next(
&mut self,
item: CursorIteration,
block_collisions: &mut Vec<VoxelShape>,
initial_chunk_pos: ChunkPos,
initial_chunk: Option<&Chunk>,
) {
if item.iteration_type == CursorIterationType::Corner {
return;
}
let item_chunk_pos = ChunkPos::from(item.pos);
let block_state: BlockState = if item_chunk_pos == initial_chunk_pos {
match &initial_chunk {
Some(initial_chunk) => initial_chunk
.get(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y)
.unwrap_or(BlockState::AIR),
_ => BlockState::AIR,
}
} else {
self.get_block_state(item.pos)
};
if block_state.is_air() {
// fast path since we can't collide with air
return;
}
// TODO: continue if self.only_suffocating_blocks and the block is not
// suffocating
// if it's a full block do a faster collision check
if block_state.is_collision_shape_full() {
if !self.aabb.intersects_aabb(&AABB {
min: item.pos.to_vec3_floored(),
max: (item.pos + 1).to_vec3_floored(),
}) {
return;
}
block_collisions.push(BLOCK_SHAPE.move_relative(item.pos.to_vec3_floored()));
return;
}
let block_shape = self.get_block_shape(block_state);
let block_shape = block_shape.move_relative(item.pos.to_vec3_floored());
// if the entity shape and block shape don't collide, continue
if !Shapes::matches_anywhere(&block_shape, &self.entity_shape, |a, b| a && b) {
return;
}
block_collisions.push(block_shape);
}
pub fn new(world: &'a Instance, aabb: &'a AABB, context: EntityCollisionContext) -> Self {
let origin = BlockPos {
x: (aabb.min.x - EPSILON).floor() as i32 - 1,
y: (aabb.min.y - EPSILON).floor() as i32 - 1,
@ -104,6 +147,8 @@ impl<'a> BlockCollisionsState<'a> {
entity_shape: VoxelShape::from(aabb),
cursor,
_context: context,
cached_sections: Vec::new(),
cached_block_shapes: Vec::new(),
}
@ -182,3 +227,78 @@ impl<'a> BlockCollisionsState<'a> {
shape
}
}
pub struct EntityCollisionContext {
pub descending: bool,
pub entity_bottom: f64,
pub held_item: ItemStack,
can_stand_on_fluid_predicate: CanStandOnFluidPredicate,
pub entity: Option<Entity>,
}
impl EntityCollisionContext {
pub fn of(entity: Option<Entity>) -> Self {
Self {
descending: false,
entity_bottom: 0.0,
held_item: ItemStack::Empty,
can_stand_on_fluid_predicate: CanStandOnFluidPredicate::PassToEntity,
entity,
}
}
pub fn with_include_liquids(mut self, include_liquids: bool) -> Self {
self.can_stand_on_fluid_predicate = if include_liquids {
CanStandOnFluidPredicate::AlwaysTrue
} else {
CanStandOnFluidPredicate::PassToEntity
};
self
}
pub fn can_stand_on_fluid(&self, above: &FluidState, target: &FluidState) -> bool {
self.can_stand_on_fluid_predicate.matches(&target) && !above.is_same_kind(target)
}
}
enum CanStandOnFluidPredicate {
PassToEntity,
AlwaysTrue,
}
impl CanStandOnFluidPredicate {
pub fn matches(&self, _state: &FluidState) -> bool {
match self {
Self::AlwaysTrue => true,
// minecraft sometimes returns true for striders here, false for every other entity
// though
Self::PassToEntity => false,
}
}
}
/// This basically gets all the chunks that an entity colliding with
/// that bounding box could be in.
///
/// This is forEachAccessibleNonEmptySection in vanilla Minecraft because they
/// sort entities into sections instead of just chunks. In theory this might be
/// a performance loss for Azalea. If this ever turns out to be a bottleneck,
/// then maybe you should try having it do that instead.
pub fn for_entities_in_chunks_colliding_with(
world: &Instance,
aabb: &AABB,
mut consumer: impl FnMut(ChunkPos, &HashSet<Entity>),
) {
let min_section = ChunkSectionPos::from(aabb.min - Vec3::new(2., 4., 2.));
let max_section = ChunkSectionPos::from(aabb.max + Vec3::new(2., 0., 2.));
let min_chunk = ChunkPos::from(min_section);
let max_chunk = ChunkPos::from(max_section);
for chunk_x in min_chunk.x..=max_chunk.x {
for chunk_z in min_chunk.z..=max_chunk.z {
let chunk_pos = ChunkPos::new(chunk_x, chunk_z);
if let Some(entities) = world.entities_by_chunk.get(&chunk_pos) {
consumer(chunk_pos, entities);
}
}
}
}

View file

@ -27,6 +27,8 @@ pub fn update_in_water_state_and_do_fluid_pushing(
.expect("All entities with InLoadedChunk should be in a valid world");
let world = world_lock.read();
// reset the heights since they're going to be set in
// update_in_water_state_and_do_water_current_pushing
physics.water_fluid_height = 0.;
physics.lava_fluid_height = 0.;
@ -110,9 +112,9 @@ fn update_fluid_height_and_do_fluid_pushing(
let mut additional_player_delta = Vec3::default();
let mut num_fluids_being_touched = 0;
for cur_x in min_x..=max_x {
for cur_y in min_y..=max_y {
for cur_z in min_z..=max_z {
for cur_x in min_x..max_x {
for cur_y in min_y..max_y {
for cur_z in min_z..max_z {
let cur_pos = BlockPos::new(cur_x, cur_y, cur_z);
let Some(fluid_at_cur_pos) = world.get_fluid_state(&cur_pos) else {
continue;
@ -184,21 +186,22 @@ pub fn get_fluid_flow(fluid: &FluidState, world: &Instance, pos: BlockPos) -> Ve
let mut z_flow: f64 = 0.;
let mut x_flow: f64 = 0.;
println!("current fluid height: {}", fluid.height());
for direction in Direction::HORIZONTAL {
let adjacent_block_pos = pos.offset_with_direction(direction);
let adjacent_fluid_state = world
.get_fluid_state(&adjacent_block_pos)
let adjacent_block_state = world
.get_block_state(&adjacent_block_pos)
.unwrap_or_default();
let adjacent_fluid_state = FluidState::from(adjacent_block_state);
if fluid.affects_flow(&adjacent_fluid_state) {
let mut adjacent_fluid_height = adjacent_fluid_state.height();
let mut adjacent_height_difference: f32 = 0.;
if adjacent_fluid_height == 0. {
if !legacy_blocks_motion(
world
.get_block_state(&adjacent_block_pos)
.unwrap_or_default(),
) {
if !legacy_blocks_motion(adjacent_block_state) {
let block_pos_below_adjacent = adjacent_block_pos.down(1);
let fluid_below_adjacent = world
.get_fluid_state(&block_pos_below_adjacent)
@ -216,6 +219,14 @@ pub fn get_fluid_flow(fluid: &FluidState, world: &Instance, pos: BlockPos) -> Ve
adjacent_height_difference = fluid.height() - adjacent_fluid_height;
}
println!(
"our fluid height: {}, adjacent fluid height: {adjacent_fluid_height}",
fluid.height()
);
println!(
"{direction:?} adjacent_height_difference: {adjacent_height_difference}, {adjacent_fluid_state:?}"
);
if adjacent_height_difference != 0. {
x_flow += (direction.x() as f32 * adjacent_height_difference) as f64;
z_flow += (direction.z() as f32 * adjacent_height_difference) as f64;
@ -223,6 +234,8 @@ pub fn get_fluid_flow(fluid: &FluidState, world: &Instance, pos: BlockPos) -> Ve
}
}
println!("x_flow: {x_flow}, z_flow: {z_flow}");
let mut flow = Vec3::new(x_flow, 0., z_flow);
if fluid.falling {
for direction in Direction::HORIZONTAL {

View file

@ -90,44 +90,40 @@ pub fn ai_step(
physics.velocity.z = 0.;
}
if let Some(jumping) = jumping {
if **jumping {
// TODO: jumping in liquids and jump delay
if jumping == Some(&Jumping(true)) {
let fluid_height = if physics.is_in_lava() {
physics.lava_fluid_height
} else if physics.is_in_water() {
physics.water_fluid_height
} else {
0.
};
let fluid_height = if physics.is_in_lava() {
physics.lava_fluid_height
} else if physics.is_in_water() {
physics.water_fluid_height
} else {
0.
};
let in_water = physics.is_in_water() && fluid_height > 0.;
let fluid_jump_threshold = travel::fluid_jump_threshold();
let in_water = physics.is_in_water() && fluid_height > 0.;
let fluid_jump_threshold = travel::fluid_jump_threshold();
if !in_water || physics.on_ground() && fluid_height <= fluid_jump_threshold {
if !physics.is_in_lava()
|| physics.on_ground() && fluid_height <= fluid_jump_threshold
if !in_water || physics.on_ground() && fluid_height <= fluid_jump_threshold {
if !physics.is_in_lava()
|| physics.on_ground() && fluid_height <= fluid_jump_threshold
{
if (physics.on_ground() || in_water && fluid_height <= fluid_jump_threshold)
&& physics.no_jump_delay == 0
{
if (physics.on_ground() || in_water && fluid_height <= fluid_jump_threshold)
&& physics.no_jump_delay == 0
{
jump_from_ground(
&mut physics,
position,
look_direction,
sprinting,
instance_name,
&instance_container,
);
physics.no_jump_delay = 10;
}
} else {
jump_in_liquid(&mut physics);
jump_from_ground(
&mut physics,
position,
look_direction,
sprinting,
instance_name,
&instance_container,
);
physics.no_jump_delay = 10;
}
} else {
jump_in_liquid(&mut physics);
}
} else {
jump_in_liquid(&mut physics);
}
} else {
physics.no_jump_delay = 0;
@ -417,7 +413,6 @@ fn handle_relative_friction_and_calculate_movement(
.unwrap_or_default()
.into();
// TODO: powdered snow
if **on_climbable || block_at_feet == azalea_registry::Block::PowderSnow {
physics.velocity.y = 0.2;
}

View file

@ -1,5 +1,8 @@
use azalea_block::{Block, BlockState};
use azalea_core::{aabb::AABB, position::Vec3};
use azalea_block::{Block, BlockState, fluid_state::FluidState};
use azalea_core::{
aabb::AABB,
position::{BlockPos, Vec3},
};
use azalea_entity::{
Attributes, InLoadedChunk, Jumping, LocalEntity, LookDirection, OnClimbable, Physics, Pose,
Position, metadata::Sprinting, move_relative,
@ -9,7 +12,11 @@ use bevy_ecs::prelude::*;
use crate::{
HandleRelativeFrictionAndCalculateMovementOpts,
collision::{MoverType, move_colliding},
collision::{
MoverType, Shapes,
entity_collisions::{CollidableEntityQuery, PhysicsQuery, get_entity_collisions},
move_colliding,
},
get_block_pos_below_that_affects_movement, handle_relative_friction_and_calculate_movement,
};
@ -19,6 +26,7 @@ use crate::{
pub fn travel(
mut query: Query<
(
Entity,
&mut Physics,
&mut LookDirection,
&mut Position,
@ -32,8 +40,11 @@ pub fn travel(
(With<LocalEntity>, With<InLoadedChunk>),
>,
instance_container: Res<InstanceContainer>,
physics_query: PhysicsQuery,
collidable_entity_query: CollidableEntityQuery,
) {
for (
entity,
mut physics,
direction,
position,
@ -59,13 +70,16 @@ pub fn travel(
// !this.canStandOnFluid(fluidAtBlock)` here but it doesn't matter
// for players
travel_in_fluid(
&world,
entity,
&mut physics,
&direction,
position,
attributes,
sprinting,
on_climbable,
&world,
&physics_query,
&collidable_entity_query,
);
} else {
travel_in_air(
@ -150,13 +164,16 @@ fn travel_in_air(
}
fn travel_in_fluid(
world: &Instance,
entity: Entity,
physics: &mut Physics,
direction: &LookDirection,
mut position: Mut<Position>,
attributes: &Attributes,
sprinting: Sprinting,
on_climbable: &OnClimbable,
world: &Instance,
physics_query: &PhysicsQuery,
collidable_entity_query: &CollidableEntityQuery,
) {
let moving_down = physics.velocity.y <= 0.;
let y = position.y;
@ -185,6 +202,8 @@ fn travel_in_fluid(
// waterMovementSpeed = 0.96F;
// }
println!("travel in fluid");
move_relative(physics, direction, speed, &acceleration);
move_colliding(
MoverType::Own,
@ -206,6 +225,8 @@ fn travel_in_fluid(
physics.velocity =
get_fluid_falling_adjusted_movement(gravity, moving_down, new_velocity, sprinting);
} else {
println!("in lava");
move_relative(physics, direction, 0.02, &acceleration);
move_colliding(
MoverType::Own,
@ -239,13 +260,16 @@ fn travel_in_fluid(
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,
entity,
physics_query,
collidable_entity_query,
physics,
physics.bounding_box,
velocity.up(0.6).down(position.y).up(y),
)
{
println!("horizontal collision, setting y velocity");
physics.velocity.y = 0.3;
}
}
@ -276,14 +300,102 @@ fn get_fluid_falling_adjusted_movement(
}
}
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));
fn is_free(
world: &Instance,
source_entity: Entity,
physics_query: &PhysicsQuery,
collidable_entity_query: &CollidableEntityQuery,
entity_physics: &mut Physics,
bounding_box: AABB,
delta: Vec3,
) -> bool {
let bounding_box = bounding_box.move_relative(delta);
let _ = (bounding_box, world, x, y, z);
// this.level().noCollision(this, var1) &&
// !this.level().containsAnyLiquid(var1);
// TODO: implement this, see Entity.isFree
no_collision(
world,
source_entity,
physics_query,
collidable_entity_query,
entity_physics,
&bounding_box,
false,
) && !contains_any_liquid(world, bounding_box)
}
true
fn no_collision(
world: &Instance,
source_entity: Entity,
physics_query: &PhysicsQuery,
collidable_entity_query: &CollidableEntityQuery,
entity_physics: &mut Physics,
aabb: &AABB,
include_liquid_collisions: bool,
) -> bool {
let collisions = if include_liquid_collisions {
crate::collision::world_collisions::get_block_and_liquid_collisions(world, aabb)
} else {
crate::collision::world_collisions::get_block_collisions(world, aabb)
};
for collision in collisions {
if !collision.is_empty() {
return false;
}
}
if !get_entity_collisions(
world,
aabb,
Some(source_entity),
physics_query,
collidable_entity_query,
)
.is_empty()
{
return false;
}
// else if entity is none {
// return true;
// }
else {
let collision = border_collision(entity_physics, aabb);
if let Some(collision) = collision {
// !Shapes.joinIsNotEmpty(collision, Shapes.create(aabb), BooleanOp.AND);
!Shapes::matches_anywhere(&collision.into(), &aabb.into(), |a, b| a && b)
} else {
true
}
}
}
fn border_collision(_entity_physics: &Physics, _aabb: &AABB) -> Option<AABB> {
// TODO: implement world border, see CollisionGetter.borderCollision
None
}
fn contains_any_liquid(world: &Instance, bounding_box: AABB) -> bool {
let min = bounding_box.min.to_block_pos_floor();
let max = bounding_box.max.to_block_pos_ceil();
for x in min.x..max.x {
for y in min.y..max.y {
for z in min.z..max.z {
let block_state = world
.chunks
.get_block_state(&BlockPos::new(x, y, z))
.unwrap_or_default();
if !FluidState::from(block_state).is_empty() {
return true;
}
}
}
}
false
}
fn get_effective_gravity() -> f64 {

View file

@ -1,7 +1,7 @@
use azalea_client::InConfigState;
use azalea_client::chunks::handle_chunk_batch_finished_event;
use azalea_client::inventory::InventorySet;
use azalea_client::packet::config::SendConfigPacketEvent;
use azalea_client::packet::config::{SendConfigPacketEvent, handle_send_packet_event};
use azalea_client::packet::game::SendPacketEvent;
use azalea_client::packet::{death_event_on_0_health, game::ResourcePackEvent};
use azalea_client::respawn::perform_respawn;
@ -23,7 +23,9 @@ impl Plugin for AcceptResourcePacksPlugin {
.before(perform_respawn)
.after(death_event_on_0_health)
.after(handle_chunk_batch_finished_event)
.after(InventorySet),
.after(InventorySet)
.before(handle_send_packet_event)
.after(azalea_client::brand::handle_end_login_state),
);
}
}