Skip to main content

mcproto_types/slot/
display.rs

1//! Type-safe recipe slot display tagged union.
2
3use std::io::{Read, Write};
4
5use mcproto_codec::error::{CodecError, CodecKind, InvalidEncodingReason};
6
7use crate::{Identifier, PrefixedArray, TypeCodec, TypeStructCodec, VarInt};
8
9use super::{Slot, components::DataComponentType};
10
11macro_rules! display_struct {
12    ($(#[$meta:meta])* $name:ident { $($(#[$field_meta:meta])* $field:ident: $ty:ty),* $(,)? }) => {
13        $(#[$meta])*
14        #[derive(Debug, Clone, PartialEq, TypeStructCodec)]
15        #[type_struct_codec(kind = SlotDisplay)]
16        pub struct $name { $($(#[$field_meta])* pub $field: $ty,)* }
17    };
18}
19
20display_struct!(/// Applies any potion to a base display.
21    WithAnyPotionSlotDisplay { base: Box<SlotDisplay> });
22display_struct!(/// Shows a base only with a specific component type.
23OnlyWithComponentSlotDisplay {
24    base: Box<SlotDisplay>,
25    component_type: DataComponentType,
26});
27display_struct!(/// Displays an item registry entry.
28    ItemSlotDisplay { item_type_id: VarInt });
29display_struct!(/// Displays a complete item stack.
30    ItemStackSlotDisplay { item_stack: Slot });
31display_struct!(/// Displays the members of an item tag.
32    TagSlotDisplay { tag: Identifier });
33display_struct!(/// Displays dye and target slots.
34    DyedSlotDisplay { dye: Box<SlotDisplay>, target: Box<SlotDisplay> });
35display_struct!(/// Displays a smithing trim preview.
36SmithingTrimSlotDisplay {
37    base: Box<SlotDisplay>,
38    material: Box<SlotDisplay>,
39    pattern_id: VarInt,
40});
41display_struct!(/// Displays an ingredient together with its remainder.
42WithRemainderSlotDisplay {
43    ingredient: Box<SlotDisplay>,
44    remainder: Box<SlotDisplay>,
45});
46display_struct!(/// Displays a choice among multiple slot displays.
47    CompositeSlotDisplay { options: PrefixedArray<SlotDisplay> });
48
49/// Description of a recipe ingredient slot for use by the client.
50///
51/// The enum variant determines both the registry type ID and the exact payload,
52/// so mismatched type IDs and payloads cannot be represented in memory.
53/// See the official [Slot Display structure] documentation.
54///
55/// # Examples
56///
57/// ```
58/// use mcproto_types::{
59///     CompositeSlotDisplay, ItemSlotDisplay, PrefixedArray, SlotDisplay, TypeCodec, VarInt,
60/// };
61///
62/// let display = SlotDisplay::Composite(CompositeSlotDisplay {
63///     options: PrefixedArray(vec![
64///         SlotDisplay::AnyFuel,
65///         SlotDisplay::Item(ItemSlotDisplay { item_type_id: VarInt(5) }),
66///     ]),
67/// });
68/// let mut encoded = Vec::new();
69/// display.encode(&mut encoded)?;
70/// let mut input = encoded.as_slice();
71/// assert_eq!(SlotDisplay::decode(&mut input)?, display);
72/// # Ok::<(), mcproto_codec::error::CodecError>(())
73/// ```
74///
75/// [Slot Display structure]: https://minecraft.wiki/w/Java_Edition_protocol/Recipes#Slot_Display_structure
76#[derive(Debug, Clone, PartialEq)]
77pub enum SlotDisplay {
78    Empty,
79    AnyFuel,
80    WithAnyPotion(WithAnyPotionSlotDisplay),
81    OnlyWithComponent(OnlyWithComponentSlotDisplay),
82    Item(ItemSlotDisplay),
83    ItemStack(ItemStackSlotDisplay),
84    Tag(TagSlotDisplay),
85    Dyed(DyedSlotDisplay),
86    SmithingTrim(SmithingTrimSlotDisplay),
87    WithRemainder(WithRemainderSlotDisplay),
88    Composite(CompositeSlotDisplay),
89}
90
91impl TypeCodec for SlotDisplay {
92    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
93        let (id, payload): (i32, Option<&dyn EncodableDisplay>) = match self {
94            Self::Empty => (0, None),
95            Self::AnyFuel => (1, None),
96            Self::WithAnyPotion(v) => (2, Some(v)),
97            Self::OnlyWithComponent(v) => (3, Some(v)),
98            Self::Item(v) => (4, Some(v)),
99            Self::ItemStack(v) => (5, Some(v)),
100            Self::Tag(v) => (6, Some(v)),
101            Self::Dyed(v) => (7, Some(v)),
102            Self::SmithingTrim(v) => (8, Some(v)),
103            Self::WithRemainder(v) => (9, Some(v)),
104            Self::Composite(v) => (10, Some(v)),
105        };
106        VarInt(id)
107            .encode(writer)
108            .map_err(|e| e.with_context(CodecKind::SlotDisplay))?;
109        if let Some(payload) = payload {
110            payload
111                .encode_display(writer)
112                .map_err(|e| e.with_context(CodecKind::SlotDisplay))?;
113        }
114        Ok(())
115    }
116
117    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
118        let id = VarInt::decode(reader)
119            .map_err(|e| e.with_context(CodecKind::SlotDisplay))?
120            .0;
121        match id {
122            0 => Ok(Self::Empty),
123            1 => Ok(Self::AnyFuel),
124            2 => Ok(Self::WithAnyPotion(WithAnyPotionSlotDisplay::decode(
125                reader,
126            )?)),
127            3 => Ok(Self::OnlyWithComponent(
128                OnlyWithComponentSlotDisplay::decode(reader)?,
129            )),
130            4 => Ok(Self::Item(ItemSlotDisplay::decode(reader)?)),
131            5 => Ok(Self::ItemStack(ItemStackSlotDisplay::decode(reader)?)),
132            6 => Ok(Self::Tag(TagSlotDisplay::decode(reader)?)),
133            7 => Ok(Self::Dyed(DyedSlotDisplay::decode(reader)?)),
134            8 => Ok(Self::SmithingTrim(SmithingTrimSlotDisplay::decode(reader)?)),
135            9 => Ok(Self::WithRemainder(WithRemainderSlotDisplay::decode(
136                reader,
137            )?)),
138            10 => Ok(Self::Composite(CompositeSlotDisplay::decode(reader)?)),
139            value => Err(CodecError::invalid_encoding(
140                CodecKind::SlotDisplay,
141                0,
142                InvalidEncodingReason::InvalidEnumValue {
143                    value: i128::from(value),
144                },
145            )),
146        }
147    }
148}
149
150trait EncodableDisplay {
151    fn encode_display(&self, writer: &mut dyn Write) -> Result<(), CodecError>;
152}
153
154macro_rules! impl_encodable_display {
155    ($($ty:ty),+ $(,)?) => {$(
156        impl EncodableDisplay for $ty {
157            fn encode_display(&self, writer: &mut dyn Write) -> Result<(), CodecError> {
158                struct DynWriter<'a>(&'a mut dyn Write);
159                impl Write for DynWriter<'_> {
160                    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { self.0.write(buf) }
161                    fn flush(&mut self) -> std::io::Result<()> { self.0.flush() }
162                }
163                self.encode(&mut DynWriter(writer))
164            }
165        }
166    )+};
167}
168
169impl_encodable_display!(
170    WithAnyPotionSlotDisplay,
171    OnlyWithComponentSlotDisplay,
172    ItemSlotDisplay,
173    ItemStackSlotDisplay,
174    TagSlotDisplay,
175    DyedSlotDisplay,
176    SmithingTrimSlotDisplay,
177    WithRemainderSlotDisplay,
178    CompositeSlotDisplay,
179);