diff --git a/patches/api/Add-BlockBreakBlockEvent.patch b/patches/api/Add-BlockBreakBlockEvent.patch index f193732703..856ee9f7c7 100644 --- a/patches/api/Add-BlockBreakBlockEvent.patch +++ b/patches/api/Add-BlockBreakBlockEvent.patch @@ -15,6 +15,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import org.bukkit.block.Block; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; ++import org.bukkit.event.block.BlockExpEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + @@ -25,7 +26,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + *

+ * Currently called for piston's and liquid flows. + */ -+public class BlockBreakBlockEvent extends BlockEvent { ++public class BlockBreakBlockEvent extends BlockExpEvent { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + @@ -33,7 +34,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private final Block source; + + public BlockBreakBlockEvent(@NotNull Block block, @NotNull Block source, @NotNull List drops) { -+ super(block); ++ super(block, 0); + this.source = source; + this.drops = drops; + } diff --git a/patches/api/BlockDestroyEvent.patch b/patches/api/BlockDestroyEvent.patch index c06e0c0d26..d7a0d969e6 100644 --- a/patches/api/BlockDestroyEvent.patch +++ b/patches/api/BlockDestroyEvent.patch @@ -23,6 +23,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; ++import org.bukkit.event.block.BlockExpEvent; +import org.jetbrains.annotations.NotNull; + +/** @@ -36,23 +37,44 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * Events such as leaves decaying, pistons retracting (where the block is moving), does NOT fire this event. + * + */ -+public class BlockDestroyEvent extends BlockEvent implements Cancellable { ++public class BlockDestroyEvent extends BlockExpEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + @NotNull private final BlockData newState; + private boolean willDrop; + private boolean playEffect = true; ++ private BlockData effectBlock; + + private boolean cancelled = false; + + public BlockDestroyEvent(@NotNull Block block, @NotNull BlockData newState, boolean willDrop) { -+ super(block); ++ super(block, 0); + this.newState = newState; + this.willDrop = willDrop; + } + + /** ++ * Get the effect that will be played when the block is broken. ++ * @return block break effect ++ */ ++ @NotNull ++ public BlockData getEffectBlock() { ++ return this.effectBlock; ++ } ++ ++ /** ++ * Sets the effect that will be played when the block is broken. ++ * Note: {@link BlockDestroyEvent#playEffect()} must be true in order for this effect to be ++ * played. ++ * ++ * @param effectBlock block effect ++ */ ++ public void setEffectBlock(@NotNull BlockData effectBlock) { ++ this.effectBlock = effectBlock; ++ } ++ ++ /** + * @return The new state of this block (Air, or a Fluid type) + */ + @NotNull diff --git a/patches/server/BlockDestroyEvent.patch b/patches/server/BlockDestroyEvent.patch index fdb169699e..ab23f87b8f 100644 --- a/patches/server/BlockDestroyEvent.patch +++ b/patches/server/BlockDestroyEvent.patch @@ -30,18 +30,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // they are NOT used with same intent and the above should not fire this event. The above method is more of a BlockSetToAirEvent, + // it doesn't imply destruction of a block that plays a sound effect / drops an item. + boolean playEffect = true; ++ BlockState effectType = iblockdata; ++ int xp = iblockdata.getBlock().getExpDrop(iblockdata, (ServerLevel) this, pos, ItemStack.EMPTY, true); + if (com.destroystokyo.paper.event.block.BlockDestroyEvent.getHandlerList().getRegisteredListeners().length > 0) { + com.destroystokyo.paper.event.block.BlockDestroyEvent event = new com.destroystokyo.paper.event.block.BlockDestroyEvent(MCUtil.toBukkitBlock(this, pos), fluid.createLegacyBlock().createCraftBlockData(), drop); + if (!event.callEvent()) { + return false; + } ++ effectType = ((CraftBlockData) event.getEffectBlock()).getState(); + playEffect = event.playEffect(); + drop = event.willDrop(); ++ xp = event.getExpToDrop(); + } + // Paper end - if (!(iblockdata.getBlock() instanceof BaseFireBlock)) { -+ if (playEffect && !(iblockdata.getBlock() instanceof BaseFireBlock)) { // Paper - this.levelEvent(2001, pos, Block.getId(iblockdata)); +- this.levelEvent(2001, pos, Block.getId(iblockdata)); ++ if (playEffect && !(effectType.getBlock() instanceof BaseFireBlock)) { // Paper ++ this.levelEvent(2001, pos, Block.getId(effectType)); // Paper } + if (drop) { diff --git a/patches/server/ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch b/patches/server/ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch index 592b669825..768454e4ff 100644 --- a/patches/server/ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch +++ b/patches/server/ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch @@ -299,14 +299,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } public void popExperience(ServerLevel world, BlockPos pos, int size) { -+ // Paper start - add player parameter ++ // Paper start - add entity parameter + popExperience(world, pos, size, null); + } -+ public void popExperience(ServerLevel world, BlockPos pos, int size, net.minecraft.server.level.ServerPlayer player) { -+ // Paper end - add player parameter ++ public void popExperience(ServerLevel world, BlockPos pos, int size, net.minecraft.world.entity.Entity entity) { ++ // Paper end - add entity parameter if (world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) { - ExperienceOrb.award(world, Vec3.atCenterOf(pos), size); -+ ExperienceOrb.award(world, Vec3.atCenterOf(pos), size, org.bukkit.entity.ExperienceOrb.SpawnReason.BLOCK_BREAK, player); // Paper ++ ExperienceOrb.award(world, Vec3.atCenterOf(pos), size, org.bukkit.entity.ExperienceOrb.SpawnReason.BLOCK_BREAK, entity); // Paper } } diff --git a/patches/server/Properly-handle-BlockBreakEvent-isDropItems.patch b/patches/server/Properly-handle-BlockBreakEvent-isDropItems.patch index 514c7046e3..854836d285 100644 --- a/patches/server/Properly-handle-BlockBreakEvent-isDropItems.patch +++ b/patches/server/Properly-handle-BlockBreakEvent-isDropItems.patch @@ -19,7 +19,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items - block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1); + if (flag && flag1 && event.isDropItems()/* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion -+ block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1, event.isDropItems()); // Paper ++ block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1, event.isDropItems(), false); // Paper } // return true; // CraftBukkit @@ -33,8 +33,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @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 ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper ++ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper if (!world.isClientSide && blockEntity instanceof BeehiveBlockEntity) { BeehiveBlockEntity tileentitybeehive = (BeehiveBlockEntity) blockEntity; @@ -49,9 +49,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + @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); ++ this.playerDestroy(world, player, pos, state, blockEntity, tool, true, true); + } -+ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops) { ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { + // Paper end player.awardStat(Stats.BLOCK_MINED.get(this)); player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent @@ -71,8 +71,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @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 ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper ++ super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool, includeDrops, dropExp); // Paper } protected static void preventDropFromBottomPart(Level world, BlockPos pos, BlockState state, Player player) { @@ -86,8 +86,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @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 ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper ++ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper // Paper start this.afterDestroy(world, pos, tool); } @@ -101,8 +101,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @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 ++ public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper ++ super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper this.decreaseEggs(world, pos, state); } diff --git a/patches/server/Properly-handle-experience-dropping-on-block-break.patch b/patches/server/Properly-handle-experience-dropping-on-block-break.patch new file mode 100644 index 0000000000..a4bea4b784 --- /dev/null +++ b/patches/server/Properly-handle-experience-dropping-on-block-break.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 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 + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + if (drop) { + 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); // Don't drop xp ++ iblockdata.getBlock().popExperience((ServerLevel) this, pos, xp, breakingEntity); // Paper - handle drop experience logic, custom amount + } + + boolean flag1 = this.setBlock(pos, fluid.createLegacyBlock(), 3, maxUpdateDepth); +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 { + for (net.minecraft.world.item.ItemStack drop : net.minecraft.world.level.block.Block.getDrops(state, world.getMinecraftWorld(), pos, blockEntity)) { + items.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(drop)); + } ++ Block block = state.getBlock(); + io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.block.CraftBlock.at(world, source), items); ++ event.setExpToDrop(block.getExpDrop(state, (ServerLevel) world, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); + event.callEvent(); + for (var drop : event.getDrops()) { + popResource(world.getMinecraftWorld(), pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); + } +- state.spawnAfterBreak(world.getMinecraftWorld(), pos, ItemStack.EMPTY, true); ++ state.spawnAfterBreak(world.getMinecraftWorld(), pos, ItemStack.EMPTY, false); ++ block.popExperience((ServerLevel) world, pos, event.getExpToDrop()); + } + return true; + } + // Paper end + + public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) { ++ // Paper start ++ 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 + if (world instanceof ServerLevel) { + Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { + Block.popResource(world, pos, itemstack1); + }); +- state.spawnAfterBreak((ServerLevel) world, pos, tool, true); ++ state.spawnAfterBreak((ServerLevel) world, pos, tool, dropExperience); // Paper + } + + } +@@ -0,0 +0,0 @@ public class Block extends BlockBehaviour implements ItemLike { + 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); ++ Block.dropResources(state, world, pos, blockEntity, player, tool, dropExp); // Paper + } // Paper + } + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -0,0 +0,0 @@ public abstract class BlockBehaviour implements FeatureElement { + + 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 - spawn experience + } + + public List getDrops(LootParams.Builder builder) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -0,0 +0,0 @@ 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 + // Paper start - improve Block#breanNaturally + if (triggerEffect) { + if (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) {