--- a/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
@@ -4,6 +4,7 @@
 import javax.annotation.Nullable;
 import net.minecraft.core.BlockPos;
 import net.minecraft.core.Direction;
+import net.minecraft.core.Holder;
 import net.minecraft.core.HolderLookup;
 import net.minecraft.core.NonNullList;
 import net.minecraft.core.component.DataComponentMap;
@@ -30,91 +31,133 @@
 import net.minecraft.world.level.block.state.BlockState;
 import net.minecraft.world.level.gameevent.GameEvent;
 
+// CraftBukkit start
+import org.bukkit.craftbukkit.block.CraftBlock;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.event.block.BlockCookEvent;
+import org.bukkit.event.block.CampfireStartEvent;
+import org.bukkit.inventory.CampfireRecipe;
+// CraftBukkit end
+
 public class CampfireBlockEntity extends BlockEntity implements Clearable {
+
     private static final int BURN_COOL_SPEED = 2;
     private static final int NUM_SLOTS = 4;
-    private final NonNullList<ItemStack> items = NonNullList.withSize(4, ItemStack.EMPTY);
-    public final int[] cookingProgress = new int[4];
-    public final int[] cookingTime = new int[4];
-    private final RecipeManager.CachedCheck<Container, CampfireCookingRecipe> quickCheck = RecipeManager.createCheck(RecipeType.CAMPFIRE_COOKING);
+    private final NonNullList<ItemStack> items;
+    public final int[] cookingProgress;
+    public final int[] cookingTime;
+    private final RecipeManager.CachedCheck<Container, CampfireCookingRecipe> quickCheck;
+    public final boolean[] stopCooking; // Paper - Add more Campfire API
 
     public CampfireBlockEntity(BlockPos pos, BlockState state) {
         super(BlockEntityType.CAMPFIRE, pos, state);
+        this.items = NonNullList.withSize(4, ItemStack.EMPTY);
+        this.cookingProgress = new int[4];
+        this.cookingTime = new int[4];
+        this.quickCheck = RecipeManager.createCheck(RecipeType.CAMPFIRE_COOKING);
+        this.stopCooking = new boolean[4]; // Paper - Add more Campfire API
     }
 
     public static void cookTick(Level world, BlockPos pos, BlockState state, CampfireBlockEntity campfire) {
-        boolean bl = false;
+        boolean flag = false;
+
+        for (int i = 0; i < campfire.items.size(); ++i) {
+            ItemStack itemstack = (ItemStack) campfire.items.get(i);
+
+            if (!itemstack.isEmpty()) {
+                flag = true;
+                if (!campfire.stopCooking[i]) { // Paper - Add more Campfire API
+                int j = campfire.cookingProgress[i]++;
+                } // Paper - Add more Campfire API
 
-        for (int i = 0; i < campfire.items.size(); i++) {
-            ItemStack itemStack = campfire.items.get(i);
-            if (!itemStack.isEmpty()) {
-                bl = true;
-                campfire.cookingProgress[i]++;
                 if (campfire.cookingProgress[i] >= campfire.cookingTime[i]) {
-                    Container container = new SimpleContainer(itemStack);
-                    ItemStack itemStack2 = campfire.quickCheck
-                        .getRecipeFor(container, world)
-                        .map(recipe -> recipe.value().assemble(container, world.registryAccess()))
-                        .orElse(itemStack);
-                    if (itemStack2.isItemEnabled(world.enabledFeatures())) {
-                        Containers.dropItemStack(world, (double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), itemStack2);
+                    SimpleContainer inventorysubcontainer = new SimpleContainer(new ItemStack[]{itemstack});
+                    // Paper start - Add recipe to cook events; re-use recipeHolderOptional below
+                    Optional<RecipeHolder<CampfireCookingRecipe>> recipeHolderOptional = campfire.quickCheck.getRecipeFor(inventorysubcontainer, world);
+                    ItemStack itemstack1 = (ItemStack) recipeHolderOptional.map((recipeholder) -> {
+                        // Paper end - Add recipe to cook events
+                        return ((CampfireCookingRecipe) recipeholder.value()).assemble(inventorysubcontainer, world.registryAccess());
+                    }).orElse(itemstack);
+
+                    if (itemstack1.isItemEnabled(world.enabledFeatures())) {
+                        // CraftBukkit start - fire BlockCookEvent
+                        CraftItemStack source = CraftItemStack.asCraftMirror(itemstack);
+                        org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1);
+
+                        BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result, (org.bukkit.inventory.CookingRecipe<?>) recipeHolderOptional.map(RecipeHolder::toBukkitRecipe).orElse(null)); // Paper - Add recipe to cook events
+                        world.getCraftServer().getPluginManager().callEvent(blockCookEvent);
+
+                        if (blockCookEvent.isCancelled()) {
+                            return;
+                        }
+
+                        result = blockCookEvent.getResult();
+                        itemstack1 = CraftItemStack.asNMSCopy(result);
+                        // CraftBukkit end
+                        // Paper start - Fix item locations dropped from campfires
+                        double deviation = 0.05F * RandomSource.GAUSSIAN_SPREAD_FACTOR;
+                        while (!itemstack1.isEmpty()) {
+                            net.minecraft.world.entity.item.ItemEntity droppedItem = new net.minecraft.world.entity.item.ItemEntity(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, itemstack1.split(world.random.nextInt(21) + 10));
+                            droppedItem.setDeltaMovement(world.random.triangle(0.0D, deviation), world.random.triangle(0.2D, deviation), world.random.triangle(0.0D, deviation));
+                            world.addFreshEntity(droppedItem);
+                        }
+                        // Paper end - Fix item locations dropped from campfires
                         campfire.items.set(i, ItemStack.EMPTY);
                         world.sendBlockUpdated(pos, state, state, 3);
-                        world.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state));
+                        world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state));
                     }
                 }
             }
         }
 
-        if (bl) {
+        if (flag) {
             setChanged(world, pos, state);
         }
+
     }
 
     public static void cooldownTick(Level world, BlockPos pos, BlockState state, CampfireBlockEntity campfire) {
-        boolean bl = false;
+        boolean flag = false;
 
-        for (int i = 0; i < campfire.items.size(); i++) {
+        for (int i = 0; i < campfire.items.size(); ++i) {
             if (campfire.cookingProgress[i] > 0) {
-                bl = true;
+                flag = true;
                 campfire.cookingProgress[i] = Mth.clamp(campfire.cookingProgress[i] - 2, 0, campfire.cookingTime[i]);
             }
         }
 
-        if (bl) {
+        if (flag) {
             setChanged(world, pos, state);
         }
+
     }
 
     public static void particleTick(Level world, BlockPos pos, BlockState state, CampfireBlockEntity campfire) {
-        RandomSource randomSource = world.random;
-        if (randomSource.nextFloat() < 0.11F) {
-            for (int i = 0; i < randomSource.nextInt(2) + 2; i++) {
-                CampfireBlock.makeParticles(world, pos, state.getValue(CampfireBlock.SIGNAL_FIRE), false);
+        RandomSource randomsource = world.random;
+        int i;
+
+        if (randomsource.nextFloat() < 0.11F) {
+            for (i = 0; i < randomsource.nextInt(2) + 2; ++i) {
+                CampfireBlock.makeParticles(world, pos, (Boolean) state.getValue(CampfireBlock.SIGNAL_FIRE), false);
             }
         }
 
-        int j = state.getValue(CampfireBlock.FACING).get2DDataValue();
+        i = ((Direction) state.getValue(CampfireBlock.FACING)).get2DDataValue();
 
-        for (int k = 0; k < campfire.items.size(); k++) {
-            if (!campfire.items.get(k).isEmpty() && randomSource.nextFloat() < 0.2F) {
-                Direction direction = Direction.from2DDataValue(Math.floorMod(k + j, 4));
+        for (int j = 0; j < campfire.items.size(); ++j) {
+            if (!((ItemStack) campfire.items.get(j)).isEmpty() && randomsource.nextFloat() < 0.2F) {
+                Direction enumdirection = Direction.from2DDataValue(Math.floorMod(j + i, 4));
                 float f = 0.3125F;
-                double d = (double)pos.getX()
-                    + 0.5
-                    - (double)((float)direction.getStepX() * 0.3125F)
-                    + (double)((float)direction.getClockWise().getStepX() * 0.3125F);
-                double e = (double)pos.getY() + 0.5;
-                double g = (double)pos.getZ()
-                    + 0.5
-                    - (double)((float)direction.getStepZ() * 0.3125F)
-                    + (double)((float)direction.getClockWise().getStepZ() * 0.3125F);
+                double d0 = (double) pos.getX() + 0.5D - (double) ((float) enumdirection.getStepX() * 0.3125F) + (double) ((float) enumdirection.getClockWise().getStepX() * 0.3125F);
+                double d1 = (double) pos.getY() + 0.5D;
+                double d2 = (double) pos.getZ() + 0.5D - (double) ((float) enumdirection.getStepZ() * 0.3125F) + (double) ((float) enumdirection.getClockWise().getStepZ() * 0.3125F);
 
-                for (int l = 0; l < 4; l++) {
-                    world.addParticle(ParticleTypes.SMOKE, d, e, g, 0.0, 5.0E-4, 0.0);
+                for (int k = 0; k < 4; ++k) {
+                    world.addParticle(ParticleTypes.SMOKE, d0, d1, d2, 0.0D, 5.0E-4D, 0.0D);
                 }
             }
         }
+
     }
 
     public NonNullList<ItemStack> getItems() {
@@ -126,15 +169,28 @@
         super.loadAdditional(nbt, registryLookup);
         this.items.clear();
         ContainerHelper.loadAllItems(nbt, this.items, registryLookup);
+        int[] aint;
+
         if (nbt.contains("CookingTimes", 11)) {
-            int[] is = nbt.getIntArray("CookingTimes");
-            System.arraycopy(is, 0, this.cookingProgress, 0, Math.min(this.cookingTime.length, is.length));
+            aint = nbt.getIntArray("CookingTimes");
+            System.arraycopy(aint, 0, this.cookingProgress, 0, Math.min(this.cookingTime.length, aint.length));
         }
 
         if (nbt.contains("CookingTotalTimes", 11)) {
-            int[] js = nbt.getIntArray("CookingTotalTimes");
-            System.arraycopy(js, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, js.length));
+            aint = nbt.getIntArray("CookingTotalTimes");
+            System.arraycopy(aint, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, aint.length));
         }
+
+        // Paper start - Add more Campfire API
+        if (nbt.contains("Paper.StopCooking", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_BYTE_ARRAY)) {
+            byte[] abyte = nbt.getByteArray("Paper.StopCooking");
+            boolean[] cookingState = new boolean[4];
+            for (int index = 0; index < abyte.length; index++) {
+                cookingState[index] = abyte[index] == 1;
+            }
+            System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, abyte.length));
+        }
+        // Paper end - Add more Campfire API
     }
 
     @Override
@@ -143,6 +199,13 @@
         ContainerHelper.saveAllItems(nbt, this.items, true, registryLookup);
         nbt.putIntArray("CookingTimes", this.cookingProgress);
         nbt.putIntArray("CookingTotalTimes", this.cookingTime);
+        // Paper start - Add more Campfire API
+        byte[] cookingState = new byte[4];
+        for (int index = 0; index < cookingState.length; index++) {
+            cookingState[index] = (byte) (this.stopCooking[index] ? 1 : 0);
+        }
+        nbt.putByteArray("Paper.StopCooking", cookingState);
+        // Paper end - Add more Campfire API
     }
 
     @Override
@@ -152,23 +215,29 @@
 
     @Override
     public CompoundTag getUpdateTag(HolderLookup.Provider registryLookup) {
-        CompoundTag compoundTag = new CompoundTag();
-        ContainerHelper.saveAllItems(compoundTag, this.items, true, registryLookup);
-        return compoundTag;
+        CompoundTag nbttagcompound = new CompoundTag();
+
+        ContainerHelper.saveAllItems(nbttagcompound, this.items, true, registryLookup);
+        return nbttagcompound;
     }
 
     public Optional<RecipeHolder<CampfireCookingRecipe>> getCookableRecipe(ItemStack stack) {
-        return this.items.stream().noneMatch(ItemStack::isEmpty) ? Optional.empty() : this.quickCheck.getRecipeFor(new SimpleContainer(stack), this.level);
+        return this.items.stream().noneMatch(ItemStack::isEmpty) ? Optional.empty() : this.quickCheck.getRecipeFor(new SimpleContainer(new ItemStack[]{stack}), this.level);
     }
 
     public boolean placeFood(@Nullable Entity user, ItemStack stack, int cookTime) {
-        for (int i = 0; i < this.items.size(); i++) {
-            ItemStack itemStack = this.items.get(i);
-            if (itemStack.isEmpty()) {
-                this.cookingTime[i] = cookTime;
-                this.cookingProgress[i] = 0;
-                this.items.set(i, stack.split(1));
-                this.level.gameEvent(GameEvent.BLOCK_CHANGE, this.getBlockPos(), GameEvent.Context.of(user, this.getBlockState()));
+        for (int j = 0; j < this.items.size(); ++j) {
+            ItemStack itemstack1 = (ItemStack) this.items.get(j);
+
+            if (itemstack1.isEmpty()) {
+                // CraftBukkit start
+                CampfireStartEvent event = new CampfireStartEvent(CraftBlock.at(this.level,this.worldPosition), CraftItemStack.asCraftMirror(stack), (CampfireRecipe) this.getCookableRecipe(stack).get().toBukkitRecipe());
+                this.level.getCraftServer().getPluginManager().callEvent(event);
+                this.cookingTime[j] = event.getTotalCookTime(); // i -> event.getTotalCookTime()
+                // CraftBukkit end
+                this.cookingProgress[j] = 0;
+                this.items.set(j, stack.split(1));
+                this.level.gameEvent((Holder) GameEvent.BLOCK_CHANGE, this.getBlockPos(), GameEvent.Context.of(user, this.getBlockState()));
                 this.markUpdated();
                 return true;
             }
@@ -191,12 +260,13 @@
         if (this.level != null) {
             this.markUpdated();
         }
+
     }
 
     @Override
     protected void applyImplicitComponents(BlockEntity.DataComponentInput components) {
         super.applyImplicitComponents(components);
-        components.getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY).copyInto(this.getItems());
+        ((ItemContainerContents) components.getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY)).copyInto(this.getItems());
     }
 
     @Override
