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: &(impl Property + ?Sized), 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 self.properties.iter().all(|matcher| {
380 let Some(index) = block
381 .properties
382 .iter()
383 .position(|property| property.get_name() == matcher.name())
384 else {
385 return false;
386 };
387 REGISTRY
388 .blocks
389 .get_property_str(state, matcher.name())
390 .is_some_and(|value| matcher.value().matches(block.properties[index], value))
391 })
392 }
393
394 pub(crate) fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
395 let compound = tag.compound()?;
396 let mut properties = Vec::with_capacity(compound.len());
397 for (name, value) in compound.iter() {
398 properties.push(StatePropertyMatcher::new(
399 name.to_owned().try_into_string().ok()?,
400 StatePropertyValueMatcher::from_owned_nbt(value)?,
401 ));
402 }
403 Self::new(properties)
404 }
405
406 pub(crate) fn to_nbt_tag_ref(&self) -> NbtTag {
407 let mut compound = NbtCompound::new();
408 for property in &self.properties {
409 compound.insert(property.name.clone(), property.value.to_nbt_tag_ref());
410 }
411 NbtTag::Compound(compound)
412 }
413}
414
415impl WriteTo for StatePropertiesPredicate {
416 fn write(&self, writer: &mut impl Write) -> Result<()> {
417 write_len(self.properties.len(), writer)?;
418 for property in &self.properties {
419 write_utf(&property.name, writer)?;
420 property.value.write_network(writer)?;
421 }
422 Ok(())
423 }
424}
425
426impl ReadFrom for StatePropertiesPredicate {
427 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
428 let count = read_len(data, usize::MAX, "state property")?;
429 let mut properties = Vec::with_capacity(count.min(1024));
430 for _ in 0..count {
431 properties.push(StatePropertyMatcher::new(
432 read_utf(data)?,
433 StatePropertyValueMatcher::read_network(data)?,
434 ));
435 }
436 Self::new(properties).ok_or_else(|| Error::other("duplicate state property matcher"))
437 }
438}
439
440impl HashComponent for StatePropertiesPredicate {
441 fn hash_component(&self, hasher: &mut ComponentHasher) {
442 self.to_nbt_tag_ref().hash_component(hasher);
443 }
444}
445
446#[derive(Debug, Clone)]
448pub struct NbtPredicate {
449 tag: NbtCompound,
450}
451
452impl NbtPredicate {
453 #[must_use]
454 pub fn new(tag: NbtCompound) -> Option<Self> {
455 normalize_nbt_compound(tag).map(|tag| Self { tag })
456 }
457
458 #[must_use]
459 pub const fn tag(&self) -> &NbtCompound {
460 &self.tag
461 }
462
463 pub(crate) fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
464 match tag {
465 NbtTag::String(value) => {
466 let value = value.to_owned().try_into_string().ok()?;
467 Self::new(parse_snbt_compound(&value).ok()?)
468 }
469 NbtTag::Compound(compound) => Self::new(compound.clone()),
470 _ => None,
471 }
472 }
473
474 pub(crate) fn to_nbt_tag_ref(&self) -> NbtTag {
475 let Some(snbt) = to_canonical_snbt(&NbtTag::Compound(self.tag.clone())) else {
476 panic!("normalized NBT predicate became malformed");
477 };
478 NbtTag::String(snbt.into())
479 }
480}
481
482impl PartialEq for NbtPredicate {
483 fn eq(&self, other: &Self) -> bool {
484 steel_utils::nbt::nbt_compounds_equal(&self.tag, &other.tag)
485 }
486}
487
488impl WriteTo for NbtPredicate {
489 fn write(&self, writer: &mut impl Write) -> Result<()> {
490 let mut encoded = Vec::new();
491 NbtTag::Compound(self.tag.clone()).write(&mut encoded);
492 writer.write_all(&encoded)
493 }
494}
495
496impl ReadFrom for NbtPredicate {
497 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
498 let NbtTag::Compound(compound) = read_network_nbt(data)? else {
499 return Err(Error::other(
500 "NBT predicate network value is not a compound",
501 ));
502 };
503 Self::new(compound).ok_or_else(|| Error::other("NBT predicate contains malformed UTF-8"))
504 }
505}
506
507impl HashComponent for NbtPredicate {
508 fn hash_component(&self, hasher: &mut ComponentHasher) {
509 let NbtTag::String(value) = self.to_nbt_tag_ref() else {
510 unreachable!("NBT predicate codec always writes a string");
511 };
512 hasher.put_string(&value.to_string());
513 }
514}
515
516#[derive(Debug, Clone, PartialEq)]
518pub struct BlockPredicate {
519 blocks: Option<RegistryHolderSet<Block>>,
520 state: Option<StatePropertiesPredicate>,
521 nbt: Option<NbtPredicate>,
522 components: DataComponentMatchers,
523}
524
525impl BlockPredicate {
526 #[must_use]
527 pub const fn new(
528 blocks: Option<RegistryHolderSet<Block>>,
529 state: Option<StatePropertiesPredicate>,
530 nbt: Option<NbtPredicate>,
531 components: DataComponentMatchers,
532 ) -> Self {
533 Self {
534 blocks,
535 state,
536 nbt,
537 components,
538 }
539 }
540
541 #[must_use]
542 pub const fn blocks(&self) -> Option<&RegistryHolderSet<Block>> {
543 self.blocks.as_ref()
544 }
545
546 #[must_use]
547 pub const fn state(&self) -> Option<&StatePropertiesPredicate> {
548 self.state.as_ref()
549 }
550
551 #[must_use]
552 pub const fn nbt(&self) -> Option<&NbtPredicate> {
553 self.nbt.as_ref()
554 }
555
556 #[must_use]
557 pub const fn components(&self) -> &DataComponentMatchers {
558 &self.components
559 }
560
561 #[must_use]
563 pub fn matches_state(&self, state: BlockStateId) -> bool {
564 let Some(block) = REGISTRY.blocks.by_state_id(state) else {
565 return false;
566 };
567 if self
568 .blocks
569 .as_ref()
570 .is_some_and(|blocks| !blocks.contains(block))
571 {
572 return false;
573 }
574 self.state
575 .as_ref()
576 .is_none_or(|properties| properties.matches_block_state(state))
577 }
578
579 fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
580 let compound = tag.compound()?;
581 Some(Self::new(
582 decode_optional(compound, "blocks", RegistryHolderSet::from_owned_nbt)?,
583 decode_optional(compound, "state", StatePropertiesPredicate::from_owned_nbt)?,
584 decode_optional(compound, "nbt", NbtPredicate::from_owned_nbt)?,
585 DataComponentMatchers::from_fields(compound)?,
586 ))
587 }
588
589 fn to_nbt_tag_ref(&self) -> NbtTag {
590 let mut compound = NbtCompound::new();
591 if let Some(blocks) = &self.blocks {
592 compound.insert("blocks", blocks.clone().to_nbt_tag());
593 }
594 if let Some(state) = &self.state {
595 compound.insert("state", state.to_nbt_tag_ref());
596 }
597 if let Some(nbt) = &self.nbt {
598 compound.insert("nbt", nbt.to_nbt_tag_ref());
599 }
600 self.components.write_fields(&mut compound);
601 NbtTag::Compound(compound)
602 }
603}
604
605impl WriteTo for BlockPredicate {
606 fn write(&self, writer: &mut impl Write) -> Result<()> {
607 self.blocks.write(writer)?;
608 self.state.write(writer)?;
609 self.nbt.write(writer)?;
610 self.components.write(writer)
611 }
612}
613
614impl ReadFrom for BlockPredicate {
615 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
616 Ok(Self::new(
617 Option::<RegistryHolderSet<Block>>::read(data)?,
618 Option::<StatePropertiesPredicate>::read(data)?,
619 Option::<NbtPredicate>::read(data)?,
620 DataComponentMatchers::read(data)?,
621 ))
622 }
623}
624
625impl HashComponent for BlockPredicate {
626 fn hash_component(&self, hasher: &mut ComponentHasher) {
627 let mut entries = Vec::new();
628 if let Some(blocks) = &self.blocks {
629 push_hash_entry(&mut entries, "blocks", blocks);
630 }
631 if let Some(state) = &self.state {
632 push_hash_entry(&mut entries, "state", state);
633 }
634 if let Some(nbt) = &self.nbt {
635 push_hash_entry(&mut entries, "nbt", nbt);
636 }
637 self.components.hash_fields(&mut entries);
638 hash_entries(hasher, &mut entries);
639 }
640}
641
642#[derive(Debug, Clone, PartialEq)]
644pub struct AdventureModePredicate {
645 predicates: Vec<BlockPredicate>,
646}
647
648impl AdventureModePredicate {
649 #[must_use]
650 pub fn new(predicates: Vec<BlockPredicate>) -> Option<Self> {
651 (!predicates.is_empty()).then_some(Self { predicates })
652 }
653
654 #[must_use]
655 pub fn predicates(&self) -> &[BlockPredicate] {
656 &self.predicates
657 }
658}
659
660impl WriteTo for AdventureModePredicate {
661 fn write(&self, writer: &mut impl Write) -> Result<()> {
662 write_len(self.predicates.len(), writer)?;
663 for predicate in &self.predicates {
664 predicate.write(writer)?;
665 }
666 Ok(())
667 }
668}
669
670impl ReadFrom for AdventureModePredicate {
671 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
672 let count = read_len(data, usize::MAX, "adventure-mode predicate")?;
673 let mut predicates = Vec::with_capacity(count.min(1024));
674 for _ in 0..count {
675 predicates.push(BlockPredicate::read(data)?);
676 }
677 Self::new(predicates)
678 .ok_or_else(|| Error::other("adventure-mode predicate list cannot be empty"))
679 }
680}
681
682impl ToNbtTag for AdventureModePredicate {
683 fn to_nbt_tag(self) -> NbtTag {
684 if self.predicates.len() == 1 {
685 return self.predicates[0].to_nbt_tag_ref();
686 }
687 NbtTag::List(NbtList::Compound(
688 self.predicates
689 .iter()
690 .map(|predicate| {
691 let NbtTag::Compound(compound) = predicate.to_nbt_tag_ref() else {
692 unreachable!("block predicate codec always writes a compound");
693 };
694 compound
695 })
696 .collect(),
697 ))
698 }
699}
700
701impl FromNbtTag for AdventureModePredicate {
702 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
703 let tag = tag.to_owned();
704 let predicates = if tag.compound().is_some() {
705 vec![BlockPredicate::from_owned_nbt(&tag)?]
706 } else {
707 tag.list()?
708 .compounds()?
709 .iter()
710 .map(|compound| BlockPredicate::from_owned_nbt(&NbtTag::Compound(compound.clone())))
711 .collect::<Option<Vec<_>>>()?
712 };
713 Self::new(predicates)
714 }
715}
716
717impl HashComponent for AdventureModePredicate {
718 fn hash_component(&self, hasher: &mut ComponentHasher) {
719 if self.predicates.len() == 1 {
720 self.predicates[0].hash_component(hasher);
721 return;
722 }
723 hasher.start_list();
724 for predicate in &self.predicates {
725 hasher.put_component_hash(predicate);
726 }
727 hasher.end_list();
728 }
729}
730
731#[derive(Debug, Clone, PartialEq)]
733pub struct ItemPredicate {
734 items: Option<RegistryHolderSet<Item>>,
735 count: IntBounds,
736 components: DataComponentMatchers,
737}
738
739impl ItemPredicate {
740 #[must_use]
741 pub const fn new(
742 items: Option<RegistryHolderSet<Item>>,
743 count: IntBounds,
744 components: DataComponentMatchers,
745 ) -> Self {
746 Self {
747 items,
748 count,
749 components,
750 }
751 }
752
753 #[must_use]
754 pub const fn any() -> Self {
755 Self::new(None, IntBounds::ANY, DataComponentMatchers::ANY)
756 }
757
758 #[must_use]
759 pub const fn items(&self) -> Option<&RegistryHolderSet<Item>> {
760 self.items.as_ref()
761 }
762
763 #[must_use]
764 pub const fn count(&self) -> IntBounds {
765 self.count
766 }
767
768 #[must_use]
769 pub const fn components(&self) -> &DataComponentMatchers {
770 &self.components
771 }
772
773 pub(crate) fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
774 let compound = tag.compound()?;
775 Some(Self::new(
776 decode_optional(compound, "items", RegistryHolderSet::from_owned_nbt)?,
777 compound
778 .get("count")
779 .map_or(Some(IntBounds::ANY), IntBounds::from_owned_nbt)?,
780 DataComponentMatchers::from_fields(compound)?,
781 ))
782 }
783
784 pub(crate) fn to_nbt_tag_ref(&self) -> NbtTag {
785 let mut compound = NbtCompound::new();
786 if let Some(items) = &self.items {
787 compound.insert("items", items.clone().to_nbt_tag());
788 }
789 if !self.count.is_any() {
790 compound.insert("count", self.count.as_nbt_tag());
791 }
792 self.components.write_fields(&mut compound);
793 NbtTag::Compound(compound)
794 }
795}
796
797impl HashComponent for ItemPredicate {
798 fn hash_component(&self, hasher: &mut ComponentHasher) {
799 let mut entries = Vec::new();
800 if let Some(items) = &self.items {
801 push_hash_entry(&mut entries, "items", items);
802 }
803 if !self.count.is_any() {
804 push_hash_entry(&mut entries, "count", &self.count);
805 }
806 self.components.hash_fields(&mut entries);
807 hash_entries(hasher, &mut entries);
808 }
809}
810
811#[derive(Debug, Clone, PartialEq)]
813pub struct LockCode {
814 predicate: ItemPredicate,
815}
816
817impl LockCode {
818 pub const NO_LOCK: Self = Self {
819 predicate: ItemPredicate::any(),
820 };
821
822 #[must_use]
823 pub const fn new(predicate: ItemPredicate) -> Self {
824 Self { predicate }
825 }
826
827 #[must_use]
828 pub const fn predicate(&self) -> &ItemPredicate {
829 &self.predicate
830 }
831
832 #[must_use]
834 pub fn to_nbt_tag_ref(&self) -> NbtTag {
835 self.predicate.to_nbt_tag_ref()
836 }
837}
838
839impl WriteTo for LockCode {
840 fn write(&self, writer: &mut impl Write) -> Result<()> {
841 let mut encoded = Vec::new();
842 self.predicate.to_nbt_tag_ref().write(&mut encoded);
843 writer.write_all(&encoded)
844 }
845}
846
847impl ReadFrom for LockCode {
848 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
849 let tag = read_network_nbt(data)?;
850 ItemPredicate::from_owned_nbt(&tag)
851 .map(Self::new)
852 .ok_or_else(|| Error::other("invalid lock item predicate"))
853 }
854}
855
856impl ToNbtTag for LockCode {
857 fn to_nbt_tag(self) -> NbtTag {
858 self.predicate.to_nbt_tag_ref()
859 }
860}
861
862impl FromNbtTag for LockCode {
863 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
864 ItemPredicate::from_owned_nbt(&tag.to_owned()).map(Self::new)
865 }
866}
867
868impl HashComponent for LockCode {
869 fn hash_component(&self, hasher: &mut ComponentHasher) {
870 self.predicate.hash_component(hasher);
871 }
872}
873
874#[expect(
875 clippy::option_option,
876 reason = "the outer option rejects malformed present fields while the inner option represents absence"
877)]
878fn optional_string(compound: &NbtCompound, key: &str) -> Option<Option<String>> {
879 decode_optional(compound, key, |tag| {
880 tag.string()?.to_owned().try_into_string().ok()
881 })
882}
883
884#[expect(
885 clippy::option_option,
886 reason = "the outer option rejects malformed present fields while the inner option represents absence"
887)]
888pub(crate) fn decode_optional<T>(
889 compound: &NbtCompound,
890 key: &str,
891 decode: impl FnOnce(&NbtTag) -> Option<T>,
892) -> Option<Option<T>> {
893 match compound.get(key) {
894 Some(tag) => Some(Some(decode(tag)?)),
895 None => Some(None),
896 }
897}
898
899pub(crate) fn read_network_nbt(data: &mut Cursor<&[u8]>) -> Result<NbtTag> {
900 let tag = read_tag(data).map_err(|error| Error::other(format!("invalid NBT: {error:?}")))?;
901 let Some(heap_size) = vanilla_nbt_heap_size(&tag) else {
902 return Err(Error::other("NBT contains malformed modified UTF-8"));
903 };
904 if heap_size > DEFAULT_NBT_QUOTA {
905 return Err(Error::other(format!(
906 "NBT exceeds Vanilla's {DEFAULT_NBT_QUOTA}-byte heap quota"
907 )));
908 }
909 Ok(tag)
910}
911
912pub(crate) fn write_len(len: usize, writer: &mut impl Write) -> Result<()> {
913 let len = i32::try_from(len).map_err(|_| Error::other("list exceeds protocol range"))?;
914 VarInt(len).write(writer)
915}
916
917pub(crate) fn read_len(data: &mut Cursor<&[u8]>, max: usize, name: &str) -> Result<usize> {
918 let encoded = VarInt::read(data)?.0;
919 let len = usize::try_from(encoded)
920 .map_err(|_| Error::other(format!("negative {name} count: {encoded}")))?;
921 if len > max {
922 return Err(Error::other(format!("{name} count {len} exceeds {max}")));
923 }
924 Ok(len)
925}
926
927fn write_utf(value: &str, writer: &mut impl Write) -> Result<()> {
928 if value.encode_utf16().count() > MAX_UTF_LENGTH {
929 return Err(Error::other("string exceeds Vanilla's UTF-16 length limit"));
930 }
931 if value.len() > MAX_UTF_LENGTH * 3 {
932 return Err(Error::other("string exceeds Vanilla's UTF-8 length limit"));
933 }
934 write_len(value.len(), writer)?;
935 writer.write_all(value.as_bytes())
936}
937
938fn read_utf(data: &mut Cursor<&[u8]>) -> Result<String> {
939 use std::io::Read as _;
940
941 let len = read_len(data, MAX_UTF_LENGTH * 3, "string byte")?;
942 let mut bytes = vec![0; len];
943 data.read_exact(&mut bytes)?;
944 let value = String::from_utf8(bytes).map_err(Error::other)?;
945 if value.encode_utf16().count() > MAX_UTF_LENGTH {
946 return Err(Error::other("string exceeds Vanilla's UTF-16 length limit"));
947 }
948 Ok(value)
949}
950
951fn write_optional_utf(value: Option<&str>, writer: &mut impl Write) -> Result<()> {
952 value.is_some().write(writer)?;
953 if let Some(value) = value {
954 write_utf(value, writer)?;
955 }
956 Ok(())
957}
958
959fn read_optional_utf(data: &mut Cursor<&[u8]>) -> Result<Option<String>> {
960 bool::read(data)?.then(|| read_utf(data)).transpose()
961}
962
963pub(crate) fn push_hash_entry<T: HashComponent + ?Sized>(
964 entries: &mut Vec<HashEntry>,
965 key: &str,
966 value: &T,
967) {
968 let mut key_hasher = ComponentHasher::new();
969 key.hash_component(&mut key_hasher);
970 let mut value_hasher = ComponentHasher::new();
971 value.hash_component(&mut value_hasher);
972 entries.push(HashEntry::new(key_hasher, value_hasher));
973}
974
975pub(crate) fn push_prehashed_entry(
976 entries: &mut Vec<HashEntry>,
977 key: &str,
978 value_hasher: ComponentHasher,
979) {
980 let mut key_hasher = ComponentHasher::new();
981 key.hash_component(&mut key_hasher);
982 entries.push(HashEntry::new(key_hasher, value_hasher));
983}
984
985pub(crate) fn hash_entries(hasher: &mut ComponentHasher, entries: &mut [HashEntry]) {
986 sort_map_entries(entries);
987 hasher.start_map();
988 for entry in entries {
989 hasher.put_raw_bytes(&entry.key_bytes);
990 hasher.put_raw_bytes(&entry.value_bytes);
991 }
992 hasher.end_map();
993}
994
995#[cfg(test)]
996mod tests {
997 use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
998 use steel_utils::Direction;
999
1000 use super::{
1001 BlockPredicate, DoubleBounds, NbtPredicate, StatePropertiesPredicate, StatePropertyMatcher,
1002 StatePropertyValueMatcher,
1003 };
1004 use crate::blocks::block_state_ext::BlockStateExt as _;
1005 use crate::blocks::properties::BlockStateProperties;
1006 use crate::data_component_predicate::DataComponentMatchers;
1007 use crate::{RegistryHolderSet, init_vanilla_registry, vanilla_blocks};
1008
1009 #[test]
1010 fn double_bounds_use_java_ordering() {
1011 assert!(DoubleBounds::new(Some(-0.0), Some(0.0)).is_some());
1012 assert!(DoubleBounds::new(Some(0.0), Some(-0.0)).is_none());
1013 assert!(DoubleBounds::new(Some(f64::NAN), Some(f64::NAN)).is_some());
1014 assert!(DoubleBounds::new(Some(1.0), Some(f64::NAN)).is_some());
1015 assert!(DoubleBounds::new(Some(f64::NAN), Some(1.0)).is_none());
1016 }
1017
1018 #[test]
1019 fn nbt_predicate_persistence_round_trips_heterogeneous_lists() {
1020 let mut tag = NbtCompound::new();
1021 tag.insert(
1022 "values",
1023 NbtList::from(vec![NbtTag::Int(7), NbtTag::String("value".into())]),
1024 );
1025 let predicate = NbtPredicate::new(tag).expect("predicate NBT should normalize");
1026
1027 let encoded = predicate.to_nbt_tag_ref();
1028 assert_eq!(encoded, NbtTag::String("{values:[7,\"value\"]}".into()));
1029 assert_eq!(NbtPredicate::from_owned_nbt(&encoded), Some(predicate));
1030 }
1031
1032 #[test]
1033 fn block_predicates_use_typed_vanilla_property_order() {
1034 init_vanilla_registry();
1035
1036 let lit = StatePropertiesPredicate::new(vec![StatePropertyMatcher::new(
1037 "lit".to_owned(),
1038 StatePropertyValueMatcher::Range {
1039 min: Some("true".to_owned()),
1040 max: None,
1041 },
1042 )])
1043 .expect("one state property is valid");
1044 let ore = BlockPredicate::new(
1045 Some(RegistryHolderSet::Direct(vec![
1046 &vanilla_blocks::REDSTONE_ORE,
1047 ])),
1048 Some(lit),
1049 None,
1050 DataComponentMatchers::ANY,
1051 );
1052 let unlit_ore = vanilla_blocks::REDSTONE_ORE.default_state();
1053 assert!(!ore.matches_state(unlit_ore));
1054 assert!(ore.matches_state(unlit_ore.set_value(&BlockStateProperties::LIT, true)));
1055 assert!(!ore.matches_state(vanilla_blocks::REDSTONE_LAMP.default_state()));
1056
1057 let power = StatePropertiesPredicate::new(vec![StatePropertyMatcher::new(
1058 "power".to_owned(),
1059 StatePropertyValueMatcher::Range {
1060 min: Some("6".to_owned()),
1061 max: Some("8".to_owned()),
1062 },
1063 )])
1064 .expect("one state property is valid");
1065 let wire = vanilla_blocks::REDSTONE_WIRE.default_state();
1066 assert!(power.matches_block_state(wire.set_value(&BlockStateProperties::POWER, 7)));
1067 assert!(!power.matches_block_state(wire.set_value(&BlockStateProperties::POWER, 9)));
1068
1069 let facing = StatePropertiesPredicate::new(vec![StatePropertyMatcher::new(
1070 "facing".to_owned(),
1071 StatePropertyValueMatcher::Range {
1072 min: Some("up".to_owned()),
1073 max: None,
1074 },
1075 )])
1076 .expect("one state property is valid");
1077 let dispenser = vanilla_blocks::DISPENSER.default_state();
1078 assert!(facing.matches_block_state(
1079 dispenser.set_value(&BlockStateProperties::FACING, Direction::North)
1080 ));
1081 assert!(!facing.matches_block_state(
1082 dispenser.set_value(&BlockStateProperties::FACING, Direction::Down)
1083 ));
1084 }
1085}