PaperMC/nms-patches/ItemStack.patch
blablubbabc c3749a2358 Remove the Damage tag from items when it is 0.
CraftBukkit (and Minecraft as well in some cases, such as when getting
an item from the creative inventory menu) will omit the Damage tag when
it is zero. However, minecraft will add the tag in some situations
nevertheless, such as when loading the ItemStack, or when explictly
setting the item undamaged.

These items (with and without the Damage tag for undamaged items) will
be considered as different by minecraft and CraftBukkit in various
situations, even though they should not. In CraftBukkit these items will
actually only be considered unsimilar if the items' metadata is not
'empty' (if it contains other additional metadata, such as enchantments,
etc.).

If the item's tag is empty after removing the Damage tag, it gets
completely removed. The setRepairCost function was adapted to behave in
the same way (removal of the tag if it becomes empty).
2019-04-20 13:00:41 +10:00

342 lines
16 KiB
Diff

--- a/net/minecraft/server/ItemStack.java
+++ b/net/minecraft/server/ItemStack.java
@@ -15,6 +15,24 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+// CraftBukkit start
+import com.mojang.datafixers.Dynamic;
+import java.util.List;
+import java.util.Map;
+
+import org.bukkit.Location;
+import org.bukkit.TreeType;
+import org.bukkit.block.BlockState;
+import org.bukkit.craftbukkit.block.CraftBlock;
+import org.bukkit.craftbukkit.block.CraftBlockState;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.craftbukkit.util.CraftMagicNumbers;
+import org.bukkit.entity.Player;
+import org.bukkit.event.block.BlockFertilizeEvent;
+import org.bukkit.event.player.PlayerItemDamageEvent;
+import org.bukkit.event.world.StructureGrowEvent;
+// CraftBukkit end
+
public final class ItemStack {
private static final Logger c = LogManager.getLogger();
@@ -49,25 +67,49 @@
this.E();
}
+ // Called to run this stack through the data converter to handle older storage methods and serialized items
+ public void convertStack() {
+ if (false && MinecraftServer.getServer() != null) { // Skip for now, causes more issues than it solves
+ // Don't convert some things - both the old and new data values are valid
+ // Conversion would make getting then impossible
+ if (this.item == Blocks.PUMPKIN.getItem() || this.item == Blocks.GRASS.getItem() || this.item == Blocks.SNOW.getItem()) {
+ return;
+ }
+
+ NBTTagCompound savedStack = new NBTTagCompound();
+ this.save(savedStack);
+ savedStack = (NBTTagCompound) MinecraftServer.getServer().dataConverterManager.update(DataConverterTypes.ITEM_STACK, new Dynamic(DynamicOpsNBT.a, savedStack), -1, CraftMagicNumbers.INSTANCE.getDataVersion()).getValue();
+ this.load(savedStack);
+ }
+ }
+
private void E() {
+ if (this.h && this == ItemStack.a) throw new AssertionError("TRAP"); // CraftBukkit
this.h = false;
this.h = this.isEmpty();
}
- private ItemStack(NBTTagCompound nbttagcompound) {
+ // CraftBukkit - break into own method
+ private void load(NBTTagCompound nbttagcompound) {
Item item = (Item) IRegistry.ITEM.get(new MinecraftKey(nbttagcompound.getString("id")));
this.item = item == null ? Items.AIR : item;
this.count = nbttagcompound.getByte("Count");
if (nbttagcompound.hasKeyOfType("tag", 10)) {
- this.tag = nbttagcompound.getCompound("tag");
- this.getItem().a(nbttagcompound);
+ // CraftBukkit start - make defensive copy as this data may be coming from the save thread
+ this.tag = (NBTTagCompound) nbttagcompound.getCompound("tag").clone();
+ this.getItem().a(this.tag);
+ // CraftBukkit end
}
if (this.getItem().usesDurability()) {
this.setDamage(this.getDamage());
}
+ }
+ private ItemStack(NBTTagCompound nbttagcompound) {
+ this.load(nbttagcompound);
+ // CraftBukkit end
this.E();
}
@@ -97,7 +139,7 @@
return this.h ? Items.AIR : this.item;
}
- public EnumInteractionResult placeItem(ItemActionContext itemactioncontext) {
+ public EnumInteractionResult placeItem(ItemActionContext itemactioncontext, EnumHand enumhand) { // CraftBukkit - add hand
EntityHuman entityhuman = itemactioncontext.getEntity();
BlockPosition blockposition = itemactioncontext.getClickPosition();
ShapeDetectorBlock shapedetectorblock = new ShapeDetectorBlock(itemactioncontext.getWorld(), blockposition, false);
@@ -105,12 +147,150 @@
if (entityhuman != null && !entityhuman.abilities.mayBuild && !this.b(itemactioncontext.getWorld().F(), shapedetectorblock)) {
return EnumInteractionResult.PASS;
} else {
+ // CraftBukkit start - handle all block place event logic here
+ NBTTagCompound oldData = this.getTagClone();
+ int oldCount = this.getCount();
+ World world = itemactioncontext.getWorld();
+
+ if (!(this.getItem() instanceof ItemBucket)) { // if not bucket
+ world.captureBlockStates = true;
+ // special case bonemeal
+ if (this.getItem() == Items.BONE_MEAL) {
+ world.captureTreeGeneration = true;
+ }
+ }
Item item = this.getItem();
EnumInteractionResult enuminteractionresult = item.a(itemactioncontext);
+ NBTTagCompound newData = this.getTagClone();
+ int newCount = this.getCount();
+ this.setCount(oldCount);
+ this.setTagClone(oldData);
+ world.captureBlockStates = false;
+ if (enuminteractionresult == EnumInteractionResult.SUCCESS && world.captureTreeGeneration && world.capturedBlockStates.size() > 0) {
+ world.captureTreeGeneration = false;
+ Location location = new Location(world.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ());
+ TreeType treeType = BlockSapling.treeType;
+ BlockSapling.treeType = null;
+ List<BlockState> blocks = (List<BlockState>) world.capturedBlockStates.clone();
+ world.capturedBlockStates.clear();
+ StructureGrowEvent structureEvent = null;
+ if (treeType != null) {
+ boolean isBonemeal = getItem() == Items.BONE_MEAL;
+ structureEvent = new StructureGrowEvent(location, treeType, isBonemeal, (Player) entityhuman.getBukkitEntity(), blocks);
+ org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent);
+ }
+
+ BlockFertilizeEvent fertilizeEvent = new BlockFertilizeEvent(CraftBlock.at(world, blockposition), (Player) entityhuman.getBukkitEntity(), blocks);
+ fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled());
+ org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent);
+
+ if (!fertilizeEvent.isCancelled()) {
+ // Change the stack to its new contents if it hasn't been tampered with.
+ if (this.getCount() == oldCount && Objects.equals(this.tag, oldData)) {
+ this.setTag(newData);
+ this.setCount(newCount);
+ }
+ for (BlockState blockstate : blocks) {
+ blockstate.update(true);
+ }
+ }
+
+ return enuminteractionresult;
+ }
+ world.captureTreeGeneration = false;
if (entityhuman != null && enuminteractionresult == EnumInteractionResult.SUCCESS) {
- entityhuman.b(StatisticList.ITEM_USED.b(item));
+ org.bukkit.event.block.BlockPlaceEvent placeEvent = null;
+ List<BlockState> blocks = (List<BlockState>) world.capturedBlockStates.clone();
+ world.capturedBlockStates.clear();
+ if (blocks.size() > 1) {
+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ());
+ } else if (blocks.size() == 1) {
+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ());
+ }
+
+ if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
+ enuminteractionresult = EnumInteractionResult.FAIL; // cancel placement
+ // PAIL: Remove this when MC-99075 fixed
+ placeEvent.getPlayer().updateInventory();
+ // revert back all captured blocks
+ for (BlockState blockstate : blocks) {
+ blockstate.update(true, false);
+ }
+
+ // Brute force all possible updates
+ BlockPosition placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition();
+ for (EnumDirection dir : EnumDirection.values()) {
+ ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutBlockChange(world, placedPos.shift(dir)));
+ }
+ } else {
+ // Change the stack to its new contents if it hasn't been tampered with.
+ if (this.getCount() == oldCount && Objects.equals(this.tag, oldData)) {
+ this.setTag(newData);
+ this.setCount(newCount);
+ }
+
+ for (Map.Entry<BlockPosition, TileEntity> e : world.capturedTileEntities.entrySet()) {
+ world.setTileEntity(e.getKey(), e.getValue());
+ }
+
+ for (BlockState blockstate : blocks) {
+ int updateFlag = ((CraftBlockState) blockstate).getFlag();
+ IBlockData oldBlock = ((CraftBlockState) blockstate).getHandle();
+ BlockPosition newblockposition = ((CraftBlockState) blockstate).getPosition();
+ IBlockData block = world.getType(newblockposition);
+
+ if (!(block.getBlock() instanceof BlockTileEntity)) { // Containers get placed automatically
+ block.getBlock().onPlace(block, world, newblockposition, oldBlock);
+ }
+
+ world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getType(newblockposition), updateFlag); // send null chunk as chunk.k() returns false by this point
+ }
+
+ // Special case juke boxes as they update their tile entity. Copied from ItemRecord.
+ // PAIL: checkme on updates.
+ if (this.item instanceof ItemRecord) {
+ ((BlockJukeBox) Blocks.JUKEBOX).a(world, blockposition, world.getType(blockposition), this);
+ world.a((EntityHuman) null, 1010, blockposition, Item.getId(this.item));
+ this.subtract(1);
+ entityhuman.a(StatisticList.PLAY_RECORD);
+ }
+
+ if (this.item == Items.WITHER_SKELETON_SKULL) { // Special case skulls to allow wither spawns to be cancelled
+ BlockPosition bp = blockposition;
+ if (!world.getType(blockposition).getMaterial().isReplaceable()) {
+ if (!world.getType(blockposition).getMaterial().isBuildable()) {
+ bp = null;
+ } else {
+ bp = bp.shift(itemactioncontext.getClickedFace());
+ }
+ }
+ if (bp != null) {
+ TileEntity te = world.getTileEntity(bp);
+ if (te instanceof TileEntitySkull) {
+ BlockWitherSkull.a(world, bp, (TileEntitySkull) te);
+ }
+ }
+ }
+
+ // SPIGOT-4678
+ if (this.item instanceof ItemSign && ItemSign.openSign) {
+ ItemSign.openSign = false;
+ entityhuman.openSign((TileEntitySign) world.getTileEntity(new BlockActionContext(itemactioncontext).getClickPosition()));
+ }
+
+ // SPIGOT-1288 - play sound stripped from ItemBlock
+ if (this.item instanceof ItemBlock) {
+ SoundEffectType soundeffecttype = ((ItemBlock) this.item).getBlock().getStepSound();
+ world.a(entityhuman, blockposition, soundeffecttype.e(), SoundCategory.BLOCKS, (soundeffecttype.a() + 1.0F) / 2.0F, soundeffecttype.b() * 0.8F);
+ }
+
+ entityhuman.b(StatisticList.ITEM_USED.b(item));
+ }
}
+ world.capturedTileEntities.clear();
+ world.capturedBlockStates.clear();
+ // CraftBukkit end
return enuminteractionresult;
}
@@ -134,7 +314,7 @@
nbttagcompound.setString("id", minecraftkey == null ? "minecraft:air" : minecraftkey.toString());
nbttagcompound.setByte("Count", (byte) this.count);
if (this.tag != null) {
- nbttagcompound.set("tag", this.tag);
+ nbttagcompound.set("tag", this.tag.clone()); // CraftBukkit - make defensive copy, data is going to another thread
}
return nbttagcompound;
@@ -167,6 +347,12 @@
}
public void setDamage(int i) {
+ // CraftBukkit start - remove Damage tag if 0
+ if (i <= 0) {
+ this.c("Damage"); // PAIL removeTag
+ return;
+ }
+ // CraftBukkit end
this.getOrCreateTag().setInt("Damage", Math.max(0, i));
}
@@ -191,6 +377,21 @@
}
i -= k;
+ // CraftBukkit start
+ if (entityplayer != null) {
+ PlayerItemDamageEvent event = new PlayerItemDamageEvent(entityplayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), i);
+ event.getPlayer().getServer().getPluginManager().callEvent(event);
+
+ if (i != event.getDamage() || event.isCancelled()) {
+ event.getPlayer().updateInventory();
+ }
+ if (event.isCancelled()) {
+ return false;
+ }
+
+ i = event.getDamage();
+ }
+ // CraftBukkit end
if (i <= 0) {
return false;
}
@@ -212,6 +413,11 @@
if (this.isDamaged(i, entityliving.getRandom(), entityliving instanceof EntityPlayer ? (EntityPlayer) entityliving : null)) {
entityliving.c(this);
Item item = this.getItem();
+ // CraftBukkit start - Check for item breaking
+ if (this.count == 1 && entityliving instanceof EntityHuman) {
+ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent((EntityHuman) entityliving, this);
+ }
+ // CraftBukkit end
this.subtract(1);
if (entityliving instanceof EntityHuman) {
@@ -335,6 +541,17 @@
return this.tag;
}
+ // CraftBukkit start
+ @Nullable
+ private NBTTagCompound getTagClone() {
+ return this.tag == null ? null : this.tag.clone();
+ }
+
+ private void setTagClone(@Nullable NBTTagCompound nbtttagcompound) {
+ this.setTag(nbtttagcompound == null ? null : nbtttagcompound.clone());
+ }
+ // CraftBukkit end
+
public NBTTagCompound getOrCreateTag() {
if (this.tag == null) {
this.setTag(new NBTTagCompound());
@@ -479,6 +696,12 @@
}
public void setRepairCost(int i) {
+ // CraftBukkit start - remove RepairCost tag when 0 (SPIGOT-3945)
+ if (i == 0) {
+ this.c("RepairCost"); // PAIL removeTag
+ return;
+ }
+ // CraftBukkit end
this.getOrCreateTag().setInt("RepairCost", i);
}
@@ -521,6 +744,13 @@
nbttaglist.add((NBTBase) nbttagcompound);
}
+ // CraftBukkit start
+ @Deprecated
+ public void setItem(Item item) {
+ this.item = item;
+ }
+ // CraftBukkit end
+
public IChatBaseComponent A() {
IChatBaseComponent ichatbasecomponent = (new ChatComponentText("")).addSibling(this.getName());