Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-12-27 08:30:12 +01:00
Feature: Add recipe unlocking on Bedrock edition (#4016)
* Start on 1.20.10+ recipe unlocking system * Keeping track of multiple Bedrock recipes to unlock for a single Java recipe * Unlock stonecutter recipes * Stonecutter recipes * Unlock tipped arrows/shulker box recipes even when Java doesnt (why..?), and dont send trims if Java doesn't * Translate FurnaceDataRecipes * Revert FurnaceRecipe translation, revert stone cutter recipe identifier caching - Bedrock does not need the smelting recipe, and doesn't (un)lock stonecutter recipes (yet...?) * Remove debug message * Make decorated pot crafting just a little bit smoother :p * formatting * Use itemTag descriptors to fix https://github.com/GeyserMC/Geyser/issues/3784 * Use hashmap instead to store item tag overrides * remove unnecessary comment * Address review by @Konicai * Support for 1.20.30 * undo add whitespace * Merge upstream, use FastUtil maps, rename a few methods * Address Camotoy's review * Fix formatting
Dieser Commit ist enthalten in:
Ursprung
f40ca2004e
Commit
9dad1acfe5
@ -93,6 +93,14 @@ public final class GameProtocol {
|
|||||||
return session.getUpstream().getProtocolVersion() < Bedrock_v594.CODEC.getProtocolVersion();
|
return session.getUpstream().getProtocolVersion() < Bedrock_v594.CODEC.getProtocolVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param session the session to check
|
||||||
|
* @return true if the session needs an experiment for recipe unlocking
|
||||||
|
*/
|
||||||
|
public static boolean isUsingExperimentalRecipeUnlocking(GeyserSession session) {
|
||||||
|
return session.getUpstream().getProtocolVersion() == Bedrock_v594.CODEC.getProtocolVersion();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@link PacketCodec} for Minecraft: Java Edition.
|
* Gets the {@link PacketCodec} for Minecraft: Java Edition.
|
||||||
*
|
*
|
||||||
|
@ -128,6 +128,7 @@ import org.geysermc.geyser.item.Items;
|
|||||||
import org.geysermc.geyser.level.JavaDimension;
|
import org.geysermc.geyser.level.JavaDimension;
|
||||||
import org.geysermc.geyser.level.WorldManager;
|
import org.geysermc.geyser.level.WorldManager;
|
||||||
import org.geysermc.geyser.level.physics.CollisionManager;
|
import org.geysermc.geyser.level.physics.CollisionManager;
|
||||||
|
import org.geysermc.geyser.network.GameProtocol;
|
||||||
import org.geysermc.geyser.network.netty.LocalSession;
|
import org.geysermc.geyser.network.netty.LocalSession;
|
||||||
import org.geysermc.geyser.registry.Registries;
|
import org.geysermc.geyser.registry.Registries;
|
||||||
import org.geysermc.geyser.registry.type.BlockMappings;
|
import org.geysermc.geyser.registry.type.BlockMappings;
|
||||||
@ -391,6 +392,13 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
@Setter
|
@Setter
|
||||||
private Entity mouseoverEntity;
|
private Entity mouseoverEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores all Java recipes by recipe identifier, and matches them to all possible Bedrock recipe identifiers.
|
||||||
|
* They are not 1:1, since Bedrock can have multiple recipes for the same Java recipe.
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
private Map<String, List<String>> javaToBedrockRecipeIds;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private Int2ObjectMap<GeyserRecipe> craftingRecipes;
|
private Int2ObjectMap<GeyserRecipe> craftingRecipes;
|
||||||
private final AtomicInteger lastRecipeNetId;
|
private final AtomicInteger lastRecipeNetId;
|
||||||
@ -611,6 +619,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
this.playerInventory = new PlayerInventory();
|
this.playerInventory = new PlayerInventory();
|
||||||
this.openInventory = null;
|
this.openInventory = null;
|
||||||
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
|
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
|
||||||
|
this.javaToBedrockRecipeIds = new Object2ObjectOpenHashMap<>();
|
||||||
this.lastRecipeNetId = new AtomicInteger(1);
|
this.lastRecipeNetId = new AtomicInteger(1);
|
||||||
|
|
||||||
this.spawned = false;
|
this.spawned = false;
|
||||||
@ -690,6 +699,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
gamerulePacket.getGameRules().add(new GameRuleData<>("keepinventory", true));
|
gamerulePacket.getGameRules().add(new GameRuleData<>("keepinventory", true));
|
||||||
// Ensure client doesn't try and do anything funky; the server handles this for us
|
// Ensure client doesn't try and do anything funky; the server handles this for us
|
||||||
gamerulePacket.getGameRules().add(new GameRuleData<>("spawnradius", 0));
|
gamerulePacket.getGameRules().add(new GameRuleData<>("spawnradius", 0));
|
||||||
|
// Recipe unlocking - only needs to be added if 1. it isn't already on via an experiment, or 2. the client is on pre 1.20.10
|
||||||
|
if (!GameProtocol.isPre1_20_10(this) && !GameProtocol.isUsingExperimentalRecipeUnlocking(this)) {
|
||||||
|
gamerulePacket.getGameRules().add(new GameRuleData<>("recipesunlock", true));
|
||||||
|
}
|
||||||
upstream.sendPacket(gamerulePacket);
|
upstream.sendPacket(gamerulePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1527,6 +1540,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
startGamePacket.setRewindHistorySize(0);
|
startGamePacket.setRewindHistorySize(0);
|
||||||
startGamePacket.setServerAuthoritativeBlockBreaking(false);
|
startGamePacket.setServerAuthoritativeBlockBreaking(false);
|
||||||
|
|
||||||
|
if (GameProtocol.isUsingExperimentalRecipeUnlocking(this)) {
|
||||||
|
startGamePacket.getExperiments().add(new ExperimentData("recipe_unlocking", true));
|
||||||
|
}
|
||||||
|
|
||||||
upstream.sendPacket(startGamePacket);
|
upstream.sendPacket(startGamePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.translator.protocol.java;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundRecipePacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.UnlockedRecipesPacket;
|
||||||
|
import org.geysermc.geyser.network.GameProtocol;
|
||||||
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Translator(packet = ClientboundRecipePacket.class)
|
||||||
|
public class JavaClientboundRecipesTranslator extends PacketTranslator<ClientboundRecipePacket> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translate(GeyserSession session, ClientboundRecipePacket packet) {
|
||||||
|
// recipe unlocking does not exist pre 1.20.10
|
||||||
|
if (GameProtocol.isPre1_20_10(session)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnlockedRecipesPacket recipesPacket = new UnlockedRecipesPacket();
|
||||||
|
switch (packet.getAction()) {
|
||||||
|
case INIT -> {
|
||||||
|
recipesPacket.setAction(UnlockedRecipesPacket.ActionType.INITIALLY_UNLOCKED);
|
||||||
|
recipesPacket.getUnlockedRecipes().addAll(getBedrockRecipes(session, packet.getAlreadyKnownRecipes()));
|
||||||
|
}
|
||||||
|
case ADD -> {
|
||||||
|
recipesPacket.setAction(UnlockedRecipesPacket.ActionType.NEWLY_UNLOCKED);
|
||||||
|
recipesPacket.getUnlockedRecipes().addAll(getBedrockRecipes(session, packet.getRecipes()));
|
||||||
|
}
|
||||||
|
case REMOVE -> {
|
||||||
|
recipesPacket.setAction(UnlockedRecipesPacket.ActionType.REMOVE_UNLOCKED);
|
||||||
|
recipesPacket.getUnlockedRecipes().addAll(getBedrockRecipes(session, packet.getRecipes()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.sendUpstreamPacket(recipesPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getBedrockRecipes(GeyserSession session, String[] javaRecipeIdentifiers) {
|
||||||
|
List<String> recipes = new ArrayList<>();
|
||||||
|
for (String javaIdentifier : javaRecipeIdentifiers) {
|
||||||
|
List<String> bedrockRecipes = session.getJavaToBedrockRecipeIds().get(javaIdentifier);
|
||||||
|
// Some recipes are not (un)lockable on Bedrock edition, like furnace or stonecutter recipes.
|
||||||
|
// So we don't store/send these.
|
||||||
|
if (bedrockRecipes != null) {
|
||||||
|
recipes.addAll(bedrockRecipes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return recipes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeDa
|
|||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTrimRecipeData;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTrimRecipeData;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.DefaultDescriptor;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.DefaultDescriptor;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemTagDescriptor;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.TrimDataPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.TrimDataPacket;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
@ -67,7 +68,6 @@ import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to send all valid recipes from Java to Bedrock.
|
* Used to send all valid recipes from Java to Bedrock.
|
||||||
*
|
|
||||||
* Bedrock REQUIRES a CraftingDataPacket to be sent in order to craft anything.
|
* Bedrock REQUIRES a CraftingDataPacket to be sent in order to craft anything.
|
||||||
*/
|
*/
|
||||||
@Translator(packet = ClientboundUpdateRecipesPacket.class)
|
@Translator(packet = ClientboundUpdateRecipesPacket.class)
|
||||||
@ -94,17 +94,27 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||||||
"minecraft:netherite_boots"
|
"minecraft:netherite_boots"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes https://github.com/GeyserMC/Geyser/issues/3784 by using item tags where applicable instead of group IDs
|
||||||
|
* Item Tags allow mixing ingredients, and theoretically, adding item tags to custom items should also include them.
|
||||||
|
*/
|
||||||
|
private static final Map<String, String> RECIPE_TAGS = Map.of(
|
||||||
|
"minecraft:wood", "minecraft:logs",
|
||||||
|
"minecraft:wooden_slab", "minecraft:wooden_slabs",
|
||||||
|
"minecraft:planks", "minecraft:planks");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundUpdateRecipesPacket packet) {
|
public void translate(GeyserSession session, ClientboundUpdateRecipesPacket packet) {
|
||||||
Map<RecipeType, List<RecipeData>> recipeTypes = Registries.CRAFTING_DATA.forVersion(session.getUpstream().getProtocolVersion());
|
Map<RecipeType, List<RecipeData>> recipeTypes = Registries.CRAFTING_DATA.forVersion(session.getUpstream().getProtocolVersion());
|
||||||
// Get the last known network ID (first used for the pregenerated recipes) and increment from there.
|
// Get the last known network ID (first used for the pregenerated recipes) and increment from there.
|
||||||
int netId = InventoryUtils.LAST_RECIPE_NET_ID + 1;
|
int netId = InventoryUtils.LAST_RECIPE_NET_ID + 1;
|
||||||
boolean sendTrimRecipes = false;
|
boolean sendTrimRecipes = false;
|
||||||
|
Map<String, List<String>> recipeIDs = session.getJavaToBedrockRecipeIds();
|
||||||
Int2ObjectMap<GeyserRecipe> recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion()));
|
Int2ObjectMap<GeyserRecipe> recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion()));
|
||||||
Int2ObjectMap<List<StoneCuttingRecipeData>> unsortedStonecutterData = new Int2ObjectOpenHashMap<>();
|
Int2ObjectMap<List<StoneCuttingRecipeData>> unsortedStonecutterData = new Int2ObjectOpenHashMap<>();
|
||||||
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
|
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
|
||||||
craftingDataPacket.setCleanRecipes(true);
|
craftingDataPacket.setCleanRecipes(true);
|
||||||
|
|
||||||
for (Recipe recipe : packet.getRecipes()) {
|
for (Recipe recipe : packet.getRecipes()) {
|
||||||
switch (recipe.getType()) {
|
switch (recipe.getType()) {
|
||||||
case CRAFTING_SHAPELESS -> {
|
case CRAFTING_SHAPELESS -> {
|
||||||
@ -121,12 +131,15 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> bedrockRecipeIDs = new ArrayList<>();
|
||||||
for (ItemDescriptorWithCount[] inputs : inputCombinations) {
|
for (ItemDescriptorWithCount[] inputs : inputCombinations) {
|
||||||
UUID uuid = UUID.randomUUID();
|
UUID uuid = UUID.randomUUID();
|
||||||
|
bedrockRecipeIDs.add(uuid.toString());
|
||||||
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shapeless(uuid.toString(),
|
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shapeless(uuid.toString(),
|
||||||
Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId));
|
Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId));
|
||||||
recipeMap.put(netId++, new GeyserShapelessRecipe(shapelessRecipeData));
|
recipeMap.put(netId++, new GeyserShapelessRecipe(shapelessRecipeData));
|
||||||
}
|
}
|
||||||
|
addRecipeIdentifier(session, recipe.getIdentifier(), bedrockRecipeIDs);
|
||||||
}
|
}
|
||||||
case CRAFTING_SHAPED -> {
|
case CRAFTING_SHAPED -> {
|
||||||
ShapedRecipeData shapedRecipeData = (ShapedRecipeData) recipe.getData();
|
ShapedRecipeData shapedRecipeData = (ShapedRecipeData) recipe.getData();
|
||||||
@ -141,13 +154,17 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||||||
if (inputCombinations == null) {
|
if (inputCombinations == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> bedrockRecipeIDs = new ArrayList<>();
|
||||||
for (ItemDescriptorWithCount[] inputs : inputCombinations) {
|
for (ItemDescriptorWithCount[] inputs : inputCombinations) {
|
||||||
UUID uuid = UUID.randomUUID();
|
UUID uuid = UUID.randomUUID();
|
||||||
|
bedrockRecipeIDs.add(uuid.toString());
|
||||||
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData.shaped(uuid.toString(),
|
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData.shaped(uuid.toString(),
|
||||||
shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), Arrays.asList(inputs),
|
shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), Arrays.asList(inputs),
|
||||||
Collections.singletonList(output), uuid, "crafting_table", 0, netId));
|
Collections.singletonList(output), uuid, "crafting_table", 0, netId));
|
||||||
recipeMap.put(netId++, new GeyserShapedRecipe(shapedRecipeData));
|
recipeMap.put(netId++, new GeyserShapedRecipe(shapedRecipeData));
|
||||||
}
|
}
|
||||||
|
addRecipeIdentifier(session, recipe.getIdentifier(), bedrockRecipeIDs);
|
||||||
}
|
}
|
||||||
case STONECUTTING -> {
|
case STONECUTTING -> {
|
||||||
StoneCuttingRecipeData stoneCuttingData = (StoneCuttingRecipeData) recipe.getData();
|
StoneCuttingRecipeData stoneCuttingData = (StoneCuttingRecipeData) recipe.getData();
|
||||||
@ -157,8 +174,8 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||||||
data = new ArrayList<>();
|
data = new ArrayList<>();
|
||||||
unsortedStonecutterData.put(ingredient.getId(), data);
|
unsortedStonecutterData.put(ingredient.getId(), data);
|
||||||
}
|
}
|
||||||
data.add(stoneCuttingData);
|
|
||||||
// Save for processing after all recipes have been received
|
// Save for processing after all recipes have been received
|
||||||
|
data.add(stoneCuttingData);
|
||||||
}
|
}
|
||||||
case SMITHING_TRANSFORM -> {
|
case SMITHING_TRANSFORM -> {
|
||||||
SmithingTransformRecipeData data = (SmithingTransformRecipeData) recipe.getData();
|
SmithingTransformRecipeData data = (SmithingTransformRecipeData) recipe.getData();
|
||||||
@ -173,21 +190,29 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||||||
for (ItemStack addition : data.getAddition().getOptions()) {
|
for (ItemStack addition : data.getAddition().getOptions()) {
|
||||||
ItemDescriptorWithCount bedrockAddition = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, addition));
|
ItemDescriptorWithCount bedrockAddition = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, addition));
|
||||||
|
|
||||||
|
String id = recipe.getIdentifier();
|
||||||
// Note: vanilla inputs use aux value of Short.MAX_VALUE
|
// Note: vanilla inputs use aux value of Short.MAX_VALUE
|
||||||
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTransformRecipeData.of(recipe.getIdentifier(),
|
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTransformRecipeData.of(id,
|
||||||
bedrockTemplate, bedrockBase, bedrockAddition, output, "smithing_table", netId++));
|
bedrockTemplate, bedrockBase, bedrockAddition, output, "smithing_table", netId++));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
recipeIDs.put(id, new ArrayList<>(Collections.singletonList(id)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case SMITHING_TRIM -> {
|
case SMITHING_TRIM -> {
|
||||||
sendTrimRecipes = true;
|
sendTrimRecipes = true;
|
||||||
// ignored currently - see below
|
// ignored currently - see below
|
||||||
}
|
}
|
||||||
|
case CRAFTING_DECORATED_POT -> {
|
||||||
|
// Paper 1.20 seems to send only one recipe, which seems to be hardcoded to include all recipes.
|
||||||
|
// We can send the equivalent Bedrock MultiRecipe! :)
|
||||||
|
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("685a742a-c42e-4a4e-88ea-5eb83fc98e5b"), netId++));
|
||||||
|
}
|
||||||
default -> {
|
default -> {
|
||||||
List<RecipeData> craftingData = recipeTypes.get(recipe.getType());
|
List<RecipeData> craftingData = recipeTypes.get(recipe.getType());
|
||||||
if (craftingData != null) {
|
if (craftingData != null) {
|
||||||
|
addSpecialRecipesIdentifiers(session, recipe, craftingData);
|
||||||
craftingDataPacket.getCraftingData().addAll(craftingData);
|
craftingDataPacket.getCraftingData().addAll(craftingData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,14 +243,15 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
UUID uuid = UUID.randomUUID();
|
UUID uuid = UUID.randomUUID();
|
||||||
|
// We need to register stonecutting recipes, so they show up on Bedrock
|
||||||
// We need to register stonecutting recipes so they show up on Bedrock
|
|
||||||
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shapeless(uuid.toString(),
|
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shapeless(uuid.toString(),
|
||||||
Collections.singletonList(descriptor), Collections.singletonList(output), uuid, "stonecutter", 0, netId));
|
Collections.singletonList(descriptor), Collections.singletonList(output), uuid, "stonecutter", 0, netId));
|
||||||
|
|
||||||
// Save the recipe list for reference when crafting
|
// Save the recipe list for reference when crafting
|
||||||
// Add the net ID as the key and the button required + output for the value
|
// Add the net ID as the key and the button required + output for the value
|
||||||
stonecutterRecipeMap.put(netId++, new GeyserStonecutterData(buttonId++, javaOutput));
|
stonecutterRecipeMap.put(netId++, new GeyserStonecutterData(buttonId++, javaOutput));
|
||||||
|
|
||||||
|
// Currently, stone cutter recipes are not locked/unlocked on Bedrock; so no need to cache their identifiers.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,6 +277,38 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||||||
session.sendUpstreamPacket(craftingDataPacket);
|
session.sendUpstreamPacket(craftingDataPacket);
|
||||||
session.setCraftingRecipes(recipeMap);
|
session.setCraftingRecipes(recipeMap);
|
||||||
session.setStonecutterRecipes(stonecutterRecipeMap);
|
session.setStonecutterRecipes(stonecutterRecipeMap);
|
||||||
|
session.setJavaToBedrockRecipeIds(recipeIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSpecialRecipesIdentifiers(GeyserSession session, Recipe recipe, List<RecipeData> craftingData) {
|
||||||
|
String javaRecipeID = recipe.getIdentifier();
|
||||||
|
|
||||||
|
switch (recipe.getType()) {
|
||||||
|
case CRAFTING_SPECIAL_BOOKCLONING, CRAFTING_SPECIAL_REPAIRITEM, CRAFTING_SPECIAL_MAPEXTENDING, CRAFTING_SPECIAL_MAPCLONING:
|
||||||
|
// We do not want to (un)lock these, since BDS does not do it for MultiRecipes
|
||||||
|
return;
|
||||||
|
case CRAFTING_SPECIAL_SHULKERBOXCOLORING:
|
||||||
|
// BDS (un)locks the dyeing with the shulker box recipe, Java never - we want BDS behavior for ease of use
|
||||||
|
javaRecipeID = "minecraft:shulker_box";
|
||||||
|
break;
|
||||||
|
case CRAFTING_SPECIAL_TIPPEDARROW:
|
||||||
|
// similar as above
|
||||||
|
javaRecipeID = "minecraft:arrow";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
List<String> bedrockRecipeIDs = new ArrayList<>();
|
||||||
|
|
||||||
|
// defined in the recipes.json mappings file: Only tipped arrows use shaped recipes, we need the cast for the identifier
|
||||||
|
if (recipe.getType() == RecipeType.CRAFTING_SPECIAL_TIPPEDARROW) {
|
||||||
|
for (RecipeData data : craftingData) {
|
||||||
|
bedrockRecipeIDs.add(((org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData) data).getId());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (RecipeData data : craftingData) {
|
||||||
|
bedrockRecipeIDs.add(((org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData) data).getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addRecipeIdentifier(session, javaRecipeID, bedrockRecipeIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: rewrite
|
//TODO: rewrite
|
||||||
@ -277,6 +335,13 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||||||
for (Map.Entry<GroupedItem, List<ItemDescriptorWithCount>> entry : groupedByIds.entrySet()) {
|
for (Map.Entry<GroupedItem, List<ItemDescriptorWithCount>> entry : groupedByIds.entrySet()) {
|
||||||
if (entry.getValue().size() > 1) {
|
if (entry.getValue().size() > 1) {
|
||||||
GroupedItem groupedItem = entry.getKey();
|
GroupedItem groupedItem = entry.getKey();
|
||||||
|
|
||||||
|
String recipeTag = RECIPE_TAGS.get(groupedItem.id.getIdentifier());
|
||||||
|
if (recipeTag != null) {
|
||||||
|
optionSet.add(new ItemDescriptorWithCount(new ItemTagDescriptor(recipeTag), groupedItem.count));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int idCount = 0;
|
int idCount = 0;
|
||||||
//not optimal
|
//not optimal
|
||||||
for (ItemMapping mapping : session.getItemMappings().getItems()) {
|
for (ItemMapping mapping : session.getItemMappings().getItems()) {
|
||||||
@ -337,6 +402,10 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||||||
return combinations;
|
return combinations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addRecipeIdentifier(GeyserSession session, String javaIdentifier, List<String> bedrockIdentifiers) {
|
||||||
|
session.getJavaToBedrockRecipeIds().computeIfAbsent(javaIdentifier, k -> new ArrayList<>()).addAll(bedrockIdentifiers);
|
||||||
|
}
|
||||||
|
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
private static class GroupedItem {
|
private static class GroupedItem {
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren