1use std::cmp::Ordering;
4use std::fmt::Debug;
5use std::io::{Cursor, Error, Result, Write};
6
7use simdnbt::owned::{NbtCompound, NbtList, NbtTag, read_tag};
8use simdnbt::{FromNbtTag, ToNbtTag};
9use steel_utils::BlockStateId;
10use steel_utils::codec::VarInt;
11use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
12use steel_utils::nbt::{
13 NbtNumeric, normalize_nbt_compound, parse_snbt_compound, to_canonical_snbt,
14 vanilla_nbt_heap_size,
15};
16use steel_utils::serial::{ReadFrom, WriteTo};
17
18use crate::blocks::{Block, properties::Property};
19use crate::data_component_predicate::DataComponentMatchers;
20use crate::items::Item;
21use crate::{REGISTRY, RegistryHolderSet};
22
23const DEFAULT_NBT_QUOTA: u64 = 2_097_152;
24const MAX_UTF_LENGTH: usize = 32_767;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub struct IntBounds {
29 min: Option<i32>,
30 max: Option<i32>,
31}
32
33impl IntBounds {
34 pub const ANY: Self = Self {
35 min: None,
36 max: None,
37 };
38
39 #[must_use]
40 pub fn new(min: Option<i32>, max: Option<i32>) -> Option<Self> {
41 if min.zip(max).is_some_and(|(min, max)| min > max) {
42 return None;
43 }
44 Some(Self { min, max })
45 }
46
47 #[must_use]
48 pub const fn exactly(value: i32) -> Self {
49 Self {
50 min: Some(value),
51 max: Some(value),
52 }
53 }
54
55 #[must_use]
56 pub const fn min(&self) -> Option<i32> {
57 self.min
58 }
59
60 #[must_use]
61 pub const fn max(&self) -> Option<i32> {
62 self.max
63 }
64
65 #[must_use]
66 pub const fn is_any(&self) -> bool {
67 self.min.is_none() && self.max.is_none()
68 }
69
70 pub(crate) fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
71 if let Some(value) = tag.codec_i32() {
72 return Some(Self::exactly(value));
73 }
74 let compound = tag.compound()?;
75 Self::new(
76 decode_optional(compound, "min", NbtNumeric::codec_i32)?,
77 decode_optional(compound, "max", NbtNumeric::codec_i32)?,
78 )
79 }
80
81 pub(crate) fn as_nbt_tag(&self) -> NbtTag {
82 if let (Some(min), Some(max)) = (self.min, self.max)
83 && min == max
84 {
85 return NbtTag::Int(min);
86 }
87 let mut compound = NbtCompound::new();
88 if let Some(min) = self.min {
89 compound.insert("min", min);
90 }
91 if let Some(max) = self.max {
92 compound.insert("max", max);
93 }
94 NbtTag::Compound(compound)
95 }
96}
97
98impl HashComponent for IntBounds {
99 fn hash_component(&self, hasher: &mut ComponentHasher) {
100 self.as_nbt_tag().hash_component(hasher);
101 }
102}
103
104#[derive(Debug, Clone, Copy, Default)]
106pub struct DoubleBounds {
107 min: Option<f64>,
108 max: Option<f64>,
109}
110
111impl DoubleBounds {
112 pub const ANY: Self = Self {
113 min: None,
114 max: None,
115 };
116
117 #[must_use]
118 pub fn new(min: Option<f64>, max: Option<f64>) -> Option<Self> {
119 if min
120 .zip(max)
121 .is_some_and(|(min, max)| java_double_compare(min, max).is_gt())
122 {
123 return None;
124 }
125 Some(Self { min, max })
126 }
127
128 #[must_use]
129 pub const fn exactly(value: f64) -> Self {
130 Self {
131 min: Some(value),
132 max: Some(value),
133 }
134 }
135
136 #[must_use]
137 pub const fn min(&self) -> Option<f64> {
138 self.min
139 }
140
141 #[must_use]
142 pub const fn max(&self) -> Option<f64> {
143 self.max
144 }
145
146 #[must_use]
147 pub const fn is_any(&self) -> bool {
148 self.min.is_none() && self.max.is_none()
149 }
150
151 pub(crate) fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
152 if let Some(value) = tag.codec_f64() {
153 return Some(Self::exactly(value));
154 }
155 let compound = tag.compound()?;
156 Self::new(
157 decode_optional(compound, "min", NbtNumeric::codec_f64)?,
158 decode_optional(compound, "max", NbtNumeric::codec_f64)?,
159 )
160 }
161
162 pub(crate) fn as_nbt_tag(&self) -> NbtTag {
163 if let (Some(min), Some(max)) = (self.min, self.max)
164 && java_double_equals(min, max)
165 {
166 return NbtTag::Double(min);
167 }
168 let mut compound = NbtCompound::new();
169 if let Some(min) = self.min {
170 compound.insert("min", min);
171 }
172 if let Some(max) = self.max {
173 compound.insert("max", max);
174 }
175 NbtTag::Compound(compound)
176 }
177}
178
179impl PartialEq for DoubleBounds {
180 fn eq(&self, other: &Self) -> bool {
181 option_double_equals(self.min, other.min) && option_double_equals(self.max, other.max)
182 }
183}
184
185impl HashComponent for DoubleBounds {
186 fn hash_component(&self, hasher: &mut ComponentHasher) {
187 if let (Some(min), Some(max)) = (self.min, self.max)
188 && java_double_equals(min, max)
189 {
190 hasher.put_double(min);
191 return;
192 }
193 let mut entries = Vec::new();
194 if let Some(min) = self.min {
195 push_hash_entry(&mut entries, "min", &min);
196 }
197 if let Some(max) = self.max {
198 push_hash_entry(&mut entries, "max", &max);
199 }
200 hash_entries(hasher, &mut entries);
201 }
202}
203
204const fn option_double_equals(left: Option<f64>, right: Option<f64>) -> bool {
205 match (left, right) {
206 (Some(left), Some(right)) => java_double_equals(left, right),
207 (None, None) => true,
208 _ => false,
209 }
210}
211
212const fn java_double_equals(left: f64, right: f64) -> bool {
213 java_double_bits(left) == java_double_bits(right)
214}
215
216fn java_double_compare(left: f64, right: f64) -> std::cmp::Ordering {
217 if left < right {
218 return std::cmp::Ordering::Less;
219 }
220 if left > right {
221 return std::cmp::Ordering::Greater;
222 }
223 java_double_bits(left).cmp(&java_double_bits(right))
224}
225
226const fn java_double_bits(value: f64) -> i64 {
227 if value.is_nan() {
228 return 0x7ff8_0000_0000_0000;
229 }
230 i64::from_ne_bytes(value.to_bits().to_ne_bytes())
231}
232
233#[derive(Debug, Clone, PartialEq, Eq)]
235pub enum StatePropertyValueMatcher {
236 Exact(String),
237 Range {
238 min: Option<String>,
239 max: Option<String>,
240 },
241}
242
243impl StatePropertyValueMatcher {
244 fn matches(&self, property: &dyn Property, actual: &str) -> bool {
245 match self {
246 Self::Exact(expected) => {
247 property.compare_value_names(actual, expected) == Some(Ordering::Equal)
248 }
249 Self::Range { min, max } => {
250 if let Some(min) = min
251 && property
252 .compare_value_names(actual, min)
253 .is_none_or(Ordering::is_lt)
254 {
255 return false;
256 }
257 if let Some(max) = max
258 && property
259 .compare_value_names(actual, max)
260 .is_none_or(Ordering::is_gt)
261 {
262 return false;
263 }
264 true
265 }
266 }
267 }
268
269 pub(crate) fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
270 if let Some(value) = tag.string() {
271 return Some(Self::Exact(value.to_string()));
272 }
273 let compound = tag.compound()?;
274 Some(Self::Range {
275 min: optional_string(compound, "min")?,
276 max: optional_string(compound, "max")?,
277 })
278 }
279
280 pub(crate) fn to_nbt_tag_ref(&self) -> NbtTag {
281 match self {
282 Self::Exact(value) => NbtTag::String(value.clone().into()),
283 Self::Range { min, max } => {
284 let mut compound = NbtCompound::new();
285 if let Some(min) = min {
286 compound.insert("min", min.as_str());
287 }
288 if let Some(max) = max {
289 compound.insert("max", max.as_str());
290 }
291 NbtTag::Compound(compound)
292 }
293 }
294 }
295
296 fn write_network(&self, writer: &mut impl Write) -> Result<()> {
297 match self {
298 Self::Exact(value) => {
299 true.write(writer)?;
300 write_utf(value, writer)
301 }
302 Self::Range { min, max } => {
303 false.write(writer)?;
304 write_optional_utf(min.as_deref(), writer)?;
305 write_optional_utf(max.as_deref(), writer)
306 }
307 }
308 }
309
310 fn read_network(data: &mut Cursor<&[u8]>) -> Result<Self> {
311 if bool::read(data)? {
312 Ok(Self::Exact(read_utf(data)?))
313 } else {
314 Ok(Self::Range {
315 min: read_optional_utf(data)?,
316 max: read_optional_utf(data)?,
317 })
318 }
319 }
320}
321
322impl HashComponent for StatePropertyValueMatcher {
323 fn hash_component(&self, hasher: &mut ComponentHasher) {
324 self.to_nbt_tag_ref().hash_component(hasher);
325 }
326}
327
328#[derive(Debug, Clone, PartialEq, Eq)]
330pub struct StatePropertyMatcher {
331 name: String,
332 value: StatePropertyValueMatcher,
333}
334
335impl StatePropertyMatcher {
336 #[must_use]
337 pub const fn new(name: String, value: StatePropertyValueMatcher) -> Self {
338 Self { name, value }
339 }
340
341 #[must_use]
342 pub fn name(&self) -> &str {
343 &self.name
344 }
345
346 #[must_use]
347 pub const fn value(&self) -> &StatePropertyValueMatcher {
348 &self.value
349 }
350}
351
352#[derive(Debug, Clone, PartialEq, Eq)]
354pub struct StatePropertiesPredicate {
355 properties: Vec<StatePropertyMatcher>,
356}
357
358impl StatePropertiesPredicate {
359 #[must_use]
360 pub fn new(properties: Vec<StatePropertyMatcher>) -> Option<Self> {
361 let mut names = rustc_hash::FxHashSet::default();
362 properties
363 .iter()
364 .all(|property| names.insert(property.name.clone()))
365 .then_some(Self { properties })
366 }
367
368 #[must_use]
369 pub fn properties(&self) -> &[StatePropertyMatcher] {
370 &self.properties
371 }
372
373 #[must_use]
375 pub fn matches_block_state(&self, state: BlockStateId) -> bool {
376 let Some(block) = REGISTRY.blocks.by_state_id(state) else {
377 return false;
378 };
379 let values = REGISTRY.blocks.get_properties(state);
380
381 self.properties.iter().all(|matcher| {
382 let Some(index) = block
383 .properties
384 .iter()
385 .position(|property| property.get_name() == matcher.name())
386 else {
387 return false;
388 };
389 matcher
390 .value()
391 .matches(block.properties[index], values[index].1)
392 })
393 }
394
395 pub(crate) fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
396 let compound = tag.compound()?;
397 let mut properties = Vec::with_capacity(compound.len());
398 for (name, value) in compound.iter() {
399 properties.push(StatePropertyMatcher::new(
400 name.to_owned().try_into_string().ok()?,
401 StatePropertyValueMatcher::from_owned_nbt(value)?,
402 ));
403 }
404 Self::new(properties)
405 }
406
407 pub(crate) fn to_nbt_tag_ref(&self) -> NbtTag {
408 let mut compound = NbtCompound::new();
409 for property in &self.properties {
410 compound.insert(property.name.clone(), property.value.to_nbt_tag_ref());
411 }
412 NbtTag::Compound(compound)
413 }
414}
415
416impl WriteTo for StatePropertiesPredicate {
417 fn write(&self, writer: &mut impl Write) -> Result<()> {
418 write_len(self.properties.len(), writer)?;
419 for property in &self.properties {
420 write_utf(&property.name, writer)?;
421 property.value.write_network(writer)?;
422 }
423 Ok(())
424 }
425}
426
427impl ReadFrom for StatePropertiesPredicate {
428 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
429 let count = read_len(data, usize::MAX, "state property")?;
430 let mut properties = Vec::with_capacity(count.min(1024));
431 for _ in 0..count {
432 properties.push(StatePropertyMatcher::new(
433 read_utf(data)?,
434 StatePropertyValueMatcher::read_network(data)?,
435 ));
436 }
437 Self::new(properties).ok_or_else(|| Error::other("duplicate state property matcher"))
438 }
439}
440
441impl HashComponent for StatePropertiesPredicate {
442 fn hash_component(&self, hasher: &mut ComponentHasher) {
443 self.to_nbt_tag_ref().hash_component(hasher);
444 }
445}
446
447#[derive(Debug, Clone)]
449pub struct NbtPredicate {
450 tag: NbtCompound,
451}
452
453impl NbtPredicate {
454 #[must_use]
455 pub fn new(tag: NbtCompound) -> Option<Self> {
456 normalize_nbt_compound(tag).map(|tag| Self { tag })
457 }
458
459 #[must_use]
460 pub const fn tag(&self) -> &NbtCompound {
461 &self.tag
462 }
463
464 pub(crate) fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
465 match tag {
466 NbtTag::String(value) => {
467 let value = value.to_owned().try_into_string().ok()?;
468 Self::new(parse_snbt_compound(&value).ok()?)
469 }
470 NbtTag::Compound(compound) => Self::new(compound.clone()),
471 _ => None,
472 }
473 }
474
475 pub(crate) fn to_nbt_tag_ref(&self) -> NbtTag {
476 let Some(snbt) = to_canonical_snbt(&NbtTag::Compound(self.tag.clone())) else {
477 panic!("normalized NBT predicate became malformed");
478 };
479 NbtTag::String(snbt.into())
480 }
481}
482
483impl PartialEq for NbtPredicate {
484 fn eq(&self, other: &Self) -> bool {
485 steel_utils::nbt::nbt_compounds_equal(&self.tag, &other.tag)
486 }
487}
488
489impl WriteTo for NbtPredicate {
490 fn write(&self, writer: &mut impl Write) -> Result<()> {
491 let mut encoded = Vec::new();
492 NbtTag::Compound(self.tag.clone()).write(&mut encoded);
493 writer.write_all(&encoded)
494 }
495}
496
497impl ReadFrom for NbtPredicate {
498 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
499 let NbtTag::Compound(compound) = read_network_nbt(data)? else {
500 return Err(Error::other(
501 "NBT predicate network value is not a compound",
502 ));
503 };
504 Self::new(compound).ok_or_else(|| Error::other("NBT predicate contains malformed UTF-8"))
505 }
506}
507
508impl HashComponent for NbtPredicate {
509 fn hash_component(&self, hasher: &mut ComponentHasher) {
510 let NbtTag::String(value) = self.to_nbt_tag_ref() else {
511 unreachable!("NBT predicate codec always writes a string");
512 };
513 hasher.put_string(&value.to_string());
514 }
515}
516
517#[derive(Debug, Clone, PartialEq)]
519pub struct BlockPredicate {
520 blocks: Option<RegistryHolderSet<Block>>,
521 state: Option<StatePropertiesPredicate>,
522 nbt: Option<NbtPredicate>,
523 components: DataComponentMatchers,
524}
525
526impl BlockPredicate {
527 #[must_use]
528 pub const fn new(
529 blocks: Option<RegistryHolderSet<Block>>,
530 state: Option<StatePropertiesPredicate>,
531 nbt: Option<NbtPredicate>,
532 components: DataComponentMatchers,
533 ) -> Self {
534 Self {
535 blocks,
536 state,
537 nbt,
538 components,
539 }
540 }
541
542 #[must_use]
543 pub const fn blocks(&self) -> Option<&RegistryHolderSet<Block>> {
544 self.blocks.as_ref()
545 }
546
547 #[must_use]
548 pub const fn state(&self) -> Option<&StatePropertiesPredicate> {
549 self.state.as_ref()
550 }
551
552 #[must_use]
553 pub const fn nbt(&self) -> Option<&NbtPredicate> {
554 self.nbt.as_ref()
555 }
556
557 #[must_use]
558 pub const fn components(&self) -> &DataComponentMatchers {
559 &self.components
560 }
561
562 #[must_use]
564 pub fn matches_state(&self, state: BlockStateId) -> bool {
565 let Some(block) = REGISTRY.blocks.by_state_id(state) else {
566 return false;
567 };
568 if self
569 .blocks
570 .as_ref()
571 .is_some_and(|blocks| !blocks.contains(block))
572 {
573 return false;
574 }
575 self.state
576 .as_ref()
577 .is_none_or(|properties| properties.matches_block_state(state))
578 }
579
580 fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
581 let compound = tag.compound()?;
582 Some(Self::new(
583 decode_optional(compound, "blocks", RegistryHolderSet::from_owned_nbt)?,
584 decode_optional(compound, "state", StatePropertiesPredicate::from_owned_nbt)?,
585 decode_optional(compound, "nbt", NbtPredicate::from_owned_nbt)?,
586 DataComponentMatchers::from_fields(compound)?,
587 ))
588 }
589
590 fn to_nbt_tag_ref(&self) -> NbtTag {
591 let mut compound = NbtCompound::new();
592 if let Some(blocks) = &self.blocks {
593 compound.insert("blocks", blocks.clone().to_nbt_tag());
594 }
595 if let Some(state) = &self.state {
596 compound.insert("state", state.to_nbt_tag_ref());
597 }
598 if let Some(nbt) = &self.nbt {
599 compound.insert("nbt", nbt.to_nbt_tag_ref());
600 }
601 self.components.write_fields(&mut compound);
602 NbtTag::Compound(compound)
603 }
604}
605
606impl WriteTo for BlockPredicate {
607 fn write(&self, writer: &mut impl Write) -> Result<()> {
608 self.blocks.write(writer)?;
609 self.state.write(writer)?;
610 self.nbt.write(writer)?;
611 self.components.write(writer)
612 }
613}
614
615impl ReadFrom for BlockPredicate {
616 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
617 Ok(Self::new(
618 Option::<RegistryHolderSet<Block>>::read(data)?,
619 Option::<StatePropertiesPredicate>::read(data)?,
620 Option::<NbtPredicate>::read(data)?,
621 DataComponentMatchers::read(data)?,
622 ))
623 }
624}
625
626impl HashComponent for BlockPredicate {
627 fn hash_component(&self, hasher: &mut ComponentHasher) {
628 let mut entries = Vec::new();
629 if let Some(blocks) = &self.blocks {
630 push_hash_entry(&mut entries, "blocks", blocks);
631 }
632 if let Some(state) = &self.state {
633 push_hash_entry(&mut entries, "state", state);
634 }
635 if let Some(nbt) = &self.nbt {
636 push_hash_entry(&mut entries, "nbt", nbt);
637 }
638 self.components.hash_fields(&mut entries);
639 hash_entries(hasher, &mut entries);
640 }
641}
642
643#[derive(Debug, Clone, PartialEq)]
645pub struct AdventureModePredicate {
646 predicates: Vec<BlockPredicate>,
647}
648
649impl AdventureModePredicate {
650 #[must_use]
651 pub fn new(predicates: Vec<BlockPredicate>) -> Option<Self> {
652 (!predicates.is_empty()).then_some(Self { predicates })
653 }
654
655 #[must_use]
656 pub fn predicates(&self) -> &[BlockPredicate] {
657 &self.predicates
658 }
659}
660
661impl WriteTo for AdventureModePredicate {
662 fn write(&self, writer: &mut impl Write) -> Result<()> {
663 write_len(self.predicates.len(), writer)?;
664 for predicate in &self.predicates {
665 predicate.write(writer)?;
666 }
667 Ok(())
668 }
669}
670
671impl ReadFrom for AdventureModePredicate {
672 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
673 let count = read_len(data, usize::MAX, "adventure-mode predicate")?;
674 let mut predicates = Vec::with_capacity(count.min(1024));
675 for _ in 0..count {
676 predicates.push(BlockPredicate::read(data)?);
677 }
678 Self::new(predicates)
679 .ok_or_else(|| Error::other("adventure-mode predicate list cannot be empty"))
680 }
681}
682
683impl ToNbtTag for AdventureModePredicate {
684 fn to_nbt_tag(self) -> NbtTag {
685 if self.predicates.len() == 1 {
686 return self.predicates[0].to_nbt_tag_ref();
687 }
688 NbtTag::List(NbtList::Compound(
689 self.predicates
690 .iter()
691 .map(|predicate| {
692 let NbtTag::Compound(compound) = predicate.to_nbt_tag_ref() else {
693 unreachable!("block predicate codec always writes a compound");
694 };
695 compound
696 })
697 .collect(),
698 ))
699 }
700}
701
702impl FromNbtTag for AdventureModePredicate {
703 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
704 let tag = tag.to_owned();
705 let predicates = if tag.compound().is_some() {
706 vec![BlockPredicate::from_owned_nbt(&tag)?]
707 } else {
708 tag.list()?
709 .compounds()?
710 .iter()
711 .map(|compound| BlockPredicate::from_owned_nbt(&NbtTag::Compound(compound.clone())))
712 .collect::<Option<Vec<_>>>()?
713 };
714 Self::new(predicates)
715 }
716}
717
718impl HashComponent for AdventureModePredicate {
719 fn hash_component(&self, hasher: &mut ComponentHasher) {
720 if self.predicates.len() == 1 {
721 self.predicates[0].hash_component(hasher);
722 return;
723 }
724 hasher.start_list();
725 for predicate in &self.predicates {
726 hasher.put_component_hash(predicate);
727 }
728 hasher.end_list();
729 }
730}
731
732#[derive(Debug, Clone, PartialEq)]
734pub struct ItemPredicate {
735 items: Option<RegistryHolderSet<Item>>,
736 count: IntBounds,
737 components: DataComponentMatchers,
738}
739
740impl ItemPredicate {
741 #[must_use]
742 pub const fn new(
743 items: Option<RegistryHolderSet<Item>>,
744 count: IntBounds,
745 components: DataComponentMatchers,
746 ) -> Self {
747 Self {
748 items,
749 count,
750 components,
751 }
752 }
753
754 #[must_use]
755 pub const fn any() -> Self {
756 Self::new(None, IntBounds::ANY, DataComponentMatchers::ANY)
757 }
758
759 #[must_use]
760 pub const fn items(&self) -> Option<&RegistryHolderSet<Item>> {
761 self.items.as_ref()
762 }
763
764 #[must_use]
765 pub const fn count(&self) -> IntBounds {
766 self.count
767 }
768
769 #[must_use]
770 pub const fn components(&self) -> &DataComponentMatchers {
771 &self.components
772 }
773
774 pub(crate) fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
775 let compound = tag.compound()?;
776 Some(Self::new(
777 decode_optional(compound, "items", RegistryHolderSet::from_owned_nbt)?,
778 compound
779 .get("count")
780 .map_or(Some(IntBounds::ANY), IntBounds::from_owned_nbt)?,
781 DataComponentMatchers::from_fields(compound)?,
782 ))
783 }
784
785 pub(crate) fn to_nbt_tag_ref(&self) -> NbtTag {
786 let mut compound = NbtCompound::new();
787 if let Some(items) = &self.items {
788 compound.insert("items", items.clone().to_nbt_tag());
789 }
790 if !self.count.is_any() {
791 compound.insert("count", self.count.as_nbt_tag());
792 }
793 self.components.write_fields(&mut compound);
794 NbtTag::Compound(compound)
795 }
796}
797
798impl HashComponent for ItemPredicate {
799 fn hash_component(&self, hasher: &mut ComponentHasher) {
800 let mut entries = Vec::new();
801 if let Some(items) = &self.items {
802 push_hash_entry(&mut entries, "items", items);
803 }
804 if !self.count.is_any() {
805 push_hash_entry(&mut entries, "count", &self.count);
806 }
807 self.components.hash_fields(&mut entries);
808 hash_entries(hasher, &mut entries);
809 }
810}
811
812#[derive(Debug, Clone, PartialEq)]
814pub struct LockCode {
815 predicate: ItemPredicate,
816}
817
818impl LockCode {
819 pub const NO_LOCK: Self = Self {
820 predicate: ItemPredicate::any(),
821 };
822
823 #[must_use]
824 pub const fn new(predicate: ItemPredicate) -> Self {
825 Self { predicate }
826 }
827
828 #[must_use]
829 pub const fn predicate(&self) -> &ItemPredicate {
830 &self.predicate
831 }
832
833 #[must_use]
835 pub fn to_nbt_tag_ref(&self) -> NbtTag {
836 self.predicate.to_nbt_tag_ref()
837 }
838}
839
840impl WriteTo for LockCode {
841 fn write(&self, writer: &mut impl Write) -> Result<()> {
842 let mut encoded = Vec::new();
843 self.predicate.to_nbt_tag_ref().write(&mut encoded);
844 writer.write_all(&encoded)
845 }
846}
847
848impl ReadFrom for LockCode {
849 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
850 let tag = read_network_nbt(data)?;
851 ItemPredicate::from_owned_nbt(&tag)
852 .map(Self::new)
853 .ok_or_else(|| Error::other("invalid lock item predicate"))
854 }
855}
856
857impl ToNbtTag for LockCode {
858 fn to_nbt_tag(self) -> NbtTag {
859 self.predicate.to_nbt_tag_ref()
860 }
861}
862
863impl FromNbtTag for LockCode {
864 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
865 ItemPredicate::from_owned_nbt(&tag.to_owned()).map(Self::new)
866 }
867}
868
869impl HashComponent for LockCode {
870 fn hash_component(&self, hasher: &mut ComponentHasher) {
871 self.predicate.hash_component(hasher);
872 }
873}
874
875#[expect(
876 clippy::option_option,
877 reason = "the outer option rejects malformed present fields while the inner option represents absence"
878)]
879fn optional_string(compound: &NbtCompound, key: &str) -> Option<Option<String>> {
880 decode_optional(compound, key, |tag| {
881 tag.string()?.to_owned().try_into_string().ok()
882 })
883}
884
885#[expect(
886 clippy::option_option,
887 reason = "the outer option rejects malformed present fields while the inner option represents absence"
888)]
889pub(crate) fn decode_optional<T>(
890 compound: &NbtCompound,
891 key: &str,
892 decode: impl FnOnce(&NbtTag) -> Option<T>,
893) -> Option<Option<T>> {
894 match compound.get(key) {
895 Some(tag) => Some(Some(decode(tag)?)),
896 None => Some(None),
897 }
898}
899
900pub(crate) fn read_network_nbt(data: &mut Cursor<&[u8]>) -> Result<NbtTag> {
901 let tag = read_tag(data).map_err(|error| Error::other(format!("invalid NBT: {error:?}")))?;
902 let Some(heap_size) = vanilla_nbt_heap_size(&tag) else {
903 return Err(Error::other("NBT contains malformed modified UTF-8"));
904 };
905 if heap_size > DEFAULT_NBT_QUOTA {
906 return Err(Error::other(format!(
907 "NBT exceeds Vanilla's {DEFAULT_NBT_QUOTA}-byte heap quota"
908 )));
909 }
910 Ok(tag)
911}
912
913pub(crate) fn write_len(len: usize, writer: &mut impl Write) -> Result<()> {
914 let len = i32::try_from(len).map_err(|_| Error::other("list exceeds protocol range"))?;
915 VarInt(len).write(writer)
916}
917
918pub(crate) fn read_len(data: &mut Cursor<&[u8]>, max: usize, name: &str) -> Result<usize> {
919 let encoded = VarInt::read(data)?.0;
920 let len = usize::try_from(encoded)
921 .map_err(|_| Error::other(format!("negative {name} count: {encoded}")))?;
922 if len > max {
923 return Err(Error::other(format!("{name} count {len} exceeds {max}")));
924 }
925 Ok(len)
926}
927
928fn write_utf(value: &str, writer: &mut impl Write) -> Result<()> {
929 if value.encode_utf16().count() > MAX_UTF_LENGTH {
930 return Err(Error::other("string exceeds Vanilla's UTF-16 length limit"));
931 }
932 if value.len() > MAX_UTF_LENGTH * 3 {
933 return Err(Error::other("string exceeds Vanilla's UTF-8 length limit"));
934 }
935 write_len(value.len(), writer)?;
936 writer.write_all(value.as_bytes())
937}
938
939fn read_utf(data: &mut Cursor<&[u8]>) -> Result<String> {
940 use std::io::Read as _;
941
942 let len = read_len(data, MAX_UTF_LENGTH * 3, "string byte")?;
943 let mut bytes = vec![0; len];
944 data.read_exact(&mut bytes)?;
945 let value = String::from_utf8(bytes).map_err(Error::other)?;
946 if value.encode_utf16().count() > MAX_UTF_LENGTH {
947 return Err(Error::other("string exceeds Vanilla's UTF-16 length limit"));
948 }
949 Ok(value)
950}
951
952fn write_optional_utf(value: Option<&str>, writer: &mut impl Write) -> Result<()> {
953 value.is_some().write(writer)?;
954 if let Some(value) = value {
955 write_utf(value, writer)?;
956 }
957 Ok(())
958}
959
960fn read_optional_utf(data: &mut Cursor<&[u8]>) -> Result<Option<String>> {
961 bool::read(data)?.then(|| read_utf(data)).transpose()
962}
963
964pub(crate) fn push_hash_entry<T: HashComponent + ?Sized>(
965 entries: &mut Vec<HashEntry>,
966 key: &str,
967 value: &T,
968) {
969 let mut key_hasher = ComponentHasher::new();
970 key.hash_component(&mut key_hasher);
971 let mut value_hasher = ComponentHasher::new();
972 value.hash_component(&mut value_hasher);
973 entries.push(HashEntry::new(key_hasher, value_hasher));
974}
975
976pub(crate) fn push_prehashed_entry(
977 entries: &mut Vec<HashEntry>,
978 key: &str,
979 value_hasher: ComponentHasher,
980) {
981 let mut key_hasher = ComponentHasher::new();
982 key.hash_component(&mut key_hasher);
983 entries.push(HashEntry::new(key_hasher, value_hasher));
984}
985
986pub(crate) fn hash_entries(hasher: &mut ComponentHasher, entries: &mut [HashEntry]) {
987 sort_map_entries(entries);
988 hasher.start_map();
989 for entry in entries {
990 hasher.put_raw_bytes(&entry.key_bytes);
991 hasher.put_raw_bytes(&entry.value_bytes);
992 }
993 hasher.end_map();
994}
995
996#[cfg(test)]
997mod tests {
998 use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
999 use steel_utils::Direction;
1000
1001 use super::{
1002 BlockPredicate, DoubleBounds, NbtPredicate, StatePropertiesPredicate, StatePropertyMatcher,
1003 StatePropertyValueMatcher,
1004 };
1005 use crate::blocks::block_state_ext::BlockStateExt as _;
1006 use crate::blocks::properties::BlockStateProperties;
1007 use crate::data_component_predicate::DataComponentMatchers;
1008 use crate::{RegistryHolderSet, init_vanilla_registry, vanilla_blocks};
1009
1010 #[test]
1011 fn double_bounds_use_java_ordering() {
1012 assert!(DoubleBounds::new(Some(-0.0), Some(0.0)).is_some());
1013 assert!(DoubleBounds::new(Some(0.0), Some(-0.0)).is_none());
1014 assert!(DoubleBounds::new(Some(f64::NAN), Some(f64::NAN)).is_some());
1015 assert!(DoubleBounds::new(Some(1.0), Some(f64::NAN)).is_some());
1016 assert!(DoubleBounds::new(Some(f64::NAN), Some(1.0)).is_none());
1017 }
1018
1019 #[test]
1020 fn nbt_predicate_persistence_round_trips_heterogeneous_lists() {
1021 let mut tag = NbtCompound::new();
1022 tag.insert(
1023 "values",
1024 NbtList::from(vec![NbtTag::Int(7), NbtTag::String("value".into())]),
1025 );
1026 let predicate = NbtPredicate::new(tag).expect("predicate NBT should normalize");
1027
1028 let encoded = predicate.to_nbt_tag_ref();
1029 assert_eq!(encoded, NbtTag::String("{values:[7,\"value\"]}".into()));
1030 assert_eq!(NbtPredicate::from_owned_nbt(&encoded), Some(predicate));
1031 }
1032
1033 #[test]
1034 fn block_predicates_use_typed_vanilla_property_order() {
1035 init_vanilla_registry();
1036
1037 let lit = StatePropertiesPredicate::new(vec![StatePropertyMatcher::new(
1038 "lit".to_owned(),
1039 StatePropertyValueMatcher::Range {
1040 min: Some("true".to_owned()),
1041 max: None,
1042 },
1043 )])
1044 .expect("one state property is valid");
1045 let ore = BlockPredicate::new(
1046 Some(RegistryHolderSet::Direct(vec![
1047 &vanilla_blocks::REDSTONE_ORE,
1048 ])),
1049 Some(lit),
1050 None,
1051 DataComponentMatchers::ANY,
1052 );
1053 let unlit_ore = vanilla_blocks::REDSTONE_ORE.default_state();
1054 assert!(!ore.matches_state(unlit_ore));
1055 assert!(ore.matches_state(unlit_ore.set_value(&BlockStateProperties::LIT, true)));
1056 assert!(!ore.matches_state(vanilla_blocks::REDSTONE_LAMP.default_state()));
1057
1058 let power = StatePropertiesPredicate::new(vec![StatePropertyMatcher::new(
1059 "power".to_owned(),
1060 StatePropertyValueMatcher::Range {
1061 min: Some("6".to_owned()),
1062 max: Some("8".to_owned()),
1063 },
1064 )])
1065 .expect("one state property is valid");
1066 let wire = vanilla_blocks::REDSTONE_WIRE.default_state();
1067 assert!(power.matches_block_state(wire.set_value(&BlockStateProperties::POWER, 7)));
1068 assert!(!power.matches_block_state(wire.set_value(&BlockStateProperties::POWER, 9)));
1069
1070 let facing = StatePropertiesPredicate::new(vec![StatePropertyMatcher::new(
1071 "facing".to_owned(),
1072 StatePropertyValueMatcher::Range {
1073 min: Some("up".to_owned()),
1074 max: None,
1075 },
1076 )])
1077 .expect("one state property is valid");
1078 let dispenser = vanilla_blocks::DISPENSER.default_state();
1079 assert!(facing.matches_block_state(
1080 dispenser.set_value(&BlockStateProperties::FACING, Direction::North)
1081 ));
1082 assert!(!facing.matches_block_state(
1083 dispenser.set_value(&BlockStateProperties::FACING, Direction::Down)
1084 ));
1085 }
1086}