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..7ea357ac2f3a93db4ebdf24b5072be7d1cad3e33
--- /dev/null
+++ b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java
@@ -0,0 +1,21 @@
+package io.papermc.paper.potion;
+
+import java.util.function.Predicate;
+import net.minecraft.world.item.ItemStack;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.craftbukkit.inventory.CraftRecipe;
+import org.bukkit.inventory.RecipeChoice;
+
+public record PaperPotionMix(ItemStack result, Predicate<ItemStack> input, Predicate<ItemStack> ingredient) {
+
+    public PaperPotionMix(PotionMix potionMix) {
+        this(CraftItemStack.asNMSCopy(potionMix.getResult()), convert(potionMix.getInput()), convert(potionMix.getIngredient()));
+    }
+
+    static Predicate<ItemStack> convert(final RecipeChoice choice) {
+        if (choice instanceof PredicateRecipeChoice predicateRecipeChoice) {
+            return stack -> predicateRecipeChoice.test(CraftItemStack.asBukkitCopy(stack));
+        }
+        return CraftRecipe.toIngredient(choice, true);
+    }
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index ea442883eda28e5673cef9470145d5c40ac66159..79f7e0fb2f1aa6af441c6e09d2c443d7d4bb47ef 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -2141,6 +2141,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
 
             this.worldData.setDataConfiguration(worlddataconfiguration);
             this.resources.managers.updateRegistryTags();
+            this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
             this.getPlayerList().saveAll();
             this.getPlayerList().reloadResources();
             this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
diff --git a/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java b/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java
index 8be4e04df480faaa3aac516012709e9ff852f8a5..5dceffcb6883db128ea41561ed8a8ba41eb275ef 100644
--- a/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java
@@ -173,7 +173,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 - Custom Potion Mixes
         }
     }
 
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 50e81e3babd331077eda8daa769eb2b3f99e8ca2..45b1a0346c7b0a541b336e2a89f825bf58d993f8 100644
--- a/src/main/java/net/minecraft/world/item/alchemy/PotionBrewing.java
+++ b/src/main/java/net/minecraft/world/item/alchemy/PotionBrewing.java
@@ -19,6 +19,7 @@ public class PotionBrewing {
     private final List<Ingredient> containers;
     private final List<PotionBrewing.Mix<Potion>> potionMixes;
     private final List<PotionBrewing.Mix<Item>> containerMixes;
+    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 - Custom Potion Mixes
 
     PotionBrewing(List<Ingredient> potionTypes, List<PotionBrewing.Mix<Potion>> potionRecipes, List<PotionBrewing.Mix<Item>> itemRecipes) {
         this.containers = potionTypes;
@@ -27,7 +28,7 @@ public class PotionBrewing {
     }
 
     public boolean isIngredient(ItemStack stack) {
-        return this.isContainerIngredient(stack) || this.isPotionIngredient(stack);
+        return this.isContainerIngredient(stack) || this.isPotionIngredient(stack) || isCustomIngredient(stack); // Paper - Custom Potion Mixes
     }
 
     private boolean isContainer(ItemStack stack) {
@@ -71,6 +72,11 @@ public class PotionBrewing {
     }
 
     public boolean hasMix(ItemStack input, ItemStack ingredient) {
+        // Paper start - Custom Potion Mixes
+        if (hasCustomMix(input, ingredient)) {
+            return true;
+        }
+        // Paper end - Custom Potion Mixes
         return this.isContainer(input) && (this.hasContainerMix(input, ingredient) || this.hasPotionMix(input, ingredient));
     }
 
@@ -107,6 +113,13 @@ public class PotionBrewing {
             if (optional.isEmpty()) {
                 return input;
             } else {
+                // Paper start - Custom Potion Mixes
+                for (var mix : CUSTOM_MIXES.values()) {
+                    if (mix.input().test(input) && mix.ingredient().test(ingredient)) {
+                        return mix.result().copy();
+                    }
+                }
+                // Paper end - Custom Potion Mixes
                 for (PotionBrewing.Mix<Item> mix : this.containerMixes) {
                     if (input.is(mix.from) && mix.ingredient.test(ingredient)) {
                         return PotionContents.createItemStack(mix.to.value(), optional.get());
@@ -190,6 +203,54 @@ public class PotionBrewing {
         builder.addMix(Potions.SLOW_FALLING, Items.REDSTONE, Potions.LONG_SLOW_FALLING);
     }
 
+    // Paper start - Custom Potion Mixes
+    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 void reload(FeatureFlagSet flags) {
+        potionMixes.clear();
+        containerMixes.clear();
+        containers.clear();
+        CUSTOM_MIXES.clear();
+        bootstrap(flags);
+    }
+    // Paper end - Custom Potion Mixes
+
     public static class Builder {
         private final List<Ingredient> containers = new ArrayList<>();
         private final List<PotionBrewing.Mix<Potion>> potionMixes = new ArrayList<>();
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 3ebfd564d4bbf00da5919e966f3d047285845640..c1254088fc65fe46101c82cf2629cf0269711d39 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
@@ -316,7 +316,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements
 
             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) || PotionBrewing.isCustomInput(stack)) && this.getItem(slot).isEmpty(); // Paper - Custom Potion Mixes
         }
     }
 
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index e25dc948bfd59b8d0a9350c0ffe496f53d042ab8..e1903d551e2183cc92dfb4fc832ec5bf961475d1 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -308,6 +308,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 org.bukkit.craftbukkit.potion.CraftPotionBrewer potionBrewer; // Paper - Custom Potion Mixes
 
     static {
         ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
@@ -389,6 +390,7 @@ public final class CraftServer implements Server {
         if (this.configuration.getBoolean("settings.use-map-color-cache")) {
             MapPalette.setMapColorCache(new CraftMapColorCache(this.logger));
         }
+        this.potionBrewer = new org.bukkit.craftbukkit.potion.CraftPotionBrewer(playerList.getServer()); // Paper - custom potion mixes
         datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper
     }
 
@@ -3100,5 +3102,10 @@ public final class CraftServer implements Server {
         return datapackManager;
     }
 
+    @Override
+    public org.bukkit.craftbukkit.potion.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 139dff90561ac6c51954c6289918a07aeea13a1b..6ba29875d78ede4aa7978ff689e588f7fed11528 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
@@ -15,6 +15,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
new file mode 100644
index 0000000000000000000000000000000000000000..5c08be880b81ce8627e7679942f273a948bed934
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java
@@ -0,0 +1,66 @@
+package org.bukkit.craftbukkit.potion;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.bukkit.potion.PotionBrewer;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+import org.bukkit.potion.PotionType;
+
+public class CraftPotionBrewer implements PotionBrewer {
+
+    private final net.minecraft.server.MinecraftServer minecraftServer;
+
+    public CraftPotionBrewer(net.minecraft.server.MinecraftServer minecraftServer) {
+        this.minecraftServer = minecraftServer;
+    }
+
+    // Paper start - keep old spigot methods, removal in 1.20.6
+    @Override
+    public Collection<PotionEffect> getEffects(PotionType type, boolean upgraded, boolean extended) {
+        final org.bukkit.NamespacedKey key = type.getKey();
+
+        Preconditions.checkArgument(!key.getKey().startsWith("strong_"), "Strong potion type cannot be used directly, got %s", key);
+        Preconditions.checkArgument(!key.getKey().startsWith("long_"), "Extended potion type cannot be used directly, got %s", key);
+
+        org.bukkit.NamespacedKey effectiveKey = key;
+        if (upgraded) {
+            effectiveKey = new org.bukkit.NamespacedKey(key.namespace(), "strong_" + key.key());
+        } else if (extended) {
+            effectiveKey = new org.bukkit.NamespacedKey(key.namespace(), "long_" + key.key());
+        }
+
+        final org.bukkit.potion.PotionType effectivePotionType = org.bukkit.Registry.POTION.get(effectiveKey);
+        Preconditions.checkNotNull(type, "Unknown potion type from data " + effectiveKey.asMinimalString()); // Legacy error message in 1.20.4
+        return effectivePotionType.getPotionEffects();
+    }
+
+    @Override
+    public Collection<PotionEffect> getEffectsFromDamage(int damage) {
+        return new ArrayList<>();
+    }
+
+    @Override
+    public PotionEffect createEffect(PotionEffectType potion, int duration, int amplifier) {
+        return new PotionEffect(potion, potion.isInstant() ? 1 : duration, amplifier);
+    }
+    // Paper end - keep old spigot methods, removal in 1.20.6
+
+    // 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() {
+        this.minecraftServer.potionBrewing().reload(this.minecraftServer.getWorldData().enabledFeatures());
+    }
+    // Paper end
+}