From 21837e419bd4fde103a2c3effe7a81a37c4fb5a5 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 30 Dec 2023 15:00:06 -0500
Subject: [PATCH] Properly handle experience dropping on block break

This causes spawnAfterBreak to spawn xp by default, removing the need to manually add xp wherever this method is used.
For classes that use custom xp amounts, they can drop the resources with disabling
---
 .../minecraft/world/level/Level.java.patch    | 36 +++++++++-------
 .../world/level/block/Block.java.patch        | 41 ++++++++++++++-----
 .../block/state/BlockBehaviour.java.patch     | 10 ++++-
 .../bukkit/craftbukkit/block/CraftBlock.java  |  2 +-
 4 files changed, 62 insertions(+), 27 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
index 62c9a13d52..0a4453e024 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
@@ -303,7 +303,7 @@
  
 +    // Paper start - if loaded
      @Nullable
-+    @Override
+     @Override
 +    public final ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
 +        return ((ServerLevel)this).chunkSource.getChunkAtIfLoadedImmediately(x, z);
 +    }
@@ -356,7 +356,7 @@
 +        return getWorldBorder().isWithinBounds(blockposition) ? getBlockStateIfLoaded(blockposition) : null;
 +    }
 +
-     @Override
++    @Override
      public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
 +        // Paper end
          ChunkAccess ichunkaccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create);
@@ -523,7 +523,7 @@
      public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) {}
  
      @Override
-@@ -270,9 +591,26 @@
+@@ -270,15 +591,33 @@
              return false;
          } else {
              FluidState fluid = this.getFluidState(pos);
@@ -552,7 +552,15 @@
              }
  
              if (drop) {
-@@ -340,10 +678,18 @@
+                 BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null;
+ 
+-                Block.dropResources(iblockdata, this, pos, tileentity, breakingEntity, ItemStack.EMPTY);
++                Block.dropResources(iblockdata, this, pos, tileentity, breakingEntity, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping
++                iblockdata.getBlock().popExperience((ServerLevel) this, pos, xp, breakingEntity); // Paper - Properly handle xp dropping; custom amount
+             }
+ 
+             boolean flag1 = this.setBlock(pos, fluid.createLegacyBlock(), 3, maxUpdateDepth);
+@@ -340,10 +679,18 @@
  
      @Override
      public BlockState getBlockState(BlockPos pos) {
@@ -572,7 +580,7 @@
  
              return chunk.getBlockState(pos);
          }
-@@ -446,34 +792,53 @@
+@@ -446,34 +793,53 @@
              this.pendingBlockEntityTickers.clear();
          }
  
@@ -624,26 +632,26 @@
 +            entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
 +            // Paper end - Prevent block entity and entity crashes
          }
-+    }
+     }
 +    // Paper start - Option to prevent armor stands from doing entity lookups
 +    @Override
 +    public boolean noCollision(@Nullable Entity entity, AABB box) {
 +        if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false;
 +        return LevelAccessor.super.noCollision(entity, box);
-     }
++    }
 +    // Paper end - Option to prevent armor stands from doing entity lookups
  
      public boolean shouldTickDeath(Entity entity) {
          return true;
-@@ -510,13 +875,32 @@
+@@ -510,13 +876,32 @@
      @Nullable
      @Override
      public BlockEntity getBlockEntity(BlockPos pos) {
 -        return this.isOutsideBuildHeight(pos) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(pos).getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE));
 +        // CraftBukkit start
 +        return this.getBlockEntity(pos, true);
-+    }
-+
+     }
+ 
 +    @Nullable
 +    public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) {
 +        // Paper start - Perf: Optimize capturedTileEntities lookup
@@ -654,8 +662,8 @@
 +        // Paper end - Perf: Optimize capturedTileEntities lookup
 +        // CraftBukkit end
 +        return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE));
-     }
- 
++    }
++
      public void setBlockEntity(BlockEntity blockEntity) {
          BlockPos blockposition = blockEntity.getBlockPos();
  
@@ -669,7 +677,7 @@
              this.getChunkAt(blockposition).addAndRegisterBlockEntity(blockEntity);
          }
      }
-@@ -643,7 +1027,7 @@
+@@ -643,7 +1028,7 @@
  
                  for (int k = 0; k < j; ++k) {
                      EnderDragonPart entitycomplexpart = aentitycomplexpart[k];
@@ -678,7 +686,7 @@
  
                      if (t0 != null && predicate.test(t0)) {
                          result.add(t0);
-@@ -912,7 +1296,7 @@
+@@ -912,7 +1297,7 @@
  
      public static enum ExplosionInteraction implements StringRepresentable {
  
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
index 18672557ed..8b81b9c548 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
@@ -1,8 +1,11 @@
 --- a/net/minecraft/world/level/block/Block.java
 +++ b/net/minecraft/world/level/block/Block.java
-@@ -295,6 +295,24 @@
- 
-     }
+@@ -292,15 +292,41 @@
+             });
+             state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true);
+         }
++
++    }
  
 +    // Paper start - Add BlockBreakBlockEvent
 +    public static boolean dropResources(BlockState state, LevelAccessor levelAccessor, BlockPos pos, @Nullable BlockEntity blockEntity, BlockPos source) {
@@ -11,21 +14,36 @@
 +            for (ItemStack drop : Block.getDrops(state, serverLevel, pos, blockEntity)) {
 +                items.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(drop));
 +            }
++            Block block = state.getBlock(); // Paper - Properly handle xp dropping
 +            io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, pos), org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, source), items);
++            event.setExpToDrop(block.getExpDrop(state, serverLevel, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); // Paper - Properly handle xp dropping
 +            event.callEvent();
 +            for (org.bukkit.inventory.ItemStack drop : event.getDrops()) {
 +                popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop));
 +            }
-+            state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, true);
++            state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping
++            block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping
 +        }
 +        return true;
-+    }
+     }
 +    // Paper end - Add BlockBreakBlockEvent
-+
+ 
      public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) {
++    // Paper start - Properly handle xp dropping
++        dropResources(state, world, pos, blockEntity, entity, tool, true);
++    }
++    public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool, boolean dropExperience) {
++    // Paper end - Properly handle xp dropping
          if (world instanceof ServerLevel) {
              Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> {
-@@ -340,7 +358,13 @@
+                 Block.popResource(world, pos, itemstack1);
+             });
+-            state.spawnAfterBreak((ServerLevel) world, pos, tool, true);
++            state.spawnAfterBreak((ServerLevel) world, pos, tool, dropExperience); // Paper - Properly handle xp dropping
+         }
+ 
+     }
+@@ -340,7 +366,13 @@
                  ItemEntity entityitem = (ItemEntity) itemEntitySupplier.get();
  
                  entityitem.setDefaultPickUpDelay();
@@ -40,7 +58,7 @@
                  return;
              }
          }
-@@ -348,8 +372,13 @@
+@@ -348,8 +380,13 @@
      }
  
      public void popExperience(ServerLevel world, BlockPos pos, int size) {
@@ -55,7 +73,7 @@
          }
  
      }
-@@ -367,10 +396,18 @@
+@@ -367,10 +404,18 @@
          return this.defaultBlockState();
      }
  
@@ -68,14 +86,15 @@
 +        // Paper end - fix drops not preventing stats/food exhaustion
          player.awardStat(Stats.BLOCK_MINED.get(this));
 -        player.causeFoodExhaustion(0.005F);
+-        Block.dropResources(state, world, pos, blockEntity, player, tool);
 +        player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent
 +        if (includeDrops) { // Paper - fix drops not preventing stats/food exhaustion
-         Block.dropResources(state, world, pos, blockEntity, player, tool);
++        Block.dropResources(state, world, pos, blockEntity, player, tool, dropExp); // Paper - Properly handle xp dropping
 +        } // Paper - fix drops not preventing stats/food exhaustion
      }
  
      public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {}
-@@ -490,15 +527,35 @@
+@@ -490,15 +535,35 @@
          return this.builtInRegistryHolder;
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
index 0715e14829..f250348594 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
@@ -132,7 +132,15 @@
          public void onRemove(Level world, BlockPos pos, BlockState state, boolean moved) {
              this.getBlock().onRemove(this.asState(), world, pos, state, moved);
          }
-@@ -1250,11 +1280,11 @@
+@@ -1154,6 +1184,7 @@
+ 
+         public void spawnAfterBreak(ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {
+             this.getBlock().spawnAfterBreak(this.asState(), world, pos, tool, dropExperience);
++            if (dropExperience) {getBlock().popExperience(world, pos, this.getBlock().getExpDrop(asState(), world, pos, tool, true));} // Paper - Properly handle xp dropping
+         }
+ 
+         public List<ItemStack> getDrops(LootParams.Builder builder) {
+@@ -1250,11 +1281,11 @@
              return this.getBlock().builtInRegistryHolder().is(key);
          }
  
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 6dceb60825..5cb69d0b82 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
@@ -509,7 +509,7 @@ public class CraftBlock implements Block {
 
         // Modelled off EntityHuman#hasBlock
         if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) {
-            net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem);
+            net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); // Paper - Properly handle xp dropping
             // Paper start - improve Block#breanNaturally
             if (triggerEffect) {
                 if (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) {