diff --git a/paper-api/src/main/java/org/bukkit/block/Block.java b/paper-api/src/main/java/org/bukkit/block/Block.java index 32f5d2631b..318bb02fe2 100644 --- a/paper-api/src/main/java/org/bukkit/block/Block.java +++ b/paper-api/src/main/java/org/bukkit/block/Block.java @@ -591,6 +591,18 @@ public interface Block extends Metadatable, Translatable, net.kyori.adventure.tr */ boolean breakNaturally(@NotNull ItemStack tool, boolean triggerEffect, boolean dropExperience); + /** + * Breaks the block and spawns item drops as if a player had broken it + * with a specific tool + * + * @param tool The tool or item in hand used for digging + * @param triggerEffect Play the block break particle effect and sound + * @param dropExperience drop exp if the block normally does so + * @param forceEffect Forces the break effect to be triggered even if the tool is not the correct tool for the block + * @return true if the block was destroyed + */ + boolean breakNaturally(@NotNull ItemStack tool, boolean triggerEffect, boolean dropExperience, boolean forceEffect); + /** * Causes the block to be ticked, this is different from {@link Block#randomTick()}, * in that it is usually scheduled to occur, for example diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java index a4d5c65edc..6809f9dcf8 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -499,6 +499,11 @@ public class CraftBlock implements Block { @Override public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience) { + return this.breakNaturally(item, triggerEffect, dropExperience, false); + } + + @Override + public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience, boolean forceEffect) { // Paper end // Order matters here, need to drop before setting to air so skulls can get their data net.minecraft.world.level.block.state.BlockState state = this.getNMS(); @@ -510,18 +515,19 @@ public class CraftBlock implements Block { if (block != Blocks.AIR && (item == null || !state.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(state))) { net.minecraft.world.level.block.Block.dropResources(state, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); // Paper - Properly handle xp dropping // Paper start - improve Block#breakNaturally - if (triggerEffect) { - if (state.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) { - this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE, this.position, 0); - } else { - this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK, this.position, net.minecraft.world.level.block.Block.getId(state)); - } - } if (dropExperience) block.popExperience(this.world.getMinecraftWorld(), this.position, block.getExpDrop(state, this.world.getMinecraftWorld(), this.position, nmsItem, true)); // Paper end result = true; } + if ((result && triggerEffect) || (forceEffect && block != Blocks.AIR)) { + if (state.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) { + this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE, this.position, 0); + } else { + this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK, this.position, net.minecraft.world.level.block.Block.getId(state)); + } + } + // SPIGOT-6778: Directly call setBlock instead of setBlockState, so that the block entity is not removed and custom remove logic is run. // Paper start - improve breakNaturally boolean destroyed = this.world.removeBlock(this.position, false);