diff --git a/gradle.properties b/gradle.properties index 5a2481f94..a907f3cea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ forge_version=33.0.42 # Provided APIs # ######################################################### jei_minecraft_version=1.16.2 -jei_version=7.1.1.15 +jei_version=7.3.2.25 top_version=3.0.3-beta-6 hwyla_version=1.10.8-B72_1.15.2 ctm_version=MC1.15.2-1.1.0.9 diff --git a/src/main/java/appeng/core/Registration.java b/src/main/java/appeng/core/Registration.java index f1f13d9bf..8c1ef1551 100644 --- a/src/main/java/appeng/core/Registration.java +++ b/src/main/java/appeng/core/Registration.java @@ -22,7 +22,6 @@ import java.util.function.Supplier; import net.minecraft.advancements.CriteriaTriggers; import net.minecraft.block.Block; -import net.minecraft.block.BlockState; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ScreenManager; import net.minecraft.client.particle.ParticleManager; @@ -33,17 +32,7 @@ import net.minecraft.item.crafting.IRecipeSerializer; import net.minecraft.particles.ParticleType; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.ResourceLocation; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.WorldGenRegistries; import net.minecraft.world.World; -import net.minecraft.world.biome.Biome; -import net.minecraft.world.gen.GenerationStage; -import net.minecraft.world.gen.feature.Feature; -import net.minecraft.world.gen.feature.IFeatureConfig; -import net.minecraft.world.gen.feature.OreFeatureConfig; -import net.minecraft.world.gen.feature.structure.Structure; -import net.minecraft.world.gen.placement.IPlacementConfig; -import net.minecraft.world.gen.placement.Placement; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.client.event.ColorHandlerEvent; @@ -62,14 +51,12 @@ import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.loading.FMLEnvironment; import net.minecraftforge.fml.network.IContainerFactory; -import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.IForgeRegistry; import appeng.api.config.Upgrades; import appeng.api.definitions.IBlocks; import appeng.api.definitions.IItems; import appeng.api.definitions.IParts; -import appeng.api.features.AEFeature; import appeng.api.features.IRegistryContainer; import appeng.api.features.IWirelessTermHandler; import appeng.api.features.IWorldGen; @@ -219,15 +206,8 @@ import appeng.recipes.game.FacadeRecipe; import appeng.recipes.handlers.GrinderRecipeSerializer; import appeng.recipes.handlers.InscriberRecipeSerializer; import appeng.server.AECommand; -import appeng.spatial.SpatialStorageBiome; -import appeng.spatial.SpatialStorageChunkGenerator; -import appeng.spatial.SpatialStorageDimensionIds; import appeng.tile.AEBaseTileEntity; import appeng.tile.crafting.MolecularAssemblerRenderer; -import appeng.worldgen.ChargedQuartzOreConfig; -import appeng.worldgen.ChargedQuartzOreFeature; -import appeng.worldgen.meteorite.MeteoriteStructure; -import appeng.worldgen.meteorite.MeteoriteStructurePiece; final class Registration { diff --git a/src/main/java/appeng/core/sync/packets/JEIRecipePacket.java b/src/main/java/appeng/core/sync/packets/JEIRecipePacket.java index c98c64b21..7c0ec908c 100644 --- a/src/main/java/appeng/core/sync/packets/JEIRecipePacket.java +++ b/src/main/java/appeng/core/sync/packets/JEIRecipePacket.java @@ -18,15 +18,27 @@ package appeng.core.sync.packets; +import java.util.Arrays; + +import javax.annotation.Nullable; + +import com.google.common.base.Preconditions; +import com.mojang.datafixers.util.Pair; + import io.netty.buffer.Unpooled; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.inventory.container.Container; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompoundNBT; -import net.minecraft.nbt.ListNBT; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.item.crafting.IRecipeSerializer; +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.item.crafting.ShapedRecipe; import net.minecraft.network.PacketBuffer; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.crafting.IShapedRecipe; import net.minecraftforge.items.IItemHandler; import appeng.api.config.Actionable; @@ -40,8 +52,10 @@ import appeng.api.networking.storage.IStorageGrid; import appeng.api.storage.IMEMonitor; import appeng.api.storage.channels.IItemStorageChannel; import appeng.api.storage.data.IAEItemStack; +import appeng.container.implementations.PatternTermContainer; import appeng.core.Api; import appeng.core.sync.BasePacket; +import appeng.core.sync.BasePacketHandler; import appeng.core.sync.network.INetworkInfo; import appeng.helpers.IContainerCraftingPacket; import appeng.items.storage.ViewCellItem; @@ -54,152 +68,289 @@ import appeng.util.prioritylist.IPartitionList; public class JEIRecipePacket extends BasePacket { - private ItemStack[][] recipe; + /** + * Transmit only a recipe ID. + */ + private static final int INLINE_RECIPE_NONE = 1; + + /** + * Transmit the information about the recipe we actually need. This is + * explicitly limited since this is untrusted client->server info. + */ + private static final int INLINE_RECIPE_SHAPED = 2; + + private ResourceLocation recipeId; + /** + * This is optional, in case the client already knows it could not resolve the + * recipe id. + */ + @Nullable + private IRecipe recipe; + private boolean crafting; public JEIRecipePacket(final PacketBuffer stream) { - final CompoundNBT comp = stream.readCompoundTag(); - if (comp != null) { - this.recipe = new ItemStack[9][]; - for (int x = 0; x < this.recipe.length; x++) { - final ListNBT list = comp.getList("#" + x, 10); - if (list.size() > 0) { - this.recipe[x] = new ItemStack[list.size()]; - for (int y = 0; y < list.size(); y++) { - this.recipe[x][y] = ItemStack.read(list.getCompound(y)); - } - } - } - } - } + this.crafting = stream.readBoolean(); + final String id = stream.readString(Short.MAX_VALUE); + this.recipeId = new ResourceLocation(id); - // api - public JEIRecipePacket(final CompoundNBT recipe) { - final PacketBuffer data = new PacketBuffer(Unpooled.buffer()); - - data.writeInt(this.getPacketID()); - - data.writeCompoundTag(recipe); - - this.configureWrite(data); - } - - @Override - public void serverPacketData(final INetworkInfo manager, final PlayerEntity player) { - final ServerPlayerEntity pmp = (ServerPlayerEntity) player; - final Container con = pmp.openContainer; - - if (!(con instanceof IContainerCraftingPacket)) { - return; - } - - final IContainerCraftingPacket cct = (IContainerCraftingPacket) con; - final IGridNode node = cct.getNetworkNode(); - - if (node == null) { - return; - } - - final IGrid grid = node.getGrid(); - if (grid == null) { - return; - } - - final IStorageGrid inv = grid.getCache(IStorageGrid.class); - final IEnergyGrid energy = grid.getCache(IEnergyGrid.class); - final ISecurityGrid security = grid.getCache(ISecurityGrid.class); - final ICraftingGrid crafting = grid.getCache(ICraftingGrid.class); - final IItemHandler craftMatrix = cct.getInventoryByName("crafting"); - final IItemHandler playerInventory = cct.getInventoryByName("player"); - - if (inv != null && this.recipe != null && security != null) { - final IMEMonitor storage = inv - .getInventory(Api.instance().storage().getStorageChannel(IItemStorageChannel.class)); - final IPartitionList filter = ViewCellItem.createFilter(cct.getViewCells()); - - for (int x = 0; x < craftMatrix.getSlots(); x++) { - ItemStack currentItem = craftMatrix.getStackInSlot(x); - - // prepare slots - if (!currentItem.isEmpty()) { - // already the correct item? - ItemStack newItem = this.canUseInSlot(x, currentItem); - - // put away old item - if (newItem != currentItem && security.hasPermission(player, SecurityPermissions.INJECT)) { - final IAEItemStack in = AEItemStack.fromItemStack(currentItem); - final IAEItemStack out = cct.useRealItems() - ? Platform.poweredInsert(energy, storage, in, cct.getActionSource()) - : null; - if (out != null) { - currentItem = out.createItemStack(); - } else { - currentItem = ItemStack.EMPTY; - } - } - } - - if (currentItem.isEmpty() && this.recipe[x] != null) { - // for each variant - for (int y = 0; y < this.recipe[x].length && currentItem.isEmpty(); y++) { - final IAEItemStack request = AEItemStack.fromItemStack(this.recipe[x][y]); - if (request != null) { - // try ae - if ((filter == null || filter.isListed(request)) - && security.hasPermission(player, SecurityPermissions.EXTRACT)) { - request.setStackSize(1); - IAEItemStack out; - - if (cct.useRealItems()) { - out = Platform.poweredExtraction(energy, storage, request, cct.getActionSource()); - } else { - // Query the crafting grid if there is a pattern providing the item - if (!crafting.getCraftingFor(request, null, 0, null).isEmpty()) { - out = request; - } else { - // Fall back using an existing item - out = storage.extractItems(request, Actionable.SIMULATE, cct.getActionSource()); - } - } - - if (out != null) { - currentItem = out.createItemStack(); - } - } - - // try inventory - if (currentItem.isEmpty()) { - AdaptorItemHandler ad = new AdaptorItemHandler(playerInventory); - - if (cct.useRealItems()) { - currentItem = ad.removeItems(1, this.recipe[x][y], null); - } else { - currentItem = ad.simulateRemove(1, this.recipe[x][y], null); - } - } - } - } - } - ItemHandlerUtil.setStackInSlot(craftMatrix, x, currentItem); - } - con.onCraftMatrixChanged(new WrapperInvItemHandler(craftMatrix)); + int inlineRecipeType = stream.readVarInt(); + switch (inlineRecipeType) { + case INLINE_RECIPE_NONE: + break; + case INLINE_RECIPE_SHAPED: + recipe = IRecipeSerializer.CRAFTING_SHAPED.read(this.recipeId, stream); + break; + default: + throw new IllegalArgumentException("Invalid inline recipe type."); } } /** - * - * @param slot - * @param is itemstack - * @return is if it can be used, else EMPTY + * Sends a recipe identified by the given recipe ID to the server for either + * filling a crafting grid or a pattern. */ - private ItemStack canUseInSlot(int slot, ItemStack is) { - if (this.recipe[slot] != null) { - for (ItemStack option : this.recipe[slot]) { - if (is.isItemEqual(option)) { - return is; + public JEIRecipePacket(final ResourceLocation recipeId, final boolean crafting) { + PacketBuffer data = createCommonHeader(recipeId, crafting, INLINE_RECIPE_NONE); + this.configureWrite(data); + } + + /** + * Sends a recipe to the server for either filling a crafting grid or a pattern. + *

+ * Prefer the id-based constructor above whereever possible. + */ + public JEIRecipePacket(final ShapedRecipe recipe, final boolean crafting) { + PacketBuffer data = createCommonHeader(recipe.getId(), crafting, INLINE_RECIPE_SHAPED); + IRecipeSerializer.CRAFTING_SHAPED.write(data, recipe); + this.configureWrite(data); + } + + private PacketBuffer createCommonHeader(ResourceLocation recipeId, boolean crafting, int inlineRecipeType) { + final PacketBuffer data = new PacketBuffer(Unpooled.buffer()); + + data.writeInt(this.getPacketID()); + data.writeBoolean(crafting); + data.writeResourceLocation(recipeId); + data.writeVarInt(inlineRecipeType); + + return data; + } + + /** + * Servside handler for this packet. + *

+ * Makes use of {@link Preconditions#checkArgument(boolean)} as the + * {@link BasePacketHandler} is catching them and in general these cases should + * never happen except in an error case and should be logged then. + */ + @Override + public void serverPacketData(final INetworkInfo manager, final PlayerEntity player) { + // Setup and verification + final ServerPlayerEntity pmp = (ServerPlayerEntity) player; + final Container con = pmp.openContainer; + Preconditions.checkArgument(con instanceof IContainerCraftingPacket); + + IRecipe recipe = player.getEntityWorld().getRecipeManager().getRecipe(this.recipeId).orElse(null); + if (recipe == null && this.recipe != null) { + // Certain recipes (i.e. AE2 facades) are represented in JEI as ShapedRecipe's, + // while in reality they + // are special recipes. Those recipes are sent across the wire... + recipe = this.recipe; + } + Preconditions.checkArgument(recipe != null); + + final IContainerCraftingPacket cct = (IContainerCraftingPacket) con; + final IGridNode node = cct.getNetworkNode(); + + Preconditions.checkArgument(node != null); + + final IGrid grid = node.getGrid(); + Preconditions.checkArgument(grid != null); + + final IStorageGrid inv = grid.getCache(IStorageGrid.class); + Preconditions.checkArgument(inv != null); + + final ISecurityGrid security = grid.getCache(ISecurityGrid.class); + Preconditions.checkArgument(security != null); + + final IEnergyGrid energy = grid.getCache(IEnergyGrid.class); + final ICraftingGrid crafting = grid.getCache(ICraftingGrid.class); + final IItemHandler craftMatrix = cct.getInventoryByName("crafting"); + final IItemHandler playerInventory = cct.getInventoryByName("player"); + + final IMEMonitor storage = inv + .getInventory(Api.instance().storage().getStorageChannel(IItemStorageChannel.class)); + final IPartitionList filter = ViewCellItem.createFilter(cct.getViewCells()); + final NonNullList ingredients = this.ensure3by3CraftingMatrix(recipe); + + // Handle each slot + for (int x = 0; x < craftMatrix.getSlots(); x++) { + ItemStack currentItem = craftMatrix.getStackInSlot(x); + Ingredient ingredient = ingredients.get(x); + + // prepare slots + if (!currentItem.isEmpty()) { + // already the correct item? True, skip everything else + ItemStack newItem = this.canUseInSlot(ingredient, currentItem); + + // put away old item, if not correct + if (newItem != currentItem && security.hasPermission(player, SecurityPermissions.INJECT)) { + final IAEItemStack in = AEItemStack.fromItemStack(currentItem); + final IAEItemStack out = cct.useRealItems() + ? Platform.poweredInsert(energy, storage, in, cct.getActionSource()) + : null; + if (out != null) { + currentItem = out.createItemStack(); + } else { + currentItem = ItemStack.EMPTY; + } + } + } + + // Find item or pattern from the network + if (currentItem.isEmpty() && security.hasPermission(player, SecurityPermissions.EXTRACT)) { + IAEItemStack out; + + if (cct.useRealItems()) { + IAEItemStack request = findBestMatchingItemStack(ingredient, filter, storage, cct); + out = request != null + ? Platform.poweredExtraction(energy, storage, request.setStackSize(1), + cct.getActionSource()) + : null; + } else { + out = findBestMatchingPattern(ingredient, filter, crafting, storage, cct); + if (out == null) { + out = findBestMatchingItemStack(ingredient, filter, storage, cct); + } + if (out == null && ingredient.getMatchingStacks().length > 0) { + out = AEItemStack.fromItemStack(ingredient.getMatchingStacks()[0]); + } + } + + if (out != null) { + currentItem = out.createItemStack(); + } + } + + // If still nothing, search the player inventory. + if (currentItem.isEmpty()) { + ItemStack[] matchingStacks = ingredient.getMatchingStacks(); + for (ItemStack matchingStack : matchingStacks) { + if (currentItem.isEmpty()) { + AdaptorItemHandler ad = new AdaptorItemHandler(playerInventory); + + if (cct.useRealItems()) { + currentItem = ad.removeItems(1, matchingStack, null); + } else { + currentItem = ad.simulateRemove(1, matchingStack, null); + } + } + } + } + ItemHandlerUtil.setStackInSlot(craftMatrix, x, currentItem); + } + + if (!this.crafting) { + this.handleProcessing(con, cct, recipe); + } + + con.onCraftMatrixChanged(new WrapperInvItemHandler(craftMatrix)); + } + + /** + * Expand any recipe to a 3x3 matrix. + *

+ * Will throw an {@link IllegalArgumentException} in case it has more than 9 or + * a shaped recipe is either wider or higher than 3. ingredients. + */ + private NonNullList ensure3by3CraftingMatrix(IRecipe recipe) { + NonNullList ingredients = recipe.getIngredients(); + NonNullList expandedIngredients = NonNullList.withSize(9, Ingredient.EMPTY); + + Preconditions.checkArgument(ingredients.size() <= 9); + + // shaped recipes can be smaller than 3x3, expand to 3x3 to match the crafting + // matrix + if (recipe instanceof IShapedRecipe) { + IShapedRecipe shapedRecipe = (IShapedRecipe) recipe; + int width = shapedRecipe.getRecipeWidth(); + int height = shapedRecipe.getRecipeHeight(); + Preconditions.checkArgument(width <= 3 && height <= 3); + + for (int h = 0; h < height; h++) { + for (int w = 0; w < width; w++) { + int source = w + h * width; + int target = w + h * 3; + Ingredient i = ingredients.get(source); + expandedIngredients.set(target, i); } } } - return ItemStack.EMPTY; + // Anything else should be a flat list + else { + for (int i = 0; i < ingredients.size(); i++) { + expandedIngredients.set(i, ingredients.get(i)); + } + } + + return expandedIngredients; + } + + /** + * @param is itemstack + * @return is if it can be used, else EMPTY + */ + private ItemStack canUseInSlot(Ingredient ingredient, ItemStack is) { + return Arrays.stream(ingredient.getMatchingStacks()).filter(p -> p.isItemEqual(is)).findFirst() + .orElse(ItemStack.EMPTY); + } + + /** + * Finds the first matching itemstack with the highest stored amount. + */ + private IAEItemStack findBestMatchingItemStack(Ingredient ingredients, IPartitionList filter, + IMEMonitor storage, IContainerCraftingPacket cct) { + return Arrays.stream(ingredients.getMatchingStacks()).map(AEItemStack::fromItemStack) // + .filter(r -> r != null && (filter == null || filter.isListed(r))) // + .map(s -> { + // Determine the stored count + IAEItemStack stored = storage.extractItems(s.copy().setStackSize(Long.MAX_VALUE), + Actionable.SIMULATE, cct.getActionSource()); + return Pair.of(s, stored != null ? stored.getStackSize() : 0); + }).min((left, right) -> Long.compare(right.getSecond(), left.getSecond()))// + .map(Pair::getFirst).orElse(null); + } + + /** + * This tries to find the first pattern matching the list of ingredients. + *

+ * As additional condition, it sorts by the stored amount to return the one with + * the highest stored amount. + */ + private IAEItemStack findBestMatchingPattern(Ingredient ingredients, IPartitionList filter, + ICraftingGrid crafting, IMEMonitor storage, IContainerCraftingPacket cct) { + return Arrays.stream(ingredients.getMatchingStacks()).map(AEItemStack::fromItemStack) + .filter(r -> r != null && (filter == null || filter.isListed(r))) + .map(s -> s.setCraftable(!crafting.getCraftingFor(s, null, 0, null).isEmpty())) + .filter(IAEItemStack::isCraftable).map(s -> { + final IAEItemStack stored = storage.extractItems(s, Actionable.SIMULATE, cct.getActionSource()); + return s.setStackSize(stored != null ? stored.getStackSize() : 0); + }).min((left, right) -> { + final int craftable = Boolean.compare(left.isCraftable(), right.isCraftable()); + return craftable != 0 ? craftable : Long.compare(right.getStackSize(), left.getStackSize()); + }).orElse(null); + } + + private void handleProcessing(Container con, IContainerCraftingPacket cct, IRecipe recipe) { + if (con instanceof PatternTermContainer) { + PatternTermContainer patternTerm = (PatternTermContainer) con; + if (!patternTerm.craftingMode) { + final IItemHandler output = cct.getInventoryByName("output"); + ItemHandlerUtil.setStackInSlot(output, 0, recipe.getRecipeOutput()); + ItemHandlerUtil.setStackInSlot(output, 1, ItemStack.EMPTY); + ItemHandlerUtil.setStackInSlot(output, 2, ItemStack.EMPTY); + } + } } } diff --git a/src/main/java/appeng/core/sync/packets/MEInventoryUpdatePacket.java b/src/main/java/appeng/core/sync/packets/MEInventoryUpdatePacket.java index 4ec3f2289..f456a5aaa 100644 --- a/src/main/java/appeng/core/sync/packets/MEInventoryUpdatePacket.java +++ b/src/main/java/appeng/core/sync/packets/MEInventoryUpdatePacket.java @@ -19,11 +19,9 @@ package appeng.core.sync.packets; import java.io.IOException; -import java.io.OutputStream; import java.nio.BufferOverflowException; import java.util.ArrayList; import java.util.List; -import java.util.zip.GZIPOutputStream; import javax.annotation.Nullable; diff --git a/src/main/java/appeng/entity/TinyTNTPrimedEntity.java b/src/main/java/appeng/entity/TinyTNTPrimedEntity.java index cebea022f..9e2bcd5ba 100644 --- a/src/main/java/appeng/entity/TinyTNTPrimedEntity.java +++ b/src/main/java/appeng/entity/TinyTNTPrimedEntity.java @@ -40,7 +40,6 @@ import net.minecraft.util.SoundEvents; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.world.Explosion; -import net.minecraft.world.Explosion.Mode; import net.minecraft.world.World; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; import net.minecraftforge.fml.network.NetworkHooks; diff --git a/src/main/java/appeng/fluids/client/gui/widgets/FluidSlotWidget.java b/src/main/java/appeng/fluids/client/gui/widgets/FluidSlotWidget.java index 6abc95297..e81ad055f 100644 --- a/src/main/java/appeng/fluids/client/gui/widgets/FluidSlotWidget.java +++ b/src/main/java/appeng/fluids/client/gui/widgets/FluidSlotWidget.java @@ -15,7 +15,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.TranslationTextComponent; -import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.fluids.FluidAttributes; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidUtil; diff --git a/src/main/java/appeng/fluids/container/FluidConfigurableContainer.java b/src/main/java/appeng/fluids/container/FluidConfigurableContainer.java index f607edf9f..4727b0e4d 100644 --- a/src/main/java/appeng/fluids/container/FluidConfigurableContainer.java +++ b/src/main/java/appeng/fluids/container/FluidConfigurableContainer.java @@ -9,7 +9,6 @@ import net.minecraft.entity.player.PlayerInventory; import net.minecraft.inventory.container.ContainerType; import net.minecraft.inventory.container.IContainerListener; import net.minecraft.item.ItemStack; -import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidUtil; diff --git a/src/main/java/appeng/integration/abstraction/IJEI.java b/src/main/java/appeng/integration/abstraction/IJEI.java index 17199941b..1494908ed 100644 --- a/src/main/java/appeng/integration/abstraction/IJEI.java +++ b/src/main/java/appeng/integration/abstraction/IJEI.java @@ -18,6 +18,8 @@ package appeng.integration.abstraction; +import mezz.jei.api.runtime.IJeiRuntime; + import appeng.integration.IIntegrationModule; /** @@ -25,6 +27,10 @@ import appeng.integration.IIntegrationModule; */ public interface IJEI extends IIntegrationModule { + default IJeiRuntime getRuntime() { + return null; + } + default String getSearchText() { return ""; } diff --git a/src/main/java/appeng/integration/modules/jei/CraftingRecipeTransferHandler.java b/src/main/java/appeng/integration/modules/jei/CraftingRecipeTransferHandler.java new file mode 100644 index 000000000..7b3e8e7c2 --- /dev/null +++ b/src/main/java/appeng/integration/modules/jei/CraftingRecipeTransferHandler.java @@ -0,0 +1,47 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2020 Team Appliedenergistics, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.integration.modules.jei; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.crafting.IRecipe; + +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper; + +import appeng.container.implementations.CraftingTermContainer; + +public class CraftingRecipeTransferHandler extends RecipeTransferHandler { + + CraftingRecipeTransferHandler(Class containerClass, IRecipeTransferHandlerHelper helper) { + super(containerClass, helper); + } + + @Override + protected IRecipeTransferError doTransferRecipe(CraftingTermContainer container, IRecipe recipe, + IRecipeLayout recipeLayout, PlayerEntity player, boolean maxTransfer) { + return null; + } + + @Override + protected boolean isCrafting() { + return true; + } + +} diff --git a/src/main/java/appeng/integration/modules/jei/FacadeRegistryPlugin.java b/src/main/java/appeng/integration/modules/jei/FacadeRegistryPlugin.java index a776cbe5e..4a7e28bcb 100644 --- a/src/main/java/appeng/integration/modules/jei/FacadeRegistryPlugin.java +++ b/src/main/java/appeng/integration/modules/jei/FacadeRegistryPlugin.java @@ -111,6 +111,8 @@ class FacadeRegistryPlugin implements IRecipeManagerPlugin { ingredients.set(7, Ingredient.fromStacks(cableAnchor)); ingredients.set(4, Ingredient.fromStacks(textureItem)); + result.setCount(4); + return new ShapedRecipe(id, "", 3, 3, ingredients, result); } diff --git a/src/main/java/appeng/integration/modules/jei/JEIPlugin.java b/src/main/java/appeng/integration/modules/jei/JEIPlugin.java index d7365ce86..a31c0e152 100644 --- a/src/main/java/appeng/integration/modules/jei/JEIPlugin.java +++ b/src/main/java/appeng/integration/modules/jei/JEIPlugin.java @@ -82,11 +82,15 @@ public class JEIPlugin implements IModPlugin { @Override public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration) { - // Allow recipe transfer from JEI to crafting and pattern terminal - registration.addRecipeTransferHandler(new RecipeTransferHandler<>(CraftingTermContainer.class), - VanillaRecipeCategoryUid.CRAFTING); - registration.addRecipeTransferHandler(new RecipeTransferHandler<>(PatternTermContainer.class), + + // Allow vanilla crafting recipe transfer from JEI to crafting terminal + registration.addRecipeTransferHandler( + new CraftingRecipeTransferHandler(CraftingTermContainer.class, registration.getTransferHelper()), VanillaRecipeCategoryUid.CRAFTING); + + // Universal handler for processing to try and handle all IRecipe + registration.addUniversalRecipeTransferHandler( + new PatternRecipeTransferHandler(PatternTermContainer.class, registration.getTransferHelper())); } @Override diff --git a/src/main/java/appeng/integration/modules/jei/JeiRuntimeAdapter.java b/src/main/java/appeng/integration/modules/jei/JeiRuntimeAdapter.java index d6e62787f..184756b00 100644 --- a/src/main/java/appeng/integration/modules/jei/JeiRuntimeAdapter.java +++ b/src/main/java/appeng/integration/modules/jei/JeiRuntimeAdapter.java @@ -37,6 +37,10 @@ class JeiRuntimeAdapter implements IJEI { return true; } + public IJeiRuntime getRuntime() { + return runtime; + } + @Override public String getSearchText() { return Strings.nullToEmpty(this.runtime.getIngredientFilter().getFilterText()); diff --git a/src/main/java/appeng/integration/modules/jei/PatternRecipeTransferHandler.java b/src/main/java/appeng/integration/modules/jei/PatternRecipeTransferHandler.java new file mode 100644 index 000000000..e132604f9 --- /dev/null +++ b/src/main/java/appeng/integration/modules/jei/PatternRecipeTransferHandler.java @@ -0,0 +1,58 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2020 Team Appliedenergistics, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.integration.modules.jei; + +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.crafting.IRecipe; + +import mezz.jei.api.constants.VanillaRecipeCategoryUid; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper; + +import appeng.container.implementations.PatternTermContainer; + +public class PatternRecipeTransferHandler extends RecipeTransferHandler { + + PatternRecipeTransferHandler(Class containerClass, IRecipeTransferHandlerHelper helper) { + super(containerClass, helper); + } + + protected IRecipeTransferError doTransferRecipe(PatternTermContainer container, IRecipe recipe, + IRecipeLayout recipeLayout, PlayerEntity player, boolean maxTransfer) { + if (container.isCraftingMode() + && recipeLayout.getRecipeCategory().getUid() != VanillaRecipeCategoryUid.CRAFTING) { + return this.helper + .createUserErrorWithTooltip(I18n.format("jei.appliedenergistics2.requires_processing_mode")); + } + + if (recipe.getRecipeOutput().isEmpty()) { + return this.helper.createUserErrorWithTooltip(I18n.format("jei.appliedenergistics2.no_output")); + } + + return null; + } + + @Override + protected boolean isCrafting() { + return false; + } + +} diff --git a/src/main/java/appeng/integration/modules/jei/RecipeTransferHandler.java b/src/main/java/appeng/integration/modules/jei/RecipeTransferHandler.java index 89085726e..607cb4ab1 100644 --- a/src/main/java/appeng/integration/modules/jei/RecipeTransferHandler.java +++ b/src/main/java/appeng/integration/modules/jei/RecipeTransferHandler.java @@ -18,101 +18,128 @@ package appeng.integration.modules.jei; -import java.util.ArrayList; -import java.util.List; import java.util.Map; -import javax.annotation.Nullable; - +import net.minecraft.client.resources.I18n; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.inventory.container.Container; -import net.minecraft.inventory.container.Slot; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompoundNBT; -import net.minecraft.nbt.ListNBT; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.item.crafting.ShapedRecipe; +import net.minecraft.item.crafting.ShapelessRecipe; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; import mezz.jei.api.gui.IRecipeLayout; import mezz.jei.api.gui.ingredient.IGuiIngredient; import mezz.jei.api.recipe.transfer.IRecipeTransferError; import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper; -import appeng.container.slot.CraftingMatrixSlot; -import appeng.container.slot.FakeCraftingMatrixSlot; import appeng.core.sync.network.NetworkHandler; import appeng.core.sync.packets.JEIRecipePacket; -import appeng.util.Platform; +import appeng.helpers.IContainerCraftingPacket; -class RecipeTransferHandler implements IRecipeTransferHandler { +abstract class RecipeTransferHandler + implements IRecipeTransferHandler { private final Class containerClass; + protected final IRecipeTransferHandlerHelper helper; - RecipeTransferHandler(Class containerClass) { + RecipeTransferHandler(Class containerClass, IRecipeTransferHandlerHelper helper) { this.containerClass = containerClass; + this.helper = helper; } @Override - public Class getContainerClass() { + public final Class getContainerClass() { return this.containerClass; } - @Nullable @Override - public IRecipeTransferError transferRecipe(T container, IRecipeLayout recipeLayout, PlayerEntity player, - boolean maxTransfer, boolean doTransfer) { - if (!doTransfer) { - return null; + public final IRecipeTransferError transferRecipe(T container, Object recipe, IRecipeLayout recipeLayout, + PlayerEntity player, boolean maxTransfer, boolean doTransfer) { + if (!(recipe instanceof IRecipe)) { + return this.helper.createInternalError(); + } + final IRecipe irecipe = (IRecipe) recipe; + final ResourceLocation recipeId = irecipe.getId(); + + if (recipeId == null) { + return this.helper.createUserErrorWithTooltip(I18n.format("jei.appliedenergistics2.missing_id")); } - Map> ingredients = recipeLayout.getItemStacks() - .getGuiIngredients(); - - final CompoundNBT recipe = new CompoundNBT(); - - int slotIndex = 0; - for (Map.Entry> ingredientEntry : ingredients.entrySet()) { - IGuiIngredient ingredient = ingredientEntry.getValue(); - if (!ingredient.isInput()) { - continue; + // Check that the recipe can actually be looked up via the manager, i.e. our + // facade recipes + // have an ID, but are never registered with the recipe manager. + boolean canSendReference = true; + if (!player.getEntityWorld().getRecipeManager().getRecipe(recipeId).isPresent()) { + // Validate that the recipe is a shapeless or shapedrecipe, since we can + // serialize those + if (!(recipe instanceof ShapedRecipe) && !(recipe instanceof ShapelessRecipe)) { + return this.helper.createUserErrorWithTooltip(I18n.format("jei.appliedenergistics2.missing_id")); } + canSendReference = false; + } - for (final Slot slot : container.inventorySlots) { - if (slot instanceof CraftingMatrixSlot || slot instanceof FakeCraftingMatrixSlot) { - if (slot.getSlotIndex() == slotIndex) { - final ListNBT tags = new ListNBT(); - final List list = new ArrayList<>(); - final ItemStack displayed = ingredient.getDisplayedIngredient(); + if (!irecipe.canFit(3, 3)) { + return this.helper.createUserErrorWithTooltip(I18n.format("jei.appliedenergistics2.recipe_too_large")); + } - // prefer currently displayed item - if (displayed != null && !displayed.isEmpty()) { - list.add(displayed); + final IRecipeTransferError error = doTransferRecipe(container, irecipe, recipeLayout, player, maxTransfer); + + if (error != null) { + return error; + } + + if (doTransfer) { + if (canSendReference) { + NetworkHandler.instance().sendToServer(new JEIRecipePacket(recipeId, isCrafting())); + } else { + // To avoid earlier problems of too large packets being sent that crashed the + // client, + // as a fallback when the recipe ID could not be resolved, we'll just send the + // displayed + // items. + NonNullList flatIngredients = NonNullList.withSize(9, Ingredient.EMPTY); + ItemStack output = ItemStack.EMPTY; + + // Determine the first JEI slot that has an actual input, we'll use this to + // offset the + // crafting grid target slot + int firstInputSlot = recipeLayout.getItemStacks().getGuiIngredients().entrySet().stream() + .filter(e -> e.getValue().isInput()).mapToInt(Map.Entry::getKey).min().orElse(0); + + // Now map the actual ingredients into the output/input + for (Map.Entry> entry : recipeLayout.getItemStacks() + .getGuiIngredients().entrySet()) { + IGuiIngredient item = entry.getValue(); + if (item.getDisplayedIngredient() == null) { + continue; + } + + int inputIndex = entry.getKey() - firstInputSlot; + if (item.isInput() && inputIndex < flatIngredients.size()) { + ItemStack displayedIngredient = item.getDisplayedIngredient(); + if (displayedIngredient != null) { + flatIngredients.set(inputIndex, Ingredient.fromStacks(displayedIngredient)); } - - // prefer pure crystals. - for (ItemStack stack : ingredient.getAllIngredients()) { - if (Platform.isRecipePrioritized(stack)) { - list.add(0, stack); - } else { - list.add(stack); - } - } - - for (final ItemStack is : list) { - final CompoundNBT tag = new CompoundNBT(); - is.write(tag); - tags.add(tag); - } - - recipe.put("#" + slot.getSlotIndex(), tags); - break; + } else if (!item.isInput() && output.isEmpty()) { + output = item.getDisplayedIngredient(); } } + + ShapedRecipe fallbackRecipe = new ShapedRecipe(recipeId, "", 3, 3, flatIngredients, output); + NetworkHandler.instance().sendToServer(new JEIRecipePacket(fallbackRecipe, isCrafting())); } - - slotIndex++; } - NetworkHandler.instance().sendToServer(new JEIRecipePacket(recipe)); - return null; } + + protected abstract IRecipeTransferError doTransferRecipe(T container, IRecipe recipe, IRecipeLayout recipeLayout, + PlayerEntity player, boolean maxTransfer); + + protected abstract boolean isCrafting(); } diff --git a/src/main/java/appeng/mixins/structure/BiomeAccessor.java b/src/main/java/appeng/mixins/structure/BiomeAccessor.java index ece84ac80..6aa2e388f 100644 --- a/src/main/java/appeng/mixins/structure/BiomeAccessor.java +++ b/src/main/java/appeng/mixins/structure/BiomeAccessor.java @@ -7,7 +7,6 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; import net.minecraft.world.biome.Biome; -import net.minecraft.world.gen.feature.StructureFeature; import net.minecraft.world.gen.feature.structure.Structure; /** diff --git a/src/main/java/appeng/parts/automation/AnnihilationPlanePart.java b/src/main/java/appeng/parts/automation/AnnihilationPlanePart.java index de036c859..406bb05b6 100644 --- a/src/main/java/appeng/parts/automation/AnnihilationPlanePart.java +++ b/src/main/java/appeng/parts/automation/AnnihilationPlanePart.java @@ -34,7 +34,6 @@ import net.minecraft.item.Items; import net.minecraft.tags.BlockTags; import net.minecraft.tags.ITag; import net.minecraft.tags.ItemTags; -import net.minecraft.tags.Tag; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.AxisAlignedBB; diff --git a/src/main/resources/assets/appliedenergistics2/lang/en_us.json b/src/main/resources/assets/appliedenergistics2/lang/en_us.json index 295e03146..edce8ed2b 100644 --- a/src/main/resources/assets/appliedenergistics2/lang/en_us.json +++ b/src/main/resources/assets/appliedenergistics2/lang/en_us.json @@ -705,5 +705,9 @@ "waila.appliedenergistics2.P2POutput": "Linked (Output Side)", "waila.appliedenergistics2.P2PUnlinked": "Unlinked", "waila.appliedenergistics2.Showing": "Showing", - "waila.appliedenergistics2.Unlocked": "Unlocked" + "waila.appliedenergistics2.Unlocked": "Unlocked", + "jei.appliedenergistics2.missing_id": "Cannot identify recipe", + "jei.appliedenergistics2.recipe_too_large": "Recipe larger than 3x3", + "jei.appliedenergistics2.requires_processing_mode": "Requires processing mode", + "jei.appliedenergistics2.no_output": "Recipe has no output" }