1#![cfg_attr(
2 test,
3 expect(
4 clippy::unwrap_used,
5 reason = "block registry tests assert required vanilla properties are present"
6 )
7)]
8
9pub mod behavior;
10pub mod block_state_ext;
11pub mod properties;
12pub mod shapes;
13
14use std::{slice, sync::OnceLock};
15
16use glam::DVec3;
17use rustc_hash::FxHashMap;
18
19use crate::blocks::behavior::BlockConfig;
20use crate::blocks::properties::Property;
21use crate::blocks::shapes::ShapeChannel;
22use crate::fluid::{FluidRef, FluidState};
23use crate::{RegistryExt, RegistryTags, TaggedRegistryExt};
24use steel_utils::{BlockPos, BlockStateId, DowncastType, DowncastTypeKey};
25
26pub type ShapeFn = fn(u16) -> shapes::VoxelShape;
28pub type LightPropertiesFn = fn(u16) -> BlockLightProperties;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct BlockLightProperties {
33 pub light_emission: u8,
34 pub light_dampening: u8,
35 pub use_shape_for_light_occlusion: bool,
36}
37
38impl BlockLightProperties {
39 pub const OPAQUE_FULL_BLOCK: Self = Self {
40 light_emission: 0,
41 light_dampening: 15,
42 use_shape_for_light_occlusion: false,
43 };
44}
45
46#[derive(Debug, Clone, Copy)]
47pub struct StateBooleanOverwrite {
48 pub offset: u16,
49 pub value: bool,
50}
51
52impl StateBooleanOverwrite {
53 #[must_use]
54 pub const fn new(offset: u16, value: bool) -> Self {
55 Self { offset, value }
56 }
57}
58
59#[derive(Debug, Clone, Copy)]
60pub struct StateBooleanData {
61 pub default: bool,
62 pub overwrites: &'static [StateBooleanOverwrite],
63}
64
65impl StateBooleanData {
66 pub const TRUE: Self = Self::new(true, &[]);
67 pub const FALSE: Self = Self::new(false, &[]);
68
69 #[must_use]
70 pub const fn new(default: bool, overwrites: &'static [StateBooleanOverwrite]) -> Self {
71 Self {
72 default,
73 overwrites,
74 }
75 }
76
77 #[must_use]
78 pub fn value(self, offset: u16) -> bool {
79 self.overwrites
80 .iter()
81 .find(|overwrite| overwrite.offset == offset)
82 .map_or(self.default, |overwrite| overwrite.value)
83 }
84}
85
86#[derive(Debug, Clone, Copy)]
87pub struct StateFluidOverwrite {
88 pub offset: u16,
89 pub value: FluidState,
90}
91
92impl StateFluidOverwrite {
93 #[must_use]
94 pub const fn new(offset: u16, value: FluidState) -> Self {
95 Self { offset, value }
96 }
97}
98
99#[derive(Debug, Clone, Copy)]
100pub struct StateFluidData {
101 pub default: FluidState,
102 pub overwrites: &'static [StateFluidOverwrite],
103}
104
105impl StateFluidData {
106 pub const EMPTY: Self = Self::new(FluidState::EMPTY, &[]);
107
108 #[must_use]
109 pub const fn new(default: FluidState, overwrites: &'static [StateFluidOverwrite]) -> Self {
110 Self {
111 default,
112 overwrites,
113 }
114 }
115
116 #[must_use]
117 pub fn value(self, offset: u16) -> FluidState {
118 self.overwrites
119 .iter()
120 .find(|overwrite| overwrite.offset == offset)
121 .map_or(self.default, |overwrite| overwrite.value)
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub struct BlockStateTickingMetadata {
131 fluid: FluidRef,
132 amount: u8,
133 flags: u8,
134}
135
136impl BlockStateTickingMetadata {
137 const FALLING: u8 = 1 << 0;
138 const RANDOMLY_TICKING_BLOCK: u8 = 1 << 1;
139 const RANDOMLY_TICKING_FLUID: u8 = 1 << 2;
140 const IS_AIR: u8 = 1 << 3;
141 const HAS_FLUID: u8 = 1 << 4;
142
143 #[must_use]
144 pub const fn new(fluid_state: FluidState, randomly_ticking_block: bool, is_air: bool) -> Self {
145 let mut flags = 0;
146 if fluid_state.falling {
147 flags |= Self::FALLING;
148 }
149 if randomly_ticking_block {
150 flags |= Self::RANDOMLY_TICKING_BLOCK;
151 }
152 if fluid_state.fluid_id.is_randomly_ticking {
153 flags |= Self::RANDOMLY_TICKING_FLUID;
154 }
155 if is_air {
156 flags |= Self::IS_AIR;
157 }
158 if !fluid_state.is_empty() {
159 flags |= Self::HAS_FLUID;
160 }
161 Self {
162 fluid: fluid_state.fluid_id,
163 amount: fluid_state.amount,
164 flags,
165 }
166 }
167
168 #[must_use]
169 pub const fn fluid_state(self) -> FluidState {
170 FluidState::new(self.fluid, self.amount, self.flags & Self::FALLING != 0)
171 }
172
173 #[must_use]
174 pub const fn randomly_ticking_block(self) -> bool {
175 self.flags & Self::RANDOMLY_TICKING_BLOCK != 0
176 }
177
178 #[must_use]
179 pub const fn randomly_ticking_fluid(self) -> bool {
180 self.flags & Self::RANDOMLY_TICKING_FLUID != 0
181 }
182
183 #[must_use]
184 pub const fn is_air(self) -> bool {
185 self.flags & Self::IS_AIR != 0
186 }
187
188 #[must_use]
189 pub const fn has_fluid(self) -> bool {
190 self.flags & Self::HAS_FLUID != 0
191 }
192}
193
194pub struct Block {
195 pub key: Identifier,
196 pub config: BlockConfig,
197 pub properties: &'static [&'static dyn Property],
198 pub default_state_offset: u16,
199 pub suffocating: StateBooleanData,
201 pub redstone_conductor: StateBooleanData,
203 pub fluid_state: StateFluidData,
205 pub randomly_ticking: StateBooleanData,
207 pub light_properties: LightPropertiesFn,
209 pub collision_shape: ShapeFn,
211 pub support_shape: ShapeFn,
213 pub outline_shape: ShapeFn,
215 pub occlusion_shape: ShapeFn,
217 pub interaction_shape: ShapeFn,
219 pub visual_shape: ShapeFn,
221 pub shape_offsets: shapes::ShapeOffsetFlags,
223 pub id: OnceLock<usize>,
225}
226
227impl std::fmt::Debug for Block {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 f.debug_struct("Block")
230 .field("key", &self.key)
231 .field("config", &self.config)
232 .field("properties", &self.properties)
233 .field("default_state_offset", &self.default_state_offset)
234 .finish_non_exhaustive()
235 }
236}
237
238const fn full_block_shape(_offset: u16) -> shapes::VoxelShape {
240 shapes::VoxelShape::FULL_BLOCK
241}
242
243const fn opaque_full_block_light_properties(_offset: u16) -> BlockLightProperties {
244 BlockLightProperties::OPAQUE_FULL_BLOCK
245}
246
247const fn empty_shape(_offset: u16) -> shapes::VoxelShape {
249 shapes::VoxelShape::EMPTY
250}
251
252impl Block {
253 pub const fn new(
254 key: Identifier,
255 config: BlockConfig,
256 properties: &'static [&'static dyn Property],
257 ) -> Self {
258 let randomly_ticking = StateBooleanData::new(config.is_randomly_ticking, &[]);
259 Self {
260 key,
261 config,
262 properties,
263 default_state_offset: 0,
264 suffocating: StateBooleanData::TRUE,
265 redstone_conductor: StateBooleanData::TRUE,
266 fluid_state: StateFluidData::EMPTY,
267 randomly_ticking,
268 light_properties: opaque_full_block_light_properties,
269 collision_shape: full_block_shape,
270 support_shape: full_block_shape,
271 outline_shape: full_block_shape,
272 occlusion_shape: full_block_shape,
273 interaction_shape: empty_shape,
274 visual_shape: full_block_shape,
275 shape_offsets: shapes::ShapeOffsetFlags::NONE,
276 id: OnceLock::new(),
277 }
278 }
279
280 pub const fn with_shapes(
282 mut self,
283 collision: ShapeFn,
284 support: ShapeFn,
285 outline: ShapeFn,
286 occlusion: ShapeFn,
287 interaction: ShapeFn,
288 visual: ShapeFn,
289 ) -> Self {
290 self.collision_shape = collision;
291 self.support_shape = support;
292 self.outline_shape = outline;
293 self.occlusion_shape = occlusion;
294 self.interaction_shape = interaction;
295 self.visual_shape = visual;
296 self
297 }
298
299 pub const fn with_suffocating(mut self, suffocating: StateBooleanData) -> Self {
301 self.suffocating = suffocating;
302 self
303 }
304
305 pub const fn with_redstone_conductor(mut self, redstone_conductor: StateBooleanData) -> Self {
307 self.redstone_conductor = redstone_conductor;
308 self
309 }
310
311 pub const fn with_fluid_state(mut self, fluid_state: StateFluidData) -> Self {
313 self.fluid_state = fluid_state;
314 self
315 }
316
317 pub const fn with_randomly_ticking(mut self, randomly_ticking: StateBooleanData) -> Self {
319 self.randomly_ticking = randomly_ticking;
320 self
321 }
322
323 pub const fn with_light_properties(mut self, light_properties: LightPropertiesFn) -> Self {
325 self.light_properties = light_properties;
326 self
327 }
328
329 pub const fn with_shape_offsets(mut self, offsets: shapes::ShapeOffsetFlags) -> Self {
331 self.shape_offsets = offsets;
332 self
333 }
334
335 #[inline]
337 pub fn get_collision_shape(&self, offset: u16) -> shapes::VoxelShape {
338 (self.collision_shape)(offset)
339 }
340
341 #[inline]
343 pub fn get_support_shape(&self, offset: u16) -> shapes::VoxelShape {
344 (self.support_shape)(offset)
345 }
346
347 #[inline]
349 pub fn get_outline_shape(&self, offset: u16) -> shapes::VoxelShape {
350 (self.outline_shape)(offset)
351 }
352
353 #[inline]
355 pub fn get_occlusion_shape(&self, offset: u16) -> shapes::VoxelShape {
356 (self.occlusion_shape)(offset)
357 }
358
359 #[inline]
361 pub fn get_interaction_shape(&self, offset: u16) -> shapes::VoxelShape {
362 (self.interaction_shape)(offset)
363 }
364
365 #[inline]
367 pub fn get_visual_shape(&self, offset: u16) -> shapes::VoxelShape {
368 (self.visual_shape)(offset)
369 }
370
371 #[inline]
372 pub fn get_light_properties(&self, offset: u16) -> BlockLightProperties {
373 (self.light_properties)(offset)
374 }
375
376 #[must_use]
377 pub fn get_ticking_metadata(&self, offset: u16) -> BlockStateTickingMetadata {
378 BlockStateTickingMetadata::new(
379 self.fluid_state.value(offset),
380 self.randomly_ticking.value(offset),
381 self.config.is_air,
382 )
383 }
384
385 #[must_use]
387 pub fn offset_at(&self, pos: BlockPos) -> DVec3 {
388 self.config.offset_at(pos)
389 }
390
391 pub(crate) const fn with_default_state(mut self, offset: u16) -> Self {
403 self.default_state_offset = offset;
404
405 self
406 }
407
408 #[must_use]
412 pub const fn calculate_offset(property_indices: &[usize], property_counts: &[usize]) -> u16 {
413 let mut offset = 0u16;
414 let mut multiplier = 1u16;
415 let len = property_indices.len();
416
417 let mut i = len;
419 while i > 0 {
420 i -= 1;
421 offset += property_indices[i] as u16 * multiplier;
422 multiplier *= property_counts[i] as u16;
423 }
424
425 offset
426 }
427
428 #[must_use]
429 pub fn default_state(&'static self) -> BlockStateId {
430 crate::REGISTRY.blocks.get_default_state_id(self)
431 }
432
433 #[must_use]
435 pub fn state_count(&self) -> u16 {
436 self.properties
437 .iter()
438 .map(|property| property.value_count() as u16)
439 .product()
440 }
441
442 pub fn has_tag(&'static self, tag: &Identifier) -> bool {
444 crate::REGISTRY.blocks.is_in_tag(self, tag)
445 }
446}
447
448pub type BlockRef = &'static Block;
449
450pub struct BlockRegistry {
452 blocks_by_id: Vec<BlockRef>,
453 blocks_by_key: FxHashMap<Identifier, usize>,
454 tags: RegistryTags,
455 allows_registering: bool,
456 pub state_to_block_lookup: Vec<BlockRef>,
457 pub state_to_block_id: Vec<usize>,
459 state_ticking_metadata: Vec<BlockStateTickingMetadata>,
461 pub block_to_base_state: Vec<u16>,
463 pub next_state_id: u16,
465}
466
467unsafe impl DowncastType for BlockRegistry {
469 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:registry/block");
470}
471
472impl Default for BlockRegistry {
473 fn default() -> Self {
474 Self::new()
475 }
476}
477
478struct StateProperties {
479 properties: slice::Iter<'static, &'static dyn Property>,
480 offset: u16,
481 stride: u16,
482}
483
484impl Iterator for StateProperties {
485 type Item = (&'static str, &'static str);
486
487 fn next(&mut self) -> Option<Self::Item> {
488 let property = *self.properties.next()?;
489 let count = property.value_count() as u16;
490 self.stride /= count;
491 let value_index = usize::from(self.offset / self.stride % count);
492 Some((
493 property.get_name(),
494 property.value_name_from_index(value_index),
495 ))
496 }
497
498 fn size_hint(&self) -> (usize, Option<usize>) {
499 self.properties.size_hint()
500 }
501}
502
503impl ExactSizeIterator for StateProperties {}
504
505impl BlockRegistry {
506 #[must_use]
508 pub fn new() -> Self {
509 Self {
510 blocks_by_id: Vec::new(),
511 blocks_by_key: FxHashMap::default(),
512 tags: RegistryTags::default(),
513 allows_registering: true,
514 state_to_block_lookup: Vec::new(),
515 state_to_block_id: Vec::new(),
516 state_ticking_metadata: Vec::new(),
517 block_to_base_state: Vec::new(),
518 next_state_id: 0,
519 }
520 }
521
522 pub fn register(&mut self, block: BlockRef) -> usize {
523 assert!(
524 self.allows_registering,
525 "Cannot register blocks after the registry has been frozen"
526 );
527
528 let id = self.blocks_by_id.len();
529 let base_state_id = self.next_state_id;
530
531 let cached = block.id.get_or_init(|| id);
532 assert_eq!(*cached, id, "block registered with conflicting id");
533 self.blocks_by_key.insert(block.key.clone(), id);
534 self.blocks_by_id.push(block);
535 self.block_to_base_state.push(base_state_id);
536
537 let state_count = block.state_count();
538 for offset in 0..state_count {
539 self.state_to_block_lookup.push(block);
540 self.state_to_block_id.push(id);
541 self.state_ticking_metadata
542 .push(block.get_ticking_metadata(offset));
543 }
544
545 self.next_state_id += state_count;
546
547 id
548 }
549
550 fn try_block_index(&self, block: BlockRef) -> Option<usize> {
551 if let Some(id) = block.id.get().copied()
552 && self
553 .blocks_by_id
554 .get(id)
555 .is_some_and(|registered| *registered == block)
556 {
557 return Some(id);
558 }
559
560 self.blocks_by_key.get(&block.key).copied()
561 }
562
563 fn block_index(&self, block: BlockRef) -> usize {
564 let Some(id) = self.try_block_index(block) else {
565 panic!("Block not found");
566 };
567 id
568 }
569
570 #[must_use]
571 pub fn get_base_state_id(&self, block: BlockRef) -> BlockStateId {
572 let id = self.block_index(block);
573 BlockStateId(self.block_to_base_state[id])
574 }
575
576 #[must_use]
578 pub fn get_default_state_id(&self, block: BlockRef) -> BlockStateId {
579 let id = self.block_index(block);
580 let base = self.block_to_base_state[id];
581 BlockStateId(base + block.default_state_offset)
582 }
583
584 #[must_use]
585 pub fn by_state_id(&self, state_id: BlockStateId) -> Option<BlockRef> {
586 self.state_to_block_lookup.get(state_id.0 as usize).copied()
587 }
588
589 #[must_use]
590 pub fn get_ticking_metadata(
591 &self,
592 state_id: BlockStateId,
593 ) -> Option<BlockStateTickingMetadata> {
594 self.state_ticking_metadata
595 .get(state_id.0 as usize)
596 .copied()
597 }
598
599 #[inline]
604 pub fn properties(
605 &self,
606 id: BlockStateId,
607 ) -> impl ExactSizeIterator<Item = (&'static str, &'static str)> + '_ {
608 let Some(block) = self.by_state_id(id) else {
609 panic!("Invalid state ID");
610 };
611 if block.properties.is_empty() {
612 return StateProperties {
613 properties: block.properties.iter(),
614 offset: 0,
615 stride: 1,
616 };
617 }
618 let block_id = self.state_to_block_id[id.0 as usize];
619 let base_state_id = self.block_to_base_state[block_id];
620 let relative_index = id.0 - base_state_id;
621 StateProperties {
622 properties: block.properties.iter(),
623 offset: relative_index,
624 stride: block.state_count(),
625 }
626 }
627
628 #[must_use]
633 #[inline]
634 pub fn get_property_str(&self, id: BlockStateId, name: &str) -> Option<&'static str> {
635 let Some(block) = self.by_state_id(id) else {
636 panic!("Invalid state ID");
637 };
638 if block.properties.is_empty() {
639 return None;
640 }
641 self.find_property_str(block, id, name)
642 }
643
644 fn find_property_str(
645 &self,
646 block: BlockRef,
647 id: BlockStateId,
648 name: &str,
649 ) -> Option<&'static str> {
650 let property_index = block
651 .properties
652 .iter()
653 .position(|property| property.get_name() == name)?;
654 let property = block.properties[property_index];
655 let block_id = self.state_to_block_id[id.0 as usize];
656 let relative_index = id.0 - self.block_to_base_state[block_id];
657 let stride = Self::property_stride(block, property_index);
658 let value_index = usize::from(relative_index / stride % property.value_count() as u16);
659 Some(property.value_name_from_index(value_index))
660 }
661
662 #[must_use]
669 pub fn state_id_from_properties(
670 &self,
671 key: &Identifier,
672 properties: &[(&str, &str)],
673 ) -> Option<BlockStateId> {
674 let block = self.by_key(key)?;
675 self.state_id_from_block_properties(block, properties)
676 }
677
678 #[must_use]
683 pub fn state_id_from_block_properties(
684 &self,
685 block: BlockRef,
686 properties: &[(&str, &str)],
687 ) -> Option<BlockStateId> {
688 let block_id = self.try_block_index(block)?;
689 let base_state_id = self.block_to_base_state[block_id];
690
691 let mut property_indices = vec![0usize; block.properties.len()];
692 Self::apply_property_overrides(block, &mut property_indices, properties.iter().copied())?;
693
694 Some(BlockStateId(
695 base_state_id + Self::encode_property_indices(block, &property_indices),
696 ))
697 }
698
699 #[must_use]
705 pub fn state_id_from_block_defaulted_properties<'a>(
706 &self,
707 block: BlockRef,
708 properties: impl IntoIterator<Item = (&'a str, &'a str)>,
709 ) -> Option<BlockStateId> {
710 let block_id = self.try_block_index(block)?;
711 let base_state_id = self.block_to_base_state[block_id];
712
713 let mut property_indices = Self::decode_property_indices(block, block.default_state_offset);
714 Self::apply_property_overrides(block, &mut property_indices, properties)?;
715
716 Some(BlockStateId(
717 base_state_id + Self::encode_property_indices(block, &property_indices),
718 ))
719 }
720
721 #[must_use]
727 pub fn try_set_property_by_name(
728 &self,
729 id: BlockStateId,
730 name: &str,
731 value: &str,
732 ) -> Option<BlockStateId> {
733 let block = self.by_state_id(id)?;
734 let block_id = *self.state_to_block_id.get(id.0 as usize)?;
735 let base_state_id = *self.block_to_base_state.get(block_id)?;
736 let property_index = block
737 .properties
738 .iter()
739 .position(|property| property.get_name() == name)?;
740 let property = block.properties[property_index];
741 let new_value_index = (0..property.value_count())
742 .find(|&index| property.value_name_from_index(index) == value)?;
743 let relative_index = id.0.checked_sub(base_state_id)?;
744 let stride = Self::property_stride(block, property_index);
745 let old_value_index = usize::from(relative_index / stride % property.value_count() as u16);
746 let new_relative_index = if new_value_index >= old_value_index {
747 relative_index.checked_add((new_value_index - old_value_index) as u16 * stride)?
748 } else {
749 relative_index.checked_sub((old_value_index - new_value_index) as u16 * stride)?
750 };
751
752 Some(BlockStateId(base_state_id + new_relative_index))
753 }
754
755 #[must_use]
760 pub fn matching_states(&self, block: BlockRef, filter: &[(&str, &str)]) -> Vec<BlockStateId> {
761 let block_id = self.block_index(block);
762 let base_state_id = self.block_to_base_state[block_id];
763
764 (0..block.state_count())
765 .map(|offset| BlockStateId(base_state_id + offset))
766 .filter(|&state_id| {
767 filter
768 .iter()
769 .all(|(name, value)| self.get_property_str(state_id, name) == Some(*value))
770 })
771 .collect()
772 }
773
774 fn decode_property_indices(block: BlockRef, mut offset: u16) -> Vec<usize> {
775 let mut property_indices = vec![0; block.properties.len()];
776
777 for (i, prop) in block.properties.iter().enumerate().rev() {
778 let count = prop.value_count() as u16;
779 property_indices[i] = (offset % count) as usize;
780 offset /= count;
781 }
782
783 property_indices
784 }
785
786 fn apply_property_overrides<'a>(
787 block: BlockRef,
788 property_indices: &mut [usize],
789 properties: impl IntoIterator<Item = (&'a str, &'a str)>,
790 ) -> Option<()> {
791 for (prop_name, prop_value) in properties {
792 let prop_idx = block
793 .properties
794 .iter()
795 .position(|p| p.get_name() == prop_name)?;
796
797 let prop = block.properties[prop_idx];
798 let value_idx = (0..prop.value_count())
799 .find(|&index| prop.value_name_from_index(index) == prop_value)?;
800
801 property_indices[prop_idx] = value_idx;
802 }
803
804 Some(())
805 }
806
807 fn encode_property_indices(block: BlockRef, property_indices: &[usize]) -> u16 {
808 let mut offset = 0u16;
809 let mut multiplier = 1u16;
810 for (idx, prop) in property_indices.iter().zip(block.properties.iter()).rev() {
811 offset += *idx as u16 * multiplier;
812 multiplier *= prop.value_count() as u16;
813 }
814
815 offset
816 }
817
818 fn property_stride(block: BlockRef, property_index: usize) -> u16 {
819 block.properties[property_index + 1..]
820 .iter()
821 .map(|property| property.value_count() as u16)
822 .product()
823 }
824
825 pub fn get_property<P: Property>(&self, id: BlockStateId, property: &P) -> P::Value {
827 self.try_get_property(id, property)
828 .expect("Property not found on this block")
829 }
830
831 #[must_use]
833 pub fn try_get_property<P: Property>(
834 &self,
835 id: BlockStateId,
836 property: &P,
837 ) -> Option<P::Value> {
838 let block = self.by_state_id(id).expect("Invalid state ID");
839
840 let property_index = block
842 .properties
843 .iter()
844 .position(|prop| prop.get_name() == property.get_name())?;
845
846 let block_id = self.state_to_block_id[id.0 as usize];
848 let base_state_id = self.block_to_base_state[block_id];
849
850 let relative_index = id.0 - base_state_id;
852
853 let block_property = block.properties[property_index];
854 let stride = Self::property_stride(block, property_index);
855 let value_index =
856 usize::from(relative_index / stride % block_property.value_count() as u16);
857 let block_value = block_property.value_name_from_index(value_index);
858
859 property.get_value(block_value)
860 }
861
862 pub fn set_property<P: Property>(
864 &self,
865 id: BlockStateId,
866 property: &P,
867 value: P::Value,
868 ) -> BlockStateId {
869 let block = self.by_state_id(id).expect("Invalid state ID");
870
871 let property_index = block
873 .properties
874 .iter()
875 .position(|prop| prop.get_name() == property.get_name())
876 .unwrap_or_else(|| {
877 panic!(
878 "Property {} not found on block {}",
879 property.get_name(),
880 block.key
881 )
882 });
883
884 let block_id = self.state_to_block_id[id.0 as usize];
886 let base_state_id = self.block_to_base_state[block_id];
887
888 let relative_index = id.0 - base_state_id;
890
891 let caller_value_index = property.get_internal_index(&value);
892 let value_name = property.value_name_from_index(caller_value_index);
893 let block_property = block.properties[property_index];
894 let Some(new_value_index) = (0..block_property.value_count())
895 .find(|&index| block_property.value_name_from_index(index) == value_name)
896 else {
897 panic!(
898 "Value {} for property {} not found on block {}",
899 value_name,
900 property.get_name(),
901 block.key
902 );
903 };
904 let stride = Self::property_stride(block, property_index);
905 let old_value_index =
906 usize::from(relative_index / stride % block_property.value_count() as u16);
907 let new_relative_index = if new_value_index >= old_value_index {
908 relative_index + (new_value_index - old_value_index) as u16 * stride
909 } else {
910 relative_index - (old_value_index - new_value_index) as u16 * stride
911 };
912
913 BlockStateId(base_state_id + new_relative_index)
914 }
915
916 pub fn iter(&self) -> impl Iterator<Item = (usize, BlockRef)> + '_ {
917 self.blocks_by_id
918 .iter()
919 .enumerate()
920 .map(|(id, &block)| (id, block))
921 }
922}
923
924crate::impl_registry_ext!(BlockRegistry, Block, blocks_by_id, blocks_by_key);
925
926crate::impl_registry_entry_eq!(Block);
927
928impl crate::RegistryEntry for Block {
929 fn key(&self) -> &Identifier {
930 &self.key
931 }
932
933 fn try_id(&self) -> Option<usize> {
934 self.id.get().copied()
935 }
936}
937crate::impl_tagged_registry!(BlockRegistry, blocks_by_key, "block");
938
939impl BlockRegistry {
941 fn block_and_state_offset(&self, state_id: BlockStateId) -> Option<(BlockRef, u16)> {
942 let block = self
943 .state_to_block_lookup
944 .get(state_id.0 as usize)
945 .copied()?;
946 let block_id = self
947 .state_to_block_id
948 .get(state_id.0 as usize)
949 .copied()
950 .unwrap_or(0);
951 let base_state = self.block_to_base_state.get(block_id).copied().unwrap_or(0);
952 let offset = state_id.0.saturating_sub(base_state);
953 Some((block, offset))
954 }
955
956 fn static_shape_for_state(
957 &self,
958 state_id: BlockStateId,
959 shape: fn(&Block, u16) -> shapes::VoxelShape,
960 ) -> shapes::VoxelShape {
961 let Some((block, offset)) = self.block_and_state_offset(state_id) else {
962 return shapes::VoxelShape::FULL_BLOCK;
963 };
964 shape(block, offset)
965 }
966
967 fn offset_shape_for_state(
968 &self,
969 state_id: BlockStateId,
970 pos: BlockPos,
971 channel: ShapeChannel,
972 shape: fn(&Block, u16) -> shapes::VoxelShape,
973 ) -> shapes::OffsetVoxelShape {
974 let Some((block, offset)) = self.block_and_state_offset(state_id) else {
975 return shapes::OffsetVoxelShape::without_offset(shapes::VoxelShape::FULL_BLOCK);
976 };
977
978 let shape = shape(block, offset);
979 let offset = if block.shape_offsets.uses_offset(channel) {
980 block.offset_at(pos)
981 } else {
982 DVec3::ZERO
983 };
984 shapes::OffsetVoxelShape::new(shape, offset)
985 }
986
987 #[must_use]
992 pub fn get_static_collision_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
993 self.static_shape_for_state(state_id, Block::get_collision_shape)
994 }
995
996 #[must_use]
997 pub fn is_suffocating(&self, state_id: BlockStateId) -> bool {
998 let Some((block, offset)) = self.block_and_state_offset(state_id) else {
999 return false;
1000 };
1001 block.suffocating.value(offset)
1002 }
1003
1004 #[must_use]
1008 pub fn is_static_redstone_conductor(&self, state_id: BlockStateId) -> bool {
1009 let Some((block, offset)) = self.block_and_state_offset(state_id) else {
1010 return false;
1011 };
1012 block.redstone_conductor.value(offset)
1013 }
1014
1015 #[must_use]
1016 pub fn get_collision_shape_at(
1017 &self,
1018 state_id: BlockStateId,
1019 pos: BlockPos,
1020 ) -> shapes::OffsetVoxelShape {
1021 self.offset_shape_for_state(
1022 state_id,
1023 pos,
1024 ShapeChannel::Collision,
1025 Block::get_collision_shape,
1026 )
1027 }
1028
1029 #[must_use]
1034 pub fn get_static_support_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
1035 self.static_shape_for_state(state_id, Block::get_support_shape)
1036 }
1037
1038 #[must_use]
1039 pub fn get_support_shape_at(
1040 &self,
1041 state_id: BlockStateId,
1042 pos: BlockPos,
1043 ) -> shapes::OffsetVoxelShape {
1044 self.offset_shape_for_state(
1045 state_id,
1046 pos,
1047 ShapeChannel::Support,
1048 Block::get_support_shape,
1049 )
1050 }
1051
1052 #[must_use]
1057 pub fn get_static_outline_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
1058 self.static_shape_for_state(state_id, Block::get_outline_shape)
1059 }
1060
1061 #[must_use]
1062 pub fn get_outline_shape_at(
1063 &self,
1064 state_id: BlockStateId,
1065 pos: BlockPos,
1066 ) -> shapes::OffsetVoxelShape {
1067 self.offset_shape_for_state(
1068 state_id,
1069 pos,
1070 ShapeChannel::Outline,
1071 Block::get_outline_shape,
1072 )
1073 }
1074
1075 #[must_use]
1080 pub fn get_occlusion_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
1081 self.static_shape_for_state(state_id, Block::get_occlusion_shape)
1082 }
1083
1084 #[must_use]
1089 pub fn get_static_interaction_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
1090 self.static_shape_for_state(state_id, Block::get_interaction_shape)
1091 }
1092
1093 #[must_use]
1094 pub fn get_interaction_shape_at(
1095 &self,
1096 state_id: BlockStateId,
1097 pos: BlockPos,
1098 ) -> shapes::OffsetVoxelShape {
1099 self.offset_shape_for_state(
1100 state_id,
1101 pos,
1102 ShapeChannel::Interaction,
1103 Block::get_interaction_shape,
1104 )
1105 }
1106
1107 #[must_use]
1112 pub fn get_static_visual_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
1113 self.static_shape_for_state(state_id, Block::get_visual_shape)
1114 }
1115
1116 #[must_use]
1117 pub fn get_visual_shape_at(
1118 &self,
1119 state_id: BlockStateId,
1120 pos: BlockPos,
1121 ) -> shapes::OffsetVoxelShape {
1122 self.offset_shape_for_state(state_id, pos, ShapeChannel::Visual, Block::get_visual_shape)
1123 }
1124
1125 #[must_use]
1127 pub fn get_static_shapes(&self, state_id: BlockStateId) -> shapes::BlockShapes {
1128 shapes::BlockShapes::new(
1129 self.get_static_collision_shape(state_id),
1130 self.get_static_support_shape(state_id),
1131 self.get_static_outline_shape(state_id),
1132 self.get_occlusion_shape(state_id),
1133 self.get_static_interaction_shape(state_id),
1134 self.get_static_visual_shape(state_id),
1135 )
1136 }
1137
1138 #[must_use]
1139 pub fn get_light_properties(&self, state_id: BlockStateId) -> BlockLightProperties {
1140 let Some((block, offset)) = self.block_and_state_offset(state_id) else {
1141 return BlockLightProperties::OPAQUE_FULL_BLOCK;
1142 };
1143 block.get_light_properties(offset)
1144 }
1145
1146 pub fn copy_matching_properties(&self, source: BlockStateId, target: BlockRef) -> BlockStateId {
1147 self.with_properties_of(self.get_default_state_id(target), source)
1148 }
1149
1150 #[must_use]
1152 pub fn with_properties_of(&self, target: BlockStateId, source: BlockStateId) -> BlockStateId {
1153 let Some((target_block, target_offset)) = self.block_and_state_offset(target) else {
1154 panic!("cannot copy properties onto invalid block state {target:?}");
1155 };
1156
1157 let Some((source_block, source_offset)) = self.block_and_state_offset(source) else {
1158 panic!("cannot copy properties from invalid block state {source:?}");
1159 };
1160
1161 let mut target_indices = Self::decode_property_indices(target_block, target_offset);
1162 let source_indices = Self::decode_property_indices(source_block, source_offset);
1163
1164 for (target_index, target_property) in target_block.properties.iter().enumerate() {
1165 let Some(source_index) = source_block.properties.iter().position(|source_property| {
1166 source_property.get_name() == target_property.get_name()
1167 && source_property.value_count() == target_property.value_count()
1168 && (0..target_property.value_count()).all(|index| {
1169 source_property.value_name_from_index(index)
1170 == target_property.value_name_from_index(index)
1171 })
1172 }) else {
1173 continue;
1174 };
1175
1176 target_indices[target_index] = source_indices[source_index];
1177 }
1178
1179 BlockStateId(
1180 target.0 - target_offset + Self::encode_property_indices(target_block, &target_indices),
1181 )
1182 }
1183}
1184
1185#[macro_export]
1207macro_rules! offset {
1208 ($($prop:expr => $value:expr),* $(,)?) => {{
1209 const INDICES: &[usize] = &[$($value as usize),*];
1210 const COUNTS: &[usize] = &[$($prop.value_count()),*];
1211 $crate::blocks::Block::calculate_offset(INDICES, COUNTS)
1212 }};
1213}
1214
1215pub use offset;
1217use steel_utils::Identifier;
1218
1219#[cfg(test)]
1220mod tests {
1221 use super::*;
1222 use crate::blocks::properties::{BlockStateProperties, Direction};
1223 use crate::vanilla_blocks;
1224
1225 fn create_test_registry() -> BlockRegistry {
1226 let mut registry = BlockRegistry::new();
1227 vanilla_blocks::register_blocks(&mut registry);
1228 registry.freeze();
1229 registry
1230 }
1231
1232 #[test]
1233 fn tag_modification_keeps_order_and_membership_in_sync() {
1234 let mut registry = BlockRegistry::new();
1235 vanilla_blocks::register_blocks(&mut registry);
1236 let tag = Identifier::new_static("test", "ordered_membership");
1237 registry.register_tag(
1238 Identifier::new_static("test", "ordered_membership"),
1239 &["stone", "dirt"],
1240 );
1241
1242 assert!(registry.is_in_tag(&vanilla_blocks::STONE, &tag));
1243 assert_eq!(
1244 registry
1245 .iter_tag(&tag)
1246 .map(|block| block.key.path.as_ref())
1247 .collect::<Vec<_>>(),
1248 ["stone", "dirt"]
1249 );
1250
1251 registry.modify_tag(&tag, |_| {
1252 vec![
1253 Identifier::vanilla_static("oak_log"),
1254 Identifier::vanilla_static("dirt"),
1255 ]
1256 });
1257 registry.freeze();
1258
1259 assert!(!registry.is_in_tag(&vanilla_blocks::STONE, &tag));
1260 assert!(registry.is_in_tag(&vanilla_blocks::OAK_LOG, &tag));
1261 assert!(registry.is_in_tag(&vanilla_blocks::DIRT, &tag));
1262 assert_eq!(
1263 registry
1264 .iter_tag(&tag)
1265 .map(|block| block.key.path.as_ref())
1266 .collect::<Vec<_>>(),
1267 ["oak_log", "dirt"]
1268 );
1269 }
1270
1271 #[test]
1272 fn test_redstone_wire_properties() {
1273 let registry = create_test_registry();
1274 let redstone_wire = registry
1275 .by_key(&Identifier::vanilla_static("redstone_wire"))
1276 .expect("redstone_wire should exist");
1277
1278 assert_eq!(redstone_wire.properties.len(), 5);
1280
1281 let prop_names: Vec<&str> = redstone_wire
1283 .properties
1284 .iter()
1285 .map(|p| p.get_name())
1286 .collect();
1287 assert!(prop_names.contains(&"east"));
1288 assert!(prop_names.contains(&"north"));
1289 assert!(prop_names.contains(&"south"));
1290 assert!(prop_names.contains(&"west"));
1291 assert!(prop_names.contains(&"power"));
1292 }
1293
1294 #[test]
1295 fn test_redstone_wire_state_count() {
1296 let registry = create_test_registry();
1297
1298 let redstone_wire = registry
1301 .by_key(&Identifier::vanilla_static("redstone_wire"))
1302 .expect("redstone_wire should exist");
1303
1304 let mut state_count = 1;
1305 for prop in redstone_wire.properties {
1306 state_count *= prop.get_possible_value_names().len();
1307 }
1308 assert_eq!(state_count, 3 * 3 * 3 * 3 * 16); }
1310
1311 #[test]
1312 fn property_iterator_preserves_mixed_radix_values_when_skipping() {
1313 let registry = create_test_registry();
1314 let block = registry
1315 .by_key(&Identifier::vanilla_static("oak_stairs"))
1316 .expect("oak_stairs should exist");
1317 let base = registry.get_base_state_id(block);
1318
1319 for offset in 0..block.state_count() {
1320 let state = BlockStateId(base.0 + offset);
1321 let indices = BlockRegistry::decode_property_indices(block, offset);
1322 let expected = block
1323 .properties
1324 .iter()
1325 .zip(indices)
1326 .map(|(property, index)| {
1327 (property.get_name(), property.value_name_from_index(index))
1328 })
1329 .collect::<Vec<_>>();
1330
1331 assert_eq!(registry.properties(state).collect::<Vec<_>>(), expected);
1332 for (skip, &value) in expected.iter().enumerate() {
1333 assert_eq!(registry.properties(state).nth(skip), Some(value));
1334 }
1335 assert_eq!(registry.properties(state).last(), expected.last().copied());
1336 }
1337 }
1338
1339 #[test]
1340 fn test_properties_default_state() {
1341 let registry = create_test_registry();
1342 let redstone_wire = registry
1343 .by_key(&Identifier::vanilla_static("redstone_wire"))
1344 .expect("redstone_wire should exist");
1345
1346 let default_state = registry.get_default_state_id(redstone_wire);
1347 let properties = registry.properties(default_state).collect::<Vec<_>>();
1348
1349 assert_eq!(properties.len(), 5);
1351
1352 for (name, value) in &properties {
1353 match *name {
1354 "east" | "north" | "south" | "west" => {
1355 assert_eq!(*value, "none", "Default side should be 'none'");
1356 }
1357 "power" => {
1358 assert_eq!(*value, "0", "Default power should be '0'");
1359 }
1360 _ => panic!("Unexpected property: {name}"),
1361 }
1362 }
1363 }
1364
1365 #[test]
1366 fn test_state_id_from_properties_roundtrip() {
1367 let registry = create_test_registry();
1368 let key = Identifier::vanilla_static("redstone_wire");
1369
1370 let properties = [
1372 ("east", "up"),
1373 ("north", "side"),
1374 ("south", "none"),
1375 ("west", "up"),
1376 ("power", "15"),
1377 ];
1378
1379 let state_id = registry
1380 .state_id_from_properties(&key, &properties)
1381 .expect("Should find state");
1382
1383 let retrieved = registry.properties(state_id).collect::<Vec<_>>();
1385 assert_eq!(retrieved.len(), 5);
1386
1387 for (name, value) in &properties {
1388 let found = retrieved
1389 .iter()
1390 .find(|(n, _)| n == name)
1391 .expect("Property should exist");
1392 assert_eq!(found.1, *value, "Property {name} mismatch");
1393 }
1394 }
1395
1396 #[test]
1397 fn test_state_id_from_properties_partial() {
1398 let registry = create_test_registry();
1399 let key = Identifier::vanilla_static("redstone_wire");
1400
1401 let partial_props = [("power", "10"), ("east", "side")];
1403
1404 let state_id = registry
1405 .state_id_from_properties(&key, &partial_props)
1406 .expect("Should find state");
1407
1408 let retrieved = registry.properties(state_id).collect::<Vec<_>>();
1409
1410 let power = retrieved.iter().find(|(n, _)| *n == "power").unwrap();
1412 assert_eq!(power.1, "10");
1413
1414 let east = retrieved.iter().find(|(n, _)| *n == "east").unwrap();
1415 assert_eq!(east.1, "side");
1416
1417 let north = retrieved.iter().find(|(n, _)| *n == "north").unwrap();
1419 assert_eq!(north.1, "up"); }
1421
1422 #[test]
1423 fn dynamically_named_property_update_preserves_other_state_values() {
1424 let registry = create_test_registry();
1425 let wire = registry
1426 .state_id_from_block_defaulted_properties(
1427 &vanilla_blocks::REDSTONE_WIRE,
1428 [("east", "side"), ("power", "7")],
1429 )
1430 .expect("redstone wire state should exist");
1431
1432 let updated = registry
1433 .try_set_property_by_name(wire, "east", "up")
1434 .expect("dynamic property should update");
1435
1436 let properties = registry.properties(updated).collect::<Vec<_>>();
1437 assert!(properties.contains(&("east", "up")));
1438 assert!(properties.contains(&("power", "7")));
1439 assert!(
1440 registry
1441 .try_set_property_by_name(updated, "missing", "value")
1442 .is_none()
1443 );
1444 }
1445
1446 #[test]
1447 fn test_state_id_from_block_defaulted_properties_keeps_missing_defaults() {
1448 let registry = create_test_registry();
1449 let key = Identifier::vanilla_static("redstone_wire");
1450 let block = registry.by_key(&key).expect("redstone_wire should exist");
1451
1452 let state_id = registry
1453 .state_id_from_block_defaulted_properties(block, [("power", "10")])
1454 .expect("Should find state");
1455
1456 let retrieved = registry.properties(state_id).collect::<Vec<_>>();
1457
1458 let power = retrieved.iter().find(|(n, _)| *n == "power").unwrap();
1459 assert_eq!(power.1, "10");
1460
1461 for direction in ["east", "north", "south", "west"] {
1462 let side = retrieved.iter().find(|(n, _)| *n == direction).unwrap();
1463 assert_eq!(side.1, "none");
1464 }
1465 }
1466
1467 #[test]
1468 fn test_state_id_from_properties_empty() {
1469 let registry = create_test_registry();
1470 let key = Identifier::vanilla_static("redstone_wire");
1471
1472 let state_id = registry
1474 .state_id_from_properties(&key, &[])
1475 .expect("Should find state");
1476
1477 let retrieved = registry.properties(state_id).collect::<Vec<_>>();
1478
1479 for (name, value) in &retrieved {
1481 match *name {
1482 "east" | "north" | "south" | "west" => {
1483 assert_eq!(*value, "up", "Empty props should use index 0 = 'up'");
1484 }
1485 "power" => {
1486 assert_eq!(*value, "0", "Empty props should use index 0 = '0'");
1487 }
1488 _ => {}
1489 }
1490 }
1491 }
1492
1493 #[test]
1494 fn test_state_id_from_properties_invalid_block() {
1495 let registry = create_test_registry();
1496 let key = Identifier::vanilla_static("nonexistent_block");
1497
1498 let result = registry.state_id_from_properties(&key, &[]);
1499 assert!(result.is_none(), "Should return None for invalid block");
1500 }
1501
1502 #[test]
1503 fn test_state_id_from_properties_invalid_property() {
1504 let registry = create_test_registry();
1505 let key = Identifier::vanilla_static("redstone_wire");
1506
1507 let invalid_props = [("invalid_property", "value")];
1508 let result = registry.state_id_from_properties(&key, &invalid_props);
1509 assert!(result.is_none(), "Should return None for invalid property");
1510 }
1511
1512 #[test]
1513 fn test_state_id_from_properties_invalid_value() {
1514 let registry = create_test_registry();
1515 let key = Identifier::vanilla_static("redstone_wire");
1516
1517 let invalid_props = [("power", "999")]; let result = registry.state_id_from_properties(&key, &invalid_props);
1519 assert!(result.is_none(), "Should return None for invalid value");
1520 }
1521
1522 #[test]
1523 fn same_named_direction_properties_translate_by_value_name() {
1524 let registry = create_test_registry();
1525 let wall_torch = registry.get_default_state_id(&vanilla_blocks::WALL_TORCH);
1526
1527 let south_torch = registry.set_property(
1528 wall_torch,
1529 &BlockStateProperties::HORIZONTAL_FACING,
1530 Direction::South,
1531 );
1532 let facing_from_six_way_property =
1533 registry.try_get_property(south_torch, &BlockStateProperties::FACING);
1534 assert_eq!(facing_from_six_way_property, Some(Direction::South));
1535
1536 let west_torch =
1537 registry.set_property(south_torch, &BlockStateProperties::FACING, Direction::West);
1538 let facing_from_horizontal_property =
1539 registry.try_get_property(west_torch, &BlockStateProperties::HORIZONTAL_FACING);
1540 assert_eq!(facing_from_horizontal_property, Some(Direction::West));
1541
1542 let dispenser = registry.get_default_state_id(&vanilla_blocks::DISPENSER);
1543 let upward_dispenser =
1544 registry.set_property(dispenser, &BlockStateProperties::FACING, Direction::Up);
1545 let horizontal_facing =
1546 registry.try_get_property(upward_dispenser, &BlockStateProperties::HORIZONTAL_FACING);
1547 assert_eq!(horizontal_facing, None);
1548 }
1549
1550 #[test]
1551 fn test_state_id_from_properties_rejects_properties_on_propertyless_block() {
1552 let registry = create_test_registry();
1553 let key = Identifier::vanilla_static("stone");
1554 let stone = registry.by_key(&key).expect("stone should exist");
1555
1556 let result = registry.state_id_from_properties(&key, &[("power", "1")]);
1557 assert!(
1558 result.is_none(),
1559 "Should return None for invalid property on propertyless block"
1560 );
1561
1562 let result = registry.state_id_from_block_defaulted_properties(stone, [("power", "1")]);
1563 assert!(
1564 result.is_none(),
1565 "Should return None for invalid defaulted property on propertyless block"
1566 );
1567 }
1568
1569 #[test]
1570 fn test_stone_no_properties() {
1571 let registry = create_test_registry();
1572 let key = Identifier::vanilla_static("stone");
1573
1574 let stone = registry.by_key(&key).expect("stone should exist");
1576 assert!(stone.properties.is_empty());
1577
1578 let state_id = registry
1580 .state_id_from_properties(&key, &[])
1581 .expect("Should find state");
1582
1583 let retrieved = registry.properties(state_id).collect::<Vec<_>>();
1584 assert_eq!(retrieved.len(), 0);
1585 }
1586
1587 #[test]
1588 fn test_all_redstone_power_levels() {
1589 let registry = create_test_registry();
1590 let key = Identifier::vanilla_static("redstone_wire");
1591
1592 for power in 0..=15 {
1594 let power_str = power.to_string();
1595 let props = [("power", power_str.as_str())];
1596
1597 let state_id = registry
1598 .state_id_from_properties(&key, &props)
1599 .unwrap_or_else(|| panic!("Should find state for power {power}"));
1600
1601 let retrieved = registry.properties(state_id).collect::<Vec<_>>();
1602 let found_power = retrieved.iter().find(|(n, _)| *n == "power").unwrap();
1603 assert_eq!(
1604 found_power.1,
1605 power_str.as_str(),
1606 "Power level {power} mismatch"
1607 );
1608 }
1609 }
1610
1611 #[test]
1612 #[cfg(feature = "minecraft-src")]
1613 fn test_all_block_state_ids_match_minecraft() {
1614 use rustc_hash::FxHashMap as HashMap;
1615 use std::fs;
1616
1617 #[derive(serde::Deserialize)]
1618 struct BlockState {
1619 id: u16,
1620 #[serde(default)]
1621 properties: HashMap<String, String>,
1622 #[serde(default)]
1623 default: bool,
1624 }
1625
1626 #[derive(serde::Deserialize)]
1627 struct BlockData {
1628 states: Vec<BlockState>,
1629 }
1630
1631 let possible_paths = [
1633 "minecraft-src/minecraft/resources/datagen-reports/blocks.json",
1634 "../minecraft-src/minecraft/resources/datagen-reports/blocks.json",
1635 ];
1636 let json_content = possible_paths
1637 .iter()
1638 .find_map(|path| fs::read_to_string(path).ok())
1639 .expect("Failed to read blocks.json - make sure minecraft-src is available");
1640 let blocks: HashMap<String, BlockData> =
1641 serde_json::from_str(&json_content).expect("Failed to parse blocks.json");
1642
1643 let registry = create_test_registry();
1644 let mut errors = Vec::new();
1645
1646 for (block_name, block_data) in &blocks {
1647 let key = Identifier::vanilla_static(
1649 block_name
1650 .strip_prefix("minecraft:")
1651 .unwrap_or(block_name)
1652 .to_string()
1653 .leak(),
1654 );
1655
1656 let Some(block) = registry.by_key(&key) else {
1657 errors.push(format!("Block {block_name} not found in registry"));
1658 continue;
1659 };
1660
1661 for state in &block_data.states {
1663 if state.default {
1664 let our_default = registry.get_default_state_id(block);
1665 if our_default.0 != state.id {
1666 errors.push(format!(
1667 "{}: default state mismatch - expected {}, got {}",
1668 block_name, state.id, our_default.0
1669 ));
1670 }
1671 }
1672 }
1673
1674 for state in &block_data.states {
1676 let props: Vec<(&str, &str)> = state
1677 .properties
1678 .iter()
1679 .map(|(k, v)| (k.as_str(), v.as_str()))
1680 .collect();
1681
1682 let Some(our_state_id) = registry.state_id_from_properties(&key, &props) else {
1683 errors.push(format!(
1684 "{block_name}: failed to get state for properties {props:?}"
1685 ));
1686 continue;
1687 };
1688
1689 if our_state_id.0 != state.id {
1690 errors.push(format!(
1691 "{}: state mismatch for {:?} - expected {}, got {}",
1692 block_name, props, state.id, our_state_id.0
1693 ));
1694 }
1695 }
1696 }
1697
1698 if !errors.is_empty() {
1699 let display_errors: String = errors
1701 .iter()
1702 .take(20)
1703 .cloned()
1704 .collect::<Vec<_>>()
1705 .join("\n");
1706 panic!(
1707 "Found {} state ID mismatches:\n{}{}",
1708 errors.len(),
1709 display_errors,
1710 if errors.len() > 20 {
1711 format!("\n... and {} more", errors.len() - 20)
1712 } else {
1713 String::new()
1714 }
1715 );
1716 }
1717 }
1718}