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::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
478impl BlockRegistry {
479 #[must_use]
481 pub fn new() -> Self {
482 Self {
483 blocks_by_id: Vec::new(),
484 blocks_by_key: FxHashMap::default(),
485 tags: RegistryTags::default(),
486 allows_registering: true,
487 state_to_block_lookup: Vec::new(),
488 state_to_block_id: Vec::new(),
489 state_ticking_metadata: Vec::new(),
490 block_to_base_state: Vec::new(),
491 next_state_id: 0,
492 }
493 }
494
495 pub fn register(&mut self, block: BlockRef) -> usize {
496 assert!(
497 self.allows_registering,
498 "Cannot register blocks after the registry has been frozen"
499 );
500
501 let id = self.blocks_by_id.len();
502 let base_state_id = self.next_state_id;
503
504 let cached = block.id.get_or_init(|| id);
505 assert_eq!(*cached, id, "block registered with conflicting id");
506 self.blocks_by_key.insert(block.key.clone(), id);
507 self.blocks_by_id.push(block);
508 self.block_to_base_state.push(base_state_id);
509
510 let state_count = block.state_count();
511 for offset in 0..state_count {
512 self.state_to_block_lookup.push(block);
513 self.state_to_block_id.push(id);
514 self.state_ticking_metadata
515 .push(block.get_ticking_metadata(offset));
516 }
517
518 self.next_state_id += state_count;
519
520 id
521 }
522
523 fn try_block_index(&self, block: BlockRef) -> Option<usize> {
524 if let Some(id) = block.id.get().copied()
525 && self
526 .blocks_by_id
527 .get(id)
528 .is_some_and(|registered| *registered == block)
529 {
530 return Some(id);
531 }
532
533 self.blocks_by_key.get(&block.key).copied()
534 }
535
536 fn block_index(&self, block: BlockRef) -> usize {
537 let Some(id) = self.try_block_index(block) else {
538 panic!("Block not found");
539 };
540 id
541 }
542
543 #[must_use]
544 pub fn get_base_state_id(&self, block: BlockRef) -> BlockStateId {
545 let id = self.block_index(block);
546 BlockStateId(self.block_to_base_state[id])
547 }
548
549 #[must_use]
551 pub fn get_default_state_id(&self, block: BlockRef) -> BlockStateId {
552 let id = self.block_index(block);
553 let base = self.block_to_base_state[id];
554 BlockStateId(base + block.default_state_offset)
555 }
556
557 #[must_use]
558 pub fn by_state_id(&self, state_id: BlockStateId) -> Option<BlockRef> {
559 self.state_to_block_lookup.get(state_id.0 as usize).copied()
560 }
561
562 #[must_use]
563 pub fn get_ticking_metadata(
564 &self,
565 state_id: BlockStateId,
566 ) -> Option<BlockStateTickingMetadata> {
567 self.state_ticking_metadata
568 .get(state_id.0 as usize)
569 .copied()
570 }
571
572 #[must_use]
573 pub fn get_properties(&self, id: BlockStateId) -> Vec<(&'static str, &'static str)> {
574 let block = self.by_state_id(id).expect("Invalid state ID");
575
576 if block.properties.is_empty() {
578 return Vec::new();
579 }
580
581 let block_id = self.state_to_block_id[id.0 as usize];
583 let base_state_id = self.block_to_base_state[block_id];
584
585 let relative_index = id.0 - base_state_id;
587
588 Self::decode_property_indices(block, relative_index)
589 .into_iter()
590 .zip(block.properties)
591 .map(|(value_index, prop)| (prop.get_name(), prop.value_name_from_index(value_index)))
592 .collect()
593 }
594
595 #[must_use]
602 pub fn state_id_from_properties(
603 &self,
604 key: &Identifier,
605 properties: &[(&str, &str)],
606 ) -> Option<BlockStateId> {
607 let block = self.by_key(key)?;
608 self.state_id_from_block_properties(block, properties)
609 }
610
611 #[must_use]
616 pub fn state_id_from_block_properties(
617 &self,
618 block: BlockRef,
619 properties: &[(&str, &str)],
620 ) -> Option<BlockStateId> {
621 let block_id = self.try_block_index(block)?;
622 let base_state_id = self.block_to_base_state[block_id];
623
624 let mut property_indices = vec![0usize; block.properties.len()];
625 Self::apply_property_overrides(block, &mut property_indices, properties.iter().copied())?;
626
627 Some(BlockStateId(
628 base_state_id + Self::encode_property_indices(block, &property_indices),
629 ))
630 }
631
632 #[must_use]
638 pub fn state_id_from_block_defaulted_properties<'a>(
639 &self,
640 block: BlockRef,
641 properties: impl IntoIterator<Item = (&'a str, &'a str)>,
642 ) -> Option<BlockStateId> {
643 let block_id = self.try_block_index(block)?;
644 let base_state_id = self.block_to_base_state[block_id];
645
646 let mut property_indices = Self::decode_property_indices(block, block.default_state_offset);
647 Self::apply_property_overrides(block, &mut property_indices, properties)?;
648
649 Some(BlockStateId(
650 base_state_id + Self::encode_property_indices(block, &property_indices),
651 ))
652 }
653
654 #[must_use]
660 pub fn try_set_property_by_name(
661 &self,
662 id: BlockStateId,
663 name: &str,
664 value: &str,
665 ) -> Option<BlockStateId> {
666 let block = self.by_state_id(id)?;
667 let block_id = *self.state_to_block_id.get(id.0 as usize)?;
668 let base_state_id = *self.block_to_base_state.get(block_id)?;
669 let property_index = block
670 .properties
671 .iter()
672 .position(|property| property.get_name() == name)?;
673 let property = block.properties[property_index];
674 let new_value_index = (0..property.value_count())
675 .find(|&index| property.value_name_from_index(index) == value)?;
676 let relative_index = id.0.checked_sub(base_state_id)?;
677 let stride = Self::property_stride(block, property_index);
678 let old_value_index = usize::from(relative_index / stride % property.value_count() as u16);
679 let new_relative_index = if new_value_index >= old_value_index {
680 relative_index.checked_add((new_value_index - old_value_index) as u16 * stride)?
681 } else {
682 relative_index.checked_sub((old_value_index - new_value_index) as u16 * stride)?
683 };
684
685 Some(BlockStateId(base_state_id + new_relative_index))
686 }
687
688 #[must_use]
693 pub fn matching_states(&self, block: BlockRef, filter: &[(&str, &str)]) -> Vec<BlockStateId> {
694 let block_id = self.block_index(block);
695 let base_state_id = self.block_to_base_state[block_id];
696
697 (0..block.state_count())
698 .map(|offset| BlockStateId(base_state_id + offset))
699 .filter(|&state_id| {
700 let properties = self.get_properties(state_id);
701 filter
702 .iter()
703 .all(|(name, value)| properties.iter().any(|(n, v)| n == name && v == value))
704 })
705 .collect()
706 }
707
708 fn decode_property_indices(block: BlockRef, mut offset: u16) -> Vec<usize> {
709 let mut property_indices = vec![0; block.properties.len()];
710
711 for (i, prop) in block.properties.iter().enumerate().rev() {
712 let count = prop.value_count() as u16;
713 property_indices[i] = (offset % count) as usize;
714 offset /= count;
715 }
716
717 property_indices
718 }
719
720 fn apply_property_overrides<'a>(
721 block: BlockRef,
722 property_indices: &mut [usize],
723 properties: impl IntoIterator<Item = (&'a str, &'a str)>,
724 ) -> Option<()> {
725 for (prop_name, prop_value) in properties {
726 let prop_idx = block
727 .properties
728 .iter()
729 .position(|p| p.get_name() == prop_name)?;
730
731 let prop = block.properties[prop_idx];
732 let value_idx = (0..prop.value_count())
733 .find(|&index| prop.value_name_from_index(index) == prop_value)?;
734
735 property_indices[prop_idx] = value_idx;
736 }
737
738 Some(())
739 }
740
741 fn encode_property_indices(block: BlockRef, property_indices: &[usize]) -> u16 {
742 let mut offset = 0u16;
743 let mut multiplier = 1u16;
744 for (idx, prop) in property_indices.iter().zip(block.properties.iter()).rev() {
745 offset += *idx as u16 * multiplier;
746 multiplier *= prop.value_count() as u16;
747 }
748
749 offset
750 }
751
752 fn property_stride(block: BlockRef, property_index: usize) -> u16 {
753 block.properties[property_index + 1..]
754 .iter()
755 .map(|property| property.value_count() as u16)
756 .product()
757 }
758
759 pub fn get_property<P: Property>(&self, id: BlockStateId, property: &P) -> P::Value {
761 self.try_get_property(id, property)
762 .expect("Property not found on this block")
763 }
764
765 #[must_use]
767 pub fn try_get_property<P: Property>(
768 &self,
769 id: BlockStateId,
770 property: &P,
771 ) -> Option<P::Value> {
772 let block = self.by_state_id(id).expect("Invalid state ID");
773
774 let property_index = block
776 .properties
777 .iter()
778 .position(|prop| prop.get_name() == property.get_name())?;
779
780 let block_id = self.state_to_block_id[id.0 as usize];
782 let base_state_id = self.block_to_base_state[block_id];
783
784 let relative_index = id.0 - base_state_id;
786
787 let block_property = block.properties[property_index];
788 let stride = Self::property_stride(block, property_index);
789 let value_index =
790 usize::from(relative_index / stride % block_property.value_count() as u16);
791 let block_value = block_property.value_name_from_index(value_index);
792
793 property.get_value(block_value)
794 }
795
796 pub fn set_property<P: Property>(
798 &self,
799 id: BlockStateId,
800 property: &P,
801 value: P::Value,
802 ) -> BlockStateId {
803 let block = self.by_state_id(id).expect("Invalid state ID");
804
805 let property_index = block
807 .properties
808 .iter()
809 .position(|prop| prop.get_name() == property.get_name())
810 .unwrap_or_else(|| {
811 panic!(
812 "Property {} not found on block {}",
813 property.get_name(),
814 block.key
815 )
816 });
817
818 let block_id = self.state_to_block_id[id.0 as usize];
820 let base_state_id = self.block_to_base_state[block_id];
821
822 let relative_index = id.0 - base_state_id;
824
825 let caller_value_index = property.get_internal_index(&value);
826 let value_name = property.value_name_from_index(caller_value_index);
827 let block_property = block.properties[property_index];
828 let Some(new_value_index) = (0..block_property.value_count())
829 .find(|&index| block_property.value_name_from_index(index) == value_name)
830 else {
831 panic!(
832 "Value {} for property {} not found on block {}",
833 value_name,
834 property.get_name(),
835 block.key
836 );
837 };
838 let stride = Self::property_stride(block, property_index);
839 let old_value_index =
840 usize::from(relative_index / stride % block_property.value_count() as u16);
841 let new_relative_index = if new_value_index >= old_value_index {
842 relative_index + (new_value_index - old_value_index) as u16 * stride
843 } else {
844 relative_index - (old_value_index - new_value_index) as u16 * stride
845 };
846
847 BlockStateId(base_state_id + new_relative_index)
848 }
849
850 pub fn iter(&self) -> impl Iterator<Item = (usize, BlockRef)> + '_ {
851 self.blocks_by_id
852 .iter()
853 .enumerate()
854 .map(|(id, &block)| (id, block))
855 }
856}
857
858crate::impl_registry_ext!(BlockRegistry, Block, blocks_by_id, blocks_by_key);
859
860crate::impl_registry_entry_eq!(Block);
861
862impl crate::RegistryEntry for Block {
863 fn key(&self) -> &Identifier {
864 &self.key
865 }
866
867 fn try_id(&self) -> Option<usize> {
868 self.id.get().copied()
869 }
870}
871crate::impl_tagged_registry!(BlockRegistry, blocks_by_key, "block");
872
873impl BlockRegistry {
875 fn block_and_state_offset(&self, state_id: BlockStateId) -> Option<(BlockRef, u16)> {
876 let block = self
877 .state_to_block_lookup
878 .get(state_id.0 as usize)
879 .copied()?;
880 let block_id = self
881 .state_to_block_id
882 .get(state_id.0 as usize)
883 .copied()
884 .unwrap_or(0);
885 let base_state = self.block_to_base_state.get(block_id).copied().unwrap_or(0);
886 let offset = state_id.0.saturating_sub(base_state);
887 Some((block, offset))
888 }
889
890 fn static_shape_for_state(
891 &self,
892 state_id: BlockStateId,
893 shape: fn(&Block, u16) -> shapes::VoxelShape,
894 ) -> shapes::VoxelShape {
895 let Some((block, offset)) = self.block_and_state_offset(state_id) else {
896 return shapes::VoxelShape::FULL_BLOCK;
897 };
898 shape(block, offset)
899 }
900
901 fn offset_shape_for_state(
902 &self,
903 state_id: BlockStateId,
904 pos: BlockPos,
905 channel: ShapeChannel,
906 shape: fn(&Block, u16) -> shapes::VoxelShape,
907 ) -> shapes::OffsetVoxelShape {
908 let Some((block, offset)) = self.block_and_state_offset(state_id) else {
909 return shapes::OffsetVoxelShape::without_offset(shapes::VoxelShape::FULL_BLOCK);
910 };
911
912 let shape = shape(block, offset);
913 let offset = if block.shape_offsets.uses_offset(channel) {
914 block.offset_at(pos)
915 } else {
916 DVec3::ZERO
917 };
918 shapes::OffsetVoxelShape::new(shape, offset)
919 }
920
921 #[must_use]
926 pub fn get_static_collision_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
927 self.static_shape_for_state(state_id, Block::get_collision_shape)
928 }
929
930 #[must_use]
932 pub fn is_suffocating(&self, state_id: BlockStateId) -> bool {
933 let Some((block, offset)) = self.block_and_state_offset(state_id) else {
934 return false;
935 };
936 block.suffocating.value(offset)
937 }
938
939 #[must_use]
943 pub fn is_static_redstone_conductor(&self, state_id: BlockStateId) -> bool {
944 let Some((block, offset)) = self.block_and_state_offset(state_id) else {
945 return false;
946 };
947 block.redstone_conductor.value(offset)
948 }
949
950 #[must_use]
951 pub fn get_collision_shape_at(
952 &self,
953 state_id: BlockStateId,
954 pos: BlockPos,
955 ) -> shapes::OffsetVoxelShape {
956 self.offset_shape_for_state(
957 state_id,
958 pos,
959 ShapeChannel::Collision,
960 Block::get_collision_shape,
961 )
962 }
963
964 #[must_use]
969 pub fn get_static_support_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
970 self.static_shape_for_state(state_id, Block::get_support_shape)
971 }
972
973 #[must_use]
974 pub fn get_support_shape_at(
975 &self,
976 state_id: BlockStateId,
977 pos: BlockPos,
978 ) -> shapes::OffsetVoxelShape {
979 self.offset_shape_for_state(
980 state_id,
981 pos,
982 ShapeChannel::Support,
983 Block::get_support_shape,
984 )
985 }
986
987 #[must_use]
992 pub fn get_static_outline_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
993 self.static_shape_for_state(state_id, Block::get_outline_shape)
994 }
995
996 #[must_use]
997 pub fn get_outline_shape_at(
998 &self,
999 state_id: BlockStateId,
1000 pos: BlockPos,
1001 ) -> shapes::OffsetVoxelShape {
1002 self.offset_shape_for_state(
1003 state_id,
1004 pos,
1005 ShapeChannel::Outline,
1006 Block::get_outline_shape,
1007 )
1008 }
1009
1010 #[must_use]
1015 pub fn get_occlusion_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
1016 self.static_shape_for_state(state_id, Block::get_occlusion_shape)
1017 }
1018
1019 #[must_use]
1024 pub fn get_static_interaction_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
1025 self.static_shape_for_state(state_id, Block::get_interaction_shape)
1026 }
1027
1028 #[must_use]
1029 pub fn get_interaction_shape_at(
1030 &self,
1031 state_id: BlockStateId,
1032 pos: BlockPos,
1033 ) -> shapes::OffsetVoxelShape {
1034 self.offset_shape_for_state(
1035 state_id,
1036 pos,
1037 ShapeChannel::Interaction,
1038 Block::get_interaction_shape,
1039 )
1040 }
1041
1042 #[must_use]
1047 pub fn get_static_visual_shape(&self, state_id: BlockStateId) -> shapes::VoxelShape {
1048 self.static_shape_for_state(state_id, Block::get_visual_shape)
1049 }
1050
1051 #[must_use]
1052 pub fn get_visual_shape_at(
1053 &self,
1054 state_id: BlockStateId,
1055 pos: BlockPos,
1056 ) -> shapes::OffsetVoxelShape {
1057 self.offset_shape_for_state(state_id, pos, ShapeChannel::Visual, Block::get_visual_shape)
1058 }
1059
1060 #[must_use]
1062 pub fn get_static_shapes(&self, state_id: BlockStateId) -> shapes::BlockShapes {
1063 shapes::BlockShapes::new(
1064 self.get_static_collision_shape(state_id),
1065 self.get_static_support_shape(state_id),
1066 self.get_static_outline_shape(state_id),
1067 self.get_occlusion_shape(state_id),
1068 self.get_static_interaction_shape(state_id),
1069 self.get_static_visual_shape(state_id),
1070 )
1071 }
1072
1073 #[must_use]
1074 pub fn get_light_properties(&self, state_id: BlockStateId) -> BlockLightProperties {
1075 let Some((block, offset)) = self.block_and_state_offset(state_id) else {
1076 return BlockLightProperties::OPAQUE_FULL_BLOCK;
1077 };
1078 block.get_light_properties(offset)
1079 }
1080
1081 pub fn copy_matching_properties(&self, source: BlockStateId, target: BlockRef) -> BlockStateId {
1082 let props = self.get_properties(source);
1083 let matching: Vec<(&str, &str)> = props
1084 .iter()
1085 .filter(|(name, _)| target.properties.iter().any(|p| p.get_name() == *name))
1086 .copied()
1087 .collect();
1088 self.state_id_from_block_defaulted_properties(target, matching)
1089 .unwrap_or_else(|| self.get_default_state_id(target))
1090 }
1091}
1092
1093#[macro_export]
1115macro_rules! offset {
1116 ($($prop:expr => $value:expr),* $(,)?) => {{
1117 const INDICES: &[usize] = &[$($value as usize),*];
1118 const COUNTS: &[usize] = &[$($prop.value_count()),*];
1119 $crate::blocks::Block::calculate_offset(INDICES, COUNTS)
1120 }};
1121}
1122
1123pub use offset;
1125use steel_utils::Identifier;
1126
1127#[cfg(test)]
1128mod tests {
1129 use super::*;
1130 use crate::blocks::properties::{BlockStateProperties, Direction};
1131 use crate::vanilla_blocks;
1132
1133 fn create_test_registry() -> BlockRegistry {
1134 let mut registry = BlockRegistry::new();
1135 vanilla_blocks::register_blocks(&mut registry);
1136 registry.freeze();
1137 registry
1138 }
1139
1140 #[test]
1141 fn tag_modification_keeps_order_and_membership_in_sync() {
1142 let mut registry = BlockRegistry::new();
1143 vanilla_blocks::register_blocks(&mut registry);
1144 let tag = Identifier::new_static("test", "ordered_membership");
1145 registry.register_tag(
1146 Identifier::new_static("test", "ordered_membership"),
1147 &["stone", "dirt"],
1148 );
1149
1150 assert!(registry.is_in_tag(&vanilla_blocks::STONE, &tag));
1151 assert_eq!(
1152 registry
1153 .iter_tag(&tag)
1154 .map(|block| block.key.path.as_ref())
1155 .collect::<Vec<_>>(),
1156 ["stone", "dirt"]
1157 );
1158
1159 registry.modify_tag(&tag, |_| {
1160 vec![
1161 Identifier::vanilla_static("oak_log"),
1162 Identifier::vanilla_static("dirt"),
1163 ]
1164 });
1165 registry.freeze();
1166
1167 assert!(!registry.is_in_tag(&vanilla_blocks::STONE, &tag));
1168 assert!(registry.is_in_tag(&vanilla_blocks::OAK_LOG, &tag));
1169 assert!(registry.is_in_tag(&vanilla_blocks::DIRT, &tag));
1170 assert_eq!(
1171 registry
1172 .iter_tag(&tag)
1173 .map(|block| block.key.path.as_ref())
1174 .collect::<Vec<_>>(),
1175 ["oak_log", "dirt"]
1176 );
1177 }
1178
1179 #[test]
1180 fn test_redstone_wire_properties() {
1181 let registry = create_test_registry();
1182 let redstone_wire = registry
1183 .by_key(&Identifier::vanilla_static("redstone_wire"))
1184 .expect("redstone_wire should exist");
1185
1186 assert_eq!(redstone_wire.properties.len(), 5);
1188
1189 let prop_names: Vec<&str> = redstone_wire
1191 .properties
1192 .iter()
1193 .map(|p| p.get_name())
1194 .collect();
1195 assert!(prop_names.contains(&"east"));
1196 assert!(prop_names.contains(&"north"));
1197 assert!(prop_names.contains(&"south"));
1198 assert!(prop_names.contains(&"west"));
1199 assert!(prop_names.contains(&"power"));
1200 }
1201
1202 #[test]
1203 fn test_redstone_wire_state_count() {
1204 let registry = create_test_registry();
1205
1206 let redstone_wire = registry
1209 .by_key(&Identifier::vanilla_static("redstone_wire"))
1210 .expect("redstone_wire should exist");
1211
1212 let mut state_count = 1;
1213 for prop in redstone_wire.properties {
1214 state_count *= prop.get_possible_value_names().len();
1215 }
1216 assert_eq!(state_count, 3 * 3 * 3 * 3 * 16); }
1218
1219 #[test]
1220 fn test_get_properties_default_state() {
1221 let registry = create_test_registry();
1222 let redstone_wire = registry
1223 .by_key(&Identifier::vanilla_static("redstone_wire"))
1224 .expect("redstone_wire should exist");
1225
1226 let default_state = registry.get_default_state_id(redstone_wire);
1227 let properties = registry.get_properties(default_state);
1228
1229 assert_eq!(properties.len(), 5);
1231
1232 for (name, value) in &properties {
1233 match *name {
1234 "east" | "north" | "south" | "west" => {
1235 assert_eq!(*value, "none", "Default side should be 'none'");
1236 }
1237 "power" => {
1238 assert_eq!(*value, "0", "Default power should be '0'");
1239 }
1240 _ => panic!("Unexpected property: {name}"),
1241 }
1242 }
1243 }
1244
1245 #[test]
1246 fn test_state_id_from_properties_roundtrip() {
1247 let registry = create_test_registry();
1248 let key = Identifier::vanilla_static("redstone_wire");
1249
1250 let properties = [
1252 ("east", "up"),
1253 ("north", "side"),
1254 ("south", "none"),
1255 ("west", "up"),
1256 ("power", "15"),
1257 ];
1258
1259 let state_id = registry
1260 .state_id_from_properties(&key, &properties)
1261 .expect("Should find state");
1262
1263 let retrieved = registry.get_properties(state_id);
1265 assert_eq!(retrieved.len(), 5);
1266
1267 for (name, value) in &properties {
1268 let found = retrieved
1269 .iter()
1270 .find(|(n, _)| n == name)
1271 .expect("Property should exist");
1272 assert_eq!(found.1, *value, "Property {name} mismatch");
1273 }
1274 }
1275
1276 #[test]
1277 fn test_state_id_from_properties_partial() {
1278 let registry = create_test_registry();
1279 let key = Identifier::vanilla_static("redstone_wire");
1280
1281 let partial_props = [("power", "10"), ("east", "side")];
1283
1284 let state_id = registry
1285 .state_id_from_properties(&key, &partial_props)
1286 .expect("Should find state");
1287
1288 let retrieved = registry.get_properties(state_id);
1289
1290 let power = retrieved.iter().find(|(n, _)| *n == "power").unwrap();
1292 assert_eq!(power.1, "10");
1293
1294 let east = retrieved.iter().find(|(n, _)| *n == "east").unwrap();
1295 assert_eq!(east.1, "side");
1296
1297 let north = retrieved.iter().find(|(n, _)| *n == "north").unwrap();
1299 assert_eq!(north.1, "up"); }
1301
1302 #[test]
1303 fn dynamically_named_property_update_preserves_other_state_values() {
1304 let registry = create_test_registry();
1305 let wire = registry
1306 .state_id_from_block_defaulted_properties(
1307 &vanilla_blocks::REDSTONE_WIRE,
1308 [("east", "side"), ("power", "7")],
1309 )
1310 .expect("redstone wire state should exist");
1311
1312 let updated = registry
1313 .try_set_property_by_name(wire, "east", "up")
1314 .expect("dynamic property should update");
1315
1316 let properties = registry.get_properties(updated);
1317 assert!(properties.contains(&("east", "up")));
1318 assert!(properties.contains(&("power", "7")));
1319 assert!(
1320 registry
1321 .try_set_property_by_name(updated, "missing", "value")
1322 .is_none()
1323 );
1324 }
1325
1326 #[test]
1327 fn test_state_id_from_block_defaulted_properties_keeps_missing_defaults() {
1328 let registry = create_test_registry();
1329 let key = Identifier::vanilla_static("redstone_wire");
1330 let block = registry.by_key(&key).expect("redstone_wire should exist");
1331
1332 let state_id = registry
1333 .state_id_from_block_defaulted_properties(block, [("power", "10")])
1334 .expect("Should find state");
1335
1336 let retrieved = registry.get_properties(state_id);
1337
1338 let power = retrieved.iter().find(|(n, _)| *n == "power").unwrap();
1339 assert_eq!(power.1, "10");
1340
1341 for direction in ["east", "north", "south", "west"] {
1342 let side = retrieved.iter().find(|(n, _)| *n == direction).unwrap();
1343 assert_eq!(side.1, "none");
1344 }
1345 }
1346
1347 #[test]
1348 fn test_state_id_from_properties_empty() {
1349 let registry = create_test_registry();
1350 let key = Identifier::vanilla_static("redstone_wire");
1351
1352 let state_id = registry
1354 .state_id_from_properties(&key, &[])
1355 .expect("Should find state");
1356
1357 let retrieved = registry.get_properties(state_id);
1358
1359 for (name, value) in &retrieved {
1361 match *name {
1362 "east" | "north" | "south" | "west" => {
1363 assert_eq!(*value, "up", "Empty props should use index 0 = 'up'");
1364 }
1365 "power" => {
1366 assert_eq!(*value, "0", "Empty props should use index 0 = '0'");
1367 }
1368 _ => {}
1369 }
1370 }
1371 }
1372
1373 #[test]
1374 fn test_state_id_from_properties_invalid_block() {
1375 let registry = create_test_registry();
1376 let key = Identifier::vanilla_static("nonexistent_block");
1377
1378 let result = registry.state_id_from_properties(&key, &[]);
1379 assert!(result.is_none(), "Should return None for invalid block");
1380 }
1381
1382 #[test]
1383 fn test_state_id_from_properties_invalid_property() {
1384 let registry = create_test_registry();
1385 let key = Identifier::vanilla_static("redstone_wire");
1386
1387 let invalid_props = [("invalid_property", "value")];
1388 let result = registry.state_id_from_properties(&key, &invalid_props);
1389 assert!(result.is_none(), "Should return None for invalid property");
1390 }
1391
1392 #[test]
1393 fn test_state_id_from_properties_invalid_value() {
1394 let registry = create_test_registry();
1395 let key = Identifier::vanilla_static("redstone_wire");
1396
1397 let invalid_props = [("power", "999")]; let result = registry.state_id_from_properties(&key, &invalid_props);
1399 assert!(result.is_none(), "Should return None for invalid value");
1400 }
1401
1402 #[test]
1403 fn same_named_direction_properties_translate_by_value_name() {
1404 let registry = create_test_registry();
1405 let wall_torch = registry.get_default_state_id(&vanilla_blocks::WALL_TORCH);
1406
1407 let south_torch = registry.set_property(
1408 wall_torch,
1409 &BlockStateProperties::HORIZONTAL_FACING,
1410 Direction::South,
1411 );
1412 let facing_from_six_way_property =
1413 registry.try_get_property(south_torch, &BlockStateProperties::FACING);
1414 assert_eq!(facing_from_six_way_property, Some(Direction::South));
1415
1416 let west_torch =
1417 registry.set_property(south_torch, &BlockStateProperties::FACING, Direction::West);
1418 let facing_from_horizontal_property =
1419 registry.try_get_property(west_torch, &BlockStateProperties::HORIZONTAL_FACING);
1420 assert_eq!(facing_from_horizontal_property, Some(Direction::West));
1421
1422 let dispenser = registry.get_default_state_id(&vanilla_blocks::DISPENSER);
1423 let upward_dispenser =
1424 registry.set_property(dispenser, &BlockStateProperties::FACING, Direction::Up);
1425 let horizontal_facing =
1426 registry.try_get_property(upward_dispenser, &BlockStateProperties::HORIZONTAL_FACING);
1427 assert_eq!(horizontal_facing, None);
1428 }
1429
1430 #[test]
1431 fn test_state_id_from_properties_rejects_properties_on_propertyless_block() {
1432 let registry = create_test_registry();
1433 let key = Identifier::vanilla_static("stone");
1434 let stone = registry.by_key(&key).expect("stone should exist");
1435
1436 let result = registry.state_id_from_properties(&key, &[("power", "1")]);
1437 assert!(
1438 result.is_none(),
1439 "Should return None for invalid property on propertyless block"
1440 );
1441
1442 let result = registry.state_id_from_block_defaulted_properties(stone, [("power", "1")]);
1443 assert!(
1444 result.is_none(),
1445 "Should return None for invalid defaulted property on propertyless block"
1446 );
1447 }
1448
1449 #[test]
1450 fn test_stone_no_properties() {
1451 let registry = create_test_registry();
1452 let key = Identifier::vanilla_static("stone");
1453
1454 let stone = registry.by_key(&key).expect("stone should exist");
1456 assert!(stone.properties.is_empty());
1457
1458 let state_id = registry
1460 .state_id_from_properties(&key, &[])
1461 .expect("Should find state");
1462
1463 let retrieved = registry.get_properties(state_id);
1464 assert_eq!(retrieved.len(), 0);
1465 }
1466
1467 #[test]
1468 fn test_all_redstone_power_levels() {
1469 let registry = create_test_registry();
1470 let key = Identifier::vanilla_static("redstone_wire");
1471
1472 for power in 0..=15 {
1474 let power_str = power.to_string();
1475 let props = [("power", power_str.as_str())];
1476
1477 let state_id = registry
1478 .state_id_from_properties(&key, &props)
1479 .unwrap_or_else(|| panic!("Should find state for power {power}"));
1480
1481 let retrieved = registry.get_properties(state_id);
1482 let found_power = retrieved.iter().find(|(n, _)| *n == "power").unwrap();
1483 assert_eq!(
1484 found_power.1,
1485 power_str.as_str(),
1486 "Power level {power} mismatch"
1487 );
1488 }
1489 }
1490
1491 #[test]
1492 #[cfg(feature = "minecraft-src")]
1493 fn test_all_block_state_ids_match_minecraft() {
1494 use rustc_hash::FxHashMap as HashMap;
1495 use std::fs;
1496
1497 #[derive(serde::Deserialize)]
1498 struct BlockState {
1499 id: u16,
1500 #[serde(default)]
1501 properties: HashMap<String, String>,
1502 #[serde(default)]
1503 default: bool,
1504 }
1505
1506 #[derive(serde::Deserialize)]
1507 struct BlockData {
1508 states: Vec<BlockState>,
1509 }
1510
1511 let possible_paths = [
1513 "minecraft-src/minecraft/resources/datagen-reports/blocks.json",
1514 "../minecraft-src/minecraft/resources/datagen-reports/blocks.json",
1515 ];
1516 let json_content = possible_paths
1517 .iter()
1518 .find_map(|path| fs::read_to_string(path).ok())
1519 .expect("Failed to read blocks.json - make sure minecraft-src is available");
1520 let blocks: HashMap<String, BlockData> =
1521 serde_json::from_str(&json_content).expect("Failed to parse blocks.json");
1522
1523 let registry = create_test_registry();
1524 let mut errors = Vec::new();
1525
1526 for (block_name, block_data) in &blocks {
1527 let key = Identifier::vanilla_static(
1529 block_name
1530 .strip_prefix("minecraft:")
1531 .unwrap_or(block_name)
1532 .to_string()
1533 .leak(),
1534 );
1535
1536 let Some(block) = registry.by_key(&key) else {
1537 errors.push(format!("Block {block_name} not found in registry"));
1538 continue;
1539 };
1540
1541 for state in &block_data.states {
1543 if state.default {
1544 let our_default = registry.get_default_state_id(block);
1545 if our_default.0 != state.id {
1546 errors.push(format!(
1547 "{}: default state mismatch - expected {}, got {}",
1548 block_name, state.id, our_default.0
1549 ));
1550 }
1551 }
1552 }
1553
1554 for state in &block_data.states {
1556 let props: Vec<(&str, &str)> = state
1557 .properties
1558 .iter()
1559 .map(|(k, v)| (k.as_str(), v.as_str()))
1560 .collect();
1561
1562 let Some(our_state_id) = registry.state_id_from_properties(&key, &props) else {
1563 errors.push(format!(
1564 "{block_name}: failed to get state for properties {props:?}"
1565 ));
1566 continue;
1567 };
1568
1569 if our_state_id.0 != state.id {
1570 errors.push(format!(
1571 "{}: state mismatch for {:?} - expected {}, got {}",
1572 block_name, props, state.id, our_state_id.0
1573 ));
1574 }
1575 }
1576 }
1577
1578 if !errors.is_empty() {
1579 let display_errors: String = errors
1581 .iter()
1582 .take(20)
1583 .cloned()
1584 .collect::<Vec<_>>()
1585 .join("\n");
1586 panic!(
1587 "Found {} state ID mismatches:\n{}{}",
1588 errors.len(),
1589 display_errors,
1590 if errors.len() > 20 {
1591 format!("\n... and {} more", errors.len() - 20)
1592 } else {
1593 String::new()
1594 }
1595 );
1596 }
1597 }
1598}