--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
@@ -2,8 +2,10 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectIterator;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import javax.annotation.Nullable;
@@ -25,7 +27,6 @@
 import net.minecraft.world.ContainerHelper;
 import net.minecraft.world.WorldlyContainer;
 import net.minecraft.world.entity.ExperienceOrb;
-import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.entity.player.StackedContents;
 import net.minecraft.world.inventory.ContainerData;
 import net.minecraft.world.inventory.RecipeHolder;
@@ -43,8 +44,22 @@
 import net.minecraft.world.level.block.Blocks;
 import net.minecraft.world.level.block.state.BlockState;
 import net.minecraft.world.phys.Vec3;
+// CraftBukkit start
+import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlock;
+import org.bukkit.craftbukkit.v1_19_R1.entity.CraftHumanEntity;
+import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.block.BlockExpEvent;
+import org.bukkit.event.inventory.FurnaceBurnEvent;
+import org.bukkit.event.inventory.FurnaceExtractEvent;
+import org.bukkit.event.inventory.FurnaceSmeltEvent;
+import org.bukkit.event.inventory.FurnaceStartSmeltEvent;
+import org.bukkit.inventory.CookingRecipe;
+// CraftBukkit end
 
 public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, RecipeHolder, StackedContentsCompatible {
+
     protected static final int SLOT_INPUT = 0;
     protected static final int SLOT_FUEL = 1;
     protected static final int SLOT_RESULT = 2;
@@ -58,150 +73,205 @@
     public static final int NUM_DATA_VALUES = 4;
     public static final int BURN_TIME_STANDARD = 200;
     public static final int BURN_COOL_SPEED = 2;
-    protected NonNullList<ItemStack> items = NonNullList.withSize(3, ItemStack.EMPTY);
+    protected NonNullList<ItemStack> items;
     public int litTime;
     int litDuration;
+    public double cookSpeedMultiplier = 1.0; // Paper - cook speed multiplier API
     public int cookingProgress;
     public int cookingTotalTime;
-    protected final ContainerData dataAccess = new ContainerData() {
-        @Override
-        public int get(int index) {
-            switch (index) {
-                case 0:
-                    return AbstractFurnaceBlockEntity.this.litTime;
-                case 1:
-                    return AbstractFurnaceBlockEntity.this.litDuration;
-                case 2:
-                    return AbstractFurnaceBlockEntity.this.cookingProgress;
-                case 3:
-                    return AbstractFurnaceBlockEntity.this.cookingTotalTime;
-                default:
-                    return 0;
-            }
-        }
+    protected final ContainerData dataAccess;
+    public final Object2IntOpenHashMap<ResourceLocation> recipesUsed;
+    private final RecipeManager.CachedCheck<Container, ? extends AbstractCookingRecipe> quickCheck;
+    public final RecipeType<? extends AbstractCookingRecipe> recipeType; // Paper
 
-        @Override
-        public void set(int index, int value) {
-            switch (index) {
-                case 0:
-                    AbstractFurnaceBlockEntity.this.litTime = value;
-                    break;
-                case 1:
-                    AbstractFurnaceBlockEntity.this.litDuration = value;
-                    break;
-                case 2:
-                    AbstractFurnaceBlockEntity.this.cookingProgress = value;
-                    break;
-                case 3:
-                    AbstractFurnaceBlockEntity.this.cookingTotalTime = value;
+    protected AbstractFurnaceBlockEntity(BlockEntityType<?> blockEntityType, BlockPos pos, BlockState state, RecipeType<? extends AbstractCookingRecipe> recipeType) {
+        super(blockEntityType, pos, state);
+        this.items = NonNullList.withSize(3, ItemStack.EMPTY);
+        this.dataAccess = new ContainerData() {
+            @Override
+            public int get(int index) {
+                switch (index) {
+                    case 0:
+                        return AbstractFurnaceBlockEntity.this.litTime;
+                    case 1:
+                        return AbstractFurnaceBlockEntity.this.litDuration;
+                    case 2:
+                        return AbstractFurnaceBlockEntity.this.cookingProgress;
+                    case 3:
+                        return AbstractFurnaceBlockEntity.this.cookingTotalTime;
+                    default:
+                        return 0;
+                }
             }
 
-        }
+            @Override
+            public void set(int index, int value) {
+                switch (index) {
+                    case 0:
+                        AbstractFurnaceBlockEntity.this.litTime = value;
+                        break;
+                    case 1:
+                        AbstractFurnaceBlockEntity.this.litDuration = value;
+                        break;
+                    case 2:
+                        AbstractFurnaceBlockEntity.this.cookingProgress = value;
+                        break;
+                    case 3:
+                        AbstractFurnaceBlockEntity.this.cookingTotalTime = value;
+                }
 
-        @Override
-        public int getCount() {
-            return 4;
-        }
-    };
-    public final Object2IntOpenHashMap<ResourceLocation> recipesUsed = new Object2IntOpenHashMap<>();
-    private final RecipeManager.CachedCheck<Container, ? extends AbstractCookingRecipe> quickCheck;
+            }
 
-    protected AbstractFurnaceBlockEntity(BlockEntityType<?> blockEntityType, BlockPos pos, BlockState state, RecipeType<? extends AbstractCookingRecipe> recipeType) {
-        super(blockEntityType, pos, state);
-        this.quickCheck = RecipeManager.createCheck(recipeType);
+            @Override
+            public int getCount() {
+                return 4;
+            }
+        };
+        this.recipesUsed = new Object2IntOpenHashMap();
+        this.quickCheck = RecipeManager.createCheck((RecipeType<AbstractCookingRecipe>) recipeType); // CraftBukkit - decompile error // Eclipse fail
+        this.recipeType = recipeType; // Paper
     }
 
+    private static Map<Item, Integer> cachedBurnDurations = null; // Paper - cache burn durations
     public static Map<Item, Integer> getFuel() {
+        // Paper start - cache burn durations
+        if(cachedBurnDurations != null) {
+            return cachedBurnDurations;
+        }
+        // Paper end
         Map<Item, Integer> map = Maps.newLinkedHashMap();
-        add(map, Items.LAVA_BUCKET, 20000);
-        add(map, Blocks.COAL_BLOCK, 16000);
-        add(map, Items.BLAZE_ROD, 2400);
-        add(map, Items.COAL, 1600);
-        add(map, Items.CHARCOAL, 1600);
-        add(map, ItemTags.LOGS, 300);
-        add(map, ItemTags.PLANKS, 300);
-        add(map, ItemTags.WOODEN_STAIRS, 300);
-        add(map, ItemTags.WOODEN_SLABS, 150);
-        add(map, ItemTags.WOODEN_TRAPDOORS, 300);
-        add(map, ItemTags.WOODEN_PRESSURE_PLATES, 300);
-        add(map, Blocks.OAK_FENCE, 300);
-        add(map, Blocks.BIRCH_FENCE, 300);
-        add(map, Blocks.SPRUCE_FENCE, 300);
-        add(map, Blocks.JUNGLE_FENCE, 300);
-        add(map, Blocks.DARK_OAK_FENCE, 300);
-        add(map, Blocks.ACACIA_FENCE, 300);
-        add(map, Blocks.MANGROVE_FENCE, 300);
-        add(map, Blocks.OAK_FENCE_GATE, 300);
-        add(map, Blocks.BIRCH_FENCE_GATE, 300);
-        add(map, Blocks.SPRUCE_FENCE_GATE, 300);
-        add(map, Blocks.JUNGLE_FENCE_GATE, 300);
-        add(map, Blocks.DARK_OAK_FENCE_GATE, 300);
-        add(map, Blocks.ACACIA_FENCE_GATE, 300);
-        add(map, Blocks.MANGROVE_FENCE_GATE, 300);
-        add(map, Blocks.NOTE_BLOCK, 300);
-        add(map, Blocks.BOOKSHELF, 300);
-        add(map, Blocks.LECTERN, 300);
-        add(map, Blocks.JUKEBOX, 300);
-        add(map, Blocks.CHEST, 300);
-        add(map, Blocks.TRAPPED_CHEST, 300);
-        add(map, Blocks.CRAFTING_TABLE, 300);
-        add(map, Blocks.DAYLIGHT_DETECTOR, 300);
-        add(map, ItemTags.BANNERS, 300);
-        add(map, Items.BOW, 300);
-        add(map, Items.FISHING_ROD, 300);
-        add(map, Blocks.LADDER, 300);
-        add(map, ItemTags.SIGNS, 200);
-        add(map, Items.WOODEN_SHOVEL, 200);
-        add(map, Items.WOODEN_SWORD, 200);
-        add(map, Items.WOODEN_HOE, 200);
-        add(map, Items.WOODEN_AXE, 200);
-        add(map, Items.WOODEN_PICKAXE, 200);
-        add(map, ItemTags.WOODEN_DOORS, 200);
-        add(map, ItemTags.BOATS, 1200);
-        add(map, ItemTags.WOOL, 100);
-        add(map, ItemTags.WOODEN_BUTTONS, 100);
-        add(map, Items.STICK, 100);
-        add(map, ItemTags.SAPLINGS, 100);
-        add(map, Items.BOWL, 100);
-        add(map, ItemTags.WOOL_CARPETS, 67);
-        add(map, Blocks.DRIED_KELP_BLOCK, 4001);
-        add(map, Items.CROSSBOW, 300);
-        add(map, Blocks.BAMBOO, 50);
-        add(map, Blocks.DEAD_BUSH, 100);
-        add(map, Blocks.SCAFFOLDING, 50);
-        add(map, Blocks.LOOM, 300);
-        add(map, Blocks.BARREL, 300);
-        add(map, Blocks.CARTOGRAPHY_TABLE, 300);
-        add(map, Blocks.FLETCHING_TABLE, 300);
-        add(map, Blocks.SMITHING_TABLE, 300);
-        add(map, Blocks.COMPOSTER, 300);
-        add(map, Blocks.AZALEA, 100);
-        add(map, Blocks.FLOWERING_AZALEA, 100);
-        add(map, Blocks.MANGROVE_ROOTS, 300);
-        return map;
+
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.LAVA_BUCKET, 20000);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.COAL_BLOCK, 16000);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.BLAZE_ROD, 2400);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.COAL, 1600);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.CHARCOAL, 1600);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.LOGS, 300);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.PLANKS, 300);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.WOODEN_STAIRS, 300);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.WOODEN_SLABS, 150);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.WOODEN_TRAPDOORS, 300);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.WOODEN_PRESSURE_PLATES, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.OAK_FENCE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.BIRCH_FENCE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.SPRUCE_FENCE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.JUNGLE_FENCE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.DARK_OAK_FENCE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.ACACIA_FENCE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.MANGROVE_FENCE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.OAK_FENCE_GATE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.BIRCH_FENCE_GATE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.SPRUCE_FENCE_GATE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.JUNGLE_FENCE_GATE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.DARK_OAK_FENCE_GATE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.ACACIA_FENCE_GATE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.MANGROVE_FENCE_GATE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.NOTE_BLOCK, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.BOOKSHELF, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.LECTERN, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.JUKEBOX, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.CHEST, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.TRAPPED_CHEST, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.CRAFTING_TABLE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.DAYLIGHT_DETECTOR, 300);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.BANNERS, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.BOW, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.FISHING_ROD, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.LADDER, 300);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.SIGNS, 200);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.WOODEN_SHOVEL, 200);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.WOODEN_SWORD, 200);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.WOODEN_HOE, 200);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.WOODEN_AXE, 200);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.WOODEN_PICKAXE, 200);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.WOODEN_DOORS, 200);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.BOATS, 1200);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.WOOL, 100);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.WOODEN_BUTTONS, 100);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.STICK, 100);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.SAPLINGS, 100);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.BOWL, 100);
+        AbstractFurnaceBlockEntity.add(map, ItemTags.WOOL_CARPETS, 67);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.DRIED_KELP_BLOCK, 4001);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.CROSSBOW, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.BAMBOO, 50);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.DEAD_BUSH, 100);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.SCAFFOLDING, 50);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.LOOM, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.BARREL, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.CARTOGRAPHY_TABLE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.FLETCHING_TABLE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.SMITHING_TABLE, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.COMPOSTER, 300);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.AZALEA, 100);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.FLOWERING_AZALEA, 100);
+        AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.MANGROVE_ROOTS, 300);
+        // Paper start - cache burn durations
+        cachedBurnDurations = com.google.common.collect.ImmutableMap.copyOf(map);
+        return cachedBurnDurations;
+        // Paper end
+    }
+
+    // CraftBukkit start - add fields and methods
+    private int maxStack = MAX_STACK;
+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
+
+    public List<ItemStack> getContents() {
+        return this.items;
+    }
+
+    public void onOpen(CraftHumanEntity who) {
+        this.transaction.add(who);
+    }
+
+    public void onClose(CraftHumanEntity who) {
+        this.transaction.remove(who);
     }
 
+    public List<HumanEntity> getViewers() {
+        return this.transaction;
+    }
+
+    @Override
+    public int getMaxStackSize() {
+        return this.maxStack;
+    }
+
+    public void setMaxStackSize(int size) {
+        this.maxStack = size;
+    }
+
+    public Object2IntOpenHashMap<ResourceLocation> getRecipesUsed() {
+        return this.recipesUsed; // PAIL private -> public
+    }
+    // CraftBukkit end
+
     private static boolean isNeverAFurnaceFuel(Item item) {
         return item.builtInRegistryHolder().is(ItemTags.NON_FLAMMABLE_WOOD);
     }
 
     private static void add(Map<Item, Integer> fuelTimes, TagKey<Item> tag, int fuelTime) {
-        for(Holder<Item> holder : Registry.ITEM.getTagOrEmpty(tag)) {
-            if (!isNeverAFurnaceFuel(holder.value())) {
-                fuelTimes.put(holder.value(), fuelTime);
+        Iterator iterator = Registry.ITEM.getTagOrEmpty(tag).iterator();
+
+        while (iterator.hasNext()) {
+            Holder<Item> holder = (Holder) iterator.next();
+
+            if (!AbstractFurnaceBlockEntity.isNeverAFurnaceFuel((Item) holder.value())) {
+                fuelTimes.put((Item) holder.value(), fuelTime);
             }
         }
 
     }
 
     private static void add(Map<Item, Integer> fuelTimes, ItemLike item, int fuelTime) {
-        Item item2 = item.asItem();
-        if (isNeverAFurnaceFuel(item2)) {
+        Item item1 = item.asItem();
+
+        if (AbstractFurnaceBlockEntity.isNeverAFurnaceFuel(item1)) {
             if (SharedConstants.IS_RUNNING_IN_IDE) {
-                throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("A developer tried to explicitly make fire resistant item " + item2.getName((ItemStack)null).getString() + " a furnace fuel. That will not work!"));
+                throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("A developer tried to explicitly make fire resistant item " + item1.getName((ItemStack) null).getString() + " a furnace fuel. That will not work!"));
             }
         } else {
-            fuelTimes.put(item2, fuelTime);
+            fuelTimes.put(item1, fuelTime);
         }
     }
 
@@ -217,132 +287,195 @@
         this.litTime = nbt.getShort("BurnTime");
         this.cookingProgress = nbt.getShort("CookTime");
         this.cookingTotalTime = nbt.getShort("CookTimeTotal");
-        this.litDuration = this.getBurnDuration(this.items.get(1));
-        CompoundTag compoundTag = nbt.getCompound("RecipesUsed");
+        this.litDuration = this.getBurnDuration((ItemStack) this.items.get(1));
+        CompoundTag nbttagcompound1 = nbt.getCompound("RecipesUsed");
+        Iterator iterator = nbttagcompound1.getAllKeys().iterator();
 
-        for(String string : compoundTag.getAllKeys()) {
-            this.recipesUsed.put(new ResourceLocation(string), compoundTag.getInt(string));
+        while (iterator.hasNext()) {
+            String s = (String) iterator.next();
+
+            this.recipesUsed.put(new ResourceLocation(s), nbttagcompound1.getInt(s));
         }
 
+        // Paper start - cook speed API
+        if (nbt.contains("Paper.CookSpeedMultiplier")) {
+            this.cookSpeedMultiplier = nbt.getDouble("Paper.CookSpeedMultiplier");
+        }
+        // Paper end
     }
 
     @Override
     protected void saveAdditional(CompoundTag nbt) {
         super.saveAdditional(nbt);
-        nbt.putShort("BurnTime", (short)this.litTime);
-        nbt.putShort("CookTime", (short)this.cookingProgress);
-        nbt.putShort("CookTimeTotal", (short)this.cookingTotalTime);
+        nbt.putShort("BurnTime", (short) this.litTime);
+        nbt.putShort("CookTime", (short) this.cookingProgress);
+        nbt.putShort("CookTimeTotal", (short) this.cookingTotalTime);
+        nbt.putDouble("Paper.CookSpeedMultiplier", this.cookSpeedMultiplier); // Paper - cook speed multiplier API
         ContainerHelper.saveAllItems(nbt, this.items);
-        CompoundTag compoundTag = new CompoundTag();
-        this.recipesUsed.forEach((identifier, count) -> {
-            compoundTag.putInt(identifier.toString(), count);
+        CompoundTag nbttagcompound1 = new CompoundTag();
+
+        this.recipesUsed.forEach((minecraftkey, integer) -> {
+            nbttagcompound1.putInt(minecraftkey.toString(), integer);
         });
-        nbt.put("RecipesUsed", compoundTag);
+        nbt.put("RecipesUsed", nbttagcompound1);
     }
 
     public static void serverTick(Level world, BlockPos pos, BlockState state, AbstractFurnaceBlockEntity blockEntity) {
-        boolean bl = blockEntity.isLit();
-        boolean bl2 = false;
+        boolean flag = blockEntity.isLit();
+        boolean flag1 = false;
+
         if (blockEntity.isLit()) {
             --blockEntity.litTime;
         }
 
-        ItemStack itemStack = blockEntity.items.get(1);
-        boolean bl3 = !blockEntity.items.get(0).isEmpty();
-        boolean bl4 = !itemStack.isEmpty();
-        if (blockEntity.isLit() || bl4 && bl3) {
-            Recipe<?> recipe;
-            if (bl3) {
-                recipe = blockEntity.quickCheck.getRecipeFor(blockEntity, world).orElse((AbstractCookingRecipe)null);
+        ItemStack itemstack = (ItemStack) blockEntity.items.get(1);
+        boolean flag2 = !((ItemStack) blockEntity.items.get(0)).isEmpty();
+        boolean flag3 = !itemstack.isEmpty();
+
+        if (!blockEntity.isLit() && (!flag3 || !flag2)) {
+            if (!blockEntity.isLit() && blockEntity.cookingProgress > 0) {
+                blockEntity.cookingProgress = Mth.clamp(blockEntity.cookingProgress - 2, (int) 0, blockEntity.cookingTotalTime);
+            }
+        } else {
+            Recipe irecipe;
+
+            if (flag2) {
+                irecipe = (Recipe) blockEntity.quickCheck.getRecipeFor(blockEntity, world).orElse(null); // CraftBukkit - decompile error
             } else {
-                recipe = null;
+                irecipe = null;
             }
 
             int i = blockEntity.getMaxStackSize();
-            if (!blockEntity.isLit() && canBurn(recipe, blockEntity.items, i)) {
-                blockEntity.litTime = blockEntity.getBurnDuration(itemStack);
+
+            if (!blockEntity.isLit() && AbstractFurnaceBlockEntity.canBurn(irecipe, blockEntity.items, i)) {
+                // CraftBukkit start
+                CraftItemStack fuel = CraftItemStack.asCraftMirror(itemstack);
+
+                FurnaceBurnEvent furnaceBurnEvent = new FurnaceBurnEvent(CraftBlock.at(world, pos), fuel, blockEntity.getBurnDuration(itemstack));
+                world.getCraftServer().getPluginManager().callEvent(furnaceBurnEvent);
+
+                if (furnaceBurnEvent.isCancelled()) {
+                    return;
+                }
+
+                blockEntity.litTime = furnaceBurnEvent.getBurnTime();
                 blockEntity.litDuration = blockEntity.litTime;
-                if (blockEntity.isLit()) {
-                    bl2 = true;
-                    if (bl4) {
-                        Item item = itemStack.getItem();
-                        itemStack.shrink(1);
-                        if (itemStack.isEmpty()) {
-                            Item item2 = item.getCraftingRemainingItem();
-                            blockEntity.items.set(1, item2 == null ? ItemStack.EMPTY : new ItemStack(item2));
+                if (blockEntity.isLit() && furnaceBurnEvent.isBurning()) {
+                    // CraftBukkit end
+                    flag1 = true;
+                    if (flag3 && furnaceBurnEvent.willConsumeFuel()) { // Paper
+                        Item item = itemstack.getItem();
+
+                        itemstack.shrink(1);
+                        if (itemstack.isEmpty()) {
+                            Item item1 = item.getCraftingRemainingItem();
+
+                            blockEntity.items.set(1, item1 == null ? ItemStack.EMPTY : new ItemStack(item1));
                         }
                     }
                 }
             }
 
-            if (blockEntity.isLit() && canBurn(recipe, blockEntity.items, i)) {
+            if (blockEntity.isLit() && AbstractFurnaceBlockEntity.canBurn(irecipe, blockEntity.items, i)) {
+                // CraftBukkit start
+                if (irecipe != null && blockEntity.cookingProgress == 0) {
+                    CraftItemStack source = CraftItemStack.asCraftMirror(blockEntity.items.get(0));
+                    CookingRecipe<?> recipe = (CookingRecipe<?>) irecipe.toBukkitRecipe();
+
+                    FurnaceStartSmeltEvent event = new FurnaceStartSmeltEvent(CraftBlock.at(world, pos), source, recipe, AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity.recipeType, blockEntity, blockEntity.cookSpeedMultiplier)); // Paper - cook speed multiplier API
+                    world.getCraftServer().getPluginManager().callEvent(event);
+
+                    blockEntity.cookingTotalTime = event.getTotalCookTime();
+                }
+                // CraftBukkit end
+
                 ++blockEntity.cookingProgress;
-                if (blockEntity.cookingProgress == blockEntity.cookingTotalTime) {
+                if (blockEntity.cookingProgress >= blockEntity.cookingTotalTime) { // Paper - cook speed multiplier API
                     blockEntity.cookingProgress = 0;
-                    blockEntity.cookingTotalTime = getTotalCookTime(world, blockEntity);
-                    if (burn(recipe, blockEntity.items, i)) {
-                        blockEntity.setRecipeUsed(recipe);
+                    blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity.recipeType, blockEntity, blockEntity.cookSpeedMultiplier); // Paper
+                    if (AbstractFurnaceBlockEntity.burn(blockEntity.level, blockEntity.worldPosition, irecipe, blockEntity.items, i)) { // CraftBukkit
+                        blockEntity.setRecipeUsed(irecipe);
                     }
 
-                    bl2 = true;
+                    flag1 = true;
                 }
             } else {
                 blockEntity.cookingProgress = 0;
             }
-        } else if (!blockEntity.isLit() && blockEntity.cookingProgress > 0) {
-            blockEntity.cookingProgress = Mth.clamp(blockEntity.cookingProgress - 2, 0, blockEntity.cookingTotalTime);
         }
 
-        if (bl != blockEntity.isLit()) {
-            bl2 = true;
-            state = state.setValue(AbstractFurnaceBlock.LIT, Boolean.valueOf(blockEntity.isLit()));
+        if (flag != blockEntity.isLit()) {
+            flag1 = true;
+            state = (BlockState) state.setValue(AbstractFurnaceBlock.LIT, blockEntity.isLit());
             world.setBlock(pos, state, 3);
         }
 
-        if (bl2) {
+        if (flag1) {
             setChanged(world, pos, state);
         }
 
     }
 
     private static boolean canBurn(@Nullable Recipe<?> recipe, NonNullList<ItemStack> slots, int count) {
-        if (!slots.get(0).isEmpty() && recipe != null) {
-            ItemStack itemStack = recipe.getResultItem();
-            if (itemStack.isEmpty()) {
+        if (!((ItemStack) slots.get(0)).isEmpty() && recipe != null) {
+            ItemStack itemstack = recipe.getResultItem();
+
+            if (itemstack.isEmpty()) {
                 return false;
             } else {
-                ItemStack itemStack2 = slots.get(2);
-                if (itemStack2.isEmpty()) {
-                    return true;
-                } else if (!itemStack2.sameItem(itemStack)) {
-                    return false;
-                } else if (itemStack2.getCount() < count && itemStack2.getCount() < itemStack2.getMaxStackSize()) {
-                    return true;
-                } else {
-                    return itemStack2.getCount() < itemStack.getMaxStackSize();
-                }
+                ItemStack itemstack1 = (ItemStack) slots.get(2);
+
+                return itemstack1.isEmpty() ? true : (!itemstack1.sameItem(itemstack) ? false : (itemstack1.getCount() < count && itemstack1.getCount() < itemstack1.getMaxStackSize() ? true : itemstack1.getCount() < itemstack.getMaxStackSize()));
             }
         } else {
             return false;
         }
     }
 
-    private static boolean burn(@Nullable Recipe<?> recipe, NonNullList<ItemStack> slots, int count) {
-        if (recipe != null && canBurn(recipe, slots, count)) {
-            ItemStack itemStack = slots.get(0);
-            ItemStack itemStack2 = recipe.getResultItem();
-            ItemStack itemStack3 = slots.get(2);
-            if (itemStack3.isEmpty()) {
-                slots.set(2, itemStack2.copy());
-            } else if (itemStack3.is(itemStack2.getItem())) {
-                itemStack3.grow(1);
+    private static boolean burn(Level world, BlockPos blockposition, @Nullable Recipe<?> irecipe, NonNullList<ItemStack> nonnulllist, int i) { // CraftBukkit
+        if (irecipe != null && AbstractFurnaceBlockEntity.canBurn(irecipe, nonnulllist, i)) {
+            ItemStack itemstack = (ItemStack) nonnulllist.get(0);
+            ItemStack itemstack1 = irecipe.getResultItem();
+            ItemStack itemstack2 = (ItemStack) nonnulllist.get(2);
+
+            // CraftBukkit start - fire FurnaceSmeltEvent
+            CraftItemStack source = CraftItemStack.asCraftMirror(itemstack);
+            org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1);
+
+            FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result, (org.bukkit.inventory.CookingRecipe<?>) irecipe.toBukkitRecipe()); // Paper
+            world.getCraftServer().getPluginManager().callEvent(furnaceSmeltEvent);
+
+            if (furnaceSmeltEvent.isCancelled()) {
+                return false;
             }
 
-            if (itemStack.is(Blocks.WET_SPONGE.asItem()) && !slots.get(1).isEmpty() && slots.get(1).is(Items.BUCKET)) {
-                slots.set(1, new ItemStack(Items.WATER_BUCKET));
+            result = furnaceSmeltEvent.getResult();
+            itemstack1 = CraftItemStack.asNMSCopy(result);
+
+            if (!itemstack1.isEmpty()) {
+                if (itemstack2.isEmpty()) {
+                    nonnulllist.set(2, itemstack1.copy());
+                } else if (CraftItemStack.asCraftMirror(itemstack2).isSimilar(result)) {
+                    itemstack2.grow(itemstack1.getCount());
+                } else {
+                    return false;
+                }
             }
 
-            itemStack.shrink(1);
+            /*
+            if (itemstack2.isEmpty()) {
+                nonnulllist.set(2, itemstack1.copy());
+            } else if (itemstack2.is(itemstack1.getItem())) {
+                itemstack2.grow(1);
+            }
+            */
+            // CraftBukkit end
+
+            if (itemstack.is(Blocks.WET_SPONGE.asItem()) && !((ItemStack) nonnulllist.get(1)).isEmpty() && ((ItemStack) nonnulllist.get(1)).is(Items.BUCKET)) {
+                nonnulllist.set(1, new ItemStack(Items.WATER_BUCKET));
+            }
+
+            itemstack.shrink(1);
             return true;
         } else {
             return false;
@@ -354,25 +487,26 @@
             return 0;
         } else {
             Item item = fuel.getItem();
-            return getFuel().getOrDefault(item, 0);
+
+            return (Integer) AbstractFurnaceBlockEntity.getFuel().getOrDefault(item, 0);
         }
     }
 
-    private static int getTotalCookTime(Level world, AbstractFurnaceBlockEntity furnace) {
-        return furnace.quickCheck.getRecipeFor(furnace, world).map(AbstractCookingRecipe::getCookingTime).orElse(200);
+    // Paper start
+    public static int getTotalCookTime(@Nullable Level world, RecipeType<? extends AbstractCookingRecipe> recipeType, AbstractFurnaceBlockEntity furnace, double cookSpeedMultiplier) {
+        /* Scale the recipe's cooking time to the current cookSpeedMultiplier */
+        int cookTime = world != null ? furnace.quickCheck.getRecipeFor(furnace, world).map(AbstractCookingRecipe::getCookingTime).orElse(200) : (net.minecraft.server.MinecraftServer.getServer().getRecipeManager().getRecipeFor(recipeType, furnace, world /* passing a null level here is safe. world is only used for map extending recipes which won't happen here */).map(AbstractCookingRecipe::getCookingTime).orElse(200));
+        return (int) Math.ceil (cookTime / cookSpeedMultiplier);
     }
+    // Paper end
 
     public static boolean isFuel(ItemStack stack) {
-        return getFuel().containsKey(stack.getItem());
+        return AbstractFurnaceBlockEntity.getFuel().containsKey(stack.getItem());
     }
 
     @Override
     public int[] getSlotsForFace(Direction side) {
-        if (side == Direction.DOWN) {
-            return SLOTS_FOR_DOWN;
-        } else {
-            return side == Direction.UP ? SLOTS_FOR_UP : SLOTS_FOR_SIDES;
-        }
+        return side == Direction.DOWN ? AbstractFurnaceBlockEntity.SLOTS_FOR_DOWN : (side == Direction.UP ? AbstractFurnaceBlockEntity.SLOTS_FOR_UP : AbstractFurnaceBlockEntity.SLOTS_FOR_SIDES);
     }
 
     @Override
@@ -382,11 +516,7 @@
 
     @Override
     public boolean canTakeItemThroughFace(int slot, ItemStack stack, Direction dir) {
-        if (dir == Direction.DOWN && slot == 1) {
-            return stack.is(Items.WATER_BUCKET) || stack.is(Items.BUCKET);
-        } else {
-            return true;
-        }
+        return dir == Direction.DOWN && slot == 1 ? stack.is(Items.WATER_BUCKET) || stack.is(Items.BUCKET) : true;
     }
 
     @Override
@@ -396,18 +526,24 @@
 
     @Override
     public boolean isEmpty() {
-        for(ItemStack itemStack : this.items) {
-            if (!itemStack.isEmpty()) {
-                return false;
+        Iterator iterator = this.items.iterator();
+
+        ItemStack itemstack;
+
+        do {
+            if (!iterator.hasNext()) {
+                return true;
             }
-        }
 
-        return true;
+            itemstack = (ItemStack) iterator.next();
+        } while (itemstack.isEmpty());
+
+        return false;
     }
 
     @Override
     public ItemStack getItem(int slot) {
-        return this.items.get(slot);
+        return (ItemStack) this.items.get(slot);
     }
 
     @Override
@@ -422,15 +558,16 @@
 
     @Override
     public void setItem(int slot, ItemStack stack) {
-        ItemStack itemStack = this.items.get(slot);
-        boolean bl = !stack.isEmpty() && stack.sameItem(itemStack) && ItemStack.tagMatches(stack, itemStack);
+        ItemStack itemstack1 = (ItemStack) this.items.get(slot);
+        boolean flag = !stack.isEmpty() && stack.sameItem(itemstack1) && ItemStack.tagMatches(stack, itemstack1);
+
         this.items.set(slot, stack);
         if (stack.getCount() > this.getMaxStackSize()) {
             stack.setCount(this.getMaxStackSize());
         }
 
-        if (slot == 0 && !bl) {
-            this.cookingTotalTime = getTotalCookTime(this.level, this);
+        if (slot == 0 && !flag) {
+            this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.level, this.recipeType, this, this.cookSpeedMultiplier); // Paper
             this.cookingProgress = 0;
             this.setChanged();
         }
@@ -438,12 +575,8 @@
     }
 
     @Override
-    public boolean stillValid(Player player) {
-        if (this.level.getBlockEntity(this.worldPosition) != this) {
-            return false;
-        } else {
-            return player.distanceToSqr((double)this.worldPosition.getX() + 0.5D, (double)this.worldPosition.getY() + 0.5D, (double)this.worldPosition.getZ() + 0.5D) <= 64.0D;
-        }
+    public boolean stillValid(net.minecraft.world.entity.player.Player player) {
+        return this.level.getBlockEntity(this.worldPosition) != this ? false : player.distanceToSqr((double) this.worldPosition.getX() + 0.5D, (double) this.worldPosition.getY() + 0.5D, (double) this.worldPosition.getZ() + 0.5D) <= 64.0D;
     }
 
     @Override
@@ -453,8 +586,9 @@
         } else if (slot != 1) {
             return true;
         } else {
-            ItemStack itemStack = this.items.get(1);
-            return isFuel(stack) || stack.is(Items.BUCKET) && !itemStack.is(Items.BUCKET);
+            ItemStack itemstack1 = (ItemStack) this.items.get(1);
+
+            return AbstractFurnaceBlockEntity.isFuel(stack) || stack.is(Items.BUCKET) && !itemstack1.is(Items.BUCKET);
         }
     }
 
@@ -466,8 +600,9 @@
     @Override
     public void setRecipeUsed(@Nullable Recipe<?> recipe) {
         if (recipe != null) {
-            ResourceLocation resourceLocation = recipe.getId();
-            this.recipesUsed.addTo(resourceLocation, 1);
+            ResourceLocation minecraftkey = recipe.getId();
+
+            this.recipesUsed.addTo(minecraftkey, 1);
         }
 
     }
@@ -479,42 +614,67 @@
     }
 
     @Override
-    public void awardUsedRecipes(Player player) {
-    }
+    public void awardUsedRecipes(net.minecraft.world.entity.player.Player player) {}
 
-    public void awardUsedRecipesAndPopExperience(ServerPlayer player) {
-        List<Recipe<?>> list = this.getRecipesToAwardAndPopExperience(player.getLevel(), player.position());
-        player.awardRecipes(list);
+    public void awardUsedRecipesAndPopExperience(ServerPlayer entityplayer, ItemStack itemstack, int amount) { // CraftBukkit
+        List<Recipe<?>> list = this.getRecipesToAwardAndPopExperience(entityplayer.getLevel(), entityplayer.position(), this.worldPosition, entityplayer, itemstack, amount); // CraftBukkit
+
+        entityplayer.awardRecipes(list);
         this.recipesUsed.clear();
     }
 
     public List<Recipe<?>> getRecipesToAwardAndPopExperience(ServerLevel world, Vec3 pos) {
+        // CraftBukkit start
+        return this.getRecipesToAwardAndPopExperience(world, pos, this.worldPosition, null, null, 0);
+    }
+
+    public List<Recipe<?>> getRecipesToAwardAndPopExperience(ServerLevel worldserver, Vec3 vec3d, BlockPos blockposition, ServerPlayer entityplayer, ItemStack itemstack, int amount) {
+        // CraftBukkit end
         List<Recipe<?>> list = Lists.newArrayList();
+        ObjectIterator objectiterator = this.recipesUsed.object2IntEntrySet().iterator();
 
-        for(Object2IntMap.Entry<ResourceLocation> entry : this.recipesUsed.object2IntEntrySet()) {
-            world.getRecipeManager().byKey(entry.getKey()).ifPresent((recipe) -> {
-                list.add(recipe);
-                createExperience(world, pos, entry.getIntValue(), ((AbstractCookingRecipe)recipe).getExperience());
+        while (objectiterator.hasNext()) {
+            Entry<ResourceLocation> entry = (Entry) objectiterator.next();
+
+            worldserver.getRecipeManager().byKey((ResourceLocation) entry.getKey()).ifPresent((irecipe) -> {
+                list.add(irecipe);
+                AbstractFurnaceBlockEntity.createExperience(worldserver, vec3d, entry.getIntValue(), ((AbstractCookingRecipe) irecipe).getExperience(), blockposition, entityplayer, itemstack, amount); // CraftBukkit
             });
         }
 
         return list;
     }
 
-    private static void createExperience(ServerLevel world, Vec3 pos, int multiplier, float experience) {
-        int i = Mth.floor((float)multiplier * experience);
-        float f = Mth.frac((float)multiplier * experience);
-        if (f != 0.0F && Math.random() < (double)f) {
-            ++i;
+    private static void createExperience(ServerLevel worldserver, Vec3 vec3d, int i, float f, BlockPos blockposition, net.minecraft.world.entity.player.Player entityhuman, ItemStack itemstack, int amount) { // CraftBukkit
+        int j = Mth.floor((float) i * f);
+        float f1 = Mth.frac((float) i * f);
+
+        if (f1 != 0.0F && Math.random() < (double) f1) {
+            ++j;
         }
 
-        ExperienceOrb.award(world, pos, i);
+        // CraftBukkit start - fire FurnaceExtractEvent / BlockExpEvent
+        BlockExpEvent event;
+        if (amount != 0) {
+            event = new FurnaceExtractEvent((Player) entityhuman.getBukkitEntity(), CraftBlock.at(worldserver, blockposition), org.bukkit.craftbukkit.v1_19_R1.util.CraftMagicNumbers.getMaterial(itemstack.getItem()), amount, j);
+        } else {
+            event = new BlockExpEvent(CraftBlock.at(worldserver, blockposition), j);
+        }
+        worldserver.getCraftServer().getPluginManager().callEvent(event);
+        j = event.getExpToDrop();
+        // CraftBukkit end
+
+        ExperienceOrb.award(worldserver, vec3d, j, org.bukkit.entity.ExperienceOrb.SpawnReason.FURNACE, entityhuman); // Paper
     }
 
     @Override
     public void fillStackedContents(StackedContents finder) {
-        for(ItemStack itemStack : this.items) {
-            finder.accountStack(itemStack);
+        Iterator iterator = this.items.iterator();
+
+        while (iterator.hasNext()) {
+            ItemStack itemstack = (ItemStack) iterator.next();
+
+            finder.accountStack(itemstack);
         }
 
     }
