Skip to main content

steel_registry/
entity_variant.rs

1//! Closed Vanilla entity variants shared by item components and entities.
2
3use 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}