Skip to main content

azalea_block/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod behavior;
4pub mod block_state;
5pub mod fluid_state;
6mod generated;
7mod range;
8
9use core::fmt::Debug;
10use std::{any::Any, collections::HashMap, str::FromStr};
11
12use azalea_registry::builtin::BlockKind;
13pub use behavior::BlockBehavior;
14// re-exported for convenience
15pub use block_state::BlockState;
16pub use generated::{blocks, properties};
17pub use range::BlockStates;
18
19/// A trait that's implemented on block structs.
20///
21/// See the [azalea_block documentation](crate) for details.
22pub trait BlockTrait: Debug + Any + Send + Sync {
23    /// Convert the block struct to a boxed trait object.
24    #[must_use]
25    fn boxed(&self) -> Box<dyn BlockTrait>;
26
27    /// Get the [`BlockBehavior`] of this block.
28    #[must_use]
29    fn behavior(&self) -> BlockBehavior;
30
31    /// Get the Minecraft string ID for this block.
32    ///
33    /// For example, `stone` or `grass_block`.
34    #[must_use]
35    fn id(&self) -> &'static str;
36
37    /// Convert the block struct to a [`BlockState`].
38    ///
39    /// This is a lossless conversion, as [`BlockState`] also contains state
40    /// data.
41    #[must_use]
42    fn as_block_state(&self) -> BlockState;
43
44    /// Convert the block struct to a [`BlockKind`].
45    ///
46    /// This is a lossy conversion, as [`BlockKind`] doesn't contain any state
47    /// data.
48    #[must_use]
49    fn as_block_kind(&self) -> BlockKind;
50
51    #[deprecated = "renamed to as_block_kind"]
52    #[doc(hidden)]
53    fn as_registry_block(&self) -> BlockKind {
54        self.as_block_kind()
55    }
56
57    /// Returns a map of property names on this block to their values as
58    /// strings.
59    ///
60    /// Consider using [`Self::get_property`] if you only need a single
61    /// property.
62    #[must_use]
63    fn property_map(&self) -> HashMap<&'static str, &'static str>;
64
65    /// Get a property's value as a string by its name, or `None` if the block
66    /// has no property with that name.
67    ///
68    /// To get all properties, you may use [`Self::property_map`].
69    ///
70    /// To set a property, use [`Self::set_property`].
71    #[must_use]
72    fn get_property(&self, name: &str) -> Option<&'static str>;
73
74    /// Update a property on this block, with the name and value being strings.
75    ///
76    /// Returns `Ok(())`, if the property name and value are valid, otherwise it
77    /// returns `Err(InvalidPropertyError)`.
78    ///
79    /// To get a property, use [`Self::get_property`].
80    fn set_property(&mut self, name: &str, new_value: &str) -> Result<(), InvalidPropertyError>;
81}
82
83#[derive(Debug)]
84pub struct InvalidPropertyError;
85
86impl dyn BlockTrait {
87    pub fn downcast_ref<T: BlockTrait>(&self) -> Option<&T> {
88        (self as &dyn Any).downcast_ref::<T>()
89    }
90}
91
92pub trait Property: FromStr {
93    type Value;
94
95    fn try_from_block_state(state: BlockState) -> Option<Self::Value>;
96
97    /// Convert the value of the property to a string, like "x" or "true".
98    fn to_static_str(&self) -> &'static str;
99}
100
101#[cfg(test)]
102mod tests {
103    use crate::BlockTrait;
104
105    #[test]
106    pub fn roundtrip_block_state() {
107        let block = crate::blocks::OakTrapdoor {
108            facing: crate::properties::FacingCardinal::East,
109            half: crate::properties::TopBottom::Bottom,
110            open: true,
111            powered: false,
112            waterlogged: false,
113        };
114        let block_state = block.as_block_state();
115        let block_from_state = *block_state
116            .downcast_ref::<crate::blocks::OakTrapdoor>()
117            .unwrap();
118        assert_eq!(block, block_from_state);
119    }
120
121    #[test]
122    pub fn test_property_map() {
123        let block = crate::blocks::OakTrapdoor {
124            facing: crate::properties::FacingCardinal::East,
125            half: crate::properties::TopBottom::Bottom,
126            open: true,
127            powered: false,
128            waterlogged: false,
129        };
130
131        let property_map = block.property_map();
132
133        assert_eq!(property_map.len(), 5);
134        assert_eq!(property_map.get("facing"), Some(&"east"));
135        assert_eq!(property_map.get("half"), Some(&"bottom"));
136        assert_eq!(property_map.get("open"), Some(&"true"));
137        assert_eq!(property_map.get("powered"), Some(&"false"));
138        assert_eq!(property_map.get("waterlogged"), Some(&"false"));
139    }
140
141    #[test]
142    pub fn test_integer_properties() {
143        // Test with oak sapling that has an integer-like stage property
144        let sapling_stage_0 = crate::blocks::OakSapling {
145            stage: crate::properties::Stage::_0,
146        };
147
148        let sapling_stage_1 = crate::blocks::OakSapling {
149            stage: crate::properties::Stage::_1,
150        };
151
152        // Test stage 0
153        let properties_0 = sapling_stage_0.property_map();
154        assert_eq!(properties_0.len(), 1);
155        assert_eq!(properties_0.get("stage"), Some(&"0"));
156        assert_eq!(sapling_stage_0.get_property("stage"), Some("0"));
157
158        // Test stage 1
159        let properties_1 = sapling_stage_1.property_map();
160        assert_eq!(properties_1.len(), 1);
161        assert_eq!(properties_1.get("stage"), Some(&"1"));
162        assert_eq!(sapling_stage_1.get_property("stage"), Some("1"));
163
164        // Test non-existent property
165        assert_eq!(sapling_stage_0.get_property("nonexistent"), None);
166    }
167}