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 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 let added_count = VarInt::decode(r)?.0;
303 let removed_count = VarInt::decode(r)?.0;
304
305 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 }; 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 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 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
591impl 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}