package com.nukkitx.proxypass.network.bedrock.util; import com.fasterxml.jackson.annotation.JsonInclude; import com.nukkitx.nbt.NbtUtils; import com.nukkitx.nbt.stream.NBTOutputStream; import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; import com.nukkitx.proxypass.ProxyPass; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Value; import lombok.experimental.UtilityClass; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.*; @UtilityClass public class RecipeUtils { private static final char[] SHAPE_CHARS = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'}; public static void writeRecipes(CraftingDataPacket packet, ProxyPass proxy) { List<CraftingDataEntry> entries = new ArrayList<>(); for (CraftingData craftingData : packet.getCraftingData()) { CraftingDataEntry entry = new CraftingDataEntry(); CraftingType type = craftingData.getType(); entry.type = type.ordinal(); if (type != CraftingType.MULTI) { entry.block = craftingData.getCraftingTag(); } else { entry.uuid = craftingData.getUuid(); } if (type == CraftingType.SHAPED || type == CraftingType.SHAPELESS || type == CraftingType.SHAPELESS_CHEMISTRY || type == CraftingType.SHULKER_BOX || type == CraftingType.SHAPED_CHEMISTRY) { entry.id = craftingData.getRecipeId(); entry.priority = craftingData.getPriority(); entry.output = writeItemArray(craftingData.getOutputs(), true); } if (type == CraftingType.SHAPED || type == CraftingType.SHAPED_CHEMISTRY) { int charCounter = 0; ItemData[] inputs = craftingData.getInputs(); Map<Item, Character> charItemMap = new HashMap<>(); char[][] shape = new char[craftingData.getHeight()][craftingData.getWidth()]; for (int height = 0; height < craftingData.getHeight(); height++) { Arrays.fill(shape[height], ' '); int index = height * craftingData.getWidth(); for (int width = 0; width < craftingData.getWidth(); width++) { int slot = index + width; Item item = itemFromNetwork(inputs[slot], false); if (item == Item.EMPTY) { continue; } Character shapeChar = charItemMap.get(item); if (shapeChar == null) { shapeChar = SHAPE_CHARS[charCounter++]; charItemMap.put(item, shapeChar); } shape[height][width] = shapeChar; } } String[] shapeString = new String[shape.length]; for (int i = 0; i < shape.length; i++) { shapeString[i] = new String(shape[i]); } entry.shape = shapeString; Map<Character, Item> itemMap = new HashMap<>(); for (Map.Entry<Item, Character> mapEntry : charItemMap.entrySet()) { itemMap.put(mapEntry.getValue(), mapEntry.getKey()); } entry.input = itemMap; } if (type == CraftingType.SHAPELESS || type == CraftingType.SHAPELESS_CHEMISTRY || type == CraftingType.SHULKER_BOX) { entry.input = writeItemArray(craftingData.getInputs(), false); } if (type == CraftingType.FURNACE || type == CraftingType.FURNACE_DATA) { Integer damage = craftingData.getInputDamage(); if (damage == 0x7fff) damage = -1; if (damage == 0) damage = null; entry.input = new Item(craftingData.getInputId(), damage, null, null); entry.output = itemFromNetwork(craftingData.getOutputs()[0], true); } entries.add(entry); } Recipes recipes = new Recipes(ProxyPass.CODEC.getProtocolVersion(), entries, packet.getPotionMixData(), packet.getContainerMixData()); proxy.saveJson("recipes.json", recipes); } private static Item[] writeItemArray(ItemData[] inputs, boolean output) { List<Item> outputs = new ArrayList<>(); for (ItemData input : inputs) { Item item = itemFromNetwork(input, output); if (item != Item.EMPTY) { outputs.add(item); } } return outputs.toArray(new Item[0]); } private static String nbtToBase64(CompoundTag tag) { if (tag != null) { ByteArrayOutputStream tagStream = new ByteArrayOutputStream(); try (NBTOutputStream writer = NbtUtils.createWriterLE(tagStream)) { writer.write(tag); } catch (IOException e) { throw new RuntimeException(e); } return Base64.getEncoder().encodeToString(tagStream.toByteArray()); } else { return null; } } private static Item itemFromNetwork(ItemData data, boolean output) { int id = data.getId(); Integer damage = (int) data.getDamage(); Integer count = data.getCount(); String tag = nbtToBase64(data.getTag()); if (id == 0) { return Item.EMPTY; } if (damage == 0 || (damage == -1 && output)) damage = null; if (count == 1) count = null; return new Item(id, damage, count, tag); } @NoArgsConstructor @AllArgsConstructor @Getter @JsonInclude(JsonInclude.Include.NON_NULL) private static class CraftingDataEntry { private String id; private int type; private Object input; private Object output; private String[] shape; private String block; private UUID uuid; private Integer priority; } @Value @JsonInclude(JsonInclude.Include.NON_NULL) private static class Item { public static final Item EMPTY = new Item(0, null, null, null); private final int id; private final Integer damage; private final Integer count; private final String nbt_b64; } @Value private static class Recipes { private final int version; private final List<CraftingDataEntry> recipes; private final List<PotionMixData> potionMixes; private final List<ContainerMixData> containerMixes; } }