-
-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
- Loading branch information
Showing
38 changed files
with
2,173 additions
and
1,096 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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), | ||
}), | ||
} | ||
} | ||
} |
Oops, something went wrong.