From 7e6bdb92a438f3f9ec9b45b10825b43bb6699126 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 20 Aug 2023 20:49:20 -0700
Subject: [PATCH] Properly handle BlockBreakEvent#isDropItems (#8936)

Setting whether a block break dropped items controlled
far more than just whether blocks dropped, like stat increases
food consumption, turtle egg count decreases, ice to water
conversions and beehive releases
---
 ...y-handle-BlockBreakEvent-isDropItems.patch | 165 ++++++++++++++++++
 1 file changed, 165 insertions(+)
 create mode 100644 patches/server/Properly-handle-BlockBreakEvent-isDropItems.patch

diff --git a/patches/server/Properly-handle-BlockBreakEvent-isDropItems.patch b/patches/server/Properly-handle-BlockBreakEvent-isDropItems.patch
new file mode 100644
index 0000000000..bf2516999e
--- /dev/null
+++ b/patches/server/Properly-handle-BlockBreakEvent-isDropItems.patch
@@ -0,0 +1,165 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
+Date: Sat, 4 Mar 2023 10:52:52 -0800
+Subject: [PATCH] Properly handle BlockBreakEvent#isDropItems
+
+Setting whether a block break dropped items controlled
+far more than just whether blocks dropped, like stat increases
+food consumption, turtle egg count decreases, ice to water
+conversions and beehive releases
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
+                     isCorrectTool = flag1; // Paper
+ 
+                     itemstack.mineBlock(this.level, iblockdata, pos, this.player);
+-                    if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items
+-                        block.playerDestroy(this.level, this.player, pos, iblockdata, tileentity, itemstack1);
++                    if (flag && flag1 /* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion
++                        block.playerDestroy(this.level, this.player, pos, iblockdata, tileentity, itemstack1, event.isDropItems()); // Paper
+                     }
+ 
+                     // return true; // CraftBukkit
+diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java
+@@ -0,0 +0,0 @@ public class BeehiveBlock extends BaseEntityBlock {
+     }
+ 
+     @Override
+-    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
+-        super.playerDestroy(world, player, pos, state, blockEntity, tool);
++    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops) { // Paper
++        super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops); // Paper
+         if (!world.isClientSide && blockEntity instanceof BeehiveBlockEntity) {
+             BeehiveBlockEntity tileentitybeehive = (BeehiveBlockEntity) blockEntity;
+ 
+diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/block/Block.java
++++ b/src/main/java/net/minecraft/world/level/block/Block.java
+@@ -0,0 +0,0 @@ public class Block extends BlockBehaviour implements ItemLike {
+         return this.defaultBlockState();
+     }
+ 
++    @io.papermc.paper.annotation.DoNotUse // Paper - method below allows better control of item drops
+     public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
++        // Paper start
++        this.playerDestroy(world, player, pos, state, blockEntity, tool, true);
++    }
++    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops) {
++        // Paper end
+         player.awardStat(Stats.BLOCK_MINED.get(this));
+         player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent
++        if (includeDrops) { // Paper
+         Block.dropResources(state, world, pos, blockEntity, player, tool);
++        } // Paper
+     }
+ 
+     public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {}
+diff --git a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java
+@@ -0,0 +0,0 @@ public class DoublePlantBlock extends BushBlock {
+     }
+ 
+     @Override
+-    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
+-        super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool);
++    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops) { // Paper
++        super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool, includeDrops); // Paper
+     }
+ 
+     protected static void preventCreativeDropFromBottomPart(Level world, BlockPos pos, BlockState state, Player player) {
+diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/block/IceBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java
+@@ -0,0 +0,0 @@ public class IceBlock extends HalfTransparentBlock {
+     }
+ 
+     @Override
+-    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
+-        super.playerDestroy(world, player, pos, state, blockEntity, tool);
++    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops) { // Paper
++        super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops); // Paper
+         // Paper start
+         this.afterDestroy(world, pos, tool);
+     }
+diff --git a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java
+@@ -0,0 +0,0 @@ public class TurtleEggBlock extends Block {
+     }
+ 
+     @Override
+-    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
+-        super.playerDestroy(world, player, pos, state, blockEntity, tool);
++    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops) { // Paper
++        super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops); // Paper
+         this.decreaseEggs(world, pos, state);
+     }
+ 
+diff --git a/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java b/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/world/block/BlockPlayerDestroyOverrideTest.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.world.block;
++
++import io.github.classgraph.ClassGraph;
++import io.github.classgraph.ClassInfo;
++import io.github.classgraph.MethodInfo;
++import io.github.classgraph.MethodInfoList;
++import io.github.classgraph.MethodParameterInfo;
++import io.github.classgraph.ScanResult;
++import java.util.ArrayList;
++import java.util.List;
++import org.bukkit.support.AbstractTestingBase;
++import org.junit.Test;
++import org.junit.runner.RunWith;
++import org.junit.runners.Parameterized;
++
++import static org.junit.Assert.assertEquals;
++
++@RunWith(Parameterized.class)
++public class BlockPlayerDestroyOverrideTest extends AbstractTestingBase {
++
++    @Parameterized.Parameter
++    public ClassInfo overridesPlayerDestroy;
++
++    @Parameterized.Parameters
++    public static Iterable<ClassInfo> parameters() {
++        final List<ClassInfo> classInfo = new ArrayList<>();
++        try (ScanResult scanResult = new ClassGraph()
++            .enableClassInfo()
++            .enableMethodInfo()
++            .whitelistPackages("net.minecraft")
++            .scan()
++        ) {
++            for (final ClassInfo subclass : scanResult.getSubclasses("net.minecraft.world.level.block.Block")) {
++                final MethodInfoList playerDestroy = subclass.getDeclaredMethodInfo("playerDestroy");
++                if (!playerDestroy.isEmpty()) {
++                    classInfo.add(subclass);
++                }
++            }
++        }
++        return classInfo;
++    }
++
++    @Test
++    public void checkPlayerDestroyOverrides() {
++        final MethodInfoList playerDestroy = this.overridesPlayerDestroy.getDeclaredMethodInfo("playerDestroy");
++        assertEquals(this.overridesPlayerDestroy.getName() + " has multiple playerDestroy methods", 1, playerDestroy.size());
++        final MethodInfo next = playerDestroy.iterator().next();
++        final MethodParameterInfo[] parameterInfo = next.getParameterInfo();
++        assertEquals(this.overridesPlayerDestroy.getName() + " needs to change it's override of playerDestroy", "boolean", parameterInfo[parameterInfo.length - 1].getTypeDescriptor().toStringWithSimpleNames());
++    }
++}