mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 23:44:38 +00:00
bubble columns
This commit is contained in:
parent
63b4406af2
commit
579aefdf68
18 changed files with 1273 additions and 847 deletions
|
@ -5,7 +5,7 @@ use crate::{
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FluidState {
|
||||
pub fluid: azalea_registry::Fluid,
|
||||
pub kind: FluidKind,
|
||||
/// 0 = empty, 8 = full, 9 = max.
|
||||
///
|
||||
/// 9 is meant to be used when there's another fluid block of the same type
|
||||
|
@ -23,42 +23,44 @@ pub struct FluidState {
|
|||
/// set (see FlowingFluid.getFlowing)
|
||||
pub falling: bool,
|
||||
}
|
||||
#[derive(Default, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FluidKind {
|
||||
#[default]
|
||||
Empty,
|
||||
Water,
|
||||
Lava,
|
||||
}
|
||||
impl FluidState {
|
||||
pub fn new_source_block(kind: FluidKind, falling: bool) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
amount: 8,
|
||||
falling,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.amount == 0
|
||||
}
|
||||
|
||||
pub fn affects_flow(&self, other: &FluidState) -> bool {
|
||||
other.amount == 0 || self.is_same_kind(other)
|
||||
}
|
||||
|
||||
pub fn is_same_kind(&self, other: &FluidState) -> bool {
|
||||
(other.is_water() && self.is_water())
|
||||
|| (other.is_lava() && self.is_lava())
|
||||
|| (self.amount == 0 && other.amount == 0)
|
||||
}
|
||||
|
||||
pub fn is_water(&self) -> bool {
|
||||
matches!(
|
||||
self.fluid,
|
||||
azalea_registry::Fluid::Water | azalea_registry::Fluid::FlowingWater
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_lava(&self) -> bool {
|
||||
matches!(
|
||||
self.fluid,
|
||||
azalea_registry::Fluid::Lava | azalea_registry::Fluid::FlowingLava
|
||||
)
|
||||
(other.kind == self.kind) || (self.amount == 0 && other.amount == 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FluidState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fluid: azalea_registry::Fluid::Empty,
|
||||
kind: FluidKind::Empty,
|
||||
amount: 0,
|
||||
falling: false,
|
||||
}
|
||||
|
@ -74,38 +76,47 @@ impl From<BlockState> for FluidState {
|
|||
.property::<crate::properties::Waterlogged>()
|
||||
.unwrap_or_default()
|
||||
{
|
||||
Self {
|
||||
fluid: azalea_registry::Fluid::Water,
|
||||
return Self {
|
||||
kind: FluidKind::Water,
|
||||
amount: 8,
|
||||
falling: false,
|
||||
}
|
||||
} else {
|
||||
let block = Box::<dyn Block>::from(state);
|
||||
if let Some(water) = block.downcast_ref::<crate::blocks::Water>() {
|
||||
Self {
|
||||
fluid: azalea_registry::Fluid::Water,
|
||||
amount: to_or_from_legacy_fluid_level(water.level as u8),
|
||||
falling: false,
|
||||
}
|
||||
} else if let Some(lava) = block.downcast_ref::<crate::blocks::Lava>() {
|
||||
Self {
|
||||
fluid: azalea_registry::Fluid::Lava,
|
||||
amount: to_or_from_legacy_fluid_level(lava.level as u8),
|
||||
falling: false,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
fluid: azalea_registry::Fluid::Empty,
|
||||
amount: 0,
|
||||
falling: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let registry_block = azalea_registry::Block::from(state);
|
||||
match registry_block {
|
||||
azalea_registry::Block::Water => {
|
||||
let level = state
|
||||
.property::<crate::properties::WaterLevel>()
|
||||
.expect("water block should always have WaterLevel");
|
||||
return Self {
|
||||
kind: FluidKind::Water,
|
||||
amount: to_or_from_legacy_fluid_level(level as u8),
|
||||
falling: false,
|
||||
};
|
||||
}
|
||||
azalea_registry::Block::Lava => {
|
||||
let level = state
|
||||
.property::<crate::properties::LavaLevel>()
|
||||
.expect("lava block should always have LavaLevel");
|
||||
return Self {
|
||||
kind: FluidKind::Lava,
|
||||
amount: to_or_from_legacy_fluid_level(level as u8),
|
||||
falling: false,
|
||||
};
|
||||
}
|
||||
azalea_registry::Block::BubbleColumn => {
|
||||
return Self::new_source_block(FluidKind::Water, false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sometimes Minecraft represents fluids with 0 being the empty and 8 being
|
||||
/// full, and sometimes it's the opposite. You can use this function to convert
|
||||
/// Sometimes Minecraft represents fluids with 0 being empty and 8 being full,
|
||||
/// and sometimes it's the opposite. You can use this function to convert
|
||||
/// in between those two representations.
|
||||
///
|
||||
/// You usually don't need to call this yourself, see [`FluidState`].
|
||||
|
@ -116,22 +127,14 @@ pub fn to_or_from_legacy_fluid_level(level: u8) -> u8 {
|
|||
|
||||
impl From<FluidState> for BlockState {
|
||||
fn from(state: FluidState) -> Self {
|
||||
match state.fluid {
|
||||
azalea_registry::Fluid::Empty => BlockState::AIR,
|
||||
azalea_registry::Fluid::Water | azalea_registry::Fluid::FlowingWater => {
|
||||
BlockState::from(crate::blocks::Water {
|
||||
level: crate::properties::WaterLevel::from(
|
||||
state.amount as BlockStateIntegerRepr,
|
||||
),
|
||||
})
|
||||
}
|
||||
azalea_registry::Fluid::Lava | azalea_registry::Fluid::FlowingLava => {
|
||||
BlockState::from(crate::blocks::Lava {
|
||||
level: crate::properties::LavaLevel::from(
|
||||
state.amount as BlockStateIntegerRepr,
|
||||
),
|
||||
})
|
||||
}
|
||||
match state.kind {
|
||||
FluidKind::Empty => BlockState::AIR,
|
||||
FluidKind::Water => BlockState::from(crate::blocks::Water {
|
||||
level: crate::properties::WaterLevel::from(state.amount as BlockStateIntegerRepr),
|
||||
}),
|
||||
FluidKind::Lava => BlockState::from(crate::blocks::Lava {
|
||||
level: crate::properties::LavaLevel::from(state.amount as BlockStateIntegerRepr),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -500,6 +500,9 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
**position = new_pos;
|
||||
}
|
||||
|
||||
// old_pos is set to the current position when we're teleported
|
||||
physics.set_old_pos(&position);
|
||||
|
||||
// send the relevant packets
|
||||
|
||||
send_packet_events.send(SendPacketEvent::new(
|
||||
|
@ -853,10 +856,14 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
if new_pos != **position {
|
||||
**position = new_pos;
|
||||
}
|
||||
let position = position.clone();
|
||||
let mut look_direction = entity.get_mut::<LookDirection>().unwrap();
|
||||
if new_look_direction != *look_direction {
|
||||
*look_direction = new_look_direction;
|
||||
}
|
||||
// old_pos is set to the current position when we're teleported
|
||||
let mut physics = entity.get_mut::<Physics>().unwrap();
|
||||
physics.set_old_pos(&position);
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -18,8 +18,8 @@ pub struct ClipPointOpts<'a> {
|
|||
pub delta: &'a Vec3,
|
||||
pub begin: f64,
|
||||
pub min_x: f64,
|
||||
pub max_x: f64,
|
||||
pub min_z: f64,
|
||||
pub max_x: f64,
|
||||
pub max_z: f64,
|
||||
pub result_dir: Direction,
|
||||
pub start: &'a Vec3,
|
||||
|
@ -27,36 +27,28 @@ pub struct ClipPointOpts<'a> {
|
|||
|
||||
impl AABB {
|
||||
pub fn contract(&self, x: f64, y: f64, z: f64) -> AABB {
|
||||
let mut min_x = self.min.x;
|
||||
let mut min_y = self.min.y;
|
||||
let mut min_z = self.min.z;
|
||||
|
||||
let mut max_x = self.max.x;
|
||||
let mut max_y = self.max.y;
|
||||
let mut max_z = self.max.z;
|
||||
let mut min = self.min;
|
||||
let mut max = self.max;
|
||||
|
||||
if x < 0.0 {
|
||||
min_x -= x;
|
||||
min.x -= x;
|
||||
} else if x > 0.0 {
|
||||
max_x -= x;
|
||||
max.x -= x;
|
||||
}
|
||||
|
||||
if y < 0.0 {
|
||||
min_y -= y;
|
||||
min.y -= y;
|
||||
} else if y > 0.0 {
|
||||
max_y -= y;
|
||||
max.y -= y;
|
||||
}
|
||||
|
||||
if z < 0.0 {
|
||||
min_z -= z;
|
||||
min.z -= z;
|
||||
} else if z > 0.0 {
|
||||
max_z -= z;
|
||||
max.z -= z;
|
||||
}
|
||||
|
||||
AABB {
|
||||
min: Vec3::new(min_x, min_y, min_z),
|
||||
max: Vec3::new(max_x, max_y, max_z),
|
||||
}
|
||||
AABB { min, max }
|
||||
}
|
||||
|
||||
pub fn expand_towards(&self, other: &Vec3) -> AABB {
|
||||
|
@ -167,13 +159,13 @@ impl AABB {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn contains(&self, x: f64, y: f64, z: f64) -> bool {
|
||||
x >= self.min.x
|
||||
&& x < self.max.x
|
||||
&& y >= self.min.y
|
||||
&& y < self.max.y
|
||||
&& z >= self.min.z
|
||||
&& z < self.max.z
|
||||
pub fn contains(&self, point: &Vec3) -> bool {
|
||||
point.x >= self.min.x
|
||||
&& point.x < self.max.x
|
||||
&& point.y >= self.min.y
|
||||
&& point.y < self.max.y
|
||||
&& point.z >= self.min.z
|
||||
&& point.z < self.max.z
|
||||
}
|
||||
|
||||
pub fn size(&self) -> f64 {
|
||||
|
@ -202,10 +194,17 @@ impl AABB {
|
|||
pub fn clip(&self, min: &Vec3, max: &Vec3) -> Option<Vec3> {
|
||||
let mut t = 1.0;
|
||||
let delta = max - min;
|
||||
let _dir = Self::get_direction(self, min, &mut t, None, &delta)?;
|
||||
let _dir = Self::get_direction_aabb(self, min, &mut t, None, &delta)?;
|
||||
Some(min + &(delta * t))
|
||||
}
|
||||
|
||||
pub fn clip_with_from_and_to(min: &Vec3, max: &Vec3, from: &Vec3, to: &Vec3) -> Option<Vec3> {
|
||||
let mut t = 1.0;
|
||||
let delta = to - from;
|
||||
let _dir = Self::get_direction(min, max, from, &mut t, None, &delta)?;
|
||||
Some(from + &(delta * t))
|
||||
}
|
||||
|
||||
pub fn clip_iterable(
|
||||
boxes: &Vec<AABB>,
|
||||
from: &Vec3,
|
||||
|
@ -217,7 +216,7 @@ impl AABB {
|
|||
let delta = to - from;
|
||||
|
||||
for aabb in boxes {
|
||||
dir = Self::get_direction(
|
||||
dir = Self::get_direction_aabb(
|
||||
&aabb.move_relative(pos.to_vec3_floored()),
|
||||
from,
|
||||
&mut t,
|
||||
|
@ -236,8 +235,19 @@ impl AABB {
|
|||
})
|
||||
}
|
||||
|
||||
fn get_direction_aabb(
|
||||
&self,
|
||||
from: &Vec3,
|
||||
t: &mut f64,
|
||||
dir: Option<Direction>,
|
||||
delta: &Vec3,
|
||||
) -> Option<Direction> {
|
||||
AABB::get_direction(&self.min, &self.max, from, t, dir, delta)
|
||||
}
|
||||
|
||||
fn get_direction(
|
||||
aabb: &AABB,
|
||||
min: &Vec3,
|
||||
max: &Vec3,
|
||||
from: &Vec3,
|
||||
t: &mut f64,
|
||||
mut dir: Option<Direction>,
|
||||
|
@ -248,11 +258,11 @@ impl AABB {
|
|||
t,
|
||||
approach_dir: dir,
|
||||
delta,
|
||||
begin: aabb.min.x,
|
||||
min_x: aabb.min.y,
|
||||
max_x: aabb.max.y,
|
||||
min_z: aabb.min.z,
|
||||
max_z: aabb.max.z,
|
||||
begin: min.x,
|
||||
min_x: min.y,
|
||||
max_x: max.y,
|
||||
min_z: min.z,
|
||||
max_z: max.z,
|
||||
result_dir: Direction::West,
|
||||
start: from,
|
||||
});
|
||||
|
@ -261,11 +271,11 @@ impl AABB {
|
|||
t,
|
||||
approach_dir: dir,
|
||||
delta,
|
||||
begin: aabb.max.x,
|
||||
min_x: aabb.min.y,
|
||||
max_x: aabb.max.y,
|
||||
min_z: aabb.min.z,
|
||||
max_z: aabb.max.z,
|
||||
begin: max.x,
|
||||
min_x: min.y,
|
||||
max_x: max.y,
|
||||
min_z: min.z,
|
||||
max_z: max.z,
|
||||
result_dir: Direction::East,
|
||||
start: from,
|
||||
});
|
||||
|
@ -280,11 +290,11 @@ impl AABB {
|
|||
y: delta.z,
|
||||
z: delta.x,
|
||||
},
|
||||
begin: aabb.min.y,
|
||||
min_x: aabb.min.z,
|
||||
max_x: aabb.max.z,
|
||||
min_z: aabb.min.x,
|
||||
max_z: aabb.max.x,
|
||||
begin: min.y,
|
||||
min_x: min.z,
|
||||
max_x: max.z,
|
||||
min_z: min.x,
|
||||
max_z: max.x,
|
||||
result_dir: Direction::Down,
|
||||
start: &Vec3 {
|
||||
x: from.y,
|
||||
|
@ -301,11 +311,11 @@ impl AABB {
|
|||
y: delta.z,
|
||||
z: delta.x,
|
||||
},
|
||||
begin: aabb.max.y,
|
||||
min_x: aabb.min.z,
|
||||
max_x: aabb.max.z,
|
||||
min_z: aabb.min.x,
|
||||
max_z: aabb.max.x,
|
||||
begin: max.y,
|
||||
min_x: min.z,
|
||||
max_x: max.z,
|
||||
min_z: min.x,
|
||||
max_z: max.x,
|
||||
result_dir: Direction::Up,
|
||||
start: &Vec3 {
|
||||
x: from.y,
|
||||
|
@ -324,11 +334,11 @@ impl AABB {
|
|||
y: delta.x,
|
||||
z: delta.y,
|
||||
},
|
||||
begin: aabb.min.z,
|
||||
min_x: aabb.min.x,
|
||||
max_x: aabb.max.x,
|
||||
min_z: aabb.min.y,
|
||||
max_z: aabb.max.y,
|
||||
begin: min.z,
|
||||
min_x: min.x,
|
||||
max_x: max.x,
|
||||
min_z: min.y,
|
||||
max_z: max.y,
|
||||
result_dir: Direction::North,
|
||||
start: &Vec3 {
|
||||
x: from.z,
|
||||
|
@ -345,11 +355,11 @@ impl AABB {
|
|||
y: delta.x,
|
||||
z: delta.y,
|
||||
},
|
||||
begin: aabb.max.z,
|
||||
min_x: aabb.min.x,
|
||||
max_x: aabb.max.x,
|
||||
min_z: aabb.min.y,
|
||||
max_z: aabb.max.y,
|
||||
begin: max.z,
|
||||
min_x: min.x,
|
||||
max_x: max.x,
|
||||
min_z: min.y,
|
||||
max_z: max.y,
|
||||
result_dir: Direction::South,
|
||||
start: &Vec3 {
|
||||
x: from.z,
|
||||
|
@ -418,6 +428,60 @@ impl AABB {
|
|||
pub fn min(&self, axis: &Axis) -> f64 {
|
||||
axis.choose(self.min.x, self.min.y, self.min.z)
|
||||
}
|
||||
|
||||
pub fn collided_along_vector(&self, vector: Vec3, boxes: &Vec<AABB>) -> bool {
|
||||
let center = self.get_center();
|
||||
let new_center = center + vector;
|
||||
|
||||
for aabb in boxes {
|
||||
let inflated = aabb.inflate(
|
||||
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(¢er) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if inflated.clip(¢er, &new_center).is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockPos {
|
||||
pub fn between_closed_aabb(aabb: &AABB) -> Vec<BlockPos> {
|
||||
BlockPos::between_closed(BlockPos::from(aabb.min), BlockPos::from(aabb.max))
|
||||
}
|
||||
|
||||
pub fn between_closed(min: BlockPos, max: BlockPos) -> Vec<BlockPos> {
|
||||
assert!(min.x <= max.x);
|
||||
assert!(min.y <= max.y);
|
||||
assert!(min.z <= max.z);
|
||||
|
||||
let length_x = max.x - min.x + 1;
|
||||
let length_y = max.y - min.y + 1;
|
||||
let length_z = max.z - min.z + 1;
|
||||
let volume = length_x * length_y * length_z;
|
||||
|
||||
let mut result = Vec::with_capacity(volume as usize);
|
||||
for index in 0..volume {
|
||||
let index_x = index % length_x;
|
||||
let remaining_after_x = index / length_x;
|
||||
let index_y = remaining_after_x % length_y;
|
||||
let index_z = remaining_after_x / length_y;
|
||||
result.push(BlockPos::new(
|
||||
min.x + index_x,
|
||||
min.y + index_y,
|
||||
min.z + index_z,
|
||||
));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -99,6 +99,7 @@ pub enum CardinalDirection {
|
|||
East,
|
||||
}
|
||||
|
||||
/// A 3D axis like x, y, z.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Axis {
|
||||
X = 0,
|
||||
|
|
|
@ -86,6 +86,25 @@ pub fn to_degrees(radians: f64) -> f64 {
|
|||
radians * 57.29577951308232
|
||||
}
|
||||
|
||||
/// Returns either -1, 0, or 1, depending on whether the number is negative,
|
||||
/// zero, or positive.
|
||||
///
|
||||
/// This function exists because f64::signum doesn't check for 0.
|
||||
pub fn sign(num: f64) -> f64 {
|
||||
if num == 0. {
|
||||
0.
|
||||
} else {
|
||||
num.signum()
|
||||
}
|
||||
}
|
||||
pub fn sign_as_int(num: f64) -> i32 {
|
||||
if num == 0. {
|
||||
0
|
||||
} else {
|
||||
num.signum() as i32
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -7,7 +7,7 @@ pub struct EntityDimensions {
|
|||
}
|
||||
|
||||
impl EntityDimensions {
|
||||
pub fn make_bounding_box(&self, pos: Vec3) -> AABB {
|
||||
pub fn make_bounding_box(&self, pos: &Vec3) -> AABB {
|
||||
let radius = (self.width / 2.0) as f64;
|
||||
let height = self.height as f64;
|
||||
AABB {
|
||||
|
|
|
@ -12,13 +12,12 @@ mod plugin;
|
|||
pub mod vec_delta_codec;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Debug,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
pub use attributes::Attributes;
|
||||
use azalea_block::BlockState;
|
||||
use azalea_block::{fluid_state::FluidKind, BlockState};
|
||||
use azalea_buf::AzBuf;
|
||||
use azalea_core::{
|
||||
aabb::AABB,
|
||||
|
@ -257,6 +256,11 @@ pub struct Physics {
|
|||
pub velocity: Vec3,
|
||||
pub vec_delta_codec: VecDeltaCodec,
|
||||
|
||||
/// The position of the entity before it moved this tick.
|
||||
///
|
||||
/// This is set immediately before physics is done.
|
||||
pub old_position: Vec3,
|
||||
|
||||
/// The acceleration here is the force that will be attempted to be added to
|
||||
/// the entity's velocity next tick.
|
||||
///
|
||||
|
@ -297,6 +301,8 @@ impl Physics {
|
|||
velocity: Vec3::default(),
|
||||
vec_delta_codec: VecDeltaCodec::new(pos),
|
||||
|
||||
old_position: pos,
|
||||
|
||||
x_acceleration: 0.,
|
||||
y_acceleration: 0.,
|
||||
z_acceleration: 0.,
|
||||
|
@ -304,7 +310,7 @@ impl Physics {
|
|||
on_ground: false,
|
||||
last_on_ground: false,
|
||||
|
||||
bounding_box: dimensions.make_bounding_box(pos),
|
||||
bounding_box: dimensions.make_bounding_box(&pos),
|
||||
dimensions,
|
||||
|
||||
has_impulse: false,
|
||||
|
@ -355,6 +361,10 @@ impl Physics {
|
|||
// TODO: also check `!this.firstTick &&`
|
||||
self.lava_fluid_height > 0.
|
||||
}
|
||||
|
||||
pub fn set_old_pos(&mut self, pos: &Position) {
|
||||
self.old_position = **pos;
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker component for entities that are dead.
|
||||
|
@ -458,7 +468,7 @@ impl EntityBundle {
|
|||
},
|
||||
|
||||
jumping: Jumping(false),
|
||||
fluid_on_eyes: FluidOnEyes(azalea_registry::Fluid::Empty),
|
||||
fluid_on_eyes: FluidOnEyes(FluidKind::Empty),
|
||||
on_climbable: OnClimbable(false),
|
||||
}
|
||||
}
|
||||
|
@ -479,10 +489,10 @@ pub struct PlayerBundle {
|
|||
pub struct LocalEntity;
|
||||
|
||||
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
||||
pub struct FluidOnEyes(azalea_registry::Fluid);
|
||||
pub struct FluidOnEyes(FluidKind);
|
||||
|
||||
impl FluidOnEyes {
|
||||
pub fn new(fluid: azalea_registry::Fluid) -> Self {
|
||||
pub fn new(fluid: FluidKind) -> Self {
|
||||
Self(fluid)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use azalea_block::{Block, BlockBehavior};
|
||||
use azalea_block::{fluid_state::FluidKind, Block, BlockBehavior};
|
||||
use azalea_core::tier::get_item_tier;
|
||||
use azalea_registry as registry;
|
||||
|
||||
|
@ -105,7 +105,7 @@ fn destroy_speed(
|
|||
base_destroy_speed *= multiplier;
|
||||
}
|
||||
|
||||
if registry::tags::fluids::WATER.contains(fluid_on_eyes)
|
||||
if **fluid_on_eyes == FluidKind::Water
|
||||
&& enchantments::get_enchant_level(registry::Enchantment::AquaAffinity, player_inventory)
|
||||
== 0
|
||||
{
|
||||
|
|
|
@ -3,7 +3,7 @@ mod relative_updates;
|
|||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use azalea_block::BlockState;
|
||||
use azalea_block::{fluid_state::FluidKind, BlockState};
|
||||
use azalea_core::position::{BlockPos, ChunkPos, Vec3};
|
||||
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId};
|
||||
use bevy_app::{App, Plugin, PreUpdate, Update};
|
||||
|
@ -106,9 +106,9 @@ pub fn update_fluid_on_eyes(
|
|||
.unwrap_or_default();
|
||||
let fluid_cutoff_y = (eye_block_pos.y as f32 + fluid_at_eye.height()) as f64;
|
||||
if fluid_cutoff_y > adjusted_eye_y {
|
||||
**fluid_on_eyes = fluid_at_eye.fluid;
|
||||
**fluid_on_eyes = fluid_at_eye.kind;
|
||||
} else {
|
||||
**fluid_on_eyes = azalea_registry::Fluid::Empty;
|
||||
**fluid_on_eyes = FluidKind::Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ pub fn clamp_look_direction(mut query: Query<&mut LookDirection>) {
|
|||
/// Cached position in the world must be updated.
|
||||
pub fn update_bounding_box(mut query: Query<(&Position, &mut Physics), Changed<Position>>) {
|
||||
for (position, mut physics) in query.iter_mut() {
|
||||
let bounding_box = physics.dimensions.make_bounding_box(**position);
|
||||
let bounding_box = physics.dimensions.make_bounding_box(&position);
|
||||
physics.bounding_box = bounding_box;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
use azalea_block::{fluid_state::FluidState, BlockState};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use azalea_block::{
|
||||
fluid_state::{FluidKind, FluidState},
|
||||
BlockState,
|
||||
};
|
||||
use azalea_core::{
|
||||
aabb::AABB,
|
||||
block_hit_result::BlockHitResult,
|
||||
direction::Direction,
|
||||
direction::{Axis, Direction},
|
||||
math::{self, lerp, EPSILON},
|
||||
position::{BlockPos, Vec3},
|
||||
};
|
||||
|
@ -80,8 +86,8 @@ impl FluidPickType {
|
|||
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,
|
||||
Self::Any => fluid_state.kind != FluidKind::Empty,
|
||||
Self::Water => fluid_state.kind == FluidKind::Water,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,22 +204,10 @@ pub fn traverse_blocks<C, T>(
|
|||
|
||||
let vec = right_after_end - right_before_start;
|
||||
|
||||
/// Returns either -1, 0, or 1, depending on whether the number is negative,
|
||||
/// zero, or positive.
|
||||
///
|
||||
/// This function exists because f64::signum doesn't check for 0.
|
||||
fn get_number_sign(num: f64) -> f64 {
|
||||
if num == 0. {
|
||||
0.
|
||||
} else {
|
||||
num.signum()
|
||||
}
|
||||
}
|
||||
|
||||
let vec_sign = Vec3 {
|
||||
x: get_number_sign(vec.x),
|
||||
y: get_number_sign(vec.y),
|
||||
z: get_number_sign(vec.z),
|
||||
x: math::sign(vec.x),
|
||||
y: math::sign(vec.y),
|
||||
z: math::sign(vec.z),
|
||||
};
|
||||
|
||||
#[rustfmt::skip]
|
||||
|
@ -270,3 +264,125 @@ pub fn traverse_blocks<C, T>(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn box_traverse_blocks(from: &Vec3, to: &Vec3, aabb: &AABB) -> HashSet<BlockPos> {
|
||||
let delta = to - from;
|
||||
let traversed_blocks = BlockPos::between_closed_aabb(aabb);
|
||||
if delta.length_squared() < (0.99999_f32 * 0.99999) as f64 {
|
||||
return traversed_blocks.into_iter().collect();
|
||||
}
|
||||
|
||||
let mut traversed_and_collided_blocks = HashSet::new();
|
||||
let target_min_pos = aabb.min;
|
||||
let from_min_pos = target_min_pos - delta;
|
||||
add_collisions_along_travel(
|
||||
&mut traversed_and_collided_blocks,
|
||||
from_min_pos,
|
||||
target_min_pos,
|
||||
*aabb,
|
||||
);
|
||||
traversed_and_collided_blocks.extend(traversed_blocks);
|
||||
traversed_and_collided_blocks
|
||||
}
|
||||
|
||||
pub fn add_collisions_along_travel(
|
||||
collisions: &mut HashSet<BlockPos>,
|
||||
from: Vec3,
|
||||
to: Vec3,
|
||||
aabb: AABB,
|
||||
) {
|
||||
let delta = to - from;
|
||||
let mut min_x = from.x.floor() as i32;
|
||||
let mut min_y = from.y.floor() as i32;
|
||||
let mut min_z = from.z.floor() as i32;
|
||||
let direction_x = math::sign_as_int(delta.x);
|
||||
let direction_y = math::sign_as_int(delta.y);
|
||||
let direction_z = math::sign_as_int(delta.z);
|
||||
let step_x = if direction_x == 0 {
|
||||
f64::MAX
|
||||
} else {
|
||||
direction_x as f64 / delta.x
|
||||
};
|
||||
let step_y = if direction_y == 0 {
|
||||
f64::MAX
|
||||
} else {
|
||||
direction_y as f64 / delta.y
|
||||
};
|
||||
let step_z = if direction_z == 0 {
|
||||
f64::MAX
|
||||
} else {
|
||||
direction_z as f64 / delta.z
|
||||
};
|
||||
let mut cur_x = step_x
|
||||
* if direction_x > 0 {
|
||||
1. - math::fract(from.x)
|
||||
} else {
|
||||
math::fract(from.x)
|
||||
};
|
||||
let mut cur_y = step_y
|
||||
* if direction_y > 0 {
|
||||
1. - math::fract(from.y)
|
||||
} else {
|
||||
math::fract(from.y)
|
||||
};
|
||||
let mut cur_z = step_z
|
||||
* if direction_z > 0 {
|
||||
1. - math::fract(from.z)
|
||||
} else {
|
||||
math::fract(from.z)
|
||||
};
|
||||
let mut step_count = 0;
|
||||
|
||||
while cur_x <= 1. || cur_y <= 1. || cur_z <= 1. {
|
||||
if cur_x < cur_y {
|
||||
if cur_x < cur_z {
|
||||
min_x += direction_x;
|
||||
cur_x += step_x;
|
||||
} else {
|
||||
min_z += direction_z;
|
||||
cur_z += step_z;
|
||||
}
|
||||
} else if cur_y < cur_z {
|
||||
min_y += direction_y;
|
||||
cur_y += step_y;
|
||||
} else {
|
||||
min_z += direction_z;
|
||||
cur_z += step_z;
|
||||
}
|
||||
|
||||
if step_count > 16 {
|
||||
break;
|
||||
}
|
||||
step_count += 1;
|
||||
|
||||
let Some(clip_location) = AABB::clip_with_from_and_to(
|
||||
&Vec3::new(min_x as f64, min_y as f64, min_z as f64),
|
||||
&Vec3::new((min_x + 1) as f64, (min_y + 1) as f64, (min_z + 1) as f64),
|
||||
&from,
|
||||
&to,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let initial_max_x = clip_location
|
||||
.x
|
||||
.clamp(min_x as f64 + 1.0E-5, min_x as f64 + 1.0 - 1.0E-5);
|
||||
let initial_max_y = clip_location
|
||||
.y
|
||||
.clamp(min_y as f64 + 1.0E-5, min_y as f64 + 1.0 - 1.0E-5);
|
||||
let initial_max_z = clip_location
|
||||
.z
|
||||
.clamp(min_z as f64 + 1.0E-5, min_z as f64 + 1.0 - 1.0E-5);
|
||||
let max_x = (initial_max_x + aabb.get_size(Axis::X)).floor() as i32;
|
||||
let max_y = (initial_max_y + aabb.get_size(Axis::Y)).floor() as i32;
|
||||
let max_z = (initial_max_z + aabb.get_size(Axis::Z)).floor() as i32;
|
||||
|
||||
for x in min_x..=max_x {
|
||||
for y in min_y..=max_y {
|
||||
for z in min_z..=max_z {
|
||||
collisions.insert(BlockPos::new(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -355,7 +355,7 @@ pub fn fluid_shape(
|
|||
) -> &'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 {
|
||||
if fluid_state_above.kind == fluid.kind {
|
||||
return &BLOCK_SHAPE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -381,16 +381,25 @@ impl VoxelShape {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn move_relative(&self, x: f64, y: f64, z: f64) -> VoxelShape {
|
||||
pub fn move_relative(&self, delta: Vec3) -> VoxelShape {
|
||||
if self.shape().is_empty() {
|
||||
return EMPTY_SHAPE.clone();
|
||||
}
|
||||
|
||||
VoxelShape::Array(ArrayVoxelShape::new(
|
||||
self.shape().to_owned(),
|
||||
self.get_coords(Axis::X).iter().map(|c| c + x).collect(),
|
||||
self.get_coords(Axis::Y).iter().map(|c| c + y).collect(),
|
||||
self.get_coords(Axis::Z).iter().map(|c| c + z).collect(),
|
||||
self.get_coords(Axis::X)
|
||||
.iter()
|
||||
.map(|c| c + delta.x)
|
||||
.collect(),
|
||||
self.get_coords(Axis::Y)
|
||||
.iter()
|
||||
.map(|c| c + delta.y)
|
||||
.collect(),
|
||||
self.get_coords(Axis::Z)
|
||||
.iter()
|
||||
.map(|c| c + delta.z)
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -55,18 +55,13 @@ pub fn get_block_collisions(world: &Instance, aabb: AABB) -> Vec<VoxelShape> {
|
|||
continue;
|
||||
}
|
||||
|
||||
block_collisions.push(BLOCK_SHAPE.move_relative(
|
||||
item.pos.x as f64,
|
||||
item.pos.y as f64,
|
||||
item.pos.z as f64,
|
||||
));
|
||||
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.x as f64, item.pos.y as f64, item.pos.z as f64);
|
||||
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;
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use std::cmp;
|
||||
|
||||
use azalea_block::{fluid_state::FluidState, BlockState};
|
||||
use azalea_block::{
|
||||
fluid_state::{FluidKind, FluidState},
|
||||
BlockState,
|
||||
};
|
||||
use azalea_core::{
|
||||
direction::Direction,
|
||||
position::{BlockPos, Vec3},
|
||||
};
|
||||
use azalea_entity::{metadata::AbstractBoat, InLoadedChunk, LocalEntity, Physics, Position};
|
||||
use azalea_registry::{EntityKind, Fluid};
|
||||
use azalea_entity::{InLoadedChunk, LocalEntity, Physics, Position};
|
||||
use azalea_registry::Fluid;
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
|
@ -50,7 +51,7 @@ fn update_in_water_state_and_do_water_current_pushing(
|
|||
// }
|
||||
|
||||
// updateFluidHeightAndDoFluidPushing
|
||||
if update_fluid_height_and_do_fluid_pushing(physics, world, Fluid::Water, 0.014) {
|
||||
if update_fluid_height_and_do_fluid_pushing(physics, world, FluidKind::Water, 0.014) {
|
||||
// if !was_touching_water && !first_tick {
|
||||
// do_water_splash_effect();
|
||||
// }
|
||||
|
@ -66,7 +67,7 @@ fn update_in_water_state_and_do_water_current_pushing(
|
|||
fn update_fluid_height_and_do_fluid_pushing(
|
||||
physics: &mut Physics,
|
||||
world: &Instance,
|
||||
checking_fluid: Fluid,
|
||||
checking_fluid: FluidKind,
|
||||
fluid_push_factor: f64,
|
||||
) -> bool {
|
||||
// if touching_unloaded_chunk() {
|
||||
|
@ -96,7 +97,7 @@ fn update_fluid_height_and_do_fluid_pushing(
|
|||
let Some(fluid_at_cur_pos) = world.get_fluid_state(&cur_pos) else {
|
||||
continue;
|
||||
};
|
||||
if fluid_at_cur_pos.fluid != checking_fluid {
|
||||
if fluid_at_cur_pos.kind != checking_fluid {
|
||||
continue;
|
||||
}
|
||||
let fluid_max_y = (cur_y as f32 + fluid_at_cur_pos.height()) as f64;
|
||||
|
@ -146,9 +147,9 @@ fn update_fluid_height_and_do_fluid_pushing(
|
|||
}
|
||||
|
||||
match checking_fluid {
|
||||
Fluid::Water => physics.water_fluid_height = min_height_touching,
|
||||
Fluid::Lava => physics.lava_fluid_height = min_height_touching,
|
||||
checking_fluid => panic!("unknown fluid {checking_fluid}"),
|
||||
FluidKind::Water => physics.water_fluid_height = min_height_touching,
|
||||
FluidKind::Lava => physics.lava_fluid_height = min_height_touching,
|
||||
FluidKind::Empty => panic!("FluidKind::Empty should not be passed to update_fluid_height"),
|
||||
};
|
||||
|
||||
touching_fluid
|
||||
|
|
|
@ -4,8 +4,11 @@
|
|||
pub mod clip;
|
||||
pub mod collision;
|
||||
pub mod fluids;
|
||||
pub mod travel;
|
||||
|
||||
use azalea_block::{Block, BlockState};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use azalea_block::{fluid_state::FluidState, properties, Block, BlockState};
|
||||
use azalea_core::{
|
||||
aabb::AABB,
|
||||
math,
|
||||
|
@ -13,8 +16,8 @@ use azalea_core::{
|
|||
tick::GameTick,
|
||||
};
|
||||
use azalea_entity::{
|
||||
metadata::Sprinting, move_relative, Attributes, InLoadedChunk, Jumping, LocalEntity,
|
||||
LookDirection, OnClimbable, Physics, Pose, Position,
|
||||
metadata::Sprinting, move_relative, Attributes, InLoadedChunk, Jumping, LastSentPosition,
|
||||
LocalEntity, LookDirection, OnClimbable, Physics, Pose, Position,
|
||||
};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||
use bevy_app::{App, Plugin};
|
||||
|
@ -24,7 +27,8 @@ use bevy_ecs::{
|
|||
system::{Query, Res},
|
||||
world::Mut,
|
||||
};
|
||||
use collision::{move_colliding, MoverType};
|
||||
use clip::box_traverse_blocks;
|
||||
use collision::{move_colliding, BlockWithShape, MoverType, VoxelShape, BLOCK_SHAPE};
|
||||
|
||||
/// A Bevy [`SystemSet`] for running physics that makes entities do things.
|
||||
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
||||
|
@ -38,9 +42,11 @@ impl Plugin for PhysicsPlugin {
|
|||
(
|
||||
fluids::update_in_water_state_and_do_fluid_pushing
|
||||
.before(azalea_entity::update_fluid_on_eyes),
|
||||
update_old_position,
|
||||
fluids::update_swimming.after(azalea_entity::update_fluid_on_eyes),
|
||||
ai_step,
|
||||
travel,
|
||||
travel::travel,
|
||||
apply_effects_from_blocks,
|
||||
)
|
||||
.chain()
|
||||
.in_set(PhysicsSet)
|
||||
|
@ -49,303 +55,9 @@ impl Plugin for PhysicsPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
/// Move the entity with the given acceleration while handling friction,
|
||||
/// gravity, collisions, and some other stuff.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn travel(
|
||||
mut query: Query<
|
||||
(
|
||||
&mut Physics,
|
||||
&mut LookDirection,
|
||||
&mut Position,
|
||||
Option<&Sprinting>,
|
||||
Option<&Pose>,
|
||||
&Attributes,
|
||||
&InstanceName,
|
||||
&OnClimbable,
|
||||
&Jumping,
|
||||
),
|
||||
(With<LocalEntity>, With<InLoadedChunk>),
|
||||
>,
|
||||
instance_container: Res<InstanceContainer>,
|
||||
) {
|
||||
for (
|
||||
mut physics,
|
||||
direction,
|
||||
position,
|
||||
sprinting,
|
||||
pose,
|
||||
attributes,
|
||||
world_name,
|
||||
on_climbable,
|
||||
jumping,
|
||||
) in &mut query
|
||||
{
|
||||
let Some(world_lock) = instance_container.get(world_name) else {
|
||||
continue;
|
||||
};
|
||||
let world = world_lock.read();
|
||||
|
||||
let sprinting = *sprinting.unwrap_or(&Sprinting(false));
|
||||
|
||||
// TODO: elytras
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The usual movement when we're not in water or using an elytra.
|
||||
fn travel_in_air(
|
||||
physics: &mut Physics,
|
||||
direction: &LookDirection,
|
||||
position: Mut<Position>,
|
||||
attributes: &Attributes,
|
||||
sprinting: Sprinting,
|
||||
on_climbable: &OnClimbable,
|
||||
pose: Option<&Pose>,
|
||||
jumping: &Jumping,
|
||||
world: &Instance,
|
||||
) {
|
||||
let gravity = get_effective_gravity();
|
||||
|
||||
// LivingEntity.travel starts here
|
||||
|
||||
// TODO: fluids
|
||||
|
||||
// TODO: elytra
|
||||
|
||||
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<dyn Block> = 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn travel_in_fluid(
|
||||
physics: &mut Physics,
|
||||
direction: &LookDirection,
|
||||
mut position: Mut<Position>,
|
||||
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,
|
||||
);
|
||||
|
||||
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 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;
|
||||
// }
|
||||
|
||||
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 *= 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
|
||||
/// stuff.
|
||||
/// Applies air resistance and handles jumping.
|
||||
///
|
||||
/// Happens before [`travel::travel`].
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn ai_step(
|
||||
mut query: Query<
|
||||
|
@ -400,6 +112,188 @@ pub fn ai_step(
|
|||
}
|
||||
}
|
||||
|
||||
// in minecraft, this is done as part of aiStep immediately after travel
|
||||
pub fn apply_effects_from_blocks(
|
||||
mut query: Query<
|
||||
(
|
||||
&mut Physics,
|
||||
&mut LookDirection,
|
||||
&mut Position,
|
||||
&mut LastSentPosition,
|
||||
Option<&Sprinting>,
|
||||
Option<&Pose>,
|
||||
&Attributes,
|
||||
&InstanceName,
|
||||
&OnClimbable,
|
||||
&Jumping,
|
||||
),
|
||||
(With<LocalEntity>, With<InLoadedChunk>),
|
||||
>,
|
||||
instance_container: Res<InstanceContainer>,
|
||||
) {
|
||||
for (
|
||||
mut physics,
|
||||
mut look_direction,
|
||||
mut position,
|
||||
mut last_sent_position,
|
||||
sprinting,
|
||||
pose,
|
||||
attributes,
|
||||
world_name,
|
||||
on_climbable,
|
||||
jumping,
|
||||
) in &mut query
|
||||
{
|
||||
let Some(world_lock) = instance_container.get(world_name) else {
|
||||
continue;
|
||||
};
|
||||
let world = world_lock.read();
|
||||
|
||||
// if !is_affected_by_blocks {
|
||||
// continue
|
||||
// }
|
||||
|
||||
// if (this.onGround()) {
|
||||
// BlockPos var3 = this.getOnPosLegacy();
|
||||
// BlockState var4 = this.level().getBlockState(var3);
|
||||
// var4.getBlock().stepOn(this.level(), var3, var4, this);
|
||||
// }
|
||||
|
||||
let mut movement_this_tick = Vec::<EntityMovement>::new();
|
||||
movement_this_tick.push(EntityMovement {
|
||||
from: physics.old_position,
|
||||
to: **position,
|
||||
});
|
||||
|
||||
check_inside_blocks(&mut physics, &world, &movement_this_tick);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_inside_blocks(
|
||||
physics: &mut Physics,
|
||||
world: &Instance,
|
||||
movements: &[EntityMovement],
|
||||
) -> Vec<BlockState> {
|
||||
let mut blocks_inside = Vec::new();
|
||||
let mut visited_blocks = HashSet::<BlockState>::new();
|
||||
|
||||
for movement in movements {
|
||||
let bounding_box_at_target = physics
|
||||
.dimensions
|
||||
.make_bounding_box(&movement.to)
|
||||
.deflate_all(1.0E-5);
|
||||
|
||||
for traversed_block in
|
||||
box_traverse_blocks(&movement.from, &movement.to, &bounding_box_at_target)
|
||||
{
|
||||
// if (!this.isAlive()) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
let traversed_block_state = world.get_block_state(&traversed_block).unwrap_or_default();
|
||||
if traversed_block_state.is_air() {
|
||||
continue;
|
||||
}
|
||||
if !visited_blocks.insert(traversed_block_state) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
VoxelShape var12 = traversedBlockState.getEntityInsideCollisionShape(this.level(), traversedBlock);
|
||||
if (var12 != Shapes.block() && !this.collidedWithShapeMovingFrom(from, to, traversedBlock, var12)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
traversedBlockState.entityInside(this.level(), traversedBlock, this);
|
||||
this.onInsideBlock(traversedBlockState);
|
||||
*/
|
||||
|
||||
// this is different for end portal frames and tripwire hooks, i don't think it
|
||||
// actually matters for a client though
|
||||
let entity_inside_collision_shape = &*BLOCK_SHAPE;
|
||||
|
||||
if entity_inside_collision_shape != &*BLOCK_SHAPE
|
||||
&& !collided_with_shape_moving_from(
|
||||
&movement.from,
|
||||
&movement.to,
|
||||
traversed_block,
|
||||
entity_inside_collision_shape,
|
||||
physics,
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
handle_entity_inside_block(world, traversed_block_state, traversed_block, physics);
|
||||
|
||||
blocks_inside.push(traversed_block_state);
|
||||
}
|
||||
}
|
||||
|
||||
blocks_inside
|
||||
}
|
||||
|
||||
fn collided_with_shape_moving_from(
|
||||
from: &Vec3,
|
||||
to: &Vec3,
|
||||
traversed_block: BlockPos,
|
||||
entity_inside_collision_shape: &VoxelShape,
|
||||
physics: &Physics,
|
||||
) -> bool {
|
||||
let bounding_box_from = physics.dimensions.make_bounding_box(from);
|
||||
let delta = to - from;
|
||||
bounding_box_from.collided_along_vector(
|
||||
delta,
|
||||
&entity_inside_collision_shape
|
||||
.move_relative(traversed_block.to_vec3_floored())
|
||||
.to_aabbs(),
|
||||
)
|
||||
}
|
||||
|
||||
// BlockBehavior.entityInside
|
||||
fn handle_entity_inside_block(
|
||||
world: &Instance,
|
||||
block: BlockState,
|
||||
block_pos: BlockPos,
|
||||
physics: &mut Physics,
|
||||
) {
|
||||
let registry_block = azalea_registry::Block::from(block);
|
||||
match registry_block {
|
||||
azalea_registry::Block::BubbleColumn => {
|
||||
let block_above = world.get_block_state(&block_pos.up(1)).unwrap_or_default();
|
||||
let is_block_above_empty =
|
||||
block_above.is_collision_shape_empty() && FluidState::from(block_above).is_empty();
|
||||
let drag_down = block
|
||||
.property::<properties::Drag>()
|
||||
.expect("drag property should always be present on bubble columns");
|
||||
let velocity = &mut physics.velocity;
|
||||
|
||||
if is_block_above_empty {
|
||||
let new_y = if drag_down {
|
||||
f64::max(-0.9, velocity.y - 0.03)
|
||||
} else {
|
||||
f64::min(1.8, velocity.y + 0.1)
|
||||
};
|
||||
velocity.y = new_y;
|
||||
} else {
|
||||
let new_y = if drag_down {
|
||||
f64::max(-0.3, velocity.y - 0.03)
|
||||
} else {
|
||||
f64::min(0.7, velocity.y + 0.06)
|
||||
};
|
||||
velocity.y = new_y;
|
||||
physics.reset_fall_distance();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EntityMovement {
|
||||
pub from: Vec3,
|
||||
pub to: Vec3,
|
||||
}
|
||||
|
||||
pub fn jump_from_ground(
|
||||
physics: &mut Physics,
|
||||
position: &Position,
|
||||
|
@ -433,6 +327,12 @@ pub fn jump_from_ground(
|
|||
physics.has_impulse = true;
|
||||
}
|
||||
|
||||
pub fn update_old_position(mut query: Query<(&mut Physics, &Position)>) {
|
||||
for (mut physics, position) in &mut query {
|
||||
physics.set_old_pos(position);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_block_pos_below_that_affects_movement(position: &Position) -> BlockPos {
|
||||
BlockPos::new(
|
||||
position.x.floor() as i32,
|
||||
|
@ -442,7 +342,7 @@ fn get_block_pos_below_that_affects_movement(position: &Position) -> BlockPos {
|
|||
)
|
||||
}
|
||||
|
||||
// opts for handle_relative_friction_and_calculate_movement
|
||||
/// Options for [`handle_relative_friction_and_calculate_movement`]
|
||||
struct HandleRelativeFrictionAndCalculateMovementOpts<'a> {
|
||||
block_friction: f32,
|
||||
world: &'a Instance,
|
||||
|
@ -455,7 +355,6 @@ struct HandleRelativeFrictionAndCalculateMovementOpts<'a> {
|
|||
pose: Option<&'a Pose>,
|
||||
jumping: &'a Jumping,
|
||||
}
|
||||
|
||||
fn handle_relative_friction_and_calculate_movement(
|
||||
HandleRelativeFrictionAndCalculateMovementOpts {
|
||||
block_friction,
|
||||
|
@ -619,369 +518,3 @@ fn jump_boost_power() -> f64 {
|
|||
// }
|
||||
0.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use azalea_core::{position::ChunkPos, resource_location::ResourceLocation};
|
||||
use azalea_entity::{EntityBundle, EntityPlugin};
|
||||
use azalea_world::{Chunk, MinecraftEntityId, PartialInstance};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// You need an app to spawn entities in the world and do updates.
|
||||
fn make_test_app() -> App {
|
||||
let mut app = App::new();
|
||||
app.add_plugins((PhysicsPlugin, EntityPlugin))
|
||||
.init_resource::<InstanceContainer>();
|
||||
app
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gravity() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
// the entity has to be in a loaded chunk for physics to work
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: 0.,
|
||||
y: 70.,
|
||||
z: 0.,
|
||||
},
|
||||
azalea_registry::EntityKind::Zombie,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
{
|
||||
let entity_pos = *app.world_mut().get::<Position>(entity).unwrap();
|
||||
// y should start at 70
|
||||
assert_eq!(entity_pos.y, 70.);
|
||||
}
|
||||
app.update();
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
{
|
||||
let entity_pos = *app.world_mut().get::<Position>(entity).unwrap();
|
||||
// delta is applied before gravity, so the first tick only sets the delta
|
||||
assert_eq!(entity_pos.y, 70.);
|
||||
let entity_physics = app.world_mut().get::<Physics>(entity).unwrap();
|
||||
assert!(entity_physics.velocity.y < 0.);
|
||||
}
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
{
|
||||
let entity_pos = *app.world_mut().get::<Position>(entity).unwrap();
|
||||
// the second tick applies the delta to the position, so now it should go down
|
||||
assert!(
|
||||
entity_pos.y < 70.,
|
||||
"Entity y ({}) didn't go down after physics steps",
|
||||
entity_pos.y
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: 70.,
|
||||
z: 0.5,
|
||||
},
|
||||
azalea_registry::EntityKind::Player,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
let block_state = partial_world.chunks.set_block_state(
|
||||
&BlockPos { x: 0, y: 69, z: 0 },
|
||||
azalea_registry::Block::Stone.into(),
|
||||
&world_lock.write().chunks,
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
app.update();
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
{
|
||||
let entity_pos = *app.world_mut().get::<Position>(entity).unwrap();
|
||||
// delta will change, but it won't move until next tick
|
||||
assert_eq!(entity_pos.y, 70.);
|
||||
let entity_physics = app.world_mut().get::<Physics>(entity).unwrap();
|
||||
assert!(entity_physics.velocity.y < 0.);
|
||||
}
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
{
|
||||
let entity_pos = *app.world_mut().get::<Position>(entity).unwrap();
|
||||
// the second tick applies the delta to the position, but it also does collision
|
||||
assert_eq!(entity_pos.y, 70.);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_slab_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: 71.,
|
||||
z: 0.5,
|
||||
},
|
||||
azalea_registry::EntityKind::Player,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
let block_state = partial_world.chunks.set_block_state(
|
||||
&BlockPos { x: 0, y: 69, z: 0 },
|
||||
azalea_block::blocks::StoneSlab {
|
||||
kind: azalea_block::properties::Type::Bottom,
|
||||
waterlogged: false,
|
||||
}
|
||||
.into(),
|
||||
&world_lock.write().chunks,
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
// do a few steps so we fall on the slab
|
||||
for _ in 0..20 {
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
}
|
||||
let entity_pos = app.world_mut().get::<Position>(entity).unwrap();
|
||||
assert_eq!(entity_pos.y, 69.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_top_slab_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: 71.,
|
||||
z: 0.5,
|
||||
},
|
||||
azalea_registry::EntityKind::Player,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
let block_state = world_lock.write().chunks.set_block_state(
|
||||
&BlockPos { x: 0, y: 69, z: 0 },
|
||||
azalea_block::blocks::StoneSlab {
|
||||
kind: azalea_block::properties::Type::Top,
|
||||
waterlogged: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
// do a few steps so we fall on the slab
|
||||
for _ in 0..20 {
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
}
|
||||
let entity_pos = app.world_mut().get::<Position>(entity).unwrap();
|
||||
assert_eq!(entity_pos.y, 70.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weird_wall_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: 73.,
|
||||
z: 0.5,
|
||||
},
|
||||
azalea_registry::EntityKind::Player,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
let block_state = world_lock.write().chunks.set_block_state(
|
||||
&BlockPos { x: 0, y: 69, z: 0 },
|
||||
azalea_block::blocks::CobblestoneWall {
|
||||
east: azalea_block::properties::WallEast::Low,
|
||||
north: azalea_block::properties::WallNorth::Low,
|
||||
south: azalea_block::properties::WallSouth::Low,
|
||||
west: azalea_block::properties::WallWest::Low,
|
||||
up: false,
|
||||
waterlogged: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
// do a few steps so we fall on the wall
|
||||
for _ in 0..20 {
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
}
|
||||
|
||||
let entity_pos = app.world_mut().get::<Position>(entity).unwrap();
|
||||
assert_eq!(entity_pos.y, 70.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_coordinates_weird_wall_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: -1, z: -1 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: -7.5,
|
||||
y: 73.,
|
||||
z: -7.5,
|
||||
},
|
||||
azalea_registry::EntityKind::Player,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
let block_state = world_lock.write().chunks.set_block_state(
|
||||
&BlockPos {
|
||||
x: -8,
|
||||
y: 69,
|
||||
z: -8,
|
||||
},
|
||||
azalea_block::blocks::CobblestoneWall {
|
||||
east: azalea_block::properties::WallEast::Low,
|
||||
north: azalea_block::properties::WallNorth::Low,
|
||||
south: azalea_block::properties::WallSouth::Low,
|
||||
west: azalea_block::properties::WallWest::Low,
|
||||
up: false,
|
||||
waterlogged: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
// do a few steps so we fall on the wall
|
||||
for _ in 0..20 {
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
}
|
||||
|
||||
let entity_pos = app.world_mut().get::<Position>(entity).unwrap();
|
||||
assert_eq!(entity_pos.y, 70.5);
|
||||
}
|
||||
}
|
||||
|
|
299
azalea-physics/src/travel.rs
Normal file
299
azalea-physics/src/travel.rs
Normal file
|
@ -0,0 +1,299 @@
|
|||
use azalea_block::{Block, BlockState};
|
||||
use azalea_core::{aabb::AABB, position::Vec3};
|
||||
use azalea_entity::{
|
||||
metadata::Sprinting, move_relative, Attributes, InLoadedChunk, Jumping, LocalEntity,
|
||||
LookDirection, OnClimbable, Physics, Pose, Position,
|
||||
};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
use crate::{
|
||||
collision::{move_colliding, MoverType},
|
||||
get_block_pos_below_that_affects_movement, handle_relative_friction_and_calculate_movement,
|
||||
HandleRelativeFrictionAndCalculateMovementOpts,
|
||||
};
|
||||
|
||||
/// Move the entity with the given acceleration while handling friction,
|
||||
/// gravity, collisions, and some other stuff.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn travel(
|
||||
mut query: Query<
|
||||
(
|
||||
&mut Physics,
|
||||
&mut LookDirection,
|
||||
&mut Position,
|
||||
Option<&Sprinting>,
|
||||
Option<&Pose>,
|
||||
&Attributes,
|
||||
&InstanceName,
|
||||
&OnClimbable,
|
||||
&Jumping,
|
||||
),
|
||||
(With<LocalEntity>, With<InLoadedChunk>),
|
||||
>,
|
||||
instance_container: Res<InstanceContainer>,
|
||||
) {
|
||||
for (
|
||||
mut physics,
|
||||
direction,
|
||||
position,
|
||||
sprinting,
|
||||
pose,
|
||||
attributes,
|
||||
world_name,
|
||||
on_climbable,
|
||||
jumping,
|
||||
) in &mut query
|
||||
{
|
||||
let Some(world_lock) = instance_container.get(world_name) else {
|
||||
continue;
|
||||
};
|
||||
let world = world_lock.read();
|
||||
|
||||
let sprinting = *sprinting.unwrap_or(&Sprinting(false));
|
||||
|
||||
// TODO: elytras
|
||||
|
||||
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,
|
||||
&world,
|
||||
);
|
||||
} else {
|
||||
travel_in_air(
|
||||
&mut physics,
|
||||
&direction,
|
||||
position,
|
||||
&attributes,
|
||||
sprinting,
|
||||
&on_climbable,
|
||||
pose,
|
||||
&jumping,
|
||||
&world,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The usual movement when we're not in water or using an elytra.
|
||||
fn travel_in_air(
|
||||
physics: &mut Physics,
|
||||
direction: &LookDirection,
|
||||
position: Mut<Position>,
|
||||
attributes: &Attributes,
|
||||
sprinting: Sprinting,
|
||||
on_climbable: &OnClimbable,
|
||||
pose: Option<&Pose>,
|
||||
jumping: &Jumping,
|
||||
world: &Instance,
|
||||
) {
|
||||
let gravity = get_effective_gravity();
|
||||
|
||||
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<dyn Block> = 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn travel_in_fluid(
|
||||
physics: &mut Physics,
|
||||
direction: &LookDirection,
|
||||
mut position: Mut<Position>,
|
||||
attributes: &Attributes,
|
||||
sprinting: Sprinting,
|
||||
on_climbable: &OnClimbable,
|
||||
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,
|
||||
);
|
||||
|
||||
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 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;
|
||||
// }
|
||||
|
||||
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 *= 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
|
||||
}
|
365
azalea-physics/tests/physics.rs
Normal file
365
azalea-physics/tests/physics.rs
Normal file
|
@ -0,0 +1,365 @@
|
|||
use azalea_core::{
|
||||
position::{BlockPos, ChunkPos, Vec3},
|
||||
resource_location::ResourceLocation,
|
||||
tick::GameTick,
|
||||
};
|
||||
use azalea_entity::{EntityBundle, EntityPlugin, LocalEntity, Physics, Position};
|
||||
use azalea_physics::PhysicsPlugin;
|
||||
use azalea_world::{Chunk, InstanceContainer, MinecraftEntityId, PartialInstance};
|
||||
use bevy_app::App;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// You need an app to spawn entities in the world and do updates.
|
||||
fn make_test_app() -> App {
|
||||
let mut app = App::new();
|
||||
app.add_plugins((PhysicsPlugin, EntityPlugin))
|
||||
.init_resource::<InstanceContainer>();
|
||||
app
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gravity() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
// the entity has to be in a loaded chunk for physics to work
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: 0.,
|
||||
y: 70.,
|
||||
z: 0.,
|
||||
},
|
||||
azalea_registry::EntityKind::Zombie,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
{
|
||||
let entity_pos = *app.world_mut().get::<Position>(entity).unwrap();
|
||||
// y should start at 70
|
||||
assert_eq!(entity_pos.y, 70.);
|
||||
}
|
||||
app.update();
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
{
|
||||
let entity_pos = *app.world_mut().get::<Position>(entity).unwrap();
|
||||
// delta is applied before gravity, so the first tick only sets the delta
|
||||
assert_eq!(entity_pos.y, 70.);
|
||||
let entity_physics = app.world_mut().get::<Physics>(entity).unwrap();
|
||||
assert!(entity_physics.velocity.y < 0.);
|
||||
}
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
{
|
||||
let entity_pos = *app.world_mut().get::<Position>(entity).unwrap();
|
||||
// the second tick applies the delta to the position, so now it should go down
|
||||
assert!(
|
||||
entity_pos.y < 70.,
|
||||
"Entity y ({}) didn't go down after physics steps",
|
||||
entity_pos.y
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: 70.,
|
||||
z: 0.5,
|
||||
},
|
||||
azalea_registry::EntityKind::Player,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
let block_state = partial_world.chunks.set_block_state(
|
||||
&BlockPos { x: 0, y: 69, z: 0 },
|
||||
azalea_registry::Block::Stone.into(),
|
||||
&world_lock.write().chunks,
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
app.update();
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
{
|
||||
let entity_pos = *app.world_mut().get::<Position>(entity).unwrap();
|
||||
// delta will change, but it won't move until next tick
|
||||
assert_eq!(entity_pos.y, 70.);
|
||||
let entity_physics = app.world_mut().get::<Physics>(entity).unwrap();
|
||||
assert!(entity_physics.velocity.y < 0.);
|
||||
}
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
{
|
||||
let entity_pos = *app.world_mut().get::<Position>(entity).unwrap();
|
||||
// the second tick applies the delta to the position, but it also does collision
|
||||
assert_eq!(entity_pos.y, 70.);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_slab_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: 71.,
|
||||
z: 0.5,
|
||||
},
|
||||
azalea_registry::EntityKind::Player,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
let block_state = partial_world.chunks.set_block_state(
|
||||
&BlockPos { x: 0, y: 69, z: 0 },
|
||||
azalea_block::blocks::StoneSlab {
|
||||
kind: azalea_block::properties::Type::Bottom,
|
||||
waterlogged: false,
|
||||
}
|
||||
.into(),
|
||||
&world_lock.write().chunks,
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
// do a few steps so we fall on the slab
|
||||
for _ in 0..20 {
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
}
|
||||
let entity_pos = app.world_mut().get::<Position>(entity).unwrap();
|
||||
assert_eq!(entity_pos.y, 69.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_top_slab_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: 71.,
|
||||
z: 0.5,
|
||||
},
|
||||
azalea_registry::EntityKind::Player,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
let block_state = world_lock.write().chunks.set_block_state(
|
||||
&BlockPos { x: 0, y: 69, z: 0 },
|
||||
azalea_block::blocks::StoneSlab {
|
||||
kind: azalea_block::properties::Type::Top,
|
||||
waterlogged: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
// do a few steps so we fall on the slab
|
||||
for _ in 0..20 {
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
}
|
||||
let entity_pos = app.world_mut().get::<Position>(entity).unwrap();
|
||||
assert_eq!(entity_pos.y, 70.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weird_wall_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: 73.,
|
||||
z: 0.5,
|
||||
},
|
||||
azalea_registry::EntityKind::Player,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
let block_state = world_lock.write().chunks.set_block_state(
|
||||
&BlockPos { x: 0, y: 69, z: 0 },
|
||||
azalea_block::blocks::CobblestoneWall {
|
||||
east: azalea_block::properties::WallEast::Low,
|
||||
north: azalea_block::properties::WallNorth::Low,
|
||||
south: azalea_block::properties::WallSouth::Low,
|
||||
west: azalea_block::properties::WallWest::Low,
|
||||
up: false,
|
||||
waterlogged: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
// do a few steps so we fall on the wall
|
||||
for _ in 0..20 {
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
}
|
||||
|
||||
let entity_pos = app.world_mut().get::<Position>(entity).unwrap();
|
||||
assert_eq!(entity_pos.y, 70.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_coordinates_weird_wall_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: -1, z: -1 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: -7.5,
|
||||
y: 73.,
|
||||
z: -7.5,
|
||||
},
|
||||
azalea_registry::EntityKind::Player,
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
LocalEntity,
|
||||
))
|
||||
.id();
|
||||
let block_state = world_lock.write().chunks.set_block_state(
|
||||
&BlockPos {
|
||||
x: -8,
|
||||
y: 69,
|
||||
z: -8,
|
||||
},
|
||||
azalea_block::blocks::CobblestoneWall {
|
||||
east: azalea_block::properties::WallEast::Low,
|
||||
north: azalea_block::properties::WallNorth::Low,
|
||||
south: azalea_block::properties::WallSouth::Low,
|
||||
west: azalea_block::properties::WallWest::Low,
|
||||
up: false,
|
||||
waterlogged: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
// do a few steps so we fall on the wall
|
||||
for _ in 0..20 {
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
app.update();
|
||||
}
|
||||
|
||||
let entity_pos = app.world_mut().get::<Position>(entity).unwrap();
|
||||
assert_eq!(entity_pos.y, 70.5);
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
use azalea_block::{Block, BlockState};
|
||||
use azalea_block::{fluid_state::FluidKind, Block, BlockState};
|
||||
use azalea_client::{inventory::Inventory, Client};
|
||||
use azalea_entity::{FluidOnEyes, Physics};
|
||||
use azalea_inventory::{components, ItemStack, Menu};
|
||||
use azalea_registry::Fluid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BestToolResult {
|
||||
|
@ -34,7 +33,12 @@ pub fn best_tool_in_hotbar_for_block(block: BlockState, menu: &Menu) -> BestTool
|
|||
let mut physics = Physics::default();
|
||||
physics.set_on_ground(true);
|
||||
|
||||
accurate_best_tool_in_hotbar_for_block(block, menu, &physics, &FluidOnEyes::new(Fluid::Empty))
|
||||
accurate_best_tool_in_hotbar_for_block(
|
||||
block,
|
||||
menu,
|
||||
&physics,
|
||||
&FluidOnEyes::new(FluidKind::Empty),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn accurate_best_tool_in_hotbar_for_block(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue