Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
cn.nukkit.inventory.CraftingManager Maven / Gradle / Ivy
Go to download
A Minecraft Bedrock Edition server software implementation made in Java from scratch which supports all new features.
package cn.nukkit.inventory;
import cn.nukkit.Server;
import cn.nukkit.item.Item;
import cn.nukkit.item.ItemID;
import cn.nukkit.network.protocol.BatchPacket;
import cn.nukkit.network.protocol.CraftingDataPacket;
import cn.nukkit.utils.BinaryStream;
import cn.nukkit.utils.Config;
import cn.nukkit.utils.MainLogger;
import cn.nukkit.utils.Utils;
import io.netty.util.collection.CharObjectHashMap;
import io.netty.util.internal.EmptyArrays;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.extern.log4j.Log4j2;
import java.io.File;
import java.io.InputStream;
import java.util.*;
import java.util.zip.Deflater;
/**
* @author MagicDroidX (Nukkit Project)
*/
@Log4j2
public class CraftingManager {
public final Collection recipes = new ArrayDeque<>();
public static BatchPacket packet = null;
protected final Map> shapedRecipes = new Int2ObjectOpenHashMap<>();
public final Map furnaceRecipes = new Int2ObjectOpenHashMap<>();
public final Map blastFurnaceRecipes = new Int2ObjectOpenHashMap<>();
public final Map smokerRecipes = new Int2ObjectOpenHashMap<>();
public final Map campfireRecipes = new Int2ObjectOpenHashMap<>();
public final Map brewingRecipes = new Int2ObjectOpenHashMap<>();
public final Map containerRecipes = new Int2ObjectOpenHashMap<>();
public final Map stonecutterRecipes = new Int2ObjectOpenHashMap<>();
private static int RECIPE_COUNT = 0;
protected final Map> shapelessRecipes = new Int2ObjectOpenHashMap<>();
protected final Map> cartographyRecipes = new Int2ObjectOpenHashMap<>();
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());
};
public CraftingManager() {
InputStream recipesStream = Server.class.getClassLoader().getResourceAsStream("recipes.json");
if (recipesStream == null) {
throw new AssertionError("Unable to find recipes.json");
}
Config recipesConfig = new Config(Config.JSON);
recipesConfig.load(recipesStream);
this.loadRecipes(recipesConfig);
String path = Server.getInstance().getDataPath() + "custom_recipes.json";
File filePath = new File(path);
if (filePath.exists()) {
Config customRecipes = new Config(filePath, Config.JSON);
this.loadRecipes(customRecipes);
}
this.rebuildPacket();
MainLogger.getLogger().info("Loaded " + this.recipes.size() + " recipes.");
}
@SuppressWarnings("unchecked")
private void loadRecipes(Config config) {
List
recipes = config.getMapList("recipes");
MainLogger.getLogger().info("Loading recipes...");
for (Map recipe : recipes) {
try {
switch (Utils.toInt(recipe.get("type"))) {
case 0:
String craftingBlock = (String) recipe.get("block");
if (!"crafting_table".equals(craftingBlock) && !"stonecutter".equals(craftingBlock) && !"cartography_table".equalsIgnoreCase(craftingBlock)) {
// Ignore other recipes than crafting table, stonecutter and cartography table
continue;
}
// TODO: handle multiple result items
List outputs = ((List) recipe.get("output"));
if (outputs.size() > 1) {
continue;
}
Map first = outputs.get(0);
List- sorted = new ArrayList<>();
for (Map
ingredient : ((List) recipe.get("input"))) {
sorted.add(Item.fromJson(ingredient));
}
// Bake sorted list
sorted.sort(recipeComparator);
String recipeId = (String) recipe.get("id");
int priority = Utils.toInt(recipe.get("priority"));
switch (craftingBlock) {
case "crafting_table":
this.registerRecipe(new ShapelessRecipe(recipeId, priority, Item.fromJson(first), sorted));
break;
case "stonecutter":
this.registerRecipe(new StonecutterRecipe(recipeId, priority, Item.fromJson(first), sorted.get(0)));
break;
case "cartography_table":
this.registerRecipe(new CartographyRecipe(recipeId, priority, Item.fromJson(first), sorted));
break;
}
break;
case 1:
craftingBlock = (String) recipe.get("block");
if (!"crafting_table".equals(craftingBlock)) {
// Ignore other recipes than crafting table ones
continue;
}
outputs = (List) recipe.get("output");
first = outputs.remove(0);
String[] shape = ((List) recipe.get("shape")).toArray(EmptyArrays.EMPTY_STRINGS);
Map ingredients = new CharObjectHashMap<>();
List- extraResults = new ArrayList<>();
Map
> input = (Map) recipe.get("input");
for (Map.Entry> ingredientEntry : input.entrySet()) {
char ingredientChar = ingredientEntry.getKey().charAt(0);
Item ingredient = Item.fromJson(ingredientEntry.getValue());
ingredients.put(ingredientChar, ingredient);
}
for (Map data : outputs) {
extraResults.add(Item.fromJson(data));
}
recipeId = (String) recipe.get("id");
priority = Utils.toInt(recipe.get("priority"));
this.registerRecipe(new ShapedRecipe(recipeId, priority, Item.fromJson(first), shape, ingredients, extraResults));
break;
case 2:
case 3:
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 = Item.fromJson(resultMap);
Item inputItem;
try {
Map inputMap = (Map) recipe.get("input");
inputItem = Item.fromJson(inputMap);
} catch (Exception old) {
inputItem = Item.get(Utils.toInt(recipe.get("inputId")), recipe.containsKey("inputDamage") ? Utils.toInt(recipe.get("inputDamage")) : -1, 1);
}
switch (craftingBlock) {
case "furnace":
this.registerRecipe(new FurnaceRecipe(resultItem, inputItem));
break;
case "blast_furnace":
this.registerRecipe(new BlastFurnaceRecipe(resultItem, inputItem));
break;
case "smoker":
this.registerRecipe(new SmokerRecipe(resultItem, inputItem));
break;
case "campfire":
this.registerRecipe(new CampfireRecipe(resultItem, inputItem));
break;
}
break;
default:
break;
}
} catch (Exception e) {
MainLogger.getLogger().error("Exception during registering recipe", e);
}
}
// Load brewing recipes
List potionMixes = config.getMapList("potionMixes");
for (Map potionMix : potionMixes) {
int fromPotionId = ((Number) potionMix.get("inputId")).intValue(); // gson returns doubles...
int fromPotionMeta = ((Number) potionMix.get("inputMeta")).intValue();
int ingredient = ((Number) potionMix.get("reagentId")).intValue();
int ingredientMeta = ((Number) potionMix.get("reagentMeta")).intValue();
int toPotionId = ((Number) potionMix.get("outputId")).intValue();
int toPotionMeta = ((Number) potionMix.get("outputMeta")).intValue();
registerBrewingRecipe(new BrewingRecipe(Item.get(fromPotionId, fromPotionMeta), Item.get(ingredient, ingredientMeta), Item.get(toPotionId, toPotionMeta)));
}
List containerMixes = config.getMapList("containerMixes");
for (Map containerMix : containerMixes) {
int fromItemId = ((Number) containerMix.get("inputId")).intValue();
int ingredient = ((Number) containerMix.get("reagentId")).intValue();
int toItemId = ((Number) containerMix.get("outputId")).intValue();
registerContainerRecipe(new ContainerRecipe(Item.get(fromItemId), Item.get(ingredient), Item.get(toItemId)));
}
// 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))));
}
public void rebuildPacket() {
CraftingDataPacket pk = new CraftingDataPacket();
pk.cleanRecipes = true;
for (Recipe recipe : this.getRecipes()) {
if (recipe instanceof ShapedRecipe) {
pk.addShapedRecipe((ShapedRecipe) recipe);
} else if (recipe instanceof ShapelessRecipe) {
pk.addShapelessRecipe((ShapelessRecipe) recipe);
}
}
for (Map map : cartographyRecipes.values()) {
for (CartographyRecipe recipe : map.values()) {
pk.addCartographyRecipe(recipe);
}
}
for (FurnaceRecipe recipe : this.getFurnaceRecipes().values()) {
pk.addFurnaceRecipe(recipe);
}
for (SmokerRecipe recipe : smokerRecipes.values()) {
pk.addSmokerRecipe(recipe);
}
for (BlastFurnaceRecipe recipe : blastFurnaceRecipes.values()) {
pk.addBlastFurnaceRecipe(recipe);
}
for (CampfireRecipe recipe : campfireRecipes.values()) {
pk.addCampfireRecipeRecipe(recipe);
}
for (BrewingRecipe recipe : brewingRecipes.values()) {
pk.addBrewingRecipe(recipe);
}
for (ContainerRecipe recipe : containerRecipes.values()) {
pk.addContainerRecipe(recipe);
}
for (StonecutterRecipe recipe : stonecutterRecipes.values()) {
pk.addStonecutterRecipe(recipe);
}
pk.encode();
packet = pk.compress(Deflater.BEST_COMPRESSION);
}
public Collection getRecipes() {
return recipes;
}
public Map getFurnaceRecipes() {
return furnaceRecipes;
}
public FurnaceRecipe matchFurnaceRecipe(Item input) {
FurnaceRecipe recipe = this.furnaceRecipes.get(getItemHash(input));
if (recipe == null) recipe = this.furnaceRecipes.get(getItemHash(input.getId(), 0));
return recipe;
}
public CampfireRecipe matchCampfireRecipe(Item input) {
CampfireRecipe recipe = this.campfireRecipes.get(getItemHash(input));
if (recipe == null) recipe = this.campfireRecipes.get(getItemHash(input.getId(), 0));
return recipe;
}
public BlastFurnaceRecipe matchBlastFurnaceRecipe(Item input) {
BlastFurnaceRecipe recipe = this.blastFurnaceRecipes.get(getItemHash(input));
if (recipe == null) recipe = this.blastFurnaceRecipes.get(getItemHash(input.getId(), 0));
return recipe;
}
public SmokerRecipe matchSmokerRecipe(Item input) {
SmokerRecipe recipe = this.smokerRecipes.get(getItemHash(input));
if (recipe == null) recipe = this.smokerRecipes.get(getItemHash(input.getId(), 0));
return recipe;
}
private static UUID getMultiItemHash(Collection- items) {
BinaryStream stream = new BinaryStream();
for (Item item : items) {
stream.putVarInt(getFullItemHash(item));
}
return UUID.nameUUIDFromBytes(stream.getBuffer());
}
private static int getFullItemHash(Item item) {
return 31 * getItemHash(item) + item.getCount();
}
public void registerStonecutterRecipe(StonecutterRecipe recipe) {
this.stonecutterRecipes.put(getItemHash(recipe.getResult()), recipe);
}
public void registerFurnaceRecipe(FurnaceRecipe recipe) {
Item input = recipe.getInput();
this.furnaceRecipes.put(getItemHash(input), recipe);
}
public void registerBlastFurnaceRecipe(BlastFurnaceRecipe recipe) {
Item input = recipe.getInput();
this.blastFurnaceRecipes.put(getItemHash(input), recipe);
}
public void registerSmokerRecipe(SmokerRecipe recipe) {
Item input = recipe.getInput();
this.smokerRecipes.put(getItemHash(input), recipe);
}
public void registerCampfireRecipe(CampfireRecipe recipe) {
Item input = recipe.getInput();
this.campfireRecipes.put(getItemHash(input), recipe);
}
private static int getItemHash(Item item) {
return getItemHash(item.getId(), item.getDamage());
}
private static int getItemHash(int id, int meta) {
return id << 8 | meta & 0xFF;
}
public void registerShapedRecipe(ShapedRecipe recipe) {
int resultHash = getItemHash(recipe.getResult());
Map
map = shapedRecipes.computeIfAbsent(resultHash, k -> new HashMap<>());
List- inputList = new LinkedList<>(recipe.getIngredientsAggregate());
map.put(getMultiItemHash(inputList), recipe);
}
public void registerRecipe(Recipe recipe) {
UUID id = null;
if (recipe instanceof CraftingRecipe || recipe instanceof StonecutterRecipe) {
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);
this.recipes.add(recipe);
} else if (recipe instanceof StonecutterRecipe) {
((StonecutterRecipe) recipe).setId(id);
}
recipe.registerToCraftingManager(this);
}
public void registerCartographyRecipe(CartographyRecipe recipe) {
List
- list = recipe.getIngredientList();
list.sort(recipeComparator);
UUID hash = getMultiItemHash(list);
int resultHash = getItemHash(recipe.getResult());
Map
map = cartographyRecipes.computeIfAbsent(resultHash, k -> new HashMap<>());
map.put(hash, recipe);
}
public void registerShapelessRecipe(ShapelessRecipe recipe) {
List- list = recipe.getIngredientsAggregate();
UUID hash = getMultiItemHash(list);
int resultHash = getItemHash(recipe.getResult());
Map
map = shapelessRecipes.computeIfAbsent(resultHash, k -> new HashMap<>());
map.put(hash, recipe);
}
private static int getPotionHash(Item ingredient, Item potion) {
int ingredientHash = ((ingredient.getId() & 0x3FF) << 6) | (ingredient.getDamage() & 0x3F);
int potionHash = ((potion.getId() & 0x3FF) << 6) | (potion.getDamage() & 0x3F);
return ingredientHash << 16 | potionHash;
}
private static int getContainerHash(int ingredientId, int containerId) {
return (ingredientId << 9) | containerId;
}
public void registerBrewingRecipe(BrewingRecipe recipe) {
Item input = recipe.getIngredient();
Item potion = recipe.getInput();
int potionHash = getPotionHash(input, potion);
if (this.brewingRecipes.containsKey(potionHash)) {
log.warn("The brewing recipe "+brewingRecipes.get(potionHash)+" is being replaced by "+recipe);
}
this.brewingRecipes.put(potionHash, recipe);
}
public void registerContainerRecipe(ContainerRecipe recipe) {
Item input = recipe.getIngredient();
Item potion = recipe.getInput();
this.containerRecipes.put(getContainerHash(input.getId(), potion.getId()), recipe);
}
public BrewingRecipe matchBrewingRecipe(Item input, Item potion) {
return this.brewingRecipes.get(getPotionHash(input, potion));
}
public ContainerRecipe matchContainerRecipe(Item input, Item potion) {
return this.containerRecipes.get(getContainerHash(input.getId(), potion.getId()));
}
public StonecutterRecipe matchStonecutterRecipe(Item output) {
return this.stonecutterRecipes.get(getItemHash(output));
}
public CartographyRecipe matchCartographyRecipe(List- inputList, Item primaryOutput, List
- extraOutputList) {
int outputHash = getItemHash(primaryOutput);
if (cartographyRecipes.containsKey(outputHash)) {
inputList.sort(recipeComparator);
UUID inputHash = getMultiItemHash(inputList);
Map
recipes = cartographyRecipes.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 (this.shapedRecipes.containsKey(outputHash)) {
inputList.sort(recipeComparator);
UUID inputHash = getMultiItemHash(inputList);
Map
recipeMap = shapedRecipes.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 (shapelessRecipes.containsKey(outputHash)) {
inputList.sort(recipeComparator);
UUID inputHash = getMultiItemHash(inputList);
Map recipes = shapelessRecipes.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(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;
}
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;
}
}
}