Skip to content

Commit ea1e05e

Browse files
committed
Re-register dynamic recipes cleanly
1 parent 2f1cb9b commit ea1e05e

File tree

8 files changed

+143
-138
lines changed

8 files changed

+143
-138
lines changed

core/src/main/java/org/geysermc/geyser/inventory/recipe/GeyserStonecutterData.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030

3131
/**
3232
* @param buttonId the button that needs to be pressed for Java Edition to accept this item.
33+
* @param input the input that this recipe accepts.
3334
* @param output the expected output of this item when cut.
3435
*/
35-
public record GeyserStonecutterData(int buttonId, @Nullable ItemStack output) {
36+
public record GeyserStonecutterData(int buttonId, int input, @Nullable ItemStack output) {
3637
}

core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525

2626
package org.geysermc.geyser.inventory.recipe;
2727

28+
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
2829
import net.kyori.adventure.text.Component;
2930
import org.cloudburstmc.protocol.bedrock.data.TrimMaterial;
3031
import org.cloudburstmc.protocol.bedrock.data.TrimPattern;
32+
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTrimRecipeData;
3133
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
3234
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemTagDescriptor;
3335
import org.geysermc.geyser.GeyserImpl;
@@ -43,19 +45,37 @@
4345
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ProvidesTrimMaterial;
4446

4547
import java.util.HashMap;
48+
import java.util.List;
4649
import java.util.Map;
4750

51+
import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID;
52+
4853
/**
4954
* Stores information on trim materials and patterns, including smithing armor hacks for pre-1.20.
5055
*/
5156
public final class TrimRecipe {
5257
private static final Map<ProvidesTrimMaterial, Item> trimMaterialProviders = new HashMap<>();
5358

54-
// For CraftingDataPacket
55-
public static final String ID = "minecraft:smithing_armor_trim";
56-
public static final ItemDescriptorWithCount BASE = tagDescriptor("minecraft:trimmable_armors");
57-
public static final ItemDescriptorWithCount ADDITION = tagDescriptor("minecraft:trim_materials");
58-
public static final ItemDescriptorWithCount TEMPLATE = tagDescriptor("minecraft:trim_templates");
59+
public static final List<ObjectIntPair<String>> NETHERITE_UPGRADES = List.of(
60+
ObjectIntPair.of("minecraft:netherite_sword", ++LAST_RECIPE_NET_ID),
61+
ObjectIntPair.of("minecraft:netherite_shovel", ++LAST_RECIPE_NET_ID),
62+
ObjectIntPair.of("minecraft:netherite_pickaxe", ++LAST_RECIPE_NET_ID),
63+
ObjectIntPair.of("minecraft:netherite_axe", ++LAST_RECIPE_NET_ID),
64+
ObjectIntPair.of("minecraft:netherite_hoe", ++LAST_RECIPE_NET_ID),
65+
ObjectIntPair.of("minecraft:netherite_helmet", ++LAST_RECIPE_NET_ID),
66+
ObjectIntPair.of("minecraft:netherite_chestplate", ++LAST_RECIPE_NET_ID),
67+
ObjectIntPair.of("minecraft:netherite_leggings", ++LAST_RECIPE_NET_ID),
68+
ObjectIntPair.of("minecraft:netherite_boots", ++LAST_RECIPE_NET_ID)
69+
);
70+
71+
public static final SmithingTrimRecipeData RECIPE = SmithingTrimRecipeData.of(
72+
"minecraft:smithing_armor_trim",
73+
tagDescriptor("minecraft:trimmable_armors"),
74+
tagDescriptor("minecraft:trim_materials"),
75+
tagDescriptor("minecraft:trim_templates"),
76+
"smithing_table",
77+
++LAST_RECIPE_NET_ID
78+
);
5979

6080
public static TrimMaterial readTrimMaterial(RegistryEntryContext context) {
6181
String key = context.id().asMinimalString();

core/src/main/java/org/geysermc/geyser/session/GeyserSession.java

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@
2828
import com.google.gson.JsonObject;
2929
import io.netty.channel.Channel;
3030
import io.netty.channel.EventLoop;
31-
import it.unimi.dsi.fastutil.Pair;
3231
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
32+
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
3333
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
3434
import it.unimi.dsi.fastutil.objects.Object2IntMap;
3535
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
3636
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
37+
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
3738
import lombok.AccessLevel;
3839
import lombok.Getter;
3940
import lombok.Setter;
@@ -76,14 +77,20 @@
7677
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
7778
import org.cloudburstmc.protocol.bedrock.data.command.SoftEnumUpdateType;
7879
import org.cloudburstmc.protocol.bedrock.data.definitions.DimensionDefinition;
80+
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
7981
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
8082
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
81-
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.CraftingRecipeData;
83+
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.RecipeUnlockingRequirement;
84+
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData;
85+
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTransformRecipeData;
86+
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.DefaultDescriptor;
87+
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
8288
import org.cloudburstmc.protocol.bedrock.packet.AvailableEntityIdentifiersPacket;
8389
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
8490
import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket;
8591
import org.cloudburstmc.protocol.bedrock.packet.CameraPresetsPacket;
8692
import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket;
93+
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
8794
import org.cloudburstmc.protocol.bedrock.packet.CreativeContentPacket;
8895
import org.cloudburstmc.protocol.bedrock.packet.DimensionDataPacket;
8996
import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket;
@@ -150,6 +157,8 @@
150157
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
151158
import org.geysermc.geyser.inventory.recipe.GeyserSmithingRecipe;
152159
import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData;
160+
import org.geysermc.geyser.inventory.recipe.RecipeUtil;
161+
import org.geysermc.geyser.inventory.recipe.TrimRecipe;
153162
import org.geysermc.geyser.item.Items;
154163
import org.geysermc.geyser.item.type.BlockItem;
155164
import org.geysermc.geyser.level.BedrockDimension;
@@ -158,6 +167,7 @@
158167
import org.geysermc.geyser.network.netty.LocalSession;
159168
import org.geysermc.geyser.registry.Registries;
160169
import org.geysermc.geyser.registry.type.BlockMappings;
170+
import org.geysermc.geyser.registry.type.ItemMapping;
161171
import org.geysermc.geyser.registry.type.ItemMappings;
162172
import org.geysermc.geyser.session.auth.AuthData;
163173
import org.geysermc.geyser.session.auth.BedrockClientData;
@@ -190,6 +200,7 @@
190200
import org.geysermc.geyser.skin.SkinManager;
191201
import org.geysermc.geyser.text.GeyserLocale;
192202
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
203+
import org.geysermc.geyser.translator.item.ItemTranslator;
193204
import org.geysermc.geyser.translator.text.MessageTranslator;
194205
import org.geysermc.geyser.util.ChunkUtils;
195206
import org.geysermc.geyser.util.CooldownUtils;
@@ -214,6 +225,7 @@
214225
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.HandPreference;
215226
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction;
216227
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.ResolvableProfile;
228+
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
217229
import org.geysermc.mcprotocollib.protocol.data.game.setting.ChatVisibility;
218230
import org.geysermc.mcprotocollib.protocol.data.game.setting.ParticleStatus;
219231
import org.geysermc.mcprotocollib.protocol.data.game.setting.SkinPart;
@@ -521,16 +533,14 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
521533
private final Int2ObjectMap<List<String>> javaToBedrockRecipeIds;
522534

523535
private final Int2ObjectMap<GeyserRecipe> craftingRecipes;
524-
@Setter
525-
private Pair<CraftingRecipeData, GeyserRecipe> lastCreatedRecipe = null; // TODO try to prevent sending duplicate recipes
526536
private final AtomicInteger lastRecipeNetId;
527537

528538
/**
529539
* Saves a list of all stonecutter recipes, for use in a stonecutter inventory.
530540
* The key is the Bedrock recipe net ID; the values are their respective output and button ID.
531541
*/
532542
@Setter
533-
private Int2ObjectMap<GeyserStonecutterData> stonecutterRecipes;
543+
private Int2ObjectMap<GeyserStonecutterData> stonecutterRecipes = Int2ObjectMaps.emptyMap();
534544
private final List<GeyserSmithingRecipe> smithingRecipes = new ArrayList<>();
535545

536546
/**
@@ -2525,4 +2535,57 @@ public void sendNetworkLatencyStackPacket(long timestamp, boolean ensureEventLoo
25252535
public String getDebugInfo() {
25262536
return "Username: %s, DeviceOs: %s, Version: %s".formatted(bedrockUsername(), platform(), version());
25272537
}
2538+
2539+
public CraftingDataPacket getCraftingDataPacket() {
2540+
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
2541+
craftingDataPacket.setCleanRecipes(true);
2542+
craftingDataPacket.getCraftingData().addAll(RecipeUtil.CARTOGRAPHY_RECIPES);
2543+
// Potion mixes are registered by default, as they are needed to be able to put ingredients into the brewing stand.
2544+
craftingDataPacket.getPotionMixData().addAll(Registries.POTION_MIXES.forVersion(getUpstream().getProtocolVersion()));
2545+
for (GeyserRecipe recipe : craftingRecipes.values()) {
2546+
craftingDataPacket.getCraftingData().addAll(recipe.asRecipeData(this));
2547+
}
2548+
for (GeyserSmithingRecipe recipe : smithingRecipes) {
2549+
craftingDataPacket.getCraftingData().addAll(recipe.asRecipeData(this));
2550+
}
2551+
if (oldSmithingTable) {
2552+
ItemMapping template = itemMappings.getStoredItems().upgradeTemplate();
2553+
2554+
for (ObjectIntPair<String> identifierAndNetId : TrimRecipe.NETHERITE_UPGRADES) {
2555+
craftingDataPacket.getCraftingData().add(SmithingTransformRecipeData.of(identifierAndNetId.left() + "_smithing",
2556+
getDescriptorFromId(this, template.getBedrockIdentifier()),
2557+
getDescriptorFromId(this, identifierAndNetId.left().replace("netherite", "diamond")),
2558+
getDescriptorFromId(this, "minecraft:netherite_ingot"),
2559+
ItemData.builder().definition(Objects.requireNonNull(itemMappings.getDefinition(identifierAndNetId.left()))).count(1).build(),
2560+
"smithing_table",
2561+
identifierAndNetId.rightInt()));
2562+
}
2563+
} else {
2564+
craftingDataPacket.getCraftingData().add(TrimRecipe.RECIPE);
2565+
}
2566+
for (Int2ObjectMap.Entry<GeyserStonecutterData> recipe : stonecutterRecipes.int2ObjectEntrySet()) {
2567+
int buttonId = recipe.getValue().buttonId();
2568+
int javaInput = recipe.getValue().input();
2569+
ItemMapping mapping = itemMappings.getMapping(javaInput);
2570+
ItemDescriptorWithCount descriptor = new ItemDescriptorWithCount(new DefaultDescriptor(mapping.getBedrockDefinition(), mapping.getBedrockData()), 1);
2571+
ItemStack javaOutput = recipe.getValue().output();
2572+
ItemData output = ItemTranslator.translateToBedrock(this, javaOutput);
2573+
int recipeNetId = recipe.getIntKey();
2574+
UUID uuid = UUID.randomUUID();
2575+
// We need to register stonecutting recipes, so they show up on Bedrock
2576+
// (Implementation note: recipe ID creates the order which stonecutting recipes are shown in stonecutter)
2577+
craftingDataPacket.getCraftingData().add(ShapelessRecipeData.shapeless("stonecutter_" + javaInput + "_" + buttonId,
2578+
Collections.singletonList(descriptor), Collections.singletonList(output), uuid, "stonecutter", 0, recipeNetId, RecipeUnlockingRequirement.INVALID));
2579+
}
2580+
return craftingDataPacket;
2581+
}
2582+
2583+
private ItemDescriptorWithCount getDescriptorFromId(GeyserSession session, String bedrockId) {
2584+
ItemDefinition bedrockDefinition = session.getItemMappings().getDefinition(bedrockId);
2585+
if (bedrockDefinition != null) {
2586+
return ItemDescriptorWithCount.fromItem(ItemData.builder().definition(bedrockDefinition).count(1).build());
2587+
}
2588+
GeyserImpl.getInstance().getLogger().debug("Unable to find item with identifier " + bedrockId);
2589+
return ItemDescriptorWithCount.EMPTY;
2590+
}
25282591
}

core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaFinishConfigurationTranslator.java

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@
2525

2626
package org.geysermc.geyser.translator.protocol.java;
2727

28-
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
2928
import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket;
30-
import org.geysermc.geyser.registry.Registries;
3129
import org.geysermc.geyser.session.GeyserSession;
3230
import org.geysermc.geyser.translator.protocol.PacketTranslator;
3331
import org.geysermc.geyser.translator.protocol.Translator;
@@ -38,8 +36,6 @@
3836
import java.util.ArrayList;
3937
import java.util.List;
4038

41-
import static org.geysermc.geyser.inventory.recipe.RecipeUtil.CARTOGRAPHY_RECIPES;
42-
4339
@Translator(packet = ClientboundFinishConfigurationPacket.class)
4440
public class JavaFinishConfigurationTranslator extends PacketTranslator<ClientboundFinishConfigurationPacket> {
4541

@@ -55,14 +51,7 @@ public void translate(GeyserSession session, ClientboundFinishConfigurationPacke
5551
}
5652
session.getEntityCache().removeAllPlayerEntities();
5753

58-
// Potion mixes are registered by default, as they are needed to be able to put ingredients into the brewing stand.
59-
// (Also add it here so recipes get cleared on configuration - 1.21.3)
60-
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
61-
craftingDataPacket.setCleanRecipes(true);
62-
craftingDataPacket.getCraftingData().addAll(CARTOGRAPHY_RECIPES);
63-
craftingDataPacket.getPotionMixData().addAll(Registries.POTION_MIXES.forVersion(session.getUpstream().getProtocolVersion()));
6454
if (session.isSentSpawnPacket()) {
65-
session.getUpstream().sendPacket(craftingDataPacket);
6655
// TODO proper fix to check if we've been online - in online mode (with auth screen),
6756
// recipes are not yet known
6857
if (session.getStonecutterRecipes() != null) {
@@ -72,8 +61,9 @@ public void translate(GeyserSession session, ClientboundFinishConfigurationPacke
7261
session.getSmithingRecipes().clear();
7362
session.getStonecutterRecipes().clear();
7463
}
64+
session.getUpstream().sendPacket(session.getCraftingDataPacket());
7565
} else {
76-
session.getUpstream().queuePostStartGamePacket(craftingDataPacket);
66+
session.getUpstream().queuePostStartGamePacket(session.getCraftingDataPacket());
7767
}
7868

7969
// while ClientboundLoginPacket holds the level, it doesn't hold the scoreboard.

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

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ public void translate(GeyserSession session, ClientboundRecipeBookAddPacket pack
5555
int netId = session.getLastRecipeNetId().get();
5656
Int2ObjectMap<List<String>> javaToBedrockRecipeIds = session.getJavaToBedrockRecipeIds();
5757
Int2ObjectMap<GeyserRecipe> geyserRecipes = session.getCraftingRecipes();
58-
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
5958

6059
UnlockedRecipesPacket recipesPacket = new UnlockedRecipesPacket();
6160
recipesPacket.setAction(packet.isReplace() ? UnlockedRecipesPacket.ActionType.INITIALLY_UNLOCKED : UnlockedRecipesPacket.ActionType.NEWLY_UNLOCKED);
@@ -70,11 +69,10 @@ public void translate(GeyserSession session, ClientboundRecipeBookAddPacket pack
7069
if (display instanceof ShapedCraftingRecipeDisplay shapedRecipe) {
7170
GeyserRecipe geyserRecipe = new GeyserShapedRecipe(contents.id(), netId, shapedRecipe);
7271

73-
List<RecipeData> recipeData = geyserRecipe.asRecipeData(session);
74-
craftingDataPacket.getCraftingData().addAll(recipeData);
72+
int recipeCount = geyserRecipe.asRecipeData(session).size();
7573

7674
List<String> bedrockRecipeIds = new ArrayList<>();
77-
for (int i = 0; i < recipeData.size(); i++) {
75+
for (int i = 0; i < recipeCount; i++) {
7876
String recipeId = contents.id() + "_" + i;
7977
recipesPacket.getUnlockedRecipes().add(recipeId);
8078
bedrockRecipeIds.add(recipeId);
@@ -84,11 +82,10 @@ public void translate(GeyserSession session, ClientboundRecipeBookAddPacket pack
8482
} else if (display instanceof ShapelessCraftingRecipeDisplay shapelessRecipe) {
8583
GeyserRecipe geyserRecipe = new GeyserShapelessRecipe(contents.id(), netId, shapelessRecipe);
8684

87-
List<RecipeData> recipeData = geyserRecipe.asRecipeData(session);
88-
craftingDataPacket.getCraftingData().addAll(recipeData);
85+
int recipeCount = geyserRecipe.asRecipeData(session).size();
8986

9087
List<String> bedrockRecipeIds = new ArrayList<>();
91-
for (int i = 0; i < recipeData.size(); i++) {
88+
for (int i = 0; i < recipeCount; i++) {
9289
String recipeId = contents.id() + "_" + i;
9390
recipesPacket.getUnlockedRecipes().add(recipeId);
9491
bedrockRecipeIds.add(recipeId);
@@ -104,18 +101,15 @@ public void translate(GeyserSession session, ClientboundRecipeBookAddPacket pack
104101
GeyserSmithingRecipe geyserRecipe = new GeyserSmithingRecipe(contents.id(), netId, smithingRecipe);
105102
session.getSmithingRecipes().add(geyserRecipe);
106103

107-
List<RecipeData> recipeData = geyserRecipe.asRecipeData(session);
108-
craftingDataPacket.getCraftingData().addAll(recipeData);
109-
110-
netId += recipeData.size();
104+
netId += geyserRecipe.asRecipeData(session).size();
111105
}
112106
}
113107

114108
if (!recipesPacket.getUnlockedRecipes().isEmpty()) {
115109
// Sending an empty list here will crash the client as of 1.20.60
116110
// This was definitely in the codebase the entire time and did not
117111
// accidentally get refactored out during Java 1.21.3. :)
118-
session.sendUpstreamPacket(craftingDataPacket);
112+
session.sendUpstreamPacket(session.getCraftingDataPacket());
119113
session.sendUpstreamPacket(recipesPacket);
120114
}
121115
session.getLastRecipeNetId().set(netId);

0 commit comments

Comments
 (0)