From 31358d5e48a72e468d24a0e5d1d3ea463e934253 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 5 Aug 2023 18:11:22 -0700
Subject: [PATCH] API for updating recipes on clients (#6463)

---
 ...-API-for-updating-recipes-on-clients.patch | 169 ++++++++++++++++++
 ...-API-for-updating-recipes-on-clients.patch | 114 ++++++++++++
 2 files changed, 283 insertions(+)
 create mode 100644 patches/api/0422-API-for-updating-recipes-on-clients.patch
 create mode 100644 patches/server/0995-API-for-updating-recipes-on-clients.patch

diff --git a/patches/api/0422-API-for-updating-recipes-on-clients.patch b/patches/api/0422-API-for-updating-recipes-on-clients.patch
new file mode 100644
index 0000000000..2db8356bc3
--- /dev/null
+++ b/patches/api/0422-API-for-updating-recipes-on-clients.patch
@@ -0,0 +1,169 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
+Date: Sat, 21 Aug 2021 17:25:54 -0700
+Subject: [PATCH] API for updating recipes on clients
+
+
+diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
+index b243db56756c67cd2c41d7768898d01539f9260a..f380a518bc444bfdfbbedf38805c7684e53a5629 100644
+--- a/src/main/java/org/bukkit/Bukkit.java
++++ b/src/main/java/org/bukkit/Bukkit.java
+@@ -959,6 +959,26 @@ public final class Bukkit {
+         server.reloadData();
+     }
+ 
++    // Paper start - update reloadable data
++    /**
++     * Updates all advancement, tag, and recipe data for all connected clients.
++     * Useful for updating clients to new advancements/recipes/tags.
++     * @see #updateRecipes()
++     */
++    public static void updateResources() {
++        server.updateResources();
++    }
++
++    /**
++     * Updates recipe data and the recipe book for all connected clients. Useful for
++     * updating clients to new recipes.
++     * @see #updateResources()
++     */
++    public static void updateRecipes() {
++        server.updateRecipes();
++    }
++    // Paper end - update reloadable data
++
+     /**
+      * Returns the primary logger associated with this server instance.
+      *
+@@ -1013,6 +1033,20 @@ public final class Bukkit {
+         return server.addRecipe(recipe);
+     }
+ 
++    // Paper start - method to send recipes immediately
++    /**
++     * Adds a recipe to the crafting manager.
++     *
++     * @param recipe the recipe to add
++     * @param resendRecipes true to update the client with the full set of recipes
++     * @return true if the recipe was added, false if it wasn't for some reason
++     */
++    @Contract("null, _ -> false")
++    public static boolean addRecipe(@Nullable Recipe recipe, boolean resendRecipes) {
++        return server.addRecipe(recipe, resendRecipes);
++    }
++    // Paper end - method to send recipes immediately
++
+     /**
+      * Get a list of all recipes for a given item. The stack size is ignored
+      * in comparisons. If the durability is -1, it will match any data value.
+@@ -1129,6 +1163,24 @@ public final class Bukkit {
+         return server.removeRecipe(key);
+     }
+ 
++    // Paper start - method to resend recipes
++    /**
++     * Remove a recipe from the server.
++     * <p>
++     * <b>Note that removing a recipe may cause permanent loss of data
++     * associated with that recipe (eg whether it has been discovered by
++     * players).</b>
++     *
++     * @param key NamespacedKey of recipe to remove.
++     * @param resendRecipes true to update all clients on the new recipe list.
++     *                      Will only update if a recipe was actually removed
++     * @return True if recipe was removed
++     */
++    public static boolean removeRecipe(@NotNull NamespacedKey key, boolean resendRecipes) {
++        return server.removeRecipe(key, resendRecipes);
++    }
++    // Paper end - method to resend recipes
++
+     /**
+      * Gets a list of command aliases defined in the server properties.
+      *
+diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
+index 6b72eccdcb6f75534a4267a1dd0a4cc2f39e917b..68206cf0178c26c0f528a1e14a5fb4e9ad410369 100644
+--- a/src/main/java/org/bukkit/Server.java
++++ b/src/main/java/org/bukkit/Server.java
+@@ -811,6 +811,22 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+      */
+     public void reloadData();
+ 
++    // Paper start - update reloadable data
++    /**
++     * Updates all advancement, tag, and recipe data to all connected clients.
++     * Useful for updating clients to new advancements/recipes/tags.
++     * @see #updateRecipes()
++     */
++    void updateResources();
++
++    /**
++     * Updates recipe data and the recipe book to each player. Useful for
++     * updating clients to new recipes.
++     * @see #updateResources()
++     */
++    void updateRecipes();
++    // Paper end - update reloadable data
++
+     /**
+      * Returns the primary logger associated with this server instance.
+      *
+@@ -846,15 +862,34 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+     public boolean dispatchCommand(@NotNull CommandSender sender, @NotNull String commandLine) throws CommandException;
+ 
+     /**
+-     * Adds a recipe to the crafting manager.
++     * Adds a recipe to the crafting manager. Recipes added with
++     * this method won't be sent to the client automatically. Use
++     * {@link #updateRecipes()} or {@link #updateResources()} to
++     * update clients to new recipes added.
++     * <p>
++     * Player's still have to discover recipes via {@link Player#discoverRecipe(NamespacedKey)}
++     * before seeing them in their recipe book.
+      *
+      * @param recipe the recipe to add
+      * @return true if the recipe was added, false if it wasn't for some
+      *     reason
++     * @see #addRecipe(Recipe, boolean)
+      */
+     @Contract("null -> false")
+     public boolean addRecipe(@Nullable Recipe recipe);
+ 
++    // Paper start - method to send recipes immediately
++    /**
++     * Adds a recipe to the crafting manager.
++     *
++     * @param recipe the recipe to add
++     * @param resendRecipes true to update the client with the full set of recipes
++     * @return true if the recipe was added, false if it wasn't for some reason
++     */
++    @Contract("null, _ -> false")
++    boolean addRecipe(@Nullable Recipe recipe, boolean resendRecipes);
++    // Paper end - method to send recipes immediately
++
+     /**
+      * Get a list of all recipes for a given item. The stack size is ignored
+      * in comparisons. If the durability is -1, it will match any data value.
+@@ -955,6 +990,22 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+      */
+     public boolean removeRecipe(@NotNull NamespacedKey key);
+ 
++    // Paper start - method to resend recipes
++    /**
++     * Remove a recipe from the server.
++     * <p>
++     * <b>Note that removing a recipe may cause permanent loss of data
++     * associated with that recipe (eg whether it has been discovered by
++     * players).</b>
++     *
++     * @param key NamespacedKey of recipe to remove.
++     * @param resendRecipes true to update all clients on the new recipe list.
++     *                      Will only update if a recipe was actually removed
++     * @return True if recipe was removed
++     */
++    boolean removeRecipe(@NotNull NamespacedKey key, boolean resendRecipes);
++    // Paper end - method to resend recipes
++
+     /**
+      * Gets a list of command aliases defined in the server properties.
+      *
diff --git a/patches/server/0995-API-for-updating-recipes-on-clients.patch b/patches/server/0995-API-for-updating-recipes-on-clients.patch
new file mode 100644
index 0000000000..97aae77fcc
--- /dev/null
+++ b/patches/server/0995-API-for-updating-recipes-on-clients.patch
@@ -0,0 +1,114 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
+Date: Sat, 21 Aug 2021 17:25:38 -0700
+Subject: [PATCH] API for updating recipes on clients
+
+
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index fff7ad7a45f310783ac96b44575ad3db13d537fa..640e9bd618dc8286933318744c2064ede1fd9b5f 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -1524,6 +1524,13 @@ public abstract class PlayerList {
+     }
+ 
+     public void reloadResources() {
++        // Paper start - split this method up into separate methods
++        this.reloadAdvancementData();
++        this.reloadTagData();
++        this.reloadRecipeData();
++    }
++    public void reloadAdvancementData() {
++        // Paper end
+         // CraftBukkit start
+         /*Iterator iterator = this.advancements.values().iterator();
+ 
+@@ -1539,7 +1546,15 @@ public abstract class PlayerList {
+         }
+         // CraftBukkit end
+ 
++        // Paper start
++    }
++    public void reloadTagData() {
++        // Paper end
+         this.broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries)));
++        // Paper start
++    }
++    public void reloadRecipeData() {
++        // Paper end
+         ClientboundUpdateRecipesPacket packetplayoutrecipeupdate = new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes());
+         Iterator iterator1 = this.players.iterator();
+ 
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index d62cdda5ef3691a54ce34729920bad8e16c7a883..54f27d91f941235a99e341ed84531ad7f0840728 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -1149,6 +1149,18 @@ public final class CraftServer implements Server {
+         ReloadCommand.reload(console);
+     }
+ 
++    // Paper start
++    @Override
++    public void updateResources() {
++        this.playerList.reloadResources();
++    }
++
++    @Override
++    public void updateRecipes() {
++        this.playerList.reloadRecipeData();
++    }
++    // Paper end
++
+     private void loadIcon() {
+         this.icon = new CraftIconCache(null);
+         try {
+@@ -1491,6 +1503,13 @@ public final class CraftServer implements Server {
+ 
+     @Override
+     public boolean addRecipe(Recipe recipe) {
++        // Paper start
++        return this.addRecipe(recipe, false);
++    }
++
++    @Override
++    public boolean addRecipe(Recipe recipe, boolean resendRecipes) {
++        // Paper end
+         CraftRecipe toAdd;
+         if (recipe instanceof CraftRecipe) {
+             toAdd = (CraftRecipe) recipe;
+@@ -1520,6 +1539,11 @@ public final class CraftServer implements Server {
+             }
+         }
+         toAdd.addToCraftingManager();
++        // Paper start
++        if (resendRecipes) {
++            this.playerList.reloadRecipeData();
++        }
++        // Paper end
+         return true;
+     }
+ 
+@@ -1639,10 +1663,23 @@ public final class CraftServer implements Server {
+ 
+     @Override
+     public boolean removeRecipe(NamespacedKey recipeKey) {
++        // Paper start
++        return this.removeRecipe(recipeKey, false);
++    }
++
++    @Override
++    public boolean removeRecipe(NamespacedKey recipeKey, boolean resendRecipes) {
++        // Paper end
+         Preconditions.checkArgument(recipeKey != null, "recipeKey == null");
+ 
+         ResourceLocation mcKey = CraftNamespacedKey.toMinecraft(recipeKey);
+-        return this.getServer().getRecipeManager().removeRecipe(mcKey);
++        // Paper start - resend recipes on successful removal
++        boolean removed = this.getServer().getRecipeManager().removeRecipe(mcKey);
++        if (removed && resendRecipes) {
++            this.playerList.reloadRecipeData();
++        }
++        return removed;
++        // Paper end
+     }
+ 
+     @Override