1use std::io::{Cursor, Result, Write};
4
5use simdnbt::owned::NbtTag;
6use simdnbt::{FromNbtTag, ToNbtTag};
7use steel_utils::codec::VarInt;
8use steel_utils::hash::{ComponentHasher, HashComponent};
9use steel_utils::serial::{ReadFrom, WriteTo};
10
11macro_rules! impl_variant_codecs {
12 ($type:ty) => {
13 impl WriteTo for $type {
14 fn write(&self, writer: &mut impl Write) -> Result<()> {
15 VarInt(self.id()).write(writer)
16 }
17 }
18
19 impl ReadFrom for $type {
20 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
21 Ok(Self::by_id(VarInt::read(data)?.0))
22 }
23 }
24
25 impl ToNbtTag for $type {
26 fn to_nbt_tag(self) -> NbtTag {
27 self.serialized_name().to_nbt_tag()
28 }
29 }
30
31 impl FromNbtTag for $type {
32 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
33 Self::from_serialized_name(&tag.string()?.to_str())
34 }
35 }
36
37 impl HashComponent for $type {
38 fn hash_component(&self, hasher: &mut ComponentHasher) {
39 hasher.put_string(self.serialized_name());
40 }
41 }
42 };
43}
44
45#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
46pub enum FoxVariant {
47 #[default]
48 Red,
49 Snow,
50}
51
52impl FoxVariant {
53 pub const VALUES: [Self; 2] = [Self::Red, Self::Snow];
54
55 #[must_use]
56 pub const fn id(self) -> i32 {
57 match self {
58 Self::Red => 0,
59 Self::Snow => 1,
60 }
61 }
62
63 #[must_use]
64 pub const fn by_id(id: i32) -> Self {
65 match id {
66 1 => Self::Snow,
67 _ => Self::Red,
68 }
69 }
70
71 #[must_use]
72 pub const fn serialized_name(self) -> &'static str {
73 match self {
74 Self::Red => "red",
75 Self::Snow => "snow",
76 }
77 }
78
79 #[must_use]
80 pub const fn from_serialized_name(name: &str) -> Option<Self> {
81 match name {
82 "red" => Some(Self::Red),
83 "snow" => Some(Self::Snow),
84 _ => None,
85 }
86 }
87}
88
89impl_variant_codecs!(FoxVariant);
90
91#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
92pub enum SalmonVariant {
93 Small,
94 #[default]
95 Medium,
96 Large,
97}
98
99impl SalmonVariant {
100 pub const VALUES: [Self; 3] = [Self::Small, Self::Medium, Self::Large];
101
102 #[must_use]
103 pub const fn id(self) -> i32 {
104 match self {
105 Self::Small => 0,
106 Self::Medium => 1,
107 Self::Large => 2,
108 }
109 }
110
111 #[must_use]
112 pub const fn by_id(id: i32) -> Self {
113 match id {
114 i32::MIN..=0 => Self::Small,
115 1 => Self::Medium,
116 _ => Self::Large,
117 }
118 }
119
120 #[must_use]
121 pub const fn serialized_name(self) -> &'static str {
122 match self {
123 Self::Small => "small",
124 Self::Medium => "medium",
125 Self::Large => "large",
126 }
127 }
128
129 #[must_use]
130 pub const fn from_serialized_name(name: &str) -> Option<Self> {
131 match name {
132 "small" => Some(Self::Small),
133 "medium" => Some(Self::Medium),
134 "large" => Some(Self::Large),
135 _ => None,
136 }
137 }
138
139 #[must_use]
140 pub const fn bounding_box_scale(self) -> f32 {
141 match self {
142 Self::Small => 0.5,
143 Self::Medium => 1.0,
144 Self::Large => 1.5,
145 }
146 }
147}
148
149impl_variant_codecs!(SalmonVariant);
150
151#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
152pub enum ParrotVariant {
153 #[default]
154 RedBlue,
155 Blue,
156 Green,
157 YellowBlue,
158 Gray,
159}
160
161impl ParrotVariant {
162 pub const VALUES: [Self; 5] = [
163 Self::RedBlue,
164 Self::Blue,
165 Self::Green,
166 Self::YellowBlue,
167 Self::Gray,
168 ];
169
170 #[must_use]
171 pub const fn id(self) -> i32 {
172 match self {
173 Self::RedBlue => 0,
174 Self::Blue => 1,
175 Self::Green => 2,
176 Self::YellowBlue => 3,
177 Self::Gray => 4,
178 }
179 }
180
181 #[must_use]
182 pub const fn by_id(id: i32) -> Self {
183 match id {
184 i32::MIN..=0 => Self::RedBlue,
185 1 => Self::Blue,
186 2 => Self::Green,
187 3 => Self::YellowBlue,
188 _ => Self::Gray,
189 }
190 }
191
192 #[must_use]
193 pub const fn serialized_name(self) -> &'static str {
194 match self {
195 Self::RedBlue => "red_blue",
196 Self::Blue => "blue",
197 Self::Green => "green",
198 Self::YellowBlue => "yellow_blue",
199 Self::Gray => "gray",
200 }
201 }
202
203 #[must_use]
204 pub const fn from_serialized_name(name: &str) -> Option<Self> {
205 match name {
206 "red_blue" => Some(Self::RedBlue),
207 "blue" => Some(Self::Blue),
208 "green" => Some(Self::Green),
209 "yellow_blue" => Some(Self::YellowBlue),
210 "gray" => Some(Self::Gray),
211 _ => None,
212 }
213 }
214}
215
216impl_variant_codecs!(ParrotVariant);
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
219pub enum TropicalFishBase {
220 Small,
221 Large,
222}
223
224impl TropicalFishBase {
225 #[must_use]
226 pub const fn id(self) -> i32 {
227 match self {
228 Self::Small => 0,
229 Self::Large => 1,
230 }
231 }
232}
233
234#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
235pub enum TropicalFishPattern {
236 #[default]
237 Kob,
238 Sunstreak,
239 Snooper,
240 Dasher,
241 Brinely,
242 Spotty,
243 Flopper,
244 Stripey,
245 Glitter,
246 Blockfish,
247 Betty,
248 Clayfish,
249}
250
251impl TropicalFishPattern {
252 pub const VALUES: [Self; 12] = [
253 Self::Kob,
254 Self::Sunstreak,
255 Self::Snooper,
256 Self::Dasher,
257 Self::Brinely,
258 Self::Spotty,
259 Self::Flopper,
260 Self::Stripey,
261 Self::Glitter,
262 Self::Blockfish,
263 Self::Betty,
264 Self::Clayfish,
265 ];
266
267 #[must_use]
268 pub const fn base(self) -> TropicalFishBase {
269 match self {
270 Self::Kob
271 | Self::Sunstreak
272 | Self::Snooper
273 | Self::Dasher
274 | Self::Brinely
275 | Self::Spotty => TropicalFishBase::Small,
276 Self::Flopper
277 | Self::Stripey
278 | Self::Glitter
279 | Self::Blockfish
280 | Self::Betty
281 | Self::Clayfish => TropicalFishBase::Large,
282 }
283 }
284
285 #[must_use]
286 pub const fn id(self) -> i32 {
287 let index = match self {
288 Self::Kob | Self::Flopper => 0,
289 Self::Sunstreak | Self::Stripey => 1,
290 Self::Snooper | Self::Glitter => 2,
291 Self::Dasher | Self::Blockfish => 3,
292 Self::Brinely | Self::Betty => 4,
293 Self::Spotty | Self::Clayfish => 5,
294 };
295 self.base().id() | index << 8
296 }
297
298 #[must_use]
299 pub const fn by_id(id: i32) -> Self {
300 match id {
301 0 => Self::Kob,
302 256 => Self::Sunstreak,
303 512 => Self::Snooper,
304 768 => Self::Dasher,
305 1024 => Self::Brinely,
306 1280 => Self::Spotty,
307 1 => Self::Flopper,
308 257 => Self::Stripey,
309 513 => Self::Glitter,
310 769 => Self::Blockfish,
311 1025 => Self::Betty,
312 1281 => Self::Clayfish,
313 _ => Self::Kob,
314 }
315 }
316
317 #[must_use]
318 pub const fn serialized_name(self) -> &'static str {
319 match self {
320 Self::Kob => "kob",
321 Self::Sunstreak => "sunstreak",
322 Self::Snooper => "snooper",
323 Self::Dasher => "dasher",
324 Self::Brinely => "brinely",
325 Self::Spotty => "spotty",
326 Self::Flopper => "flopper",
327 Self::Stripey => "stripey",
328 Self::Glitter => "glitter",
329 Self::Blockfish => "blockfish",
330 Self::Betty => "betty",
331 Self::Clayfish => "clayfish",
332 }
333 }
334
335 #[must_use]
336 pub const fn from_serialized_name(name: &str) -> Option<Self> {
337 match name {
338 "kob" => Some(Self::Kob),
339 "sunstreak" => Some(Self::Sunstreak),
340 "snooper" => Some(Self::Snooper),
341 "dasher" => Some(Self::Dasher),
342 "brinely" => Some(Self::Brinely),
343 "spotty" => Some(Self::Spotty),
344 "flopper" => Some(Self::Flopper),
345 "stripey" => Some(Self::Stripey),
346 "glitter" => Some(Self::Glitter),
347 "blockfish" => Some(Self::Blockfish),
348 "betty" => Some(Self::Betty),
349 "clayfish" => Some(Self::Clayfish),
350 _ => None,
351 }
352 }
353}
354
355impl_variant_codecs!(TropicalFishPattern);
356
357#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
358pub enum MooshroomVariant {
359 #[default]
360 Red,
361 Brown,
362}
363
364impl MooshroomVariant {
365 pub const VALUES: [Self; 2] = [Self::Red, Self::Brown];
366
367 #[must_use]
368 pub const fn id(self) -> i32 {
369 match self {
370 Self::Red => 0,
371 Self::Brown => 1,
372 }
373 }
374
375 #[must_use]
376 pub const fn by_id(id: i32) -> Self {
377 if id <= 0 { Self::Red } else { Self::Brown }
378 }
379
380 #[must_use]
381 pub const fn serialized_name(self) -> &'static str {
382 match self {
383 Self::Red => "red",
384 Self::Brown => "brown",
385 }
386 }
387
388 #[must_use]
389 pub const fn from_serialized_name(name: &str) -> Option<Self> {
390 match name {
391 "red" => Some(Self::Red),
392 "brown" => Some(Self::Brown),
393 _ => None,
394 }
395 }
396}
397
398impl_variant_codecs!(MooshroomVariant);
399
400#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
401pub enum RabbitVariant {
402 #[default]
403 Brown,
404 White,
405 Black,
406 WhiteSplotched,
407 Gold,
408 Salt,
409 Evil,
410}
411
412impl RabbitVariant {
413 pub const VALUES: [Self; 7] = [
414 Self::Brown,
415 Self::White,
416 Self::Black,
417 Self::WhiteSplotched,
418 Self::Gold,
419 Self::Salt,
420 Self::Evil,
421 ];
422
423 #[must_use]
424 pub const fn id(self) -> i32 {
425 match self {
426 Self::Brown => 0,
427 Self::White => 1,
428 Self::Black => 2,
429 Self::WhiteSplotched => 3,
430 Self::Gold => 4,
431 Self::Salt => 5,
432 Self::Evil => 99,
433 }
434 }
435
436 #[must_use]
437 pub const fn by_id(id: i32) -> Self {
438 match id {
439 1 => Self::White,
440 2 => Self::Black,
441 3 => Self::WhiteSplotched,
442 4 => Self::Gold,
443 5 => Self::Salt,
444 99 => Self::Evil,
445 _ => Self::Brown,
446 }
447 }
448
449 #[must_use]
450 pub const fn serialized_name(self) -> &'static str {
451 match self {
452 Self::Brown => "brown",
453 Self::White => "white",
454 Self::Black => "black",
455 Self::WhiteSplotched => "white_splotched",
456 Self::Gold => "gold",
457 Self::Salt => "salt",
458 Self::Evil => "evil",
459 }
460 }
461
462 #[must_use]
463 pub const fn from_serialized_name(name: &str) -> Option<Self> {
464 match name {
465 "brown" => Some(Self::Brown),
466 "white" => Some(Self::White),
467 "black" => Some(Self::Black),
468 "white_splotched" => Some(Self::WhiteSplotched),
469 "gold" => Some(Self::Gold),
470 "salt" => Some(Self::Salt),
471 "evil" => Some(Self::Evil),
472 _ => None,
473 }
474 }
475}
476
477impl_variant_codecs!(RabbitVariant);
478
479#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
480pub enum HorseVariant {
481 #[default]
482 White,
483 Creamy,
484 Chestnut,
485 Brown,
486 Black,
487 Gray,
488 DarkBrown,
489}
490
491impl HorseVariant {
492 pub const VALUES: [Self; 7] = [
493 Self::White,
494 Self::Creamy,
495 Self::Chestnut,
496 Self::Brown,
497 Self::Black,
498 Self::Gray,
499 Self::DarkBrown,
500 ];
501
502 #[must_use]
503 pub const fn id(self) -> i32 {
504 match self {
505 Self::White => 0,
506 Self::Creamy => 1,
507 Self::Chestnut => 2,
508 Self::Brown => 3,
509 Self::Black => 4,
510 Self::Gray => 5,
511 Self::DarkBrown => 6,
512 }
513 }
514
515 #[must_use]
516 pub const fn by_id(id: i32) -> Self {
517 match id.rem_euclid(7) {
518 0 => Self::White,
519 1 => Self::Creamy,
520 2 => Self::Chestnut,
521 3 => Self::Brown,
522 4 => Self::Black,
523 5 => Self::Gray,
524 _ => Self::DarkBrown,
525 }
526 }
527
528 #[must_use]
529 pub const fn serialized_name(self) -> &'static str {
530 match self {
531 Self::White => "white",
532 Self::Creamy => "creamy",
533 Self::Chestnut => "chestnut",
534 Self::Brown => "brown",
535 Self::Black => "black",
536 Self::Gray => "gray",
537 Self::DarkBrown => "dark_brown",
538 }
539 }
540
541 #[must_use]
542 pub const fn from_serialized_name(name: &str) -> Option<Self> {
543 match name {
544 "white" => Some(Self::White),
545 "creamy" => Some(Self::Creamy),
546 "chestnut" => Some(Self::Chestnut),
547 "brown" => Some(Self::Brown),
548 "black" => Some(Self::Black),
549 "gray" => Some(Self::Gray),
550 "dark_brown" => Some(Self::DarkBrown),
551 _ => None,
552 }
553 }
554}
555
556impl_variant_codecs!(HorseVariant);
557
558#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
559pub enum LlamaVariant {
560 #[default]
561 Creamy,
562 White,
563 Brown,
564 Gray,
565}
566
567impl LlamaVariant {
568 pub const VALUES: [Self; 4] = [Self::Creamy, Self::White, Self::Brown, Self::Gray];
569
570 #[must_use]
571 pub const fn id(self) -> i32 {
572 match self {
573 Self::Creamy => 0,
574 Self::White => 1,
575 Self::Brown => 2,
576 Self::Gray => 3,
577 }
578 }
579
580 #[must_use]
581 pub const fn by_id(id: i32) -> Self {
582 match id {
583 i32::MIN..=0 => Self::Creamy,
584 1 => Self::White,
585 2 => Self::Brown,
586 _ => Self::Gray,
587 }
588 }
589
590 #[must_use]
591 pub const fn serialized_name(self) -> &'static str {
592 match self {
593 Self::Creamy => "creamy",
594 Self::White => "white",
595 Self::Brown => "brown",
596 Self::Gray => "gray",
597 }
598 }
599
600 #[must_use]
601 pub const fn from_serialized_name(name: &str) -> Option<Self> {
602 match name {
603 "creamy" => Some(Self::Creamy),
604 "white" => Some(Self::White),
605 "brown" => Some(Self::Brown),
606 "gray" => Some(Self::Gray),
607 _ => None,
608 }
609 }
610}
611
612impl_variant_codecs!(LlamaVariant);
613
614#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
615pub enum AxolotlVariant {
616 #[default]
617 Lucy,
618 Wild,
619 Gold,
620 Cyan,
621 Blue,
622}
623
624impl AxolotlVariant {
625 pub const VALUES: [Self; 5] = [Self::Lucy, Self::Wild, Self::Gold, Self::Cyan, Self::Blue];
626
627 #[must_use]
628 pub const fn id(self) -> i32 {
629 match self {
630 Self::Lucy => 0,
631 Self::Wild => 1,
632 Self::Gold => 2,
633 Self::Cyan => 3,
634 Self::Blue => 4,
635 }
636 }
637
638 #[must_use]
639 pub const fn by_id(id: i32) -> Self {
640 match id {
641 1 => Self::Wild,
642 2 => Self::Gold,
643 3 => Self::Cyan,
644 4 => Self::Blue,
645 _ => Self::Lucy,
646 }
647 }
648
649 #[must_use]
650 pub const fn serialized_name(self) -> &'static str {
651 match self {
652 Self::Lucy => "lucy",
653 Self::Wild => "wild",
654 Self::Gold => "gold",
655 Self::Cyan => "cyan",
656 Self::Blue => "blue",
657 }
658 }
659
660 #[must_use]
661 pub const fn from_serialized_name(name: &str) -> Option<Self> {
662 match name {
663 "lucy" => Some(Self::Lucy),
664 "wild" => Some(Self::Wild),
665 "gold" => Some(Self::Gold),
666 "cyan" => Some(Self::Cyan),
667 "blue" => Some(Self::Blue),
668 _ => None,
669 }
670 }
671
672 #[must_use]
673 pub const fn is_common(self) -> bool {
674 !matches!(self, Self::Blue)
675 }
676}
677
678impl_variant_codecs!(AxolotlVariant);
679
680#[cfg(test)]
681mod tests {
682 use std::fmt::Debug;
683 use std::io::Cursor;
684
685 use simdnbt::borrow::read_tag;
686 use simdnbt::owned::NbtTag;
687 use steel_utils::codec::VarInt;
688 use steel_utils::hash::HashComponent as _;
689 use steel_utils::serial::ReadFrom;
690
691 use super::{
692 AxolotlVariant, FoxVariant, HorseVariant, LlamaVariant, MooshroomVariant, ParrotVariant,
693 RabbitVariant, SalmonVariant, TropicalFishBase, TropicalFishPattern,
694 };
695
696 fn assert_variant<T>(value: T, id: i32, name: &str)
697 where
698 T: Copy
699 + Debug
700 + PartialEq
701 + ReadFrom
702 + steel_utils::serial::WriteTo
703 + simdnbt::FromNbtTag
704 + simdnbt::ToNbtTag
705 + steel_utils::hash::HashComponent,
706 {
707 let mut encoded = Vec::new();
708 value.write(&mut encoded).expect("variant should encode");
709 assert_eq!(
710 VarInt::read(&mut Cursor::new(encoded.as_slice()))
711 .expect("variant ID should decode")
712 .0,
713 id
714 );
715 assert_eq!(
716 T::read(&mut Cursor::new(encoded.as_slice())).expect("variant should decode"),
717 value
718 );
719 assert_eq!(value.to_nbt_tag(), NbtTag::String(name.into()));
720
721 let mut bytes = Vec::new();
722 NbtTag::String(name.into()).write(&mut bytes);
723 let borrowed =
724 read_tag(&mut Cursor::new(bytes.as_slice())).expect("owned variant tag should parse");
725 assert_eq!(T::from_nbt_tag(borrowed.as_tag()), Some(value));
726 assert_eq!(
727 value.compute_hash(),
728 NbtTag::String(name.into()).compute_hash()
729 );
730 }
731
732 #[test]
733 fn ids_names_persistent_codecs_and_hashes_match_vanilla() {
734 for value in FoxVariant::VALUES {
735 assert_variant(value, value.id(), value.serialized_name());
736 }
737 for value in SalmonVariant::VALUES {
738 assert_variant(value, value.id(), value.serialized_name());
739 }
740 for value in ParrotVariant::VALUES {
741 assert_variant(value, value.id(), value.serialized_name());
742 }
743 for value in TropicalFishPattern::VALUES {
744 assert_variant(value, value.id(), value.serialized_name());
745 }
746 for value in MooshroomVariant::VALUES {
747 assert_variant(value, value.id(), value.serialized_name());
748 }
749 for value in RabbitVariant::VALUES {
750 assert_variant(value, value.id(), value.serialized_name());
751 }
752 for value in HorseVariant::VALUES {
753 assert_variant(value, value.id(), value.serialized_name());
754 }
755 for value in LlamaVariant::VALUES {
756 assert_variant(value, value.id(), value.serialized_name());
757 }
758 for value in AxolotlVariant::VALUES {
759 assert_variant(value, value.id(), value.serialized_name());
760 }
761 }
762
763 #[test]
764 fn network_out_of_bounds_strategies_match_vanilla() {
765 assert_eq!(FoxVariant::by_id(-1), FoxVariant::Red);
766 assert_eq!(FoxVariant::by_id(2), FoxVariant::Red);
767 assert_eq!(SalmonVariant::by_id(-1), SalmonVariant::Small);
768 assert_eq!(SalmonVariant::by_id(3), SalmonVariant::Large);
769 assert_eq!(ParrotVariant::by_id(-1), ParrotVariant::RedBlue);
770 assert_eq!(ParrotVariant::by_id(5), ParrotVariant::Gray);
771 assert_eq!(TropicalFishPattern::by_id(2), TropicalFishPattern::Kob);
772 assert_eq!(MooshroomVariant::by_id(-1), MooshroomVariant::Red);
773 assert_eq!(MooshroomVariant::by_id(2), MooshroomVariant::Brown);
774 assert_eq!(RabbitVariant::by_id(6), RabbitVariant::Brown);
775 assert_eq!(HorseVariant::by_id(-1), HorseVariant::DarkBrown);
776 assert_eq!(HorseVariant::by_id(7), HorseVariant::White);
777 assert_eq!(LlamaVariant::by_id(-1), LlamaVariant::Creamy);
778 assert_eq!(LlamaVariant::by_id(4), LlamaVariant::Gray);
779 assert_eq!(AxolotlVariant::by_id(-1), AxolotlVariant::Lucy);
780 assert_eq!(AxolotlVariant::by_id(5), AxolotlVariant::Lucy);
781 }
782
783 #[test]
784 fn variant_specific_metadata_matches_vanilla() {
785 assert_eq!(SalmonVariant::Small.bounding_box_scale(), 0.5);
786 assert_eq!(SalmonVariant::Large.bounding_box_scale(), 1.5);
787 assert_eq!(TropicalFishPattern::Kob.base(), TropicalFishBase::Small);
788 assert_eq!(TropicalFishPattern::Flopper.base(), TropicalFishBase::Large);
789 assert_eq!(TropicalFishPattern::Spotty.id(), 1280);
790 assert_eq!(TropicalFishPattern::Clayfish.id(), 1281);
791 assert!(AxolotlVariant::Cyan.is_common());
792 assert!(!AxolotlVariant::Blue.is_common());
793 }
794}