mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-03-22 06:55:33 +01:00
Work on recipes
This commit is contained in:
parent
e78b24830d
commit
e00ef21af4
9 changed files with 365 additions and 98 deletions
|
@ -29,7 +29,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
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 {
|
public interface GeyserRecipe {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -28,13 +28,12 @@ package org.geysermc.geyser.inventory.recipe;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
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.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 record GeyserShapedRecipe(int width, int height, Ingredient[] ingredients, @Nullable ItemStack result) implements GeyserRecipe {
|
||||||
|
|
||||||
public GeyserShapedRecipe(ShapedRecipeData data) {
|
// public GeyserShapedRecipe(ShapedRecipeData data) {
|
||||||
this(data.getWidth(), data.getHeight(), data.getIngredients(), data.getResult());
|
// this(data.getWidth(), data.getHeight(), data.getIngredients(), data.getResult());
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isShaped() {
|
public boolean isShaped() {
|
||||||
|
|
|
@ -25,16 +25,15 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.inventory.recipe;
|
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.item.ItemStack;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
|
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 record GeyserShapelessRecipe(Ingredient[] ingredients, @Nullable ItemStack result) implements GeyserRecipe {
|
||||||
|
|
||||||
public GeyserShapelessRecipe(ShapelessRecipeData data) {
|
// public GeyserShapelessRecipe(ShapelessRecipeData data) {
|
||||||
this(data.getIngredients(), data.getResult());
|
// this(data.getIngredients(), data.getResult());
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isShaped() {
|
public boolean isShaped() {
|
||||||
|
|
|
@ -37,10 +37,17 @@ import org.geysermc.geyser.api.pack.ResourcePack;
|
||||||
import org.geysermc.geyser.entity.EntityDefinition;
|
import org.geysermc.geyser.entity.EntityDefinition;
|
||||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||||
import org.geysermc.geyser.item.type.Item;
|
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.ItemRegistryPopulator;
|
||||||
import org.geysermc.geyser.registry.populator.PacketRegistryPopulator;
|
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.provider.ProviderSupplier;
|
||||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||||
import org.geysermc.geyser.registry.type.ParticleMapping;
|
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.block.BlockEntityType;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEvent;
|
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.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.
|
* 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.
|
* 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.
|
* A mapped registry holding {@link ResourcePack}'s with the pack uuid as keys.
|
||||||
|
|
|
@ -27,29 +27,19 @@ package org.geysermc.geyser.registry.loader;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
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 it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
import org.cloudburstmc.nbt.NBTInputStream;
|
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.cloudburstmc.nbt.NbtType;
|
import org.cloudburstmc.nbt.NbtType;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
|
||||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||||
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
|
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
|
||||||
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
|
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.codec.MinecraftCodecHelper;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
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.item.component.DataComponents;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
|
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.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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
|
* Populates the recipe registry with some recipes that Java does not send, to ensure they show up as intended
|
||||||
* in the recipe book.
|
* 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
|
// @Override
|
||||||
public Map<RecipeType, List<GeyserRecipe>> load(String input) {
|
// public Map<RecipeType, List<GeyserRecipe>> load(String input) {
|
||||||
if (true) {
|
// if (true) {
|
||||||
return Collections.emptyMap();
|
// return Collections.emptyMap();
|
||||||
}
|
// }
|
||||||
Map<RecipeType, List<GeyserRecipe>> deserializedRecipes = new Object2ObjectOpenHashMap<>();
|
// 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;
|
// private static Pair<RecipeType, List<GeyserRecipe>> getRecipes(NbtMap recipes, MinecraftCodecHelper helper) {
|
||||||
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/recipes.nbt")) {
|
// List<NbtMap> typedRecipes = recipes.getList("recipes", NbtType.COMPOUND);
|
||||||
try (NBTInputStream nbtStream = new NBTInputStream(new DataInputStream(stream))) {
|
// RecipeType recipeType = RecipeType.from(recipes.getInt("recipe_type", -1));
|
||||||
recipes = ((NbtMap) nbtStream.readTag()).getList("recipes", NbtType.COMPOUND);
|
// if (recipeType == RecipeType.CRAFTING_SPECIAL_TIPPEDARROW) {
|
||||||
}
|
// return Pair.of(recipeType, getShapedRecipes(typedRecipes, helper));
|
||||||
} catch (Exception e) {
|
// } else {
|
||||||
throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e);
|
// return Pair.of(recipeType, getShapelessRecipes(typedRecipes, helper));
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
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 List<GeyserRecipe> getShapelessRecipes(List<NbtMap> recipes, MinecraftCodecHelper helper) {
|
private static List<GeyserRecipe> getShapelessRecipes(List<NbtMap> recipes, MinecraftCodecHelper helper) {
|
||||||
List<GeyserRecipe> deserializedRecipes = new ObjectArrayList<>(recipes.size());
|
List<GeyserRecipe> deserializedRecipes = new ObjectArrayList<>(recipes.size());
|
||||||
|
|
|
@ -441,10 +441,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
private Entity mouseoverEntity;
|
private Entity mouseoverEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores all Java recipes by recipe identifier, and matches them to all possible Bedrock recipe identifiers.
|
* Stores all Java recipes by ID, 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.
|
|
||||||
*/
|
*/
|
||||||
private final Map<String, List<String>> javaToBedrockRecipeIds;
|
private final Int2ObjectMap<List<String>> javaToBedrockRecipeIds;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private Int2ObjectMap<GeyserRecipe> craftingRecipes;
|
private Int2ObjectMap<GeyserRecipe> craftingRecipes;
|
||||||
|
@ -694,7 +693,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.javaToBedrockRecipeIds = new Int2ObjectOpenHashMap<>();
|
||||||
this.lastRecipeNetId = new AtomicInteger(1);
|
this.lastRecipeNetId = new AtomicInteger(1);
|
||||||
|
|
||||||
this.spawned = false;
|
this.spawned = false;
|
||||||
|
|
|
@ -25,57 +25,258 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.translator.protocol.java;
|
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.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.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.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
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 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)
|
@Translator(packet = ClientboundRecipeBookAddPacket.class)
|
||||||
public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRecipeBookAddPacket> {
|
public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRecipeBookAddPacket> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundRecipeBookAddPacket packet) {
|
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();
|
UnlockedRecipesPacket recipesPacket = new UnlockedRecipesPacket();
|
||||||
recipesPacket.setAction(packet.isReplace() ? UnlockedRecipesPacket.ActionType.INITIALLY_UNLOCKED : UnlockedRecipesPacket.ActionType.NEWLY_UNLOCKED);
|
recipesPacket.setAction(packet.isReplace() ? UnlockedRecipesPacket.ActionType.INITIALLY_UNLOCKED : UnlockedRecipesPacket.ActionType.NEWLY_UNLOCKED);
|
||||||
// List<String> recipes = getBedrockRecipes(session, packet.getEntries());
|
|
||||||
// if (recipes.isEmpty() && !packet.isReplace()) {
|
for (ClientboundRecipeBookAddPacket.Entry entry : packet.getEntries()) {
|
||||||
// // Sending an empty list here packet will crash the client as of 1.20.60
|
RecipeDisplayEntry contents = entry.contents();
|
||||||
// return;
|
RecipeDisplay display = contents.display();
|
||||||
// }
|
|
||||||
// switch (packet.getAction()) {
|
switch (display.getType()) {
|
||||||
// case INIT -> {
|
case CRAFTING_SHAPED -> {
|
||||||
// recipesPacket.setAction(UnlockedRecipesPacket.ActionType.INITIALLY_UNLOCKED);
|
ShapedCraftingRecipeDisplay shapedRecipe = (ShapedCraftingRecipeDisplay) display;
|
||||||
// recipesPacket.getUnlockedRecipes().addAll(getBedrockRecipes(session, packet.getAlreadyKnownRecipes()));
|
Pair<Item, ItemData> pair = translateToOutput(session, shapedRecipe.result());
|
||||||
// }
|
if (pair == null || !pair.right().isValid()) {
|
||||||
// case ADD -> {
|
// Likely modded item Bedrock will complain about
|
||||||
//
|
continue;
|
||||||
// recipesPacket.setAction(UnlockedRecipesPacket.ActionType.NEWLY_UNLOCKED);
|
}
|
||||||
// recipesPacket.getUnlockedRecipes().addAll(recipes);
|
|
||||||
// }
|
ItemData output = pair.right();
|
||||||
// case REMOVE -> {
|
if (!(pair.left() instanceof BedrockRequiresTagItem)) {
|
||||||
// List<String> recipes = getBedrockRecipes(session, packet.getRecipes());
|
// Strip NBT - tools won't appear in the recipe book otherwise
|
||||||
// if (recipes.isEmpty()) {
|
output = output.toBuilder().tag(null).build();
|
||||||
// // Sending an empty list here will crash the client as of 1.20.60
|
}
|
||||||
// return;
|
|
||||||
// }
|
boolean empty = true;
|
||||||
// recipesPacket.setAction(UnlockedRecipesPacket.ActionType.REMOVE_UNLOCKED);
|
boolean complexInputs = false;
|
||||||
// recipesPacket.getUnlockedRecipes().addAll(recipes);
|
List<ItemDescriptorWithCount[]> inputs = new ArrayList<>(shapedRecipe.ingredients().size());
|
||||||
// }
|
for (SlotDisplay input : shapedRecipe.ingredients()) {
|
||||||
// }
|
ItemDescriptorWithCount[] translated = translateToInput(session, input);
|
||||||
// session.sendUpstreamPacket(recipesPacket);
|
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) {
|
private static final ThreadLocal<Map<int[], ItemDescriptorWithCount[]>> TAG_TO_ITEM_DESCRIPTOR_CACHE = ThreadLocal.withInitial(Object2ObjectOpenHashMap::new);
|
||||||
// List<String> recipes = new ArrayList<>();
|
|
||||||
// for (String javaIdentifier : javaRecipeIdentifiers) {
|
private ItemDescriptorWithCount[] translateToInput(GeyserSession session, SlotDisplay slotDisplay) {
|
||||||
// List<String> bedrockRecipes = session.getJavaToBedrockRecipeIds().get(javaIdentifier);
|
if (slotDisplay instanceof EmptySlotDisplay) {
|
||||||
// // Some recipes are not (un)lockable on Bedrock edition, like furnace or stonecutter recipes.
|
return new ItemDescriptorWithCount[] {ItemDescriptorWithCount.EMPTY};
|
||||||
// // So we don't store/send these.
|
}
|
||||||
// if (bedrockRecipes != null) {
|
if (slotDisplay instanceof CompositeSlotDisplay composite) {
|
||||||
// recipes.addAll(bedrockRecipes);
|
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;
|
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
* 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) {
|
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.
|
// Check if it's the crafting grid result slot.
|
||||||
if (slot != 0) {
|
if (slot != 0) {
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Add table
Reference in a new issue