mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
Fluid physics (#199)
* start implementing fluid physics * Initial implementation of fluid pushing * different travel function in water * bubble columns * jumping in water * cleanup * change ultrawarm to be required * fix for clippy
This commit is contained in:
parent
615d8f9d2a
commit
0d16f01571
38 changed files with 2173 additions and 1096 deletions
|
@ -18,7 +18,7 @@ _Currently supported Minecraft version: `1.21.4`._
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- [Accurate physics](https://github.com/azalea-rs/azalea/blob/main/azalea-physics/src/lib.rs) (but some features like entity collisions and water physics aren't yet implemented)
|
- [Accurate physics](https://github.com/azalea-rs/azalea/blob/main/azalea-physics/src/lib.rs) (but some features like entity collisions and elytras aren't yet implemented)
|
||||||
- [Pathfinder](https://azalea.matdoes.dev/azalea/pathfinder/index.html)
|
- [Pathfinder](https://azalea.matdoes.dev/azalea/pathfinder/index.html)
|
||||||
- [Swarms](https://azalea.matdoes.dev/azalea/swarm/index.html)
|
- [Swarms](https://azalea.matdoes.dev/azalea/swarm/index.html)
|
||||||
- [Breaking blocks](https://azalea.matdoes.dev/azalea/struct.Client.html#method.mine)
|
- [Breaking blocks](https://azalea.matdoes.dev/azalea/struct.Client.html#method.mine)
|
||||||
|
|
|
@ -341,8 +341,8 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
||||||
#property_enum_variants
|
#property_enum_variants
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::BlockStateIntegerRepr> for #property_struct_name {
|
impl From<crate::block_state::BlockStateIntegerRepr> for #property_struct_name {
|
||||||
fn from(value: crate::BlockStateIntegerRepr) -> Self {
|
fn from(value: crate::block_state::BlockStateIntegerRepr) -> Self {
|
||||||
match value {
|
match value {
|
||||||
#property_from_number_variants
|
#property_from_number_variants
|
||||||
_ => panic!("Invalid property value: {}", value),
|
_ => panic!("Invalid property value: {}", value),
|
||||||
|
@ -360,8 +360,8 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct #property_struct_name(pub bool);
|
pub struct #property_struct_name(pub bool);
|
||||||
|
|
||||||
impl From<crate::BlockStateIntegerRepr> for #property_struct_name {
|
impl From<crate::block_state::BlockStateIntegerRepr> for #property_struct_name {
|
||||||
fn from(value: crate::BlockStateIntegerRepr) -> Self {
|
fn from(value: crate::block_state::BlockStateIntegerRepr) -> Self {
|
||||||
match value {
|
match value {
|
||||||
0 => Self(false),
|
0 => Self(false),
|
||||||
1 => Self(true),
|
1 => Self(true),
|
||||||
|
@ -697,7 +697,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
||||||
let mut generated = quote! {
|
let mut generated = quote! {
|
||||||
impl BlockState {
|
impl BlockState {
|
||||||
/// The highest possible block state ID.
|
/// The highest possible block state ID.
|
||||||
pub const MAX_STATE: crate::BlockStateIntegerRepr = #last_state_id;
|
pub const MAX_STATE: crate::block_state::BlockStateIntegerRepr = #last_state_id;
|
||||||
|
|
||||||
/// Get a property from this block state. Will be `None` if the block can't have the property.
|
/// Get a property from this block state. Will be `None` if the block can't have the property.
|
||||||
///
|
///
|
||||||
|
|
|
@ -4,6 +4,8 @@ pub struct BlockBehavior {
|
||||||
pub destroy_time: f32,
|
pub destroy_time: f32,
|
||||||
pub explosion_resistance: f32,
|
pub explosion_resistance: f32,
|
||||||
pub requires_correct_tool_for_drops: bool,
|
pub requires_correct_tool_for_drops: bool,
|
||||||
|
|
||||||
|
pub force_solid: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BlockBehavior {
|
impl Default for BlockBehavior {
|
||||||
|
@ -14,6 +16,7 @@ impl Default for BlockBehavior {
|
||||||
destroy_time: 0.,
|
destroy_time: 0.,
|
||||||
explosion_resistance: 0.,
|
explosion_resistance: 0.,
|
||||||
requires_correct_tool_for_drops: false,
|
requires_correct_tool_for_drops: false,
|
||||||
|
force_solid: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,4 +55,10 @@ impl BlockBehavior {
|
||||||
self.requires_correct_tool_for_drops = true;
|
self.requires_correct_tool_for_drops = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: currently unused
|
||||||
|
pub fn force_solid(mut self, force_solid: bool) -> Self {
|
||||||
|
self.force_solid = Some(force_solid);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
156
azalea-block/src/block_state.rs
Normal file
156
azalea-block/src/block_state.rs
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
use std::{
|
||||||
|
fmt::{self, Debug},
|
||||||
|
io::{self, Cursor, Write},
|
||||||
|
};
|
||||||
|
|
||||||
|
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
|
||||||
|
|
||||||
|
use crate::Block;
|
||||||
|
|
||||||
|
/// The type that's used internally to represent a block state ID.
|
||||||
|
///
|
||||||
|
/// This should be either `u16` or `u32`. If you choose to modify it, you must
|
||||||
|
/// also change it in `azalea-block-macros/src/lib.rs`.
|
||||||
|
///
|
||||||
|
/// This does not affect protocol serialization, it just allows you to make the
|
||||||
|
/// internal type smaller if you want.
|
||||||
|
pub type BlockStateIntegerRepr = u16;
|
||||||
|
|
||||||
|
/// A representation of a state a block can be in.
|
||||||
|
///
|
||||||
|
/// For example, a stone block only has one state but each possible stair
|
||||||
|
/// rotation is a different state.
|
||||||
|
///
|
||||||
|
/// Note that this type is internally either a `u16` or `u32`, depending on
|
||||||
|
/// [`BlockStateIntegerRepr`].
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Default, Hash)]
|
||||||
|
pub struct BlockState {
|
||||||
|
/// The protocol ID for the block state. IDs may change every
|
||||||
|
/// version, so you shouldn't hard-code them or store them in databases.
|
||||||
|
pub id: BlockStateIntegerRepr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockState {
|
||||||
|
/// A shortcut for getting the air block state, since it always has an ID of
|
||||||
|
/// 0.
|
||||||
|
pub const AIR: BlockState = BlockState { id: 0 };
|
||||||
|
|
||||||
|
/// Whether the block state is possible to exist in vanilla Minecraft.
|
||||||
|
///
|
||||||
|
/// It's equivalent to checking that the state ID is not greater than
|
||||||
|
/// [`Self::MAX_STATE`].
|
||||||
|
#[inline]
|
||||||
|
pub fn is_valid_state(state_id: BlockStateIntegerRepr) -> bool {
|
||||||
|
state_id <= Self::MAX_STATE
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the block is air. This only checks for normal air, not
|
||||||
|
/// other types like cave air.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_air(&self) -> bool {
|
||||||
|
self == &Self::AIR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for BlockState {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
/// Safely converts a u32 state id to a block state.
|
||||||
|
fn try_from(state_id: u32) -> Result<Self, Self::Error> {
|
||||||
|
let state_id = state_id as BlockStateIntegerRepr;
|
||||||
|
if Self::is_valid_state(state_id) {
|
||||||
|
Ok(BlockState { id: state_id })
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<u16> for BlockState {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
/// Safely converts a u16 state id to a block state.
|
||||||
|
fn try_from(state_id: u16) -> Result<Self, Self::Error> {
|
||||||
|
let state_id = state_id as BlockStateIntegerRepr;
|
||||||
|
if Self::is_valid_state(state_id) {
|
||||||
|
Ok(BlockState { id: state_id })
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AzaleaRead for BlockState {
|
||||||
|
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||||
|
let state_id = u32::azalea_read_var(buf)?;
|
||||||
|
Self::try_from(state_id).map_err(|_| BufReadError::UnexpectedEnumVariant {
|
||||||
|
id: state_id as i32,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AzaleaWrite for BlockState {
|
||||||
|
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||||
|
u32::azalea_write_var(&(self.id as u32), buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for BlockState {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"BlockState(id: {}, {:?})",
|
||||||
|
self.id,
|
||||||
|
Box::<dyn Block>::from(*self)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BlockState> for azalea_registry::Block {
|
||||||
|
fn from(value: BlockState) -> Self {
|
||||||
|
Box::<dyn Block>::from(value).as_registry_block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_u32() {
|
||||||
|
assert_eq!(
|
||||||
|
BlockState::try_from(0 as BlockStateIntegerRepr).unwrap(),
|
||||||
|
BlockState::AIR
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(BlockState::try_from(BlockState::MAX_STATE).is_ok());
|
||||||
|
assert!(BlockState::try_from(BlockState::MAX_STATE + 1).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_blockstate() {
|
||||||
|
let block: Box<dyn Block> = Box::<dyn Block>::from(BlockState::AIR);
|
||||||
|
assert_eq!(block.id(), "air");
|
||||||
|
|
||||||
|
let block: Box<dyn Block> =
|
||||||
|
Box::<dyn Block>::from(BlockState::from(azalea_registry::Block::FloweringAzalea));
|
||||||
|
assert_eq!(block.id(), "flowering_azalea");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_debug_blockstate() {
|
||||||
|
let formatted = format!(
|
||||||
|
"{:?}",
|
||||||
|
BlockState::from(azalea_registry::Block::FloweringAzalea)
|
||||||
|
);
|
||||||
|
assert!(formatted.ends_with(", FloweringAzalea)"), "{}", formatted);
|
||||||
|
|
||||||
|
let formatted = format!(
|
||||||
|
"{:?}",
|
||||||
|
BlockState::from(azalea_registry::Block::BigDripleafStem)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
formatted.ends_with(", BigDripleafStem { facing: North, waterlogged: false })"),
|
||||||
|
"{}",
|
||||||
|
formatted
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
137
azalea-block/src/fluid_state.rs
Normal file
137
azalea-block/src/fluid_state.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
use crate::block_state::{BlockState, BlockStateIntegerRepr};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FluidState {
|
||||||
|
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
|
||||||
|
/// above it, but it's usually unused by this struct.
|
||||||
|
///
|
||||||
|
/// This is different from [`crate::blocks::Water::level`], which is
|
||||||
|
/// basically the opposite (0 = full, 8 = empty). You can convert between
|
||||||
|
/// the two representations with [`to_or_from_legacy_fluid_level`].
|
||||||
|
pub amount: u8,
|
||||||
|
|
||||||
|
/// Whether this fluid is at the max level and there's another fluid of the
|
||||||
|
/// same type above it.
|
||||||
|
///
|
||||||
|
/// TODO: this is currently unused (always false), make this actually get
|
||||||
|
/// 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.kind == self.kind) || (self.amount == 0 && other.amount == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FluidState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
kind: FluidKind::Empty,
|
||||||
|
amount: 0,
|
||||||
|
falling: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BlockState> for FluidState {
|
||||||
|
fn from(state: BlockState) -> Self {
|
||||||
|
// note that 8 here might be treated as 9 in some cases if there's another fluid
|
||||||
|
// block of the same type above it
|
||||||
|
|
||||||
|
if state
|
||||||
|
.property::<crate::properties::Waterlogged>()
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
return Self {
|
||||||
|
kind: FluidKind::Water,
|
||||||
|
amount: 8,
|
||||||
|
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 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`].
|
||||||
|
pub fn to_or_from_legacy_fluid_level(level: u8) -> u8 {
|
||||||
|
// see FlowingFluid.getLegacyLevel
|
||||||
|
8_u8.saturating_sub(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FluidState> for BlockState {
|
||||||
|
fn from(state: FluidState) -> Self {
|
||||||
|
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),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,18 +2,17 @@
|
||||||
#![feature(trait_upcasting)]
|
#![feature(trait_upcasting)]
|
||||||
|
|
||||||
mod behavior;
|
mod behavior;
|
||||||
|
pub mod block_state;
|
||||||
|
pub mod fluid_state;
|
||||||
mod generated;
|
mod generated;
|
||||||
mod range;
|
mod range;
|
||||||
|
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use std::{
|
use std::any::Any;
|
||||||
any::Any,
|
|
||||||
fmt,
|
|
||||||
io::{self, Cursor, Write},
|
|
||||||
};
|
|
||||||
|
|
||||||
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
|
|
||||||
pub use behavior::BlockBehavior;
|
pub use behavior::BlockBehavior;
|
||||||
|
// re-exported for convenience
|
||||||
|
pub use block_state::BlockState;
|
||||||
pub use generated::{blocks, properties};
|
pub use generated::{blocks, properties};
|
||||||
pub use range::BlockStates;
|
pub use range::BlockStates;
|
||||||
|
|
||||||
|
@ -40,233 +39,3 @@ pub trait Property {
|
||||||
|
|
||||||
fn try_from_block_state(state: BlockState) -> Option<Self::Value>;
|
fn try_from_block_state(state: BlockState) -> Option<Self::Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type that's used internally to represent a block state ID.
|
|
||||||
///
|
|
||||||
/// This should be either `u16` or `u32`. If you choose to modify it, you must
|
|
||||||
/// also change it in `azalea-block-macros/src/lib.rs`.
|
|
||||||
///
|
|
||||||
/// This does not affect protocol serialization, it just allows you to make the
|
|
||||||
/// internal type smaller if you want.
|
|
||||||
pub type BlockStateIntegerRepr = u16;
|
|
||||||
|
|
||||||
/// A representation of a state a block can be in.
|
|
||||||
///
|
|
||||||
/// For example, a stone block only has one state but each possible stair
|
|
||||||
/// rotation is a different state.
|
|
||||||
///
|
|
||||||
/// Note that this type is internally either a `u16` or `u32`, depending on
|
|
||||||
/// [`BlockStateIntegerRepr`].
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Default, Hash)]
|
|
||||||
pub struct BlockState {
|
|
||||||
/// The protocol ID for the block state. IDs may change every
|
|
||||||
/// version, so you shouldn't hard-code them or store them in databases.
|
|
||||||
pub id: BlockStateIntegerRepr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockState {
|
|
||||||
pub const AIR: BlockState = BlockState { id: 0 };
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_valid_state(state_id: BlockStateIntegerRepr) -> bool {
|
|
||||||
state_id <= Self::MAX_STATE
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the block is air. This only checks for normal air, not
|
|
||||||
/// other types like cave air.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_air(&self) -> bool {
|
|
||||||
self == &Self::AIR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u32> for BlockState {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
/// Safely converts a u32 state id to a block state.
|
|
||||||
fn try_from(state_id: u32) -> Result<Self, Self::Error> {
|
|
||||||
let state_id = state_id as BlockStateIntegerRepr;
|
|
||||||
if Self::is_valid_state(state_id) {
|
|
||||||
Ok(BlockState { id: state_id })
|
|
||||||
} else {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TryFrom<u16> for BlockState {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
/// Safely converts a u16 state id to a block state.
|
|
||||||
fn try_from(state_id: u16) -> Result<Self, Self::Error> {
|
|
||||||
let state_id = state_id as BlockStateIntegerRepr;
|
|
||||||
if Self::is_valid_state(state_id) {
|
|
||||||
Ok(BlockState { id: state_id })
|
|
||||||
} else {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AzaleaRead for BlockState {
|
|
||||||
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
|
||||||
let state_id = u32::azalea_read_var(buf)?;
|
|
||||||
Self::try_from(state_id).map_err(|_| BufReadError::UnexpectedEnumVariant {
|
|
||||||
id: state_id as i32,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl AzaleaWrite for BlockState {
|
|
||||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
|
||||||
u32::azalea_write_var(&(self.id as u32), buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for BlockState {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"BlockState(id: {}, {:?})",
|
|
||||||
self.id,
|
|
||||||
Box::<dyn Block>::from(*self)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct FluidState {
|
|
||||||
pub fluid: azalea_registry::Fluid,
|
|
||||||
/// 0 = empty, 8 = full, 9 = max.
|
|
||||||
///
|
|
||||||
/// 9 is meant to be used when there's another fluid block of the same type
|
|
||||||
/// above it, but it's usually unused by this struct.
|
|
||||||
pub amount: u8,
|
|
||||||
}
|
|
||||||
impl FluidState {
|
|
||||||
/// A floating point number in between 0 and 1 representing the height (as a
|
|
||||||
/// percentage of a full block) of the fluid.
|
|
||||||
pub fn height(&self) -> f32 {
|
|
||||||
self.amount as f32 / 9.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FluidState {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
fluid: azalea_registry::Fluid::Empty,
|
|
||||||
amount: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<BlockState> for FluidState {
|
|
||||||
fn from(state: BlockState) -> Self {
|
|
||||||
// note that 8 here might be treated as 9 in some cases if there's another fluid
|
|
||||||
// block of the same type above it
|
|
||||||
|
|
||||||
if state
|
|
||||||
.property::<crate::properties::Waterlogged>()
|
|
||||||
.unwrap_or_default()
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
fluid: azalea_registry::Fluid::Water,
|
|
||||||
amount: 8,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let block = Box::<dyn Block>::from(state);
|
|
||||||
if let Some(water) = block.downcast_ref::<crate::blocks::Water>() {
|
|
||||||
Self {
|
|
||||||
fluid: azalea_registry::Fluid::Water,
|
|
||||||
amount: to_or_from_legacy_fluid_level(water.level as u8),
|
|
||||||
}
|
|
||||||
} else if let Some(lava) = block.downcast_ref::<crate::blocks::Lava>() {
|
|
||||||
Self {
|
|
||||||
fluid: azalea_registry::Fluid::Lava,
|
|
||||||
amount: to_or_from_legacy_fluid_level(lava.level as u8),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Self {
|
|
||||||
fluid: azalea_registry::Fluid::Empty,
|
|
||||||
amount: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// see FlowingFluid.getLegacyLevel
|
|
||||||
fn to_or_from_legacy_fluid_level(level: u8) -> u8 {
|
|
||||||
8_u8.saturating_sub(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FluidState> for BlockState {
|
|
||||||
fn from(state: FluidState) -> Self {
|
|
||||||
match state.fluid {
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<BlockState> for azalea_registry::Block {
|
|
||||||
fn from(value: BlockState) -> Self {
|
|
||||||
Box::<dyn Block>::from(value).as_registry_block()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_u32() {
|
|
||||||
assert_eq!(
|
|
||||||
BlockState::try_from(0 as BlockStateIntegerRepr).unwrap(),
|
|
||||||
BlockState::AIR
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(BlockState::try_from(BlockState::MAX_STATE).is_ok());
|
|
||||||
assert!(BlockState::try_from(BlockState::MAX_STATE + 1).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_blockstate() {
|
|
||||||
let block: Box<dyn Block> = Box::<dyn Block>::from(BlockState::AIR);
|
|
||||||
assert_eq!(block.id(), "air");
|
|
||||||
|
|
||||||
let block: Box<dyn Block> =
|
|
||||||
Box::<dyn Block>::from(BlockState::from(azalea_registry::Block::FloweringAzalea));
|
|
||||||
assert_eq!(block.id(), "flowering_azalea");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_debug_blockstate() {
|
|
||||||
let formatted = format!(
|
|
||||||
"{:?}",
|
|
||||||
BlockState::from(azalea_registry::Block::FloweringAzalea)
|
|
||||||
);
|
|
||||||
assert!(formatted.ends_with(", FloweringAzalea)"), "{}", formatted);
|
|
||||||
|
|
||||||
let formatted = format!(
|
|
||||||
"{:?}",
|
|
||||||
BlockState::from(azalea_registry::Block::BigDripleafStem)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
formatted.ends_with(", BigDripleafStem { facing: North, waterlogged: false })"),
|
|
||||||
"{}",
|
|
||||||
formatted
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::{hash_set, HashSet},
|
||||||
ops::{Add, RangeInclusive},
|
ops::{Add, RangeInclusive},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{BlockState, BlockStateIntegerRepr};
|
use crate::{block_state::BlockStateIntegerRepr, BlockState};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BlockStates {
|
pub struct BlockStates {
|
||||||
|
@ -22,7 +22,7 @@ impl From<RangeInclusive<BlockStateIntegerRepr>> for BlockStates {
|
||||||
|
|
||||||
impl IntoIterator for BlockStates {
|
impl IntoIterator for BlockStates {
|
||||||
type Item = BlockState;
|
type Item = BlockState;
|
||||||
type IntoIter = std::collections::hash_set::IntoIter<BlockState>;
|
type IntoIter = hash_set::IntoIter<BlockState>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.set.into_iter()
|
self.set.into_iter()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use azalea_block::{Block, BlockState, FluidState};
|
use azalea_block::{fluid_state::FluidState, Block, BlockState};
|
||||||
use azalea_core::{direction::Direction, game_type::GameMode, position::BlockPos, tick::GameTick};
|
use azalea_core::{direction::Direction, game_type::GameMode, position::BlockPos, tick::GameTick};
|
||||||
use azalea_entity::{mining::get_mine_progress, FluidOnEyes, Physics};
|
use azalea_entity::{mining::get_mine_progress, FluidOnEyes, Physics};
|
||||||
use azalea_inventory::ItemStack;
|
use azalea_inventory::ItemStack;
|
||||||
|
|
|
@ -65,7 +65,8 @@ impl Plugin for PlayerMovePlugin {
|
||||||
(tick_controls, local_player_ai_step)
|
(tick_controls, local_player_ai_step)
|
||||||
.chain()
|
.chain()
|
||||||
.in_set(PhysicsSet)
|
.in_set(PhysicsSet)
|
||||||
.before(ai_step),
|
.before(ai_step)
|
||||||
|
.before(azalea_physics::fluids::update_in_water_state_and_do_fluid_pushing),
|
||||||
send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk),
|
send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk),
|
||||||
send_position.after(PhysicsSet),
|
send_position.after(PhysicsSet),
|
||||||
)
|
)
|
||||||
|
@ -324,8 +325,8 @@ pub fn local_player_ai_step(
|
||||||
) {
|
) {
|
||||||
for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
|
for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
|
||||||
// server ai step
|
// server ai step
|
||||||
physics.xxa = physics_state.left_impulse;
|
physics.x_acceleration = physics_state.left_impulse;
|
||||||
physics.zza = physics_state.forward_impulse;
|
physics.z_acceleration = physics_state.forward_impulse;
|
||||||
|
|
||||||
// TODO: food data and abilities
|
// TODO: food data and abilities
|
||||||
// let has_enough_food_to_sprint = self.food_data().food_level ||
|
// let has_enough_food_to_sprint = self.food_data().food_level ||
|
||||||
|
|
|
@ -500,6 +500,9 @@ pub fn process_packet_events(ecs: &mut World) {
|
||||||
**position = new_pos;
|
**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 the relevant packets
|
||||||
|
|
||||||
send_packet_events.send(SendPacketEvent::new(
|
send_packet_events.send(SendPacketEvent::new(
|
||||||
|
@ -853,10 +856,14 @@ pub fn process_packet_events(ecs: &mut World) {
|
||||||
if new_pos != **position {
|
if new_pos != **position {
|
||||||
**position = new_pos;
|
**position = new_pos;
|
||||||
}
|
}
|
||||||
|
let position = *position;
|
||||||
let mut look_direction = entity.get_mut::<LookDirection>().unwrap();
|
let mut look_direction = entity.get_mut::<LookDirection>().unwrap();
|
||||||
if new_look_direction != *look_direction {
|
if new_look_direction != *look_direction {
|
||||||
*look_direction = new_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);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,8 @@ use crate::{
|
||||||
/// A rectangular prism with a starting and ending point.
|
/// A rectangular prism with a starting and ending point.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
||||||
pub struct AABB {
|
pub struct AABB {
|
||||||
pub min_x: f64,
|
pub min: Vec3,
|
||||||
pub min_y: f64,
|
pub max: Vec3,
|
||||||
pub min_z: f64,
|
|
||||||
|
|
||||||
pub max_x: f64,
|
|
||||||
pub max_y: f64,
|
|
||||||
pub max_z: f64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClipPointOpts<'a> {
|
pub struct ClipPointOpts<'a> {
|
||||||
|
@ -23,8 +18,8 @@ pub struct ClipPointOpts<'a> {
|
||||||
pub delta: &'a Vec3,
|
pub delta: &'a Vec3,
|
||||||
pub begin: f64,
|
pub begin: f64,
|
||||||
pub min_x: f64,
|
pub min_x: f64,
|
||||||
pub max_x: f64,
|
|
||||||
pub min_z: f64,
|
pub min_z: f64,
|
||||||
|
pub max_x: f64,
|
||||||
pub max_z: f64,
|
pub max_z: f64,
|
||||||
pub result_dir: Direction,
|
pub result_dir: Direction,
|
||||||
pub start: &'a Vec3,
|
pub start: &'a Vec3,
|
||||||
|
@ -32,51 +27,38 @@ pub struct ClipPointOpts<'a> {
|
||||||
|
|
||||||
impl AABB {
|
impl AABB {
|
||||||
pub fn contract(&self, x: f64, y: f64, z: f64) -> AABB {
|
pub fn contract(&self, x: f64, y: f64, z: f64) -> AABB {
|
||||||
let mut min_x = self.min_x;
|
let mut min = self.min;
|
||||||
let mut min_y = self.min_y;
|
let mut max = self.max;
|
||||||
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;
|
|
||||||
|
|
||||||
if x < 0.0 {
|
if x < 0.0 {
|
||||||
min_x -= x;
|
min.x -= x;
|
||||||
} else if x > 0.0 {
|
} else if x > 0.0 {
|
||||||
max_x -= x;
|
max.x -= x;
|
||||||
}
|
}
|
||||||
|
|
||||||
if y < 0.0 {
|
if y < 0.0 {
|
||||||
min_y -= y;
|
min.y -= y;
|
||||||
} else if y > 0.0 {
|
} else if y > 0.0 {
|
||||||
max_y -= y;
|
max.y -= y;
|
||||||
}
|
}
|
||||||
|
|
||||||
if z < 0.0 {
|
if z < 0.0 {
|
||||||
min_z -= z;
|
min.z -= z;
|
||||||
} else if z > 0.0 {
|
} else if z > 0.0 {
|
||||||
max_z -= z;
|
max.z -= z;
|
||||||
}
|
}
|
||||||
|
|
||||||
AABB {
|
AABB { min, max }
|
||||||
min_x,
|
|
||||||
min_y,
|
|
||||||
min_z,
|
|
||||||
|
|
||||||
max_x,
|
|
||||||
max_y,
|
|
||||||
max_z,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_towards(&self, other: &Vec3) -> AABB {
|
pub fn expand_towards(&self, other: &Vec3) -> AABB {
|
||||||
let mut min_x = self.min_x;
|
let mut min_x = self.min.x;
|
||||||
let mut min_y = self.min_y;
|
let mut min_y = self.min.y;
|
||||||
let mut min_z = self.min_z;
|
let mut min_z = self.min.z;
|
||||||
|
|
||||||
let mut max_x = self.max_x;
|
let mut max_x = self.max.x;
|
||||||
let mut max_y = self.max_y;
|
let mut max_y = self.max.y;
|
||||||
let mut max_z = self.max_z;
|
let mut max_z = self.max.z;
|
||||||
|
|
||||||
if other.x < 0.0 {
|
if other.x < 0.0 {
|
||||||
min_x += other.x;
|
min_x += other.x;
|
||||||
|
@ -97,115 +79,93 @@ impl AABB {
|
||||||
}
|
}
|
||||||
|
|
||||||
AABB {
|
AABB {
|
||||||
min_x,
|
min: Vec3::new(min_x, min_y, min_z),
|
||||||
min_y,
|
max: Vec3::new(max_x, max_y, max_z),
|
||||||
min_z,
|
|
||||||
|
|
||||||
max_x,
|
|
||||||
max_y,
|
|
||||||
max_z,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inflate(&self, x: f64, y: f64, z: f64) -> AABB {
|
pub fn inflate(&self, x: f64, y: f64, z: f64) -> AABB {
|
||||||
let min_x = self.min_x - x;
|
let min_x = self.min.x - x;
|
||||||
let min_y = self.min_y - y;
|
let min_y = self.min.y - y;
|
||||||
let min_z = self.min_z - z;
|
let min_z = self.min.z - z;
|
||||||
|
|
||||||
let max_x = self.max_x + x;
|
let max_x = self.max.x + x;
|
||||||
let max_y = self.max_y + y;
|
let max_y = self.max.y + y;
|
||||||
let max_z = self.max_z + z;
|
let max_z = self.max.z + z;
|
||||||
|
|
||||||
AABB {
|
AABB {
|
||||||
min_x,
|
min: Vec3::new(min_x, min_y, min_z),
|
||||||
min_y,
|
max: Vec3::new(max_x, max_y, max_z),
|
||||||
min_z,
|
|
||||||
|
|
||||||
max_x,
|
|
||||||
max_y,
|
|
||||||
max_z,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn intersect(&self, other: &AABB) -> AABB {
|
pub fn intersect(&self, other: &AABB) -> AABB {
|
||||||
let min_x = self.min_x.max(other.min_x);
|
let min_x = self.min.x.max(other.min.x);
|
||||||
let min_y = self.min_y.max(other.min_y);
|
let min_y = self.min.y.max(other.min.y);
|
||||||
let min_z = self.min_z.max(other.min_z);
|
let min_z = self.min.z.max(other.min.z);
|
||||||
|
|
||||||
let max_x = self.max_x.min(other.max_x);
|
let max_x = self.max.x.min(other.max.x);
|
||||||
let max_y = self.max_y.min(other.max_y);
|
let max_y = self.max.y.min(other.max.y);
|
||||||
let max_z = self.max_z.min(other.max_z);
|
let max_z = self.max.z.min(other.max.z);
|
||||||
|
|
||||||
AABB {
|
AABB {
|
||||||
min_x,
|
min: Vec3::new(min_x, min_y, min_z),
|
||||||
min_y,
|
max: Vec3::new(max_x, max_y, max_z),
|
||||||
min_z,
|
|
||||||
|
|
||||||
max_x,
|
|
||||||
max_y,
|
|
||||||
max_z,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn minmax(&self, other: &AABB) -> AABB {
|
pub fn minmax(&self, other: &AABB) -> AABB {
|
||||||
let min_x = self.min_x.min(other.min_x);
|
let min_x = self.min.x.min(other.min.x);
|
||||||
let min_y = self.min_y.min(other.min_y);
|
let min_y = self.min.y.min(other.min.y);
|
||||||
let min_z = self.min_z.min(other.min_z);
|
let min_z = self.min.z.min(other.min.z);
|
||||||
|
|
||||||
let max_x = self.max_x.max(other.max_x);
|
let max_x = self.max.x.max(other.max.x);
|
||||||
let max_y = self.max_y.max(other.max_y);
|
let max_y = self.max.y.max(other.max.y);
|
||||||
let max_z = self.max_z.max(other.max_z);
|
let max_z = self.max.z.max(other.max.z);
|
||||||
|
|
||||||
AABB {
|
AABB {
|
||||||
min_x,
|
min: Vec3::new(min_x, min_y, min_z),
|
||||||
min_y,
|
max: Vec3::new(max_x, max_y, max_z),
|
||||||
min_z,
|
|
||||||
|
|
||||||
max_x,
|
|
||||||
max_y,
|
|
||||||
max_z,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_relative(&self, delta: &Vec3) -> AABB {
|
pub fn move_relative(&self, delta: Vec3) -> AABB {
|
||||||
AABB {
|
AABB {
|
||||||
min_x: self.min_x + delta.x,
|
min: self.min + delta,
|
||||||
min_y: self.min_y + delta.y,
|
max: self.max + delta,
|
||||||
min_z: self.min_z + delta.z,
|
|
||||||
|
|
||||||
max_x: self.max_x + delta.x,
|
|
||||||
max_y: self.max_y + delta.y,
|
|
||||||
max_z: self.max_z + delta.z,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn intersects_aabb(&self, other: &AABB) -> bool {
|
pub fn intersects_aabb(&self, other: &AABB) -> bool {
|
||||||
self.min_x < other.max_x
|
self.min.x < other.max.x
|
||||||
&& self.max_x > other.min_x
|
&& self.max.x > other.min.x
|
||||||
&& self.min_y < other.max_y
|
&& self.min.y < other.max.y
|
||||||
&& self.max_y > other.min_y
|
&& self.max.y > other.min.y
|
||||||
&& self.min_z < other.max_z
|
&& self.min.z < other.max.z
|
||||||
&& self.max_z > other.min_z
|
&& self.max.z > other.min.z
|
||||||
}
|
}
|
||||||
pub fn intersects_vec3(&self, other: &Vec3, other2: &Vec3) -> bool {
|
pub fn intersects_vec3(&self, other: &Vec3, other2: &Vec3) -> bool {
|
||||||
self.intersects_aabb(&AABB {
|
self.intersects_aabb(&AABB {
|
||||||
min_x: other.x.min(other2.x),
|
min: Vec3::new(
|
||||||
min_y: other.y.min(other2.y),
|
other.x.min(other2.x),
|
||||||
min_z: other.z.min(other2.z),
|
other.y.min(other2.y),
|
||||||
|
other.z.min(other2.z),
|
||||||
max_x: other.x.max(other2.x),
|
),
|
||||||
max_y: other.y.max(other2.y),
|
max: Vec3::new(
|
||||||
max_z: other.z.max(other2.z),
|
other.x.max(other2.x),
|
||||||
|
other.y.max(other2.y),
|
||||||
|
other.z.max(other2.z),
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains(&self, x: f64, y: f64, z: f64) -> bool {
|
pub fn contains(&self, point: &Vec3) -> bool {
|
||||||
x >= self.min_x
|
point.x >= self.min.x
|
||||||
&& x < self.max_x
|
&& point.x < self.max.x
|
||||||
&& y >= self.min_y
|
&& point.y >= self.min.y
|
||||||
&& y < self.max_y
|
&& point.y < self.max.y
|
||||||
&& z >= self.min_z
|
&& point.z >= self.min.z
|
||||||
&& z < self.max_z
|
&& point.z < self.max.z
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> f64 {
|
pub fn size(&self) -> f64 {
|
||||||
|
@ -217,9 +177,9 @@ impl AABB {
|
||||||
|
|
||||||
pub fn get_size(&self, axis: Axis) -> f64 {
|
pub fn get_size(&self, axis: Axis) -> f64 {
|
||||||
axis.choose(
|
axis.choose(
|
||||||
self.max_x - self.min_x,
|
self.max.x - self.min.x,
|
||||||
self.max_y - self.min_y,
|
self.max.y - self.min.y,
|
||||||
self.max_z - self.min_z,
|
self.max.z - self.min.z,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,13 +187,24 @@ impl AABB {
|
||||||
self.inflate(-x, -y, -z)
|
self.inflate(-x, -y, -z)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deflate_all(&mut self, amount: f64) -> AABB {
|
||||||
|
self.deflate(amount, amount, amount)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clip(&self, min: &Vec3, max: &Vec3) -> Option<Vec3> {
|
pub fn clip(&self, min: &Vec3, max: &Vec3) -> Option<Vec3> {
|
||||||
let mut t = 1.0;
|
let mut t = 1.0;
|
||||||
let delta = max - min;
|
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))
|
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(
|
pub fn clip_iterable(
|
||||||
boxes: &Vec<AABB>,
|
boxes: &Vec<AABB>,
|
||||||
from: &Vec3,
|
from: &Vec3,
|
||||||
|
@ -245,8 +216,8 @@ impl AABB {
|
||||||
let delta = to - from;
|
let delta = to - from;
|
||||||
|
|
||||||
for aabb in boxes {
|
for aabb in boxes {
|
||||||
dir = Self::get_direction(
|
dir = Self::get_direction_aabb(
|
||||||
&aabb.move_relative(&pos.to_vec3_floored()),
|
&aabb.move_relative(pos.to_vec3_floored()),
|
||||||
from,
|
from,
|
||||||
&mut t,
|
&mut t,
|
||||||
dir,
|
dir,
|
||||||
|
@ -264,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(
|
fn get_direction(
|
||||||
aabb: &AABB,
|
min: &Vec3,
|
||||||
|
max: &Vec3,
|
||||||
from: &Vec3,
|
from: &Vec3,
|
||||||
t: &mut f64,
|
t: &mut f64,
|
||||||
mut dir: Option<Direction>,
|
mut dir: Option<Direction>,
|
||||||
|
@ -276,11 +258,11 @@ impl AABB {
|
||||||
t,
|
t,
|
||||||
approach_dir: dir,
|
approach_dir: dir,
|
||||||
delta,
|
delta,
|
||||||
begin: aabb.min_x,
|
begin: min.x,
|
||||||
min_x: aabb.min_y,
|
min_x: min.y,
|
||||||
max_x: aabb.max_y,
|
max_x: max.y,
|
||||||
min_z: aabb.min_z,
|
min_z: min.z,
|
||||||
max_z: aabb.max_z,
|
max_z: max.z,
|
||||||
result_dir: Direction::West,
|
result_dir: Direction::West,
|
||||||
start: from,
|
start: from,
|
||||||
});
|
});
|
||||||
|
@ -289,11 +271,11 @@ impl AABB {
|
||||||
t,
|
t,
|
||||||
approach_dir: dir,
|
approach_dir: dir,
|
||||||
delta,
|
delta,
|
||||||
begin: aabb.max_x,
|
begin: max.x,
|
||||||
min_x: aabb.min_y,
|
min_x: min.y,
|
||||||
max_x: aabb.max_y,
|
max_x: max.y,
|
||||||
min_z: aabb.min_z,
|
min_z: min.z,
|
||||||
max_z: aabb.max_z,
|
max_z: max.z,
|
||||||
result_dir: Direction::East,
|
result_dir: Direction::East,
|
||||||
start: from,
|
start: from,
|
||||||
});
|
});
|
||||||
|
@ -308,11 +290,11 @@ impl AABB {
|
||||||
y: delta.z,
|
y: delta.z,
|
||||||
z: delta.x,
|
z: delta.x,
|
||||||
},
|
},
|
||||||
begin: aabb.min_y,
|
begin: min.y,
|
||||||
min_x: aabb.min_z,
|
min_x: min.z,
|
||||||
max_x: aabb.max_z,
|
max_x: max.z,
|
||||||
min_z: aabb.min_x,
|
min_z: min.x,
|
||||||
max_z: aabb.max_x,
|
max_z: max.x,
|
||||||
result_dir: Direction::Down,
|
result_dir: Direction::Down,
|
||||||
start: &Vec3 {
|
start: &Vec3 {
|
||||||
x: from.y,
|
x: from.y,
|
||||||
|
@ -329,11 +311,11 @@ impl AABB {
|
||||||
y: delta.z,
|
y: delta.z,
|
||||||
z: delta.x,
|
z: delta.x,
|
||||||
},
|
},
|
||||||
begin: aabb.max_y,
|
begin: max.y,
|
||||||
min_x: aabb.min_z,
|
min_x: min.z,
|
||||||
max_x: aabb.max_z,
|
max_x: max.z,
|
||||||
min_z: aabb.min_x,
|
min_z: min.x,
|
||||||
max_z: aabb.max_x,
|
max_z: max.x,
|
||||||
result_dir: Direction::Up,
|
result_dir: Direction::Up,
|
||||||
start: &Vec3 {
|
start: &Vec3 {
|
||||||
x: from.y,
|
x: from.y,
|
||||||
|
@ -352,11 +334,11 @@ impl AABB {
|
||||||
y: delta.x,
|
y: delta.x,
|
||||||
z: delta.y,
|
z: delta.y,
|
||||||
},
|
},
|
||||||
begin: aabb.min_z,
|
begin: min.z,
|
||||||
min_x: aabb.min_x,
|
min_x: min.x,
|
||||||
max_x: aabb.max_x,
|
max_x: max.x,
|
||||||
min_z: aabb.min_y,
|
min_z: min.y,
|
||||||
max_z: aabb.max_y,
|
max_z: max.y,
|
||||||
result_dir: Direction::North,
|
result_dir: Direction::North,
|
||||||
start: &Vec3 {
|
start: &Vec3 {
|
||||||
x: from.z,
|
x: from.z,
|
||||||
|
@ -373,11 +355,11 @@ impl AABB {
|
||||||
y: delta.x,
|
y: delta.x,
|
||||||
z: delta.y,
|
z: delta.y,
|
||||||
},
|
},
|
||||||
begin: aabb.max_z,
|
begin: max.z,
|
||||||
min_x: aabb.min_x,
|
min_x: min.x,
|
||||||
max_x: aabb.max_x,
|
max_x: max.x,
|
||||||
min_z: aabb.min_y,
|
min_z: min.y,
|
||||||
max_z: aabb.max_y,
|
max_z: max.y,
|
||||||
result_dir: Direction::South,
|
result_dir: Direction::South,
|
||||||
start: &Vec3 {
|
start: &Vec3 {
|
||||||
x: from.z,
|
x: from.z,
|
||||||
|
@ -409,38 +391,96 @@ impl AABB {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_nan(&self) -> bool {
|
pub fn has_nan(&self) -> bool {
|
||||||
self.min_x.is_nan()
|
self.min.x.is_nan()
|
||||||
|| self.min_y.is_nan()
|
|| self.min.y.is_nan()
|
||||||
|| self.min_z.is_nan()
|
|| self.min.z.is_nan()
|
||||||
|| self.max_x.is_nan()
|
|| self.max.x.is_nan()
|
||||||
|| self.max_y.is_nan()
|
|| self.max.y.is_nan()
|
||||||
|| self.max_z.is_nan()
|
|| self.max.z.is_nan()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_center(&self) -> Vec3 {
|
pub fn get_center(&self) -> Vec3 {
|
||||||
Vec3::new(
|
Vec3::new(
|
||||||
(self.min_x + self.max_x) / 2.0,
|
(self.min.x + self.max.x) / 2.0,
|
||||||
(self.min_y + self.max_y) / 2.0,
|
(self.min.y + self.max.y) / 2.0,
|
||||||
(self.min_z + self.max_z) / 2.0,
|
(self.min.z + self.max.z) / 2.0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn of_size(center: Vec3, dx: f64, dy: f64, dz: f64) -> AABB {
|
pub fn of_size(center: Vec3, dx: f64, dy: f64, dz: f64) -> AABB {
|
||||||
AABB {
|
AABB {
|
||||||
min_x: center.x - dx / 2.0,
|
min: Vec3::new(
|
||||||
min_y: center.y - dy / 2.0,
|
center.x - dx / 2.0,
|
||||||
min_z: center.z - dz / 2.0,
|
center.y - dy / 2.0,
|
||||||
max_x: center.x + dx / 2.0,
|
center.z - dz / 2.0,
|
||||||
max_y: center.y + dy / 2.0,
|
),
|
||||||
max_z: center.z + dz / 2.0,
|
max: Vec3::new(
|
||||||
|
center.x + dx / 2.0,
|
||||||
|
center.y + dy / 2.0,
|
||||||
|
center.z + dz / 2.0,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max(&self, axis: &Axis) -> f64 {
|
pub fn max(&self, axis: &Axis) -> f64 {
|
||||||
axis.choose(self.max_x, self.max_y, self.max_z)
|
axis.choose(self.max.x, self.max.y, self.max.z)
|
||||||
}
|
}
|
||||||
pub fn min(&self, axis: &Axis) -> f64 {
|
pub fn min(&self, axis: &Axis) -> f64 {
|
||||||
axis.choose(self.min_x, self.min_y, self.min_z)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,12 +493,8 @@ mod tests {
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
AABB::clip_iterable(
|
AABB::clip_iterable(
|
||||||
&vec![AABB {
|
&vec![AABB {
|
||||||
min_x: 0.,
|
min: Vec3::new(0., 0., 0.),
|
||||||
min_y: 0.,
|
max: Vec3::new(1., 1., 1.),
|
||||||
min_z: 0.,
|
|
||||||
max_x: 1.,
|
|
||||||
max_y: 1.,
|
|
||||||
max_z: 1.,
|
|
||||||
}],
|
}],
|
||||||
&Vec3::new(-1., -1., -1.),
|
&Vec3::new(-1., -1., -1.),
|
||||||
&Vec3::new(1., 1., 1.),
|
&Vec3::new(1., 1., 1.),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use azalea_buf::AzBuf;
|
use azalea_buf::AzBuf;
|
||||||
|
|
||||||
use crate::position::Vec3;
|
use crate::position::{BlockPos, Vec3};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, AzBuf, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, AzBuf, Default, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
@ -15,6 +15,14 @@ pub enum Direction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Direction {
|
impl Direction {
|
||||||
|
pub const HORIZONTAL: [Direction; 4] = [
|
||||||
|
Direction::North,
|
||||||
|
Direction::South,
|
||||||
|
Direction::West,
|
||||||
|
Direction::East,
|
||||||
|
];
|
||||||
|
pub const VERTICAL: [Direction; 2] = [Direction::Down, Direction::Up];
|
||||||
|
|
||||||
pub fn nearest(vec: Vec3) -> Direction {
|
pub fn nearest(vec: Vec3) -> Direction {
|
||||||
let mut best_direction = Direction::North;
|
let mut best_direction = Direction::North;
|
||||||
let mut best_direction_amount = 0.0;
|
let mut best_direction_amount = 0.0;
|
||||||
|
@ -29,7 +37,7 @@ impl Direction {
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
{
|
{
|
||||||
let amount = dir.normal().dot(vec);
|
let amount = dir.normal_vec3().dot(vec);
|
||||||
if amount > best_direction_amount {
|
if amount > best_direction_amount {
|
||||||
best_direction = *dir;
|
best_direction = *dir;
|
||||||
best_direction_amount = amount;
|
best_direction_amount = amount;
|
||||||
|
@ -39,17 +47,23 @@ impl Direction {
|
||||||
best_direction
|
best_direction
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn normal(self) -> Vec3 {
|
#[inline]
|
||||||
|
pub fn normal(self) -> BlockPos {
|
||||||
match self {
|
match self {
|
||||||
Direction::Down => Vec3::new(0.0, -1.0, 0.0),
|
Direction::Down => BlockPos::new(0, -1, 0),
|
||||||
Direction::Up => Vec3::new(0.0, 1.0, 0.0),
|
Direction::Up => BlockPos::new(0, 1, 0),
|
||||||
Direction::North => Vec3::new(0.0, 0.0, -1.0),
|
Direction::North => BlockPos::new(0, 0, -1),
|
||||||
Direction::South => Vec3::new(0.0, 0.0, 1.0),
|
Direction::South => BlockPos::new(0, 0, 1),
|
||||||
Direction::West => Vec3::new(-1.0, 0.0, 0.0),
|
Direction::West => BlockPos::new(-1, 0, 0),
|
||||||
Direction::East => Vec3::new(1.0, 0.0, 0.0),
|
Direction::East => BlockPos::new(1, 0, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn normal_vec3(self) -> Vec3 {
|
||||||
|
self.normal().to_vec3_floored()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn opposite(self) -> Direction {
|
pub fn opposite(self) -> Direction {
|
||||||
match self {
|
match self {
|
||||||
Direction::Down => Direction::Up,
|
Direction::Down => Direction::Up,
|
||||||
|
@ -60,6 +74,16 @@ impl Direction {
|
||||||
Direction::East => Direction::West,
|
Direction::East => Direction::West,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn x(self) -> i32 {
|
||||||
|
self.normal().x
|
||||||
|
}
|
||||||
|
pub fn y(self) -> i32 {
|
||||||
|
self.normal().y
|
||||||
|
}
|
||||||
|
pub fn z(self) -> i32 {
|
||||||
|
self.normal().z
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The four cardinal directions.
|
/// The four cardinal directions.
|
||||||
|
@ -75,6 +99,7 @@ pub enum CardinalDirection {
|
||||||
East,
|
East,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A 3D axis like x, y, z.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Axis {
|
pub enum Axis {
|
||||||
X = 0,
|
X = 0,
|
||||||
|
|
|
@ -86,6 +86,25 @@ pub fn to_degrees(radians: f64) -> f64 {
|
||||||
radians * 57.29577951308232
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -8,11 +8,12 @@ use std::{
|
||||||
fmt,
|
fmt,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
io::{Cursor, Write},
|
io::{Cursor, Write},
|
||||||
ops::{Add, AddAssign, Mul, Rem, Sub},
|
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub},
|
||||||
};
|
};
|
||||||
|
|
||||||
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
|
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
|
||||||
|
|
||||||
|
use crate::direction::Direction;
|
||||||
use crate::math;
|
use crate::math;
|
||||||
use crate::resource_location::ResourceLocation;
|
use crate::resource_location::ResourceLocation;
|
||||||
|
|
||||||
|
@ -138,7 +139,6 @@ macro_rules! vec3_impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add for $name {
|
impl Add for $name {
|
||||||
type Output = $name;
|
type Output = $name;
|
||||||
|
|
||||||
|
@ -147,6 +147,18 @@ macro_rules! vec3_impl {
|
||||||
(&self).add(&rhs)
|
(&self).add(&rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Add<$type> for $name {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn add(self, rhs: $type) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
x: self.x + rhs,
|
||||||
|
y: self.y + rhs,
|
||||||
|
z: self.z + rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AddAssign for $name {
|
impl AddAssign for $name {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -203,6 +215,35 @@ macro_rules! vec3_impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl MulAssign<$type> for $name {
|
||||||
|
#[inline]
|
||||||
|
fn mul_assign(&mut self, multiplier: $type) {
|
||||||
|
self.x *= multiplier;
|
||||||
|
self.y *= multiplier;
|
||||||
|
self.z *= multiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<$type> for $name {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn div(self, divisor: $type) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
x: self.x / divisor,
|
||||||
|
y: self.y / divisor,
|
||||||
|
z: self.z / divisor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DivAssign<$type> for $name {
|
||||||
|
#[inline]
|
||||||
|
fn div_assign(&mut self, divisor: $type) {
|
||||||
|
self.x /= divisor;
|
||||||
|
self.y /= divisor;
|
||||||
|
self.z /= divisor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<($type, $type, $type)> for $name {
|
impl From<($type, $type, $type)> for $name {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -345,6 +386,10 @@ impl BlockPos {
|
||||||
z: self.z.max(other.z),
|
z: self.z.max(other.z),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn offset_with_direction(self, direction: Direction) -> Self {
|
||||||
|
self + direction.normal()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chunk coordinates are used to represent where a chunk is in the world. You
|
/// Chunk coordinates are used to represent where a chunk is in the world. You
|
||||||
|
|
|
@ -39,6 +39,23 @@ impl RegistryHolder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the dimension type registry, or `None` if it doesn't exist. You
|
||||||
|
/// should do some type of error handling if this returns `None`.
|
||||||
|
pub fn dimension_type(&self) -> Option<RegistryType<DimensionTypeElement>> {
|
||||||
|
let name = ResourceLocation::new("minecraft:dimension_type");
|
||||||
|
match self.get(&name) {
|
||||||
|
Some(Ok(registry)) => Some(registry),
|
||||||
|
Some(Err(err)) => {
|
||||||
|
error!(
|
||||||
|
"Error deserializing dimension type registry: {err:?}\n{:?}",
|
||||||
|
self.map.get(&name)
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get<T: Deserialize>(
|
fn get<T: Deserialize>(
|
||||||
&self,
|
&self,
|
||||||
name: &ResourceLocation,
|
name: &ResourceLocation,
|
||||||
|
@ -66,23 +83,6 @@ impl RegistryHolder {
|
||||||
|
|
||||||
Some(Ok(RegistryType { map }))
|
Some(Ok(RegistryType { map }))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the dimension type registry, or `None` if it doesn't exist. You
|
|
||||||
/// should do some type of error handling if this returns `None`.
|
|
||||||
pub fn dimension_type(&self) -> Option<RegistryType<DimensionTypeElement>> {
|
|
||||||
let name = ResourceLocation::new("minecraft:dimension_type");
|
|
||||||
match self.get(&name) {
|
|
||||||
Some(Ok(registry)) => Some(registry),
|
|
||||||
Some(Err(err)) => {
|
|
||||||
error!(
|
|
||||||
"Error deserializing dimension type registry: {err:?}\n{:?}",
|
|
||||||
self.map.get(&name)
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of values for a certain type of registry data.
|
/// A collection of values for a certain type of registry data.
|
||||||
|
@ -161,6 +161,7 @@ pub struct DimensionTypeElement {
|
||||||
pub struct DimensionTypeElement {
|
pub struct DimensionTypeElement {
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub min_y: i32,
|
pub min_y: i32,
|
||||||
|
pub ultrawarm: bool,
|
||||||
#[simdnbt(flatten)]
|
#[simdnbt(flatten)]
|
||||||
pub _extra: HashMap<String, NbtTag>,
|
pub _extra: HashMap<String, NbtTag>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ use thiserror::Error;
|
||||||
pub struct Attributes {
|
pub struct Attributes {
|
||||||
pub speed: AttributeInstance,
|
pub speed: AttributeInstance,
|
||||||
pub attack_speed: AttributeInstance,
|
pub attack_speed: AttributeInstance,
|
||||||
|
pub water_movement_efficiency: AttributeInstance,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -7,17 +7,12 @@ pub struct EntityDimensions {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 radius = (self.width / 2.0) as f64;
|
||||||
let height = self.height as f64;
|
let height = self.height as f64;
|
||||||
AABB {
|
AABB {
|
||||||
min_x: pos.x - radius,
|
min: Vec3::new(pos.x - radius, pos.y, pos.z - radius),
|
||||||
min_y: pos.y,
|
max: Vec3::new(pos.x + radius, pos.y + height, pos.z + radius),
|
||||||
min_z: pos.z - radius,
|
|
||||||
|
|
||||||
max_x: pos.x + radius,
|
|
||||||
max_y: pos.y + height,
|
|
||||||
max_z: pos.z + radius,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use attributes::Attributes;
|
pub use attributes::Attributes;
|
||||||
use azalea_block::BlockState;
|
use azalea_block::{fluid_state::FluidKind, BlockState};
|
||||||
use azalea_buf::AzBuf;
|
use azalea_buf::AzBuf;
|
||||||
use azalea_core::{
|
use azalea_core::{
|
||||||
aabb::AABB,
|
aabb::AABB,
|
||||||
|
@ -207,8 +207,8 @@ impl From<&LastSentPosition> for BlockPos {
|
||||||
|
|
||||||
/// A component for entities that can jump.
|
/// A component for entities that can jump.
|
||||||
///
|
///
|
||||||
/// If this is true, the entity will try to jump every tick. (It's equivalent to
|
/// If this is true, the entity will try to jump every tick. It's equivalent to
|
||||||
/// the space key being held in vanilla.)
|
/// the space key being held in vanilla.
|
||||||
#[derive(Debug, Component, Copy, Clone, Deref, DerefMut, Default)]
|
#[derive(Debug, Component, Copy, Clone, Deref, DerefMut, Default)]
|
||||||
pub struct Jumping(bool);
|
pub struct Jumping(bool);
|
||||||
|
|
||||||
|
@ -251,19 +251,33 @@ impl Eq for LookDirection {}
|
||||||
#[derive(Debug, Component, Clone, Default)]
|
#[derive(Debug, Component, Clone, Default)]
|
||||||
pub struct Physics {
|
pub struct Physics {
|
||||||
/// How fast the entity is moving.
|
/// How fast the entity is moving.
|
||||||
|
///
|
||||||
|
/// Sometimes referred to as the delta movement.
|
||||||
pub velocity: Vec3,
|
pub velocity: Vec3,
|
||||||
|
pub vec_delta_codec: VecDeltaCodec,
|
||||||
|
|
||||||
/// X acceleration.
|
/// The position of the entity before it moved this tick.
|
||||||
pub xxa: f32,
|
///
|
||||||
/// Y acceleration.
|
/// This is set immediately before physics is done.
|
||||||
pub yya: f32,
|
pub old_position: Vec3,
|
||||||
/// Z acceleration.
|
|
||||||
pub zza: f32,
|
/// The acceleration here is the force that will be attempted to be added to
|
||||||
|
/// the entity's velocity next tick.
|
||||||
|
///
|
||||||
|
/// You should typically not set this yourself, since it's controlled by how
|
||||||
|
/// the entity is trying to move.
|
||||||
|
pub x_acceleration: f32,
|
||||||
|
pub y_acceleration: f32,
|
||||||
|
pub z_acceleration: f32,
|
||||||
|
|
||||||
on_ground: bool,
|
on_ground: bool,
|
||||||
last_on_ground: bool,
|
last_on_ground: bool,
|
||||||
|
|
||||||
pub vec_delta_codec: VecDeltaCodec,
|
/// The number of ticks until we jump again, if the jump key is being held.
|
||||||
|
///
|
||||||
|
/// This must be 0 for us to be able to jump. Sets to 10 when we do a jump
|
||||||
|
/// and sets to 0 if we're not trying to jump.
|
||||||
|
pub no_jump_delay: u32,
|
||||||
|
|
||||||
/// The width and height of the entity.
|
/// The width and height of the entity.
|
||||||
pub dimensions: EntityDimensions,
|
pub dimensions: EntityDimensions,
|
||||||
|
@ -276,21 +290,35 @@ pub struct Physics {
|
||||||
pub horizontal_collision: bool,
|
pub horizontal_collision: bool,
|
||||||
// pub minor_horizontal_collision: bool,
|
// pub minor_horizontal_collision: bool,
|
||||||
pub vertical_collision: bool,
|
pub vertical_collision: bool,
|
||||||
|
|
||||||
|
pub water_fluid_height: f64,
|
||||||
|
pub lava_fluid_height: f64,
|
||||||
|
pub was_touching_water: bool,
|
||||||
|
|
||||||
|
// TODO: implement fall_distance
|
||||||
|
pub fall_distance: f32,
|
||||||
|
// TODO: implement remaining_fire_ticks
|
||||||
|
pub remaining_fire_ticks: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Physics {
|
impl Physics {
|
||||||
pub fn new(dimensions: EntityDimensions, pos: Vec3) -> Self {
|
pub fn new(dimensions: EntityDimensions, pos: Vec3) -> Self {
|
||||||
Self {
|
Self {
|
||||||
velocity: Vec3::default(),
|
velocity: Vec3::default(),
|
||||||
|
vec_delta_codec: VecDeltaCodec::new(pos),
|
||||||
|
|
||||||
xxa: 0.,
|
old_position: pos,
|
||||||
yya: 0.,
|
|
||||||
zza: 0.,
|
x_acceleration: 0.,
|
||||||
|
y_acceleration: 0.,
|
||||||
|
z_acceleration: 0.,
|
||||||
|
|
||||||
on_ground: false,
|
on_ground: false,
|
||||||
last_on_ground: false,
|
last_on_ground: false,
|
||||||
|
|
||||||
bounding_box: dimensions.make_bounding_box(pos),
|
no_jump_delay: 0,
|
||||||
|
|
||||||
|
bounding_box: dimensions.make_bounding_box(&pos),
|
||||||
dimensions,
|
dimensions,
|
||||||
|
|
||||||
has_impulse: false,
|
has_impulse: false,
|
||||||
|
@ -298,7 +326,12 @@ impl Physics {
|
||||||
horizontal_collision: false,
|
horizontal_collision: false,
|
||||||
vertical_collision: false,
|
vertical_collision: false,
|
||||||
|
|
||||||
vec_delta_codec: VecDeltaCodec::new(pos),
|
water_fluid_height: 0.,
|
||||||
|
lava_fluid_height: 0.,
|
||||||
|
was_touching_water: false,
|
||||||
|
|
||||||
|
fall_distance: 0.,
|
||||||
|
remaining_fire_ticks: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,6 +354,25 @@ impl Physics {
|
||||||
pub fn set_last_on_ground(&mut self, last_on_ground: bool) {
|
pub fn set_last_on_ground(&mut self, last_on_ground: bool) {
|
||||||
self.last_on_ground = last_on_ground;
|
self.last_on_ground = last_on_ground;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset_fall_distance(&mut self) {
|
||||||
|
self.fall_distance = 0.;
|
||||||
|
}
|
||||||
|
pub fn clear_fire(&mut self) {
|
||||||
|
self.remaining_fire_ticks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_in_water(&self) -> bool {
|
||||||
|
self.was_touching_water
|
||||||
|
}
|
||||||
|
pub fn is_in_lava(&self) -> bool {
|
||||||
|
// 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.
|
/// Marker component for entities that are dead.
|
||||||
|
@ -420,10 +472,11 @@ impl EntityBundle {
|
||||||
// entities have different defaults
|
// entities have different defaults
|
||||||
speed: AttributeInstance::new(0.1),
|
speed: AttributeInstance::new(0.1),
|
||||||
attack_speed: AttributeInstance::new(4.0),
|
attack_speed: AttributeInstance::new(4.0),
|
||||||
|
water_movement_efficiency: AttributeInstance::new(0.0),
|
||||||
},
|
},
|
||||||
|
|
||||||
jumping: Jumping(false),
|
jumping: Jumping(false),
|
||||||
fluid_on_eyes: FluidOnEyes(azalea_registry::Fluid::Empty),
|
fluid_on_eyes: FluidOnEyes(FluidKind::Empty),
|
||||||
on_climbable: OnClimbable(false),
|
on_climbable: OnClimbable(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,10 +497,10 @@ pub struct PlayerBundle {
|
||||||
pub struct LocalEntity;
|
pub struct LocalEntity;
|
||||||
|
|
||||||
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
||||||
pub struct FluidOnEyes(azalea_registry::Fluid);
|
pub struct FluidOnEyes(FluidKind);
|
||||||
|
|
||||||
impl FluidOnEyes {
|
impl FluidOnEyes {
|
||||||
pub fn new(fluid: azalea_registry::Fluid) -> Self {
|
pub fn new(fluid: FluidKind) -> Self {
|
||||||
Self(fluid)
|
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_core::tier::get_item_tier;
|
||||||
use azalea_registry as registry;
|
use azalea_registry as registry;
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ fn destroy_speed(
|
||||||
base_destroy_speed *= multiplier;
|
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)
|
&& enchantments::get_enchant_level(registry::Enchantment::AquaAffinity, player_inventory)
|
||||||
== 0
|
== 0
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,7 @@ mod relative_updates;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use azalea_block::BlockState;
|
use azalea_block::{fluid_state::FluidKind, BlockState};
|
||||||
use azalea_core::position::{BlockPos, ChunkPos, Vec3};
|
use azalea_core::position::{BlockPos, ChunkPos, Vec3};
|
||||||
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId};
|
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId};
|
||||||
use bevy_app::{App, Plugin, PreUpdate, Update};
|
use bevy_app::{App, Plugin, PreUpdate, Update};
|
||||||
|
@ -104,11 +104,11 @@ pub fn update_fluid_on_eyes(
|
||||||
.read()
|
.read()
|
||||||
.get_fluid_state(&eye_block_pos)
|
.get_fluid_state(&eye_block_pos)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let fluid_cutoff_y = eye_block_pos.y as f64 + (fluid_at_eye.amount as f64 / 16f64);
|
let fluid_cutoff_y = (eye_block_pos.y as f32 + fluid_at_eye.height()) as f64;
|
||||||
if fluid_cutoff_y > adjusted_eye_y {
|
if fluid_cutoff_y > adjusted_eye_y {
|
||||||
**fluid_on_eyes = fluid_at_eye.fluid;
|
**fluid_on_eyes = fluid_at_eye.kind;
|
||||||
} else {
|
} 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.
|
/// Cached position in the world must be updated.
|
||||||
pub fn update_bounding_box(mut query: Query<(&Position, &mut Physics), Changed<Position>>) {
|
pub fn update_bounding_box(mut query: Query<(&Position, &mut Physics), Changed<Position>>) {
|
||||||
for (position, mut physics) in query.iter_mut() {
|
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;
|
physics.bounding_box = bounding_box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
use azalea_block::{BlockState, FluidState};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use azalea_block::{
|
||||||
|
fluid_state::{FluidKind, FluidState},
|
||||||
|
BlockState,
|
||||||
|
};
|
||||||
use azalea_core::{
|
use azalea_core::{
|
||||||
|
aabb::AABB,
|
||||||
block_hit_result::BlockHitResult,
|
block_hit_result::BlockHitResult,
|
||||||
direction::Direction,
|
direction::{Axis, Direction},
|
||||||
math::{self, lerp, EPSILON},
|
math::{self, lerp, EPSILON},
|
||||||
position::{BlockPos, Vec3},
|
position::{BlockPos, Vec3},
|
||||||
};
|
};
|
||||||
|
@ -80,8 +86,8 @@ impl FluidPickType {
|
||||||
match self {
|
match self {
|
||||||
Self::None => false,
|
Self::None => false,
|
||||||
Self::SourceOnly => fluid_state.amount == 8,
|
Self::SourceOnly => fluid_state.amount == 8,
|
||||||
Self::Any => fluid_state.fluid != azalea_registry::Fluid::Empty,
|
Self::Any => fluid_state.kind != FluidKind::Empty,
|
||||||
Self::Water => fluid_state.fluid == azalea_registry::Fluid::Water,
|
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;
|
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 {
|
let vec_sign = Vec3 {
|
||||||
x: get_number_sign(vec.x),
|
x: math::sign(vec.x),
|
||||||
y: get_number_sign(vec.y),
|
y: math::sign(vec.y),
|
||||||
z: get_number_sign(vec.z),
|
z: math::sign(vec.z),
|
||||||
};
|
};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -238,39 +238,33 @@ impl BitSetDiscreteVoxelShape {
|
||||||
var2: bool,
|
var2: bool,
|
||||||
) {
|
) {
|
||||||
let mut var3 = BitSetDiscreteVoxelShape::from(var0);
|
let mut var3 = BitSetDiscreteVoxelShape::from(var0);
|
||||||
for var4 in 0..var3.y_size {
|
for y in 0..var3.y_size {
|
||||||
for var5 in 0..var3.x_size {
|
for x in 0..var3.x_size {
|
||||||
let mut var6 = None;
|
let mut var6 = None;
|
||||||
for var7 in 0..=var3.z_size {
|
for z in 0..=var3.z_size {
|
||||||
if var3.is_full_wide(var5, var4, var7) {
|
if var3.is_full_wide(x, y, z) {
|
||||||
if var2 {
|
if var2 {
|
||||||
if var6.is_none() {
|
if var6.is_none() {
|
||||||
var6 = Some(var7);
|
var6 = Some(z);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
consumer(var5, var4, var7, var5 + 1, var4 + 1, var7 + 1);
|
consumer(x, y, z, x + 1, y + 1, z + 1);
|
||||||
}
|
}
|
||||||
} else if var6.is_some() {
|
} else if var6.is_some() {
|
||||||
let mut var8 = var5;
|
let mut var8 = x;
|
||||||
let mut var9 = var4;
|
let mut var9 = y;
|
||||||
var3.clear_z_strip(var6.unwrap(), var7, var5, var4);
|
var3.clear_z_strip(var6.unwrap(), z, x, y);
|
||||||
while var3.is_z_strip_full(var6.unwrap(), var7, var8 + 1, var4) {
|
while var3.is_z_strip_full(var6.unwrap(), z, var8 + 1, y) {
|
||||||
var3.clear_z_strip(var6.unwrap(), var7, var8 + 1, var4);
|
var3.clear_z_strip(var6.unwrap(), z, var8 + 1, y);
|
||||||
var8 += 1;
|
var8 += 1;
|
||||||
}
|
}
|
||||||
while var3.is_xz_rectangle_full(
|
while var3.is_xz_rectangle_full(x, var8 + 1, var6.unwrap(), z, var9 + 1) {
|
||||||
var5,
|
for var10 in x..=var8 {
|
||||||
var8 + 1,
|
var3.clear_z_strip(var6.unwrap(), z, var10, var9 + 1);
|
||||||
var6.unwrap(),
|
|
||||||
var7,
|
|
||||||
var9 + 1,
|
|
||||||
) {
|
|
||||||
for var10 in var5..=var8 {
|
|
||||||
var3.clear_z_strip(var6.unwrap(), var7, var10, var9 + 1);
|
|
||||||
}
|
}
|
||||||
var9 += 1;
|
var9 += 1;
|
||||||
}
|
}
|
||||||
consumer(var5, var4, var6.unwrap(), var8 + 1, var9 + 1, var7);
|
consumer(x, y, var6.unwrap(), var8 + 1, var9 + 1, z);
|
||||||
var6 = None;
|
var6 = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ mod world_collisions;
|
||||||
|
|
||||||
use std::{ops::Add, sync::LazyLock};
|
use std::{ops::Add, sync::LazyLock};
|
||||||
|
|
||||||
use azalea_block::FluidState;
|
use azalea_block::{fluid_state::FluidState, BlockState};
|
||||||
use azalea_core::{
|
use azalea_core::{
|
||||||
aabb::AABB,
|
aabb::AABB,
|
||||||
direction::Axis,
|
direction::Axis,
|
||||||
|
@ -22,6 +22,7 @@ use tracing::warn;
|
||||||
|
|
||||||
use self::world_collisions::get_block_collisions;
|
use self::world_collisions::get_block_collisions;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum MoverType {
|
pub enum MoverType {
|
||||||
Own,
|
Own,
|
||||||
Player,
|
Player,
|
||||||
|
@ -111,7 +112,7 @@ fn collide(movement: &Vec3, world: &Instance, physics: &azalea_entity::Physics)
|
||||||
y: 0.,
|
y: 0.,
|
||||||
z: movement.z,
|
z: movement.z,
|
||||||
},
|
},
|
||||||
&entity_bounding_box.move_relative(&directly_up_delta),
|
&entity_bounding_box.move_relative(directly_up_delta),
|
||||||
world,
|
world,
|
||||||
entity_collisions.clone(),
|
entity_collisions.clone(),
|
||||||
)
|
)
|
||||||
|
@ -132,7 +133,7 @@ fn collide(movement: &Vec3, world: &Instance, physics: &azalea_entity::Physics)
|
||||||
y: -step_to_delta.y + movement.y,
|
y: -step_to_delta.y + movement.y,
|
||||||
z: 0.,
|
z: 0.,
|
||||||
},
|
},
|
||||||
&entity_bounding_box.move_relative(&step_to_delta),
|
&entity_bounding_box.move_relative(step_to_delta),
|
||||||
world,
|
world,
|
||||||
entity_collisions.clone(),
|
entity_collisions.clone(),
|
||||||
));
|
));
|
||||||
|
@ -143,8 +144,10 @@ fn collide(movement: &Vec3, world: &Instance, physics: &azalea_entity::Physics)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move an entity by a given delta, checking for collisions.
|
/// Move an entity by a given delta, checking for collisions.
|
||||||
|
///
|
||||||
|
/// In Mojmap, this is `Entity.move`.
|
||||||
pub fn move_colliding(
|
pub fn move_colliding(
|
||||||
_mover_type: &MoverType,
|
_mover_type: MoverType,
|
||||||
movement: &Vec3,
|
movement: &Vec3,
|
||||||
world: &Instance,
|
world: &Instance,
|
||||||
position: &mut Mut<azalea_entity::Position>,
|
position: &mut Mut<azalea_entity::Position>,
|
||||||
|
@ -296,7 +299,7 @@ fn collide_with_shapes(
|
||||||
if y_movement != 0. {
|
if y_movement != 0. {
|
||||||
y_movement = Shapes::collide(&Axis::Y, &entity_box, collision_boxes, y_movement);
|
y_movement = Shapes::collide(&Axis::Y, &entity_box, collision_boxes, y_movement);
|
||||||
if y_movement != 0. {
|
if y_movement != 0. {
|
||||||
entity_box = entity_box.move_relative(&Vec3 {
|
entity_box = entity_box.move_relative(Vec3 {
|
||||||
x: 0.,
|
x: 0.,
|
||||||
y: y_movement,
|
y: y_movement,
|
||||||
z: 0.,
|
z: 0.,
|
||||||
|
@ -311,7 +314,7 @@ fn collide_with_shapes(
|
||||||
if more_z_movement && z_movement != 0. {
|
if more_z_movement && z_movement != 0. {
|
||||||
z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement);
|
z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement);
|
||||||
if z_movement != 0. {
|
if z_movement != 0. {
|
||||||
entity_box = entity_box.move_relative(&Vec3 {
|
entity_box = entity_box.move_relative(Vec3 {
|
||||||
x: 0.,
|
x: 0.,
|
||||||
y: 0.,
|
y: 0.,
|
||||||
z: z_movement,
|
z: z_movement,
|
||||||
|
@ -322,7 +325,7 @@ fn collide_with_shapes(
|
||||||
if x_movement != 0. {
|
if x_movement != 0. {
|
||||||
x_movement = Shapes::collide(&Axis::X, &entity_box, collision_boxes, x_movement);
|
x_movement = Shapes::collide(&Axis::X, &entity_box, collision_boxes, x_movement);
|
||||||
if x_movement != 0. {
|
if x_movement != 0. {
|
||||||
entity_box = entity_box.move_relative(&Vec3 {
|
entity_box = entity_box.move_relative(Vec3 {
|
||||||
x: x_movement,
|
x: x_movement,
|
||||||
y: 0.,
|
y: 0.,
|
||||||
z: 0.,
|
z: 0.,
|
||||||
|
@ -352,7 +355,7 @@ pub fn fluid_shape(
|
||||||
) -> &'static VoxelShape {
|
) -> &'static VoxelShape {
|
||||||
if fluid.amount == 9 {
|
if fluid.amount == 9 {
|
||||||
let fluid_state_above = world.get_fluid_state(&pos.up(1)).unwrap_or_default();
|
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;
|
return &BLOCK_SHAPE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,3 +387,28 @@ pub fn fluid_shape(
|
||||||
fn calculate_shape_for_fluid(amount: u8) -> VoxelShape {
|
fn calculate_shape_for_fluid(amount: u8) -> VoxelShape {
|
||||||
box_shape(0.0, 0.0, 0.0, 1.0, (f32::from(amount) / 9.0) as f64, 1.0)
|
box_shape(0.0, 0.0, 0.0, 1.0, (f32::from(amount) / 9.0) as f64, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the block is treated as "motion blocking".
|
||||||
|
///
|
||||||
|
/// This is marked as deprecated in Minecraft.
|
||||||
|
pub fn legacy_blocks_motion(block: BlockState) -> bool {
|
||||||
|
let registry_block = azalea_registry::Block::from(block);
|
||||||
|
legacy_calculate_solid(block)
|
||||||
|
&& registry_block != azalea_registry::Block::Cobweb
|
||||||
|
&& registry_block != azalea_registry::Block::BambooSapling
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn legacy_calculate_solid(block: BlockState) -> bool {
|
||||||
|
// force_solid has to be checked before anything else
|
||||||
|
let block_trait = Box::<dyn azalea_block::Block>::from(block);
|
||||||
|
if let Some(solid) = block_trait.behavior().force_solid {
|
||||||
|
return solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
let shape = block.collision_shape();
|
||||||
|
if shape.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let bounds = shape.bounds();
|
||||||
|
bounds.size() >= 0.7291666666666666 || bounds.get_size(Axis::Y) >= 1.0
|
||||||
|
}
|
||||||
|
|
|
@ -381,16 +381,25 @@ impl VoxelShape {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[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() {
|
if self.shape().is_empty() {
|
||||||
return EMPTY_SHAPE.clone();
|
return EMPTY_SHAPE.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
VoxelShape::Array(ArrayVoxelShape::new(
|
VoxelShape::Array(ArrayVoxelShape::new(
|
||||||
self.shape().to_owned(),
|
self.shape().to_owned(),
|
||||||
self.get_coords(Axis::X).iter().map(|c| c + x).collect(),
|
self.get_coords(Axis::X)
|
||||||
self.get_coords(Axis::Y).iter().map(|c| c + y).collect(),
|
.iter()
|
||||||
self.get_coords(Axis::Z).iter().map(|c| c + z).collect(),
|
.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(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,13 +535,6 @@ impl VoxelShape {
|
||||||
movement
|
movement
|
||||||
}
|
}
|
||||||
|
|
||||||
// public VoxelShape optimize() {
|
|
||||||
// VoxelShape[] var1 = new VoxelShape[]{Shapes.empty()};
|
|
||||||
// this.forAllBoxes((var1x, var3, var5, var7, var9, var11) -> {
|
|
||||||
// var1[0] = Shapes.joinUnoptimized(var1[0], Shapes.box(var1x, var3,
|
|
||||||
// var5, var7, var9, var11), BooleanOp.OR); });
|
|
||||||
// return var1[0];
|
|
||||||
// }
|
|
||||||
fn optimize(&self) -> VoxelShape {
|
fn optimize(&self) -> VoxelShape {
|
||||||
let mut shape = EMPTY_SHAPE.clone();
|
let mut shape = EMPTY_SHAPE.clone();
|
||||||
self.for_all_boxes(|var1x, var3, var5, var7, var9, var11| {
|
self.for_all_boxes(|var1x, var3, var5, var7, var9, var11| {
|
||||||
|
@ -545,35 +547,10 @@ impl VoxelShape {
|
||||||
shape
|
shape
|
||||||
}
|
}
|
||||||
|
|
||||||
// public void forAllBoxes(Shapes.DoubleLineConsumer var1) {
|
|
||||||
// DoubleList var2 = this.getCoords(Direction.Axis.X);
|
|
||||||
// DoubleList var3 = this.getCoords(Direction.Axis.Y);
|
|
||||||
// DoubleList var4 = this.getCoords(Direction.Axis.Z);
|
|
||||||
// this.shape.forAllBoxes((var4x, var5, var6, var7, var8, var9) -> {
|
|
||||||
// var1.consume(var2.getDouble(var4x), var3.getDouble(var5),
|
|
||||||
// var4.getDouble(var6), var2.getDouble(var7), var3.getDouble(var8),
|
|
||||||
// var4.getDouble(var9)); }, true);
|
|
||||||
// }
|
|
||||||
pub fn for_all_boxes(&self, mut consumer: impl FnMut(f64, f64, f64, f64, f64, f64))
|
pub fn for_all_boxes(&self, mut consumer: impl FnMut(f64, f64, f64, f64, f64, f64))
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
// let x_coords = self.get_coords(Axis::X);
|
|
||||||
// let y_coords = self.get_coords(Axis::Y);
|
|
||||||
// let z_coords = self.get_coords(Axis::Z);
|
|
||||||
// self.shape().for_all_boxes(
|
|
||||||
// |var4x, var5, var6, var7, var8, var9| {
|
|
||||||
// consumer(
|
|
||||||
// x_coords[var4x as usize],
|
|
||||||
// y_coords[var5 as usize],
|
|
||||||
// z_coords[var6 as usize],
|
|
||||||
// x_coords[var7 as usize],
|
|
||||||
// y_coords[var8 as usize],
|
|
||||||
// z_coords[var9 as usize],
|
|
||||||
// )
|
|
||||||
// },
|
|
||||||
// true,
|
|
||||||
// );
|
|
||||||
let x_coords = self.get_coords(Axis::X);
|
let x_coords = self.get_coords(Axis::X);
|
||||||
let y_coords = self.get_coords(Axis::Y);
|
let y_coords = self.get_coords(Axis::Y);
|
||||||
let z_coords = self.get_coords(Axis::Z);
|
let z_coords = self.get_coords(Axis::Z);
|
||||||
|
@ -596,22 +573,26 @@ impl VoxelShape {
|
||||||
let mut aabbs = Vec::new();
|
let mut aabbs = Vec::new();
|
||||||
self.for_all_boxes(|min_x, min_y, min_z, max_x, max_y, max_z| {
|
self.for_all_boxes(|min_x, min_y, min_z, max_x, max_y, max_z| {
|
||||||
aabbs.push(AABB {
|
aabbs.push(AABB {
|
||||||
min_x,
|
min: Vec3::new(min_x, min_y, min_z),
|
||||||
min_y,
|
max: Vec3::new(max_x, max_y, max_z),
|
||||||
min_z,
|
|
||||||
max_x,
|
|
||||||
max_y,
|
|
||||||
max_z,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
aabbs
|
aabbs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bounds(&self) -> AABB {
|
||||||
|
assert!(!self.is_empty(), "Can't get bounds for empty shape");
|
||||||
|
AABB {
|
||||||
|
min: Vec3::new(self.min(Axis::X), self.min(Axis::Y), self.min(Axis::Z)),
|
||||||
|
max: Vec3::new(self.max(Axis::X), self.max(Axis::Y), self.max(Axis::Z)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AABB> for VoxelShape {
|
impl From<AABB> for VoxelShape {
|
||||||
fn from(aabb: AABB) -> Self {
|
fn from(aabb: AABB) -> Self {
|
||||||
box_shape_unchecked(
|
box_shape_unchecked(
|
||||||
aabb.min_x, aabb.min_y, aabb.min_z, aabb.max_x, aabb.max_y, aabb.max_z,
|
aabb.min.x, aabb.min.y, aabb.min.z, aabb.max.x, aabb.max.y, aabb.max.z,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,28 +49,19 @@ pub fn get_block_collisions(world: &Instance, aabb: AABB) -> Vec<VoxelShape> {
|
||||||
// if it's a full block do a faster collision check
|
// if it's a full block do a faster collision check
|
||||||
if block_state.is_collision_shape_full() {
|
if block_state.is_collision_shape_full() {
|
||||||
if !state.aabb.intersects_aabb(&AABB {
|
if !state.aabb.intersects_aabb(&AABB {
|
||||||
min_x: item.pos.x as f64,
|
min: item.pos.to_vec3_floored(),
|
||||||
min_y: item.pos.y as f64,
|
max: (item.pos + 1).to_vec3_floored(),
|
||||||
min_z: item.pos.z as f64,
|
|
||||||
max_x: (item.pos.x + 1) as f64,
|
|
||||||
max_y: (item.pos.y + 1) as f64,
|
|
||||||
max_z: (item.pos.z + 1) as f64,
|
|
||||||
}) {
|
}) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
block_collisions.push(BLOCK_SHAPE.move_relative(
|
block_collisions.push(BLOCK_SHAPE.move_relative(item.pos.to_vec3_floored()));
|
||||||
item.pos.x as f64,
|
|
||||||
item.pos.y as f64,
|
|
||||||
item.pos.z as f64,
|
|
||||||
));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let block_shape = state.get_block_shape(block_state);
|
let block_shape = state.get_block_shape(block_state);
|
||||||
|
|
||||||
let block_shape =
|
let block_shape = block_shape.move_relative(item.pos.to_vec3_floored());
|
||||||
block_shape.move_relative(item.pos.x as f64, item.pos.y as f64, item.pos.z as f64);
|
|
||||||
// if the entity shape and block shape don't collide, continue
|
// if the entity shape and block shape don't collide, continue
|
||||||
if !Shapes::matches_anywhere(&block_shape, &state.entity_shape, |a, b| a && b) {
|
if !Shapes::matches_anywhere(&block_shape, &state.entity_shape, |a, b| a && b) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -95,15 +86,15 @@ pub struct BlockCollisionsState<'a> {
|
||||||
impl<'a> BlockCollisionsState<'a> {
|
impl<'a> BlockCollisionsState<'a> {
|
||||||
pub fn new(world: &'a Instance, aabb: AABB) -> Self {
|
pub fn new(world: &'a Instance, aabb: AABB) -> Self {
|
||||||
let origin = BlockPos {
|
let origin = BlockPos {
|
||||||
x: (aabb.min_x - EPSILON).floor() as i32 - 1,
|
x: (aabb.min.x - EPSILON).floor() as i32 - 1,
|
||||||
y: (aabb.min_y - EPSILON).floor() as i32 - 1,
|
y: (aabb.min.y - EPSILON).floor() as i32 - 1,
|
||||||
z: (aabb.min_z - EPSILON).floor() as i32 - 1,
|
z: (aabb.min.z - EPSILON).floor() as i32 - 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let end = BlockPos {
|
let end = BlockPos {
|
||||||
x: (aabb.max_x + EPSILON).floor() as i32 + 1,
|
x: (aabb.max.x + EPSILON).floor() as i32 + 1,
|
||||||
y: (aabb.max_y + EPSILON).floor() as i32 + 1,
|
y: (aabb.max.y + EPSILON).floor() as i32 + 1,
|
||||||
z: (aabb.max_z + EPSILON).floor() as i32 + 1,
|
z: (aabb.max.z + EPSILON).floor() as i32 + 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let cursor = Cursor3d::new(origin, end);
|
let cursor = Cursor3d::new(origin, end);
|
||||||
|
|
274
azalea-physics/src/fluids.rs
Normal file
274
azalea-physics/src/fluids.rs
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
use azalea_block::{
|
||||||
|
fluid_state::{FluidKind, FluidState},
|
||||||
|
BlockState,
|
||||||
|
};
|
||||||
|
use azalea_core::{
|
||||||
|
direction::Direction,
|
||||||
|
position::{BlockPos, Vec3},
|
||||||
|
};
|
||||||
|
use azalea_entity::{InLoadedChunk, LocalEntity, Physics, Position};
|
||||||
|
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||||
|
use bevy_ecs::prelude::*;
|
||||||
|
|
||||||
|
use crate::collision::legacy_blocks_motion;
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub fn update_in_water_state_and_do_fluid_pushing(
|
||||||
|
mut query: Query<
|
||||||
|
(&mut Physics, &Position, &InstanceName),
|
||||||
|
(With<LocalEntity>, With<InLoadedChunk>),
|
||||||
|
>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
|
) {
|
||||||
|
for (mut physics, position, instance_name) in &mut query {
|
||||||
|
let world_lock = instance_container
|
||||||
|
.get(instance_name)
|
||||||
|
.expect("All entities should be in a valid world");
|
||||||
|
let world = world_lock.read();
|
||||||
|
|
||||||
|
physics.water_fluid_height = 0.;
|
||||||
|
physics.lava_fluid_height = 0.;
|
||||||
|
|
||||||
|
update_in_water_state_and_do_water_current_pushing(&mut physics, &world, position);
|
||||||
|
|
||||||
|
let is_ultrawarm = world
|
||||||
|
.registries
|
||||||
|
.dimension_type()
|
||||||
|
.and_then(|d| d.map.get(instance_name).map(|d| d.ultrawarm))
|
||||||
|
== Some(true);
|
||||||
|
let lava_push_factor = if is_ultrawarm {
|
||||||
|
0.007
|
||||||
|
} else {
|
||||||
|
0.0023333333333333335
|
||||||
|
};
|
||||||
|
|
||||||
|
update_fluid_height_and_do_fluid_pushing(
|
||||||
|
&mut physics,
|
||||||
|
&world,
|
||||||
|
FluidKind::Lava,
|
||||||
|
lava_push_factor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn update_in_water_state_and_do_water_current_pushing(
|
||||||
|
physics: &mut Physics,
|
||||||
|
world: &Instance,
|
||||||
|
_position: &Position,
|
||||||
|
) {
|
||||||
|
// TODO: implement vehicles and boats
|
||||||
|
// if vehicle == AbstractBoat {
|
||||||
|
// if !boat.is_underwater() {
|
||||||
|
// *was_touching_water = false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// updateFluidHeightAndDoFluidPushing
|
||||||
|
if update_fluid_height_and_do_fluid_pushing(physics, world, FluidKind::Water, 0.014) {
|
||||||
|
// if !was_touching_water && !first_tick {
|
||||||
|
// do_water_splash_effect();
|
||||||
|
// }
|
||||||
|
|
||||||
|
physics.reset_fall_distance();
|
||||||
|
physics.was_touching_water = true;
|
||||||
|
physics.clear_fire();
|
||||||
|
} else {
|
||||||
|
physics.was_touching_water = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_fluid_height_and_do_fluid_pushing(
|
||||||
|
physics: &mut Physics,
|
||||||
|
world: &Instance,
|
||||||
|
checking_fluid: FluidKind,
|
||||||
|
fluid_push_factor: f64,
|
||||||
|
) -> bool {
|
||||||
|
// if touching_unloaded_chunk() {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
let checking_liquids_aabb = physics.bounding_box.deflate_all(0.001);
|
||||||
|
|
||||||
|
let min_x = checking_liquids_aabb.min.x.floor() as i32;
|
||||||
|
let min_y = checking_liquids_aabb.min.y.floor() as i32;
|
||||||
|
let min_z = checking_liquids_aabb.min.z.floor() as i32;
|
||||||
|
|
||||||
|
let max_x = checking_liquids_aabb.max.x.ceil() as i32;
|
||||||
|
let max_y = checking_liquids_aabb.max.y.ceil() as i32;
|
||||||
|
let max_z = checking_liquids_aabb.max.z.ceil() as i32;
|
||||||
|
|
||||||
|
let mut min_height_touching = 0.;
|
||||||
|
let is_entity_pushable_by_fluid = true;
|
||||||
|
let mut touching_fluid = false;
|
||||||
|
let mut additional_player_delta = Vec3::default();
|
||||||
|
let mut num_fluids_being_touched = 0;
|
||||||
|
|
||||||
|
for cur_x in min_x..=max_x {
|
||||||
|
for cur_y in min_y..=max_y {
|
||||||
|
for cur_z in min_z..=max_z {
|
||||||
|
let cur_pos = BlockPos::new(cur_x, cur_y, cur_z);
|
||||||
|
let Some(fluid_at_cur_pos) = world.get_fluid_state(&cur_pos) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if fluid_at_cur_pos.kind != checking_fluid {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let fluid_max_y = (cur_y as f32 + fluid_at_cur_pos.height()) as f64;
|
||||||
|
if fluid_max_y < checking_liquids_aabb.min.y {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
touching_fluid = true;
|
||||||
|
min_height_touching = f64::max(
|
||||||
|
fluid_max_y - checking_liquids_aabb.min.y,
|
||||||
|
min_height_touching,
|
||||||
|
);
|
||||||
|
if !is_entity_pushable_by_fluid {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut additional_player_delta_for_fluid =
|
||||||
|
get_fluid_flow(&fluid_at_cur_pos, world, cur_pos);
|
||||||
|
if min_height_touching < 0.4 {
|
||||||
|
additional_player_delta_for_fluid *= min_height_touching;
|
||||||
|
};
|
||||||
|
|
||||||
|
additional_player_delta += additional_player_delta_for_fluid;
|
||||||
|
num_fluids_being_touched += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if additional_player_delta.length() > 0. {
|
||||||
|
additional_player_delta /= num_fluids_being_touched as f64;
|
||||||
|
|
||||||
|
// if entity_kind != EntityKind::Player {
|
||||||
|
// additional_player_delta = additional_player_delta.normalize();
|
||||||
|
// }
|
||||||
|
|
||||||
|
let player_delta = physics.velocity;
|
||||||
|
additional_player_delta *= fluid_push_factor;
|
||||||
|
const MIN_PUSH: f64 = 0.003;
|
||||||
|
const MIN_PUSH_LENGTH: f64 = MIN_PUSH * 1.5;
|
||||||
|
|
||||||
|
if player_delta.x.abs() < MIN_PUSH
|
||||||
|
&& player_delta.z.abs() < MIN_PUSH
|
||||||
|
&& additional_player_delta.length() < MIN_PUSH_LENGTH
|
||||||
|
{
|
||||||
|
additional_player_delta = additional_player_delta.normalize() * MIN_PUSH_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
physics.velocity += additional_player_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
match 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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_swimming() {
|
||||||
|
// TODO: swimming
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlowingFluid.getFlow
|
||||||
|
pub fn get_fluid_flow(fluid: &FluidState, world: &Instance, pos: BlockPos) -> Vec3 {
|
||||||
|
let mut z_flow: f64 = 0.;
|
||||||
|
let mut x_flow: f64 = 0.;
|
||||||
|
|
||||||
|
for direction in Direction::HORIZONTAL {
|
||||||
|
let adjacent_block_pos = pos.offset_with_direction(direction);
|
||||||
|
let adjacent_fluid_state = world
|
||||||
|
.get_fluid_state(&adjacent_block_pos)
|
||||||
|
.unwrap_or_default();
|
||||||
|
if fluid.affects_flow(&adjacent_fluid_state) {
|
||||||
|
let mut adjacent_fluid_height = adjacent_fluid_state.height();
|
||||||
|
let mut adjacent_height_difference: f32 = 0.;
|
||||||
|
|
||||||
|
if adjacent_fluid_height == 0. {
|
||||||
|
if !legacy_blocks_motion(
|
||||||
|
world
|
||||||
|
.get_block_state(&adjacent_block_pos)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
) {
|
||||||
|
let block_pos_below_adjacent = adjacent_block_pos.down(1);
|
||||||
|
let fluid_below_adjacent = world
|
||||||
|
.get_fluid_state(&block_pos_below_adjacent)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if fluid.affects_flow(&fluid_below_adjacent) {
|
||||||
|
adjacent_fluid_height = fluid_below_adjacent.height();
|
||||||
|
if adjacent_fluid_height > 0. {
|
||||||
|
adjacent_height_difference =
|
||||||
|
fluid.height() - (adjacent_fluid_height - 0.8888889);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if adjacent_fluid_height > 0. {
|
||||||
|
adjacent_height_difference = fluid.height() - adjacent_fluid_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
if adjacent_height_difference != 0. {
|
||||||
|
x_flow += (direction.x() as f32 * adjacent_height_difference) as f64;
|
||||||
|
z_flow += (direction.z() as f32 * adjacent_height_difference) as f64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut flow = Vec3::new(x_flow, 0., z_flow);
|
||||||
|
if fluid.falling {
|
||||||
|
for direction in Direction::HORIZONTAL {
|
||||||
|
let adjacent_block_pos = pos.offset_with_direction(direction);
|
||||||
|
if is_solid_face(fluid, world, adjacent_block_pos, direction)
|
||||||
|
|| is_solid_face(fluid, world, adjacent_block_pos.up(1), direction)
|
||||||
|
{
|
||||||
|
flow = flow.normalize() + Vec3::new(0., -6., 0.);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flow.normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// i don't really get what this is for
|
||||||
|
fn is_solid_face(
|
||||||
|
fluid: &FluidState,
|
||||||
|
world: &Instance,
|
||||||
|
adjacent_pos: BlockPos,
|
||||||
|
direction: Direction,
|
||||||
|
) -> bool {
|
||||||
|
let block_state = world.get_block_state(&adjacent_pos).unwrap_or_default();
|
||||||
|
let fluid_state = world.get_fluid_state(&adjacent_pos).unwrap_or_default();
|
||||||
|
if fluid_state.is_same_kind(fluid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if direction == Direction::Up {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let registry_block = azalea_registry::Block::from(block_state);
|
||||||
|
if matches!(
|
||||||
|
registry_block,
|
||||||
|
// frosted ice is from frost walker
|
||||||
|
azalea_registry::Block::Ice | azalea_registry::Block::FrostedIce
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
is_face_sturdy(block_state, world, adjacent_pos, direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_face_sturdy(
|
||||||
|
_block_state: BlockState,
|
||||||
|
_world: &Instance,
|
||||||
|
_pos: BlockPos,
|
||||||
|
_direction: Direction,
|
||||||
|
) -> bool {
|
||||||
|
// TODO: this does a whole bunch of physics shape checks for waterlogged blocks
|
||||||
|
// that i honestly cannot be bothered to implement right now
|
||||||
|
|
||||||
|
// see BlockBehavior.isFaceSturdy in the decompiled minecraft source
|
||||||
|
|
||||||
|
// also, this probably should be in a module other than fluids.rs
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
|
@ -3,8 +3,12 @@
|
||||||
|
|
||||||
pub mod clip;
|
pub mod clip;
|
||||||
pub mod collision;
|
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::{
|
use azalea_core::{
|
||||||
math,
|
math,
|
||||||
position::{BlockPos, Vec3},
|
position::{BlockPos, Vec3},
|
||||||
|
@ -22,7 +26,8 @@ use bevy_ecs::{
|
||||||
system::{Query, Res},
|
system::{Query, Res},
|
||||||
world::Mut,
|
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.
|
/// A Bevy [`SystemSet`] for running physics that makes entities do things.
|
||||||
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
||||||
|
@ -33,7 +38,15 @@ impl Plugin for PhysicsPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
GameTick,
|
GameTick,
|
||||||
(ai_step, travel)
|
(
|
||||||
|
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,
|
||||||
|
apply_effects_from_blocks,
|
||||||
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.in_set(PhysicsSet)
|
.in_set(PhysicsSet)
|
||||||
.after(azalea_entity::update_in_loaded_chunk),
|
.after(azalea_entity::update_in_loaded_chunk),
|
||||||
|
@ -41,110 +54,9 @@ impl Plugin for PhysicsPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the entity with the given acceleration while handling friction,
|
/// Applies air resistance and handles jumping.
|
||||||
/// gravity, collisions, and some other stuff.
|
///
|
||||||
#[allow(clippy::type_complexity)]
|
/// Happens before [`travel::travel`].
|
||||||
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();
|
|
||||||
// if !self.is_effective_ai() && !self.is_controlled_by_local_instance() {
|
|
||||||
// // this.calculateEntityAnimation(this, this instanceof FlyingAnimal);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
let gravity: f64 = 0.08;
|
|
||||||
|
|
||||||
// TODO: slow falling effect
|
|
||||||
// let is_falling = self.delta.y <= 0.;
|
|
||||||
|
|
||||||
// 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: &mut physics,
|
|
||||||
direction: &direction,
|
|
||||||
position,
|
|
||||||
attributes,
|
|
||||||
is_sprinting: sprinting.map(|s| **s).unwrap_or(false),
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// applies air resistance, calls self.travel(), and some other random
|
|
||||||
/// stuff.
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn ai_step(
|
pub fn ai_step(
|
||||||
mut query: Query<
|
mut query: Query<
|
||||||
|
@ -164,6 +76,10 @@ pub fn ai_step(
|
||||||
// vanilla does movement interpolation here, doesn't really matter much for a
|
// vanilla does movement interpolation here, doesn't really matter much for a
|
||||||
// bot though
|
// bot though
|
||||||
|
|
||||||
|
if physics.no_jump_delay > 0 {
|
||||||
|
physics.no_jump_delay -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
if physics.velocity.x.abs() < 0.003 {
|
if physics.velocity.x.abs() < 0.003 {
|
||||||
physics.velocity.x = 0.;
|
physics.velocity.x = 0.;
|
||||||
}
|
}
|
||||||
|
@ -178,27 +94,221 @@ pub fn ai_step(
|
||||||
if **jumping {
|
if **jumping {
|
||||||
// TODO: jumping in liquids and jump delay
|
// TODO: jumping in liquids and jump delay
|
||||||
|
|
||||||
if physics.on_ground() {
|
let fluid_height = if physics.is_in_lava() {
|
||||||
jump_from_ground(
|
physics.lava_fluid_height
|
||||||
&mut physics,
|
} else if physics.is_in_water() {
|
||||||
position,
|
physics.water_fluid_height
|
||||||
look_direction,
|
} else {
|
||||||
sprinting,
|
0.
|
||||||
instance_name,
|
};
|
||||||
&instance_container,
|
|
||||||
)
|
let in_water = physics.is_in_water() && fluid_height > 0.;
|
||||||
|
let fluid_jump_threshold = travel::fluid_jump_threshold();
|
||||||
|
|
||||||
|
if !in_water || physics.on_ground() && fluid_height <= fluid_jump_threshold {
|
||||||
|
if !physics.is_in_lava()
|
||||||
|
|| physics.on_ground() && fluid_height <= fluid_jump_threshold
|
||||||
|
{
|
||||||
|
if physics.on_ground()
|
||||||
|
|| in_water
|
||||||
|
&& fluid_height <= fluid_jump_threshold
|
||||||
|
&& physics.no_jump_delay == 0
|
||||||
|
{
|
||||||
|
jump_from_ground(
|
||||||
|
&mut physics,
|
||||||
|
position,
|
||||||
|
look_direction,
|
||||||
|
sprinting,
|
||||||
|
instance_name,
|
||||||
|
&instance_container,
|
||||||
|
);
|
||||||
|
physics.no_jump_delay = 10;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jump_in_liquid(&mut physics);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jump_in_liquid(&mut physics);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
physics.no_jump_delay = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
physics.xxa *= 0.98;
|
physics.x_acceleration *= 0.98;
|
||||||
physics.zza *= 0.98;
|
physics.z_acceleration *= 0.98;
|
||||||
|
|
||||||
// TODO: freezing, pushEntities, drowning damage (in their own systems,
|
// TODO: freezing, pushEntities, drowning damage (in their own systems,
|
||||||
// after `travel`)
|
// after `travel`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn jump_in_liquid(physics: &mut Physics) {
|
||||||
|
physics.velocity.y += 0.04;
|
||||||
|
}
|
||||||
|
|
||||||
|
// in minecraft, this is done as part of aiStep immediately after travel
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub fn apply_effects_from_blocks(
|
||||||
|
mut query: Query<
|
||||||
|
(&mut Physics, &Position, &InstanceName),
|
||||||
|
(With<LocalEntity>, With<InLoadedChunk>),
|
||||||
|
>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
|
) {
|
||||||
|
for (mut physics, position, world_name) 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);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// minecraft adds more entries to the list when the code is running on the
|
||||||
|
// server
|
||||||
|
let movement_this_tick = [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);
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
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(
|
pub fn jump_from_ground(
|
||||||
physics: &mut Physics,
|
physics: &mut Physics,
|
||||||
position: &Position,
|
position: &Position,
|
||||||
|
@ -232,6 +342,12 @@ pub fn jump_from_ground(
|
||||||
physics.has_impulse = true;
|
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 {
|
fn get_block_pos_below_that_affects_movement(position: &Position) -> BlockPos {
|
||||||
BlockPos::new(
|
BlockPos::new(
|
||||||
position.x.floor() as i32,
|
position.x.floor() as i32,
|
||||||
|
@ -241,7 +357,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> {
|
struct HandleRelativeFrictionAndCalculateMovementOpts<'a> {
|
||||||
block_friction: f32,
|
block_friction: f32,
|
||||||
world: &'a Instance,
|
world: &'a Instance,
|
||||||
|
@ -254,7 +370,6 @@ struct HandleRelativeFrictionAndCalculateMovementOpts<'a> {
|
||||||
pose: Option<&'a Pose>,
|
pose: Option<&'a Pose>,
|
||||||
jumping: &'a Jumping,
|
jumping: &'a Jumping,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_relative_friction_and_calculate_movement(
|
fn handle_relative_friction_and_calculate_movement(
|
||||||
HandleRelativeFrictionAndCalculateMovementOpts {
|
HandleRelativeFrictionAndCalculateMovementOpts {
|
||||||
block_friction,
|
block_friction,
|
||||||
|
@ -274,22 +389,22 @@ fn handle_relative_friction_and_calculate_movement(
|
||||||
direction,
|
direction,
|
||||||
get_friction_influenced_speed(physics, attributes, block_friction, is_sprinting),
|
get_friction_influenced_speed(physics, attributes, block_friction, is_sprinting),
|
||||||
&Vec3 {
|
&Vec3 {
|
||||||
x: physics.xxa as f64,
|
x: physics.x_acceleration as f64,
|
||||||
y: physics.yya as f64,
|
y: physics.y_acceleration as f64,
|
||||||
z: physics.zza as f64,
|
z: physics.z_acceleration as f64,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
physics.velocity = handle_on_climbable(physics.velocity, on_climbable, &position, world, pose);
|
physics.velocity = handle_on_climbable(physics.velocity, on_climbable, &position, world, pose);
|
||||||
|
|
||||||
move_colliding(
|
move_colliding(
|
||||||
&MoverType::Own,
|
MoverType::Own,
|
||||||
&physics.velocity.clone(),
|
&physics.velocity.clone(),
|
||||||
world,
|
world,
|
||||||
&mut position,
|
&mut position,
|
||||||
physics,
|
physics,
|
||||||
)
|
)
|
||||||
.expect("Entity should exist.");
|
.expect("Entity should exist");
|
||||||
// let delta_movement = entity.delta;
|
// let delta_movement = entity.delta;
|
||||||
// ladders
|
// ladders
|
||||||
// if ((entity.horizontalCollision || entity.jumping) && (entity.onClimbable()
|
// if ((entity.horizontalCollision || entity.jumping) && (entity.onClimbable()
|
||||||
|
@ -418,369 +533,3 @@ fn jump_boost_power() -> f64 {
|
||||||
// }
|
// }
|
||||||
0.
|
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.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
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,
|
||||||
|
physics,
|
||||||
|
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
|
||||||
|
{
|
||||||
|
-0.003
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub 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);
|
||||||
|
}
|
|
@ -7,7 +7,8 @@ use std::{
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
|
|
||||||
use azalea_block::{BlockState, BlockStateIntegerRepr, FluidState};
|
use azalea_block::block_state::{BlockState, BlockStateIntegerRepr};
|
||||||
|
use azalea_block::fluid_state::FluidState;
|
||||||
use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
|
use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
|
||||||
use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
|
use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
|
||||||
use nohash_hasher::IntMap;
|
use nohash_hasher::IntMap;
|
||||||
|
|
|
@ -56,14 +56,14 @@ impl InstanceContainer {
|
||||||
let existing = existing_lock.read();
|
let existing = existing_lock.read();
|
||||||
if existing.chunks.height != height {
|
if existing.chunks.height != height {
|
||||||
error!(
|
error!(
|
||||||
"Shared dimension height mismatch: {} != {}",
|
"Shared dimension height mismatch: {} != {height}",
|
||||||
existing.chunks.height, height,
|
existing.chunks.height
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if existing.chunks.min_y != min_y {
|
if existing.chunks.min_y != min_y {
|
||||||
error!(
|
error!(
|
||||||
"Shared world min_y mismatch: {} != {}",
|
"Shared world min_y mismatch: {} != {min_y}",
|
||||||
existing.chunks.min_y, min_y,
|
existing.chunks.min_y
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
existing_lock.clone()
|
existing_lock.clone()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use azalea_block::{BlockState, BlockStates};
|
use azalea_block::{block_state::BlockState, BlockStates};
|
||||||
use azalea_core::position::{BlockPos, ChunkPos};
|
use azalea_core::position::{BlockPos, ChunkPos};
|
||||||
|
|
||||||
use crate::{iterators::ChunkIterator, palette::Palette, ChunkStorage, Instance};
|
use crate::{iterators::ChunkIterator, palette::Palette, ChunkStorage, Instance};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::io::{Cursor, Write};
|
use std::io::{Cursor, Write};
|
||||||
|
|
||||||
use azalea_block::BlockStateIntegerRepr;
|
use azalea_block::block_state::BlockStateIntegerRepr;
|
||||||
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
|
use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
|
||||||
use azalea_core::math;
|
use azalea_core::math;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
|
@ -5,7 +5,8 @@ use std::{
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
};
|
};
|
||||||
|
|
||||||
use azalea_block::{BlockState, FluidState};
|
use azalea_block::fluid_state::FluidState;
|
||||||
|
use azalea_block::BlockState;
|
||||||
use azalea_core::position::{BlockPos, ChunkPos};
|
use azalea_core::position::{BlockPos, ChunkPos};
|
||||||
use azalea_core::registry_holder::RegistryHolder;
|
use azalea_core::registry_holder::RegistryHolder;
|
||||||
use bevy_ecs::{component::Component, entity::Entity};
|
use bevy_ecs::{component::Component, entity::Entity};
|
||||||
|
@ -88,8 +89,12 @@ pub struct Instance {
|
||||||
/// An index of all the entities we know are in the chunks of the world
|
/// An index of all the entities we know are in the chunks of the world
|
||||||
pub entities_by_chunk: HashMap<ChunkPos, HashSet<Entity>>,
|
pub entities_by_chunk: HashMap<ChunkPos, HashSet<Entity>>,
|
||||||
|
|
||||||
/// An index of Minecraft entity IDs to Azalea ECS entities. You should
|
/// An index of Minecraft entity IDs to Azalea ECS entities.
|
||||||
/// avoid using this and instead use `azalea_entity::EntityIdIndex`
|
///
|
||||||
|
/// You should avoid using this (particularly if you're using swarms) and
|
||||||
|
/// instead use `azalea_entity::EntityIdIndex`, since some servers may
|
||||||
|
/// give different entity IDs for the same entities to different
|
||||||
|
/// players.
|
||||||
pub entity_by_id: IntMap<MinecraftEntityId, Entity>,
|
pub entity_by_id: IntMap<MinecraftEntityId, Entity>,
|
||||||
|
|
||||||
pub registries: RegistryHolder,
|
pub registries: RegistryHolder,
|
||||||
|
|
|
@ -114,7 +114,20 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
|
||||||
println!("getblock xyz {x} {y} {z}");
|
println!("getblock xyz {x} {y} {z}");
|
||||||
let block_pos = BlockPos::new(x, y, z);
|
let block_pos = BlockPos::new(x, y, z);
|
||||||
let block = source.bot.world().read().get_block_state(&block_pos);
|
let block = source.bot.world().read().get_block_state(&block_pos);
|
||||||
source.reply(&format!("Block at {block_pos:?} is {block:?}"));
|
source.reply(&format!("Block at {block_pos} is {block:?}"));
|
||||||
|
1
|
||||||
|
})),
|
||||||
|
)));
|
||||||
|
commands.register(literal("getfluid").then(argument("x", integer()).then(
|
||||||
|
argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
|
||||||
|
let source = ctx.source.lock();
|
||||||
|
let x = get_integer(ctx, "x").unwrap();
|
||||||
|
let y = get_integer(ctx, "y").unwrap();
|
||||||
|
let z = get_integer(ctx, "z").unwrap();
|
||||||
|
println!("getfluid xyz {x} {y} {z}");
|
||||||
|
let block_pos = BlockPos::new(x, y, z);
|
||||||
|
let block = source.bot.world().read().get_fluid_state(&block_pos);
|
||||||
|
source.reply(&format!("Fluid at {block_pos} is {block:?}"));
|
||||||
1
|
1
|
||||||
})),
|
})),
|
||||||
)));
|
)));
|
||||||
|
|
|
@ -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_client::{inventory::Inventory, Client};
|
||||||
use azalea_entity::{FluidOnEyes, Physics};
|
use azalea_entity::{FluidOnEyes, Physics};
|
||||||
use azalea_inventory::{components, ItemStack, Menu};
|
use azalea_inventory::{components, ItemStack, Menu};
|
||||||
use azalea_registry::Fluid;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BestToolResult {
|
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();
|
let mut physics = Physics::default();
|
||||||
physics.set_on_ground(true);
|
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(
|
pub fn accurate_best_tool_in_hotbar_for_block(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use std::{cell::UnsafeCell, ops::RangeInclusive};
|
use std::{cell::UnsafeCell, ops::RangeInclusive};
|
||||||
|
|
||||||
use azalea_block::{properties::Waterlogged, BlockState, BlockStateIntegerRepr, BlockStates};
|
use azalea_block::{
|
||||||
|
block_state::BlockStateIntegerRepr, properties::Waterlogged, BlockState, BlockStates,
|
||||||
|
};
|
||||||
use azalea_inventory::Menu;
|
use azalea_inventory::Menu;
|
||||||
use nohash_hasher::IntMap;
|
use nohash_hasher::IntMap;
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ impl SimulatedPlayerBundle {
|
||||||
attributes: Attributes {
|
attributes: Attributes {
|
||||||
speed: AttributeInstance::new(0.1),
|
speed: AttributeInstance::new(0.1),
|
||||||
attack_speed: AttributeInstance::new(4.0),
|
attack_speed: AttributeInstance::new(4.0),
|
||||||
|
water_movement_efficiency: AttributeInstance::new(0.0),
|
||||||
},
|
},
|
||||||
inventory: Inventory::default(),
|
inventory: Inventory::default(),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue