--- a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
@@ -22,8 +22,22 @@
 import net.minecraft.world.level.Level;
 import net.minecraft.world.level.block.BrewingStandBlock;
 import net.minecraft.world.level.block.state.BlockState;
+// CraftBukkit start
+import java.util.ArrayList;
+import java.util.List;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.craftbukkit.block.CraftBlock;
+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.event.block.BrewingStartEvent;
+import org.bukkit.event.inventory.BrewEvent;
+import org.bukkit.event.inventory.BrewingStandFuelEvent;
+import org.bukkit.inventory.InventoryHolder;
+// CraftBukkit end
 
 public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer {
+
     private static final int INGREDIENT_SLOT = 3;
     private static final int FUEL_SLOT = 4;
     private static final int[] SLOTS_FOR_UP = new int[]{3};
@@ -33,40 +47,82 @@
     public static final int DATA_BREW_TIME = 0;
     public static final int DATA_FUEL_USES = 1;
     public static final int NUM_DATA_VALUES = 2;
-    private NonNullList<ItemStack> items = NonNullList.withSize(5, ItemStack.EMPTY);
+    private NonNullList<ItemStack> items;
     public int brewTime;
     private boolean[] lastPotionCount;
     private Item ingredient;
     public int fuel;
-    protected final ContainerData dataAccess = new ContainerData() {
-        @Override
-        public int get(int index) {
-            return switch (index) {
-                case 0 -> BrewingStandBlockEntity.this.brewTime;
-                case 1 -> BrewingStandBlockEntity.this.fuel;
-                default -> 0;
-            };
-        }
+    protected final ContainerData dataAccess;
+    // CraftBukkit start - add fields and methods
+    private int lastTick = MinecraftServer.currentTick;
+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
+    private int maxStack = MAX_STACK;
 
-        @Override
-        public void set(int index, int value) {
-            switch (index) {
-                case 0:
-                    BrewingStandBlockEntity.this.brewTime = value;
-                    break;
-                case 1:
-                    BrewingStandBlockEntity.this.fuel = value;
-            }
-        }
+    public void onOpen(CraftHumanEntity who) {
+        this.transaction.add(who);
+    }
 
-        @Override
-        public int getCount() {
-            return 2;
-        }
-    };
+    public void onClose(CraftHumanEntity who) {
+        this.transaction.remove(who);
+    }
+
+    public List<HumanEntity> getViewers() {
+        return this.transaction;
+    }
+
+    public List<ItemStack> getContents() {
+        return this.items;
+    }
+
+    @Override
+    public int getMaxStackSize() {
+        return this.maxStack;
+    }
+
+    public void setMaxStackSize(int size) {
+        this.maxStack = size;
+    }
+    // CraftBukkit end
 
     public BrewingStandBlockEntity(BlockPos pos, BlockState state) {
         super(BlockEntityType.BREWING_STAND, pos, state);
+        this.items = NonNullList.withSize(5, ItemStack.EMPTY);
+        this.dataAccess = new ContainerData() {
+            @Override
+            public int get(int index) {
+                int j;
+
+                switch (index) {
+                    case 0:
+                        j = BrewingStandBlockEntity.this.brewTime;
+                        break;
+                    case 1:
+                        j = BrewingStandBlockEntity.this.fuel;
+                        break;
+                    default:
+                        j = 0;
+                }
+
+                return j;
+            }
+
+            @Override
+            public void set(int index, int value) {
+                switch (index) {
+                    case 0:
+                        BrewingStandBlockEntity.this.brewTime = value;
+                        break;
+                    case 1:
+                        BrewingStandBlockEntity.this.fuel = value;
+                }
+
+            }
+
+            @Override
+            public int getCount() {
+                return 2;
+            }
+        };
     }
 
     @Override
@@ -90,71 +146,99 @@
     }
 
     public static void serverTick(Level world, BlockPos pos, BlockState state, BrewingStandBlockEntity blockEntity) {
-        ItemStack itemStack = blockEntity.items.get(4);
-        if (blockEntity.fuel <= 0 && itemStack.is(Items.BLAZE_POWDER)) {
-            blockEntity.fuel = 20;
-            itemStack.shrink(1);
+        ItemStack itemstack = (ItemStack) blockEntity.items.get(4);
+
+        if (blockEntity.fuel <= 0 && itemstack.is(Items.BLAZE_POWDER)) {
+            // CraftBukkit start
+            BrewingStandFuelEvent event = new BrewingStandFuelEvent(CraftBlock.at(world, pos), CraftItemStack.asCraftMirror(itemstack), 20);
+            world.getCraftServer().getPluginManager().callEvent(event);
+
+            if (event.isCancelled()) {
+                return;
+            }
+
+            blockEntity.fuel = event.getFuelPower();
+            if (blockEntity.fuel > 0 && event.isConsuming()) {
+                itemstack.shrink(1);
+            }
+            // CraftBukkit end
             setChanged(world, pos, state);
         }
 
-        boolean bl = isBrewable(world.potionBrewing(), blockEntity.items);
-        boolean bl2 = blockEntity.brewTime > 0;
-        ItemStack itemStack2 = blockEntity.items.get(3);
-        if (bl2) {
-            blockEntity.brewTime--;
-            boolean bl3 = blockEntity.brewTime == 0;
-            if (bl3 && bl) {
-                doBrew(world, pos, blockEntity.items);
-            } else if (!bl || !itemStack2.is(blockEntity.ingredient)) {
+        boolean flag = BrewingStandBlockEntity.isBrewable(world.potionBrewing(), blockEntity.items);
+        boolean flag1 = blockEntity.brewTime > 0;
+        ItemStack itemstack1 = (ItemStack) blockEntity.items.get(3);
+
+        // CraftBukkit start - Use wall time instead of ticks for brewing
+        int elapsedTicks = MinecraftServer.currentTick - blockEntity.lastTick;
+        blockEntity.lastTick = MinecraftServer.currentTick;
+
+        if (flag1) {
+            blockEntity.brewTime -= elapsedTicks;
+            boolean flag2 = blockEntity.brewTime <= 0; // == -> <=
+            // CraftBukkit end
+
+            if (flag2 && flag) {
+                BrewingStandBlockEntity.doBrew(world, pos, blockEntity.items, blockEntity); // CraftBukkit
+            } else if (!flag || !itemstack1.is(blockEntity.ingredient)) {
                 blockEntity.brewTime = 0;
             }
 
             setChanged(world, pos, state);
-        } else if (bl && blockEntity.fuel > 0) {
-            blockEntity.fuel--;
-            blockEntity.brewTime = 400;
-            blockEntity.ingredient = itemStack2.getItem();
+        } else if (flag && blockEntity.fuel > 0) {
+            --blockEntity.fuel;
+            // CraftBukkit start
+            BrewingStartEvent event = new BrewingStartEvent(CraftBlock.at(world, pos), CraftItemStack.asCraftMirror(itemstack1), 400);
+            world.getCraftServer().getPluginManager().callEvent(event);
+            blockEntity.brewTime = event.getTotalBrewTime(); // 400 -> event.getTotalBrewTime()
+            // CraftBukkit end
+            blockEntity.ingredient = itemstack1.getItem();
             setChanged(world, pos, state);
         }
 
-        boolean[] bls = blockEntity.getPotionBits();
-        if (!Arrays.equals(bls, blockEntity.lastPotionCount)) {
-            blockEntity.lastPotionCount = bls;
-            BlockState blockState = state;
+        boolean[] aboolean = blockEntity.getPotionBits();
+
+        if (!Arrays.equals(aboolean, blockEntity.lastPotionCount)) {
+            blockEntity.lastPotionCount = aboolean;
+            BlockState iblockdata1 = state;
+
             if (!(state.getBlock() instanceof BrewingStandBlock)) {
                 return;
             }
 
-            for (int i = 0; i < BrewingStandBlock.HAS_BOTTLE.length; i++) {
-                blockState = blockState.setValue(BrewingStandBlock.HAS_BOTTLE[i], Boolean.valueOf(bls[i]));
+            for (int i = 0; i < BrewingStandBlock.HAS_BOTTLE.length; ++i) {
+                iblockdata1 = (BlockState) iblockdata1.setValue(BrewingStandBlock.HAS_BOTTLE[i], aboolean[i]);
             }
 
-            world.setBlock(pos, blockState, 2);
+            world.setBlock(pos, iblockdata1, 2);
         }
+
     }
 
     private boolean[] getPotionBits() {
-        boolean[] bls = new boolean[3];
+        boolean[] aboolean = new boolean[3];
 
-        for (int i = 0; i < 3; i++) {
-            if (!this.items.get(i).isEmpty()) {
-                bls[i] = true;
+        for (int i = 0; i < 3; ++i) {
+            if (!((ItemStack) this.items.get(i)).isEmpty()) {
+                aboolean[i] = true;
             }
         }
 
-        return bls;
+        return aboolean;
     }
 
     private static boolean isBrewable(PotionBrewing brewingRecipeRegistry, NonNullList<ItemStack> slots) {
-        ItemStack itemStack = slots.get(3);
-        if (itemStack.isEmpty()) {
+        ItemStack itemstack = (ItemStack) slots.get(3);
+
+        if (itemstack.isEmpty()) {
             return false;
-        } else if (!brewingRecipeRegistry.isIngredient(itemStack)) {
+        } else if (!brewingRecipeRegistry.isIngredient(itemstack)) {
             return false;
         } else {
-            for (int i = 0; i < 3; i++) {
-                ItemStack itemStack2 = slots.get(i);
-                if (!itemStack2.isEmpty() && brewingRecipeRegistry.hasMix(itemStack2, itemStack)) {
+            for (int i = 0; i < 3; ++i) {
+                ItemStack itemstack1 = (ItemStack) slots.get(i);
+
+                if (!itemstack1.isEmpty() && brewingRecipeRegistry.hasMix(itemstack1, itemstack)) {
                     return true;
                 }
             }
@@ -163,26 +247,49 @@
         }
     }
 
-    private static void doBrew(Level world, BlockPos pos, NonNullList<ItemStack> slots) {
-        ItemStack itemStack = slots.get(3);
-        PotionBrewing potionBrewing = world.potionBrewing();
+    private static void doBrew(Level world, BlockPos blockposition, NonNullList<ItemStack> nonnulllist, BrewingStandBlockEntity tileentitybrewingstand) { // CraftBukkit
+        ItemStack itemstack = (ItemStack) nonnulllist.get(3);
+        PotionBrewing potionbrewer = world.potionBrewing();
 
-        for (int i = 0; i < 3; i++) {
-            slots.set(i, potionBrewing.mix(itemStack, slots.get(i)));
+        // CraftBukkit start
+        InventoryHolder owner = tileentitybrewingstand.getOwner();
+        List<org.bukkit.inventory.ItemStack> brewResults = new ArrayList<>(3);
+        for (int i = 0; i < 3; ++i) {
+            brewResults.add(i, CraftItemStack.asCraftMirror(potionbrewer.mix(itemstack, (ItemStack) nonnulllist.get(i))));
         }
 
-        itemStack.shrink(1);
-        if (itemStack.getItem().hasCraftingRemainingItem()) {
-            ItemStack itemStack2 = new ItemStack(itemStack.getItem().getCraftingRemainingItem());
-            if (itemStack.isEmpty()) {
-                itemStack = itemStack2;
+        if (owner != null) {
+            BrewEvent event = new BrewEvent(CraftBlock.at(world, blockposition), (org.bukkit.inventory.BrewerInventory) owner.getInventory(), brewResults, tileentitybrewingstand.fuel);
+            org.bukkit.Bukkit.getPluginManager().callEvent(event);
+            if (event.isCancelled()) {
+                return;
+            }
+        }
+        // CraftBukkit end
+
+        for (int i = 0; i < 3; ++i) {
+            // CraftBukkit start - validate index in case it is cleared by plugins
+            if (i < brewResults.size()) {
+                nonnulllist.set(i, CraftItemStack.asNMSCopy(brewResults.get(i)));
             } else {
-                Containers.dropItemStack(world, (double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), itemStack2);
+                nonnulllist.set(i, ItemStack.EMPTY);
             }
+            // CraftBukkit end
         }
 
-        slots.set(3, itemStack);
-        world.levelEvent(1035, pos, 0);
+        itemstack.shrink(1);
+        if (itemstack.getItem().hasCraftingRemainingItem()) {
+            ItemStack itemstack1 = new ItemStack(itemstack.getItem().getCraftingRemainingItem());
+
+            if (itemstack.isEmpty()) {
+                itemstack = itemstack1;
+            } else {
+                Containers.dropItemStack(world, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), itemstack1);
+            }
+        }
+
+        nonnulllist.set(3, itemstack);
+        world.levelEvent(1035, blockposition, 0);
     }
 
     @Override
@@ -192,7 +299,7 @@
         ContainerHelper.loadAllItems(nbt, this.items, registryLookup);
         this.brewTime = nbt.getShort("BrewTime");
         if (this.brewTime > 0) {
-            this.ingredient = this.items.get(3).getItem();
+            this.ingredient = ((ItemStack) this.items.get(3)).getItem();
         }
 
         this.fuel = nbt.getByte("Fuel");
@@ -201,31 +308,25 @@
     @Override
     protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registryLookup) {
         super.saveAdditional(nbt, registryLookup);
-        nbt.putShort("BrewTime", (short)this.brewTime);
+        nbt.putShort("BrewTime", (short) this.brewTime);
         ContainerHelper.saveAllItems(nbt, this.items, registryLookup);
-        nbt.putByte("Fuel", (byte)this.fuel);
+        nbt.putByte("Fuel", (byte) this.fuel);
     }
 
     @Override
     public boolean canPlaceItem(int slot, ItemStack stack) {
+        PotionBrewing potionbrewer = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY; // Paper - move up
         if (slot == 3) {
-            PotionBrewing potionBrewing = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY;
-            return potionBrewing.isIngredient(stack);
+
+            return potionbrewer.isIngredient(stack);
         } else {
-            return slot == 4
-                ? stack.is(Items.BLAZE_POWDER)
-                : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE))
-                    && this.getItem(slot).isEmpty();
+            return slot == 4 ? stack.is(Items.BLAZE_POWDER) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionbrewer.isCustomInput(stack)) && this.getItem(slot).isEmpty(); // Paper - Custom Potion Mixes
         }
     }
 
     @Override
     public int[] getSlotsForFace(Direction side) {
-        if (side == Direction.UP) {
-            return SLOTS_FOR_UP;
-        } else {
-            return side == Direction.DOWN ? SLOTS_FOR_DOWN : SLOTS_FOR_SIDES;
-        }
+        return side == Direction.UP ? BrewingStandBlockEntity.SLOTS_FOR_UP : (side == Direction.DOWN ? BrewingStandBlockEntity.SLOTS_FOR_DOWN : BrewingStandBlockEntity.SLOTS_FOR_SIDES);
     }
 
     @Override
@@ -235,7 +336,7 @@
 
     @Override
     public boolean canTakeItemThroughFace(int slot, ItemStack stack, Direction dir) {
-        return slot != 3 || stack.is(Items.GLASS_BOTTLE);
+        return slot == 3 ? stack.is(Items.GLASS_BOTTLE) : true;
     }
 
     @Override
