From fa1f6a5d787309629c2b66f47db0229b8d170a1f Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 28 Sep 2024 15:04:11 -0700
Subject: [PATCH] Call bucket events for cauldrons (#7486)

---
 .../Call-bucket-events-for-cauldrons.patch    | 190 ++++++++++++++++++
 1 file changed, 190 insertions(+)
 create mode 100644 patches/server/Call-bucket-events-for-cauldrons.patch

diff --git a/patches/server/Call-bucket-events-for-cauldrons.patch b/patches/server/Call-bucket-events-for-cauldrons.patch
new file mode 100644
index 0000000000..f535bb6a96
--- /dev/null
+++ b/patches/server/Call-bucket-events-for-cauldrons.patch
@@ -0,0 +1,190 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
+Date: Sun, 16 Jan 2022 10:13:33 -0800
+Subject: [PATCH] Call bucket events for cauldrons
+
+
+diff --git a/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java b/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java
++++ b/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java
+@@ -0,0 +0,0 @@ public interface CauldronInteraction {
+     static CauldronInteraction.InteractionMap newInteractionMap(String name) {
+         Object2ObjectOpenHashMap<Item, CauldronInteraction> object2objectopenhashmap = new Object2ObjectOpenHashMap();
+ 
+-        object2objectopenhashmap.defaultReturnValue((iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++        object2objectopenhashmap.defaultReturnValue((iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
+             return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
+         });
+         CauldronInteraction.InteractionMap cauldroninteraction_a = new CauldronInteraction.InteractionMap(name, object2objectopenhashmap);
+@@ -0,0 +0,0 @@ public interface CauldronInteraction {
+         return cauldroninteraction_a;
+     }
+ 
+-    ItemInteractionResult interact(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack);
++    ItemInteractionResult interact(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection); // Paper - add hitDirection
+ 
+     static void bootStrap() {
+         Map<Item, CauldronInteraction> map = CauldronInteraction.EMPTY.map();
+ 
+         CauldronInteraction.addDefaultInteractions(map);
+-        map.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++        map.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
+             PotionContents potioncontents = (PotionContents) itemstack.get(DataComponents.POTION_CONTENTS);
+ 
+             if (potioncontents != null && potioncontents.is(Potions.WATER)) {
+@@ -0,0 +0,0 @@ public interface CauldronInteraction {
+         Map<Item, CauldronInteraction> map1 = CauldronInteraction.WATER.map();
+ 
+         CauldronInteraction.addDefaultInteractions(map1);
+-        map1.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++        map1.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
+             return CauldronInteraction.fillBucket(iblockdata, world, blockposition, entityhuman, enumhand, itemstack, new ItemStack(Items.WATER_BUCKET), (iblockdata1) -> {
+                 return (Integer) iblockdata1.getValue(LayeredCauldronBlock.LEVEL) == 3;
+-            }, SoundEvents.BUCKET_FILL);
++            }, SoundEvents.BUCKET_FILL, hitDirection); // Paper - add hitDirection
+         });
+-        map1.put(Items.GLASS_BOTTLE, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++        map1.put(Items.GLASS_BOTTLE, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
+             if (!world.isClientSide) {
+                 // CraftBukkit start
+                 if (!LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition, entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_FILL)) {
+@@ -0,0 +0,0 @@ public interface CauldronInteraction {
+ 
+             return ItemInteractionResult.sidedSuccess(world.isClientSide);
+         });
+-        map1.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++        map1.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
+             if ((Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL) == 3) {
+                 return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
+             } else {
+@@ -0,0 +0,0 @@ public interface CauldronInteraction {
+         map1.put(Items.YELLOW_SHULKER_BOX, CauldronInteraction.SHULKER_BOX);
+         Map<Item, CauldronInteraction> map2 = CauldronInteraction.LAVA.map();
+ 
+-        map2.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++        map2.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
+             return CauldronInteraction.fillBucket(iblockdata, world, blockposition, entityhuman, enumhand, itemstack, new ItemStack(Items.LAVA_BUCKET), (iblockdata1) -> {
+                 return true;
+-            }, SoundEvents.BUCKET_FILL_LAVA);
++            }, SoundEvents.BUCKET_FILL_LAVA, hitDirection); // Paper - add hitDirection
+         });
+         CauldronInteraction.addDefaultInteractions(map2);
+         Map<Item, CauldronInteraction> map3 = CauldronInteraction.POWDER_SNOW.map();
+ 
+-        map3.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++        map3.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
+             return CauldronInteraction.fillBucket(iblockdata, world, blockposition, entityhuman, enumhand, itemstack, new ItemStack(Items.POWDER_SNOW_BUCKET), (iblockdata1) -> {
+                 return (Integer) iblockdata1.getValue(LayeredCauldronBlock.LEVEL) == 3;
+             }, SoundEvents.BUCKET_FILL_POWDER_SNOW);
+@@ -0,0 +0,0 @@ public interface CauldronInteraction {
+     }
+ 
+     static ItemInteractionResult fillBucket(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, ItemStack output, Predicate<BlockState> fullPredicate, SoundEvent soundEvent) {
++        // Paper start - add hitDirection
++        return fillBucket(state, world, pos, player, hand, stack, output, fullPredicate, soundEvent, null); // Paper - add hitDirection
++    }
++    static ItemInteractionResult fillBucket(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, ItemStack output, Predicate<BlockState> fullPredicate, SoundEvent soundEvent, @javax.annotation.Nullable net.minecraft.core.Direction hitDirection) {
++        // Paper end - add hitDirection
+         if (!fullPredicate.test(state)) {
+             return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
+         } else {
+             if (!world.isClientSide) {
++                // Paper start - fire PlayerBucketFillEvent
++                if (hitDirection != null) {
++                    org.bukkit.event.player.PlayerBucketEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketFillEvent((net.minecraft.server.level.ServerLevel) world, player, pos, pos, hitDirection, stack, output.getItem(), hand);
++                    if (event.isCancelled()) {
++                        return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
++                    }
++                    output = event.getItemStack() != null ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY;
++                }
++                // Paper end - fire PlayerBucketFillEvent
+                 // CraftBukkit start
+                 if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL, false)) { // Paper - Call CauldronLevelChangeEvent
+                     return ItemInteractionResult.SUCCESS;
+@@ -0,0 +0,0 @@ public interface CauldronInteraction {
+     }
+ 
+     static ItemInteractionResult emptyBucket(Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, BlockState state, SoundEvent soundEvent) {
++        // Paper start - add hitDirection
++        return emptyBucket(world, pos, player, hand, stack, state, soundEvent, null);
++    }
++    static ItemInteractionResult emptyBucket(Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, BlockState state, SoundEvent soundEvent, @javax.annotation.Nullable net.minecraft.core.Direction hitDirection) {
++        // Paper end - add hitDirection
+         if (!world.isClientSide) {
++            // Paper start - fire PlayerBucketEmptyEvent
++            ItemStack output = new ItemStack(Items.BUCKET);
++            if (hitDirection != null) {
++                org.bukkit.event.player.PlayerBucketEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketEmptyEvent((net.minecraft.server.level.ServerLevel) world, player, pos, pos, hitDirection, stack, hand);
++                if (event.isCancelled()) {
++                    return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
++                }
++                output = event.getItemStack() != null ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY;
++            }
++            // Paper end - fire PlayerBucketEmptyEvent
+             // CraftBukkit start
+             if (!LayeredCauldronBlock.changeLevel(state, world, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
+                 return ItemInteractionResult.SUCCESS;
+@@ -0,0 +0,0 @@ public interface CauldronInteraction {
+             // CraftBukkit end
+             Item item = stack.getItem();
+ 
+-            player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, new ItemStack(Items.BUCKET)));
++            player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, output)); // Paper
+             player.awardStat(Stats.FILL_CAULDRON);
+             player.awardStat(Stats.ITEM_USED.get(item));
+             // world.setBlockAndUpdate(blockposition, iblockdata); // CraftBukkit
+@@ -0,0 +0,0 @@ public interface CauldronInteraction {
+         CauldronInteraction.InteractionMap WATER = CauldronInteraction.newInteractionMap("water");
+         CauldronInteraction.InteractionMap LAVA = CauldronInteraction.newInteractionMap("lava");
+         CauldronInteraction.InteractionMap POWDER_SNOW = CauldronInteraction.newInteractionMap("powder_snow");
+-        CauldronInteraction FILL_WATER = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
+-            return CauldronInteraction.emptyBucket(world, blockposition, entityhuman, enumhand, itemstack, (BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY);
++        CauldronInteraction FILL_WATER = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
++            return CauldronInteraction.emptyBucket(world, blockposition, entityhuman, enumhand, itemstack, (BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY, hitDirection); // Paper - add hitDirection
+         };
+-        CauldronInteraction FILL_LAVA = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
+-            return CauldronInteraction.emptyBucket(world, blockposition, entityhuman, enumhand, itemstack, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA);
++        CauldronInteraction FILL_LAVA = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
++            return CauldronInteraction.emptyBucket(world, blockposition, entityhuman, enumhand, itemstack, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA, hitDirection); // Paper - add hitDirection
+         };
+-        CauldronInteraction FILL_POWDER_SNOW = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++        CauldronInteraction FILL_POWDER_SNOW = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
+             return CauldronInteraction.emptyBucket(world, blockposition, entityhuman, enumhand, itemstack, (BlockState) Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY_POWDER_SNOW);
+         };
+-        CauldronInteraction SHULKER_BOX = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++        CauldronInteraction SHULKER_BOX = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
+             // CraftBukkit end
+             Block block = Block.byItem(itemstack.getItem());
+ 
+@@ -0,0 +0,0 @@ public interface CauldronInteraction {
+                 return ItemInteractionResult.sidedSuccess(world.isClientSide);
+             }
+         };
+-        CauldronInteraction BANNER = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> { // CraftBukkit - decompile error
++        CauldronInteraction BANNER = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // CraftBukkit - decompile error // Paper - add hitDirection
+             BannerPatternLayers bannerpatternlayers = (BannerPatternLayers) itemstack.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY);
+ 
+             if (bannerpatternlayers.layers().isEmpty()) {
+@@ -0,0 +0,0 @@ public interface CauldronInteraction {
+                 return ItemInteractionResult.sidedSuccess(world.isClientSide);
+             }
+         };
+-        CauldronInteraction DYED_ITEM = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> { // CraftBukkit - decompile error
++        CauldronInteraction DYED_ITEM = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // CraftBukkit - decompile error // Paper - add hitDirection
+             if (!itemstack.is(ItemTags.DYEABLE)) {
+                 return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
+             } else if (!itemstack.has(DataComponents.DYED_COLOR)) {
+diff --git a/src/main/java/net/minecraft/world/level/block/AbstractCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/AbstractCauldronBlock.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/block/AbstractCauldronBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/AbstractCauldronBlock.java
+@@ -0,0 +0,0 @@ public abstract class AbstractCauldronBlock extends Block {
+         ItemStack stack, BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit
+     ) {
+         CauldronInteraction cauldronInteraction = this.interactions.map().get(stack.getItem());
+-        return cauldronInteraction.interact(state, world, pos, player, hand, stack);
++        return cauldronInteraction.interact(state, world, pos, player, hand, stack, hit.getDirection()); // Paper - pass hit direction
+     }
+ 
+     @Override