steel_registry/blocks/
block_state_ext.rs1use crate::vanilla_blocks;
2use crate::{
3 REGISTRY,
4 blocks::{
5 self, BlockRef,
6 properties::{BlockStateProperties, Direction, Property},
7 shapes::SupportType,
8 },
9};
10use steel_utils::BlockStateId;
11
12pub trait BlockStateExt {
13 fn get_block(&self) -> BlockRef;
14 fn is_air(&self) -> bool;
15 fn has_block_entity(&self) -> bool;
16 fn get_value<T, P: Property<T>>(&self, property: &P) -> T;
17 fn try_get_value<T, P: Property<T>>(&self, property: &P) -> Option<T>;
19 #[must_use]
20 fn set_value<T, P: Property<T>>(&self, property: &P, value: T) -> BlockStateId;
21 fn get_property_str(&self, name: &str) -> Option<String>;
22 fn get_collision_shape(&self) -> blocks::shapes::VoxelShape;
23 fn get_support_shape(&self) -> blocks::shapes::VoxelShape;
24 fn get_outline_shape(&self) -> blocks::shapes::VoxelShape;
25 fn get_occlusion_shape(&self) -> blocks::shapes::VoxelShape;
26 fn get_interaction_shape(&self) -> blocks::shapes::VoxelShape;
27 fn get_visual_shape(&self) -> blocks::shapes::VoxelShape;
28 fn is_face_sturdy(&self, direction: Direction) -> bool;
31 fn is_face_sturdy_for(&self, direction: Direction, support_type: SupportType) -> bool;
33 fn is_solid(&self) -> bool;
38 fn blocks_motion(&self) -> bool;
42 fn is_solid_render(&self) -> bool;
47 fn is_replaceable(&self) -> bool;
49 fn has_fluid(&self) -> bool;
52}
53
54impl BlockStateExt for BlockStateId {
55 fn get_block(&self) -> BlockRef {
56 REGISTRY
57 .blocks
58 .by_state_id(*self)
59 .expect("Expected a valid state id")
60 }
61
62 fn is_air(&self) -> bool {
63 self.get_block().config.is_air
64 }
65
66 fn has_block_entity(&self) -> bool {
67 false
69 }
70
71 fn get_value<T, P: Property<T>>(&self, property: &P) -> T {
72 REGISTRY.blocks.get_property(*self, property)
73 }
74
75 fn try_get_value<T, P: Property<T>>(&self, property: &P) -> Option<T> {
76 REGISTRY.blocks.try_get_property(*self, property)
77 }
78
79 fn set_value<T, P: Property<T>>(&self, property: &P, value: T) -> BlockStateId {
80 REGISTRY.blocks.set_property(*self, property, value)
81 }
82
83 fn get_property_str(&self, name: &str) -> Option<String> {
84 REGISTRY
85 .blocks
86 .get_properties(*self)
87 .into_iter()
88 .find(|(n, _)| *n == name)
89 .map(|(_, v)| v.to_string())
90 }
91
92 fn get_collision_shape(&self) -> blocks::shapes::VoxelShape {
93 REGISTRY.blocks.get_collision_shape(*self)
94 }
95
96 fn get_support_shape(&self) -> blocks::shapes::VoxelShape {
97 REGISTRY.blocks.get_support_shape(*self)
98 }
99
100 fn get_outline_shape(&self) -> blocks::shapes::VoxelShape {
101 REGISTRY.blocks.get_outline_shape(*self)
102 }
103
104 fn get_occlusion_shape(&self) -> blocks::shapes::VoxelShape {
105 REGISTRY.blocks.get_occlusion_shape(*self)
106 }
107
108 fn get_interaction_shape(&self) -> blocks::shapes::VoxelShape {
109 REGISTRY.blocks.get_interaction_shape(*self)
110 }
111
112 fn get_visual_shape(&self) -> blocks::shapes::VoxelShape {
113 REGISTRY.blocks.get_visual_shape(*self)
114 }
115
116 fn is_face_sturdy(&self, direction: Direction) -> bool {
117 self.is_face_sturdy_for(direction, SupportType::Full)
118 }
119
120 fn is_face_sturdy_for(&self, direction: Direction, support_type: SupportType) -> bool {
121 let shape = self.get_support_shape();
122 blocks::shapes::is_face_sturdy(shape, direction, support_type)
123 }
124
125 fn is_solid(&self) -> bool {
126 let block = self.get_block();
127
128 if block.config.force_solid_on {
130 return true;
131 }
132 if block.config.force_solid_off {
133 return false;
134 }
135
136 let shape = self.get_collision_shape();
140 if shape.is_empty() {
141 return false;
142 }
143 let bounds = blocks::shapes::bounding_box(shape);
144 bounds.size() >= 0.729_166_7 || bounds.height() >= 1.0
145 }
146
147 fn blocks_motion(&self) -> bool {
148 let block = self.get_block();
149 block != &vanilla_blocks::COBWEB
150 && block != &vanilla_blocks::BAMBOO_SAPLING
151 && self.is_solid()
152 }
153
154 fn is_solid_render(&self) -> bool {
155 self.get_block().config.can_occlude
156 && blocks::shapes::is_shape_full_block(self.get_occlusion_shape())
157 }
158
159 fn is_replaceable(&self) -> bool {
160 self.get_block().config.replaceable
161 }
162
163 fn has_fluid(&self) -> bool {
164 self.get_block().config.liquid
165 || self
166 .try_get_value(&BlockStateProperties::WATERLOGGED)
167 .unwrap_or(false)
168 }
169}
170
171pub trait FluidReplaceableExt {
172 fn can_be_replaced_by_fluid(&self, fluid: BlockRef) -> bool;
173}
174
175impl FluidReplaceableExt for BlockStateId {
176 fn can_be_replaced_by_fluid(&self, fluid: BlockRef) -> bool {
177 let block = self.get_block();
178
179 if block == &vanilla_blocks::AIR {
180 return true;
181 }
182
183 if fluid == &vanilla_blocks::WATER
184 && let Some(false) = self.try_get_value(&BlockStateProperties::WATERLOGGED)
185 {
186 return true;
187 }
188
189 block.config.replaceable || !self.is_solid()
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::blocks::properties::BlockStateProperties;
198 use crate::blocks::shapes::SupportType;
199 use crate::test_support::init_test_registry;
200 use steel_utils::Direction;
201
202 #[test]
203 fn solid_render_uses_occlusion_shape_not_collision_shape() {
204 init_test_registry();
205
206 let stone = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::STONE);
207 assert!(stone.is_solid_render());
208
209 let glass = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::GLASS);
210 assert!(blocks::shapes::is_shape_full_block(
211 glass.get_collision_shape()
212 ));
213 assert!(!glass.is_solid_render());
214 }
215
216 #[test]
217 fn blocks_motion_matches_vanilla_base_predicate() {
218 init_test_registry();
219
220 let stone = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::STONE);
221 assert!(stone.blocks_motion());
222
223 let water = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::WATER);
224 assert!(!water.blocks_motion());
225 assert!(water.has_fluid());
226
227 let cobweb = REGISTRY
228 .blocks
229 .get_default_state_id(&vanilla_blocks::COBWEB);
230 assert!(!cobweb.blocks_motion());
231 }
232
233 #[test]
234 fn fence_post_supports_center_attachments_from_below() {
235 init_test_registry();
236
237 let fence = vanilla_blocks::OAK_FENCE
238 .default_state()
239 .set_value(&BlockStateProperties::EAST, true);
240
241 assert!(fence.is_face_sturdy_for(Direction::Down, SupportType::Center));
242 }
243}