Work on recipes

This commit is contained in:
Camotoy 2024-10-22 23:22:47 -04:00
parent e78b24830d
commit e00ef21af4
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
9 changed files with 365 additions and 98 deletions

View file

@ -29,7 +29,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
/**
* A more compact version of {@link org.geysermc.mcprotocollib.protocol.data.game.recipe.Recipe}.
* A more compact version of {link org.geysermc.mcprotocollib.protocol.data.game.recipe.Recipe}.
*/
public interface GeyserRecipe {
/**

View file

@ -28,13 +28,12 @@ package org.geysermc.geyser.inventory.recipe;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.data.ShapedRecipeData;
public record GeyserShapedRecipe(int width, int height, Ingredient[] ingredients, @Nullable ItemStack result) implements GeyserRecipe {
public GeyserShapedRecipe(ShapedRecipeData data) {
this(data.getWidth(), data.getHeight(), data.getIngredients(), data.getResult());
}
// public GeyserShapedRecipe(ShapedRecipeData data) {
// this(data.getWidth(), data.getHeight(), data.getIngredients(), data.getResult());
// }
@Override
public boolean isShaped() {

View file

@ -25,16 +25,15 @@
package org.geysermc.geyser.inventory.recipe;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.data.ShapelessRecipeData;
import org.checkerframework.checker.nullness.qual.Nullable;
public record GeyserShapelessRecipe(Ingredient[] ingredients, @Nullable ItemStack result) implements GeyserRecipe {
public GeyserShapelessRecipe(ShapelessRecipeData data) {
this(data.getIngredients(), data.getResult());
}
// public GeyserShapelessRecipe(ShapelessRecipeData data) {
// this(data.getIngredients(), data.getResult());
// }
@Override
public boolean isShaped() {

View file

@ -37,10 +37,17 @@ import org.geysermc.geyser.api.pack.ResourcePack;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.loader.*;
import org.geysermc.geyser.registry.loader.BiomeIdentifierRegistryLoader;
import org.geysermc.geyser.registry.loader.BlockEntityRegistryLoader;
import org.geysermc.geyser.registry.loader.ParticleTypesRegistryLoader;
import org.geysermc.geyser.registry.loader.PotionMixRegistryLoader;
import org.geysermc.geyser.registry.loader.ProviderRegistryLoader;
import org.geysermc.geyser.registry.loader.RegistryLoaders;
import org.geysermc.geyser.registry.loader.SoundEventsRegistryLoader;
import org.geysermc.geyser.registry.loader.SoundRegistryLoader;
import org.geysermc.geyser.registry.loader.SoundTranslatorRegistryLoader;
import org.geysermc.geyser.registry.populator.ItemRegistryPopulator;
import org.geysermc.geyser.registry.populator.PacketRegistryPopulator;
import org.geysermc.geyser.registry.loader.RecipeRegistryLoader;
import org.geysermc.geyser.registry.provider.ProviderSupplier;
import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.registry.type.ParticleMapping;
@ -54,9 +61,13 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType;
import org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEvent;
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.RecipeType;
import java.util.*;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
/**
* Holds all the common registries in Geyser.
@ -140,7 +151,7 @@ public final class Registries {
/**
* A versioned registry holding all the recipes, with the net ID being the key, and {@link GeyserRecipe} as the value.
*/
public static final SimpleMappedRegistry<RecipeType, List<GeyserRecipe>> RECIPES = SimpleMappedRegistry.create("mappings/recipes.nbt", RecipeRegistryLoader::new);
//public static final SimpleMappedRegistry<RecipeType, List<GeyserRecipe>> RECIPES = SimpleMappedRegistry.create("mappings/recipes.nbt", RecipeRegistryLoader::new);
/**
* A mapped registry holding {@link ResourcePack}'s with the pack uuid as keys.

View file

@ -27,29 +27,19 @@ package org.geysermc.geyser.registry.loader;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.cloudburstmc.nbt.NBTInputStream;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodec;
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.RecipeType;
import java.io.DataInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -57,41 +47,41 @@ import java.util.Map;
* Populates the recipe registry with some recipes that Java does not send, to ensure they show up as intended
* in the recipe book.
*/
public final class RecipeRegistryLoader implements RegistryLoader<String, Map<RecipeType, List<GeyserRecipe>>> {
public abstract class RecipeRegistryLoader implements RegistryLoader<String, Map<Object, List<GeyserRecipe>>> {
@Override
public Map<RecipeType, List<GeyserRecipe>> load(String input) {
if (true) {
return Collections.emptyMap();
}
Map<RecipeType, List<GeyserRecipe>> deserializedRecipes = new Object2ObjectOpenHashMap<>();
// @Override
// public Map<RecipeType, List<GeyserRecipe>> load(String input) {
// if (true) {
// return Collections.emptyMap();
// }
// Map<RecipeType, List<GeyserRecipe>> deserializedRecipes = new Object2ObjectOpenHashMap<>();
//
// List<NbtMap> recipes;
// try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/recipes.nbt")) {
// try (NBTInputStream nbtStream = new NBTInputStream(new DataInputStream(stream))) {
// recipes = ((NbtMap) nbtStream.readTag()).getList("recipes", NbtType.COMPOUND);
// }
// } catch (Exception e) {
// throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e);
// }
//
// MinecraftCodecHelper helper = MinecraftCodec.CODEC.getHelperFactory().get();
// for (NbtMap recipeCollection : recipes) {
// var pair = getRecipes(recipeCollection, helper);
// deserializedRecipes.put(pair.key(), pair.value());
// }
// return deserializedRecipes;
// }
List<NbtMap> recipes;
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/recipes.nbt")) {
try (NBTInputStream nbtStream = new NBTInputStream(new DataInputStream(stream))) {
recipes = ((NbtMap) nbtStream.readTag()).getList("recipes", NbtType.COMPOUND);
}
} catch (Exception e) {
throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e);
}
MinecraftCodecHelper helper = MinecraftCodec.CODEC.getHelperFactory().get();
for (NbtMap recipeCollection : recipes) {
var pair = getRecipes(recipeCollection, helper);
deserializedRecipes.put(pair.key(), pair.value());
}
return deserializedRecipes;
}
private static Pair<RecipeType, List<GeyserRecipe>> getRecipes(NbtMap recipes, MinecraftCodecHelper helper) {
List<NbtMap> typedRecipes = recipes.getList("recipes", NbtType.COMPOUND);
RecipeType recipeType = RecipeType.from(recipes.getInt("recipe_type", -1));
if (recipeType == RecipeType.CRAFTING_SPECIAL_TIPPEDARROW) {
return Pair.of(recipeType, getShapedRecipes(typedRecipes, helper));
} else {
return Pair.of(recipeType, getShapelessRecipes(typedRecipes, helper));
}
}
// private static Pair<RecipeType, List<GeyserRecipe>> getRecipes(NbtMap recipes, MinecraftCodecHelper helper) {
// List<NbtMap> typedRecipes = recipes.getList("recipes", NbtType.COMPOUND);
// RecipeType recipeType = RecipeType.from(recipes.getInt("recipe_type", -1));
// if (recipeType == RecipeType.CRAFTING_SPECIAL_TIPPEDARROW) {
// return Pair.of(recipeType, getShapedRecipes(typedRecipes, helper));
// } else {
// return Pair.of(recipeType, getShapelessRecipes(typedRecipes, helper));
// }
// }
private static List<GeyserRecipe> getShapelessRecipes(List<NbtMap> recipes, MinecraftCodecHelper helper) {
List<GeyserRecipe> deserializedRecipes = new ObjectArrayList<>(recipes.size());

View file

@ -441,10 +441,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
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.
* Stores all Java recipes by ID, and matches them to all possible Bedrock recipe identifiers.
*/
private final Map<String, List<String>> javaToBedrockRecipeIds;
private final Int2ObjectMap<List<String>> javaToBedrockRecipeIds;
@Setter
private Int2ObjectMap<GeyserRecipe> craftingRecipes;
@ -694,7 +693,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.playerInventory = new PlayerInventory();
this.openInventory = null;
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
this.javaToBedrockRecipeIds = new Object2ObjectOpenHashMap<>();
this.javaToBedrockRecipeIds = new Int2ObjectOpenHashMap<>();
this.lastRecipeNetId = new AtomicInteger(1);
this.spawned = false;

View file

@ -25,57 +25,258 @@
package org.geysermc.geyser.translator.protocol.java;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.kyori.adventure.key.Key;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.RecipeUnlockingRequirement;
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData;
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.DefaultDescriptor;
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.UnlockedRecipesPacket;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.BedrockRequiresTagItem;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.RecipeDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.RecipeDisplayEntry;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.ShapedCraftingRecipeDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.ShapelessCraftingRecipeDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.CompositeSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemStackSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.TagSlotDisplay;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundRecipeBookAddPacket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@Translator(packet = ClientboundRecipeBookAddPacket.class)
public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRecipeBookAddPacket> {
@Override
public void translate(GeyserSession session, ClientboundRecipeBookAddPacket packet) {
System.out.println(packet);
int netId = session.getLastRecipeNetId().get();
Int2ObjectMap<List<String>> javaToBedrockRecipeIds = session.getJavaToBedrockRecipeIds();
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
// Check if we should set cleanRecipes here or not.
UnlockedRecipesPacket recipesPacket = new UnlockedRecipesPacket();
recipesPacket.setAction(packet.isReplace() ? UnlockedRecipesPacket.ActionType.INITIALLY_UNLOCKED : UnlockedRecipesPacket.ActionType.NEWLY_UNLOCKED);
// List<String> recipes = getBedrockRecipes(session, packet.getEntries());
// if (recipes.isEmpty() && !packet.isReplace()) {
// // Sending an empty list here packet will crash the client as of 1.20.60
// return;
// }
// 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(recipes);
// }
// case REMOVE -> {
// List<String> recipes = getBedrockRecipes(session, packet.getRecipes());
// if (recipes.isEmpty()) {
// // Sending an empty list here will crash the client as of 1.20.60
// return;
// }
// recipesPacket.setAction(UnlockedRecipesPacket.ActionType.REMOVE_UNLOCKED);
// recipesPacket.getUnlockedRecipes().addAll(recipes);
// }
// }
// session.sendUpstreamPacket(recipesPacket);
for (ClientboundRecipeBookAddPacket.Entry entry : packet.getEntries()) {
RecipeDisplayEntry contents = entry.contents();
RecipeDisplay display = contents.display();
switch (display.getType()) {
case CRAFTING_SHAPED -> {
ShapedCraftingRecipeDisplay shapedRecipe = (ShapedCraftingRecipeDisplay) display;
Pair<Item, ItemData> pair = translateToOutput(session, shapedRecipe.result());
if (pair == null || !pair.right().isValid()) {
// Likely modded item Bedrock will complain about
continue;
}
ItemData output = pair.right();
if (!(pair.left() instanceof BedrockRequiresTagItem)) {
// Strip NBT - tools won't appear in the recipe book otherwise
output = output.toBuilder().tag(null).build();
}
boolean empty = true;
boolean complexInputs = false;
List<ItemDescriptorWithCount[]> inputs = new ArrayList<>(shapedRecipe.ingredients().size());
for (SlotDisplay input : shapedRecipe.ingredients()) {
ItemDescriptorWithCount[] translated = translateToInput(session, input);
if (translated == null) {
continue;
}
inputs.add(translated);
if (translated.length != 1 || translated[0] != ItemDescriptorWithCount.EMPTY) {
empty = false;
}
complexInputs |= translated.length > 1;
}
if (empty) {
// Crashes Bedrock 1.19.70 otherwise
// Fixes https://github.com/GeyserMC/Geyser/issues/3549
continue;
}
if (complexInputs) {
} else {
String recipeId = Integer.toString(contents.id());
craftingDataPacket.getCraftingData().add(ShapedRecipeData.shaped(recipeId,
shapedRecipe.width(), shapedRecipe.height(), inputs.stream().map(descriptors -> descriptors[0]).toList(),
Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, netId++, false, RecipeUnlockingRequirement.INVALID));
recipesPacket.getUnlockedRecipes().add(recipeId);
javaToBedrockRecipeIds.put(contents.id(), Collections.singletonList(recipeId));
}
}
case CRAFTING_SHAPELESS -> {
ShapelessCraftingRecipeDisplay shapelessRecipe = (ShapelessCraftingRecipeDisplay) display;
Pair<Item, ItemData> pair = translateToOutput(session, shapelessRecipe.result());
if (pair == null || !pair.right().isValid()) {
// Likely modded item Bedrock will complain about
continue;
}
ItemData output = pair.right();
if (!(pair.left() instanceof BedrockRequiresTagItem)) {
// Strip NBT - tools won't appear in the recipe book otherwise
output = output.toBuilder().tag(null).build();
}
}
}
}
System.out.println(craftingDataPacket);
session.sendUpstreamPacket(craftingDataPacket);
session.sendUpstreamPacket(recipesPacket);
session.getLastRecipeNetId().set(netId);
// Multi-version can mean different Bedrock item IDs
TAG_TO_ITEM_DESCRIPTOR_CACHE.remove();
}
// private List<String> getBedrockRecipes(GeyserSession session, List<ClientboundRecipeBookAddPacket.Entry> entry) {
// 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);
// }
private static final ThreadLocal<Map<int[], ItemDescriptorWithCount[]>> TAG_TO_ITEM_DESCRIPTOR_CACHE = ThreadLocal.withInitial(Object2ObjectOpenHashMap::new);
private ItemDescriptorWithCount[] translateToInput(GeyserSession session, SlotDisplay slotDisplay) {
if (slotDisplay instanceof EmptySlotDisplay) {
return new ItemDescriptorWithCount[] {ItemDescriptorWithCount.EMPTY};
}
if (slotDisplay instanceof CompositeSlotDisplay composite) {
if (composite.contents().size() == 1) {
return translateToInput(session, composite.contents().get(0));
}
return composite.contents().stream()
.map(subDisplay -> translateToInput(session, subDisplay))
.filter(Objects::nonNull)
.flatMap(Arrays::stream)
.toArray(ItemDescriptorWithCount[]::new);
}
if (slotDisplay instanceof ItemSlotDisplay itemSlot) {
return new ItemDescriptorWithCount[] {fromItem(session, itemSlot.item())};
}
if (slotDisplay instanceof ItemStackSlotDisplay itemStackSlot) {
ItemData item = ItemTranslator.translateToBedrock(session, itemStackSlot.itemStack());
return new ItemDescriptorWithCount[] {ItemDescriptorWithCount.fromItem(item)};
}
if (slotDisplay instanceof TagSlotDisplay tagSlot) {
Key tag = tagSlot.tag();
int[] items = session.getTagCache().getRaw(new Tag<>(JavaRegistries.ITEM, tag)); // I don't like this...
if (items == null || items.length == 0) {
return new ItemDescriptorWithCount[] {ItemDescriptorWithCount.EMPTY};
} else if (items.length == 1) {
return new ItemDescriptorWithCount[] {fromItem(session, items[0])};
} else {
// Cache is implemented as, presumably, an item tag will be used multiple times in succession
// (E.G. a chest with planks tags)
return TAG_TO_ITEM_DESCRIPTOR_CACHE.get().computeIfAbsent(items, key -> {
// String molang = "q.is_item_name_any('', "
// + Arrays.stream(items).mapToObj(item -> {
// ItemMapping mapping = session.getItemMappings().getMapping(item);
// return "'" + mapping.getBedrockIdentifier() + "'";
// }).collect(Collectors.joining(", "))
// + ")";
// String molang = Arrays.stream(items).mapToObj(item -> {
// ItemMapping mapping = session.getItemMappings().getMapping(item);
// return "q.identifier == '" + mapping.getBedrockIdentifier() + "'";
// }).collect(Collectors.joining(" || "));
// if ("minecraft:planks".equals(tag.toString())) {
// String molang = "q.any_tag('minecraft:planks')";
// return new ItemDescriptorWithCount[] {new ItemDescriptorWithCount(new MolangDescriptor(molang, 10), 1)};
// }
return null;
// Set<ItemDescriptorWithCount> itemDescriptors = new HashSet<>();
// for (int item : key) {
// itemDescriptors.add(fromItem(session, item));
// }
// return itemDescriptors.toArray(ItemDescriptorWithCount[]::new);
});
}
}
session.getGeyser().getLogger().warning("Unimplemented slot display type for input: " + slotDisplay);
return null;
}
private Pair<Item, ItemData> translateToOutput(GeyserSession session, SlotDisplay slotDisplay) {
if (slotDisplay instanceof EmptySlotDisplay) {
return null;
}
if (slotDisplay instanceof ItemSlotDisplay itemSlot) {
int item = itemSlot.item();
return Pair.of(Registries.JAVA_ITEMS.get(item), ItemTranslator.translateToBedrock(session, new ItemStack(item)));
}
if (slotDisplay instanceof ItemStackSlotDisplay itemStackSlot) {
ItemStack stack = itemStackSlot.itemStack();
return Pair.of(Registries.JAVA_ITEMS.get(stack.getId()), ItemTranslator.translateToBedrock(session, stack));
}
session.getGeyser().getLogger().warning("Unimplemented slot display type for output: " + slotDisplay);
return null;
}
private ItemDescriptorWithCount fromItem(GeyserSession session, int item) {
if (item == Items.AIR_ID) {
return ItemDescriptorWithCount.EMPTY;
}
ItemMapping mapping = session.getItemMappings().getMapping(item);
return new ItemDescriptorWithCount(new DefaultDescriptor(mapping.getBedrockDefinition(), mapping.getBedrockData()), 1); // Need to check count
}
// private static ItemDescriptorWithCount[][] combinations(ItemDescriptorWithCount[] itemDescriptors) {
// int totalCombinations = 1;
// for (Set<ItemDescriptorWithCount> optionSet : squashedOptions.keySet()) {
// totalCombinations *= optionSet.size();
// }
// if (totalCombinations > 500) {
// ItemDescriptorWithCount[] translatedItems = new ItemDescriptorWithCount[ingredients.length];
// for (int i = 0; i < ingredients.length; i++) {
// if (ingredients[i].getOptions().length > 0) {
// translatedItems[i] = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, ingredients[i].getOptions()[0]));
// } else {
// translatedItems[i] = ItemDescriptorWithCount.EMPTY;
// }
// }
// return new ItemDescriptorWithCount[][]{translatedItems};
// }
// List<Set<ItemDescriptorWithCount>> sortedSets = new ArrayList<>(squashedOptions.keySet());
// sortedSets.sort(Comparator.comparing(Set::size, Comparator.reverseOrder()));
// ItemDescriptorWithCount[][] combinations = new ItemDescriptorWithCount[totalCombinations][ingredients.length];
// int x = 1;
// for (Set<ItemDescriptorWithCount> set : sortedSets) {
// IntSet slotSet = squashedOptions.get(set);
// int i = 0;
// for (ItemDescriptorWithCount item : set) {
// for (int j = 0; j < totalCombinations / set.size(); j++) {
// final int comboIndex = (i * x) + (j % x) + ((j / x) * set.size() * x);
// for (IntIterator it = slotSet.iterator(); it.hasNext(); ) {
// combinations[comboIndex][it.nextInt()] = item;
// }
// }
// i++;
// }
// x *= set.size();
// }
// return recipes;
// }
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2024 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 org.cloudburstmc.protocol.bedrock.packet.UnlockedRecipesPacket;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundRecipeBookRemovePacket;
import java.util.ArrayList;
import java.util.List;
@Translator(packet = ClientboundRecipeBookRemovePacket.class)
public class JavaRecipeBookRemoveTranslator extends PacketTranslator<ClientboundRecipeBookRemovePacket> {
@Override
public void translate(GeyserSession session, ClientboundRecipeBookRemovePacket packet) {
List<String> recipes = getBedrockRecipes(session, packet.getRecipes());
if (recipes.isEmpty()) {
// Sending an empty list here will crash the client as of 1.20.60
return;
}
UnlockedRecipesPacket recipesPacket = new UnlockedRecipesPacket();
recipesPacket.setAction(UnlockedRecipesPacket.ActionType.REMOVE_UNLOCKED);
recipesPacket.getUnlockedRecipes().addAll(recipes);
session.sendUpstreamPacket(recipesPacket);
}
private List<String> getBedrockRecipes(GeyserSession session, int[] javaRecipeIds) {
List<String> recipes = new ArrayList<>();
for (int javaIdentifier : javaRecipeIds) {
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;
}
}

View file

@ -109,6 +109,9 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
* Checks for a changed output slot in the crafting grid, and ensures Bedrock sees the recipe.
*/
private static void updateCraftingGrid(GeyserSession session, int slot, ItemStack item, Inventory inventory, InventoryTranslator translator) {
if (true) {
return;
}
// Check if it's the crafting grid result slot.
if (slot != 0) {
return;