package io.github.thebusybiscuit.slimefun4.core.multiblocks; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.Chest; import org.bukkit.block.data.type.Dispenser; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import io.github.thebusybiscuit.cscorelib2.inventory.InvUtils; import io.github.thebusybiscuit.cscorelib2.protection.ProtectableAction; import io.github.thebusybiscuit.slimefun4.api.SlimefunAddon; import io.github.thebusybiscuit.slimefun4.core.attributes.NotPlaceable; import io.github.thebusybiscuit.slimefun4.core.attributes.RecipeDisplayItem; import io.github.thebusybiscuit.slimefun4.core.handlers.MultiBlockInteractionHandler; import io.github.thebusybiscuit.slimefun4.implementation.SlimefunPlugin; import me.mrCookieSlime.Slimefun.Lists.RecipeType; import me.mrCookieSlime.Slimefun.Objects.Category; import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.SlimefunItem; import me.mrCookieSlime.Slimefun.api.BlockStorage; import me.mrCookieSlime.Slimefun.api.Slimefun; import me.mrCookieSlime.Slimefun.api.SlimefunItemStack; /** * A {@link MultiBlockMachine} is a {@link SlimefunItem} that is built in the {@link World}. * It holds recipes and a {@link MultiBlock} object which represents its structure. * * @author TheBusyBiscuit * * @see MultiBlock * */ public abstract class MultiBlockMachine extends SlimefunItem implements NotPlaceable, RecipeDisplayItem { private static final BlockFace[] outputFaces = { BlockFace.UP, BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST }; protected final List<ItemStack[]> recipes; protected final List<ItemStack> displayRecipes; protected final MultiBlock multiblock; public MultiBlockMachine(Category category, SlimefunItemStack item, ItemStack[] recipe, ItemStack[] machineRecipes, BlockFace trigger) { super(category, item, RecipeType.MULTIBLOCK, recipe); this.recipes = new ArrayList<>(); this.displayRecipes = new ArrayList<>(); this.displayRecipes.addAll(Arrays.asList(machineRecipes)); this.multiblock = new MultiBlock(this, convertItemStacksToMaterial(recipe), trigger); } public List<ItemStack[]> getRecipes() { return recipes; } @Override public List<ItemStack> getDisplayRecipes() { return displayRecipes; } public MultiBlock getMultiBlock() { return multiblock; } public void addRecipe(ItemStack[] input, ItemStack output) { Validate.notNull(output, "Recipes must have an Output!"); recipes.add(input); recipes.add(new ItemStack[] { output }); } @Override public void register(SlimefunAddon addon) { addItemHandler(getInteractionHandler()); super.register(addon); } @Override public void postRegister() { SlimefunPlugin.getRegistry().getMultiBlocks().add(multiblock); } @Override public void load() { super.load(); for (ItemStack recipeItem : displayRecipes) { SlimefunItem item = SlimefunItem.getByItem(recipeItem); if (item == null || !item.isDisabled()) { recipes.add(new ItemStack[] { recipeItem }); } } } protected MultiBlockInteractionHandler getInteractionHandler() { return (p, mb, b) -> { if (mb.equals(getMultiBlock())) { if (!isDisabled() && SlimefunPlugin.getProtectionManager().hasPermission(p, b.getLocation(), ProtectableAction.ACCESS_INVENTORIES) && Slimefun.hasUnlocked(p, this, true)) { onInteract(p, b); } return true; } else return false; }; } public abstract void onInteract(Player p, Block b); /** * Overloaded method for finding a potential output chest. * Fallbacks to the old system of putting the adding back into the dispenser. * Optional last argument Inventory placeCheckerInv is for a {@link MultiBlockMachine} that create * a dummy inventory to check if there's a space for the adding, i.e. Enhanced crafting table * * @param adding * The {@link ItemStack} that should be added * @param dispBlock * The {@link Block} of our {@link Dispenser} * @param dispInv * The {@link Inventory} of our {@link Dispenser} * @return The target {@link Inventory} */ protected Inventory findOutputInventory(ItemStack adding, Block dispBlock, Inventory dispInv) { return findOutputInventory(adding, dispBlock, dispInv, dispInv); } protected Inventory findOutputInventory(ItemStack product, Block dispBlock, Inventory dispInv, Inventory placeCheckerInv) { Inventory outputInv = findOutputChest(dispBlock, product); // This if-clause will trigger if no suitable output chest was found. It's functionally the same as the old fit // check for the dispenser, only refactored. if (outputInv == null && InvUtils.fits(placeCheckerInv, product)) { return dispInv; } else { return outputInv; } } protected Inventory findOutputChest(Block b, ItemStack output) { for (BlockFace face : outputFaces) { Block potentialOutput = b.getRelative(face); if (potentialOutput.getType() == Material.CHEST) { String id = BlockStorage.checkID(potentialOutput); if (id != null && id.equals("OUTPUT_CHEST")) { // Found the output chest! Now, let's check if we can fit the product in it. Inventory inv = ((Chest) potentialOutput.getState()).getInventory(); if (InvUtils.fits(inv, output)) { return inv; } } } } return null; } private static Material[] convertItemStacksToMaterial(ItemStack[] items) { List<Material> materials = new ArrayList<>(); for (ItemStack item : items) { if (item == null) { materials.add(null); } else if (item.getType() == Material.FLINT_AND_STEEL) { materials.add(Material.FIRE); } else { materials.add(item.getType()); } } return materials.toArray(new Material[0]); } }