Files
Paper/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
Bjarne Koll b9d3147d3b Use correct placed block position for sound (#12410)
Previously the server attempted to compute the block placed by using the
BlockPlaceContext. This approach however fails on replacable blocks, as
the BlockPlaceContext computes this during its construction, which
happened after the actual world modification.

The commit reworks this approach and now stores metadata in the
InteractionResult which can later be read.
The diff is structured to allow for easy future expansion of the tracked
metadata.
2025-04-27 14:19:42 +02:00

388 lines
23 KiB
Diff

--- a/net/minecraft/world/item/ItemStack.java
+++ b/net/minecraft/world/item/ItemStack.java
@@ -198,12 +_,20 @@
@Override
public void encode(RegistryFriendlyByteBuf buffer, ItemStack value) {
- if (value.isEmpty()) {
+ if (value.isEmpty() || value.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
buffer.writeVarInt(0);
} else {
- buffer.writeVarInt(value.getCount());
+ buffer.writeVarInt(io.papermc.paper.util.sanitizer.ItemComponentSanitizer.sanitizeCount(io.papermc.paper.util.sanitizer.ItemObfuscationSession.currentSession(), value, value.getCount())); // Paper - potentially sanitize count
Item.STREAM_CODEC.encode(buffer, value.getItemHolder());
+ // Paper start - adventure; conditionally render translatable components
+ boolean prev = net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.get();
+ try (final io.papermc.paper.util.SafeAutoClosable ignored = io.papermc.paper.util.sanitizer.ItemObfuscationSession.withContext(c -> c.itemStack(value))) { // pass the itemstack as context to the obfuscation session
+ net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(true);
codec.encode(buffer, value.components.asPatch());
+ } finally {
+ net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(prev);
+ }
+ // Paper end - adventure; conditionally render translatable components
}
}
};
@@ -373,10 +_,166 @@
return InteractionResult.PASS;
} else {
Item item = this.getItem();
- InteractionResult interactionResult = item.useOn(context);
+ // CraftBukkit start - handle all block place event logic here
+ DataComponentPatch previousPatch = this.components.asPatch();
+ int oldCount = this.getCount();
+ ServerLevel serverLevel = (ServerLevel) context.getLevel();
+
+ if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement
+ serverLevel.captureBlockStates = true;
+ // special case bonemeal
+ if (item == Items.BONE_MEAL) {
+ serverLevel.captureTreeGeneration = true;
+ }
+ }
+ InteractionResult interactionResult;
+ try {
+ interactionResult = item.useOn(context);
+ } finally {
+ serverLevel.captureBlockStates = false;
+ }
+ DataComponentPatch newPatch = this.components.asPatch();
+ int newCount = this.getCount();
+ this.setCount(oldCount);
+ this.restorePatch(previousPatch);
+ if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) {
+ serverLevel.captureTreeGeneration = false;
+ org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld());
+ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType;
+ net.minecraft.world.level.block.SaplingBlock.treeType = null;
+ List<org.bukkit.craftbukkit.block.CraftBlockState> blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values());
+ serverLevel.capturedBlockStates.clear();
+ org.bukkit.event.world.StructureGrowEvent structureEvent = null;
+ if (treeType != null) {
+ boolean isBonemeal = this.getItem() == Items.BONE_MEAL;
+ structureEvent = new org.bukkit.event.world.StructureGrowEvent(location, treeType, isBonemeal, (org.bukkit.entity.Player) player.getBukkitEntity(), (List<org.bukkit.block.BlockState>) (List<? extends org.bukkit.block.BlockState>) blocks);
+ org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent);
+ }
+
+ org.bukkit.event.block.BlockFertilizeEvent fertilizeEvent = new org.bukkit.event.block.BlockFertilizeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, clickedPos), (org.bukkit.entity.Player) player.getBukkitEntity(), (List<org.bukkit.block.BlockState>) (List<? extends org.bukkit.block.BlockState>) 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.components.asPatch(), previousPatch)) {
+ this.restorePatch(newPatch);
+ this.setCount(newCount);
+ }
+ for (org.bukkit.craftbukkit.block.CraftBlockState snapshot : blocks) {
+ // SPIGOT-7572 - Move fix for SPIGOT-7248 to CapturedBlockState, to allow bees in bee nest
+ snapshot.place(snapshot.getFlags());
+ serverLevel.checkCapturedTreeStateForObserverNotify(clickedPos, snapshot); // Paper - notify observers even if grow failed
+ }
+ player.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat
+ }
+
+ SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
+ return interactionResult;
+ }
+ serverLevel.captureTreeGeneration = false;
if (player != null && interactionResult instanceof InteractionResult.Success success && success.wasItemInteraction()) {
- player.awardStat(Stats.ITEM_USED.get(item));
+ InteractionHand hand = context.getHand();
+ org.bukkit.event.block.BlockPlaceEvent placeEvent = null;
+ List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values());
+ serverLevel.capturedBlockStates.clear();
+ if (blocks.size() > 1) {
+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, clickedPos);
+ } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement
+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, hand, blocks.getFirst(), clickedPos);
+ }
+
+ if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
+ interactionResult = InteractionResult.FAIL; // cancel placement
+ // PAIL: Remove this when MC-99075 fixed
+ player.containerMenu.sendAllDataToRemote();
+ serverLevel.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot
+ // revert back all captured blocks
+ for (org.bukkit.block.BlockState blockstate : blocks) {
+ ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).revertPlace();
+ }
+
+ SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
+ } else {
+ // Change the stack to its new contents if it hasn't been tampered with.
+ if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), previousPatch)) {
+ this.restorePatch(newPatch);
+ this.setCount(newCount);
+ }
+
+ for (java.util.Map.Entry<BlockPos, net.minecraft.world.level.block.entity.BlockEntity> e : serverLevel.capturedTileEntities.entrySet()) {
+ serverLevel.setBlockEntity(e.getValue());
+ }
+
+ for (org.bukkit.block.BlockState blockstate : blocks) {
+ int updateFlags = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getFlags();
+ net.minecraft.world.level.block.state.BlockState oldBlock = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getHandle();
+ BlockPos newPos = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getPosition();
+ net.minecraft.world.level.block.state.BlockState block = serverLevel.getBlockState(newPos);
+
+ if (!(block.getBlock() instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // Containers get placed automatically
+ block.onPlace(serverLevel, newPos, oldBlock, true, context);
+ }
+
+ serverLevel.notifyAndUpdatePhysics(newPos, null, oldBlock, block, serverLevel.getBlockState(newPos), updateFlags, net.minecraft.world.level.block.Block.UPDATE_LIMIT); // send null chunk as chunk.k() returns false by this point
+ }
+
+ if (this.item == Items.WITHER_SKELETON_SKULL) { // Special case skulls to allow wither spawns to be cancelled
+ BlockPos bp = clickedPos;
+ if (!serverLevel.getBlockState(clickedPos).canBeReplaced()) {
+ if (!serverLevel.getBlockState(clickedPos).isSolid()) {
+ bp = null;
+ } else {
+ bp = bp.relative(context.getClickedFace());
+ }
+ }
+ if (bp != null) {
+ net.minecraft.world.level.block.entity.BlockEntity te = serverLevel.getBlockEntity(bp);
+ if (te instanceof net.minecraft.world.level.block.entity.SkullBlockEntity) {
+ net.minecraft.world.level.block.WitherSkullBlock.checkSpawn(serverLevel, bp, (net.minecraft.world.level.block.entity.SkullBlockEntity) te);
+ }
+ }
+ }
+
+ // SPIGOT-4678
+ if (this.item instanceof SignItem && SignItem.openSign != null) {
+ try {
+ if (serverLevel.getBlockEntity(SignItem.openSign) instanceof net.minecraft.world.level.block.entity.SignBlockEntity blockEntity) {
+ if (serverLevel.getBlockState(SignItem.openSign).getBlock() instanceof net.minecraft.world.level.block.SignBlock signBlock) {
+ signBlock.openTextEdit(player, blockEntity, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // CraftBukkit // Paper - Add PlayerOpenSignEvent
+ }
+ }
+ } finally {
+ SignItem.openSign = null;
+ }
+ }
+
+ // SPIGOT-7315: Moved from BedBlock#setPlacedBy
+ if (placeEvent != null && this.item instanceof BedItem) {
+ BlockPos pos = ((org.bukkit.craftbukkit.block.CraftBlock) placeEvent.getBlock()).getPosition();
+ net.minecraft.world.level.block.state.BlockState state = serverLevel.getBlockState(pos);
+
+ if (state.getBlock() instanceof net.minecraft.world.level.block.BedBlock) {
+ serverLevel.updateNeighborsAt(pos, net.minecraft.world.level.block.Blocks.AIR);
+ state.updateNeighbourShapes(serverLevel, pos, 3);
+ }
+ }
+
+ // SPIGOT-1288 - play sound stripped from BlockItem
+ if (this.item instanceof BlockItem && success.paperSuccessContext().placedBlockPosition() != null) {
+ // Paper start - Fix spigot sound playing for BlockItem ItemStacks
+ net.minecraft.world.level.block.state.BlockState state = serverLevel.getBlockState(success.paperSuccessContext().placedBlockPosition());
+ net.minecraft.world.level.block.SoundType soundType = state.getSoundType();
+ // Paper end - Fix spigot sound playing for BlockItem ItemStacks
+ serverLevel.playSound(player, clickedPos, soundType.getPlaceSound(), net.minecraft.sounds.SoundSource.BLOCKS, (soundType.getVolume() + 1.0F) / 2.0F, soundType.getPitch() * 0.8F);
+ }
+
+ player.awardStat(Stats.ITEM_USED.get(item));
+ }
}
+ serverLevel.capturedTileEntities.clear();
+ serverLevel.capturedBlockStates.clear();
+ // CraftBukkit end
return interactionResult;
}
@@ -473,31 +_,71 @@
return this.isDamageableItem() && this.getDamageValue() >= this.getMaxDamage() - 1;
}
- public void hurtAndBreak(int damage, ServerLevel level, @Nullable ServerPlayer player, Consumer<Item> onBreak) {
- int i = this.processDurabilityChange(damage, level, player);
- if (i != 0) {
+ public void hurtAndBreak(int damage, ServerLevel level, @Nullable LivingEntity player, Consumer<Item> onBreak) { // Paper - Add EntityDamageItemEvent
+ // Paper start - add force boolean overload
+ this.hurtAndBreak(damage, level, player, onBreak, false);
+ }
+
+ public void hurtAndBreak(int damage, ServerLevel level, @Nullable LivingEntity player, Consumer<Item> onBreak, boolean force) { // Paper - Add EntityDamageItemEvent
+ // Paper end
+ final int originalDamage = damage; // Paper - Expand PlayerItemDamageEvent
+ int i = this.processDurabilityChange(damage, level, player, force); // Paper
+ // CraftBukkit start
+ if (i > 0 && player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent - limit to positive damage and run for player
+ org.bukkit.event.player.PlayerItemDamageEvent event = new org.bukkit.event.player.PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this), i, originalDamage); // Paper - Add EntityDamageItemEvent
+ event.getPlayer().getServer().getPluginManager().callEvent(event);
+
+ if (i != event.getDamage() || event.isCancelled()) {
+ serverPlayer.containerMenu.sendAllDataToRemote();
+ }
+ if (event.isCancelled()) {
+ return;
+ }
+
+ i = event.getDamage();
+ // Paper start - Add EntityDamageItemEvent
+ } else if (i > 0 && player != null) {
+ io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this), i);
+ if (!event.callEvent()) {
+ return;
+ }
+ i = event.getDamage();
+ // Paper end - Add EntityDamageItemEvent
+ }
+ // CraftBukkit end
+ if (i != 0) { // Paper - Add EntityDamageItemEvent - diff on change for above event ifs.
this.applyDamage(this.getDamageValue() + i, player, onBreak);
}
}
- private int processDurabilityChange(int damage, ServerLevel level, @Nullable ServerPlayer player) {
+ private int processDurabilityChange(int damage, ServerLevel level, @Nullable LivingEntity player) { // Paper - Add EntityDamageItemEvent
+ // Paper start - itemstack damage api
+ return processDurabilityChange(damage, level, player, false);
+ }
+ private int processDurabilityChange(int damage, ServerLevel level, @Nullable LivingEntity player, boolean force) {
+ // Paper end - itemstack damage api
if (!this.isDamageableItem()) {
return 0;
- } else if (player != null && player.hasInfiniteMaterials()) {
+ } else if (player instanceof ServerPlayer && player.hasInfiniteMaterials() && !force) { // Paper - Add EntityDamageItemEvent
return 0;
} else {
return damage > 0 ? EnchantmentHelper.processDurabilityChange(level, this, damage) : damage;
}
}
- private void applyDamage(int damage, @Nullable ServerPlayer player, Consumer<Item> onBreak) {
- if (player != null) {
- CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, damage);
+ private void applyDamage(int damage, @Nullable LivingEntity player, Consumer<Item> onBreak) { // Paper - Add EntityDamageItemEvent
+ if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
+ CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, damage); // Paper - Add EntityDamageItemEvent
}
this.setDamageValue(damage);
if (this.isBroken()) {
Item item = this.getItem();
+ // CraftBukkit start - Check for item breaking
+ if (this.count == 1 && player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
+ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent
+ }
+ // CraftBukkit end
this.shrink(1);
onBreak.accept(item);
}
@@ -510,15 +_,39 @@
return;
}
- int min = Math.min(this.getDamageValue() + i, this.getMaxDamage() - 1);
+ int min = Math.min(this.getDamageValue() + i, this.getMaxDamage() - 1); // Paper - Expand PlayerItemDamageEvent - diff on change as min computation is copied post event.
+
+ // Paper start - Expand PlayerItemDamageEvent
+ if (min - this.getDamageValue() > 0) {
+ org.bukkit.event.player.PlayerItemDamageEvent event = new org.bukkit.event.player.PlayerItemDamageEvent(
+ serverPlayer.getBukkitEntity(),
+ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this),
+ min - this.getDamageValue(),
+ damage
+ );
+ if (!event.callEvent() || event.getDamage() == 0) {
+ return;
+ }
+
+ // Prevent breaking the item in this code path as callers may expect the item to survive
+ // (given the method name)
+ min = Math.min(this.getDamageValue() + event.getDamage(), this.getMaxDamage() - 1);
+ }
+ // Paper end - Expand PlayerItemDamageEvent
+
this.applyDamage(min, serverPlayer, item -> {});
}
}
public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot) {
+ // Paper start - add param to skip infinite mats check
+ this.hurtAndBreak(amount, entity, slot, false);
+ }
+ public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot, boolean force) {
+ // Paper end - add param to skip infinite mats check
if (entity.level() instanceof ServerLevel serverLevel) {
this.hurtAndBreak(
- amount, serverLevel, entity instanceof ServerPlayer serverPlayer ? serverPlayer : null, item -> entity.onEquippedItemBroken(item, slot)
+ amount, serverLevel, entity, item -> {if (slot != null) entity.onEquippedItemBroken(item, slot); }, force // Paper - Add EntityDamageItemEvent & itemstack damage API - do not process entity related callbacks when damaging from API
);
}
}
@@ -732,6 +_,12 @@
return this.getItem().useOnRelease(this);
}
+ // CraftBukkit start
+ public void restorePatch(DataComponentPatch datacomponentpatch) {
+ this.components.restorePatch(datacomponentpatch);
+ }
+ // CraftBukkit end
+
@Nullable
public <T> T set(DataComponentType<T> component, @Nullable T value) {
return this.components.set(component, value);
@@ -779,6 +_,28 @@
this.getItem().verifyComponentsAfterLoad(this);
}
+ // Paper start - (this is just a good no conflict location)
+ public org.bukkit.inventory.ItemStack asBukkitMirror() {
+ return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this);
+ }
+
+ public org.bukkit.inventory.ItemStack asBukkitCopy() {
+ return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this.copy());
+ }
+
+ public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) {
+ return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemstack);
+ }
+
+ private @Nullable org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack;
+ public org.bukkit.inventory.ItemStack getBukkitStack() {
+ if (this.bukkitStack == null || this.bukkitStack.handle != this) {
+ this.bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this);
+ }
+ return this.bukkitStack;
+ }
+ // Paper end
+
public Component getHoverName() {
Component customName = this.getCustomName();
return customName != null ? customName : this.getItemName();
@@ -1054,6 +_,19 @@
EnchantmentHelper.forEachModifier(this, equipmentSLot, action);
}
+ // CraftBukkit start
+ @Deprecated
+ public void setItem(Item item) {
+ this.bukkitStack = null; // Paper
+ this.item = item;
+ // Paper start - change base component prototype
+ final DataComponentPatch patch = this.getComponentsPatch();
+ this.components = new PatchedDataComponentMap(this.item.components());
+ this.applyComponents(patch);
+ // Paper end - change base component prototype
+ }
+ // CraftBukkit end
+
public Component getDisplayName() {
MutableComponent mutableComponent = Component.empty().append(this.getHoverName());
if (this.has(DataComponents.CUSTOM_NAME)) {
@@ -1109,7 +_,7 @@
}
public void consume(int amount, @Nullable LivingEntity entity) {
- if (entity == null || !entity.hasInfiniteMaterials()) {
+ if ((entity == null || !entity.hasInfiniteMaterials()) && this != ItemStack.EMPTY) { // CraftBukkit
this.shrink(amount);
}
}