aboutsummaryrefslogtreecommitdiff
path: root/azalea-block/src
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2025-01-10 16:45:27 -0600
committerGitHub <noreply@github.com>2025-01-10 16:45:27 -0600
commit0d16f01571ec8315f3979eae46981e559ade1cf9 (patch)
treeea43c32a57b0e6a67579d75a134dfbc009d09781 /azalea-block/src
parent615d8f9d2ac56b3244d328587243301da253eafd (diff)
downloadazalea-drasl-0d16f01571ec8315f3979eae46981e559ade1cf9.tar.xz
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
Diffstat (limited to 'azalea-block/src')
-rwxr-xr-xazalea-block/src/behavior.rs9
-rw-r--r--azalea-block/src/block_state.rs156
-rw-r--r--azalea-block/src/fluid_state.rs137
-rwxr-xr-xazalea-block/src/lib.rs241
-rw-r--r--azalea-block/src/range.rs6
5 files changed, 310 insertions, 239 deletions
diff --git a/azalea-block/src/behavior.rs b/azalea-block/src/behavior.rs
index 498ba06f..aeae8a74 100755
--- a/azalea-block/src/behavior.rs
+++ b/azalea-block/src/behavior.rs
@@ -4,6 +4,8 @@ pub struct BlockBehavior {
pub destroy_time: f32,
pub explosion_resistance: f32,
pub requires_correct_tool_for_drops: bool,
+
+ pub force_solid: Option<bool>,
}
impl Default for BlockBehavior {
@@ -14,6 +16,7 @@ impl Default for BlockBehavior {
destroy_time: 0.,
explosion_resistance: 0.,
requires_correct_tool_for_drops: false,
+ force_solid: None,
}
}
}
@@ -52,4 +55,10 @@ impl BlockBehavior {
self.requires_correct_tool_for_drops = true;
self
}
+
+ // TODO: currently unused
+ pub fn force_solid(mut self, force_solid: bool) -> Self {
+ self.force_solid = Some(force_solid);
+ self
+ }
}
diff --git a/azalea-block/src/block_state.rs b/azalea-block/src/block_state.rs
new file mode 100644
index 00000000..6a185ee4
--- /dev/null
+++ b/azalea-block/src/block_state.rs
@@ -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
+ );
+ }
+}
diff --git a/azalea-block/src/fluid_state.rs b/azalea-block/src/fluid_state.rs
new file mode 100644
index 00000000..edceac05
--- /dev/null
+++ b/azalea-block/src/fluid_state.rs
@@ -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),
+ }),
+ }
+ }
+}
diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs
index 4719ef42..bbf3ece7 100755
--- a/azalea-block/src/lib.rs
+++ b/azalea-block/src/lib.rs
@@ -2,18 +2,17 @@
#![feature(trait_upcasting)]
mod behavior;
+pub mod block_state;
+pub mod fluid_state;
mod generated;
mod range;
use core::fmt::Debug;
-use std::{
- any::Any,
- fmt,
- io::{self, Cursor, Write},
-};
+use std::any::Any;
-use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
pub use behavior::BlockBehavior;
+// re-exported for convenience
+pub use block_state::BlockState;
pub use generated::{blocks, properties};
pub use range::BlockStates;
@@ -40,233 +39,3 @@ pub trait Property {
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
- );
- }
-}
diff --git a/azalea-block/src/range.rs b/azalea-block/src/range.rs
index fb6552d5..7182bb65 100644
--- a/azalea-block/src/range.rs
+++ b/azalea-block/src/range.rs
@@ -1,9 +1,9 @@
use std::{
- collections::HashSet,
+ collections::{hash_set, HashSet},
ops::{Add, RangeInclusive},
};
-use crate::{BlockState, BlockStateIntegerRepr};
+use crate::{block_state::BlockStateIntegerRepr, BlockState};
#[derive(Debug, Clone)]
pub struct BlockStates {
@@ -22,7 +22,7 @@ impl From<RangeInclusive<BlockStateIntegerRepr>> for BlockStates {
impl IntoIterator for BlockStates {
type Item = BlockState;
- type IntoIter = std::collections::hash_set::IntoIter<BlockState>;
+ type IntoIter = hash_set::IntoIter<BlockState>;
fn into_iter(self) -> Self::IntoIter {
self.set.into_iter()