1pub mod behavior;
2pub mod block_state_ext;
3pub mod properties;
4pub mod shapes;
5
6use std::sync::OnceLock;
7
8use rustc_hash::FxHashMap;
9
10use crate::blocks::behavior::BlockConfig;
11use crate::blocks::properties::{DynProperty, Property};
12use crate::{RegistryExt, TaggedRegistryExt};
13
14pub type ShapeFn = fn(u16) -> shapes::VoxelShape;
16
17pub struct Block {
18 pub key: Identifier,
19 pub config: BlockConfig,
20 pub properties: &'static [&'static dyn DynProperty],
21 pub default_state_offset: u16,
22 pub collision_shape: ShapeFn,
24 pub support_shape: ShapeFn,
26 pub outline_shape: ShapeFn,
28 pub occlusion_shape: ShapeFn,
30 pub interaction_shape: ShapeFn,
32 pub visual_shape: ShapeFn,
34 pub id: OnceLock<usize>,
36}
37
38impl std::fmt::Debug for Block {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("Block")
41 .field("key", &self.key)
42 .field("config", &self.config)
43 .field("properties", &self.properties)
44 .field("default_state_offset", &self.default_state_offset)
45 .finish_non_exhaustive()
46 }
47}
48
49const fn full_block_shape(_offset: u16) -> shapes::VoxelShape {
51 shapes::VoxelShape::FULL_BLOCK
52}
53
54const fn empty_shape(_offset: u16) -> shapes::VoxelShape {
56 shapes::VoxelShape::EMPTY
57}
58
59impl Block {
60 pub const fn new(
61 key: Identifier,
62 config: BlockConfig,
63 properties: &'static [&'static dyn DynProperty],
64 ) -> Self {
65 Self {
66 key,
67 config,
68 properties,
69 default_state_offset: 0,
70 collision_shape: full_block_shape,
71 support_shape: full_block_shape,
72 outline_shape: full_block_shape,
73 occlusion_shape: full_block_shape,
74 interaction_shape: empty_shape,
75 visual_shape: full_block_shape,
76 id: OnceLock::new(),
77 }
78 }
79
80 pub const fn with_shapes(
82 mut self,
83 collision: ShapeFn,
84 support: ShapeFn,
85 outline: ShapeFn,
86 occlusion: ShapeFn,
87 interaction: ShapeFn,
88 visual: ShapeFn,
89 ) -> Self {
90 self.collision_shape = collision;
91 self.support_shape = support;
92 self.outline_shape = outline;
93 self.occlusion_shape = occlusion;
94 self.interaction_shape = interaction;
95 self.visual_shape = visual;
96 self
97 }
98
99 #[inline]
101 pub fn get_collision_shape(&self, offset: u16) -> shapes::VoxelShape {
102 (self.collision_shape)(offset)
103 }
104
105 #[inline]
107 pub fn get_support_shape(&self, offset: u16) -> shapes::VoxelShape {
108 (self.support_shape)(offset)
109 }
110
111 #[inline]
113 pub fn get_outline_shape(&self, offset: u16) -> shapes::VoxelShape {
114 (self.outline_shape)(offset)
115 }
116
117 #[inline]
119 pub fn get_occlusion_shape(&self, offset: u16) -> shapes::VoxelShape {
120 (self.occlusion_shape)(offset)
121 }
122
123 #[inline]
125 pub fn get_interaction_shape(&self, offset: u16) -> shapes::VoxelShape {
126 (self.interaction_shape)(offset)
127 }
128
129 #[inline]
131 pub fn get_visual_shape(&self, offset: u16) -> shapes::VoxelShape {
132 (self.visual_shape)(offset)
133 }
134
135 pub(crate) const fn with_default_state(mut self, offset: u16) -> Self {
147 self.default_state_offset = offset;
148
149 self
150 }
151
152 #[must_use]
156 pub const fn calculate_offset(property_indices: &[usize], property_counts: &[usize]) -> u16 {
157 let mut offset = 0u16;
158 let mut multiplier = 1u16;
159 let len = property_indices.len();
160
161 let mut i = len;
163 while i > 0 {
164 i -= 1;
165 offset += property_indices[i] as u16 * multiplier;
166 multiplier *= property_counts[i] as u16;
167 }
168
169 offset
170 }
171
172 #[must_use]
173 pub fn default_state(&'static self) -> BlockStateId {
174 crate::REGISTRY.blocks.get_default_state_id(self)
175 }
176
177 pub fn has_tag(&'static self, tag: &Identifier) -> bool {
179 crate::REGISTRY.blocks.is_in_tag(self, tag)
180 }
181}
182
183pub type BlockRef = &'static Block;
184
185impl PartialEq for BlockRef {
186 #[expect(clippy::disallowed_methods)] fn eq(&self, other: &Self) -> bool {
188 std::ptr::eq(*self, *other)
189 }
190}
191
192impl Eq for BlockRef {}
193
194pub struct BlockRegistry {
196 blocks_by_id: Vec<BlockRef>,
197 blocks_by_key: FxHashMap<Identifier, usize>,
198 tags: FxHashMap<Identifier, Vec<Identifier>>,
199 allows_registering: bool,
200 pub state_to_block_lookup: Vec<BlockRef>,
201 pub state_to_block_id: Vec<usize>,
203 pub block_to_base_state: Vec<u16>,
205 pub next_state_id: u16,
207}
208
209impl Default for BlockRegistry {
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215impl BlockRegistry {
216 #[must_use]
218 pub fn new() -> Self {
219 Self {
220 blocks_by_id: Vec::new(),
221 blocks_by_key: FxHashMap::default(),
222 tags: FxHashMap::default(),
223 allows_registering: true,
224 state_to_block_lookup: Vec::new(),
225 state_to_block_id: Vec::new(),
226 block_to_base_state: Vec::new(),
227 next_state_id: 0,
228 }
229 }
230
231 pub fn register(&mut self, block: BlockRef) -> usize {
232 assert!(
233 self.allows_registering,
234 "Cannot register blocks after the registry has been frozen"
235 );
236
237 let id = self.blocks_by_id.len();
238 let base_state_id = self.next_state_id;
239
240 let cached = block.id.get_or_init(|| id);
241 assert_eq!(*cached, id, "block registered with conflicting id");
242 self.blocks_by_key.insert(block.key.clone(), id);
243 self.blocks_by_id.push(block);
244 self.block_to_base_state.push(base_state_id);
245
246 let mut state_count = 1;
247 for property in block.properties {
248 state_count *= property.get_possible_values().len();
249 }
250
251 for _ in 0..state_count {
252 self.state_to_block_lookup.push(block);
253 self.state_to_block_id.push(id);
254 }
255
256 self.next_state_id += state_count as u16;
257
258 id
259 }
260
261 fn try_block_index(&self, block: BlockRef) -> Option<usize> {
262 if let Some(id) = block.id.get().copied()
263 && self
264 .blocks_by_id
265 .get(id)
266 .is_some_and(|registered| *registered == block)
267 {
268 return Some(id);
269 }
270
271 self.blocks_by_key.get(&block.key).copied()
272 }
273
274 fn block_index(&self, block: BlockRef) -> usize {
275 let Some(id) = self.try_block_index(block) else {
276 panic!("Block not found");
277 };
278 id
279 }
280
281 #[must_use]
282 pub fn get_base_state_id(&self, block: BlockRef) -> BlockStateId {
283 let id = self.block_index(block);
284 BlockStateId(self.block_to_base_state[id])
285 }
286
287 #[must_use]
289 pub fn get_default_state_id(&self, block: BlockRef) -> BlockStateId {
290 let id = self.block_index(block);
291 let base = self.block_to_base_state[id];
292 BlockStateId(base + block.default_state_offset)
293 }
294
295 #[must_use]
296 pub fn by_state_id(&self, state_id: BlockStateId) -> Option<BlockRef> {
297 self.state_to_block_lookup.get(state_id.0 as usize).copied()
298 }
299
300 #[must_use]
301 pub fn get_properties(&self, id: BlockStateId) -> Vec<(&'static str, &'static str)> {
302 let block = self.by_state_id(id).expect("Invalid state ID");
303
304 if block.properties.is_empty() {
306 return Vec::new();
307 }
308
309 let block_id = self.state_to_block_id[id.0 as usize];
311 let base_state_id = self.block_to_base_state[block_id];
312
313 let relative_index = id.0 - base_state_id;
315
316 Self::decode_property_indices(block, relative_index)
317 .into_iter()
318 .zip(block.properties)
319 .map(|(value_index, prop)| (prop.get_name(), prop.get_possible_values()[value_index]))
320 .collect()
321 }
322
323 #[must_use]
330 pub fn state_id_from_properties(
331 &self,
332 key: &Identifier,
333 properties: &[(&str, &str)],
334 ) -> Option<BlockStateId> {
335 let block = self.by_key(key)?;
336 self.state_id_from_block_properties(block, properties)
337 }
338
339 #[must_use]
344 pub fn state_id_from_block_properties(
345 &self,
346 block: BlockRef,
347 properties: &[(&str, &str)],
348 ) -> Option<BlockStateId> {
349 let block_id = self.try_block_index(block)?;
350 let base_state_id = self.block_to_base_state[block_id];
351
352 let mut property_indices = vec![0usize; block.properties.len()];
353 Self::apply_property_overrides(block, &mut property_indices, properties.iter().copied())?;
354
355 Some(BlockStateId(
356 base_state_id + Self::encode_property_indices(block, &property_indices),
357 ))
358 }
359
360 #[must_use]
366 pub fn state_id_from_block_defaulted_properties<'a>(
367 &self,
368 block: BlockRef,
369 properties: impl IntoIterator<Item = (&'a str, &'a str)>,
370 ) -> Option<BlockStateId> {
371 let block_id = self.try_block_index(block)?;
372 let base_state_id = self.block_to_base_state[block_id];
373
374 let mut property_indices = Self::decode_property_indices(block, block.default_state_offset);
375 Self::apply_property_overrides(block, &mut property_indices, properties)?;
376
377 Some(BlockStateId(
378 base_state_id + Self::encode_property_indices(block, &property_indices),
379 ))
380 }
381
382 fn decode_property_indices(block: BlockRef, mut offset: u16) -> Vec<usize> {
383 let mut property_indices = vec![0; block.properties.len()];
384
385 for (i, prop) in block.properties.iter().enumerate().rev() {
386 let count = prop.get_possible_values().len() as u16;
387 property_indices[i] = (offset % count) as usize;
388 offset /= count;
389 }
390
391 property_indices
392 }
393
394 fn apply_property_overrides<'a>(
395 block: BlockRef,
396 property_indices: &mut [usize],
397 properties: impl IntoIterator<Item = (&'a str, &'a str)>,
398 ) -> Option<()> {
399 for (prop_name, prop_value) in properties {
400 let prop_idx = block
401 .properties
402 .iter()
403 .position(|p| p.get_name() == prop_name)?;
404
405 let prop = block.properties[prop_idx];
406 let value_idx = prop
407 .get_possible_values()
408 .iter()
409 .position(|v| *v == prop_value)?;
410
411 property_indices[prop_idx] = value_idx;
412 }
413
414 Some(())
415 }
416
417 fn encode_property_indices(block: BlockRef, property_indices: &[usize]) -> u16 {
418 let mut offset = 0u16;
419 let mut multiplier = 1u16;
420 for (idx, prop) in property_indices.iter().zip(block.properties.iter()).rev() {
421 offset += *idx as u16 * multiplier;
422 multiplier *= prop.get_possible_values().len() as u16;
423 }
424
425 offset
426 }
427
428 pub fn get_property<T, P: Property<T>>(&self, id: BlockStateId, property: &P) -> T {
430 self.try_get_property(id, property)
431 .expect("Property not found on this block")
432 }
433
434 #[must_use]
436 pub fn try_get_property<T, P: Property<T>>(&self, id: BlockStateId, property: &P) -> Option<T> {
437 let block = self.by_state_id(id).expect("Invalid state ID");
438
439 let property_index = block
441 .properties
442 .iter()
443 .position(|prop| prop.get_name() == property.as_dyn().get_name())?;
444
445 let block_id = self.state_to_block_id[id.0 as usize];
447 let base_state_id = self.block_to_base_state[block_id];
448
449 let relative_index = id.0 - base_state_id;
451
452 let property_indices = Self::decode_property_indices(block, relative_index);
453 let block_property = block.properties[property_index];
454 let block_values = block_property.get_possible_values();
455 let block_value = block_values[property_indices[property_index]];
456
457 property.get_value(block_value)
458 }
459
460 pub fn set_property<T, P: Property<T>>(
462 &self,
463 id: BlockStateId,
464 property: &P,
465 value: T,
466 ) -> BlockStateId {
467 let block = self.by_state_id(id).expect("Invalid state ID");
468
469 let property_index = block
471 .properties
472 .iter()
473 .position(|prop| prop.get_name() == property.as_dyn().get_name())
474 .unwrap_or_else(|| {
475 panic!(
476 "Property {} not found on block {}",
477 property.as_dyn().get_name(),
478 block.key
479 )
480 });
481
482 let block_id = self.state_to_block_id[id.0 as usize];
484 let base_state_id = self.block_to_base_state[block_id];
485
486 let relative_index = id.0 - base_state_id;
488
489 let mut index = relative_index;
492 let mut property_indices = vec![0usize; block.properties.len()];
493
494 for (i, prop) in block.properties.iter().enumerate().rev() {
495 let count = prop.get_possible_values().len() as u16;
496 property_indices[i] = (index % count) as usize;
497 index /= count;
498 }
499
500 let caller_value_index = property.get_internal_index(&value);
501 let caller_values = property.as_dyn().get_possible_values();
502 let value_name = caller_values[caller_value_index];
503 let block_values = block.properties[property_index].get_possible_values();
504 let Some(new_value_index) = block_values.iter().position(|v| *v == value_name) else {
505 panic!(
506 "Value {} for property {} not found on block {}",
507 value_name,
508 property.as_dyn().get_name(),
509 block.key
510 );
511 };
512 property_indices[property_index] = new_value_index;
513
514 let mut new_relative_index = 0u16;
517 let mut multiplier = 1u16;
518 for (i, prop) in block.properties.iter().enumerate().rev() {
519 let count = prop.get_possible_values().len() as u16;
520 new_relative_index += property_indices[i] as u16 * multiplier;
521 multiplier *= count;
522 }
523
524 BlockStateId(base_state_id + new_relative_index)
525 }
526
527 pub fn iter(&self) -> impl Iterator<Item = (usize, BlockRef)> + '_ {
528 self.blocks_by_id
529 .iter()
530 .enumerate()
531 .map(|(id, &block)| (id, block))
532 }
533}
534
535crate::impl_registry_ext!(BlockRegistry, Block, blocks_by_id, blocks_by_key);
536
537impl crate::RegistryEntry for Block {
538 fn key(&self) -> &Identifier {
539 &self.key
540 }
541
542 fn try_id(&self) -> Option<usize> {
543 self.id.get().copied()
544 }
545}
546crate::impl_tagged_registry!(BlockRegistry, blocks_by_key, "block");
547
548impl BlockRegistry {
550 fn shape_for_state(
551 &self,
552 state_id: BlockStateId,
553 shape: fn(&Block, u16) -> shapes::VoxelShape,
554 ) -> shapes::VoxelShape {
555 let block = self.state_to_block_lookup.get(state_id.0 as usize).copied();
556 let Some(block) = block else {
557 return shapes::VoxelShape::FULL_BLOCK;
558 };
559 let block_id = self
560 .state_to_block_id
561 .get(state_id.0 as usize)
562 .copied()
563 .unwrap_or(0);
564 let base_state = self.block_to_base_state.get(block_id).copied().unwrap_or(0);
565 let offset = state_id.0.saturating_sub(base_state);
566 shape(block, offset)
567 }
568
569 #[must_use]
574 pub fn get_collision_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
575 self.shape_for_state(state_id, Block::get_collision_shape)
576 }
577
578 #[must_use]
583 pub fn get_support_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
584 self.shape_for_state(state_id, Block::get_support_shape)
585 }
586
587 #[must_use]
592 pub fn get_outline_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
593 self.shape_for_state(state_id, Block::get_outline_shape)
594 }
595
596 #[must_use]
601 pub fn get_occlusion_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
602 self.shape_for_state(state_id, Block::get_occlusion_shape)
603 }
604
605 #[must_use]
610 pub fn get_interaction_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
611 self.shape_for_state(state_id, Block::get_interaction_shape)
612 }
613
614 #[must_use]
619 pub fn get_visual_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
620 self.shape_for_state(state_id, Block::get_visual_shape)
621 }
622
623 #[must_use]
625 pub fn get_shapes(&self, state_id: BlockStateId) -> shapes::BlockShapes {
626 shapes::BlockShapes::new(
627 self.get_collision_shape(state_id),
628 self.get_support_shape(state_id),
629 self.get_outline_shape(state_id),
630 self.get_occlusion_shape(state_id),
631 self.get_interaction_shape(state_id),
632 self.get_visual_shape(state_id),
633 )
634 }
635
636 pub fn copy_matching_properties(&self, source: BlockStateId, target: BlockRef) -> BlockStateId {
637 let props = self.get_properties(source);
638 let matching: Vec<(&str, &str)> = props
639 .iter()
640 .filter(|(name, _)| target.properties.iter().any(|p| p.get_name() == *name))
641 .copied()
642 .collect();
643 self.state_id_from_block_properties(target, &matching)
644 .unwrap_or_else(|| self.get_default_state_id(target))
645 }
646}
647
648#[macro_export]
670macro_rules! offset {
671 ($($prop:expr => $value:expr),* $(,)?) => {{
672 const INDICES: &[usize] = &[$($value as usize),*];
673 const COUNTS: &[usize] = &[$($prop.value_count()),*];
674 $crate::blocks::Block::calculate_offset(INDICES, COUNTS)
675 }};
676}
677
678pub use offset;
680use steel_utils::{BlockStateId, Identifier};
681
682#[cfg(test)]
683mod tests {
684 use super::*;
685 use crate::blocks::properties::{BlockStateProperties, Direction};
686 use crate::vanilla_blocks;
687
688 fn create_test_registry() -> BlockRegistry {
689 let mut registry = BlockRegistry::new();
690 vanilla_blocks::register_blocks(&mut registry);
691 registry.freeze();
692 registry
693 }
694
695 #[test]
696 fn test_redstone_wire_properties() {
697 let registry = create_test_registry();
698 let redstone_wire = registry
699 .by_key(&Identifier::vanilla_static("redstone_wire"))
700 .expect("redstone_wire should exist");
701
702 assert_eq!(redstone_wire.properties.len(), 5);
704
705 let prop_names: Vec<&str> = redstone_wire
707 .properties
708 .iter()
709 .map(|p| p.get_name())
710 .collect();
711 assert!(prop_names.contains(&"east"));
712 assert!(prop_names.contains(&"north"));
713 assert!(prop_names.contains(&"south"));
714 assert!(prop_names.contains(&"west"));
715 assert!(prop_names.contains(&"power"));
716 }
717
718 #[test]
719 fn test_redstone_wire_state_count() {
720 let registry = create_test_registry();
721
722 let redstone_wire = registry
725 .by_key(&Identifier::vanilla_static("redstone_wire"))
726 .expect("redstone_wire should exist");
727
728 let mut state_count = 1;
729 for prop in redstone_wire.properties {
730 state_count *= prop.get_possible_values().len();
731 }
732 assert_eq!(state_count, 3 * 3 * 3 * 3 * 16); }
734
735 #[test]
736 fn test_get_properties_default_state() {
737 let registry = create_test_registry();
738 let redstone_wire = registry
739 .by_key(&Identifier::vanilla_static("redstone_wire"))
740 .expect("redstone_wire should exist");
741
742 let default_state = registry.get_default_state_id(redstone_wire);
743 let properties = registry.get_properties(default_state);
744
745 assert_eq!(properties.len(), 5);
747
748 for (name, value) in &properties {
749 match *name {
750 "east" | "north" | "south" | "west" => {
751 assert_eq!(*value, "none", "Default side should be 'none'");
752 }
753 "power" => {
754 assert_eq!(*value, "0", "Default power should be '0'");
755 }
756 _ => panic!("Unexpected property: {}", name),
757 }
758 }
759 }
760
761 #[test]
762 fn test_state_id_from_properties_roundtrip() {
763 let registry = create_test_registry();
764 let key = Identifier::vanilla_static("redstone_wire");
765
766 let properties = [
768 ("east", "up"),
769 ("north", "side"),
770 ("south", "none"),
771 ("west", "up"),
772 ("power", "15"),
773 ];
774
775 let state_id = registry
776 .state_id_from_properties(&key, &properties)
777 .expect("Should find state");
778
779 let retrieved = registry.get_properties(state_id);
781 assert_eq!(retrieved.len(), 5);
782
783 for (name, value) in &properties {
784 let found = retrieved
785 .iter()
786 .find(|(n, _)| n == name)
787 .expect("Property should exist");
788 assert_eq!(found.1, *value, "Property {} mismatch", name);
789 }
790 }
791
792 #[test]
793 fn test_state_id_from_properties_partial() {
794 let registry = create_test_registry();
795 let key = Identifier::vanilla_static("redstone_wire");
796
797 let partial_props = [("power", "10"), ("east", "side")];
799
800 let state_id = registry
801 .state_id_from_properties(&key, &partial_props)
802 .expect("Should find state");
803
804 let retrieved = registry.get_properties(state_id);
805
806 let power = retrieved.iter().find(|(n, _)| *n == "power").unwrap();
808 assert_eq!(power.1, "10");
809
810 let east = retrieved.iter().find(|(n, _)| *n == "east").unwrap();
811 assert_eq!(east.1, "side");
812
813 let north = retrieved.iter().find(|(n, _)| *n == "north").unwrap();
815 assert_eq!(north.1, "up"); }
817
818 #[test]
819 fn test_state_id_from_block_defaulted_properties_keeps_missing_defaults() {
820 let registry = create_test_registry();
821 let key = Identifier::vanilla_static("redstone_wire");
822 let block = registry.by_key(&key).expect("redstone_wire should exist");
823
824 let state_id = registry
825 .state_id_from_block_defaulted_properties(block, [("power", "10")])
826 .expect("Should find state");
827
828 let retrieved = registry.get_properties(state_id);
829
830 let power = retrieved.iter().find(|(n, _)| *n == "power").unwrap();
831 assert_eq!(power.1, "10");
832
833 for direction in ["east", "north", "south", "west"] {
834 let side = retrieved.iter().find(|(n, _)| *n == direction).unwrap();
835 assert_eq!(side.1, "none");
836 }
837 }
838
839 #[test]
840 fn test_state_id_from_properties_empty() {
841 let registry = create_test_registry();
842 let key = Identifier::vanilla_static("redstone_wire");
843
844 let state_id = registry
846 .state_id_from_properties(&key, &[])
847 .expect("Should find state");
848
849 let retrieved = registry.get_properties(state_id);
850
851 for (name, value) in &retrieved {
853 match *name {
854 "east" | "north" | "south" | "west" => {
855 assert_eq!(*value, "up", "Empty props should use index 0 = 'up'");
856 }
857 "power" => {
858 assert_eq!(*value, "0", "Empty props should use index 0 = '0'");
859 }
860 _ => {}
861 }
862 }
863 }
864
865 #[test]
866 fn test_state_id_from_properties_invalid_block() {
867 let registry = create_test_registry();
868 let key = Identifier::vanilla_static("nonexistent_block");
869
870 let result = registry.state_id_from_properties(&key, &[]);
871 assert!(result.is_none(), "Should return None for invalid block");
872 }
873
874 #[test]
875 fn test_state_id_from_properties_invalid_property() {
876 let registry = create_test_registry();
877 let key = Identifier::vanilla_static("redstone_wire");
878
879 let invalid_props = [("invalid_property", "value")];
880 let result = registry.state_id_from_properties(&key, &invalid_props);
881 assert!(result.is_none(), "Should return None for invalid property");
882 }
883
884 #[test]
885 fn test_state_id_from_properties_invalid_value() {
886 let registry = create_test_registry();
887 let key = Identifier::vanilla_static("redstone_wire");
888
889 let invalid_props = [("power", "999")]; let result = registry.state_id_from_properties(&key, &invalid_props);
891 assert!(result.is_none(), "Should return None for invalid value");
892 }
893
894 #[test]
895 fn same_named_direction_properties_translate_by_value_name() {
896 let registry = create_test_registry();
897 let wall_torch = registry.get_default_state_id(&vanilla_blocks::WALL_TORCH);
898
899 let south_torch = registry.set_property(
900 wall_torch,
901 &BlockStateProperties::HORIZONTAL_FACING,
902 Direction::South,
903 );
904 let facing_from_six_way_property =
905 registry.try_get_property(south_torch, &BlockStateProperties::FACING);
906 assert_eq!(facing_from_six_way_property, Some(Direction::South));
907
908 let west_torch =
909 registry.set_property(south_torch, &BlockStateProperties::FACING, Direction::West);
910 let facing_from_horizontal_property =
911 registry.try_get_property(west_torch, &BlockStateProperties::HORIZONTAL_FACING);
912 assert_eq!(facing_from_horizontal_property, Some(Direction::West));
913
914 let dispenser = registry.get_default_state_id(&vanilla_blocks::DISPENSER);
915 let upward_dispenser =
916 registry.set_property(dispenser, &BlockStateProperties::FACING, Direction::Up);
917 let horizontal_facing =
918 registry.try_get_property(upward_dispenser, &BlockStateProperties::HORIZONTAL_FACING);
919 assert_eq!(horizontal_facing, None);
920 }
921
922 #[test]
923 fn test_state_id_from_properties_rejects_properties_on_propertyless_block() {
924 let registry = create_test_registry();
925 let key = Identifier::vanilla_static("stone");
926 let stone = registry.by_key(&key).expect("stone should exist");
927
928 let result = registry.state_id_from_properties(&key, &[("power", "1")]);
929 assert!(
930 result.is_none(),
931 "Should return None for invalid property on propertyless block"
932 );
933
934 let result = registry.state_id_from_block_defaulted_properties(stone, [("power", "1")]);
935 assert!(
936 result.is_none(),
937 "Should return None for invalid defaulted property on propertyless block"
938 );
939 }
940
941 #[test]
942 fn test_stone_no_properties() {
943 let registry = create_test_registry();
944 let key = Identifier::vanilla_static("stone");
945
946 let stone = registry.by_key(&key).expect("stone should exist");
948 assert!(stone.properties.is_empty());
949
950 let state_id = registry
952 .state_id_from_properties(&key, &[])
953 .expect("Should find state");
954
955 let retrieved = registry.get_properties(state_id);
956 assert!(retrieved.is_empty());
957 }
958
959 #[test]
960 fn test_all_redstone_power_levels() {
961 let registry = create_test_registry();
962 let key = Identifier::vanilla_static("redstone_wire");
963
964 for power in 0..=15 {
966 let power_str = power.to_string();
967 let props = [("power", power_str.as_str())];
968
969 let state_id = registry
970 .state_id_from_properties(&key, &props)
971 .unwrap_or_else(|| panic!("Should find state for power {}", power));
972
973 let retrieved = registry.get_properties(state_id);
974 let found_power = retrieved.iter().find(|(n, _)| *n == "power").unwrap();
975 assert_eq!(
976 found_power.1,
977 power_str.as_str(),
978 "Power level {} mismatch",
979 power
980 );
981 }
982 }
983
984 #[test]
985 #[cfg(feature = "minecraft-src")]
986 fn test_all_block_state_ids_match_minecraft() {
987 use rustc_hash::FxHashMap as HashMap;
988 use std::fs;
989
990 #[derive(serde::Deserialize)]
991 struct BlockState {
992 id: u16,
993 #[serde(default)]
994 properties: HashMap<String, String>,
995 #[serde(default)]
996 default: bool,
997 }
998
999 #[derive(serde::Deserialize)]
1000 struct BlockData {
1001 states: Vec<BlockState>,
1002 }
1003
1004 let possible_paths = [
1006 "minecraft-src/minecraft/resources/datagen-reports/blocks.json",
1007 "../minecraft-src/minecraft/resources/datagen-reports/blocks.json",
1008 ];
1009 let json_content = possible_paths
1010 .iter()
1011 .find_map(|path| fs::read_to_string(path).ok())
1012 .expect("Failed to read blocks.json - make sure minecraft-src is available");
1013 let blocks: HashMap<String, BlockData> =
1014 serde_json::from_str(&json_content).expect("Failed to parse blocks.json");
1015
1016 let registry = create_test_registry();
1017 let mut errors = Vec::new();
1018
1019 for (block_name, block_data) in &blocks {
1020 let key = Identifier::vanilla_static(
1022 block_name
1023 .strip_prefix("minecraft:")
1024 .unwrap_or(block_name)
1025 .to_string()
1026 .leak(),
1027 );
1028
1029 let Some(block) = registry.by_key(&key) else {
1030 errors.push(format!("Block {} not found in registry", block_name));
1031 continue;
1032 };
1033
1034 for state in &block_data.states {
1036 if state.default {
1037 let our_default = registry.get_default_state_id(block);
1038 if our_default.0 != state.id {
1039 errors.push(format!(
1040 "{}: default state mismatch - expected {}, got {}",
1041 block_name, state.id, our_default.0
1042 ));
1043 }
1044 }
1045 }
1046
1047 for state in &block_data.states {
1049 let props: Vec<(&str, &str)> = state
1050 .properties
1051 .iter()
1052 .map(|(k, v)| (k.as_str(), v.as_str()))
1053 .collect();
1054
1055 let Some(our_state_id) = registry.state_id_from_properties(&key, &props) else {
1056 errors.push(format!(
1057 "{}: failed to get state for properties {:?}",
1058 block_name, props
1059 ));
1060 continue;
1061 };
1062
1063 if our_state_id.0 != state.id {
1064 errors.push(format!(
1065 "{}: state mismatch for {:?} - expected {}, got {}",
1066 block_name, props, state.id, our_state_id.0
1067 ));
1068 }
1069 }
1070 }
1071
1072 if !errors.is_empty() {
1073 let display_errors: String = errors
1075 .iter()
1076 .take(20)
1077 .cloned()
1078 .collect::<Vec<_>>()
1079 .join("\n");
1080 panic!(
1081 "Found {} state ID mismatches:\n{}{}",
1082 errors.len(),
1083 display_errors,
1084 if errors.len() > 20 {
1085 format!("\n... and {} more", errors.len() - 20)
1086 } else {
1087 String::new()
1088 }
1089 );
1090 }
1091 }
1092}