Both components had incorrect Parse/Serialize implementations that would
cause packet deserialization misalignment when encountered in-game.
PotionContentsComponent (3 bugs):
- Serialize unconditionally wrote VarInt(PotionId) and Int(CustomColor)
even when HasPotionId/HasCustomColor was false. The official format
(PotionContents.STREAM_CODEC) uses Optional encoding: Bool(hasValue)
followed by the value only when true. The extra bytes caused all
subsequent fields in the packet to be read at wrong offsets.
- Serialize omitted the VarInt(count) prefix for the custom effects list.
The official codec uses ByteBufCodecs.list() which always writes a
VarInt count header before the list elements.
- Also fixed typo: PotiononId -> PotionId.
InstrumentComponent (3 bugs):
- The official Instrument.STREAM_CODEC uses ByteBufCodecs.holder() which
encodes as VarInt(holderId): 0 = inline data, N>0 = registry ref (N-1).
The SoundEvent field inside uses the same holder pattern. The old code
unconditionally read SoundName (ResourceLocation) and HasFixedRange/
FixedRange even when SoundEventHolderId != 0 (registry reference case
has no inline data).
- UseDuration was read/written as Float, but the official codec uses
ByteBufCodecs.VAR_INT. This caused a 4-byte vs variable-length
mismatch that would shift all subsequent data.
- HasFixedRange was read unconditionally when SoundEventHolderId == 0,
but FixedRange was also read unconditionally. The official SoundEvent
DIRECT_STREAM_CODEC uses Optional<Float> encoding: Bool(hasValue)
followed by Float only when true.
These components are used for potion items and goat horns respectively.
Verified against official 1.20.6 decompiled source:
- net.minecraft.world.item.alchemy.PotionContents (STREAM_CODEC)
- net.minecraft.world.item.Instrument (STREAM_CODEC/DIRECT_STREAM_CODEC)
- net.minecraft.sounds.SoundEvent (STREAM_CODEC/DIRECT_STREAM_CODEC)
- net.minecraft.network.codec.ByteBufCodecs (holder/optional/list)
Made-with: Cursor
Audited all 58 StructuredComponent subclasses against the official Minecraft
1.20.6 decompiled source to verify Parse()/Serialize() symmetry. Found and
fixed four bugs across four components:
1. ContainerComponent: Parse() skipped null item slots (empty slots in a
container) but Serialize() looped NumberOfItems times using Items[i],
causing IndexOutOfRangeException when any slot was empty. The official
ItemContainerContents uses OPTIONAL_STREAM_CODEC which serializes empty
slots as VarInt(0). Fixed: Parse now stores all slots including nulls,
Serialize uses Items.Count and iterates all entries. GetItemSlot(null)
correctly writes VarInt(0) for empty slots.
2. ChargedProjectilesComponent: Used Items.OfType<Item>() in Serialize()
which silently dropped null entries, causing the serialized count to
differ from the written VarInt header. The official ChargedProjectiles
uses STREAM_CODEC (non-optional, no empty slots allowed). Fixed: Items
list is now List<Item> (non-nullable), Parse defensively skips nulls,
Serialize writes Items.Count matching the actual list.
3. BundleContentsComponent: Same issue as ChargedProjectilesComponent.
Applied the same fix pattern.
4. FoodComponentComponent: Two type mismatches vs the official
FoodProperties.DIRECT_STREAM_CODEC:
- Saturation was declared as bool and read with ReadNextBool (1 byte),
but the protocol sends it as float (4 bytes). This caused all
subsequent fields in the component to be read at wrong offsets,
corrupting CanAlwaysEat, SecondsToEat, and the effects list.
- NumberOfEffects was serialized with GetFloat() instead of GetVarInt(),
writing 4 bytes of IEEE 754 float instead of a variable-length integer.
Fixed both Parse and Serialize to use correct types.
Also removed redundant NumberOfItems/NumberOfEffects fields from components
where the count is derivable from the list length, and replaced
ArgumentNullException with cleaner patterns.
Tested end-to-end on vanilla 1.20.6 server: item receiving (diamond_sword,
golden_apple, diamond_pickaxe), inventory slot movement (click to pick up
and place), and inventory listing all work correctly with no server-side
protocol errors.
Made-with: Cursor