This repository has been archived on 2024-10-31. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
LibZontreck/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java

440 lines
No EOL
18 KiB
Java

/*
* @file Recipes.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Recipe utility functionality.
*/
package dev.zontreck.libzontreck.edlibmc;
import net.minecraft.core.NonNullList;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.util.Tuple;
import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.StackedContents;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.EnchantedBookItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.EnchantmentInstance;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.ComposterBlock;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.brewing.BrewingRecipeRegistry;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.BiPredicate;
public class Crafting {
// -------------------------------------------------------------------------------------------------------------------
/**
* Returns a Crafting recipe by registry name.
*/
public static Optional<CraftingRecipe> getCraftingRecipe(Level world, ResourceLocation recipe_id) {
Recipe<?> recipe = world.getRecipeManager().byKey(recipe_id).orElse(null);
return (recipe instanceof CraftingRecipe) ? Optional.of((CraftingRecipe) recipe) : Optional.empty();
}
/**
* Returns a list of matching recipes by the first N slots (crafting grid slots) of the given inventory.
*/
public static List<CraftingRecipe> get3x3CraftingRecipes(Level world, Container crafting_grid_slots) {
return CraftingGrid.instance3x3.getRecipes(world, crafting_grid_slots);
}
/**
* Returns a recipe by the first N slots (crafting grid slots).
*/
public static Optional<CraftingRecipe> get3x3CraftingRecipe(Level world, Container crafting_grid_slots) {
return get3x3CraftingRecipes(world, crafting_grid_slots).stream().findFirst();
}
/**
* Returns the result item of the recipe with the given grid layout.
*/
public static ItemStack get3x3CraftingResult(Level world, Container grid, CraftingRecipe recipe) {
return CraftingGrid.instance3x3.getCraftingResult(world, grid, recipe);
}
/**
* Returns the items remaining in the grid after crafting 3x3.
*/
public static List<ItemStack> get3x3RemainingItems(Level world, Container grid, CraftingRecipe recipe) {
return CraftingGrid.instance3x3.getRemainingItems(world, grid, recipe);
}
public static List<ItemStack> get3x3Placement(Level world, CraftingRecipe recipe, Container item_inventory, @Nullable Container crafting_grid) {
final int width = 3;
final int height = 3;
if (!recipe.canCraftInDimensions(width, height)) return Collections.emptyList();
List<ItemStack> used = new ArrayList<>(); //NonNullList.withSize(width*height);
for (int i = width * height; i > 0; --i) used.add(ItemStack.EMPTY);
Container check_inventory = Inventories.copyOf(item_inventory);
Inventories.InventoryRange source = new Inventories.InventoryRange(check_inventory);
final List<Ingredient> ingredients = recipe.getIngredients();
final List<ItemStack> preferred = new ArrayList<>(width * height);
if (crafting_grid != null) {
for (int i = 0; i < crafting_grid.getContainerSize(); ++i) {
ItemStack stack = crafting_grid.getItem(i);
if (stack.isEmpty()) continue;
stack = stack.copy();
stack.setCount(1);
if (!source.extract(stack).isEmpty()) preferred.add(stack);
}
}
for (int i = 0; i < ingredients.size(); ++i) {
final Ingredient ingredient = ingredients.get(i);
if (ingredient == Ingredient.EMPTY) continue;
ItemStack stack = preferred.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY);
if (!stack.isEmpty()) {
preferred.remove(stack);
} else {
stack = source.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY);
if (stack.isEmpty()) return Collections.emptyList();
stack = stack.copy();
stack.setCount(1);
if (source.extract(stack).isEmpty()) return Collections.emptyList();
}
used.set(i, stack);
}
if (recipe instanceof ShapedRecipe shaped) {
List<ItemStack> placement = NonNullList.withSize(width * height, ItemStack.EMPTY);
for (int row = 0; row < shaped.getRecipeHeight(); ++row) {
for (int col = 0; col < shaped.getRecipeWidth(); ++col) {
placement.set(width * row + col, used.get(row * shaped.getRecipeWidth() + col));
}
}
return placement;
} else {
return used;
}
}
/**
* Returns the recipe for a given input stack to smelt, null if there is no recipe
* for the given type (SMELTING,BLASTING,SMOKING, etc).
*/
public static <T extends Recipe<?>> Optional<AbstractCookingRecipe> getFurnaceRecipe(RecipeType<T> recipe_type, Level world, ItemStack input_stack) {
if (input_stack.isEmpty()) {
return Optional.empty();
} else if (recipe_type == RecipeType.SMELTING) {
SimpleContainer inventory = new SimpleContainer(3);
inventory.setItem(0, input_stack);
SmeltingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMELTING, inventory, world).orElse(null);
return (recipe == null) ? Optional.empty() : Optional.of(recipe);
} else if (recipe_type == RecipeType.BLASTING) {
SimpleContainer inventory = new SimpleContainer(3);
inventory.setItem(0, input_stack);
BlastingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.BLASTING, inventory, world).orElse(null);
return (recipe == null) ? Optional.empty() : Optional.of(recipe);
} else if (recipe_type == RecipeType.SMOKING) {
SimpleContainer inventory = new SimpleContainer(3);
inventory.setItem(0, input_stack);
SmokingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMOKING, inventory, world).orElse(null);
return (recipe == null) ? Optional.empty() : Optional.of(recipe);
} else {
return Optional.empty();
}
}
// -------------------------------------------------------------------------------------------------------------------
public static <T extends Recipe<?>> int getSmeltingTimeNeeded(RecipeType<T> recipe_type, Level world, ItemStack stack) {
if (stack.isEmpty()) return 0;
final int t = getFurnaceRecipe(recipe_type, world, stack).map((AbstractCookingRecipe::getCookingTime)).orElse(0);
return (t <= 0) ? 200 : t;
}
/**
* Returns the burn time of an item when used as fuel, 0 if it is no fuel.
*/
public static int getFuelBurntime(Level world, ItemStack stack) {
if (stack.isEmpty()) return 0;
int t = ForgeHooks.getBurnTime(stack, null);
return Math.max(t, 0);
}
/**
* Returns true if an item can be used as fuel.
*/
public static boolean isFuel(Level world, ItemStack stack) {
return (getFuelBurntime(world, stack) > 0) || (stack.getItem() == Items.LAVA_BUCKET);
}
/**
* Returns burntime and remaining stack then the item shall be used as fuel.
*/
public static Tuple<Integer, ItemStack> consumeFuel(Level world, ItemStack stack) {
if (stack.isEmpty()) return new Tuple<>(0, stack);
int burnime = getFuelBurntime(world, stack);
if ((stack.getItem() == Items.LAVA_BUCKET)) {
if (burnime <= 0) burnime = 1000 * 20;
return new Tuple<>(burnime, new ItemStack(Items.BUCKET));
} else if (burnime <= 0) {
return new Tuple<>(0, stack);
} else {
ItemStack left_over = stack.copy();
left_over.shrink(1);
return new Tuple<>(burnime, left_over);
}
}
/**
* Returns true if the item can be used as brewing fuel.
*/
public static boolean isBrewingFuel(Level world, ItemStack stack) {
return (stack.getItem() == Items.BLAZE_POWDER) || (stack.getItem() == Items.BLAZE_ROD);
}
// -------------------------------------------------------------------------------------------------------------------
/**
* Returns true if the item can be used as brewing ingredient.
*/
public static boolean isBrewingIngredient(Level world, ItemStack stack) {
return BrewingRecipeRegistry.isValidIngredient(stack);
}
/**
* Returns true if the item can be used as brewing bottle.
*/
public static boolean isBrewingInput(Level world, ItemStack stack) {
return BrewingRecipeRegistry.isValidInput(stack);
}
/**
* Returns the burn time for brewing of the given stack.
*/
public static int getBrewingFuelBurntime(Level world, ItemStack stack) {
if (stack.isEmpty()) return 0;
if (stack.getItem() == Items.BLAZE_POWDER) return (400 * 20);
if (stack.getItem() == Items.BLAZE_ROD) return (400 * 40);
return 0;
}
/**
* Returns brewing burn time and remaining stack if the item shall be used as fuel.
*/
public static Tuple<Integer, ItemStack> consumeBrewingFuel(Level world, ItemStack stack) {
int burntime = getBrewingFuelBurntime(world, stack);
if (burntime <= 0) return new Tuple<>(0, stack.copy());
stack = stack.copy();
stack.shrink(1);
return new Tuple<>(burntime, stack.isEmpty() ? ItemStack.EMPTY : stack);
}
public static double getCompostingChance(ItemStack stack) {
return ComposterBlock.COMPOSTABLES.getOrDefault(stack.getItem(), 0);
}
/**
* Returns the enchtments bound to the given stack.
*/
public static Map<Enchantment, Integer> getEnchantmentsOnItem(Level world, ItemStack stack) {
return (stack.isEmpty() || (stack.getTag() == null)) ? Collections.emptyMap() : EnchantmentHelper.getEnchantments(stack);
}
// -------------------------------------------------------------------------------------------------------------------
/**
* Returns an enchanted book with the given enchantment, emtpy stack if not applicable.
*/
public static ItemStack getEnchantmentBook(Level world, Enchantment enchantment, int level) {
return ((!enchantment.isAllowedOnBooks()) || (level <= 0)) ? ItemStack.EMPTY : EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level));
}
// -------------------------------------------------------------------------------------------------------------------
/**
* Returns the accumulated repair cost for the given enchantments.
*/
public static int getEnchantmentRepairCost(Level world, Map<Enchantment, Integer> enchantments) {
int repair_cost = 0;
for (Map.Entry<Enchantment, Integer> e : enchantments.entrySet())
repair_cost = repair_cost * 2 + 1; // @see: RepairContainer.getNewRepairCost()
return repair_cost;
}
/**
* Trys to add an enchtment to the given stack, returns boolean success.
*/
public static boolean addEnchantmentOnItem(Level world, ItemStack stack, Enchantment enchantment, int level) {
if (stack.isEmpty() || (level <= 0) || (!stack.isEnchantable()) || (level >= enchantment.getMaxLevel()))
return false;
final Map<Enchantment, Integer> on_item = getEnchantmentsOnItem(world, stack);
if (on_item.keySet().stream().anyMatch(ench -> ench.isCompatibleWith(enchantment))) return false;
final ItemStack book = EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level));
if ((!(stack.isBookEnchantable(book) && enchantment.isAllowedOnBooks())) && (!stack.canApplyAtEnchantingTable(enchantment)) && (!enchantment.canEnchant(stack)))
return false;
final int existing_level = on_item.getOrDefault(enchantment, 0);
if (existing_level > 0) level = Mth.clamp(level + existing_level, 1, enchantment.getMaxLevel());
on_item.put(enchantment, level);
EnchantmentHelper.setEnchantments(on_item, stack);
stack.setRepairCost(getEnchantmentRepairCost(world, on_item));
return true;
}
/**
* Removes enchantments from a stack, returns the removed enchantments.
*/
public static Map<Enchantment, Integer> removeEnchantmentsOnItem(Level world, ItemStack stack, BiPredicate<Enchantment, Integer> filter) {
if (stack.isEmpty()) return Collections.emptyMap();
final Map<Enchantment, Integer> on_item = getEnchantmentsOnItem(world, stack);
final Map<Enchantment, Integer> removed = new HashMap<>();
for (Map.Entry<Enchantment, Integer> e : on_item.entrySet()) {
if (filter.test(e.getKey(), e.getValue())) {
removed.put(e.getKey(), e.getValue());
}
}
for (Enchantment e : removed.keySet()) {
on_item.remove(e);
}
EnchantmentHelper.setEnchantments(on_item, stack);
stack.setRepairCost(getEnchantmentRepairCost(world, on_item));
return removed;
}
public static final class CraftingGrid implements CraftingContainer {
private static final CraftingGrid instance3x3 = new CraftingGrid(3, 3);
final int _width;
private CraftingGrid(int width, int height) {
_width=width;
}
private void fill(Container grid) {
for (int i = 0; i < getContainerSize(); ++i)
setItem(i, i >= grid.getContainerSize() ? ItemStack.EMPTY : grid.getItem(i));
}
public List<CraftingRecipe> getRecipes(Level world, Container grid) {
fill(grid);
return world.getRecipeManager().getRecipesFor(RecipeType.CRAFTING, this, world);
}
public List<ItemStack> getRemainingItems(Level world, Container grid, CraftingRecipe recipe) {
fill(grid);
return recipe.getRemainingItems(this);
}
public ItemStack getCraftingResult(Level world, Container grid, CraftingRecipe recipe) {
fill(grid);
return recipe.assemble(this, world.registryAccess());
}
@Override
public int getWidth() {
return _width;
}
@Override
public int getHeight() {
return 0;
}
@Override
public List<ItemStack> getItems() {
return null;
}
@Override
public int getContainerSize() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public ItemStack getItem(int i) {
return null;
}
@Override
public ItemStack removeItem(int i, int i1) {
return null;
}
@Override
public ItemStack removeItemNoUpdate(int i) {
return null;
}
@Override
public void setItem(int i, ItemStack itemStack) {
}
@Override
public void setChanged() {
}
@Override
public boolean stillValid(Player player) {
return false;
}
@Override
public void clearContent() {
}
@Override
public void fillStackedContents(StackedContents stackedContents) {
}
}
public static final class BrewingOutput {
public static final int DEFAULT_BREWING_TIME = 400;
public static final BrewingOutput EMPTY = new BrewingOutput(ItemStack.EMPTY, new SimpleContainer(1), new SimpleContainer(1), 0, 0, DEFAULT_BREWING_TIME);
public final ItemStack item;
public final Container potionInventory;
public final Container ingredientInventory;
public final int potionSlot;
public final int ingredientSlot;
public final int brewTime;
public BrewingOutput(ItemStack output_potion, Container potion_inventory, Container ingredient_inventory, int potion_slot, int ingredient_slot, int time_needed) {
item = output_potion;
potionInventory = potion_inventory;
ingredientInventory = ingredient_inventory;
potionSlot = potion_slot;
ingredientSlot = ingredient_slot;
brewTime = time_needed;
}
public static BrewingOutput find(Level world, Container potion_inventory, Container ingredient_inventory) {
for (int potion_slot = 0; potion_slot < potion_inventory.getContainerSize(); ++potion_slot) {
final ItemStack pstack = potion_inventory.getItem(potion_slot);
if (!isBrewingInput(world, pstack)) continue;
for (int ingredient_slot = 0; ingredient_slot < ingredient_inventory.getContainerSize(); ++ingredient_slot) {
final ItemStack istack = ingredient_inventory.getItem(ingredient_slot);
if ((!isBrewingIngredient(world, istack)) || (ingredient_slot == potion_slot) || (isBrewingFuel(world, istack)))
continue;
final ItemStack result = BrewingRecipeRegistry.getOutput(pstack, istack);
if (result.isEmpty()) continue;
return new BrewingOutput(result, potion_inventory, ingredient_inventory, potion_slot, ingredient_slot, DEFAULT_BREWING_TIME);
}
}
return BrewingOutput.EMPTY;
}
}
}