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
19pub trait BlockTrait: Debug + Any {
20    fn behavior(&self) -> BlockBehavior;
21    /// Get the Minecraft string ID for this block.
22    ///
23    /// For example, `stone` or `grass_block`.
24    fn id(&self) -> &'static str;
25    /// Convert the block struct to a [`BlockState`].
26    ///
27    /// This is a lossless conversion, as [`BlockState`] also contains state
28    /// data.
29    fn as_block_state(&self) -> BlockState;
30    /// Convert the block struct to a [`BlockKind`].
31    ///
32    /// This is a lossy conversion, as [`BlockKind`] doesn't contain any state
33    /// data.
34    fn as_registry_block(&self) -> BlockKind;
35
36    /// Returns a map of property names on this block to their values as
37    /// strings.
38    ///
39    /// Consider using [`Self::get_property`] if you only need a single
40    /// property.
41    fn property_map(&self) -> HashMap<&'static str, &'static str>;
42    /// Get a property's value as a string by its name, or `None` if the block
43    /// has no property with that name.
44    ///
45    /// To get all properties, you may use [`Self::property_map`].
46    ///
47    /// To set a property, use [`Self::set_property`].
48    fn get_property(&self, name: &str) -> Option<&'static str>;
49    /// Update a property on this block, with the name and value being strings.
50    ///
51    /// Returns `Ok(())`, if the property name and value are valid, otherwise it
52    /// returns `Err(InvalidPropertyError)`.
53    ///
54    /// To get a property, use [`Self::get_property`].
55    fn set_property(&mut self, name: &str, new_value: &str) -> Result<(), InvalidPropertyError>;
56}
57
58#[derive(Debug)]
59pub struct InvalidPropertyError;
60
61impl dyn BlockTrait {
62    pub fn downcast_ref<T: BlockTrait>(&self) -> Option<&T> {
63        (self as &dyn Any).downcast_ref::<T>()
64    }
65}
66
67pub trait Property: FromStr {
68    type Value;
69
70    fn try_from_block_state(state: BlockState) -> Option<Self::Value>;
71
72    /// Convert the value of the property to a string, like "x" or "true".
73    fn to_static_str(&self) -> &'static str;
74}
75
76#[cfg(test)]
77mod tests {
78    use crate::BlockTrait;
79
80    #[test]
81    pub fn roundtrip_block_state() {
82        let block = crate::blocks::OakTrapdoor {
83            facing: crate::properties::FacingCardinal::East,
84            half: crate::properties::TopBottom::Bottom,
85            open: true,
86            powered: false,
87            waterlogged: false,
88        };
89        let block_state = block.as_block_state();
90        let block_from_state = Box::<dyn BlockTrait>::from(block_state);
91        let block_from_state = *block_from_state
92            .downcast_ref::<crate::blocks::OakTrapdoor>()
93            .unwrap();
94        assert_eq!(block, block_from_state);
95    }
96
97    #[test]
98    pub fn test_property_map() {
99        let block = crate::blocks::OakTrapdoor {
100            facing: crate::properties::FacingCardinal::East,
101            half: crate::properties::TopBottom::Bottom,
102            open: true,
103            powered: false,
104            waterlogged: false,
105        };
106
107        let property_map = block.property_map();
108
109        assert_eq!(property_map.len(), 5);
110        assert_eq!(property_map.get("facing"), Some(&"east"));
111        assert_eq!(property_map.get("half"), Some(&"bottom"));
112        assert_eq!(property_map.get("open"), Some(&"true"));
113        assert_eq!(property_map.get("powered"), Some(&"false"));
114        assert_eq!(property_map.get("waterlogged"), Some(&"false"));
115    }
116
117    #[test]
118    pub fn test_integer_properties() {
119        // Test with oak sapling that has an integer-like stage property
120        let sapling_stage_0 = crate::blocks::OakSapling {
121            stage: crate::properties::OakSaplingStage::_0,
122        };
123
124        let sapling_stage_1 = crate::blocks::OakSapling {
125            stage: crate::properties::OakSaplingStage::_1,
126        };
127
128        // Test stage 0
129        let properties_0 = sapling_stage_0.property_map();
130        assert_eq!(properties_0.len(), 1);
131        assert_eq!(properties_0.get("stage"), Some(&"0"));
132        assert_eq!(sapling_stage_0.get_property("stage"), Some("0"));
133
134        // Test stage 1
135        let properties_1 = sapling_stage_1.property_map();
136        assert_eq!(properties_1.len(), 1);
137        assert_eq!(properties_1.get("stage"), Some(&"1"));
138        assert_eq!(sapling_stage_1.get_property("stage"), Some("1"));
139
140        // Test non-existent property
141        assert_eq!(sapling_stage_0.get_property("nonexistent"), None);
142    }
143}