Skip to main content

mcproto_types/slot/
types.rs

1//! Structures shared by multiple structured item components.
2
3use std::io::{Read, Write};
4
5use mcproto_codec::{
6    error::{CodecError, CodecKind, CodecOperation, InvalidEncodingReason},
7    io::{read_exact_counted, write_all_counted},
8};
9
10use crate::{
11    Boolean, Double, Float, IdOr, IdSet, Identifier, Int, Nbt, Position, PrefixedArray,
12    PrefixedOptional, PrefixedString, ProtocolEnum, SoundEvent, TextComponent, TypeCodec,
13    TypeStructCodec, VarInt,
14};
15
16use super::DataComponent;
17
18/// A network NBT `TAG_String` root value.
19///
20/// Unlike [`Nbt`], which represents a root compound, this type writes tag ID
21/// `8` followed directly by the NBT string payload. NBT uses Java CESU-8.
22#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
23pub struct NbtString(pub String);
24
25impl TypeCodec for NbtString {
26    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
27        let encoded = cesu8::to_java_cesu8(&self.0);
28        let length = u16::try_from(encoded.len()).map_err(|_| {
29            CodecError::invalid_encoding_for_operation(
30                CodecKind::Nbt,
31                CodecOperation::Write,
32                0,
33                InvalidEncodingReason::TooLong {
34                    max_bytes: u16::MAX as usize,
35                },
36            )
37        })?;
38        write_all_counted(writer, &[8], CodecKind::Nbt, 0)?;
39        write_all_counted(writer, &length.to_be_bytes(), CodecKind::Nbt, 1)?;
40        write_all_counted(writer, encoded.as_ref(), CodecKind::Nbt, 3)
41    }
42
43    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
44        let mut tag = [0; 1];
45        read_exact_counted(reader, &mut tag, CodecKind::Nbt, 0)?;
46        if tag[0] != 8 {
47            return Err(CodecError::invalid_encoding(
48                CodecKind::Nbt,
49                1,
50                InvalidEncodingReason::InvalidNbt,
51            ));
52        }
53        let mut length = [0; 2];
54        read_exact_counted(reader, &mut length, CodecKind::Nbt, 1)?;
55        let mut encoded = vec![0; u16::from_be_bytes(length) as usize];
56        read_exact_counted(reader, &mut encoded, CodecKind::Nbt, 3)?;
57        let value = cesu8::from_java_cesu8(&encoded).map_err(|_| {
58            CodecError::invalid_encoding(
59                CodecKind::Nbt,
60                3 + encoded.len(),
61                InvalidEncodingReason::InvalidNbt,
62            )
63        })?;
64        Ok(Self(value.into_owned()))
65    }
66}
67
68macro_rules! protocol_struct {
69    ($(#[$meta:meta])* $name:ident { $($(#[$field_meta:meta])* $field:ident: $ty:ty),* $(,)? }) => {
70        $(#[$meta])*
71        #[derive(Debug, Clone, PartialEq, TypeStructCodec)]
72        #[type_struct_codec(kind = StructuredComponent)]
73        pub struct $name { $($(#[$field_meta])* pub $field: $ty,)* }
74    };
75}
76
77/// A raw value and an optional filtered replacement.
78#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
79#[type_struct_codec(kind = StructuredComponent)]
80pub struct Filterable<T> {
81    /// Unfiltered value.
82    pub raw: T,
83    /// Filtered value, when supplied.
84    pub filtered: PrefixedOptional<T>,
85}
86
87protocol_struct!(/// One enchantment and its level.
88    EnchantmentEntry { type_id: VarInt, level: VarInt });
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, ProtocolEnum)]
91#[protocol_enum(repr = VarInt)]
92pub enum Rarity {
93    Common = 0,
94    Uncommon = 1,
95    Rare = 2,
96    Epic = 3,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, ProtocolEnum)]
100#[protocol_enum(repr = VarInt)]
101pub enum AttributeOperation {
102    Add = 0,
103    MultiplyBase = 1,
104    MultiplyTotal = 2,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, ProtocolEnum)]
108#[protocol_enum(repr = VarInt)]
109pub enum AttributeSlot {
110    Any = 0,
111    MainHand = 1,
112    OffHand = 2,
113    Hand = 3,
114    Feet = 4,
115    Legs = 5,
116    Chest = 6,
117    Head = 7,
118    Armor = 8,
119    Body = 9,
120}
121
122protocol_struct!(/// One item attribute modifier.
123AttributeModifier {
124    attribute_id: VarInt,
125    modifier_id: Identifier,
126    value: Double,
127    operation: AttributeOperation,
128    slot: AttributeSlot,
129});
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, ProtocolEnum)]
132#[protocol_enum(repr = VarInt)]
133pub enum ItemUseAnimation {
134    None = 0,
135    Eat = 1,
136    Drink = 2,
137    Block = 3,
138    Bow = 4,
139    Spear = 5,
140    Crossbow = 6,
141    Spyglass = 7,
142    TootHorn = 8,
143    Brush = 9,
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq, ProtocolEnum)]
147#[protocol_enum(repr = VarInt)]
148pub enum EquipmentSlot {
149    MainHand = 0,
150    Feet = 1,
151    Legs = 2,
152    Chest = 3,
153    Head = 4,
154    OffHand = 5,
155    Body = 6,
156}
157
158protocol_struct!(/// A tool rule for a set of blocks.
159ToolRule {
160    blocks: IdSet,
161    speed: PrefixedOptional<Float>,
162    correct_drop_for_blocks: PrefixedOptional<Boolean>,
163});
164
165protocol_struct!(/// One damage reduction rule for a blocking item.
166DamageReduction {
167    horizontal_blocking_angle: Float,
168    damage_types: PrefixedOptional<IdSet>,
169    base: Float,
170    factor: Float,
171});
172
173protocol_struct!(/// A condition used by a kinetic weapon action.
174KineticWeaponCondition {
175    max_duration_ticks: VarInt,
176    min_speed: Float,
177    min_relative_speed: Float,
178});
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, ProtocolEnum)]
181#[protocol_enum(repr = VarInt)]
182pub enum SwingAnimationType {
183    None = 0,
184    Whack = 1,
185    Stab = 2,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq, ProtocolEnum)]
189#[protocol_enum(repr = VarInt)]
190pub enum MapPostProcessing {
191    Lock = 0,
192    Scale = 1,
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, ProtocolEnum)]
196#[protocol_enum(repr = VarInt)]
197pub enum DyeColor {
198    White = 0,
199    Orange = 1,
200    Magenta = 2,
201    LightBlue = 3,
202    Yellow = 4,
203    Lime = 5,
204    Pink = 6,
205    Gray = 7,
206    LightGray = 8,
207    Cyan = 9,
208    Purple = 10,
209    Blue = 11,
210    Brown = 12,
211    Green = 13,
212    Red = 14,
213    Black = 15,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, ProtocolEnum)]
217#[protocol_enum(repr = VarInt)]
218pub enum FireworkShape {
219    SmallBall = 0,
220    LargeBall = 1,
221    Star = 2,
222    Creeper = 3,
223    Burst = 4,
224}
225
226protocol_struct!(/// A firework explosion description.
227FireworkExplosion {
228    shape: FireworkShape,
229    colors: PrefixedArray<Int>,
230    fade_colors: PrefixedArray<Int>,
231    has_trail: Boolean,
232    has_twinkle: Boolean,
233});
234
235protocol_struct!(/// A suspicious stew effect entry.
236    SuspiciousStewEffect { type_id: VarInt, duration: VarInt });
237
238protocol_struct!(/// A potion effect and its recursive details.
239    PotionEffect { type_id: VarInt, details: PotionEffectDetail });
240
241/// Detailed potion effect settings, including an optional hidden effect.
242#[derive(Debug, Clone, PartialEq)]
243pub struct PotionEffectDetail {
244    pub amplifier: VarInt,
245    pub duration: VarInt,
246    pub ambient: Boolean,
247    pub show_particles: Boolean,
248    pub show_icon: Boolean,
249    pub hidden_effect: PrefixedOptional<Box<PotionEffectDetail>>,
250}
251
252impl TypeCodec for PotionEffectDetail {
253    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
254        self.amplifier.encode(writer)?;
255        self.duration.encode(writer)?;
256        self.ambient.encode(writer)?;
257        self.show_particles.encode(writer)?;
258        self.show_icon.encode(writer)?;
259        self.hidden_effect.encode(writer)
260    }
261    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
262        Ok(Self {
263            amplifier: VarInt::decode(reader)?,
264            duration: VarInt::decode(reader)?,
265            ambient: Boolean::decode(reader)?,
266            show_particles: Boolean::decode(reader)?,
267            show_icon: Boolean::decode(reader)?,
268            hidden_effect: PrefixedOptional::decode(reader)?,
269        })
270    }
271}
272
273protocol_struct!(/// An armor material asset override.
274    TrimMaterialOverride { armor_material: Identifier, asset_name: PrefixedString });
275protocol_struct!(/// Inline trim material data.
276TrimMaterial {
277    suffix: PrefixedString,
278    overrides: PrefixedArray<TrimMaterialOverride>,
279    description: TextComponent,
280});
281protocol_struct!(/// Inline trim pattern data.
282TrimPattern {
283    asset_name: PrefixedString,
284    template_item: VarInt,
285    description: TextComponent,
286    decal: Boolean,
287});
288protocol_struct!(/// Inline instrument data.
289Instrument {
290    sound_event: IdOr<SoundEvent>,
291    use_duration: Float,
292    range: Float,
293    description: TextComponent,
294});
295protocol_struct!(/// Inline jukebox song data.
296JukeboxSong {
297    sound_event: IdOr<SoundEvent>,
298    description: TextComponent,
299    duration: Float,
300    output: VarInt,
301});
302protocol_struct!(/// Inline banner pattern data.
303    BannerPattern { asset_id: Identifier, translation_key: PrefixedString });
304protocol_struct!(/// One banner pattern layer.
305    BannerPatternLayer { pattern: IdOr<BannerPattern>, color: DyeColor });
306protocol_struct!(/// One block-state name/value pair.
307    BlockStateProperty { name: PrefixedString, value: PrefixedString });
308protocol_struct!(/// A bee stored in a hive.
309Bee {
310    entity_type_id: VarInt,
311    entity_data: Nbt,
312    ticks_in_hive: VarInt,
313    min_ticks_in_hive: VarInt,
314});
315protocol_struct!(/// A dimension and block position.
316    GlobalPosition { dimension: Identifier, position: Position });
317/// Backwards-compatible name for a property used by Slot profile components.
318pub type SlotProfileProperty = crate::GameProfileProperty;
319
320protocol_struct!(/// Inline painting variant data.
321PaintingVariant {
322    width: Int,
323    height: Int,
324    asset_id: Identifier,
325    title: PrefixedOptional<TextComponent>,
326    author: PrefixedOptional<TextComponent>,
327});
328
329/// A property predicate represented without invalid combinations of optional fields.
330#[derive(Debug, Clone, PartialEq)]
331pub struct Property {
332    pub name: PrefixedString,
333    pub matcher: PropertyMatcher,
334}
335
336#[derive(Debug, Clone, PartialEq)]
337pub enum PropertyMatcher {
338    Exact(PrefixedString),
339    Range {
340        min: PrefixedString,
341        max: PrefixedString,
342    },
343}
344
345impl TypeCodec for Property {
346    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
347        self.name.encode(writer)?;
348        match &self.matcher {
349            PropertyMatcher::Exact(value) => {
350                Boolean(true).encode(writer)?;
351                value.encode(writer)
352            }
353            PropertyMatcher::Range { min, max } => {
354                Boolean(false).encode(writer)?;
355                min.encode(writer)?;
356                max.encode(writer)
357            }
358        }
359    }
360    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
361        let name = PrefixedString::decode(reader)?;
362        let matcher = if Boolean::decode(reader)?.0 {
363            PropertyMatcher::Exact(PrefixedString::decode(reader)?)
364        } else {
365            PropertyMatcher::Range {
366                min: PrefixedString::decode(reader)?,
367                max: PrefixedString::decode(reader)?,
368            }
369        };
370        Ok(Self { name, matcher })
371    }
372}
373
374protocol_struct!(/// An exact typed data-component matcher.
375    ExactDataComponentMatcher { component: DataComponent });
376
377#[derive(Debug, Clone, Copy, PartialEq, Eq, ProtocolEnum)]
378#[protocol_enum(repr = VarInt)]
379pub enum PartialMatcherType {
380    Damage = 0,
381    Enchantments = 1,
382    StoredEnchantments = 2,
383    PotionContents = 3,
384    CustomData = 4,
385    Container = 5,
386    BundleContents = 6,
387    FireworkExplosion = 7,
388    Fireworks = 8,
389    WritableBookContent = 9,
390    WrittenBookContent = 10,
391    AttributeModifiers = 11,
392    Trim = 12,
393    JukeboxPlayable = 13,
394}
395protocol_struct!(/// An NBT-backed partial component predicate.
396    PartialDataComponentMatcher { matcher_type: PartialMatcherType, predicate: Nbt });
397protocol_struct!(/// A block predicate used by adventure-mode components.
398BlockPredicate {
399    blocks: PrefixedOptional<IdSet>,
400    properties: PrefixedOptional<PrefixedArray<Property>>,
401    nbt: PrefixedOptional<Nbt>,
402    exact_components: PrefixedArray<ExactDataComponentMatcher>,
403    partial_components: PrefixedArray<PartialDataComponentMatcher>,
404});
405
406protocol_struct!(/// Data for the apply-effects consume action.
407    ApplyEffects { effects: PrefixedArray<PotionEffect>, probability: Float });
408protocol_struct!(/// Data for the remove-effects consume action.
409    RemoveEffects { effects: IdSet });
410protocol_struct!(/// Data for random teleport consumption.
411    TeleportRandomly { diameter: Float });
412protocol_struct!(/// Data for play-sound consumption.
413    PlaySound { sound: SoundEvent });
414
415/// A type-safe consume-effect tagged union.
416#[derive(Debug, Clone, PartialEq)]
417pub enum ConsumeEffect {
418    ApplyEffects(ApplyEffects),
419    RemoveEffects(RemoveEffects),
420    ClearAllEffects,
421    TeleportRandomly(TeleportRandomly),
422    PlaySound(PlaySound),
423}
424
425impl TypeCodec for ConsumeEffect {
426    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
427        match self {
428            Self::ApplyEffects(v) => {
429                VarInt(0).encode(writer)?;
430                v.encode(writer)
431            }
432            Self::RemoveEffects(v) => {
433                VarInt(1).encode(writer)?;
434                v.encode(writer)
435            }
436            Self::ClearAllEffects => VarInt(2).encode(writer),
437            Self::TeleportRandomly(v) => {
438                VarInt(3).encode(writer)?;
439                v.encode(writer)
440            }
441            Self::PlaySound(v) => {
442                VarInt(4).encode(writer)?;
443                v.encode(writer)
444            }
445        }
446    }
447    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
448        match VarInt::decode(reader)?.0 {
449            0 => Ok(Self::ApplyEffects(ApplyEffects::decode(reader)?)),
450            1 => Ok(Self::RemoveEffects(RemoveEffects::decode(reader)?)),
451            2 => Ok(Self::ClearAllEffects),
452            3 => Ok(Self::TeleportRandomly(TeleportRandomly::decode(reader)?)),
453            4 => Ok(Self::PlaySound(PlaySound::decode(reader)?)),
454            value => Err(CodecError::invalid_encoding(
455                CodecKind::StructuredComponent,
456                0,
457                InvalidEncodingReason::InvalidEnumValue {
458                    value: i128::from(value),
459                },
460            )),
461        }
462    }
463}