chunkedge_item/
impls.rs

1use std::io::Write;
2
3use chunkedge_binary::{cautious_capacity, Decode, Encode, VarInt};
4use chunkedge_generated::item::ItemKind;
5
6use crate::components::{BlockPredicate, ExactComponentMatcher, ItemComponent, Patchable};
7use crate::vanilla_components::ItemKindExt;
8use crate::{HashedItemStack, ItemStack, MAX_RECURSION_DEPTH, NUM_ITEM_COMPONENTS};
9
10impl Encode for ItemStack {
11    fn encode(&self, w: impl Write) -> anyhow::Result<()> {
12        self.encode_recursive(w, false)
13    }
14}
15
16impl Encode for ItemComponent {
17    fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
18        // Break recursion loop by erasing the type
19        let w: &mut dyn Write = &mut w;
20
21        match self {
22            ItemComponent::CustomData(v) => v.encode(w),
23            ItemComponent::MaxStackSize(v) => v.encode(w),
24            ItemComponent::MaxDamage(v) => v.encode(w),
25            ItemComponent::Damage(v) => v.encode(w),
26            ItemComponent::Unbreakable => Ok(()),
27            ItemComponent::CustomName(v) => v.encode(w),
28            ItemComponent::ItemName(v) => v.encode(w),
29            ItemComponent::ItemModel(v) => v.encode(w),
30            ItemComponent::Lore(v) => v.encode(w),
31            ItemComponent::Rarity(v) => v.encode(w),
32            ItemComponent::Enchantments(v) => v.encode(w),
33            ItemComponent::CanPlaceOn(v) => v.encode(w),
34            ItemComponent::CanBreak(v) => v.encode(w),
35            ItemComponent::AttributeModifiers { modifiers } => modifiers.encode(w),
36            ItemComponent::CustomModelData {
37                floats,
38                flags,
39                strings,
40                colors,
41            } => {
42                floats.encode(&mut *w)?;
43                flags.encode(&mut *w)?;
44                strings.encode(&mut *w)?;
45                colors.encode(w)
46            }
47            ItemComponent::TooltipDisplay {
48                hide_tooltip,
49                hidden_components,
50            } => {
51                hide_tooltip.encode(&mut *w)?;
52                hidden_components.encode(w)
53            }
54            ItemComponent::RepairCost(v) => v.encode(w),
55            ItemComponent::CreativeSlotLock => Ok(()),
56            ItemComponent::EnchantmentGlintOverride(v) => v.encode(w),
57            ItemComponent::IntangibleProjectile(v) => v.encode(w),
58            ItemComponent::Food {
59                nutrition,
60                saturation_modifier,
61                can_always_eat,
62            } => {
63                nutrition.encode(&mut *w)?;
64                saturation_modifier.encode(&mut *w)?;
65                can_always_eat.encode(w)
66            }
67            ItemComponent::Consumable {
68                consume_seconds,
69                animation,
70                sound,
71                has_consume_particles,
72                effects,
73            } => {
74                consume_seconds.encode(&mut *w)?;
75                animation.encode(&mut *w)?;
76                sound.encode(&mut *w)?;
77                has_consume_particles.encode(&mut *w)?;
78                effects.encode(w)
79            }
80            ItemComponent::UseRemainder(v) => v.encode(w),
81            ItemComponent::UseCooldown {
82                seconds,
83                cooldown_group,
84            } => {
85                seconds.encode(&mut *w)?;
86                cooldown_group.encode(w)
87            }
88            ItemComponent::DamageResistant(v) => v.encode(w),
89            ItemComponent::Tool {
90                rules,
91                default_mining_speed,
92                damage_per_block,
93                can_destroy_blocks_in_creative,
94            } => {
95                rules.encode(&mut *w)?;
96                default_mining_speed.encode(&mut *w)?;
97                damage_per_block.encode(&mut *w)?;
98                can_destroy_blocks_in_creative.encode(w)
99            }
100            ItemComponent::Weapon {
101                damage_per_attack,
102                disable_blocking_for_seconds,
103            } => {
104                damage_per_attack.encode(&mut *w)?;
105                disable_blocking_for_seconds.encode(w)
106            }
107            ItemComponent::Enchantable(v) => v.encode(w),
108            ItemComponent::Equippable {
109                slot,
110                equip_sound,
111                model,
112                camera_overlay,
113                allowed_entities,
114                dispensable,
115                swappable,
116                damage_on_hurt,
117                shearing_sound,
118            } => {
119                slot.encode(&mut *w)?;
120                equip_sound.encode(&mut *w)?;
121                model.encode(&mut *w)?;
122                camera_overlay.encode(&mut *w)?;
123                allowed_entities.encode(&mut *w)?;
124                dispensable.encode(&mut *w)?;
125                swappable.encode(&mut *w)?;
126                damage_on_hurt.encode(&mut *w)?;
127                shearing_sound.encode(w)
128            }
129            ItemComponent::Repairable(v) => v.encode(w),
130            ItemComponent::Glider => Ok(()),
131            ItemComponent::TooltipStyle(v) => v.encode(w),
132            ItemComponent::DeathProtection(v) => v.encode(w),
133            ItemComponent::BlocksAttacks {
134                block_delay_seconds,
135                disable_cooldown_scale,
136                damage_reductions,
137                item_damage_threshold,
138                item_damage_base,
139                item_damage_factor,
140                bypassed_by,
141                block_sound,
142                disable_sound,
143            } => {
144                block_delay_seconds.encode(&mut *w)?;
145                disable_cooldown_scale.encode(&mut *w)?;
146                damage_reductions.encode(&mut *w)?;
147                item_damage_threshold.encode(&mut *w)?;
148                item_damage_base.encode(&mut *w)?;
149                item_damage_factor.encode(&mut *w)?;
150                bypassed_by.encode(&mut *w)?;
151                block_sound.encode(&mut *w)?;
152                disable_sound.encode(w)
153            }
154            ItemComponent::StoredEnchantments {
155                enchantments,
156                show_in_tooltip,
157            } => {
158                enchantments.encode(&mut *w)?;
159                show_in_tooltip.encode(w)
160            }
161            ItemComponent::DyedColor { color } => color.encode(w),
162            ItemComponent::MapColor(v) => v.encode(w),
163            ItemComponent::MapId(v) => v.encode(w),
164            ItemComponent::MapDecorations(v) => v.encode(w),
165            ItemComponent::MapPostProcessing(v) => v.encode(w),
166            ItemComponent::ChargedProjectiles(v) => v.encode(w),
167            ItemComponent::BundleContents(v) => v.encode(w),
168            ItemComponent::PotionContents {
169                potion_id,
170                custom_color,
171                custom_effects,
172                custom_name,
173            } => {
174                potion_id.encode(&mut *w)?;
175                custom_color.encode(&mut *w)?;
176                custom_effects.encode(&mut *w)?;
177                custom_name.encode(w)
178            }
179            ItemComponent::PotionDurationScale(v) => v.encode(w),
180            ItemComponent::SuspiciousStewEffects(v) => v.encode(w),
181            ItemComponent::WritableBookContent { pages } => pages.encode(w),
182            ItemComponent::WrittenBookContent {
183                raw_title,
184                filtered_title,
185                author,
186                generation,
187                pages,
188                resolved,
189            } => {
190                raw_title.encode(&mut *w)?;
191                filtered_title.encode(&mut *w)?;
192                author.encode(&mut *w)?;
193                generation.encode(&mut *w)?;
194                pages.encode(&mut *w)?;
195                resolved.encode(w)
196            }
197            ItemComponent::Trim {
198                material,
199                pattern,
200                show_in_tooltip,
201            } => {
202                material.encode(&mut *w)?;
203                pattern.encode(&mut *w)?;
204                show_in_tooltip.encode(w)
205            }
206            ItemComponent::DebugStickState(v) => v.encode(w),
207            ItemComponent::EntityData { id, data } => {
208                id.encode(&mut *w)?;
209                data.encode(w)
210            }
211            ItemComponent::BucketEntityData(v) => v.encode(w),
212            ItemComponent::BlockEntityData { id, data } => {
213                id.encode(&mut *w)?;
214                data.encode(w)
215            }
216            ItemComponent::Instrument(v) => v.encode(w),
217            ItemComponent::ProvidesTrimMaterial(v) => v.encode(w),
218            ItemComponent::OminousBottleAmplifier(v) => v.encode(w),
219            ItemComponent::JukeboxPlayable {
220                song,
221                show_in_tooltip,
222            } => {
223                song.encode(&mut *w)?;
224                show_in_tooltip.encode(w)
225            }
226            ItemComponent::ProvidesBannerPatterns(v) => v.encode(w),
227            ItemComponent::Recipes(v) => v.encode(w),
228            ItemComponent::LodestoneTracker { target, tracked } => {
229                target.encode(&mut *w)?;
230                tracked.encode(w)
231            }
232            ItemComponent::FireworkExplosion(v) => v.encode(w),
233            ItemComponent::Fireworks {
234                flight_duration,
235                explosions,
236            } => {
237                flight_duration.encode(&mut *w)?;
238                explosions.encode(w)
239            }
240            ItemComponent::Profile(v) => v.encode(w),
241            ItemComponent::NoteBlockSound(v) => v.encode(w),
242            ItemComponent::BannerPatterns(v) => v.encode(w),
243            ItemComponent::BaseColor(v) => v.encode(w),
244            ItemComponent::PotDecorations(v) => v.encode(w),
245            ItemComponent::Container(v) => v.encode(w),
246            ItemComponent::BlockState(v) => v.encode(w),
247            ItemComponent::Bees(v) => v.encode(w),
248            ItemComponent::Lock(v) => v.encode(w),
249            ItemComponent::ContainerLoot(v) => v.encode(w),
250            ItemComponent::BreakSound(v) => v.encode(w),
251            ItemComponent::VillagerVariant(v) => v.encode(w),
252            ItemComponent::WolfVariant(v) => v.encode(w),
253            ItemComponent::WolfSoundVariant(v) => v.encode(w),
254            ItemComponent::WolfCollar(v) => v.encode(w),
255            ItemComponent::FoxVariant(v) => v.encode(w),
256            ItemComponent::SalmonSize(v) => v.encode(w),
257            ItemComponent::ParrotVariant(v) => v.encode(w),
258            ItemComponent::TropicalFishPattern(v) => v.encode(w),
259            ItemComponent::TropicalFishBaseColor(v) => v.encode(w),
260            ItemComponent::TropicalFishPatternColor(v) => v.encode(w),
261            ItemComponent::MooshroomVariant(v) => v.encode(w),
262            ItemComponent::RabbitVariant(v) => v.encode(w),
263            ItemComponent::PigVariant(v) => v.encode(w),
264            ItemComponent::CowVariant(v) => v.encode(w),
265            ItemComponent::ChickenVariant(v) => v.encode(w),
266            ItemComponent::FrogVariant(v) => v.encode(w),
267            ItemComponent::HorseVariant(v) => v.encode(w),
268            ItemComponent::PaintingVariant(v) => v.encode(w),
269            ItemComponent::LlamaVariant(v) => v.encode(w),
270            ItemComponent::AxolotlVariant(v) => v.encode(w),
271            ItemComponent::CatVariant(v) => v.encode(w),
272            ItemComponent::CatCollar(v) => v.encode(w),
273            ItemComponent::SheepColor(v) => v.encode(w),
274            ItemComponent::ShulkerColor(v) => v.encode(w),
275        }
276    }
277}
278impl<'a> Decode<'a> for ItemStack {
279    fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
280        decode_item_stack_recursive(r, 0, false)
281    }
282}
283
284pub fn decode_item_stack_recursive(
285    r: &mut &[u8],
286    depth: usize,
287    prefixed: bool,
288) -> anyhow::Result<ItemStack> {
289    if depth > MAX_RECURSION_DEPTH {
290        return Err(anyhow::anyhow!("ItemStack recursion limit exceeded"));
291    }
292
293    let count = VarInt::decode(r)?.0;
294    if count <= 0 {
295        return Ok(ItemStack::EMPTY);
296    }
297    let item = ItemKind::decode(r)?;
298
299    let mut components = item.default_components();
300
301    // Decode counts
302    let added_count = VarInt::decode(r)?.0;
303    let removed_count = VarInt::decode(r)?.0;
304
305    // Decode Added Components
306    for _ in 0..added_count {
307        let id = VarInt::decode(r)?.0 as usize;
308        if id >= NUM_ITEM_COMPONENTS {
309            return Err(anyhow::anyhow!("Invalid item component ID: {id}"));
310        }
311
312        let _prefix = if prefixed {
313            Some(VarInt::decode(r)?)
314        } else {
315            None
316        }; // TODO: Use prefix?
317
318        let component = decode_item_component(r, id, depth)?;
319        let hash = component.hash();
320        components[id] = Patchable::Added((Box::new(component), hash));
321    }
322
323    // Decode Removed Components
324    for _ in 0..removed_count {
325        let id = VarInt::decode(r)?.0 as usize;
326        if id >= NUM_ITEM_COMPONENTS {
327            return Err(anyhow::anyhow!("Invalid item component ID: {id}"));
328        }
329        components[id] = Patchable::Removed;
330    }
331
332    Ok(ItemStack {
333        item,
334        count: count as i8,
335        components,
336    })
337}
338
339fn decode_block_predicate(r: &mut &[u8], depth: usize) -> anyhow::Result<BlockPredicate> {
340    Ok(BlockPredicate {
341        blocks: Decode::decode(r)?,
342        properties: Decode::decode(r)?,
343        nbt: Decode::decode(r)?,
344        exact_components: {
345            // Vec = |len|item*len|
346            let length = VarInt::decode(r)?.0 as usize;
347            let mut vec = Vec::with_capacity(cautious_capacity::<ExactComponentMatcher>(length));
348            for _ in 0..length {
349                let component_type = VarInt::decode(r)?;
350                let component_data =
351                    decode_item_component(r, component_type.0 as usize, depth + 1)?;
352                vec.push(ExactComponentMatcher {
353                    component_type,
354                    component_data,
355                });
356            }
357            vec
358        },
359        partial_components: Decode::decode(r)?,
360    })
361}
362
363fn decode_item_component(r: &mut &[u8], id: usize, depth: usize) -> anyhow::Result<ItemComponent> {
364    Ok(match id {
365        0 => ItemComponent::CustomData(Decode::decode(r)?),
366        1 => ItemComponent::MaxStackSize(Decode::decode(r)?),
367        2 => ItemComponent::MaxDamage(Decode::decode(r)?),
368        3 => ItemComponent::Damage(Decode::decode(r)?),
369        4 => ItemComponent::Unbreakable,
370        5 => ItemComponent::CustomName(Decode::decode(r)?),
371        6 => ItemComponent::ItemName(Decode::decode(r)?),
372        7 => ItemComponent::ItemModel(Decode::decode(r)?),
373        8 => ItemComponent::Lore(Decode::decode(r)?),
374        9 => ItemComponent::Rarity(Decode::decode(r)?),
375        10 => ItemComponent::Enchantments(Decode::decode(r)?),
376        11 => ItemComponent::CanPlaceOn({
377            let count = VarInt::decode(r)?.0;
378            let mut items = Vec::with_capacity(cautious_capacity::<BlockPredicate>(count as usize));
379            for _ in 0..count {
380                items.push(decode_block_predicate(r, depth)?);
381            }
382            items
383        }),
384        12 => ItemComponent::CanBreak({
385            let count = VarInt::decode(r)?.0;
386            let mut items = Vec::with_capacity(cautious_capacity::<BlockPredicate>(count as usize));
387            for _ in 0..count {
388                items.push(decode_block_predicate(r, depth)?);
389            }
390            items
391        }),
392        13 => ItemComponent::AttributeModifiers {
393            modifiers: Decode::decode(r)?,
394        },
395        14 => ItemComponent::CustomModelData {
396            floats: Decode::decode(r)?,
397            flags: Decode::decode(r)?,
398            strings: Decode::decode(r)?,
399            colors: Decode::decode(r)?,
400        },
401        15 => ItemComponent::TooltipDisplay {
402            hide_tooltip: Decode::decode(r)?,
403            hidden_components: Decode::decode(r)?,
404        },
405        16 => ItemComponent::RepairCost(Decode::decode(r)?),
406        17 => ItemComponent::CreativeSlotLock,
407        18 => ItemComponent::EnchantmentGlintOverride(Decode::decode(r)?),
408        19 => ItemComponent::IntangibleProjectile(Decode::decode(r)?),
409        20 => ItemComponent::Food {
410            nutrition: Decode::decode(r)?,
411            saturation_modifier: Decode::decode(r)?,
412            can_always_eat: Decode::decode(r)?,
413        },
414        21 => ItemComponent::Consumable {
415            consume_seconds: Decode::decode(r)?,
416            animation: Decode::decode(r)?,
417            sound: Decode::decode(r)?,
418            has_consume_particles: Decode::decode(r)?,
419            effects: Decode::decode(r)?,
420        },
421        22 => {
422            ItemComponent::UseRemainder(Box::new(decode_item_stack_recursive(r, depth + 1, false)?))
423        }
424        23 => ItemComponent::UseCooldown {
425            seconds: Decode::decode(r)?,
426            cooldown_group: Decode::decode(r)?,
427        },
428        24 => ItemComponent::DamageResistant(Decode::decode(r)?),
429        25 => ItemComponent::Tool {
430            rules: Decode::decode(r)?,
431            default_mining_speed: Decode::decode(r)?,
432            damage_per_block: Decode::decode(r)?,
433            can_destroy_blocks_in_creative: Decode::decode(r)?,
434        },
435        26 => ItemComponent::Weapon {
436            damage_per_attack: Decode::decode(r)?,
437            disable_blocking_for_seconds: Decode::decode(r)?,
438        },
439        27 => ItemComponent::Enchantable(Decode::decode(r)?),
440        28 => ItemComponent::Equippable {
441            slot: Decode::decode(r)?,
442            equip_sound: Decode::decode(r)?,
443            model: Decode::decode(r)?,
444            camera_overlay: Decode::decode(r)?,
445            allowed_entities: Decode::decode(r)?,
446            dispensable: Decode::decode(r)?,
447            swappable: Decode::decode(r)?,
448            damage_on_hurt: Decode::decode(r)?,
449            shearing_sound: Decode::decode(r)?,
450        },
451        29 => ItemComponent::Repairable(Decode::decode(r)?),
452        30 => ItemComponent::Glider,
453        31 => ItemComponent::TooltipStyle(Decode::decode(r)?),
454        32 => ItemComponent::DeathProtection(Decode::decode(r)?),
455        33 => ItemComponent::BlocksAttacks {
456            block_delay_seconds: Decode::decode(r)?,
457            disable_cooldown_scale: Decode::decode(r)?,
458            damage_reductions: Decode::decode(r)?,
459            item_damage_threshold: Decode::decode(r)?,
460            item_damage_base: Decode::decode(r)?,
461            item_damage_factor: Decode::decode(r)?,
462            bypassed_by: Decode::decode(r)?,
463            block_sound: Decode::decode(r)?,
464            disable_sound: Decode::decode(r)?,
465        },
466        34 => ItemComponent::StoredEnchantments {
467            enchantments: Decode::decode(r)?,
468            show_in_tooltip: Decode::decode(r)?,
469        },
470        35 => ItemComponent::DyedColor {
471            color: Decode::decode(r)?,
472        },
473        36 => ItemComponent::MapColor(Decode::decode(r)?),
474        37 => ItemComponent::MapId(Decode::decode(r)?),
475        38 => ItemComponent::MapDecorations(Decode::decode(r)?),
476        39 => ItemComponent::MapPostProcessing(Decode::decode(r)?),
477        40 => {
478            let count = VarInt::decode(r)?.0;
479            let mut items = Vec::with_capacity(cautious_capacity::<ItemStack>(count as usize));
480            for _ in 0..count {
481                items.push(decode_item_stack_recursive(r, depth + 1, false)?);
482            }
483            ItemComponent::ChargedProjectiles(items)
484        }
485        41 => {
486            let count = VarInt::decode(r)?.0;
487            let mut items = Vec::with_capacity(cautious_capacity::<ItemStack>(count as usize));
488            for _ in 0..count {
489                items.push(decode_item_stack_recursive(r, depth + 1, false)?);
490            }
491            ItemComponent::BundleContents(items)
492        }
493        42 => ItemComponent::PotionContents {
494            potion_id: Decode::decode(r)?,
495            custom_color: Decode::decode(r)?,
496            custom_effects: Decode::decode(r)?,
497            custom_name: Decode::decode(r)?,
498        },
499        43 => ItemComponent::PotionDurationScale(Decode::decode(r)?),
500        44 => ItemComponent::SuspiciousStewEffects(Decode::decode(r)?),
501        45 => ItemComponent::WritableBookContent {
502            pages: Decode::decode(r)?,
503        },
504        46 => ItemComponent::WrittenBookContent {
505            raw_title: Decode::decode(r)?,
506            filtered_title: Decode::decode(r)?,
507            author: Decode::decode(r)?,
508            generation: Decode::decode(r)?,
509            pages: Decode::decode(r)?,
510            resolved: Decode::decode(r)?,
511        },
512        47 => ItemComponent::Trim {
513            material: Decode::decode(r)?,
514            pattern: Decode::decode(r)?,
515            show_in_tooltip: Decode::decode(r)?,
516        },
517        48 => ItemComponent::DebugStickState(Decode::decode(r)?),
518        49 => ItemComponent::EntityData {
519            id: Decode::decode(r)?,
520            data: Decode::decode(r)?,
521        },
522        50 => ItemComponent::BucketEntityData(Decode::decode(r)?),
523        51 => ItemComponent::BlockEntityData {
524            id: Decode::decode(r)?,
525            data: Decode::decode(r)?,
526        },
527        52 => ItemComponent::Instrument(Decode::decode(r)?),
528        53 => ItemComponent::ProvidesTrimMaterial(Decode::decode(r)?),
529        54 => ItemComponent::OminousBottleAmplifier(Decode::decode(r)?),
530        55 => ItemComponent::JukeboxPlayable {
531            song: Decode::decode(r)?,
532            show_in_tooltip: Decode::decode(r)?,
533        },
534        56 => ItemComponent::ProvidesBannerPatterns(Decode::decode(r)?),
535        57 => ItemComponent::Recipes(Decode::decode(r)?),
536        58 => ItemComponent::LodestoneTracker {
537            target: Decode::decode(r)?,
538            tracked: Decode::decode(r)?,
539        },
540        59 => ItemComponent::FireworkExplosion(Decode::decode(r)?),
541        60 => ItemComponent::Fireworks {
542            flight_duration: Decode::decode(r)?,
543            explosions: Decode::decode(r)?,
544        },
545        61 => ItemComponent::Profile(Decode::decode(r)?),
546        62 => ItemComponent::NoteBlockSound(Decode::decode(r)?),
547        63 => ItemComponent::BannerPatterns(Decode::decode(r)?),
548        64 => ItemComponent::BaseColor(Decode::decode(r)?),
549        65 => ItemComponent::PotDecorations(Decode::decode(r)?),
550        66 => {
551            let count = VarInt::decode(r)?.0;
552            let mut items = Vec::with_capacity(cautious_capacity::<ItemStack>(count as usize));
553            for _ in 0..count {
554                items.push(decode_item_stack_recursive(r, depth + 1, false)?);
555            }
556            ItemComponent::Container(items)
557        }
558        67 => ItemComponent::BlockState(Decode::decode(r)?),
559        68 => ItemComponent::Bees(Decode::decode(r)?),
560        69 => ItemComponent::Lock(Decode::decode(r)?),
561        70 => ItemComponent::ContainerLoot(Decode::decode(r)?),
562        71 => ItemComponent::BreakSound(Decode::decode(r)?),
563        72 => ItemComponent::VillagerVariant(Decode::decode(r)?),
564        73 => ItemComponent::WolfVariant(Decode::decode(r)?),
565        74 => ItemComponent::WolfSoundVariant(Decode::decode(r)?),
566        75 => ItemComponent::WolfCollar(Decode::decode(r)?),
567        76 => ItemComponent::FoxVariant(Decode::decode(r)?),
568        77 => ItemComponent::SalmonSize(Decode::decode(r)?),
569        78 => ItemComponent::ParrotVariant(Decode::decode(r)?),
570        79 => ItemComponent::TropicalFishPattern(Decode::decode(r)?),
571        80 => ItemComponent::TropicalFishBaseColor(Decode::decode(r)?),
572        81 => ItemComponent::TropicalFishPatternColor(Decode::decode(r)?),
573        82 => ItemComponent::MooshroomVariant(Decode::decode(r)?),
574        83 => ItemComponent::RabbitVariant(Decode::decode(r)?),
575        84 => ItemComponent::PigVariant(Decode::decode(r)?),
576        85 => ItemComponent::CowVariant(Decode::decode(r)?),
577        86 => ItemComponent::ChickenVariant(Decode::decode(r)?),
578        87 => ItemComponent::FrogVariant(Decode::decode(r)?),
579        88 => ItemComponent::HorseVariant(Decode::decode(r)?),
580        89 => ItemComponent::PaintingVariant(Decode::decode(r)?),
581        90 => ItemComponent::LlamaVariant(Decode::decode(r)?),
582        91 => ItemComponent::AxolotlVariant(Decode::decode(r)?),
583        92 => ItemComponent::CatVariant(Decode::decode(r)?),
584        93 => ItemComponent::CatCollar(Decode::decode(r)?),
585        94 => ItemComponent::SheepColor(Decode::decode(r)?),
586        95 => ItemComponent::ShulkerColor(Decode::decode(r)?),
587        _ => return Err(anyhow::anyhow!("Unknown ItemComponent ID: {id}")),
588    })
589}
590
591// Encode for HashedItemStack as described in "Hashed Format"
592impl Encode for HashedItemStack {
593    fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
594        if self.is_empty() {
595            false.encode(&mut w)
596        } else {
597            true.encode(&mut w)?;
598            self.item.encode(&mut w)?;
599            VarInt(i32::from(self.count)).encode(&mut w)?;
600
601            let mut added = Vec::new();
602            let mut removed = Vec::new();
603
604            for (i, c) in self.components.iter().enumerate() {
605                match c {
606                    Patchable::Added(((), hash)) => added.push((i, hash)),
607                    Patchable::Removed => removed.push(i),
608                    _ => {}
609                }
610            }
611
612            VarInt(added.len() as i32).encode(&mut w)?;
613            for (id, hash) in added {
614                VarInt(id as i32).encode(&mut w)?;
615                hash.encode(&mut w)?;
616            }
617
618            VarInt(removed.len() as i32).encode(&mut w)?;
619            for id in removed {
620                VarInt(id as i32).encode(&mut w)?;
621            }
622
623            Ok(())
624        }
625    }
626}
627impl Decode<'_> for HashedItemStack {
628    fn decode(r: &mut &'_ [u8]) -> anyhow::Result<Self> {
629        let has_item = bool::decode(r)?;
630        if !has_item {
631            Ok(Self::EMPTY)
632        } else {
633            let item = ItemKind::decode(r)?;
634            let item_count = VarInt::decode(r)?;
635
636            let mut components = [Patchable::None; NUM_ITEM_COMPONENTS];
637
638            let components_added: Vec<(VarInt, i32)> = Vec::decode(r)?;
639            let components_removed: Vec<VarInt> = Vec::decode(r)?;
640
641            for (id, hash) in components_added {
642                let id = id.0 as usize;
643                if id >= NUM_ITEM_COMPONENTS {
644                    return Err(anyhow::anyhow!("Invalid item component ID: {id}"));
645                }
646                components[id] = Patchable::Added(((), hash));
647            }
648
649            for id in components_removed {
650                let id = id.0 as usize;
651                if id >= NUM_ITEM_COMPONENTS {
652                    return Err(anyhow::anyhow!("Invalid item component ID: {id}"));
653                }
654                components[id] = Patchable::Removed;
655            }
656
657            Ok(Self {
658                item,
659                count: item_count.0 as i8,
660                components,
661            })
662        }
663    }
664}