From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Thu, 7 Oct 2021 14:34:55 -0700
Subject: [PATCH] Custom Potion Mixes


diff --git a/src/main/java/io/papermc/paper/potion/PaperPotionMix.java b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b0bed550763f34e18c9e92f9a47ec0c945b2c8b
--- /dev/null
+++ b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java
@@ -0,0 +1,13 @@
+package io.papermc.paper.potion;
+
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.crafting.Ingredient;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.craftbukkit.inventory.CraftRecipe;
+
+public record PaperPotionMix(ItemStack result, Ingredient input, Ingredient ingredient) {
+
+    public PaperPotionMix(PotionMix potionMix) {
+        this(CraftItemStack.asNMSCopy(potionMix.getResult()), CraftRecipe.toIngredient(potionMix.getInput(), true), CraftRecipe.toIngredient(potionMix.getIngredient(), true));
+    }
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index cd1edd0cb404cab5e71efdb82f3b911c2fe96d01..dca520534feac302b8e0f389ee9286bd719e31a6 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -2033,6 +2033,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
             this.worldData.setDataPackConfig(MinecraftServer.getSelectedPacks(this.packRepository));
             this.resources.managers.updateRegistryTags(this.registryAccess());
             io.papermc.paper.registry.PaperRegistry.clearCaches(); // Paper
+            net.minecraft.world.item.alchemy.PotionBrewing.reload(); // Paper
             new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper
             // Paper start
             if (Thread.currentThread() != this.serverThread) {
diff --git a/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java b/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java
index a809e8a903f98fcbcea3184fac6dfd04adc735f9..13720c2df8ac37d020d4a785e48c45877edf74b9 100644
--- a/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java
@@ -168,7 +168,7 @@ public class BrewingStandMenu extends AbstractContainerMenu {
         }
 
         public static boolean mayPlaceItem(ItemStack stack) {
-            return stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE);
+            return stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || PotionBrewing.isCustomInput(stack); // Paper
         }
     }
 
diff --git a/src/main/java/net/minecraft/world/item/alchemy/PotionBrewing.java b/src/main/java/net/minecraft/world/item/alchemy/PotionBrewing.java
index eee96f87f68d9146b1d19d5923d073fb5c39d507..5d86895f93cadcf0b123263c2a6f718fedcb24ac 100644
--- a/src/main/java/net/minecraft/world/item/alchemy/PotionBrewing.java
+++ b/src/main/java/net/minecraft/world/item/alchemy/PotionBrewing.java
@@ -14,6 +14,7 @@ public class PotionBrewing {
     public static final int BREWING_TIME_SECONDS = 20;
     private static final List<PotionBrewing.Mix<Potion>> POTION_MIXES = Lists.newArrayList();
     private static final List<PotionBrewing.Mix<Item>> CONTAINER_MIXES = Lists.newArrayList();
+    private static final it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<org.bukkit.NamespacedKey, io.papermc.paper.potion.PaperPotionMix> CUSTOM_MIXES = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // Paper
     private static final List<Ingredient> ALLOWED_CONTAINERS = Lists.newArrayList();
     private static final Predicate<ItemStack> ALLOWED_CONTAINER = (stack) -> {
         for(Ingredient ingredient : ALLOWED_CONTAINERS) {
@@ -26,7 +27,7 @@ public class PotionBrewing {
     };
 
     public static boolean isIngredient(ItemStack stack) {
-        return isContainerIngredient(stack) || isPotionIngredient(stack);
+        return isContainerIngredient(stack) || isPotionIngredient(stack) || isCustomIngredient(stack); // Paper
     }
 
     protected static boolean isContainerIngredient(ItemStack stack) {
@@ -66,6 +67,11 @@ public class PotionBrewing {
     }
 
     public static boolean hasMix(ItemStack input, ItemStack ingredient) {
+        // Paper start
+        if (hasCustomMix(input, ingredient)) {
+            return true;
+        }
+        // Paper end
         if (!ALLOWED_CONTAINER.test(input)) {
             return false;
         } else {
@@ -103,6 +109,13 @@ public class PotionBrewing {
 
     public static ItemStack mix(ItemStack ingredient, ItemStack input) {
         if (!input.isEmpty()) {
+            // Paper start
+            for (var mix : CUSTOM_MIXES.values()) {
+                if (mix.input().test(input) && mix.ingredient().test(ingredient)) {
+                    return mix.result().copy();
+                }
+            }
+            // Paper end
             Potion potion = PotionUtils.getPotion(input);
             Item item = input.getItem();
             int i = 0;
@@ -127,6 +140,54 @@ public class PotionBrewing {
         return input;
     }
 
+    // Paper start
+    public static boolean isCustomIngredient(ItemStack stack) {
+        for (var mix : CUSTOM_MIXES.values()) {
+            if (mix.ingredient().test(stack)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isCustomInput(ItemStack stack) {
+        for (var mix : CUSTOM_MIXES.values()) {
+            if (mix.input().test(stack)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasCustomMix(ItemStack input, ItemStack ingredient) {
+        for (var mix : CUSTOM_MIXES.values()) {
+            if (mix.input().test(input) && mix.ingredient().test(ingredient)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static void addPotionMix(io.papermc.paper.potion.PotionMix mix) {
+        if (CUSTOM_MIXES.containsKey(mix.getKey())) {
+            throw new IllegalArgumentException("Duplicate recipe ignored with ID " + mix.getKey());
+        }
+        CUSTOM_MIXES.putAndMoveToFirst(mix.getKey(), new io.papermc.paper.potion.PaperPotionMix(mix));
+    }
+
+    public static boolean removePotionMix(org.bukkit.NamespacedKey key) {
+        return CUSTOM_MIXES.remove(key) != null;
+    }
+
+    public static void reload() {
+        POTION_MIXES.clear();
+        CONTAINER_MIXES.clear();
+        ALLOWED_CONTAINERS.clear();
+        CUSTOM_MIXES.clear();
+        bootStrap();
+    }
+    // Paper end
+
     public static void bootStrap() {
         addContainer(Items.POTION);
         addContainer(Items.SPLASH_POTION);
diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
index 3d688e334c7287f41460bd866bfd1155e8bb55d2..55006724ccec9f3de828ec18693728e9741ff65f 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
@@ -335,7 +335,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements
 
     @Override
     public boolean canPlaceItem(int slot, ItemStack stack) {
-        return slot == 3 ? PotionBrewing.isIngredient(stack) : (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 == 3 ? PotionBrewing.isIngredient(stack) : (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) || PotionBrewing.isCustomInput(stack)) && this.getItem(slot).isEmpty()); // Paper
     }
 
     @Override
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 8d1992ba6446c2c5a3b4e2410603d98110207d5f..91ba3684c73bb127d67c6385bf55e71d96f69075 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -283,6 +283,7 @@ public final class CraftServer implements Server {
     private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper
     public static Exception excessiveVelEx; // Paper - Velocity warnings
     private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper
+    private final CraftPotionBrewer potionBrewer = new CraftPotionBrewer(); // Paper
 
     static {
         ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
@@ -309,7 +310,7 @@ public final class CraftServer implements Server {
         Enchantments.SHARPNESS.getClass();
         org.bukkit.enchantments.Enchantment.stopAcceptingRegistrations();
 
-        Potion.setPotionBrewer(new CraftPotionBrewer());
+        Potion.setPotionBrewer(potionBrewer); // Paper
         MobEffects.BLINDNESS.getClass();
         PotionEffectType.stopAcceptingRegistrations();
         // Ugly hack :(
@@ -2889,5 +2890,10 @@ public final class CraftServer implements Server {
         return datapackManager;
     }
 
+    @Override
+    public CraftPotionBrewer getPotionBrewer() {
+        return this.potionBrewer;
+    }
+
     // Paper end
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
index 10ace7bb17c36bfefc584a6322841ab6ea4c866f..71486c08db28caf89f2366e082f6f6fab5609b71 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
@@ -13,6 +13,11 @@ public interface CraftRecipe extends Recipe {
     void addToCraftingManager();
 
     default Ingredient toNMS(RecipeChoice bukkit, boolean requireNotEmpty) {
+        // Paper start
+        return toIngredient(bukkit, requireNotEmpty);
+    }
+    static Ingredient toIngredient(RecipeChoice bukkit, boolean requireNotEmpty) {
+        // Paper end
         Ingredient stack;
 
         if (bukkit == null) {
diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java
index 8fdc9a3bb2f1b6bdc6c2c96f8ade7e9cd88ea4e0..a0b0c64b819b8f713eeea78210e276664e30e66e 100644
--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java
+++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java
@@ -49,4 +49,21 @@ public class CraftPotionBrewer implements PotionBrewer {
     public PotionEffect createEffect(PotionEffectType potion, int duration, int amplifier) {
         return new PotionEffect(potion, potion.isInstant() ? 1 : (int) (duration * potion.getDurationModifier()), amplifier);
     }
+
+    // Paper start
+    @Override
+    public void addPotionMix(io.papermc.paper.potion.PotionMix potionMix) {
+        net.minecraft.world.item.alchemy.PotionBrewing.addPotionMix(potionMix);
+    }
+
+    @Override
+    public void removePotionMix(org.bukkit.NamespacedKey key) {
+        net.minecraft.world.item.alchemy.PotionBrewing.removePotionMix(key);
+    }
+
+    @Override
+    public void resetPotionMixes() {
+        net.minecraft.world.item.alchemy.PotionBrewing.reload();
+    }
+    // Paper end
 }