mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-31 16:41:02 +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;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
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;
|
||||
|
|
Loading…
Reference in a new issue