Re-register dynamic recipes cleanly, allow for NBT in output#6233
Re-register dynamic recipes cleanly, allow for NBT in output#6233valaphee wants to merge 1 commit intoGeyserMC:masterfrom
Conversation
There was a problem hiding this comment.
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
RecipeDatadirectly (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 importsRecipeDataandCraftingDataPacketare 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.
...a/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java
Show resolved
Hide resolved
...a/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java
Show resolved
Hide resolved
...ain/java/org/geysermc/geyser/translator/protocol/java/JavaFinishConfigurationTranslator.java
Show resolved
Hide resolved
core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
Outdated
Show resolved
Hide resolved
...ain/java/org/geysermc/geyser/translator/protocol/java/JavaFinishConfigurationTranslator.java
Show resolved
Hide resolved
...a/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java
Show resolved
Hide resolved
.../src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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.
...a/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java
Show resolved
Hide resolved
...a/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java
Show resolved
Hide resolved
.../src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeBookAddTranslator.java
Show resolved
Hide resolved
...a/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java
Show resolved
Hide resolved
...a/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java
Show resolved
Hide resolved
...ain/java/org/geysermc/geyser/translator/protocol/java/JavaFinishConfigurationTranslator.java
Show resolved
Hide resolved
2982ec9 to
cc5bc80
Compare
cc5bc80 to
ea1e05e
Compare
There was a problem hiding this comment.
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
*RecipeDatadirectly 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
javaIngredientsis populated but no longer used after switching away fromGeyserShapedRecipe. 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, |
| 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; | ||
|
|
| for (GeyserRecipe recipe : craftingRecipes.values()) { | ||
| craftingDataPacket.getCraftingData().addAll(recipe.asRecipeData(this)); | ||
| } |
| @@ -72,8 +61,9 @@ public void translate(GeyserSession session, ClientboundFinishConfigurationPacke | |||
| session.getSmithingRecipes().clear(); | |||
| session.getStonecutterRecipes().clear(); | |||
| } | |||
onebeastchris
left a comment
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
Can we rename this to avoid confusion with getters for fields? e.g. a create prefix would be, imo, clearer
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.