From 5d843f31208d49de91f715ed36614540480534eb Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sun, 12 May 2024 15:49:36 -0700 Subject: [PATCH] Fix issues with Recipe API --- .../item/crafting/ShapedRecipe.java.patch | 2 +- .../craftbukkit/inventory/CraftRecipe.java | 13 +++++++--- .../inventory/recipe/TestRecipeChoice.java | 25 +++++++++++++++++++ .../org/bukkit/support/DummyServerHelper.java | 9 +++++++ 4 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 paper-server/src/test/java/io/papermc/paper/inventory/recipe/TestRecipeChoice.java diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapedRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapedRecipe.java.patch index bbd6d81b4b..05fee5cb09 100644 --- a/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapedRecipe.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapedRecipe.java.patch @@ -70,7 +70,7 @@ + char c = 'a'; + for (Optional list : this.pattern.ingredients()) { + RecipeChoice choice = CraftRecipe.toBukkit(list); -+ if (choice != null) { ++ if (choice != RecipeChoice.empty()) { // Paper + recipe.setIngredient(c, choice); + } + diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java index 5b176cd5f8..4864e2016c 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java @@ -19,7 +19,7 @@ public interface CraftRecipe extends Recipe { void addToCraftingManager(); default Optional toNMSOptional(RecipeChoice bukkit, boolean requireNotEmpty) { - return (bukkit == null) ? Optional.empty() : Optional.of(this.toNMS(bukkit, requireNotEmpty)); + return (bukkit == null || bukkit == RecipeChoice.empty()) ? Optional.empty() : Optional.of(this.toNMS(bukkit, requireNotEmpty)); // Paper - support "empty" choices } default Ingredient toNMS(RecipeChoice bukkit, boolean requireNotEmpty) { @@ -36,6 +36,13 @@ public interface CraftRecipe extends Recipe { stack = Ingredient.of(((RecipeChoice.MaterialChoice) bukkit).getChoices().stream().map((mat) -> CraftItemType.bukkitToMinecraft(mat))); } else if (bukkit instanceof RecipeChoice.ExactChoice) { stack = Ingredient.ofStacks(((RecipeChoice.ExactChoice) bukkit).getChoices().stream().map((mat) -> CraftItemStack.asNMSCopy(mat)).toList()); + // Paper start - support "empty" choices - legacy method that spigot might incorrectly call + // Their impl of Ingredient.of() will error, ingredients need at least one entry. + // Callers running into this exception may have passed an incorrect empty() recipe choice to a non-empty slot or + // spigot calls this method in a wrong place. + } else if (bukkit == RecipeChoice.empty()) { + throw new IllegalArgumentException("This ingredient cannot be empty"); + // Paper end - support "empty" choices } else { throw new IllegalArgumentException("Unknown recipe stack instance " + bukkit); } @@ -48,12 +55,12 @@ public interface CraftRecipe extends Recipe { } public static RecipeChoice toBukkit(Optional list) { - return list.map(CraftRecipe::toBukkit).orElse(null); + return list.map(CraftRecipe::toBukkit).orElse(RecipeChoice.empty()); // Paper - fix issue with recipe API } public static RecipeChoice toBukkit(Ingredient list) { if (list.isEmpty()) { - return null; + return RecipeChoice.empty(); // Paper - null breaks API contracts } if (list.isExact()) { diff --git a/paper-server/src/test/java/io/papermc/paper/inventory/recipe/TestRecipeChoice.java b/paper-server/src/test/java/io/papermc/paper/inventory/recipe/TestRecipeChoice.java new file mode 100644 index 0000000000..45ab2b6d32 --- /dev/null +++ b/paper-server/src/test/java/io/papermc/paper/inventory/recipe/TestRecipeChoice.java @@ -0,0 +1,25 @@ +package io.papermc.paper.inventory.recipe; + +import java.util.Iterator; +import org.bukkit.Bukkit; +import org.bukkit.inventory.Recipe; +import org.bukkit.support.environment.AllFeatures; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@AllFeatures +class TestRecipeChoice { + + @Test + void testRecipeChoices() { + final Iterator iter = Bukkit.recipeIterator(); + boolean foundRecipes = false; + while (iter.hasNext()) { + foundRecipes = true; + assertDoesNotThrow(iter::next, "Failed to convert a recipe to Bukkit recipe!"); + } + assertTrue(foundRecipes, "No recipes found!"); + } +} diff --git a/paper-server/src/test/java/org/bukkit/support/DummyServerHelper.java b/paper-server/src/test/java/org/bukkit/support/DummyServerHelper.java index 55c05c16a8..309d371247 100644 --- a/paper-server/src/test/java/org/bukkit/support/DummyServerHelper.java +++ b/paper-server/src/test/java/org/bukkit/support/DummyServerHelper.java @@ -92,6 +92,15 @@ public final class DummyServerHelper { // Paper end - testing additions io.papermc.paper.configuration.GlobalConfigTestingBase.setupGlobalConfigForTest(); // Paper - configuration files - setup global configuration test base + + // Paper start - add test for recipe conversion + when(instance.recipeIterator()).thenAnswer(ignored -> + com.google.common.collect.Iterators.transform( + RegistryHelper.getDataPack().getRecipeManager().recipes.byType.entries().iterator(), + input -> input.getValue().toBukkitRecipe() + ) + ); + // Paper end - add test for recipe conversion return instance; } }