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;
14pub use block_state::BlockState;
16pub use generated::{blocks, properties};
17pub use range::BlockStates;
18
19pub trait BlockTrait: Debug + Any + Send + Sync {
23 #[must_use]
25 fn boxed(&self) -> Box<dyn BlockTrait>;
26
27 #[must_use]
29 fn behavior(&self) -> BlockBehavior;
30
31 #[must_use]
35 fn id(&self) -> &'static str;
36
37 #[must_use]
42 fn as_block_state(&self) -> BlockState;
43
44 #[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 #[must_use]
63 fn property_map(&self) -> HashMap<&'static str, &'static str>;
64
65 #[must_use]
72 fn get_property(&self, name: &str) -> Option<&'static str>;
73
74 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 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 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 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 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 assert_eq!(sapling_stage_0.get_property("nonexistent"), None);
166 }
167}