From c1a70c77545cac5ddc5b82dfc4422759cb7c3277 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 24 Aug 2020 22:14:44 -0400 Subject: [PATCH] Translate client-computed recipes (#1181) * Translate client-computed recipes A handful of recipes are complex enough on Java Edition that the client simply calculates them after getting an assurance that they are valid recipes. This PR stores those recipes in a Bedrock-compatible format in mappings, then generates the CraftingData information on startup to send to the Bedrock client when called. This fixes firework rocket and star crafting, and fixes leather armor and shulker box dyeing. The recipe information for everything except leather armor was taken right from the Bedrock server. The leather armor had to be created separately (see https://github.com/DoctorMacc/LeatherDyeingCreation). There will be a slight visual difference in the crafting result preview if the armor is not perfectly dyed to one of the sixteen colors, but this is a visual issue that will persist unless we calculate every single possbile combination. * Revert other changes * Register shulker box recipes properly * Add break * Update mappings --- .../geysermc/connector/GeyserConnector.java | 2 + .../translators/item/ItemRegistry.java | 44 ++++--- .../translators/item/RecipeRegistry.java | 117 ++++++++++++++++++ .../translators/nbt/FireworkTranslator.java | 3 + .../java/JavaDeclareRecipesTranslator.java | 23 +++- 5 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index fb0840d3..ec332ed6 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -47,6 +47,7 @@ import org.geysermc.connector.network.translators.effect.EffectRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.item.PotionMixRegistry; +import org.geysermc.connector.network.translators.item.RecipeRegistry; import org.geysermc.connector.network.translators.sound.SoundHandlerRegistry; import org.geysermc.connector.network.translators.sound.SoundRegistry; import org.geysermc.connector.network.translators.world.WorldManager; @@ -131,6 +132,7 @@ public class GeyserConnector { ItemTranslator.init(); LocaleUtils.init(); PotionMixRegistry.init(); + RecipeRegistry.init(); SoundRegistry.init(); SoundHandlerRegistry.init(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 4828fbf2..56ed2d6e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -173,21 +173,8 @@ public class ItemRegistry { int netId = 1; List creativeItems = new ArrayList<>(); for (JsonNode itemNode : creativeItemEntries) { - try { - short damage = 0; - NbtMap tag = null; - if (itemNode.has("damage")) { - damage = itemNode.get("damage").numberValue().shortValue(); - } - if (itemNode.has("nbt_b64")) { - byte[] bytes = Base64.getDecoder().decode(itemNode.get("nbt_b64").asText()); - ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); - } - creativeItems.add(ItemData.fromNet(netId++, itemNode.get("id").asInt(), damage, 1, tag)); - } catch (IOException e) { - e.printStackTrace(); - } + ItemData item = getBedrockItemFromJson(itemNode); + creativeItems.add(ItemData.fromNet(netId++, item.getId(), item.getDamage(), item.getCount(), item.getTag())); } CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]); } @@ -233,4 +220,31 @@ public class ItemRegistry { return JAVA_IDENTIFIER_MAP.computeIfAbsent(javaIdentifier, key -> ITEM_ENTRIES.values() .stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null)); } + + /** + * Gets a Bedrock {@link ItemData} from a {@link JsonNode} + * @param itemNode the JSON node that contains ProxyPass-compatible Bedrock item data + * @return + */ + public static ItemData getBedrockItemFromJson(JsonNode itemNode) { + int count = 1; + short damage = 0; + NbtMap tag = null; + if (itemNode.has("damage")) { + damage = itemNode.get("damage").numberValue().shortValue(); + } + if (itemNode.has("count")) { + count = itemNode.get("count").asInt(); + } + if (itemNode.has("nbt_b64")) { + byte[] bytes = Base64.getDecoder().decode(itemNode.get("nbt_b64").asText()); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + try { + tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return ItemData.of(itemNode.get("id").asInt(), damage, count, tag); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java new file mode 100644 index 00000000..191b285c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019-2020 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.connector.network.translators.item; + +import com.fasterxml.jackson.databind.JsonNode; +import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.utils.LanguageUtils; + +import java.io.InputStream; +import java.util.List; +import java.util.UUID; + +/** + * Manages any recipe-related storing + */ +public class RecipeRegistry { + + /** + * A list of all possible leather armor dyeing recipes. + * Created manually. + */ + public static List LEATHER_DYEING_RECIPES = new ObjectArrayList<>(); + /** + * A list of all possible firework rocket recipes, including the base rocket. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static List FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(21); + /** + * A list of all possible firework star recipes. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static List FIREWORK_STAR_RECIPES = new ObjectArrayList<>(40); + /** + * A list of all possible shulker box dyeing options. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static List SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>(); + + static { + // Get all recipes that are not directly sent from a Java server + InputStream stream = FileUtils.getResource("mappings/recipes.json"); + + JsonNode items; + try { + items = GeyserConnector.JSON_MAPPER.readTree(stream); + } catch (Exception e) { + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); + } + + 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 + LEATHER_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } + for (JsonNode entry : items.get("firework_rockets")) { + FIREWORK_ROCKET_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } + for (JsonNode entry : items.get("firework_stars")) { + FIREWORK_STAR_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } + for (JsonNode entry : items.get("shulker_boxes")) { + SHULKER_BOX_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } + } + + /** + * Computes a Bedrock crafting recipe from the given JSON data. + * @param node the JSON data to compute + * @return the {@link CraftingData} to send to the Bedrock client. + */ + private static CraftingData getCraftingDataFromJsonNode(JsonNode node) { + ItemData output = ItemRegistry.getBedrockItemFromJson(node.get("output").get(0)); + List inputs = new ObjectArrayList<>(); + for (JsonNode entry : node.get("input")) { + inputs.add(ItemRegistry.getBedrockItemFromJson(entry)); + } + UUID uuid = UUID.randomUUID(); + if (node.get("type").asInt() == 5) { + // Shulker box + return CraftingData.fromShulkerBox(uuid.toString(), + inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0); + } + return CraftingData.fromShapeless(uuid.toString(), + inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0); + } + + public static void init() { + // no-op + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java index 6023d77d..3b453ea1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java @@ -107,6 +107,9 @@ public class FireworkTranslator extends NbtItemStackTranslator { fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); } + if (!itemTag.contains("Explosions")) { + return; + } ListTag explosions = fireworks.get("Explosions"); for (Tag effect : explosions.getValue()) { CompoundTag effectData = (CompoundTag) effect; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 75ccc0a5..9ffb4f0d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -41,10 +41,7 @@ import lombok.EqualsAndHashCode; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.item.PotionMixRegistry; +import org.geysermc.connector.network.translators.item.*; import java.util.*; import java.util.stream.Collectors; @@ -83,6 +80,24 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator