Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-12-25 15:50:14 +01:00
Refactor static recipe loading
The only recipes added should be the ones that are sent on Bedrock, so it appears in the recipe book. Every other recipe will be handled through our fallback system.
Dieser Commit ist enthalten in:
Ursprung
a42c979abb
Commit
ecffb564ed
@ -25,6 +25,9 @@
|
||||
|
||||
package org.geysermc.geyser.inventory.recipe;
|
||||
|
||||
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}.
|
||||
*/
|
||||
@ -33,4 +36,7 @@ public interface GeyserRecipe {
|
||||
* Whether the recipe is flexible or not in which items can be placed where.
|
||||
*/
|
||||
boolean isShaped();
|
||||
|
||||
@Nullable
|
||||
ItemStack result();
|
||||
}
|
||||
|
@ -60,9 +60,6 @@ public class BannerItem extends BlockItem {
|
||||
*/
|
||||
private static final List<Pair<BannerPattern, DyeColor>> OMINOUS_BANNER_PATTERN;
|
||||
|
||||
// TODO fix - we somehow need to be able to get the sessions banner pattern registry, which we don't have where we need this :/
|
||||
private static final int[] ominousBannerPattern = new int[] { 21, 29, 30, 1, 34, 15, 3, 1 };
|
||||
|
||||
static {
|
||||
// Construct what an ominous banner is supposed to look like
|
||||
OMINOUS_BANNER_PATTERN = List.of(
|
||||
@ -215,20 +212,22 @@ public class BannerItem extends BlockItem {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToJava(@NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
|
||||
super.translateNbtToJava(bedrockTag, components, mapping);
|
||||
public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
|
||||
super.translateNbtToJava(session, bedrockTag, components, mapping);
|
||||
|
||||
if (bedrockTag.getInt("Type") == 1) {
|
||||
// Ominous banner pattern
|
||||
List<BannerPatternLayer> patternLayers = new ArrayList<>();
|
||||
for (int i = 0; i < ominousBannerPattern.length; i++) {
|
||||
patternLayers.add(new BannerPatternLayer(Holder.ofId(ominousBannerPattern[i]), OMINOUS_BANNER_PATTERN.get(i).right().ordinal()));
|
||||
for (int i = 0; i < OMINOUS_BANNER_PATTERN.size(); i++) {
|
||||
var pair = OMINOUS_BANNER_PATTERN.get(i);
|
||||
patternLayers.add(new BannerPatternLayer(Holder.ofId(session.getRegistryCache().bannerPatterns().byValue(pair.left())),
|
||||
pair.right().ordinal()));
|
||||
}
|
||||
|
||||
components.put(DataComponentType.BANNER_PATTERNS, patternLayers);
|
||||
components.put(DataComponentType.HIDE_ADDITIONAL_TOOLTIP, Unit.INSTANCE);
|
||||
components.put(DataComponentType.ITEM_NAME, Component
|
||||
.translatable("block.minecraft.ominous_banner") // thank god this works
|
||||
.translatable("block.minecraft.ominous_banner")
|
||||
.style(Style.style(TextColor.color(16755200)))
|
||||
);
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.item.BedrockEnchantment;
|
||||
import org.geysermc.geyser.item.enchantment.Enchantment;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
@ -69,8 +70,8 @@ public class EnchantedBookItem extends Item {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToJava(@NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
|
||||
super.translateNbtToJava(bedrockTag, components, mapping);
|
||||
public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
|
||||
super.translateNbtToJava(session, bedrockTag, components, mapping);
|
||||
|
||||
List<NbtMap> enchantmentTag = bedrockTag.getList("ench", NbtType.COMPOUND);
|
||||
if (enchantmentTag != null) {
|
||||
@ -80,9 +81,14 @@ public class EnchantedBookItem extends Item {
|
||||
|
||||
BedrockEnchantment enchantment = BedrockEnchantment.getByBedrockId(bedrockId);
|
||||
if (enchantment != null) {
|
||||
int level = bedrockEnchantment.getShort("lvl", (short) 1);
|
||||
// TODO
|
||||
//javaEnchantments.put(BedrockEnchantment.JavaEnchantment.valueOf(enchantment.name()).ordinal(), level);
|
||||
List<Enchantment> enchantments = session.getRegistryCache().enchantments().values();
|
||||
for (int i = 0; i < enchantments.size(); i++) {
|
||||
if (enchantments.get(i).bedrockEnchantment() == enchantment) {
|
||||
int level = bedrockEnchantment.getShort("lvl", (short) 1);
|
||||
javaEnchantments.put(i, level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().debug("Unknown bedrock enchantment: " + bedrockId);
|
||||
}
|
||||
|
@ -70,8 +70,8 @@ public class FireworkRocketItem extends Item {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToJava(@NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
|
||||
super.translateNbtToJava(bedrockTag, components, mapping);
|
||||
public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
|
||||
super.translateNbtToJava(session, bedrockTag, components, mapping);
|
||||
|
||||
NbtMap fireworksTag = bedrockTag.getCompound("Fireworks");
|
||||
if (!fireworksTag.isEmpty()) {
|
||||
|
@ -78,8 +78,8 @@ public class FireworkStarItem extends Item {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToJava(@NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
|
||||
super.translateNbtToJava(bedrockTag, components, mapping);
|
||||
public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
|
||||
super.translateNbtToJava(session, bedrockTag, components, mapping);
|
||||
|
||||
NbtMap explosion = bedrockTag.getCompound("FireworksItem");
|
||||
if (!explosion.isEmpty()) {
|
||||
|
@ -172,7 +172,7 @@ public class Item {
|
||||
* </ul>
|
||||
* Therefore, if translation cannot be achieved for a certain item, it is not necessarily bad.
|
||||
*/
|
||||
public void translateNbtToJava(@NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
|
||||
public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
|
||||
// TODO see if any items from the creative menu need this
|
||||
// CompoundTag displayTag = tag.get("display");
|
||||
// if (displayTag != null) {
|
||||
@ -190,41 +190,6 @@ public class Item {
|
||||
// }
|
||||
// displayTag.put(new ListTag("Lore", lore));
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO no creative item should have enchantments *except* enchanted books
|
||||
// List<NbtMap> enchantmentTag = bedrockTag.getList("ench", NbtType.COMPOUND);
|
||||
// if (enchantmentTag != null) {
|
||||
// List<Tag> enchantments = new ArrayList<>();
|
||||
// for (Tag value : enchantmentTag.getValue()) {
|
||||
// if (!(value instanceof CompoundTag tagValue))
|
||||
// continue;
|
||||
//
|
||||
// ShortTag bedrockId = tagValue.get("id");
|
||||
// if (bedrockId == null) continue;
|
||||
//
|
||||
// BedrockEnchantment enchantment = BedrockEnchantment.getByBedrockId(bedrockId.getValue());
|
||||
// if (enchantment != null) {
|
||||
// CompoundTag javaTag = new CompoundTag("");
|
||||
// Map<String, Tag> javaValue = javaTag.getValue();
|
||||
// javaValue.put("id", new StringTag("id", enchantment.getJavaIdentifier()));
|
||||
// ShortTag levelTag = tagValue.get("lvl");
|
||||
// javaValue.put("lvl", new IntTag("lvl", levelTag != null ? levelTag.getValue() : 1));
|
||||
// javaTag.setValue(javaValue);
|
||||
//
|
||||
// enchantments.add(javaTag);
|
||||
// } else {
|
||||
// GeyserImpl.getInstance().getLogger().debug("Unknown bedrock enchantment: " + bedrockId);
|
||||
// }
|
||||
// }
|
||||
// if (!enchantments.isEmpty()) {
|
||||
// if ((this instanceof EnchantedBookItem)) {
|
||||
// bedrockTag.put(new ListTag("StoredEnchantments", enchantments));
|
||||
// components.put(DataComponentType.STORED_ENCHANTMENTS, enchantments);
|
||||
// } else {
|
||||
// components.put(DataComponentType.ENCHANTMENTS, enchantments);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
@ -25,14 +25,12 @@
|
||||
|
||||
package org.geysermc.geyser.registry;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.PotionMixData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
@ -42,7 +40,7 @@ import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.registry.loader.*;
|
||||
import org.geysermc.geyser.registry.populator.ItemRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.populator.PacketRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.populator.RecipeRegistryPopulator;
|
||||
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;
|
||||
@ -95,11 +93,6 @@ public final class Registries {
|
||||
*/
|
||||
public static final SimpleMappedRegistry<BlockEntityType, BlockEntityTranslator> BLOCK_ENTITIES = SimpleMappedRegistry.create("org.geysermc.geyser.translator.level.block.entity.BlockEntity", BlockEntityRegistryLoader::new);
|
||||
|
||||
/**
|
||||
* A versioned registry which holds a {@link RecipeType} to a corresponding list of {@link RecipeData}.
|
||||
*/
|
||||
public static final VersionedRegistry<Map<RecipeType, List<RecipeData>>> CRAFTING_DATA = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||
|
||||
/**
|
||||
* A map containing all entity types and their respective Geyser definitions
|
||||
*/
|
||||
@ -147,7 +140,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 VersionedRegistry<Int2ObjectMap<GeyserRecipe>> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::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.
|
||||
@ -176,7 +169,7 @@ public final class Registries {
|
||||
static {
|
||||
PacketRegistryPopulator.populate();
|
||||
ItemRegistryPopulator.populate();
|
||||
RecipeRegistryPopulator.populate();
|
||||
System.out.println(RECIPES.get());
|
||||
|
||||
// Create registries that require other registries to load first
|
||||
POTION_MIXES = VersionedRegistry.create(PotionMixRegistryLoader::new);
|
||||
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (c) 2019-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.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.List;
|
||||
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>>> {
|
||||
|
||||
@Override
|
||||
public Map<RecipeType, List<GeyserRecipe>> load(String input) {
|
||||
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;
|
||||
}
|
||||
|
||||
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());
|
||||
for (NbtMap recipe : recipes) {
|
||||
ItemStack output = toItemStack(recipe.getCompound("output"), helper);
|
||||
List<NbtMap> rawInputs = recipe.getList("inputs", NbtType.COMPOUND);
|
||||
Ingredient[] javaInputs = new Ingredient[rawInputs.size()];
|
||||
for (int i = 0; i < rawInputs.size(); i++) {
|
||||
javaInputs[i] = new Ingredient(new ItemStack[] {toItemStack(rawInputs.get(i), helper)});
|
||||
}
|
||||
deserializedRecipes.add(new GeyserShapelessRecipe(javaInputs, output));
|
||||
}
|
||||
return deserializedRecipes;
|
||||
}
|
||||
|
||||
private static List<GeyserRecipe> getShapedRecipes(List<NbtMap> recipes, MinecraftCodecHelper helper) {
|
||||
List<GeyserRecipe> deserializedRecipes = new ObjectArrayList<>(recipes.size());
|
||||
for (NbtMap recipe : recipes) {
|
||||
ItemStack output = toItemStack(recipe.getCompound("output"), helper);
|
||||
List<int[]> shape = recipe.getList("shape", NbtType.INT_ARRAY);
|
||||
|
||||
// In the recipes mapping, each recipe is mapped by a number
|
||||
List<ItemStack> letterToRecipe = new ArrayList<>();
|
||||
for (NbtMap rawInput : recipe.getList("inputs", NbtType.COMPOUND)) {
|
||||
letterToRecipe.add(toItemStack(rawInput, helper));
|
||||
}
|
||||
|
||||
Ingredient[] inputs = new Ingredient[shape.size() * shape.get(0).length];
|
||||
int i = 0;
|
||||
// Create a linear array of items from the "cube" of the shape
|
||||
for (int j = 0; i < shape.size() * shape.get(0).length; j++) {
|
||||
for (int index : shape.get(j)) {
|
||||
ItemStack stack = letterToRecipe.get(index);
|
||||
inputs[i++] = new Ingredient(new ItemStack[] {stack});
|
||||
}
|
||||
}
|
||||
deserializedRecipes.add(new GeyserShapedRecipe(shape.size(), shape.get(0).length, inputs, output));
|
||||
}
|
||||
return deserializedRecipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts our serialized NBT into an ItemStack.
|
||||
* id is the Java item ID as an integer, components is an optional String of the data components serialized
|
||||
* as bytes in Base64 (so MCProtocolLib can parse the data).
|
||||
*/
|
||||
private static ItemStack toItemStack(NbtMap nbt, MinecraftCodecHelper helper) {
|
||||
int id = nbt.getInt("id");
|
||||
int count = nbt.getInt("count");
|
||||
String componentsRaw = nbt.getString("components", null);
|
||||
if (componentsRaw != null) {
|
||||
byte[] bytes = Base64.getDecoder().decode(componentsRaw);
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(bytes);
|
||||
DataComponents components = helper.readDataComponentPatch(buf);
|
||||
return new ItemStack(id, count, components);
|
||||
}
|
||||
return new ItemStack(id, count);
|
||||
}
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 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.registry.populator;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
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.RecipeType;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtUtils;
|
||||
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.MultiRecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
|
||||
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.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.translator.item.ItemTranslator;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
|
||||
import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID;
|
||||
|
||||
/**
|
||||
* Populates the recipe registry.
|
||||
*/
|
||||
public class RecipeRegistryPopulator {
|
||||
|
||||
public static void populate() {
|
||||
JsonNode items;
|
||||
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/recipes.json")) {
|
||||
items = GeyserImpl.JSON_MAPPER.readTree(stream);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e);
|
||||
}
|
||||
|
||||
int currentRecipeId = LAST_RECIPE_NET_ID;
|
||||
for (Int2ObjectMap.Entry<ItemMappings> version : Registries.ITEMS.get().int2ObjectEntrySet()) {
|
||||
// Make a bit of an assumption here that the last recipe net ID will be equivalent between all versions
|
||||
LAST_RECIPE_NET_ID = currentRecipeId;
|
||||
Map<RecipeType, List<RecipeData>> craftingData = new EnumMap<>(RecipeType.class);
|
||||
Int2ObjectMap<GeyserRecipe> recipes = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
craftingData.put(RecipeType.CRAFTING_SPECIAL_BOOKCLONING,
|
||||
Collections.singletonList(MultiRecipeData.of(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d"), ++LAST_RECIPE_NET_ID)));
|
||||
craftingData.put(RecipeType.CRAFTING_SPECIAL_REPAIRITEM,
|
||||
Collections.singletonList(MultiRecipeData.of(UUID.fromString("00000000-0000-0000-0000-000000000001"), ++LAST_RECIPE_NET_ID)));
|
||||
craftingData.put(RecipeType.CRAFTING_SPECIAL_MAPEXTENDING,
|
||||
Collections.singletonList(MultiRecipeData.of(UUID.fromString("d392b075-4ba1-40ae-8789-af868d56f6ce"), ++LAST_RECIPE_NET_ID)));
|
||||
craftingData.put(RecipeType.CRAFTING_SPECIAL_MAPCLONING,
|
||||
Collections.singletonList(MultiRecipeData.of(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), ++LAST_RECIPE_NET_ID)));
|
||||
|
||||
// https://github.com/pmmp/PocketMine-MP/blob/stable/src/pocketmine/inventory/MultiRecipe.php
|
||||
|
||||
for (JsonNode entry : items.get("leather_armor")) {
|
||||
// This won't be perfect, as we can't possibly send every leather input for every kind of color
|
||||
// But it does display the correct output from a base leather armor, and besides visuals everything works fine
|
||||
craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_ARMORDYE,
|
||||
c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue()));
|
||||
}
|
||||
for (JsonNode entry : items.get("firework_rockets")) {
|
||||
craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_FIREWORK_ROCKET,
|
||||
c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue()));
|
||||
}
|
||||
for (JsonNode entry : items.get("firework_stars")) {
|
||||
craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_FIREWORK_STAR,
|
||||
c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue()));
|
||||
}
|
||||
for (JsonNode entry : items.get("shulker_boxes")) {
|
||||
craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_SHULKERBOXCOLORING,
|
||||
c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue()));
|
||||
}
|
||||
for (JsonNode entry : items.get("suspicious_stew")) {
|
||||
craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_SUSPICIOUSSTEW,
|
||||
c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue()));
|
||||
}
|
||||
for (JsonNode entry : items.get("tipped_arrows")) {
|
||||
craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_TIPPEDARROW,
|
||||
c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue()));
|
||||
}
|
||||
|
||||
Registries.CRAFTING_DATA.register(version.getIntKey(), craftingData);
|
||||
Registries.RECIPES.register(version.getIntKey(), recipes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a Bedrock crafting recipe from the given JSON data.
|
||||
* @param node the JSON data to compute
|
||||
* @param recipes a list of all the recipes
|
||||
* @return the {@link RecipeData} to send to the Bedrock client.
|
||||
*/
|
||||
private static RecipeData getCraftingDataFromJsonNode(JsonNode node, Int2ObjectMap<GeyserRecipe> recipes, ItemMappings mappings) {
|
||||
int netId = ++LAST_RECIPE_NET_ID;
|
||||
int type = node.get("bedrockRecipeType").asInt();
|
||||
JsonNode outputNode = node.get("output");
|
||||
ItemMapping outputEntry = mappings.getMapping(outputNode.get("identifier").asText());
|
||||
ItemData output = getBedrockItemFromIdentifierJson(outputEntry, outputNode);
|
||||
UUID uuid = UUID.randomUUID();
|
||||
if (type == 1) {
|
||||
// Shaped recipe
|
||||
List<String> shape = new ArrayList<>();
|
||||
// Get the shape of the recipe
|
||||
for (JsonNode chars : node.get("shape")) {
|
||||
shape.add(chars.asText());
|
||||
}
|
||||
|
||||
// In recipes.json each recipe is mapped by a letter
|
||||
Map<String, ItemData> letterToRecipe = new HashMap<>();
|
||||
Iterator<Map.Entry<String, JsonNode>> iterator = node.get("inputs").fields();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = iterator.next();
|
||||
JsonNode inputNode = entry.getValue();
|
||||
ItemMapping inputEntry = mappings.getMapping(inputNode.get("identifier").asText());
|
||||
letterToRecipe.put(entry.getKey(), getBedrockItemFromIdentifierJson(inputEntry, inputNode));
|
||||
}
|
||||
|
||||
List<ItemData> inputs = new ArrayList<>(shape.size() * shape.get(0).length());
|
||||
int i = 0;
|
||||
// Create a linear array of items from the "cube" of the shape
|
||||
for (int j = 0; i < shape.size() * shape.get(0).length(); j++) {
|
||||
for (char c : shape.get(j).toCharArray()) {
|
||||
ItemData data = letterToRecipe.getOrDefault(String.valueOf(c), ItemData.AIR);
|
||||
inputs.add(data);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert into a Java recipe class for autocrafting */
|
||||
List<Ingredient> ingredients = new ArrayList<>();
|
||||
for (ItemData input : inputs) {
|
||||
ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)}));
|
||||
}
|
||||
GeyserRecipe recipe = new GeyserShapedRecipe(shape.get(0).length(), shape.size(),
|
||||
ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings));
|
||||
recipes.put(netId, recipe);
|
||||
/* Convert end */
|
||||
|
||||
return ShapedRecipeData.shaped(uuid.toString(), shape.get(0).length(), shape.size(),
|
||||
inputs.stream().map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(output), uuid, "crafting_table", 0, netId, false, RecipeUnlockingRequirement.INVALID);
|
||||
}
|
||||
List<ItemData> inputs = new ObjectArrayList<>();
|
||||
for (JsonNode entry : node.get("inputs")) {
|
||||
ItemMapping inputEntry = mappings.getMapping(entry.get("identifier").asText());
|
||||
inputs.add(getBedrockItemFromIdentifierJson(inputEntry, entry));
|
||||
}
|
||||
|
||||
/* Convert into a Java Recipe class for autocrafting */
|
||||
List<Ingredient> ingredients = new ArrayList<>();
|
||||
for (ItemData input : inputs) {
|
||||
ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)}));
|
||||
}
|
||||
GeyserRecipe recipe = new GeyserShapelessRecipe(ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings));
|
||||
recipes.put(netId, recipe);
|
||||
/* Convert end */
|
||||
|
||||
if (type == 5) {
|
||||
// Shulker box
|
||||
return ShapelessRecipeData.shulkerBox(uuid.toString(),
|
||||
inputs.stream().map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(output), uuid, "crafting_table", 0, netId);
|
||||
}
|
||||
return ShapelessRecipeData.shapeless(uuid.toString(),
|
||||
inputs.stream().map(ItemDescriptorWithCount::fromItem).toList(), Collections.singletonList(output), uuid, "crafting_table", 0, netId, RecipeUnlockingRequirement.INVALID);
|
||||
}
|
||||
|
||||
private static ItemData getBedrockItemFromIdentifierJson(ItemMapping mapping, JsonNode itemNode) {
|
||||
int count = 1;
|
||||
short damage = 0;
|
||||
NbtMap tag = null;
|
||||
JsonNode damageNode = itemNode.get("bedrockDamage");
|
||||
if (damageNode != null) {
|
||||
damage = damageNode.numberValue().shortValue();
|
||||
}
|
||||
JsonNode countNode = itemNode.get("count");
|
||||
if (countNode != null) {
|
||||
count = countNode.asInt();
|
||||
}
|
||||
JsonNode nbtNode = itemNode.get("bedrockNbt");
|
||||
if (nbtNode != null) {
|
||||
byte[] bytes = Base64.getDecoder().decode(nbtNode.asText());
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
try {
|
||||
tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return ItemData.builder()
|
||||
.definition(mapping.getBedrockDefinition())
|
||||
.damage(damage)
|
||||
.count(count)
|
||||
.blockDefinition(mapping.getBedrockBlockDefinition())
|
||||
.tag(tag)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -356,8 +356,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
* Stores all Java recipes by recipe identifier, and matches them to all possible Bedrock recipe identifiers.
|
||||
* They are not 1:1, since Bedrock can have multiple recipes for the same Java recipe.
|
||||
*/
|
||||
@Setter
|
||||
private Map<String, List<String>> javaToBedrockRecipeIds;
|
||||
private final Map<String, List<String>> javaToBedrockRecipeIds;
|
||||
|
||||
@Setter
|
||||
private Int2ObjectMap<GeyserRecipe> craftingRecipes;
|
||||
|
@ -423,7 +423,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
||||
}
|
||||
// Reference the creative items list we send to the client to know what it's asking of us
|
||||
ItemData creativeItem = creativeItems[creativeId];
|
||||
javaCreativeItem = ItemTranslator.translateToJava(creativeItem, session.getItemMappings());
|
||||
javaCreativeItem = ItemTranslator.translateToJava(session, creativeItem);
|
||||
break;
|
||||
}
|
||||
case CRAFT_RESULTS_DEPRECATED: {
|
||||
|
@ -47,7 +47,6 @@ import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.CustomSkull;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
@ -83,25 +82,21 @@ public final class ItemTranslator {
|
||||
private ItemTranslator() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mappings item mappings to use while translating. This can't just be a Geyser session as this method is used
|
||||
* when loading recipes.
|
||||
*/
|
||||
public static ItemStack translateToJava(ItemData data, ItemMappings mappings) {
|
||||
public static ItemStack translateToJava(GeyserSession session, ItemData data) {
|
||||
if (data == null) {
|
||||
return new ItemStack(Items.AIR_ID);
|
||||
}
|
||||
|
||||
ItemMapping bedrockItem = mappings.getMapping(data);
|
||||
ItemMapping bedrockItem = session.getItemMappings().getMapping(data);
|
||||
Item javaItem = bedrockItem.getJavaItem();
|
||||
|
||||
GeyserItemStack itemStack = javaItem.translateToJava(data, bedrockItem, mappings);
|
||||
GeyserItemStack itemStack = javaItem.translateToJava(data, bedrockItem, session.getItemMappings());
|
||||
|
||||
NbtMap nbt = data.getTag();
|
||||
if (nbt != null && !nbt.isEmpty()) {
|
||||
// translateToJava may have added components
|
||||
DataComponents components = itemStack.getComponents() == null ? new DataComponents(new HashMap<>()) : itemStack.getComponents();
|
||||
javaItem.translateNbtToJava(nbt, components, bedrockItem);
|
||||
javaItem.translateNbtToJava(session, nbt, components, bedrockItem);
|
||||
if (!components.getDataComponents().isEmpty()) {
|
||||
itemStack.setComponents(components);
|
||||
}
|
||||
|
@ -103,66 +103,31 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundUpdateRecipesPacket packet) {
|
||||
Map<RecipeType, List<RecipeData>> recipeTypes = Registries.CRAFTING_DATA.forVersion(session.getUpstream().getProtocolVersion());
|
||||
// Get the last known network ID (first used for the pregenerated recipes) and increment from there.
|
||||
int netId = InventoryUtils.LAST_RECIPE_NET_ID + 1;
|
||||
boolean sendTrimRecipes = false;
|
||||
Map<String, List<String>> recipeIDs = session.getJavaToBedrockRecipeIds();
|
||||
Int2ObjectMap<GeyserRecipe> recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion()));
|
||||
recipeIDs.clear();
|
||||
Int2ObjectMap<GeyserRecipe> recipeMap = new Int2ObjectOpenHashMap<>();
|
||||
Int2ObjectMap<List<StoneCuttingRecipeData>> unsortedStonecutterData = new Int2ObjectOpenHashMap<>();
|
||||
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
|
||||
craftingDataPacket.setCleanRecipes(true);
|
||||
|
||||
RecipeContext context = new RecipeContext(session, craftingDataPacket, recipeMap);
|
||||
|
||||
for (Recipe recipe : packet.getRecipes()) {
|
||||
switch (recipe.getType()) {
|
||||
case CRAFTING_SHAPELESS -> {
|
||||
ShapelessRecipeData shapelessRecipeData = (ShapelessRecipeData) recipe.getData();
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, shapelessRecipeData.getResult());
|
||||
if (!output.isValid()) {
|
||||
// Likely modded item that Bedrock will complain about if it persists
|
||||
continue;
|
||||
List<String> bedrockRecipeIDs = context.translateShapelessRecipe(new GeyserShapelessRecipe(shapelessRecipeData));
|
||||
if (bedrockRecipeIDs != null) {
|
||||
context.addRecipeIdentifier(session, recipe.getIdentifier(), bedrockRecipeIDs);
|
||||
}
|
||||
// Strip NBT - tools won't appear in the recipe book otherwise
|
||||
output = output.toBuilder().tag(null).build();
|
||||
ItemDescriptorWithCount[][] inputCombinations = combinations(session, shapelessRecipeData.getIngredients());
|
||||
if (inputCombinations == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> bedrockRecipeIDs = new ArrayList<>();
|
||||
for (ItemDescriptorWithCount[] inputs : inputCombinations) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
bedrockRecipeIDs.add(uuid.toString());
|
||||
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shapeless(uuid.toString(),
|
||||
Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId, RecipeUnlockingRequirement.INVALID));
|
||||
recipeMap.put(netId++, new GeyserShapelessRecipe(shapelessRecipeData));
|
||||
}
|
||||
addRecipeIdentifier(session, recipe.getIdentifier(), bedrockRecipeIDs);
|
||||
}
|
||||
case CRAFTING_SHAPED -> {
|
||||
ShapedRecipeData shapedRecipeData = (ShapedRecipeData) recipe.getData();
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, shapedRecipeData.getResult());
|
||||
if (!output.isValid()) {
|
||||
// Likely modded item that Bedrock will complain about if it persists
|
||||
continue;
|
||||
List<String> bedrockRecipeIDs = context.translateShapedRecipe(new GeyserShapedRecipe(shapedRecipeData));
|
||||
if (bedrockRecipeIDs != null) {
|
||||
context.addRecipeIdentifier(session, recipe.getIdentifier(), bedrockRecipeIDs);
|
||||
}
|
||||
// See above
|
||||
output = output.toBuilder().tag(null).build();
|
||||
ItemDescriptorWithCount[][] inputCombinations = combinations(session, shapedRecipeData.getIngredients());
|
||||
if (inputCombinations == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> bedrockRecipeIDs = new ArrayList<>();
|
||||
for (ItemDescriptorWithCount[] inputs : inputCombinations) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
bedrockRecipeIDs.add(uuid.toString());
|
||||
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData.shaped(uuid.toString(),
|
||||
shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), Arrays.asList(inputs),
|
||||
Collections.singletonList(output), uuid, "crafting_table", 0, netId, false, RecipeUnlockingRequirement.INVALID));
|
||||
recipeMap.put(netId++, new GeyserShapedRecipe(shapedRecipeData));
|
||||
}
|
||||
addRecipeIdentifier(session, recipe.getIdentifier(), bedrockRecipeIDs);
|
||||
}
|
||||
case STONECUTTING -> {
|
||||
StoneCuttingRecipeData stoneCuttingData = (StoneCuttingRecipeData) recipe.getData();
|
||||
@ -198,7 +163,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
String id = recipe.getIdentifier();
|
||||
// Note: vanilla inputs use aux value of Short.MAX_VALUE
|
||||
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTransformRecipeData.of(id,
|
||||
bedrockTemplate, bedrockBase, bedrockAddition, output, "smithing_table", netId++));
|
||||
bedrockTemplate, bedrockBase, bedrockAddition, output, "smithing_table", context.getAndIncrementNetId()));
|
||||
|
||||
recipeIDs.put(id, new ArrayList<>(Collections.singletonList(id)));
|
||||
}
|
||||
@ -212,13 +177,48 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
case CRAFTING_DECORATED_POT -> {
|
||||
// Paper 1.20 seems to send only one recipe, which seems to be hardcoded to include all recipes.
|
||||
// We can send the equivalent Bedrock MultiRecipe! :)
|
||||
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("685a742a-c42e-4a4e-88ea-5eb83fc98e5b"), netId++));
|
||||
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("685a742a-c42e-4a4e-88ea-5eb83fc98e5b"), context.getAndIncrementNetId()));
|
||||
}
|
||||
case CRAFTING_SPECIAL_BOOKCLONING -> {
|
||||
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d"), context.getAndIncrementNetId()));
|
||||
}
|
||||
case CRAFTING_SPECIAL_REPAIRITEM -> {
|
||||
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("00000000-0000-0000-0000-000000000001"), context.getAndIncrementNetId()));
|
||||
}
|
||||
case CRAFTING_SPECIAL_MAPEXTENDING -> {
|
||||
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("d392b075-4ba1-40ae-8789-af868d56f6ce"), context.getAndIncrementNetId()));
|
||||
}
|
||||
case CRAFTING_SPECIAL_MAPCLONING -> {
|
||||
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), context.getAndIncrementNetId()));
|
||||
}
|
||||
default -> {
|
||||
List<RecipeData> craftingData = recipeTypes.get(recipe.getType());
|
||||
if (craftingData != null) {
|
||||
addSpecialRecipesIdentifiers(session, recipe, craftingData);
|
||||
craftingDataPacket.getCraftingData().addAll(craftingData);
|
||||
List<GeyserRecipe> recipes = Registries.RECIPES.get(recipe.getType());
|
||||
if (recipes != null) {
|
||||
List<String> bedrockRecipeIds = new ArrayList<>();
|
||||
if (recipe.getType() == RecipeType.CRAFTING_SPECIAL_TIPPEDARROW) {
|
||||
// Only shaped recipe at this moment
|
||||
for (GeyserRecipe builtInRecipe : recipes) {
|
||||
var recipeIds = context.translateShapedRecipe((GeyserShapedRecipe) builtInRecipe);
|
||||
if (recipeIds != null) {
|
||||
bedrockRecipeIds.addAll(recipeIds);
|
||||
}
|
||||
}
|
||||
} else if (recipe.getType() == RecipeType.CRAFTING_SPECIAL_SHULKERBOXCOLORING) {
|
||||
for (GeyserRecipe builtInRecipe : recipes) {
|
||||
var recipeIds = context.translateShulkerBoxRecipe((GeyserShapelessRecipe) builtInRecipe);
|
||||
if (recipeIds != null) {
|
||||
bedrockRecipeIds.addAll(recipeIds);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (GeyserRecipe builtInRecipe : recipes) {
|
||||
var recipeIds = context.translateShapelessRecipe((GeyserShapelessRecipe) builtInRecipe);
|
||||
if (recipeIds != null) {
|
||||
bedrockRecipeIds.addAll(recipeIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
context.addSpecialRecipesIdentifiers(recipe, bedrockRecipeIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -250,17 +250,17 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
UUID uuid = UUID.randomUUID();
|
||||
// We need to register stonecutting recipes, so they show up on Bedrock
|
||||
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shapeless(uuid.toString(),
|
||||
Collections.singletonList(descriptor), Collections.singletonList(output), uuid, "stonecutter", 0, netId, RecipeUnlockingRequirement.INVALID));
|
||||
Collections.singletonList(descriptor), Collections.singletonList(output), uuid, "stonecutter", 0, context.netId, RecipeUnlockingRequirement.INVALID));
|
||||
|
||||
// Save the recipe list for reference when crafting
|
||||
// Add the net ID as the key and the button required + output for the value
|
||||
stonecutterRecipeMap.put(netId++, new GeyserStonecutterData(buttonId++, javaOutput));
|
||||
stonecutterRecipeMap.put(context.getAndIncrementNetId(), new GeyserStonecutterData(buttonId++, javaOutput));
|
||||
|
||||
// Currently, stone cutter recipes are not locked/unlocked on Bedrock; so no need to cache their identifiers.
|
||||
}
|
||||
}
|
||||
|
||||
session.getLastRecipeNetId().set(netId);
|
||||
session.getLastRecipeNetId().set(context.netId); // No increment
|
||||
|
||||
// Only send smithing trim recipes if Java/ViaVersion sends them.
|
||||
if (sendTrimRecipes) {
|
||||
@ -282,38 +282,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
session.sendUpstreamPacket(craftingDataPacket);
|
||||
session.setCraftingRecipes(recipeMap);
|
||||
session.setStonecutterRecipes(stonecutterRecipeMap);
|
||||
session.setJavaToBedrockRecipeIds(recipeIDs);
|
||||
}
|
||||
|
||||
private void addSpecialRecipesIdentifiers(GeyserSession session, Recipe recipe, List<RecipeData> craftingData) {
|
||||
String javaRecipeID = recipe.getIdentifier();
|
||||
|
||||
switch (recipe.getType()) {
|
||||
case CRAFTING_SPECIAL_BOOKCLONING, CRAFTING_SPECIAL_REPAIRITEM, CRAFTING_SPECIAL_MAPEXTENDING, CRAFTING_SPECIAL_MAPCLONING:
|
||||
// We do not want to (un)lock these, since BDS does not do it for MultiRecipes
|
||||
return;
|
||||
case CRAFTING_SPECIAL_SHULKERBOXCOLORING:
|
||||
// BDS (un)locks the dyeing with the shulker box recipe, Java never - we want BDS behavior for ease of use
|
||||
javaRecipeID = "minecraft:shulker_box";
|
||||
break;
|
||||
case CRAFTING_SPECIAL_TIPPEDARROW:
|
||||
// similar as above
|
||||
javaRecipeID = "minecraft:arrow";
|
||||
break;
|
||||
}
|
||||
List<String> bedrockRecipeIDs = new ArrayList<>();
|
||||
|
||||
// defined in the recipes.json mappings file: Only tipped arrows use shaped recipes, we need the cast for the identifier
|
||||
if (recipe.getType() == RecipeType.CRAFTING_SPECIAL_TIPPEDARROW) {
|
||||
for (RecipeData data : craftingData) {
|
||||
bedrockRecipeIDs.add(((org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData) data).getId());
|
||||
}
|
||||
} else {
|
||||
for (RecipeData data : craftingData) {
|
||||
bedrockRecipeIDs.add(((org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData) data).getId());
|
||||
}
|
||||
}
|
||||
addRecipeIdentifier(session, javaRecipeID, bedrockRecipeIDs);
|
||||
System.out.println(craftingDataPacket);
|
||||
}
|
||||
|
||||
//TODO: rewrite
|
||||
@ -323,7 +292,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
*
|
||||
* @return the Java ingredient list as an array that Bedrock can understand
|
||||
*/
|
||||
private ItemDescriptorWithCount[][] combinations(GeyserSession session, Ingredient[] ingredients) {
|
||||
private static ItemDescriptorWithCount[][] combinations(GeyserSession session, Ingredient[] ingredients) {
|
||||
boolean empty = true;
|
||||
Map<Set<ItemDescriptorWithCount>, IntSet> squashedOptions = new HashMap<>();
|
||||
for (int i = 0; i < ingredients.length; i++) {
|
||||
@ -407,17 +376,6 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
return combinations;
|
||||
}
|
||||
|
||||
private void addRecipeIdentifier(GeyserSession session, String javaIdentifier, List<String> bedrockIdentifiers) {
|
||||
session.getJavaToBedrockRecipeIds().computeIfAbsent(javaIdentifier, k -> new ArrayList<>()).addAll(bedrockIdentifiers);
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
@AllArgsConstructor
|
||||
private static class GroupedItem {
|
||||
ItemDefinition id;
|
||||
int count;
|
||||
}
|
||||
|
||||
private List<RecipeData> getSmithingTransformRecipes(GeyserSession session) {
|
||||
List<RecipeData> recipes = new ArrayList<>();
|
||||
ItemMapping template = session.getItemMappings().getStoredItems().upgradeTemplate();
|
||||
@ -442,4 +400,120 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
GeyserImpl.getInstance().getLogger().debug("Unable to find item with identifier " + bedrockId);
|
||||
return ItemDescriptorWithCount.EMPTY;
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
@AllArgsConstructor
|
||||
private static class GroupedItem {
|
||||
ItemDefinition id;
|
||||
int count;
|
||||
}
|
||||
|
||||
private static final class RecipeContext {
|
||||
private final GeyserSession session;
|
||||
private final CraftingDataPacket packet;
|
||||
private final Int2ObjectMap<GeyserRecipe> recipeMap;
|
||||
// Get the last known network ID (first used for some pregenerated recipes) and increment from there.
|
||||
private int netId = InventoryUtils.LAST_RECIPE_NET_ID + 1;
|
||||
|
||||
private RecipeContext(GeyserSession session, CraftingDataPacket packet, Int2ObjectMap<GeyserRecipe> recipeMap) {
|
||||
this.session = session;
|
||||
this.packet = packet;
|
||||
this.recipeMap = recipeMap;
|
||||
}
|
||||
|
||||
List<String> translateShulkerBoxRecipe(GeyserShapelessRecipe recipe) {
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, recipe.result());
|
||||
if (!output.isValid()) {
|
||||
// Likely modded item that Bedrock will complain about if it persists
|
||||
return null;
|
||||
}
|
||||
// Strip NBT - tools won't appear in the recipe book otherwise
|
||||
output = output.toBuilder().tag(null).build();
|
||||
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
||||
if (inputCombinations == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> bedrockRecipeIDs = new ArrayList<>();
|
||||
for (ItemDescriptorWithCount[] inputs : inputCombinations) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
bedrockRecipeIDs.add(uuid.toString());
|
||||
packet.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shulkerBox(uuid.toString(),
|
||||
Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId));
|
||||
recipeMap.put(netId++, recipe);
|
||||
}
|
||||
return bedrockRecipeIDs;
|
||||
}
|
||||
|
||||
List<String> translateShapelessRecipe(GeyserShapelessRecipe recipe) {
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, recipe.result());
|
||||
if (!output.isValid()) {
|
||||
// Likely modded item that Bedrock will complain about if it persists
|
||||
return null;
|
||||
}
|
||||
// Strip NBT - tools won't appear in the recipe book otherwise
|
||||
output = output.toBuilder().tag(null).build();
|
||||
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
||||
if (inputCombinations == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> bedrockRecipeIDs = new ArrayList<>();
|
||||
for (ItemDescriptorWithCount[] inputs : inputCombinations) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
bedrockRecipeIDs.add(uuid.toString());
|
||||
packet.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shapeless(uuid.toString(),
|
||||
Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId, RecipeUnlockingRequirement.INVALID));
|
||||
recipeMap.put(netId++, recipe);
|
||||
}
|
||||
return bedrockRecipeIDs;
|
||||
}
|
||||
|
||||
List<String> translateShapedRecipe(GeyserShapedRecipe recipe) {
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, recipe.result());
|
||||
if (!output.isValid()) {
|
||||
// Likely modded item that Bedrock will complain about if it persists
|
||||
return null;
|
||||
}
|
||||
// See above
|
||||
output = output.toBuilder().tag(null).build();
|
||||
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
||||
if (inputCombinations == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> bedrockRecipeIDs = new ArrayList<>();
|
||||
for (ItemDescriptorWithCount[] inputs : inputCombinations) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
bedrockRecipeIDs.add(uuid.toString());
|
||||
packet.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData.shaped(uuid.toString(),
|
||||
recipe.width(), recipe.height(), Arrays.asList(inputs),
|
||||
Collections.singletonList(output), uuid, "crafting_table", 0, netId, false, RecipeUnlockingRequirement.INVALID));
|
||||
recipeMap.put(netId++, recipe);
|
||||
}
|
||||
return bedrockRecipeIDs;
|
||||
}
|
||||
|
||||
void addSpecialRecipesIdentifiers(Recipe recipe, List<String> identifiers) {
|
||||
String javaRecipeID = switch (recipe.getType()) {
|
||||
case CRAFTING_SPECIAL_SHULKERBOXCOLORING ->
|
||||
// BDS (un)locks the dyeing with the shulker box recipe, Java never - we want BDS behavior for ease of use
|
||||
"minecraft:shulker_box";
|
||||
case CRAFTING_SPECIAL_TIPPEDARROW ->
|
||||
// similar as above
|
||||
"minecraft:arrow";
|
||||
default -> recipe.getIdentifier();
|
||||
};
|
||||
|
||||
addRecipeIdentifier(session, javaRecipeID, identifiers);
|
||||
}
|
||||
|
||||
void addRecipeIdentifier(GeyserSession session, String javaIdentifier, List<String> bedrockIdentifiers) {
|
||||
session.getJavaToBedrockRecipeIds().computeIfAbsent(javaIdentifier, k -> new ArrayList<>()).addAll(bedrockIdentifiers);
|
||||
}
|
||||
|
||||
int getAndIncrementNetId() {
|
||||
return this.netId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 54705bcd2bcba830267efbb1fbfd4e52972c40f7
|
||||
Subproject commit 8795baeb170f7c9832da2def8625f0c5702abd91
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren