From bfa50ad5b346ac7a5520084348a9618a20fb8925 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Tue, 22 Feb 2022 19:09:15 -0800 Subject: [PATCH] Custom Potion Mixes (#6744) --- patches/api/0371-Custom-Potion-Mixes.patch | 170 +++++++++++++ ...8-Replace-player-chunk-loader-system.patch | 2 +- ...fault-CustomSpawners-in-custom-worl.patch} | 2 +- ...-worldlist-before-initing-the-world.patch} | 4 +- ... => 0876-Fix-Entity-Position-Desync.patch} | 0 patches/server/0877-Custom-Potion-Mixes.patch | 239 ++++++++++++++++++ 6 files changed, 413 insertions(+), 4 deletions(-) create mode 100644 patches/api/0371-Custom-Potion-Mixes.patch rename patches/server/{0875-Option-to-have-default-CustomSpawners-in-custom-worl.patch => 0874-Option-to-have-default-CustomSpawners-in-custom-worl.patch} (97%) rename patches/server/{0874-Put-world-into-worldlist-before-initing-the-world.patch => 0875-Put-world-into-worldlist-before-initing-the-world.patch} (92%) rename patches/server/{0866-Fix-Entity-Position-Desync.patch => 0876-Fix-Entity-Position-Desync.patch} (100%) create mode 100644 patches/server/0877-Custom-Potion-Mixes.patch diff --git a/patches/api/0371-Custom-Potion-Mixes.patch b/patches/api/0371-Custom-Potion-Mixes.patch new file mode 100644 index 0000000000..9ec547303b --- /dev/null +++ b/patches/api/0371-Custom-Potion-Mixes.patch @@ -0,0 +1,170 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 7 Oct 2021 14:34:59 -0700 +Subject: [PATCH] Custom Potion Mixes + + +diff --git a/src/main/java/io/papermc/paper/potion/PotionMix.java b/src/main/java/io/papermc/paper/potion/PotionMix.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cb6d93526b637946aec311bef103ad3096781113 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/potion/PotionMix.java +@@ -0,0 +1,91 @@ ++package io.papermc.paper.potion; ++ ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++import org.bukkit.inventory.ItemStack; ++import org.bukkit.inventory.RecipeChoice; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.Objects; ++ ++/** ++ * Represents a potion mix made in a Brewing Stand. ++ */ ++@ApiStatus.NonExtendable ++public class PotionMix implements Keyed { ++ ++ private final NamespacedKey key; ++ private final ItemStack result; ++ private final RecipeChoice input; ++ private final RecipeChoice ingredient; ++ ++ /** ++ * Creates a new potion mix. Add it to the server with {@link org.bukkit.potion.PotionBrewer#addPotionMix(PotionMix)}. ++ * ++ * @param key a unique key for the mix ++ * @param result the resulting itemstack that will appear in the 3 bottom slots ++ * @param input the input placed into the bottom 3 slots ++ * @param ingredient the ingredient placed into the top slot ++ */ ++ public PotionMix(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull RecipeChoice input, @NotNull RecipeChoice ingredient) { ++ this.key = key; ++ this.result = result; ++ this.input = input; ++ this.ingredient = ingredient; ++ } ++ ++ @Override ++ public @NotNull NamespacedKey getKey() { ++ return this.key; ++ } ++ ++ /** ++ * Gets the resulting itemstack after the brew has finished. ++ * ++ * @return the result itemstack ++ */ ++ public @NotNull ItemStack getResult() { ++ return this.result; ++ } ++ ++ /** ++ * Gets the input for the bottom 3 slots in the brewing stand. ++ * ++ * @return the bottom 3 slot ingredients ++ */ ++ public @NotNull RecipeChoice getInput() { ++ return this.input; ++ } ++ ++ /** ++ * Gets the ingredient in the top slot of the brewing stand. ++ * ++ * @return the top slot input ++ */ ++ public @NotNull RecipeChoice getIngredient() { ++ return this.ingredient; ++ } ++ ++ @Override ++ public String toString() { ++ return "PotionMix{" + ++ "result=" + this.result + ++ ", base=" + this.input + ++ ", addition=" + this.ingredient + ++ '}'; ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ PotionMix potionMix = (PotionMix) o; ++ return this.key.equals(potionMix.key) && this.result.equals(potionMix.result) && this.input.equals(potionMix.input) && this.ingredient.equals(potionMix.ingredient); ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hash(this.key, this.result, this.input, this.ingredient); ++ } ++} +diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java +index 795f8c0aa3929f6de4b4ea4b139bef8b672ab97a..944f9b87a11472ac6d7e328acc00bf09f899e648 100644 +--- a/src/main/java/org/bukkit/Bukkit.java ++++ b/src/main/java/org/bukkit/Bukkit.java +@@ -2297,6 +2297,15 @@ public final class Bukkit { + public static io.papermc.paper.datapack.DatapackManager getDatapackManager() { + return server.getDatapackManager(); + } ++ ++ /** ++ * Gets the potion brewer. ++ * ++ * @return the potion brewer ++ */ ++ public static @NotNull org.bukkit.potion.PotionBrewer getPotionBrewer() { ++ return server.getPotionBrewer(); ++ } + // Paper end + + @NotNull +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index a62c27777672eff1c488517b37876e3a44a2d57d..cca362e54d6ff4a5a1e60f85a7eb1b3d222d3d48 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -1995,5 +1995,12 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi + */ + @NotNull + io.papermc.paper.datapack.DatapackManager getDatapackManager(); ++ ++ /** ++ * Gets the potion brewer. ++ * ++ * @return the potion brewer ++ */ ++ @NotNull org.bukkit.potion.PotionBrewer getPotionBrewer(); + // Paper end + } +diff --git a/src/main/java/org/bukkit/potion/PotionBrewer.java b/src/main/java/org/bukkit/potion/PotionBrewer.java +index d21f407cc16cfd709c1cabf408e8d8d16aba7e1a..1598f34d306fb34ff7ffe7886b0d6e4abe734b6b 100644 +--- a/src/main/java/org/bukkit/potion/PotionBrewer.java ++++ b/src/main/java/org/bukkit/potion/PotionBrewer.java +@@ -43,4 +43,25 @@ public interface PotionBrewer { + */ + @NotNull + public Collection getEffects(@NotNull PotionType type, boolean upgraded, boolean extended); ++ ++ // Paper start ++ /** ++ * Adds a new potion mix recipe. ++ * ++ * @param potionMix the potion mix to add ++ */ ++ void addPotionMix(@NotNull io.papermc.paper.potion.PotionMix potionMix); ++ ++ /** ++ * Removes a potion mix recipe. ++ * ++ * @param key the key of the mix to remove ++ */ ++ void removePotionMix(@NotNull org.bukkit.NamespacedKey key); ++ ++ /** ++ * Resets potion mixes to their default, removing all custom ones. ++ */ ++ void resetPotionMixes(); ++ // Paper end + } diff --git a/patches/server/0868-Replace-player-chunk-loader-system.patch b/patches/server/0868-Replace-player-chunk-loader-system.patch index 5eab8f07c5..da0839491d 100644 --- a/patches/server/0868-Replace-player-chunk-loader-system.patch +++ b/patches/server/0868-Replace-player-chunk-loader-system.patch @@ -1984,7 +1984,7 @@ index 46ca1a11930c57813fbcbab7de7dd2fd47241f64..2429bdc5fc5150dcadbedf6c33810889 double deltaZ = soundPos.getZ() - player.getZ(); double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 7dda99a5464816f1488fb110da587f12d751b3fb..87c8c59b9d47b6c292a92e97471c558c03453cfb 100644 +index a0fb656be28a73c4d605eab0f7db05d205ebdc96..11e146241a01ab9ec206b9d3f39aebf5c201a16e 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -655,6 +655,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { diff --git a/patches/server/0875-Option-to-have-default-CustomSpawners-in-custom-worl.patch b/patches/server/0874-Option-to-have-default-CustomSpawners-in-custom-worl.patch similarity index 97% rename from patches/server/0875-Option-to-have-default-CustomSpawners-in-custom-worl.patch rename to patches/server/0874-Option-to-have-default-CustomSpawners-in-custom-worl.patch index 6da8332101..ef1fc582d0 100644 --- a/patches/server/0875-Option-to-have-default-CustomSpawners-in-custom-worl.patch +++ b/patches/server/0874-Option-to-have-default-CustomSpawners-in-custom-worl.patch @@ -24,7 +24,7 @@ index 153f07bac06093b43a1f5b0f8e1a46ffbe6407e5..a7ebf6d9f79ce50a90c3c903563e00a1 + } } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 053dbe5eef6574cfe98ab7499181bdd83a81f2e1..6d1d83bc17403346cc9d3143666b927ef55fb9df 100644 +index 1674deebbeab0995ed7acacf8052e1daf4d2a7bc..8014b8a20dfc1f348510eaad6ff42200c9ba4baa 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -689,7 +689,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +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 6d1d83bc17403346cc9d3143666b927ef55fb9df..c847a2b935130a293d9ac4c196c9cff27470649b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2072,6 +2072,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> POTION_MIXES = Lists.newArrayList(); + private static final List> CONTAINER_MIXES = Lists.newArrayList(); ++ private static final it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap CUSTOM_MIXES = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // Paper + private static final List ALLOWED_CONTAINERS = Lists.newArrayList(); + private static final Predicate 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 287205bce7f655f9a6b815f40d349c3db4c1e788..5c0f1488c8a8100cd39a03adeccded9984722249 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 +@@ -336,7 +336,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 caeaf2978d7a8b7f2d1595e102f2751d837172b4..51f67a2944034552d57b939ef29e0249e74383b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -287,6 +287,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); +@@ -313,7 +314,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 :( +@@ -2850,5 +2851,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 + }