All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.inventory.CraftingManager Maven / Gradle / Ivy

There is a newer version: 1.20.40-r1
Show newest version
package cn.nukkit.inventory;

import cn.nukkit.Server;
import cn.nukkit.api.*;
import cn.nukkit.inventory.recipe.DefaultDescriptor;
import cn.nukkit.inventory.recipe.ItemDescriptor;
import cn.nukkit.inventory.recipe.ItemTagDescriptor;
import cn.nukkit.item.Item;
import cn.nukkit.item.ItemID;
import cn.nukkit.item.StringItem;
import cn.nukkit.nbt.NBTIO;
import cn.nukkit.nbt.tag.CompoundTag;
import cn.nukkit.network.protocol.CraftingDataPacket;
import cn.nukkit.network.protocol.DataPacket;
import cn.nukkit.utils.BinaryStream;
import cn.nukkit.utils.Config;
import cn.nukkit.utils.Utils;
import com.google.gson.GsonBuilder;
import io.netty.util.collection.CharObjectHashMap;
import io.netty.util.internal.EmptyArrays;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteOrder;
import java.util.*;
import java.util.zip.Deflater;

/**
 * @author MagicDroidX (Nukkit Project)
 */
@Log4j2
public class CraftingManager {

    public static final Comparator recipeComparator = (i1, i2) -> {
        if (i1.getId() > i2.getId()) {
            return 1;
        } else if (i1.getId() < i2.getId()) {
            return -1;
        } else if (i1.getDamage() > i2.getDamage()) {
            return 1;
        } else if (i1.getDamage() < i2.getDamage()) {
            return -1;
        } else return Integer.compare(i1.getCount(), i2.getCount());
    };

    //
    /**
     * 缓存着配方数据包
     */
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.",
            replaceWith = "getPacket()")
    @Since("1.5.0.0-PN")
    public static DataPacket packet = null;
    @PowerNukkitXDifference(info = "Now it is the count of all recipes", since = "1.19.50-r3")
    private static int RECIPE_COUNT = 0;
    private final Int2ObjectMap> shapedRecipeMap = new Int2ObjectOpenHashMap<>();
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    protected final Map> shapedRecipes = shapedRecipeMap;
    private final Int2ObjectMap furnaceRecipeMap = new Int2ObjectOpenHashMap<>();
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    public final Map furnaceRecipes = furnaceRecipeMap;
    private final Int2ObjectMap blastFurnaceRecipeMap = new Int2ObjectOpenHashMap<>();
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    @PowerNukkitOnly
    public final Map blastFurnaceRecipes = blastFurnaceRecipeMap;
    private final Int2ObjectMap campfireRecipeMap = new Int2ObjectOpenHashMap<>();
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    @PowerNukkitOnly
    public final Map campfireRecipes = campfireRecipeMap;
    private final Int2ObjectMap smokerRecipeMap = new Int2ObjectOpenHashMap<>();
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    @PowerNukkitOnly
    public final Map smokerRecipes = smokerRecipeMap;
    private final Map multiRecipeMap = new HashMap<>();
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    @Since("1.4.0.0-PN")
    public final Map multiRecipes = multiRecipeMap;
    private final Int2ObjectMap brewingRecipeMap = new Int2ObjectOpenHashMap<>();
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    public final Map brewingRecipes = brewingRecipeMap;
    private final Int2ObjectMap containerRecipeMap = new Int2ObjectOpenHashMap<>();
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    public final Map containerRecipes = containerRecipeMap;
    private final Int2ObjectMap stonecutterRecipeMap = new Int2ObjectOpenHashMap<>();
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    @PowerNukkitOnly
    public final Map stonecutterRecipes = stonecutterRecipeMap;
    private final Int2ObjectMap> shapelessRecipeMap = new Int2ObjectOpenHashMap<>();
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    protected final Map> shapelessRecipes = shapelessRecipeMap;
    private final Int2ObjectMap> cartographyRecipeMap = new Int2ObjectOpenHashMap<>();
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    @PowerNukkitOnly
    protected final Map> cartographyRecipes = cartographyRecipeMap;
    private final Int2ObjectOpenHashMap> smithingRecipeMap = new Int2ObjectOpenHashMap<>();
    @Since("1.19.50-r3")
    @PowerNukkitXOnly
    private final Map> modProcessRecipeMap = new HashMap<>();
    @Since("1.19.50-r3")
    @PowerNukkitXOnly
    private final Object2DoubleOpenHashMap recipeXpMap = new Object2DoubleOpenHashMap<>();
    private final Deque recipeList = new ArrayDeque<>();

    /**
     * 一个包含全部种类配方的双端队列集合
     */
    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", reason = "Direct access to fields are not future-proof.")
    @PowerNukkitXDifference(info = "Now it is contain all type recipes", since = "1.19.50-r3")
    public final Collection recipes = recipeList;
    //

    //
    public CraftingManager() {
        log.info("Loading recipes...");
        this.loadRecipes();
        this.rebuildPacket();
        log.info("Loaded {} recipes.", this.recipes.size());
    }

    private CraftingManager(Boolean Test) {
    }

    @SneakyThrows
    @SuppressWarnings({"unchecked", "rawtypes"})
    private void loadRecipes() {
        var gson = new GsonBuilder().create();

        //load xp config
        var furnaceXpConfig = new Config(Config.JSON);
        try {
            furnaceXpConfig.load(Server.class.getModule().getResourceAsStream("furnace_xp.json"));
        } catch (IOException e) {
            log.warn("Failed to load furnace xp config");
        }

        //load potion
        try (var r = Server.class.getModule().getResourceAsStream("vanilla_recipes/potion_type.json")) {
            if (r == null) {
                throw new AssertionError("Unable to find potion_type.json");
            }
            List> map = gson.fromJson(new InputStreamReader(r), List.class);
            for (Map recipe : map) {
                Map ingredient = (Map) recipe.get("ingredient");
                Map input = (Map) recipe.get("input");
                Map output = (Map) recipe.get("output");
                Item inputItem = parseRecipeItem(input);
                Item ingredientItem = parseRecipeItem(ingredient);
                Item outputItem = parseRecipeItem(output);
                if (inputItem.isNull() || ingredientItem.isNull() || outputItem.isNull()) {
                    continue;
                }
                registerBrewingRecipe(new BrewingRecipe(
                        inputItem,
                        ingredientItem,
                        outputItem
                ));
            }
        }

        //load shaped recipe
        try (var r = Server.class.getModule().getResourceAsStream("vanilla_recipes/shaped_crafting.json")) {
            if (r == null) {
                throw new AssertionError("Unable to find shaped_crafting.json");
            }
            List> map = gson.fromJson(new InputStreamReader(r), List.class);
            for (Map recipe : map) {
                String craftingBlock = (String) recipe.get("block");
                if (!"crafting_table".equals(craftingBlock)) {
                    continue;
                }
                var reg = parseShapeRecipe(recipe);
                if (reg == null) {
                    continue;
                }
                this.registerRecipe(reg);
            }
        }

        //load shapeless recipe
        {
            var shapelessCrafting = Server.class.getModule().getResourceAsStream("vanilla_recipes/shapeless_crafting.json");
            if (shapelessCrafting == null) {
                throw new AssertionError("Unable to find shapeless_crafting.json");
            }

            var smithing = Server.class.getModule().getResourceAsStream("vanilla_recipes/smithing.json");
            if (smithing == null) {
                throw new AssertionError("Unable to find smithing.json");
            }
            List> smithingRecipes = gson.fromJson(new InputStreamReader(smithing), List.class);
            smithing.close();

            List> shapelessRecipes = new ArrayList<>(gson.fromJson(new InputStreamReader(shapelessCrafting), List.class));
            shapelessRecipes.addAll(smithingRecipes);
            shapelessCrafting.close();

            for (Map recipe : shapelessRecipes) {
                String craftingBlock = (String) recipe.get("block");
                var reg = parseShapelessRecipe(recipe, craftingBlock);
                if (reg == null) {
                    continue;
                }
                this.registerRecipe(reg);
            }

            var shapelessShulkerBox = Server.class.getModule().getResourceAsStream("vanilla_recipes/shapeless_shulker_box.json");
            if (shapelessShulkerBox == null) {
                throw new AssertionError("Unable to find shapeless_shulker_box.json");
            }
            List> shulkerBoxRecipes = gson.fromJson(new InputStreamReader(shapelessShulkerBox), List.class);
            for (Map recipe : shulkerBoxRecipes) {
                String craftingBlock = "shulker_box";
                var reg = parseShapelessRecipe(recipe, craftingBlock);
                if (reg == null) {
                    continue;
                }
                this.registerRecipe(reg);
            }
            shapelessShulkerBox.close();
        }

        //load furnace recipe
        try (var r = Server.class.getModule().getResourceAsStream("vanilla_recipes/smelting.json")) {
            if (r == null) {
                throw new AssertionError("Unable to find smelting.json");
            }
            List> map = gson.fromJson(new InputStreamReader(r), List.class);
            for (Map recipe : map) {
                String craftingBlock = (String) recipe.get("block");
                if (!"furnace".equals(craftingBlock) && !"blast_furnace".equals(craftingBlock)
                        && !"smoker".equals(craftingBlock) && !"campfire".equals(craftingBlock)) {
                    // Ignore other recipes than furnaces, blast furnaces, smokers and campfire
                    continue;
                }
                Map resultMap = (Map) recipe.get("output");
                Item resultItem = parseRecipeItem(resultMap);
                if (resultItem.isNull()) {
                    continue;
                }
                Item inputItem;
                try {
                    Map inputMap = (Map) recipe.get("input");
                    inputItem = parseRecipeItem(inputMap);
                } catch (Exception old) {
                    inputItem = Item.get(Utils.toInt(recipe.get("inputId")), recipe.containsKey("inputDamage") ? Utils.toInt(recipe.get("inputDamage")) : -1, 1);
                }
                if (inputItem.isNull()) {
                    continue;
                }
                Recipe furnaceRecipe = null;
                switch (craftingBlock) {
                    case "furnace" -> this.registerRecipe(furnaceRecipe = new FurnaceRecipe(resultItem, inputItem));
                    case "blast_furnace" ->
                            this.registerRecipe(furnaceRecipe = new BlastFurnaceRecipe(resultItem, inputItem));
                    case "smoker" -> this.registerRecipe(furnaceRecipe = new SmokerRecipe(resultItem, inputItem));
                    case "campfire" -> this.registerRecipe(furnaceRecipe = new CampfireRecipe(resultItem, inputItem));
                }
                var xp = furnaceXpConfig.getDouble(inputItem.getNamespaceId() + ":" + inputItem.getDamage());
                if (xp != 0) {
                    this.setRecipeXp(furnaceRecipe, xp);
                }
            }
        }

        //load multi recipe
        try (var r = Server.class.getModule().getResourceAsStream("vanilla_recipes/special_hardcoded.json")) {
            if (r == null) {
                throw new AssertionError("Unable to find special_hardcoded.json");
            }
            List uuids = gson.fromJson(new InputStreamReader(r), List.class);
            for (String uuid : uuids) {
                this.registerRecipe(new MultiRecipe(UUID.fromString(uuid)));
            }
        }

        // Allow to rename without crafting
        registerCartographyRecipe(new CartographyRecipe(Item.get(ItemID.EMPTY_MAP), Collections.singletonList(Item.get(ItemID.EMPTY_MAP))));
        registerCartographyRecipe(new CartographyRecipe(Item.get(ItemID.EMPTY_MAP, 2), Collections.singletonList(Item.get(ItemID.EMPTY_MAP, 2))));
        registerCartographyRecipe(new CartographyRecipe(Item.get(ItemID.MAP), Collections.singletonList(Item.get(ItemID.MAP))));
        registerCartographyRecipe(new CartographyRecipe(Item.get(ItemID.MAP, 3), Collections.singletonList(Item.get(ItemID.MAP, 3))));
        registerCartographyRecipe(new CartographyRecipe(Item.get(ItemID.MAP, 4), Collections.singletonList(Item.get(ItemID.MAP, 4))));
        registerCartographyRecipe(new CartographyRecipe(Item.get(ItemID.MAP, 5), Collections.singletonList(Item.get(ItemID.MAP, 5))));
    }

    @PowerNukkitXOnly
    @Since("1.19.50-r2")
    @SuppressWarnings("unchecked")
    private Recipe parseShapelessRecipe(Map recipeObject, String craftingBlock) {
        if (craftingBlock.equals("smithing_table")) {
            List items = new ArrayList<>();
            Map addition = (Map) recipeObject.get("addition");
            Item additionItem = parseRecipeItem(addition);
            Map input = (Map) recipeObject.get("input");
            Item inputItem = parseRecipeItem(input);
            Map output = (Map) recipeObject.get("output");
            Item outputItem = parseRecipeItem(output);
            if (additionItem.isNull() || inputItem.isNull() || outputItem.isNull()) {
                return null;
            }
            items.add(inputItem);
            items.add(additionItem);
            return new SmithingRecipe(null, 0, items, outputItem);
        }

        List itemDescriptors = new ArrayList<>();
        List> outputs = ((List>) recipeObject.get("output"));
        List> inputs = ((List>) recipeObject.get("input"));
        // TODO: handle multiple result items
        if (outputs.size() > 1) {
            return null;
        }
        Map first = outputs.get(0);

        int priority = recipeObject.containsKey("priority") ? Utils.toInt(recipeObject.get("priority")) : 0;

        Item result = parseRecipeItem(first);
        if (result.isNull()) {
            return null;
        }

        for (Map ingredient : inputs) {
            if (ingredient.containsKey("tag")) {
                var itemTag = ingredient.get("tag").toString();
                int count = ingredient.containsKey("count") ? ((Number) ingredient.get("count")).intValue() : 1;
                itemDescriptors.add(new ItemTagDescriptor(itemTag, count));
            } else {
                Item recipeItem = parseRecipeItem(ingredient);
                if (recipeItem.isNull()) {
                    return null;
                }
                itemDescriptors.add(new DefaultDescriptor(recipeItem));
            }
        }

        return switch (craftingBlock) {
            case "crafting_table" -> new ShapelessRecipe(null, priority, result, itemDescriptors);
            case "shulker_box" -> new ShulkerBoxRecipe(null, priority, result, itemDescriptors);
            case "stonecutter" -> new StonecutterRecipe(null, priority, result, itemDescriptors.get(0).toItem());
            case "cartography_table" -> new CartographyRecipe(null, priority, result, itemDescriptors);
            default -> null;
        };
    }

    @PowerNukkitXOnly
    @Since("1.19.50-r2")
    @SuppressWarnings("unchecked")
    private Recipe parseShapeRecipe(Map recipeObject) {
        List> outputs = (List>) recipeObject.get("output");

        Map first = outputs.remove(0);
        String[] shape = ((List) recipeObject.get("shape")).toArray(EmptyArrays.EMPTY_STRINGS);
        Map ingredients = new CharObjectHashMap<>();
        List extraResults = new ArrayList<>();

        int priority = Utils.toInt(recipeObject.get("priority"));
        Item primaryResult = parseRecipeItem(first);
        if (primaryResult.isNull()) {
            return null;
        }

        for (Map data : outputs) {
            Item output = parseRecipeItem(data);
            if (output.isNull()) {
                return null;
            }
            extraResults.add(output);
        }

        Map> input = (Map>) recipeObject.get("input");
        for (Map.Entry> ingredientEntry : input.entrySet()) {
            char ingredientChar = ingredientEntry.getKey().charAt(0);
            var ingredient = ingredientEntry.getValue();

            if (ingredient.containsKey("tag")) {
                var tag = ingredient.get("tag").toString();
                int count = ingredient.containsKey("count") ? ((Number) ingredient.get("count")).intValue() : 1;
                ingredients.put(ingredientChar, new ItemTagDescriptor(tag, count));
            } else {
                Item recipeItem = parseRecipeItem(ingredient);
                if (recipeItem.isNull()) {
                    return null;
                }
                ingredients.put(ingredientChar, new DefaultDescriptor(recipeItem));
            }
        }
        return new ShapedRecipe(null, priority, primaryResult, shape, ingredients, extraResults);
    }

    @PowerNukkitXDifference(info = "Recipe formats exported from proxypass before 1.19.40 are no longer supported", since = "1.19.50-r1")
    private Item parseRecipeItem(Map data) {
        Item item;

        String name = data.get("name").toString();
        int count = data.containsKey("count") ? ((Number) data.get("count")).intValue() : 1;

        //block item
        if (data.containsKey("block_states")) {
            try {
                item = NBTIO.getBlockHelper(new CompoundTag()
                        .putString("name", name)
                        .putCompound("states", NBTIO.read(Base64.getDecoder().decode(data.get("block_states").toString()), ByteOrder.LITTLE_ENDIAN))
                ).toItem();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            item.setCount(count);
            return item;
        }

        String nbt = (String) data.get("nbt");
        byte[] nbtBytes = nbt != null ? Base64.getDecoder().decode(nbt) : EmptyArrays.EMPTY_BYTES;

        Integer meta = null;
        if (data.containsKey("meta")) {
            meta = Utils.toInt(data.get("meta"));
        }
        if (meta != null) {
            if (meta == Short.MAX_VALUE) {
                item = Item.fromString(name).createFuzzyCraftingRecipe();
            } else {
                item = Item.fromString(name + ":" + meta);
            }
        } else {
            item = Item.fromString(name);
        }

        item.setCount(count);
        item.setCompoundTag(nbtBytes);
        return item;
    }
    //

    //
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static DataPacket getCraftingPacket() {
        return packet;
    }

    @PowerNukkitOnly("Public only in PowerNukkit")
    public static UUID getMultiItemHash(Collection items) {
        BinaryStream stream = new BinaryStream();
        for (Item item : items) {
            stream.putVarInt(getFullItemHash(item));
        }
        return UUID.nameUUIDFromBytes(stream.getBuffer());
    }

    @PowerNukkitXOnly
    @Since("1.19.50-r2")
    public static UUID getItemWithItemDescriptorsHash(Collection items, Collection itemDescriptors) {
        BinaryStream stream = new BinaryStream();
        for (Item item : items) {
            stream.putVarInt(getFullItemHash(item));
        }
        for (var des : itemDescriptors) {
            if (des instanceof ItemTagDescriptor) {
                stream.putVarInt(des.hashCode());
            }
        }
        return UUID.nameUUIDFromBytes(stream.getBuffer());
    }

    @Since("1.19.50-r3")
    @PowerNukkitXOnly
    public static UUID getShapelessItemDescriptorHash(Collection itemDescriptors) {
        var stream = new BinaryStream();
        itemDescriptors.stream().mapToInt(Objects::hashCode).sorted().forEachOrdered(stream::putVarInt);
        return UUID.nameUUIDFromBytes(stream.getBuffer());
    }

    @Since("1.19.50-r3")
    @PowerNukkitXOnly
    public static UUID getShapelessMultiItemHash(Collection items) {
        var stream = new BinaryStream();
        items.stream().mapToInt(CraftingManager::getFullItemHash).sorted().forEachOrdered(stream::putVarInt);
        return UUID.nameUUIDFromBytes(stream.getBuffer());
    }

    @PowerNukkitOnly("Public only in PowerNukkit")
    @Since("FUTURE")
    public static int getFullItemHash(Item item) {
        return 31 * getItemHash(item) + item.getCount();
    }

    @PowerNukkitOnly("Public only in PowerNukkit")
    @Since("FUTURE")
    public static int getItemHash(Item item) {
        return getItemHash(item, item.getDamage());
    }

    @PowerNukkitOnly
    @Since("FUTURE")
    public static int getItemHash(Item item, int meta) {
        int id = item.getId();
        int hash = 31 + (id << 8 | meta & 0xFF);
        hash *= 31 + (id == ItemID.STRING_IDENTIFIED_ITEM && item instanceof StringItem ?
                item.getNamespaceId().hashCode()
                : 0);
        return hash;
    }

    @PowerNukkitOnly("Public only in PowerNukkit")
    public static int getPotionHash(Item ingredient, Item potion) {
        int ingredientId = ingredient.getId();
        int potionId = potion.getId();
        int hash = 17;
        hash *= 31 + ingredientId;
        hash *= 31 + (ingredientId == ItemID.STRING_IDENTIFIED_ITEM ? ingredient.getNamespaceId().hashCode() : 0);
        hash *= 31 + potion.getDamage();
        hash *= 31 + potionId;
        hash *= 31 + (potionId == ItemID.STRING_IDENTIFIED_ITEM ? potion.getNamespaceId().hashCode() : 0);
        hash *= 31 + potion.getDamage();
        return hash;
    }

    @PowerNukkitOnly
    public static int getContainerHash(@NotNull Item ingredient, @NotNull Item container) {
        int ingredientId = ingredient.getId();
        int containerId = container.getId();
        int hash = 17;
        hash *= 31 + ingredientId;
        hash *= 31 + (ingredientId == ItemID.STRING_IDENTIFIED_ITEM ? ingredient.getNamespaceId().hashCode() : 0);
        hash *= 31 + containerId;
        hash *= 31 + (containerId == ItemID.STRING_IDENTIFIED_ITEM ? container.getNamespaceId().hashCode() : 0);
        return hash;
    }

    @PowerNukkitOnly
    public Int2ObjectMap> getShapedRecipeMap() {
        return shapedRecipeMap;
    }

    @PowerNukkitOnly
    public Int2ObjectMap getFurnaceRecipesMap() {
        return furnaceRecipeMap;
    }

    @PowerNukkitOnly
    public Int2ObjectMap getBlastFurnaceRecipeMap() {
        return blastFurnaceRecipeMap;
    }

    @PowerNukkitOnly
    public Int2ObjectMap getSmokerRecipeMap() {
        return smokerRecipeMap;
    }

    @PowerNukkitOnly
    public Int2ObjectMap getCampfireRecipeMap() {
        return campfireRecipeMap;
    }


    @PowerNukkitOnly
    public Map getMultiRecipeMap() {
        return multiRecipeMap;
    }

    @PowerNukkitOnly
    public Int2ObjectMap getBrewingRecipeMap() {
        return brewingRecipeMap;
    }

    @PowerNukkitOnly
    public Int2ObjectMap getContainerRecipeMap() {
        return containerRecipeMap;
    }

    @PowerNukkitOnly
    public Int2ObjectMap getStonecutterRecipeMap() {
        return stonecutterRecipeMap;
    }

    @PowerNukkitOnly
    protected Int2ObjectMap> getShapelessRecipeMap() {
        return shapelessRecipeMap;
    }

    @PowerNukkitOnly
    public Int2ObjectMap> getCartographyRecipeMap() {
        return cartographyRecipeMap;
    }

    @PowerNukkitOnly
    public Int2ObjectMap> getSmithingRecipeMap() {
        return smithingRecipeMap;
    }

    @Since("1.19.50-r3")
    @PowerNukkitXOnly
    public Object2DoubleOpenHashMap getRecipeXpMap() {
        return recipeXpMap;
    }

    // Get Mod-Processing Recipes
    @Since("1.19.50-r3")
    @PowerNukkitXOnly
    public Map> getModProcessRecipeMap() {
        return modProcessRecipeMap;
    }

    @Deprecated
    @DeprecationDetails(by = "PowerNukkit", since = "FUTURE", replaceWith = "getFurnaceRecipeMap()",
            reason = "The other provides a specialized map which performs better")
    public Map getFurnaceRecipes() {
        return furnaceRecipes;
    }

    public Collection getRecipes() {
        return recipeList;
    }

    public static int getRecipeCount() {
        return RECIPE_COUNT;
    }

    //

    public void rebuildPacket() {
        CraftingDataPacket pk = new CraftingDataPacket();
        pk.cleanRecipes = true;

        for (Map map : getShapedRecipeMap().values()) {
            for (ShapedRecipe recipe : map.values()) {
                pk.addShapedRecipe(recipe);
            }
        }

        for (Map map : getShapelessRecipeMap().values()) {
            for (ShapelessRecipe recipe : map.values()) {
                pk.addShapelessRecipe(recipe);
            }
        }

        for (Map map : getCartographyRecipeMap().values()) {
            for (CartographyRecipe recipe : map.values()) {
                pk.addCartographyRecipe(recipe);
            }
        }

        for (FurnaceRecipe recipe : getFurnaceRecipesMap().values()) {
            pk.addFurnaceRecipe(recipe);
        }

        for (MultiRecipe recipe : getMultiRecipeMap().values()) {
            pk.addMultiRecipe(recipe);
        }

        for (SmokerRecipe recipe : getSmokerRecipeMap().values()) {
            pk.addSmokerRecipe(recipe);
        }

        for (BlastFurnaceRecipe recipe : getBlastFurnaceRecipeMap().values()) {
            pk.addBlastFurnaceRecipe(recipe);
        }

        for (CampfireRecipe recipe : getCampfireRecipeMap().values()) {
            pk.addCampfireRecipeRecipe(recipe);
        }

        for (BrewingRecipe recipe : getBrewingRecipeMap().values()) {
            pk.addBrewingRecipe(recipe);
        }

        for (ContainerRecipe recipe : getContainerRecipeMap().values()) {
            pk.addContainerRecipe(recipe);
        }

        for (StonecutterRecipe recipe : getStonecutterRecipeMap().values()) {
            pk.addStonecutterRecipe(recipe);
        }

        pk.tryEncode();
        //TODO: find out whats wrong with compression
        packet = pk.compress(Deflater.BEST_COMPRESSION);
    }

    public FurnaceRecipe matchFurnaceRecipe(Item input) {
        if (input.isNull()) {
            return null;
        }
        FurnaceRecipe recipe = getFurnaceRecipesMap().get(getItemHash(input));
        if (recipe == null) recipe = getFurnaceRecipesMap().get(getItemHash(input, 0));
        return recipe;
    }

    @PowerNukkitOnly
    public CampfireRecipe matchCampfireRecipe(Item input) {
        if (input.isNull()) {
            return null;
        }
        CampfireRecipe recipe = getCampfireRecipeMap().get(getItemHash(input));
        if (recipe == null) recipe = getCampfireRecipeMap().get(getItemHash(input, 0));
        return recipe;
    }

    @PowerNukkitOnly
    public BlastFurnaceRecipe matchBlastFurnaceRecipe(Item input) {
        if (input.isNull()) {
            return null;
        }
        BlastFurnaceRecipe recipe = getBlastFurnaceRecipeMap().get(getItemHash(input));
        if (recipe == null) recipe = getBlastFurnaceRecipeMap().get(getItemHash(input, 0));
        return recipe;
    }

    @PowerNukkitOnly
    public SmokerRecipe matchSmokerRecipe(Item input) {
        if (input.isNull()) {
            return null;
        }
        SmokerRecipe recipe = getSmokerRecipeMap().get(getItemHash(input));
        if (recipe == null) recipe = getSmokerRecipeMap().get(getItemHash(input, 0));
        return recipe;
    }

    public void registerRecipe(Recipe recipe) {
        recipe.registerToCraftingManager(this);
    }

    @PowerNukkitOnly
    public void registerCartographyRecipe(CartographyRecipe recipe) {
        this.registerShapelessRecipe(recipe);
    }

    public void registerShapedRecipe(ShapedRecipe recipe) {
        this.addRecipe(recipe);
        int resultHash = getItemHash(recipe.getResult());
        Map map = getShapedRecipeMap().computeIfAbsent(resultHash, k -> new HashMap<>());
        List list1 = new LinkedList<>(recipe.getIngredientsAggregate());
        var list2 = recipe.getNewIngredientList();
        map.put(getItemWithItemDescriptorsHash(list1, list2), recipe);
    }

    public void registerShapelessRecipe(ShapelessRecipe recipe) {
        this.addRecipe(recipe);
        List list1 = recipe.getIngredientsAggregate();
        List list2 = recipe.getNewIngredients();

        UUID hash = getItemWithItemDescriptorsHash(list1, list2);

        int resultHash = getItemHash(recipe.getResult());
        Map map = getShapelessRecipeMap().computeIfAbsent(resultHash, k -> new HashMap<>());

        map.put(hash, recipe);
    }

    @PowerNukkitOnly
    public void registerStonecutterRecipe(StonecutterRecipe recipe) {
        this.addRecipe(recipe);
        getStonecutterRecipeMap().put(getItemHash(recipe.getResult()), recipe);
    }

    public void registerFurnaceRecipe(FurnaceRecipe recipe) {
        this.addRecipe(recipe);
        Item input = recipe.getInput();
        getFurnaceRecipesMap().put(getItemHash(input), recipe);
    }

    @PowerNukkitOnly
    public void registerBlastFurnaceRecipe(BlastFurnaceRecipe recipe) {
        this.addRecipe(recipe);
        Item input = recipe.getInput();
        getBlastFurnaceRecipeMap().put(getItemHash(input), recipe);
    }

    @PowerNukkitOnly
    public void registerSmokerRecipe(SmokerRecipe recipe) {
        this.addRecipe(recipe);
        Item input = recipe.getInput();
        getSmokerRecipeMap().put(getItemHash(input), recipe);
    }

    @PowerNukkitOnly
    public void registerCampfireRecipe(CampfireRecipe recipe) {
        this.addRecipe(recipe);
        Item input = recipe.getInput();
        getCampfireRecipeMap().put(getItemHash(input), recipe);
    }

    @Since("1.19.50-r3")
    @PowerNukkitXOnly
    public void registerModProcessRecipe(@NotNull ModProcessRecipe recipe) {
        this.addRecipe(recipe);
        var map = getModProcessRecipeMap().computeIfAbsent(recipe.getCategory(), k -> new HashMap<>());
        var inputHash = getShapelessItemDescriptorHash(recipe.getIngredients());
        map.put(inputHash, recipe);
    }


    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public void registerSmithingRecipe(@NotNull SmithingRecipe recipe) {
        this.addRecipe(recipe);
        List list1 = recipe.getIngredientsAggregate();
        List list2 = recipe.getNewIngredients();

        UUID hash = getItemWithItemDescriptorsHash(list1, list2);

        int resultHash = getItemHash(recipe.getResult());
        Map map1 = getShapelessRecipeMap().computeIfAbsent(resultHash, k -> new HashMap<>());
        Map map2 = getSmithingRecipeMap().computeIfAbsent(resultHash, k -> new HashMap<>());
        map1.put(hash, recipe);
        map2.put(hash, recipe);
    }

    public void registerBrewingRecipe(BrewingRecipe recipe) {
        this.addRecipe(recipe);
        Item input = recipe.getIngredient();
        Item potion = recipe.getInput();

        int potionHash = getPotionHash(input, potion);
        var brewingRecipes = getBrewingRecipeMap();
        if (brewingRecipes.containsKey(potionHash)) {
            log.warn("The brewing recipe {} is being replaced by {}", brewingRecipes.get(potionHash), recipe);
        }
        brewingRecipes.put(potionHash, recipe);
    }

    public void registerContainerRecipe(ContainerRecipe recipe) {
        this.addRecipe(recipe);
        Item input = recipe.getIngredient();
        Item potion = recipe.getInput();

        getContainerRecipeMap().put(getContainerHash(input, potion), recipe);
    }

    @Since("1.4.0.0-PN")
    public void registerMultiRecipe(MultiRecipe recipe) {
        this.addRecipe(recipe);
        getMultiRecipeMap().put(recipe.getId(), recipe);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nullable
    public SmithingRecipe matchSmithingRecipe(Item equipment, Item ingredient) {
        List inputList = new ArrayList<>(2);
        inputList.add(equipment.decrement(equipment.count - 1));
        inputList.add(ingredient.decrement(ingredient.count - 1));
        return matchSmithingRecipe(inputList);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nullable
    public SmithingRecipe matchSmithingRecipe(@NotNull List inputList) {
        inputList.sort(recipeComparator);
        UUID inputHash = getMultiItemHash(inputList);

        return getSmithingRecipeMap().values().stream().flatMap(map -> map.entrySet().stream())
                .filter(entry -> entry.getKey().equals(inputHash))
                .map(Map.Entry::getValue)
                .findFirst().orElseGet(() ->
                        getSmithingRecipeMap().values().stream().flatMap(map -> map.values().stream())
                                .filter(recipe -> recipe.matchItems(inputList))
                                .findFirst().orElse(null)
                );
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nullable
    public SmithingRecipe matchSmithingRecipe(@NotNull Item equipment, @NotNull Item ingredient, @NotNull Item primaryOutput) {
        List inputList = Arrays.asList(equipment, ingredient);
        return matchSmithingRecipe(inputList, primaryOutput);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public SmithingRecipe matchSmithingRecipe(@NotNull List inputList, @NotNull Item primaryOutput) {
        int outputHash = getItemHash(primaryOutput);
        if (!getSmithingRecipeMap().containsKey(outputHash)) {
            return null;
        }

        inputList.sort(recipeComparator);

        UUID inputHash = getMultiItemHash(inputList);

        Map recipeMap = getSmithingRecipeMap().get(outputHash);

        if (recipeMap != null) {
            SmithingRecipe recipe = recipeMap.get(inputHash);

            if (recipe != null && (recipe.matchItems(inputList) || matchItemsAccumulation(recipe, inputList, primaryOutput))) {
                return recipe;
            }

            for (SmithingRecipe smithingRecipe : recipeMap.values()) {
                if (smithingRecipe.matchItems(inputList) || matchItemsAccumulation(smithingRecipe, inputList, primaryOutput)) {
                    return smithingRecipe;
                }
            }
        }

        return null;
    }

    public BrewingRecipe matchBrewingRecipe(Item input, Item potion) {
        return getBrewingRecipeMap().get(getPotionHash(input, potion));
    }

    public ContainerRecipe matchContainerRecipe(Item input, Item potion) {
        return getContainerRecipeMap().get(getContainerHash(input, potion));
    }

    @PowerNukkitOnly
    public StonecutterRecipe matchStonecutterRecipe(Item output) {
        return getStonecutterRecipeMap().get(getItemHash(output));
    }

    @PowerNukkitOnly
    public CartographyRecipe matchCartographyRecipe(List inputList, Item primaryOutput, List extraOutputList) {
        int outputHash = getItemHash(primaryOutput);

        if (getCartographyRecipeMap().containsKey(outputHash)) {
            inputList.sort(recipeComparator);

            UUID inputHash = getMultiItemHash(inputList);

            Map recipes = getCartographyRecipeMap().get(outputHash);

            if (recipes == null) {
                return null;
            }

            CartographyRecipe recipe = recipes.get(inputHash);

            if (recipe != null && (recipe.matchItems(inputList, extraOutputList) || matchItemsAccumulation(recipe, inputList, primaryOutput, extraOutputList))) {
                return recipe;
            }

            for (CartographyRecipe cartographyRecipe : recipes.values()) {
                if (cartographyRecipe.matchItems(inputList, extraOutputList) || matchItemsAccumulation(cartographyRecipe, inputList, primaryOutput, extraOutputList)) {
                    return cartographyRecipe;
                }
            }
        }

        return null;
    }

    public CraftingRecipe matchRecipe(List inputList, Item primaryOutput, List extraOutputList) {
        //TODO: try to match special recipes before anything else (first they need to be implemented!)

        int outputHash = getItemHash(primaryOutput);
        if (getShapedRecipeMap().containsKey(outputHash)) {
            inputList.sort(recipeComparator);

            UUID inputHash = getMultiItemHash(inputList);

            Map recipeMap = getShapedRecipeMap().get(outputHash);

            if (recipeMap != null) {
                ShapedRecipe recipe = recipeMap.get(inputHash);

                if (recipe != null && (recipe.matchItems(inputList, extraOutputList) || matchItemsAccumulation(recipe, inputList, primaryOutput, extraOutputList))) {
                    return recipe;
                }

                for (ShapedRecipe shapedRecipe : recipeMap.values()) {
                    if (shapedRecipe.matchItems(inputList, extraOutputList) || matchItemsAccumulation(shapedRecipe, inputList, primaryOutput, extraOutputList)) {
                        return shapedRecipe;
                    }
                }
            }
        }

        if (getShapelessRecipeMap().containsKey(outputHash)) {
            inputList.sort(recipeComparator);

            UUID inputHash = getMultiItemHash(inputList);

            Map recipes = getShapelessRecipeMap().get(outputHash);

            if (recipes == null) {
                return null;
            }

            ShapelessRecipe recipe = recipes.get(inputHash);

            if (recipe != null && (recipe.matchItems(inputList, extraOutputList) || matchItemsAccumulation(recipe, inputList, primaryOutput, extraOutputList))) {
                return recipe;
            }

            for (ShapelessRecipe shapelessRecipe : recipes.values()) {
                if (shapelessRecipe.matchItems(inputList, extraOutputList) || matchItemsAccumulation(shapelessRecipe, inputList, primaryOutput, extraOutputList)) {
                    return shapelessRecipe;
                }
            }
        }

        return null;
    }

    private boolean matchItemsAccumulation(SmithingRecipe recipe, List inputList, Item primaryOutput) {
        Item recipeResult = recipe.getResult();
        if (primaryOutput.equals(recipeResult, recipeResult.hasMeta(), recipeResult.hasCompoundTag()) && primaryOutput.getCount() % recipeResult.getCount() == 0) {
            int multiplier = primaryOutput.getCount() / recipeResult.getCount();
            return recipe.matchItems(inputList, multiplier);
        }
        return false;
    }

    private boolean matchItemsAccumulation(CraftingRecipe recipe, List inputList, Item primaryOutput, List extraOutputList) {
        Item recipeResult = recipe.getResult();
        if (primaryOutput.equals(recipeResult, recipeResult.hasMeta(), recipeResult.hasCompoundTag()) && primaryOutput.getCount() % recipeResult.getCount() == 0) {
            int multiplier = primaryOutput.getCount() / recipeResult.getCount();
            return recipe.matchItems(inputList, extraOutputList, multiplier);
        }
        return false;
    }

    @Since("1.19.50-r3")
    @PowerNukkitXOnly
    @Nullable
    public ModProcessRecipe matchModProcessRecipe(@NotNull String category, @NotNull List inputList) {
        var recipeMap = getModProcessRecipeMap();
        var subMap = recipeMap.get(category);
        if (subMap != null) {
            var uuid = getMultiItemHash(inputList);
            var recipe = subMap.get(uuid);
            if (recipe != null) return recipe;
            for (var modProcessRecipe : subMap.values()) {
                if (modProcessRecipe.matchItems(Collections.unmodifiableList(inputList))) {
                    return modProcessRecipe;
                }
            }
        }
        return null;
    }

    @Since("1.19.50-r3")
    @PowerNukkitXOnly
    public void setRecipeXp(Recipe recipe, double xp) {
        recipeXpMap.put(recipe, xp);
    }

    @PowerNukkitXOnly
    @Since("1.19.50-r3")
    public static void setCraftingPacket(DataPacket craftingPacket) {
        CraftingManager.packet = craftingPacket;
    }

    @Since("1.19.50-r3")
    @PowerNukkitXOnly
    public double getRecipeXp(Recipe recipe) {
        return recipeXpMap.getOrDefault(recipe, 0.0);
    }

    private void addRecipe(Recipe recipe) {
        ++RECIPE_COUNT;
        if (recipe instanceof CraftingRecipe || recipe instanceof StonecutterRecipe) {
            UUID id = Utils.dataToUUID(String.valueOf(RECIPE_COUNT), String.valueOf(recipe.getResult().getId()), String.valueOf(recipe.getResult().getDamage()), String.valueOf(recipe.getResult().getCount()), Arrays.toString(recipe.getResult().getCompoundTag()));
            if (recipe instanceof CraftingRecipe) {
                ((CraftingRecipe) recipe).setId(id);
            } else {
                ((StonecutterRecipe) recipe).setId(id);
            }
        }
        this.recipeList.add(recipe);
    }

    public static class Entry {
        final int resultItemId;
        final int resultMeta;
        final int ingredientItemId;
        final int ingredientMeta;
        final String recipeShape;
        final int resultAmount;

        public Entry(int resultItemId, int resultMeta, int ingredientItemId, int ingredientMeta, String recipeShape, int resultAmount) {
            this.resultItemId = resultItemId;
            this.resultMeta = resultMeta;
            this.ingredientItemId = ingredientItemId;
            this.ingredientMeta = ingredientMeta;
            this.recipeShape = recipeShape;
            this.resultAmount = resultAmount;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy