Skip to content

Re-register dynamic recipes cleanly, allow for NBT in output#6233

Open
valaphee wants to merge 1 commit intoGeyserMC:masterfrom
valaphee:clean-dynamic-recipes
Open

Re-register dynamic recipes cleanly, allow for NBT in output#6233
valaphee wants to merge 1 commit intoGeyserMC:masterfrom
valaphee:clean-dynamic-recipes

Conversation

@valaphee
Copy link
Copy Markdown
Contributor

Dynamic recipes, recipes which are not registered beforehand through the recipe book, don't show up with NBT, which is a regression to before.

There is also sometimes the issue that recipes are registered incorrectly, or that recipes depend on NBT and there are multiple recipes with the same base item.

ViaVersion might also always add NBT to certain items, which makes the crafting output non stackable.

This PR solves this by always re-registering dynamic recipes and not keeping them.

@valaphee valaphee marked this pull request as ready for review March 13, 2026 08:42
Copilot AI review requested due to automatic review settings March 13, 2026 08:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses regressions with dynamic crafting/smithing recipes not showing correct NBT in Bedrock outputs by re-registering recipes (instead of caching/keeping dynamic ones) and centralizing CraftingDataPacket construction on the session.

Changes:

  • Switch dynamic crafting/smithing output registration to build Bedrock RecipeData directly (supporting NBT outputs).
  • Centralize CraftingDataPacket creation into GeyserSession#getCraftingDataPacket() and reuse it from multiple translators.
  • Refactor stonecutter recipe tracking to also retain the Java input ID.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java Reworks dynamic recipe registration for crafting/smithing outputs to use Bedrock recipe types and session-provided crafting data.
core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java Adjusts recipe update flow and stonecutter caching; now relies on session-wide CraftingDataPacket creation.
core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeBookAddTranslator.java Stops assembling incremental CraftingDataPackets and instead sends the session-wide packet, while still unlocking recipe IDs.
core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaFinishConfigurationTranslator.java Uses session-wide CraftingDataPacket during config->play transition and clears cached recipe state.
core/src/main/java/org/geysermc/geyser/session/GeyserSession.java Introduces getCraftingDataPacket() to rebuild and return a full CraftingDataPacket (including smithing/stonecutter handling).
core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java Moves trim + netherite-upgrade recipe definitions into reusable static data.
core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java Extends stored stonecutter metadata to include the Java input item ID.
Comments suppressed due to low confidence (2)

core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeBookAddTranslator.java:61

  • After removing the local CraftingDataPacket, the imports RecipeData and CraftingDataPacket are no longer used. Unused imports fail Java compilation; remove these imports.
        int netId = session.getLastRecipeNetId().get();
        Int2ObjectMap<List<String>> javaToBedrockRecipeIds = session.getJavaToBedrockRecipeIds();
        Int2ObjectMap<GeyserRecipe> geyserRecipes = session.getCraftingRecipes();

        UnlockedRecipesPacket recipesPacket = new UnlockedRecipesPacket();
        recipesPacket.setAction(packet.isReplace() ? UnlockedRecipesPacket.ActionType.INITIALLY_UNLOCKED : UnlockedRecipesPacket.ActionType.NEWLY_UNLOCKED);

core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeBookAddTranslator.java:78

  • geyserRecipe.asRecipeData(session) is recomputed repeatedly in the loop condition (for (...; i < geyserRecipe.asRecipeData(session).size(); i++)). This can be expensive (it builds recipe combinations) and needlessly allocates lists multiple times. Compute the list (or at least its size) once per recipe and reuse it.
                GeyserRecipe geyserRecipe = new GeyserShapedRecipe(contents.id(), netId, shapedRecipe);

                List<String> bedrockRecipeIds = new ArrayList<>();
                for (int i = 0; i < geyserRecipe.asRecipeData(session).size(); i++) {
                    String recipeId = contents.id() + "_" + i;
                    recipesPacket.getUnlockedRecipes().add(recipeId);
                    bedrockRecipeIds.add(recipeId);
                    geyserRecipes.put(netId++, geyserRecipe);
                }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Copilot AI review requested due to automatic review settings March 13, 2026 09:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to fix regressions with dynamic (non-recipe-book) crafting outputs by rebuilding/sending Bedrock recipe data so outputs with NBT are recognized correctly, and by centralizing CraftingDataPacket construction.

Changes:

  • Centralize CraftingDataPacket construction in GeyserSession#getCraftingDataPacket() and reuse it across translators.
  • Rework dynamic crafting-table and smithing-table output handling to register shaped/smithing recipes using Bedrock recipe data (including NBT in outputs).
  • Adjust stonecutter recipe tracking to include the Java input ID when reconstructing Bedrock stonecutter recipes.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java Initializes global recipe net ID counter used for reserved recipe IDs.
core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java Replaces dynamic recipe registration with direct Bedrock RecipeData creation and sends CraftingDataPacket on output changes.
core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java Stops building CraftingDataPacket inline; stores stonecutter data and sends session-built packet.
core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeBookAddTranslator.java Sends session-built CraftingDataPacket instead of assembling a new one for recipe-book adds.
core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaFinishConfigurationTranslator.java Uses session-built CraftingDataPacket after configuration transition; clears recipe caches.
core/src/main/java/org/geysermc/geyser/session/GeyserSession.java Adds getCraftingDataPacket() that rebuilds full recipe data (cartography, potions, smithing, stonecutter, trims).
core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java Defines trim recipe + netherite upgrade list with reserved recipe net IDs.
core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java Extends record to include Java input ID.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Copilot AI review requested due to automatic review settings March 15, 2026 14:02
@valaphee valaphee force-pushed the clean-dynamic-recipes branch from 2982ec9 to cc5bc80 Compare March 15, 2026 14:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to fix Bedrock crafting outputs for dynamic (non-recipe-book) recipes—especially when outputs include NBT—by re-registering recipes cleanly and centralizing CraftingDataPacket creation.

Changes:

  • Centralize CraftingDataPacket generation in GeyserSession#getCraftingDataPacket() and reuse it across recipe/config translators.
  • Rework dynamic crafting/smithing output registration to use Cloudburst *RecipeData directly and allow NBT in outputs.
  • Adjust supporting systems (stonecutter data model, inventory container type nullability, shelf block entity item list density, custom item validation) and add/update the integrated resource pack.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
core/src/main/resources/GeyserIntegratedPack.mcpack Adds/updates the integrated Bedrock resource pack payload.
core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java Initializes LAST_RECIPE_NET_ID (now impacts recipe net-id seeding/ordering).
core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java Re-registers dynamic shaped/smithing recipes via CraftingDataPacket for NBT outputs.
core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java Defers CraftingDataPacket assembly to GeyserSession#getCraftingDataPacket(); adjusts stonecutter caching.
core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeBookAddTranslator.java Switches to sending the centralized CraftingDataPacket; adjusts recipe-id bookkeeping.
core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaFinishConfigurationTranslator.java Sends centralized CraftingDataPacket on config->play transition and resets recipe caches.
core/src/main/java/org/geysermc/geyser/translator/level/block/entity/ShelfBlockEntityTranslator.java Ensures shelf “Items” list is dense and index-based for Bedrock.
core/src/main/java/org/geysermc/geyser/translator/item/BedrockItemBuilder.java Adds a shared EMPTY_ITEM NBT constant for dense item lists.
core/src/main/java/org/geysermc/geyser/session/GeyserSession.java Adds getCraftingDataPacket() and moves recipe compilation logic into the session.
core/src/main/java/org/geysermc/geyser/registry/populator/custom/CustomItemContext.java Softens equippable+stack-size validation (warns on first pass).
core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java Threads “first pass” through custom item registration.
core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java Threads “first pass” into CustomItemContext validation.
core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java Moves trim + old-smithing-table netherite upgrades into static recipe data.
core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java Extends record to include the Java input id.
core/src/main/java/org/geysermc/geyser/inventory/Inventory.java Makes containerType nullable.
core/src/main/java/org/geysermc/geyser/inventory/Container.java Handles null container type when choosing integrated pack title prefix.
Comments suppressed due to low confidence (1)

core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java:172

  • javaIngredients is populated but no longer used after switching away from GeyserShapedRecipe. This adds unnecessary work on every output update and makes the code harder to follow; consider removing the list (or using it if still needed for validation).
            ItemData[] ingredients = new ItemData[height * width];
            //construct ingredient list and clear slots on client
            List<SlotDisplay> javaIngredients = new ArrayList<>(height * width);
            int index = 0;
            for (int row = firstRow; row < height + firstRow; row++) {
                for (int col = firstCol; col < width + firstCol; col++) {
                    GeyserItemStack geyserItemStack = holder.inventory().getItem(col + (row * gridDimensions) + 1);
                    ingredients[index] = geyserItemStack.getItemData(session);
                    javaIngredients.add(geyserItemStack.asIngredient());


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

uuid,
"crafting_table",
0,
1,
Comment on lines +251 to +260
CraftingDataPacket craftingDataPacket = session.getCraftingDataPacket();
craftingDataPacket.getCraftingData().add(SmithingTransformRecipeData.of(
uuid.toString(),
ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, template.getItemStack())),
ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, input.getItemStack())),
ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, material.getItemStack())),
ItemTranslator.translateToBedrock(session, output),
"smithing_table",
1
));
*/
public static int LAST_RECIPE_NET_ID;
public static int LAST_RECIPE_NET_ID = 1;

Comment on lines +2545 to +2547
for (GeyserRecipe recipe : craftingRecipes.values()) {
craftingDataPacket.getCraftingData().addAll(recipe.asRecipeData(this));
}
Comment on lines 55 to 63
@@ -72,8 +61,9 @@ public void translate(GeyserSession session, ClientboundFinishConfigurationPacke
session.getSmithingRecipes().clear();
session.getStonecutterRecipes().clear();
}
Copy link
Copy Markdown
Member

@onebeastchris onebeastchris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nitpicks; looks good otherwise. Not sure how expensive this operation is though; isn't the recipe packet quite heavy?

* @param output the expected output of this item when cut.
*/
public record GeyserStonecutterData(int buttonId, @Nullable ItemStack output) {
public record GeyserStonecutterData(int buttonId, int input, @Nullable ItemStack output) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is input the java item id? Would be lovely to clarify in Javadocs

return "Username: %s, DeviceOs: %s, Version: %s".formatted(bedrockUsername(), platform(), version());
}

public CraftingDataPacket getCraftingDataPacket() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this to avoid confusion with getters for fields? e.g. a create prefix would be, imo, clearer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants