mojira.dev
MC-131290

Enchantments are saved as shorts, but are loaded as and function with integer values

The bug

The game saves natural enchantments as shorts, yet using integer values (eg 32768 (max short + 1)) makes the game load them fine as integers numbers.

Note that the enchantment saving and loading logic exists in multiple methods which all behave differently.

20w07a Mojang names:

  • ItemStack.enchant(Enchantment, int): Casts the int enchantment level to byte (-128 to 127) and then stores it as short

  • EnchantmentHelper.setEnchantments(...): Casts the int enchantment level to short (-32768 to 32767) and then stores it as short

  • EnchantmentHelper.getItemEnchantmentLevel(Enchantment, ItemStack): Reads as int and then clamps it to 0 to 255

  • EnchantmentHelper.deserializeEnchantments(ListTag): Reads as int

  • EnchantmentHelper.runIterationOnItem(EnchantmentVisitor, ItemStack): Reads as int

  • EnchantedBookItem.addEnchantment(ItemStack, EnchantmentInstance): Reads as int, then writes as short

  • ItemStack.appendEnchantmentNames(List<Component>, ListTag): Reads as int

How to reproduce

  1. /give @s stick{Enchantments:[{id:"sharpness",lvl:32768}]}
  2. Close and reopen the game
    :info: Note that the enchantment level was loaded just fine

  3. Put the stick in an anvil and rename it
    ❌ The level overflows and becomes -32768

Related issues

Comments

NeunEinser

Okay, the code that is responsible for adding an enchantment is very weird.
This code is from 1.12 using mcp 940

net.minecraft.item.ItemStack.addEnchantment(Enchantment, int)

/**
 * Adds an enchantment with a desired level on the ItemStack.
 */
public void addEnchantment(Enchantment ench, int level)
{
	if (this.stackTagCompound == null)
	{
		this.setTagCompound(new NBTTagCompound());
	}
	
	if (!this.stackTagCompound.hasKey("ench", 9))
	{
		this.stackTagCompound.setTag("ench", new NBTTagList());
	}
	
	NBTTagList nbttaglist = this.stackTagCompound.getTagList("ench", 10);
	NBTTagCompound nbttagcompound = new NBTTagCompound();
	nbttagcompound.setShort("id", (short)Enchantment.getEnchantmentID(ench));
	nbttagcompound.setShort("lvl", (short)((byte)level));
	nbttaglist.appendTag(nbttagcompound);
}

The method accepts an int as parameter, then when adding it casts it to a byte, and then stores it as a short. Only thing that's missing here is a long.

Edit
The code in 1.13-pre1 looks very similar. This code was decompiled using the CFR decompiler.

awr.a(azq, int)

public void a(azq azq2, int n2) {
	this.o();
	if (!this.g.c("Enchantments", 9)) {
		this.g.a("Enchantments", new hd());
	}
	hd hd2 = this.g.d("Enchantments", 10);
	gx gx2 = new gx();
	gx2.a("id", String.valueOf(azq.b.b(azq2)));
	gx2.a("lvl", (short)((byte)n2));
	hd2.a(gx2);
}

the o() method seems to initialize the stackTagCompound field (g) if it is null. That's what has been there before, just in its own method. Other than that, there where some changes to the tag itself: It was renamed to Enchantments and is now using a String-ID.

marcono1234

@@unknown, it looks like this method is (at least in 1.12.2) only used for cases where the level is restricted to maximum values of the enchantments anyways (enchantment table, loot tables, /enchant ...), so it is not such a big problem.

migrated

Still in 18w50a.

muzikbike

Tested in 20w07a, stick stays at level 32768 even after relogging.

muzikbike

Why was this marked as affecting 20w07a?

tryashtar

That's the same behavior as before

muzikbike

Where's the issue then? How am I supposed to reproduce it?

tryashtar

1. /give @p diamond_sword
2. /enchant @p sharpness
3. /data get entity @p SelectedItem.tag.Enchantments[0].lvl
1s instead of 1

muzikbike

Can reproduce that.

Makes me believe I may have also come to the wrong conclusions for the related ticket MC-135506, can you check that one?

Avoma

Can confirm in 21w03a.

ZYX_2D

So is it means that 32767 will no longer happens?

Uriel Salischiker

slicedlime

Confirmed

(Unassigned)

enchantment-level, enchantments, nbt

Minecraft 1.13-pre1, Minecraft 1.13.2, Minecraft 18w50a, 1.15.2, 20w07a, 20w15a, 21w03a

1.17.1 Pre-release 1

Retrieved