Add Entity physics
Thanks to akashbeh for his work https://github.com/Pumpkin-MC/Pumpkin/pull/824 Co-Authored-By: akashbeh <112662680+akashbeh@users.noreply.github.com>
This commit is contained in:
parent
e75e52cd37
commit
3f2484672c
@ -1,6 +1,6 @@
|
||||
use crate::block_properties::{Axis, Facing, HorizontalAxis, HorizontalFacing};
|
||||
use pumpkin_util::{
|
||||
math::vector3::Vector3,
|
||||
math::vector3::{Axis as MathAxis, Vector3},
|
||||
random::{RandomGenerator, RandomImpl},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
@ -17,6 +17,25 @@ pub enum BlockDirection {
|
||||
East,
|
||||
}
|
||||
|
||||
impl From<MathAxis> for Axis {
|
||||
fn from(a: MathAxis) -> Self {
|
||||
match a {
|
||||
MathAxis::X => Self::X,
|
||||
MathAxis::Y => Self::Y,
|
||||
MathAxis::Z => Self::Z,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Axis> for MathAxis {
|
||||
fn from(a: Axis) -> MathAxis {
|
||||
match a {
|
||||
Axis::X => Self::X,
|
||||
Axis::Y => Self::Y,
|
||||
Axis::Z => Self::Z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InvalidBlockFace;
|
||||
|
||||
impl TryFrom<i32> for BlockDirection {
|
||||
@ -94,6 +113,10 @@ impl BlockDirection {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn positive(&self) -> bool {
|
||||
matches!(self, Self::South | Self::East | Self::Up)
|
||||
}
|
||||
|
||||
pub fn all() -> [BlockDirection; 6] {
|
||||
[
|
||||
BlockDirection::Down,
|
||||
|
||||
@ -11,6 +11,13 @@ impl CollisionShape {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn to_bounding_box(&self) -> BoundingBox {
|
||||
BoundingBox {
|
||||
min: self.min,
|
||||
max: self.max,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(min: Vector3<f64>, max: Vector3<f64>) -> Self {
|
||||
Self { min, max }
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use crate::math::{vector2::Vector2, vector3::Axis};
|
||||
|
||||
use super::{position::BlockPos, vector3::Vector3};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@ -6,6 +8,39 @@ pub struct BoundingBox {
|
||||
pub max: Vector3<f64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
struct BoundingPlane {
|
||||
pub min: Vector2<f64>,
|
||||
pub max: Vector2<f64>,
|
||||
}
|
||||
|
||||
impl BoundingPlane {
|
||||
pub fn intersects(&self, other: &Self) -> bool {
|
||||
self.min.x < other.max.x
|
||||
&& self.max.x > other.min.x
|
||||
&& self.min.y < other.max.y
|
||||
&& self.max.y > other.min.y
|
||||
}
|
||||
|
||||
// Projecting a 3D box into 2D
|
||||
pub fn from_box(bounding_box: &BoundingBox, excluded: Axis) -> Self {
|
||||
let [axis1, axis2] = Axis::excluding(excluded);
|
||||
|
||||
Self {
|
||||
min: Vector2::new(
|
||||
bounding_box.get_side(false).get_axis(axis1),
|
||||
bounding_box.get_side(false).get_axis(axis2),
|
||||
),
|
||||
|
||||
max: Vector2::new(
|
||||
bounding_box.get_side(true).get_axis(axis1),
|
||||
bounding_box.get_side(true).get_axis(axis2),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingBox {
|
||||
pub fn new_default(size: &EntityDimensions) -> Self {
|
||||
Self::new_from_pos(0., 0., 0., size)
|
||||
@ -56,6 +91,88 @@ impl BoundingBox {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_side(&self, max: bool) -> Vector3<f64> {
|
||||
if max { self.max } else { self.min }
|
||||
}
|
||||
|
||||
pub fn calculate_collision_time(
|
||||
&self,
|
||||
other: &Self,
|
||||
movement: Vector3<f64>,
|
||||
axis: Axis,
|
||||
max_time: f64, // Start with 1.0
|
||||
) -> Option<f64> {
|
||||
let movement_on_axis = movement.get_axis(axis);
|
||||
|
||||
if movement_on_axis == 0.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let move_positive = movement_on_axis.is_sign_positive();
|
||||
|
||||
let self_plane_const = self.get_side(move_positive).get_axis(axis);
|
||||
|
||||
let other_plane_const = other.get_side(!move_positive).get_axis(axis);
|
||||
|
||||
let collision_time = (other_plane_const - self_plane_const) / movement_on_axis;
|
||||
|
||||
if collision_time < 0.0 || collision_time >= max_time {
|
||||
return None;
|
||||
}
|
||||
|
||||
let self_moved = self.shift(movement * collision_time);
|
||||
|
||||
let self_plane_moved = BoundingPlane::from_box(&self_moved, axis);
|
||||
|
||||
let other_plane = BoundingPlane::from_box(other, axis);
|
||||
|
||||
if !self_plane_moved.intersects(&other_plane) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(collision_time)
|
||||
}
|
||||
|
||||
pub fn min_block_pos(&self) -> BlockPos {
|
||||
BlockPos::floored_v(self.min)
|
||||
}
|
||||
|
||||
pub fn max_block_pos(&self) -> BlockPos {
|
||||
BlockPos::ceiled_v(self.max)
|
||||
}
|
||||
|
||||
pub fn shift(&self, delta: Vector3<f64>) -> Self {
|
||||
Self {
|
||||
min: self.min + delta,
|
||||
|
||||
max: self.max + delta,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stretch(&self, other: Vector3<f64>) -> Self {
|
||||
let mut new = *self;
|
||||
|
||||
if other.x < 0.0 {
|
||||
new.min.x += other.x;
|
||||
} else if other.x > 0.0 {
|
||||
new.max.x += other.x;
|
||||
}
|
||||
|
||||
if other.y < 0.0 {
|
||||
new.min.y += other.y;
|
||||
} else if other.y > 0.0 {
|
||||
new.max.y += other.y;
|
||||
}
|
||||
|
||||
if other.z < 0.0 {
|
||||
new.min.z += other.z;
|
||||
} else if other.z > 0.0 {
|
||||
new.max.z += other.z;
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
pub fn from_block_raw(position: &BlockPos) -> Self {
|
||||
let position = position.0;
|
||||
Self {
|
||||
|
||||
@ -270,6 +270,22 @@ impl BlockPos {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn floored_v(pos: Vector3<f64>) -> Self {
|
||||
Self(Vector3::new(
|
||||
pos.x.floor() as i32,
|
||||
pos.y.floor() as i32,
|
||||
pos.z.floor() as i32,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn ceiled_v(pos: Vector3<f64>) -> Self {
|
||||
Self(Vector3::new(
|
||||
pos.x.ceil() as i32,
|
||||
pos.y.ceil() as i32,
|
||||
pos.z.ceil() as i32,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn to_f64(&self) -> Vector3<f64> {
|
||||
Vector3::new(
|
||||
self.0.x as f64 + 0.5,
|
||||
|
||||
@ -13,6 +13,54 @@ pub struct Vector3<T> {
|
||||
pub z: T,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
|
||||
pub enum Axis {
|
||||
X,
|
||||
|
||||
Y,
|
||||
|
||||
Z,
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
pub fn all() -> [Self; 3] {
|
||||
[Self::Y, Self::X, Self::Z]
|
||||
}
|
||||
|
||||
pub fn horizontal() -> [Self; 2] {
|
||||
[Self::X, Self::Z]
|
||||
}
|
||||
|
||||
pub fn excluding(axis: Self) -> [Self; 2] {
|
||||
match axis {
|
||||
Self::X => [Self::Y, Self::Z],
|
||||
|
||||
Self::Y => [Self::X, Self::Z],
|
||||
|
||||
Self::Z => [Self::X, Self::Y],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Vector3<T> {
|
||||
pub fn get_axis(&self, a: Axis) -> T {
|
||||
match a {
|
||||
Axis::X => self.x,
|
||||
Axis::Y => self.y,
|
||||
Axis::Z => self.z,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_axis(&mut self, a: Axis, value: T) {
|
||||
match a {
|
||||
Axis::X => self.x = value,
|
||||
Axis::Y => self.y = value,
|
||||
Axis::Z => self.z = value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Math + PartialOrd + Copy> Vector3<T> {
|
||||
pub const fn new(x: T, y: T, z: T) -> Self {
|
||||
Vector3 { x, y, z }
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use pumpkin_protocol::java::client::play::CUpdateEntityPos;
|
||||
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
|
||||
|
||||
use crate::entity::living::LivingEntity;
|
||||
@ -77,25 +74,8 @@ impl Navigator {
|
||||
goal.current_progress += best_move.normalize() * goal.speed;
|
||||
|
||||
// Now let's move
|
||||
entity.set_pos(goal.current_progress);
|
||||
let pos = entity.entity.pos.load();
|
||||
let last_pos = entity.last_pos.load();
|
||||
|
||||
entity
|
||||
.entity
|
||||
.world
|
||||
.read()
|
||||
.await
|
||||
.broadcast_packet_all(&CUpdateEntityPos::new(
|
||||
entity.entity.entity_id.into(),
|
||||
Vector3::new(
|
||||
pos.x.mul_add(4096.0, -(last_pos.x * 4096.0)) as i16,
|
||||
pos.y.mul_add(4096.0, -(last_pos.y * 4096.0)) as i16,
|
||||
pos.z.mul_add(4096.0, -(last_pos.z * 4096.0)) as i16,
|
||||
),
|
||||
entity.entity.on_ground.load(Ordering::Relaxed),
|
||||
))
|
||||
.await;
|
||||
entity.entity.set_pos(goal.current_progress);
|
||||
entity.entity.send_pos().await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
use core::f32;
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU32, Ordering::Relaxed},
|
||||
atomic::{
|
||||
AtomicBool, AtomicU32,
|
||||
Ordering::{self},
|
||||
},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
@ -27,6 +30,8 @@ pub struct ItemEntity {
|
||||
item_stack: Mutex<ItemStack>,
|
||||
pickup_delay: Mutex<u8>,
|
||||
health: AtomicCell<f32>,
|
||||
never_despawn: AtomicBool,
|
||||
never_pickup: AtomicBool,
|
||||
}
|
||||
|
||||
impl ItemEntity {
|
||||
@ -45,6 +50,8 @@ impl ItemEntity {
|
||||
item_age: AtomicU32::new(0),
|
||||
pickup_delay: Mutex::new(10), // Vanilla pickup delay is 10 ticks
|
||||
health: AtomicCell::new(5.0),
|
||||
never_despawn: AtomicBool::new(false),
|
||||
never_pickup: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +69,127 @@ impl ItemEntity {
|
||||
item_age: AtomicU32::new(0),
|
||||
pickup_delay: Mutex::new(pickup_delay), // Vanilla pickup delay is 10 ticks
|
||||
health: AtomicCell::new(5.0),
|
||||
never_despawn: AtomicBool::new(false),
|
||||
never_pickup: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
async fn can_merge(&self) -> bool {
|
||||
if self.never_pickup.load(Ordering::Relaxed) || self.entity.removed.load(Ordering::Relaxed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let item_stack = self.item_stack.lock().await;
|
||||
|
||||
item_stack.item_count < item_stack.get_max_stack_size()
|
||||
}
|
||||
|
||||
async fn try_merge(&self) {
|
||||
let bounding_box = self.entity.bounding_box.load().expand(0.5, 0.0, 0.5);
|
||||
|
||||
let items: Vec<_> = self
|
||||
.entity
|
||||
.world
|
||||
.read()
|
||||
.await
|
||||
.entities
|
||||
.read()
|
||||
.await
|
||||
.values()
|
||||
.filter_map(|entity: &Arc<dyn EntityBase>| {
|
||||
entity.clone().get_item_entity().filter(|item| {
|
||||
item.entity.entity_id != self.entity.entity_id
|
||||
&& !item.never_despawn.load(Ordering::Relaxed)
|
||||
&& item.entity.bounding_box.load().intersects(&bounding_box)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
for item in items {
|
||||
if item.can_merge().await {
|
||||
self.try_merge_with(&item).await;
|
||||
|
||||
if self.entity.removed.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_merge_with(&self, other: &Self) {
|
||||
// Check if merge is possible
|
||||
|
||||
let self_stack = self.item_stack.lock().await;
|
||||
|
||||
let other_stack = other.item_stack.lock().await;
|
||||
|
||||
if !self_stack.are_equal(&other_stack)
|
||||
|| self_stack.item_count + other_stack.item_count > self_stack.get_max_stack_size()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let (target, mut stack1, source, mut stack2) =
|
||||
if other_stack.item_count < self_stack.item_count {
|
||||
(self, self_stack, other, other_stack)
|
||||
} else {
|
||||
(other, other_stack, self, self_stack)
|
||||
};
|
||||
|
||||
// Vanilla code adds a .min(64). Not needed with Vanilla item data
|
||||
|
||||
let max_size = stack1.get_max_stack_size();
|
||||
|
||||
let j = stack2.item_count.min(max_size - stack1.item_count);
|
||||
|
||||
stack1.increment(j);
|
||||
|
||||
stack2.decrement(j);
|
||||
|
||||
let empty1 = stack1.item_count == 0;
|
||||
|
||||
let empty2 = stack2.item_count == 0;
|
||||
|
||||
drop(stack1);
|
||||
|
||||
drop(stack2);
|
||||
|
||||
let never_despawn = source.never_despawn.load(Ordering::Relaxed);
|
||||
|
||||
target.never_despawn.store(never_despawn, Ordering::Relaxed);
|
||||
|
||||
if !never_despawn {
|
||||
let age = target
|
||||
.item_age
|
||||
.load(Ordering::Relaxed)
|
||||
.min(source.item_age.load(Ordering::Relaxed));
|
||||
|
||||
target.item_age.store(age, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let never_pickup = source.never_pickup.load(Ordering::Relaxed);
|
||||
|
||||
target.never_pickup.store(never_pickup, Ordering::Relaxed);
|
||||
|
||||
if !never_pickup {
|
||||
let mut target_delay = target.pickup_delay.lock().await;
|
||||
|
||||
let delay = (*target_delay).max(*source.pickup_delay.lock().await);
|
||||
|
||||
*target_delay = delay;
|
||||
}
|
||||
|
||||
if empty1 {
|
||||
target.entity.remove().await;
|
||||
} else {
|
||||
target.init_data_tracker().await;
|
||||
}
|
||||
|
||||
if empty2 {
|
||||
source.entity.remove().await;
|
||||
} else {
|
||||
source.init_data_tracker().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,15 +198,147 @@ impl ItemEntity {
|
||||
impl EntityBase for ItemEntity {
|
||||
async fn tick(&self, caller: Arc<dyn EntityBase>, server: &Server) {
|
||||
let entity = &self.entity;
|
||||
entity.tick(caller, server).await;
|
||||
entity.tick(caller.clone(), server).await;
|
||||
{
|
||||
let mut delay = self.pickup_delay.lock().await;
|
||||
*delay = delay.saturating_sub(1);
|
||||
};
|
||||
|
||||
let age = self.item_age.fetch_add(1, Relaxed);
|
||||
let original_velo = entity.velocity.load();
|
||||
|
||||
let mut velo = original_velo;
|
||||
|
||||
if entity.touching_water.load(Ordering::SeqCst) && entity.water_height.load() > 0.1 {
|
||||
velo.x *= 0.99;
|
||||
|
||||
velo.z *= 0.99;
|
||||
|
||||
if velo.y < 0.06 {
|
||||
velo.y += 5.0e-4;
|
||||
}
|
||||
} else if entity.touching_lava.load(Ordering::SeqCst) && entity.lava_height.load() > 0.1 {
|
||||
velo.x *= 0.95;
|
||||
|
||||
velo.z *= 0.95;
|
||||
|
||||
if velo.y < 0.06 {
|
||||
velo.y += 5.0e-4;
|
||||
}
|
||||
} else {
|
||||
velo.y -= self.get_gravity();
|
||||
}
|
||||
|
||||
entity.velocity.store(velo);
|
||||
|
||||
let pos = entity.pos.load();
|
||||
|
||||
let bounding_box = entity.bounding_box.load();
|
||||
|
||||
let no_clip = !self
|
||||
.entity
|
||||
.world
|
||||
.read()
|
||||
.await
|
||||
.is_space_empty(bounding_box.expand(-1.0e-7, -1.0e-7, -1.0e-7))
|
||||
.await;
|
||||
|
||||
entity.no_clip.store(no_clip, Ordering::Relaxed);
|
||||
|
||||
if no_clip {
|
||||
entity
|
||||
.push_out_of_blocks(Vector3::new(
|
||||
pos.x,
|
||||
f64::midpoint(bounding_box.min.y, bounding_box.max.y),
|
||||
pos.z,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
let mut velo = entity.velocity.load(); // In case push_out_of_blocks modifies it
|
||||
|
||||
let mut tick_move =
|
||||
!entity.on_ground.load(Ordering::SeqCst) || velo.horizontal_length_squared() > 1.0e-5;
|
||||
|
||||
if !tick_move {
|
||||
let Ok(item_age) = i32::try_from(self.item_age.load(Ordering::Relaxed)) else {
|
||||
entity.remove().await;
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
tick_move = (item_age + entity.entity_id) % 4 == 0;
|
||||
}
|
||||
|
||||
if tick_move {
|
||||
entity.move_entity(caller.clone(), velo).await;
|
||||
|
||||
entity.tick_block_collisions(&caller, server).await;
|
||||
|
||||
let mut friction = 0.98;
|
||||
|
||||
let on_ground = entity.on_ground.load(Ordering::SeqCst);
|
||||
|
||||
if on_ground {
|
||||
let block_affecting_velo = entity.get_block_with_y_offset(0.999_999).await.1;
|
||||
|
||||
friction *= f64::from(block_affecting_velo.slipperiness);
|
||||
}
|
||||
|
||||
velo = velo.multiply(friction, 0.98, friction);
|
||||
|
||||
if on_ground && velo.y < 0.0 {
|
||||
velo = velo.multiply(1.0, -0.5, 1.0);
|
||||
}
|
||||
|
||||
entity.velocity.store(velo);
|
||||
}
|
||||
|
||||
if !self.never_despawn.load(Ordering::Relaxed) {
|
||||
let age = self.item_age.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
|
||||
if age >= 6000 {
|
||||
entity.remove().await;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let n = if entity
|
||||
.last_pos
|
||||
.load()
|
||||
.sub(&entity.pos.load())
|
||||
.length_squared()
|
||||
== 0.0
|
||||
{
|
||||
40
|
||||
} else {
|
||||
2
|
||||
};
|
||||
|
||||
if age % n == 0 && self.can_merge().await {
|
||||
self.try_merge().await;
|
||||
}
|
||||
}
|
||||
|
||||
entity.update_fluid_state(&caller).await;
|
||||
|
||||
let velocity_dirty = entity.velocity_dirty.swap(false, Ordering::SeqCst)
|
||||
|
||||
|
||||
|| entity.touching_water.load(Ordering::SeqCst)
|
||||
|
||||
|
||||
|| entity.touching_lava.load(Ordering::SeqCst)
|
||||
|
||||
|
||||
//|| entity.velocity.load().sub(&original_velo).length_squared() > 0.01;
|
||||
|
||||
|
||||
|| entity.velocity.load() != original_velo;
|
||||
|
||||
if velocity_dirty {
|
||||
entity.send_pos_rot().await;
|
||||
|
||||
entity.send_velocity().await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,4 +418,12 @@ impl EntityBase for ItemEntity {
|
||||
fn get_living_entity(&self) -> Option<&LivingEntity> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_item_entity(self: Arc<Self>) -> Option<Arc<ItemEntity>> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn get_gravity(&self) -> f64 {
|
||||
0.04
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use pumpkin_data::potion::Effect;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::{AtomicU8, Ordering::Relaxed};
|
||||
@ -37,8 +38,6 @@ use tokio::sync::Mutex;
|
||||
pub struct LivingEntity {
|
||||
/// The underlying entity object, providing basic entity information and functionality.
|
||||
pub entity: Entity,
|
||||
/// The last known position of the entity.
|
||||
pub last_pos: AtomicCell<Vector3<f64>>,
|
||||
/// Tracks the remaining time until the entity can regenerate health.
|
||||
pub hurt_cooldown: AtomicI32,
|
||||
/// Stores the amount of damage the entity last received.
|
||||
@ -52,6 +51,20 @@ pub struct LivingEntity {
|
||||
pub fall_distance: AtomicCell<f32>,
|
||||
pub active_effects: Mutex<HashMap<&'static StatusEffect, Effect>>,
|
||||
pub entity_equipment: Arc<Mutex<EntityEquipment>>,
|
||||
pub movement_input: AtomicCell<Vector3<f64>>,
|
||||
|
||||
pub movement_speed: AtomicCell<f64>,
|
||||
|
||||
pub jumping: AtomicBool,
|
||||
|
||||
pub jumping_cooldown: AtomicU8,
|
||||
|
||||
pub climbing: AtomicBool,
|
||||
|
||||
/// The position where the entity was last climbing, used for death messages
|
||||
pub climbing_pos: AtomicCell<Option<BlockPos>>,
|
||||
|
||||
_water_movement_speed_multiplier: f32,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -63,10 +76,18 @@ pub trait LivingEntityTrait: EntityBase {
|
||||
|
||||
impl LivingEntity {
|
||||
pub fn new(entity: Entity) -> Self {
|
||||
let pos = entity.pos.load();
|
||||
let water_movement_speed_multiplier = if entity.entity_type == &EntityType::POLAR_BEAR {
|
||||
0.98
|
||||
} else if entity.entity_type == &EntityType::SKELETON_HORSE {
|
||||
0.96
|
||||
} else {
|
||||
0.8
|
||||
};
|
||||
|
||||
// TODO: Extract default MOVEMENT_SPEED Entity Attribute
|
||||
let default_movement_speed = 0.25;
|
||||
Self {
|
||||
entity,
|
||||
last_pos: AtomicCell::new(pos),
|
||||
hurt_cooldown: AtomicI32::new(0),
|
||||
last_damage_taken: AtomicCell::new(0.0),
|
||||
health: AtomicCell::new(20.0),
|
||||
@ -75,6 +96,13 @@ impl LivingEntity {
|
||||
dead: AtomicBool::new(false),
|
||||
active_effects: Mutex::new(HashMap::new()),
|
||||
entity_equipment: Arc::new(Mutex::new(EntityEquipment::new())),
|
||||
jumping: AtomicBool::new(false),
|
||||
jumping_cooldown: AtomicU8::new(0),
|
||||
climbing: AtomicBool::new(false),
|
||||
climbing_pos: AtomicCell::new(None),
|
||||
movement_input: AtomicCell::new(Vector3::default()),
|
||||
movement_speed: AtomicCell::new(default_movement_speed),
|
||||
_water_movement_speed_multiplier: water_movement_speed_multiplier,
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,11 +142,6 @@ impl LivingEntity {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub fn set_pos(&self, position: Vector3<f64>) {
|
||||
self.last_pos.store(self.entity.pos.load());
|
||||
self.entity.set_pos(position);
|
||||
}
|
||||
|
||||
pub async fn heal(&self, additional_health: f32) {
|
||||
assert!(additional_health > 0.0);
|
||||
self.set_health(self.health.load() + additional_health)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use crate::entity::item::ItemEntity;
|
||||
use crate::world::World;
|
||||
use crate::{server::Server, world::portal::PortalManager};
|
||||
use async_trait::async_trait;
|
||||
@ -6,7 +7,10 @@ use core::f32;
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
use living::LivingEntity;
|
||||
use player::Player;
|
||||
use pumpkin_data::BlockState;
|
||||
use pumpkin_data::block_properties::Integer0To15;
|
||||
use pumpkin_data::fluid::Fluid;
|
||||
use pumpkin_data::{Block, BlockDirection};
|
||||
use pumpkin_data::{
|
||||
block_properties::{Facing, HorizontalFacing},
|
||||
damage::DamageType,
|
||||
@ -14,6 +18,7 @@ use pumpkin_data::{
|
||||
sound::{Sound, SoundCategory},
|
||||
};
|
||||
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
|
||||
use pumpkin_protocol::java::client::play::{CUpdateEntityPos, CUpdateEntityPosRot};
|
||||
use pumpkin_protocol::{
|
||||
codec::var_int::VarInt,
|
||||
java::client::play::{
|
||||
@ -23,6 +28,7 @@ use pumpkin_protocol::{
|
||||
ser::serializer::Serializer,
|
||||
};
|
||||
use pumpkin_registry::VanillaDimensionType;
|
||||
use pumpkin_util::math::vector3::Axis;
|
||||
use pumpkin_util::math::{
|
||||
boundingbox::{BoundingBox, EntityDimensions},
|
||||
get_section_cord,
|
||||
@ -34,6 +40,7 @@ use pumpkin_util::math::{
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::text::hover::HoverEvent;
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{
|
||||
@ -106,6 +113,14 @@ pub trait EntityBase: Send + Sync {
|
||||
.await;
|
||||
}
|
||||
|
||||
fn is_pushed_by_fluids(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn get_gravity(&self) -> f64 {
|
||||
0.0
|
||||
}
|
||||
|
||||
/// Returns if damage was successful or not
|
||||
async fn damage(&self, amount: f32, damage_type: DamageType) -> bool {
|
||||
self.damage_with_context(amount, damage_type, None, None, None)
|
||||
@ -138,6 +153,10 @@ pub trait EntityBase: Send + Sync {
|
||||
fn get_entity(&self) -> &Entity;
|
||||
fn get_living_entity(&self) -> Option<&LivingEntity>;
|
||||
|
||||
fn get_item_entity(self: Arc<Self>) -> Option<Arc<ItemEntity>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_player(&self) -> Option<&Player> {
|
||||
None
|
||||
}
|
||||
@ -224,8 +243,12 @@ pub struct Entity {
|
||||
pub world: Arc<RwLock<Arc<World>>>,
|
||||
/// The entity's current position in the world
|
||||
pub pos: AtomicCell<Vector3<f64>>,
|
||||
/// The last known position of the entity.
|
||||
pub last_pos: AtomicCell<Vector3<f64>>,
|
||||
/// The entity's position rounded to the nearest block coordinates
|
||||
pub block_pos: AtomicCell<BlockPos>,
|
||||
/// The block supporting the entity
|
||||
pub supporting_block_pos: AtomicCell<Option<BlockPos>>,
|
||||
/// The chunk coordinates of the entity's current position
|
||||
pub chunk_pos: AtomicCell<Vector2<i32>>,
|
||||
/// Indicates whether the entity is sneaking
|
||||
@ -236,8 +259,18 @@ pub struct Entity {
|
||||
pub fall_flying: AtomicBool,
|
||||
/// The entity's current velocity vector, aka knockback
|
||||
pub velocity: AtomicCell<Vector3<f64>>,
|
||||
/// Tracks a horizontal collision
|
||||
pub horizontal_collision: AtomicBool,
|
||||
/// Indicates whether the entity is on the ground (may not always be accurate).
|
||||
pub on_ground: AtomicBool,
|
||||
/// Indicates whether the entity is touching water
|
||||
pub touching_water: AtomicBool,
|
||||
/// Indicates the fluid height
|
||||
pub water_height: AtomicCell<f64>,
|
||||
/// Indicates whether the entity is touching lava
|
||||
pub touching_lava: AtomicBool,
|
||||
/// Indicates the fluid height
|
||||
pub lava_height: AtomicCell<f64>,
|
||||
/// The entity's yaw rotation (horizontal rotation) ← →
|
||||
pub yaw: AtomicCell<f32>,
|
||||
/// The entity's head yaw rotation (horizontal rotation of the head)
|
||||
@ -276,9 +309,16 @@ pub struct Entity {
|
||||
pub custom_name: Option<TextComponent>,
|
||||
/// Indicates whether the entity's custom name is visible
|
||||
pub custom_name_visible: bool,
|
||||
|
||||
/// The data send in the Entity Spawn packet
|
||||
pub data: AtomicI32,
|
||||
/// If true, the entity cannot collide with anything (e.g. spectator)
|
||||
pub no_clip: AtomicBool,
|
||||
/// Multiplies movement for one tick before being reset
|
||||
pub movement_multiplier: AtomicCell<Vector3<f64>>,
|
||||
/// Determines whether the entity's velocity needs to be sent
|
||||
pub velocity_dirty: AtomicBool,
|
||||
/// Set when an Entity is to be removed but could still be referenced
|
||||
pub removed: AtomicBool,
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
@ -303,8 +343,15 @@ impl Entity {
|
||||
entity_uuid,
|
||||
entity_type,
|
||||
on_ground: AtomicBool::new(false),
|
||||
touching_water: AtomicBool::new(false),
|
||||
water_height: AtomicCell::new(0.0),
|
||||
touching_lava: AtomicBool::new(false),
|
||||
lava_height: AtomicCell::new(0.0),
|
||||
horizontal_collision: AtomicBool::new(false),
|
||||
pos: AtomicCell::new(position),
|
||||
last_pos: AtomicCell::new(position),
|
||||
block_pos: AtomicCell::new(BlockPos(Vector3::new(floor_x, floor_y, floor_z))),
|
||||
supporting_block_pos: AtomicCell::new(None),
|
||||
chunk_pos: AtomicCell::new(Vector2::new(
|
||||
get_section_cord(floor_x),
|
||||
get_section_cord(floor_z),
|
||||
@ -341,11 +388,20 @@ impl Entity {
|
||||
portal_manager: Mutex::new(None),
|
||||
custom_name: None,
|
||||
custom_name_visible: false,
|
||||
no_clip: AtomicBool::new(false),
|
||||
movement_multiplier: AtomicCell::new(Vector3::default()),
|
||||
velocity_dirty: AtomicBool::new(true),
|
||||
removed: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_velocity(&self, velocity: Vector3<f64>) {
|
||||
self.velocity.store(velocity);
|
||||
self.send_velocity().await;
|
||||
}
|
||||
|
||||
pub async fn send_velocity(&self) {
|
||||
let velocity = self.velocity.load();
|
||||
self.world
|
||||
.read()
|
||||
.await
|
||||
@ -417,24 +473,42 @@ impl Entity {
|
||||
self.pitch.store(pitch);
|
||||
self.yaw.store(yaw);
|
||||
|
||||
self.send_rotation().await;
|
||||
}
|
||||
|
||||
pub async fn send_rotation(&self) {
|
||||
let yaw = self.yaw.load();
|
||||
let pitch = self.pitch.load();
|
||||
|
||||
// Broadcast the update packet.
|
||||
|
||||
// TODO: Do caching to only send the packet when needed.
|
||||
|
||||
let yaw = (yaw * 256.0 / 360.0).rem_euclid(256.0);
|
||||
|
||||
let yaw = (yaw * 256.0 / 360.0).rem_euclid(256.0) as u8;
|
||||
|
||||
let pitch = (pitch * 256.0 / 360.0).rem_euclid(256.0);
|
||||
|
||||
self.world
|
||||
.read()
|
||||
.await
|
||||
.broadcast_packet_all(&CUpdateEntityRot::new(
|
||||
self.entity_id.into(),
|
||||
yaw as u8,
|
||||
yaw,
|
||||
pitch as u8,
|
||||
self.on_ground.load(Relaxed),
|
||||
))
|
||||
.await;
|
||||
|
||||
self.send_head_rot(yaw).await;
|
||||
}
|
||||
|
||||
pub async fn send_head_rot(&self, head_yaw: u8) {
|
||||
self.world
|
||||
.read()
|
||||
.await
|
||||
.broadcast_packet_all(&CHeadRot::new(self.entity_id.into(), yaw as u8))
|
||||
.broadcast_packet_all(&CHeadRot::new(self.entity_id.into(), head_yaw))
|
||||
.await;
|
||||
}
|
||||
|
||||
@ -446,6 +520,757 @@ impl Entity {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
async fn adjust_movement_for_collisions(&self, movement: Vector3<f64>) -> Vector3<f64> {
|
||||
self.on_ground.store(false, Ordering::SeqCst);
|
||||
|
||||
self.supporting_block_pos.store(None);
|
||||
|
||||
self.horizontal_collision.store(false, Ordering::SeqCst);
|
||||
|
||||
if movement.length_squared() == 0.0 {
|
||||
return movement;
|
||||
}
|
||||
|
||||
let bounding_box = self.bounding_box.load();
|
||||
|
||||
let (collisions, block_positions) = self
|
||||
.world
|
||||
.read()
|
||||
.await
|
||||
.get_block_collisions(bounding_box.stretch(movement))
|
||||
.await;
|
||||
|
||||
if collisions.is_empty() {
|
||||
return movement;
|
||||
}
|
||||
|
||||
let mut adjusted_movement = movement;
|
||||
|
||||
// Y-Axis adjustment
|
||||
|
||||
if movement.get_axis(Axis::Y) != 0.0 {
|
||||
let mut max_time = 1.0;
|
||||
|
||||
let mut positions = block_positions.into_iter();
|
||||
|
||||
let (mut collisions_len, mut position) = positions.next().unwrap();
|
||||
|
||||
let mut supporting_block_pos = None;
|
||||
|
||||
for (i, inert_box) in collisions.iter().enumerate() {
|
||||
if i == collisions_len {
|
||||
(collisions_len, position) = positions.next().unwrap();
|
||||
}
|
||||
|
||||
if let Some(collision_time) = bounding_box.calculate_collision_time(
|
||||
inert_box,
|
||||
adjusted_movement,
|
||||
Axis::Y,
|
||||
max_time,
|
||||
) {
|
||||
max_time = collision_time;
|
||||
|
||||
supporting_block_pos = Some(position);
|
||||
}
|
||||
}
|
||||
|
||||
if max_time != 1.0 {
|
||||
let changed_component = adjusted_movement.get_axis(Axis::Y) * max_time;
|
||||
|
||||
adjusted_movement.set_axis(Axis::Y, changed_component);
|
||||
}
|
||||
|
||||
self.on_ground
|
||||
.store(supporting_block_pos.is_some(), Ordering::SeqCst);
|
||||
|
||||
self.supporting_block_pos.store(supporting_block_pos);
|
||||
}
|
||||
|
||||
let mut horizontal_collision = false;
|
||||
|
||||
for axis in Axis::horizontal() {
|
||||
if movement.get_axis(axis) == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut max_time = 1.0;
|
||||
|
||||
for inert_box in &collisions {
|
||||
if let Some(collision_time) = bounding_box.calculate_collision_time(
|
||||
inert_box,
|
||||
adjusted_movement,
|
||||
axis,
|
||||
max_time,
|
||||
) {
|
||||
max_time = collision_time;
|
||||
}
|
||||
}
|
||||
|
||||
if max_time != 1.0 {
|
||||
let changed_component = adjusted_movement.get_axis(axis) * max_time;
|
||||
|
||||
adjusted_movement.set_axis(axis, changed_component);
|
||||
|
||||
horizontal_collision = true;
|
||||
}
|
||||
}
|
||||
|
||||
self.horizontal_collision
|
||||
.store(horizontal_collision, Ordering::SeqCst);
|
||||
|
||||
adjusted_movement
|
||||
}
|
||||
|
||||
/// Applies knockback to the entity, following vanilla Minecraft's mechanics.
|
||||
/// `LivingEntity.takeKnockback()`
|
||||
/// This function calculates the entity's new velocity based on the specified knockback strength and direction.
|
||||
pub fn apply_knockback(&self, strength: f64, mut x: f64, mut z: f64) {
|
||||
// TODO: strength *= 1 - Entity attribute knockback resistance
|
||||
|
||||
if strength <= 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.velocity_dirty.store(true, Ordering::SeqCst);
|
||||
|
||||
// This has some vanilla magic
|
||||
|
||||
while x.mul_add(x, z * z) < 1.0E-5 {
|
||||
x = (rand::random::<f64>() - rand::random::<f64>()) * 0.01;
|
||||
|
||||
z = (rand::random::<f64>() - rand::random::<f64>()) * 0.01;
|
||||
}
|
||||
|
||||
let var8 = Vector3::new(x, 0.0, z).normalize() * strength;
|
||||
|
||||
let velocity = self.velocity.load();
|
||||
|
||||
self.velocity.store(Vector3::new(
|
||||
velocity.x / 2.0 - var8.x,
|
||||
if self.on_ground.load(Relaxed) {
|
||||
(velocity.y / 2.0 + strength).min(0.4)
|
||||
} else {
|
||||
velocity.y
|
||||
},
|
||||
velocity.z / 2.0 - var8.z,
|
||||
));
|
||||
}
|
||||
|
||||
// Part of LivingEntity.tickMovement() in yarn
|
||||
|
||||
pub fn check_zero_velo(&self) {
|
||||
let mut motion = self.velocity.load();
|
||||
|
||||
if self.entity_type == &EntityType::PLAYER {
|
||||
if motion.horizontal_length_squared() < 9.0E-6 {
|
||||
motion.x = 0.0;
|
||||
|
||||
motion.z = 0.0;
|
||||
}
|
||||
} else {
|
||||
if motion.x.abs() < 0.003 {
|
||||
motion.x = 0.0;
|
||||
}
|
||||
|
||||
if motion.z.abs() < 0.003 {
|
||||
motion.z = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
if motion.y.abs() < 0.003 {
|
||||
motion.y = 0.0;
|
||||
}
|
||||
|
||||
self.velocity.store(motion);
|
||||
}
|
||||
|
||||
fn _tick_block_underneath(_caller: &Arc<dyn EntityBase>) {
|
||||
// let world = self.world.read().await;
|
||||
|
||||
// let (pos, block, state) = self.get_block_with_y_offset(0.2).await;
|
||||
|
||||
// world
|
||||
// .block_registry
|
||||
// .on_stepped_on(&world, caller.as_ref(), pos, block, state)
|
||||
// .await;
|
||||
|
||||
// TODO: Add this to on_stepped_on
|
||||
|
||||
/*
|
||||
|
||||
|
||||
if self.on_ground.load(Ordering::SeqCst) {
|
||||
|
||||
|
||||
let (_pos, block, state) = self.get_block_with_y_offset(0.2).await;
|
||||
|
||||
|
||||
if let Some(live) = living {
|
||||
|
||||
|
||||
if block == Block::CAMPFIRE
|
||||
|
||||
|
||||
|| block == Block::SOUL_CAMPFIRE
|
||||
|
||||
|
||||
&& CampfireLikeProperties::from_state_id(state.id, &block).r#signal_fire
|
||||
|
||||
|
||||
{
|
||||
|
||||
|
||||
let _ = live.damage(1.0, DamageType::CAMPFIRE).await;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if block == Block::MAGMA_BLOCK {
|
||||
|
||||
|
||||
let _ = live.damage(1.0, DamageType::HOT_FLOOR).await;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
// Returns whether the entity's eye level is in a wall
|
||||
|
||||
async fn tick_block_collisions(&self, caller: &Arc<dyn EntityBase>, server: &Server) -> bool {
|
||||
let bounding_box = self.bounding_box.load();
|
||||
|
||||
let mut suffocating = false;
|
||||
|
||||
let aabb = bounding_box.expand(-0.001, -0.001, -0.001);
|
||||
|
||||
let min = aabb.min_block_pos();
|
||||
|
||||
let max = aabb.max_block_pos();
|
||||
|
||||
let world = self.world.read().await;
|
||||
|
||||
let mut eye_level_box = aabb;
|
||||
|
||||
let eye_height = f64::from(self.standing_eye_height);
|
||||
|
||||
eye_level_box.min.y += eye_height;
|
||||
|
||||
eye_level_box.max.y = eye_level_box.min.y;
|
||||
|
||||
for x in min.0.x..=max.0.x {
|
||||
for y in min.0.y..=max.0.y {
|
||||
for z in min.0.z..=max.0.z {
|
||||
let pos = BlockPos::new(x, y, z);
|
||||
|
||||
let (block, state) = world.get_block_and_state(&pos).await;
|
||||
|
||||
let collided = World::check_outline(
|
||||
&bounding_box,
|
||||
pos,
|
||||
state,
|
||||
!suffocating && state.is_solid(),
|
||||
|collision_shape: &BoundingBox| {
|
||||
suffocating = collision_shape.intersects(&eye_level_box);
|
||||
},
|
||||
);
|
||||
|
||||
if collided {
|
||||
world
|
||||
.block_registry
|
||||
.on_entity_collision(
|
||||
block,
|
||||
&world,
|
||||
caller.as_ref(),
|
||||
&pos,
|
||||
state,
|
||||
server,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suffocating
|
||||
}
|
||||
|
||||
pub async fn send_pos_rot(&self) {
|
||||
let old = self.update_last_pos();
|
||||
|
||||
let new = self.pos.load();
|
||||
|
||||
let converted = Vector3::new(
|
||||
new.x.mul_add(4096.0, -(old.x * 4096.0)) as i16,
|
||||
new.y.mul_add(4096.0, -(old.y * 4096.0)) as i16,
|
||||
new.z.mul_add(4096.0, -(old.z * 4096.0)) as i16,
|
||||
);
|
||||
|
||||
let yaw = self.yaw.load();
|
||||
|
||||
let pitch = self.pitch.load();
|
||||
|
||||
// Broadcast the update packet.
|
||||
|
||||
// TODO: Do caching to only send the packet when needed.
|
||||
|
||||
let yaw = (yaw * 256.0 / 360.0).rem_euclid(256.0) as u8;
|
||||
|
||||
let pitch = (pitch * 256.0 / 360.0).rem_euclid(256.0);
|
||||
|
||||
self.world
|
||||
.read()
|
||||
.await
|
||||
.broadcast_packet_all(&CUpdateEntityPosRot::new(
|
||||
self.entity_id.into(),
|
||||
Vector3::new(converted.x, converted.y, converted.z),
|
||||
yaw,
|
||||
pitch as u8,
|
||||
self.on_ground.load(Relaxed),
|
||||
))
|
||||
.await;
|
||||
self.send_head_rot(yaw).await;
|
||||
}
|
||||
|
||||
pub fn update_last_pos(&self) -> Vector3<f64> {
|
||||
let pos = self.pos.load();
|
||||
let old = self.last_pos.load();
|
||||
|
||||
self.last_pos.store(pos);
|
||||
old
|
||||
}
|
||||
|
||||
pub async fn send_pos(&self) {
|
||||
let old = self.update_last_pos();
|
||||
let new = self.pos.load();
|
||||
|
||||
let converted = Vector3::new(
|
||||
new.x.mul_add(4096.0, -(old.x * 4096.0)) as i16,
|
||||
new.y.mul_add(4096.0, -(old.y * 4096.0)) as i16,
|
||||
new.z.mul_add(4096.0, -(old.z * 4096.0)) as i16,
|
||||
);
|
||||
|
||||
self.world
|
||||
.read()
|
||||
.await
|
||||
.broadcast_packet_all(&CUpdateEntityPos::new(
|
||||
self.entity_id.into(),
|
||||
Vector3::new(converted.x, converted.y, converted.z),
|
||||
self.on_ground.load(Relaxed),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
// updateWaterState() in yarn
|
||||
|
||||
async fn update_fluid_state(&self, caller: &Arc<dyn EntityBase>) {
|
||||
let is_pushed = caller.is_pushed_by_fluids();
|
||||
|
||||
let mut fluids = BTreeMap::new();
|
||||
|
||||
let water_push = Vector3::default();
|
||||
|
||||
let water_n = 0;
|
||||
|
||||
let lava_push = Vector3::default();
|
||||
|
||||
let lava_n = 0;
|
||||
|
||||
let mut fluid_push = [water_push, lava_push];
|
||||
|
||||
let mut fluid_n = [water_n, lava_n];
|
||||
|
||||
let mut in_fluid = [false, false];
|
||||
|
||||
// The maximum fluid height found
|
||||
|
||||
let mut fluid_height: [f64; 2] = [0.0, 0.0];
|
||||
|
||||
let bounding_box = self.bounding_box.load().expand(-0.001, -0.001, -0.001);
|
||||
|
||||
let min = bounding_box.min_block_pos();
|
||||
|
||||
let max = bounding_box.max_block_pos();
|
||||
|
||||
let world = self.world.read().await;
|
||||
|
||||
for x in min.0.x..=max.0.x {
|
||||
for y in min.0.y..=max.0.y {
|
||||
for z in min.0.z..=max.0.z {
|
||||
let pos = BlockPos::new(x, y, z);
|
||||
|
||||
let (fluid, state) = world.get_fluid_and_fluid_state(&pos).await;
|
||||
|
||||
if fluid.id != Fluid::EMPTY.id {
|
||||
let marginal_height =
|
||||
f64::from(state.height) + f64::from(y) - bounding_box.min.y;
|
||||
|
||||
if marginal_height >= 0.0 {
|
||||
let i = usize::from(
|
||||
fluid.id == Fluid::FLOWING_LAVA.id || fluid.id == Fluid::LAVA.id,
|
||||
);
|
||||
|
||||
fluid_height[i] = fluid_height[i].max(marginal_height);
|
||||
|
||||
in_fluid[i] = true;
|
||||
|
||||
if !is_pushed {
|
||||
fluids.insert(fluid.id, fluid);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut fluid_velo =
|
||||
world.get_fluid_velocity(pos, &fluid, &state).await;
|
||||
|
||||
if fluid_height[i] < 0.4 {
|
||||
fluid_velo = fluid_velo * fluid_height[i];
|
||||
}
|
||||
|
||||
fluid_push[i] += fluid_velo;
|
||||
|
||||
fluid_n[i] += 1;
|
||||
|
||||
fluids.insert(fluid.id, fluid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BTreeMap auto-sorts water before lava as in vanilla
|
||||
|
||||
for (_, fluid) in fluids {
|
||||
world
|
||||
.block_registry
|
||||
.on_entity_collision_fluid(&fluid, caller.as_ref())
|
||||
.await;
|
||||
}
|
||||
|
||||
let lava_speed =
|
||||
if self.world.read().await.dimension_type == VanillaDimensionType::TheNether {
|
||||
0.007
|
||||
} else {
|
||||
0.002_333_333
|
||||
};
|
||||
|
||||
self.push_by_fluid(0.014, fluid_push[0], fluid_n[0]);
|
||||
|
||||
self.push_by_fluid(lava_speed, fluid_push[1], fluid_n[1]);
|
||||
|
||||
let water_height = fluid_height[0];
|
||||
|
||||
let in_water = in_fluid[0];
|
||||
|
||||
if in_water {
|
||||
if let Some(living) = caller.get_living_entity() {
|
||||
living.fall_distance.store(0.0);
|
||||
}
|
||||
|
||||
if !self.touching_water.load(Ordering::SeqCst) {
|
||||
|
||||
// TODO: Spawn splash particles
|
||||
}
|
||||
}
|
||||
|
||||
self.water_height.store(water_height);
|
||||
|
||||
self.touching_water.store(in_water, Ordering::SeqCst);
|
||||
|
||||
let lava_height = fluid_height[1];
|
||||
|
||||
let in_lava = in_fluid[1];
|
||||
|
||||
if in_lava {
|
||||
if let Some(living) = caller.get_living_entity() {
|
||||
let halved_fall = living.fall_distance.load() / 2.0;
|
||||
|
||||
if halved_fall != 0.0 {
|
||||
living.fall_distance.store(halved_fall);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.lava_height.store(lava_height);
|
||||
|
||||
self.touching_lava.store(in_lava, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
fn push_by_fluid(&self, speed: f64, mut push: Vector3<f64>, n: usize) {
|
||||
if push.length_squared() != 0.0 {
|
||||
if n > 0 {
|
||||
push = push * (1.0 / (n as f64));
|
||||
}
|
||||
|
||||
if self.entity_type != &EntityType::PLAYER {
|
||||
push = push.normalize();
|
||||
}
|
||||
|
||||
push = push * speed;
|
||||
|
||||
let velo = self.velocity.load();
|
||||
|
||||
if velo.x.abs() < 0.003 && velo.z.abs() < 0.003 && velo.length_squared() < 0.000_020_25
|
||||
{
|
||||
push = push.normalize() * 0.0045;
|
||||
}
|
||||
|
||||
self.velocity.store(velo + push);
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_pos_with_y_offset(
|
||||
&self,
|
||||
offset: f64,
|
||||
) -> (
|
||||
BlockPos,
|
||||
Option<&'static Block>,
|
||||
Option<&'static BlockState>,
|
||||
) {
|
||||
if let Some(mut supporting_block) = self.supporting_block_pos.load() {
|
||||
if offset > 1.0e-5 {
|
||||
let (block, state) = self
|
||||
.world
|
||||
.read()
|
||||
.await
|
||||
.get_block_and_state(&supporting_block)
|
||||
.await;
|
||||
|
||||
// if let Some(props) = block.properties(state.id) {
|
||||
// let name = props.;
|
||||
|
||||
// if offset <= 0.5
|
||||
// && (name == "OakFenceLikeProperties"
|
||||
// || name == "ResinBrickWallLikeProperties"
|
||||
// || name == "OakFenceGateLikeProperties"
|
||||
// && OakFenceGateLikeProperties::from_state_id(state.id, &block)
|
||||
// .r#open)
|
||||
// {
|
||||
// return (supporting_block, Some(block), Some(state));
|
||||
// }
|
||||
// }
|
||||
|
||||
supporting_block.0.y = (self.pos.load().y - offset).floor() as i32;
|
||||
|
||||
return (supporting_block, Some(block), Some(state));
|
||||
}
|
||||
|
||||
return (supporting_block, None, None);
|
||||
}
|
||||
|
||||
let mut block_pos = self.block_pos.load();
|
||||
|
||||
block_pos.0.y = (self.pos.load().y - offset).floor() as i32;
|
||||
|
||||
(block_pos, None, None)
|
||||
}
|
||||
|
||||
async fn get_block_with_y_offset(
|
||||
&self,
|
||||
offset: f64,
|
||||
) -> (BlockPos, &'static Block, &'static BlockState) {
|
||||
let (pos, block, state) = self.get_pos_with_y_offset(offset).await;
|
||||
|
||||
if let (Some(b), Some(s)) = (block, state) {
|
||||
(pos, b, s)
|
||||
} else {
|
||||
let (b, s) = self.world.read().await.get_block_and_state(&pos).await;
|
||||
|
||||
(pos, b, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Entity.updateVelocity in yarn
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn _update_velocity_from_input(&self, movement_input: Vector3<f64>, speed: f64) {
|
||||
let final_input = self.movement_input_to_velocity(movement_input, speed);
|
||||
|
||||
self.velocity.store(self.velocity.load() + final_input);
|
||||
}
|
||||
|
||||
// Entity.movementInputToVelocity in yarn
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn movement_input_to_velocity(&self, movement_input: Vector3<f64>, speed: f64) -> Vector3<f64> {
|
||||
let yaw = f64::from(self.yaw.load()).to_radians();
|
||||
|
||||
let dist = movement_input.length_squared();
|
||||
|
||||
if dist < 1.0e-7 {
|
||||
return Vector3::default();
|
||||
}
|
||||
|
||||
let input = if dist > 1.0 {
|
||||
movement_input.normalize()
|
||||
} else {
|
||||
movement_input * speed
|
||||
};
|
||||
|
||||
let sin = yaw.sin();
|
||||
|
||||
let cos = yaw.cos();
|
||||
|
||||
Vector3::new(
|
||||
input.x * cos - input.z * sin,
|
||||
input.y,
|
||||
input.z * cos + input.x * sin,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
async fn get_velocity_multiplier(&self) -> f32 {
|
||||
let world = self.world.read().await;
|
||||
|
||||
let block = world.get_block(&self.block_pos.load()).await;
|
||||
|
||||
let m1 = block.velocity_multiplier;
|
||||
|
||||
if m1 != 1f32 || block == &Block::WATER || block == &Block::BUBBLE_COLUMN {
|
||||
m1
|
||||
} else {
|
||||
let (_pos, block, _state) = self.get_block_with_y_offset(0.500_001).await;
|
||||
|
||||
block.velocity_multiplier
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
async fn _get_jump_velocity_multiplier(&self) -> f32 {
|
||||
let world = self.world.read().await;
|
||||
|
||||
let f = world
|
||||
.get_block(&self.block_pos.load())
|
||||
.await
|
||||
.jump_velocity_multiplier;
|
||||
|
||||
let g = self
|
||||
.get_block_with_y_offset(0.500_001)
|
||||
.await
|
||||
.1
|
||||
.jump_velocity_multiplier;
|
||||
|
||||
if f == 1f32 { g } else { f }
|
||||
}
|
||||
|
||||
pub fn move_pos(&self, delta: Vector3<f64>) {
|
||||
self.set_pos(self.pos.load() + delta);
|
||||
}
|
||||
|
||||
// Move by a delta, adjust for collisions, and send
|
||||
|
||||
// Does not send movement. That must be done separately
|
||||
|
||||
async fn move_entity(&self, caller: Arc<dyn EntityBase>, mut motion: Vector3<f64>) {
|
||||
// TODO: Player movement checking (anticheat)
|
||||
|
||||
if caller.get_player().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.no_clip.load(Ordering::Relaxed) {
|
||||
self.move_pos(motion);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let movement_multiplier = self.movement_multiplier.swap(Vector3::default());
|
||||
|
||||
if movement_multiplier.length_squared() > 1.0e-7 {
|
||||
motion = motion.multiply(
|
||||
movement_multiplier.x,
|
||||
movement_multiplier.y,
|
||||
movement_multiplier.z,
|
||||
);
|
||||
|
||||
self.velocity.store(Vector3::default());
|
||||
}
|
||||
|
||||
let final_move = self.adjust_movement_for_collisions(motion).await;
|
||||
|
||||
self.move_pos(final_move);
|
||||
|
||||
let velocity_multiplier = f64::from(self.get_velocity_multiplier().await);
|
||||
|
||||
self.velocity.store(final_move * velocity_multiplier);
|
||||
|
||||
if let Some(living) = caller.get_living_entity() {
|
||||
living
|
||||
.update_fall_distance(final_move.y, self.on_ground.load(Ordering::SeqCst), false)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn push_out_of_blocks(&self, center_pos: Vector3<f64>) {
|
||||
let block_pos = BlockPos::floored_v(center_pos);
|
||||
|
||||
let delta = center_pos.sub(&block_pos.0.to_f64());
|
||||
|
||||
let mut min_dist = f64::MAX;
|
||||
|
||||
let mut direction = BlockDirection::Up;
|
||||
|
||||
for dir in BlockDirection::all() {
|
||||
if dir == BlockDirection::Down {
|
||||
continue;
|
||||
}
|
||||
|
||||
let offset = dir.to_offset();
|
||||
|
||||
if self
|
||||
.world
|
||||
.read()
|
||||
.await
|
||||
.get_block_state(&block_pos.offset(offset))
|
||||
.await
|
||||
.is_full_cube()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let component = delta.get_axis(dir.to_axis().into());
|
||||
|
||||
let dist = if dir.positive() {
|
||||
1.0 - component
|
||||
} else {
|
||||
component
|
||||
};
|
||||
|
||||
if dist < min_dist {
|
||||
min_dist = dist;
|
||||
|
||||
direction = dir;
|
||||
}
|
||||
}
|
||||
|
||||
let amplitude = rand::random::<f64>() * 0.2 + 0.1;
|
||||
|
||||
let axis = direction.to_axis().into();
|
||||
|
||||
let sign = if direction.positive() { 1.0 } else { -1.0 };
|
||||
|
||||
let mut velo = self.velocity.load();
|
||||
|
||||
velo = velo * 0.75;
|
||||
|
||||
velo.set_axis(axis, sign * amplitude);
|
||||
|
||||
self.velocity.store(velo);
|
||||
}
|
||||
|
||||
async fn tick_portal(&self, caller: &Arc<dyn EntityBase>) {
|
||||
if self.portal_cooldown.load(Ordering::Relaxed) > 0 {
|
||||
self.portal_cooldown.fetch_sub(1, Ordering::Relaxed);
|
||||
@ -977,6 +1802,7 @@ impl EntityBase for Entity {
|
||||
|
||||
async fn tick(&self, caller: Arc<dyn EntityBase>, _server: &Server) {
|
||||
self.tick_portal(&caller).await;
|
||||
self.update_fluid_state(&caller).await;
|
||||
self.check_out_of_world(&*caller).await;
|
||||
let fire_ticks = self.fire_ticks.load(Ordering::Relaxed);
|
||||
if fire_ticks > 0 {
|
||||
|
||||
@ -588,6 +588,7 @@ impl Player {
|
||||
|
||||
self.get_entity().set_pose(EntityPose::Sleeping).await;
|
||||
self.living_entity
|
||||
.entity
|
||||
.set_pos(bed_head_pos.to_f64().add_raw(0.5, 0.6875, 0.5));
|
||||
self.get_entity()
|
||||
.send_meta_data(&[Metadata::new(
|
||||
@ -1094,7 +1095,7 @@ impl Player {
|
||||
new_world.players.write().await.insert(uuid, self.clone());
|
||||
self.unload_watched_chunks(¤t_world).await;
|
||||
|
||||
let last_pos = self.living_entity.last_pos.load();
|
||||
let last_pos = self.living_entity.entity.last_pos.load();
|
||||
let death_dimension = self.world().await.dimension_type.resource_location();
|
||||
let death_location = BlockPos(Vector3::new(
|
||||
last_pos.x.round() as i32,
|
||||
@ -1118,7 +1119,7 @@ impl Player {
|
||||
;
|
||||
self.send_permission_lvl_update().await;
|
||||
self.clone().request_teleport(position, yaw, pitch).await;
|
||||
self.living_entity.last_pos.store(position);
|
||||
self.living_entity.entity.last_pos.store(position);
|
||||
self.send_abilities_update().await;
|
||||
self.enqueue_set_held_item_packet(&CSetSelectedSlot::new(
|
||||
self.get_inventory().get_selected_slot() as i8,
|
||||
@ -1153,7 +1154,7 @@ impl Player {
|
||||
.teleport_id_count
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
let teleport_id = i + 1;
|
||||
self.living_entity.set_pos(position);
|
||||
self.living_entity.entity.set_pos(position);
|
||||
let entity = &self.living_entity.entity;
|
||||
entity.set_rotation(yaw, pitch);
|
||||
*self.awaiting_teleport.lock().await = Some((teleport_id.into(), position));
|
||||
|
||||
@ -12,7 +12,10 @@ use std::{
|
||||
f64::consts::TAU,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU32, Ordering::Relaxed},
|
||||
atomic::{
|
||||
AtomicU32,
|
||||
Ordering::{self, Relaxed},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -34,7 +37,27 @@ impl TNTEntity {
|
||||
|
||||
#[async_trait]
|
||||
impl EntityBase for TNTEntity {
|
||||
async fn tick(&self, _caller: Arc<dyn EntityBase>, server: &Server) {
|
||||
async fn tick(&self, caller: Arc<dyn EntityBase>, server: &Server) {
|
||||
let entity = &self.entity;
|
||||
let original_velo = entity.velocity.load();
|
||||
|
||||
let mut velo = original_velo;
|
||||
velo.y -= self.get_gravity();
|
||||
|
||||
entity.move_entity(caller.clone(), velo).await;
|
||||
entity.tick_block_collisions(&caller, server).await;
|
||||
entity.velocity.store(velo.multiply(0.98, 0.98, 0.98));
|
||||
if entity.on_ground.load(Ordering::Relaxed) {
|
||||
entity.velocity.store(velo.multiply(0.7, -0.5, 0.7));
|
||||
}
|
||||
let velocity_dirty = entity.velocity_dirty.swap(false, Ordering::SeqCst);
|
||||
|
||||
if velocity_dirty {
|
||||
entity.send_pos_rot().await;
|
||||
|
||||
entity.send_velocity().await;
|
||||
}
|
||||
|
||||
let fuse = self.fuse.fetch_sub(1, Relaxed);
|
||||
if fuse == 0 {
|
||||
self.entity.remove().await;
|
||||
@ -44,6 +67,8 @@ impl EntityBase for TNTEntity {
|
||||
.await
|
||||
.explode(server, self.entity.pos.load(), self.power)
|
||||
.await;
|
||||
} else {
|
||||
entity.update_fluid_state(&caller).await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,4 +114,8 @@ impl EntityBase for TNTEntity {
|
||||
fn get_living_entity(&self) -> Option<&LivingEntity> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_gravity(&self) -> f64 {
|
||||
0.04
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ impl BedrockClient {
|
||||
|
||||
pub async fn player_pos_update(&self, player: &Arc<Player>, packet: SPlayerAuthInput) {
|
||||
let pos = packet.position;
|
||||
player.living_entity.set_pos(pos.to_f64());
|
||||
player.living_entity.entity.set_pos(pos.to_f64());
|
||||
|
||||
chunker::update_position(player).await;
|
||||
//self.send_game_packet(&CMovePlayer {
|
||||
|
||||
@ -180,7 +180,7 @@ impl JavaClient {
|
||||
if id == &confirm_teleport.teleport_id {
|
||||
// We should set the position now to what we requested in the teleport packet.
|
||||
// This may fix issues when the client sends the position while being teleported.
|
||||
player.living_entity.set_pos(*position);
|
||||
player.living_entity.entity.set_pos(*position);
|
||||
|
||||
*awaiting_teleport = None;
|
||||
} else {
|
||||
@ -294,7 +294,7 @@ impl JavaClient {
|
||||
let pos = event.to;
|
||||
let entity = &player.living_entity.entity;
|
||||
let last_pos = entity.pos.load();
|
||||
player.living_entity.set_pos(pos);
|
||||
player.living_entity.entity.set_pos(pos);
|
||||
|
||||
let height_difference = pos.y - last_pos.y;
|
||||
if entity.on_ground.load(Ordering::Relaxed) && packet.collision & FLAG_ON_GROUND == 0 && height_difference > 0.0 {
|
||||
@ -395,7 +395,7 @@ impl JavaClient {
|
||||
let pos = event.to;
|
||||
let entity = &player.living_entity.entity;
|
||||
let last_pos = entity.pos.load();
|
||||
player.living_entity.set_pos(pos);
|
||||
player.living_entity.entity.set_pos(pos);
|
||||
|
||||
let height_difference = pos.y - last_pos.y;
|
||||
if entity.on_ground.load(Ordering::Relaxed)
|
||||
|
||||
@ -39,7 +39,7 @@ use explosion::Explosion;
|
||||
use pumpkin_config::BasicConfiguration;
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
use pumpkin_data::entity::MobCategory;
|
||||
use pumpkin_data::fluid::{Falling, FluidProperties};
|
||||
use pumpkin_data::fluid::{Falling, FluidProperties, FluidState};
|
||||
use pumpkin_data::{
|
||||
Block,
|
||||
entity::{EntityStatus, EntityType},
|
||||
@ -773,6 +773,309 @@ impl World {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_fluid_collisions(self: &Arc<Self>, bounding_box: BoundingBox) -> Vec<Fluid> {
|
||||
let mut collisions = Vec::new();
|
||||
|
||||
let min = bounding_box.min_block_pos();
|
||||
|
||||
let max = bounding_box.max_block_pos();
|
||||
|
||||
for x in min.0.x..=max.0.x {
|
||||
for y in min.0.y..=max.0.y {
|
||||
for z in min.0.z..=max.0.z {
|
||||
let pos = BlockPos::new(x, y, z);
|
||||
|
||||
let (fluid, state) = self.get_fluid_and_fluid_state(&pos).await;
|
||||
|
||||
if fluid.id != Fluid::EMPTY.id {
|
||||
let height = f64::from(state.height);
|
||||
|
||||
if height >= bounding_box.min.y {
|
||||
collisions.push(fluid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collisions
|
||||
}
|
||||
|
||||
pub async fn check_fluid_collision(self: &Arc<Self>, bounding_box: BoundingBox) -> bool {
|
||||
let min = bounding_box.min_block_pos();
|
||||
|
||||
let max = bounding_box.max_block_pos();
|
||||
|
||||
for x in min.0.x..=max.0.x {
|
||||
for y in min.0.y..=max.0.y {
|
||||
for z in min.0.z..=max.0.z {
|
||||
let pos = BlockPos::new(x, y, z);
|
||||
|
||||
let (fluid, state) = self.get_fluid_and_fluid_state(&pos).await;
|
||||
|
||||
if fluid.id != Fluid::EMPTY.id {
|
||||
let height = f64::from(state.height);
|
||||
|
||||
if height >= bounding_box.min.y {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
// FlowableFluid.getVelocity()
|
||||
|
||||
pub async fn get_fluid_velocity(
|
||||
&self,
|
||||
|
||||
pos0: BlockPos,
|
||||
|
||||
fluid0: &Fluid,
|
||||
|
||||
state0: &FluidState,
|
||||
) -> Vector3<f64> {
|
||||
let mut velo = Vector3::default();
|
||||
|
||||
for dir in BlockDirection::horizontal() {
|
||||
let mut amplitude = 0.0;
|
||||
|
||||
let offset = dir.to_offset();
|
||||
|
||||
let pos = pos0.offset(offset);
|
||||
|
||||
let block_state_id = self.get_block_state_id(&pos).await;
|
||||
|
||||
let fluid = Fluid::from_state_id(block_state_id).unwrap_or(&Fluid::EMPTY);
|
||||
|
||||
if fluid.id == Fluid::EMPTY.id {
|
||||
let block = Block::from_state_id(block_state_id);
|
||||
let block_state = BlockState::from_id(block_state_id);
|
||||
|
||||
let blocks_movement = block_state.is_solid()
|
||||
&& block != &Block::COBWEB
|
||||
&& block != &Block::BAMBOO_SAPLING;
|
||||
|
||||
if !blocks_movement {
|
||||
let down_pos = pos.down();
|
||||
|
||||
let (down_fluid, down_state) = self.get_fluid_and_fluid_state(&down_pos).await;
|
||||
|
||||
if down_fluid.id == fluid0.id {
|
||||
amplitude = f64::from(state0.height - down_state.height) + 0.888_888_9;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if fluid.id != fluid0.id {
|
||||
continue;
|
||||
}
|
||||
|
||||
//let state = fluid.get_state(block_state_id);
|
||||
let state = fluid.states[0].clone();
|
||||
|
||||
amplitude = f64::from(state0.height - state.height);
|
||||
}
|
||||
|
||||
if amplitude == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
velo.x += f64::from(offset.x) * amplitude;
|
||||
|
||||
velo.z += f64::from(offset.z) * amplitude;
|
||||
}
|
||||
|
||||
// TODO: FALLING
|
||||
|
||||
if state0.falling {
|
||||
for dir in BlockDirection::horizontal() {
|
||||
let pos = pos0.offset(dir.to_offset());
|
||||
|
||||
if self.is_flow_blocked(fluid0.id, pos, dir).await
|
||||
|| self.is_flow_blocked(fluid0.id, pos.up(), dir).await
|
||||
{
|
||||
if velo.length_squared() != 0.0 {
|
||||
velo = velo.normalize();
|
||||
}
|
||||
|
||||
velo.y -= 6.0;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if velo.length_squared() == 0.0 {
|
||||
velo
|
||||
} else {
|
||||
velo.normalize()
|
||||
}
|
||||
}
|
||||
|
||||
// FlowableFluid.isFlowBlocked()
|
||||
|
||||
async fn is_flow_blocked(
|
||||
&self,
|
||||
fluid0_id: u16,
|
||||
pos: BlockPos,
|
||||
direction: BlockDirection,
|
||||
) -> bool {
|
||||
let id = self.get_block_state_id(&pos).await;
|
||||
|
||||
let fluid = Fluid::from_state_id(id).unwrap_or(&Fluid::EMPTY);
|
||||
|
||||
if fluid.id == fluid0_id {
|
||||
return false;
|
||||
}
|
||||
|
||||
if direction == BlockDirection::Up {
|
||||
return true;
|
||||
}
|
||||
|
||||
let block = Block::from_state_id(id);
|
||||
let state = BlockState::from_id(id);
|
||||
|
||||
// Doesn't count blue ice or packed ice
|
||||
|
||||
if block == &Block::ICE || block == &Block::FROSTED_ICE {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.is_side_solid(direction)
|
||||
}
|
||||
|
||||
pub fn check_outline<F>(
|
||||
bounding_box: &BoundingBox,
|
||||
|
||||
pos: BlockPos,
|
||||
|
||||
state: &BlockState,
|
||||
|
||||
use_outline_shape: bool,
|
||||
|
||||
mut using_outline_shape: F,
|
||||
) -> bool
|
||||
where
|
||||
F: FnMut(&BoundingBox),
|
||||
{
|
||||
if state.outline_shapes.is_empty() {
|
||||
// Apparently we need this for air and moving pistons
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut inside = false;
|
||||
|
||||
'shapes: for shape in state.get_block_outline_shapes().unwrap() {
|
||||
let outline_shape = shape.at_pos(pos);
|
||||
|
||||
if outline_shape.intersects(bounding_box) {
|
||||
inside = true;
|
||||
|
||||
if !use_outline_shape {
|
||||
break 'shapes;
|
||||
}
|
||||
|
||||
using_outline_shape(&outline_shape.to_bounding_box());
|
||||
}
|
||||
}
|
||||
|
||||
inside
|
||||
}
|
||||
|
||||
pub fn check_collision<F>(
|
||||
bounding_box: &BoundingBox,
|
||||
pos: BlockPos,
|
||||
state: &BlockState,
|
||||
use_collision_shape: bool,
|
||||
mut using_collision_shape: F,
|
||||
) -> bool
|
||||
where
|
||||
F: FnMut(&BoundingBox),
|
||||
{
|
||||
let mut collided = false;
|
||||
|
||||
if !state.is_air() && state.is_solid() && !state.collision_shapes.is_empty() {
|
||||
for shape in state.get_block_collision_shapes() {
|
||||
let collision_shape = shape.at_pos(pos);
|
||||
|
||||
if collision_shape.intersects(bounding_box) {
|
||||
collided = true;
|
||||
|
||||
if !use_collision_shape {
|
||||
break;
|
||||
}
|
||||
|
||||
using_collision_shape(&collision_shape.to_bounding_box());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collided
|
||||
}
|
||||
|
||||
// For adjusting movement
|
||||
|
||||
pub async fn get_block_collisions(
|
||||
self: &Arc<Self>,
|
||||
|
||||
bounding_box: BoundingBox,
|
||||
) -> (Vec<BoundingBox>, Vec<(usize, BlockPos)>) {
|
||||
let mut collisions = Vec::new();
|
||||
|
||||
let mut positions = Vec::new();
|
||||
|
||||
// Include downwards for fences
|
||||
|
||||
let min = BlockPos::floored_v(bounding_box.min.add_raw(0.0, -0.50001, 0.0));
|
||||
|
||||
let max = bounding_box.max_block_pos();
|
||||
|
||||
for x in min.0.x..=max.0.x {
|
||||
for y in min.0.y..=max.0.y {
|
||||
for z in min.0.z..=max.0.z {
|
||||
let pos = BlockPos::new(x, y, z);
|
||||
|
||||
let state = self.get_block_state(&pos).await;
|
||||
|
||||
let collided = Self::check_collision(
|
||||
&bounding_box,
|
||||
pos,
|
||||
state,
|
||||
true,
|
||||
|collision_shape: &BoundingBox| {
|
||||
collisions.push(*collision_shape);
|
||||
},
|
||||
);
|
||||
|
||||
if collided {
|
||||
positions.push((collisions.len(), pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(collisions, positions)
|
||||
}
|
||||
|
||||
pub async fn is_space_empty(&self, bounding_box: BoundingBox) -> bool {
|
||||
let min = bounding_box.min_block_pos();
|
||||
let max = bounding_box.max_block_pos();
|
||||
|
||||
for pos in BlockPos::iterate(min, max) {
|
||||
let state = self.get_block_state(&pos).await;
|
||||
let collided = Self::check_collision(&bounding_box, pos, state, false, |_| ());
|
||||
|
||||
if collided {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn tick_spawning_chunk(
|
||||
self: &Arc<Self>,
|
||||
chunk_pos: &Vector2<i32>,
|
||||
@ -1149,7 +1452,7 @@ impl World {
|
||||
log::debug!("Sending player teleport to {}", player.gameprofile.name);
|
||||
player.request_teleport(position, yaw, pitch).await;
|
||||
|
||||
player.living_entity.last_pos.store(position);
|
||||
player.living_entity.entity.last_pos.store(position);
|
||||
|
||||
let gameprofile = &player.gameprofile;
|
||||
// Firstly, send an info update to our new player, so they can see their skin
|
||||
@ -1447,7 +1750,7 @@ impl World {
|
||||
}
|
||||
|
||||
pub async fn respawn_player(&self, player: &Arc<Player>, alive: bool) {
|
||||
let last_pos = player.living_entity.last_pos.load();
|
||||
let last_pos = player.living_entity.entity.last_pos.load();
|
||||
let death_dimension = player.world().await.dimension_type.resource_location();
|
||||
let death_location = BlockPos(Vector3::new(
|
||||
last_pos.x.round() as i32,
|
||||
@ -1511,7 +1814,7 @@ impl World {
|
||||
|
||||
log::debug!("Sending player teleport to {}", player.gameprofile.name);
|
||||
player.request_teleport(position, yaw, pitch).await;
|
||||
player.living_entity.last_pos.store(position);
|
||||
player.living_entity.entity.last_pos.store(position);
|
||||
|
||||
// TODO: difficulty, exp bar, status effect
|
||||
|
||||
@ -2449,6 +2752,40 @@ impl World {
|
||||
(block, fluid)
|
||||
}
|
||||
|
||||
pub async fn get_fluid_and_fluid_state(&self, position: &BlockPos) -> (Fluid, FluidState) {
|
||||
let id = self.get_block_state_id(position).await;
|
||||
|
||||
let Some(fluid) = Fluid::from_state_id(id) else {
|
||||
let block = Block::from_state_id(id);
|
||||
if let Some(properties) = block.properties(id) {
|
||||
for (name, value) in properties.to_props() {
|
||||
if name == *"waterlogged" {
|
||||
if value == true.to_string() {
|
||||
let fluid = Fluid::FLOWING_WATER;
|
||||
|
||||
let state = fluid.states[0].clone();
|
||||
|
||||
return (fluid, state);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fluid = Fluid::EMPTY;
|
||||
|
||||
let state = fluid.states[0].clone();
|
||||
|
||||
return (fluid, state);
|
||||
};
|
||||
|
||||
//let state = fluid.get_state(id);
|
||||
let state = fluid.states[0].clone();
|
||||
|
||||
(fluid.clone(), state)
|
||||
}
|
||||
|
||||
pub async fn get_block_state_id(&self, position: &BlockPos) -> BlockStateId {
|
||||
self.level.get_block_state(position).await.0
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user