diff --git a/src/main/java/appeng/core/sync/packets/PacketJEIRecipe.java b/src/main/java/appeng/core/sync/packets/PacketJEIRecipe.java index d1948eddb..c93ba55e0 100644 --- a/src/main/java/appeng/core/sync/packets/PacketJEIRecipe.java +++ b/src/main/java/appeng/core/sync/packets/PacketJEIRecipe.java @@ -32,9 +32,7 @@ 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.ContainerExpandedProcessingPatternTerm; import appeng.container.implementations.ContainerPatternEncoder; -import appeng.container.implementations.ContainerPatternTerm; import appeng.core.sync.AppEngPacket; import appeng.core.sync.network.INetworkInfo; import appeng.helpers.IContainerCraftingPacket; @@ -47,6 +45,7 @@ import appeng.util.item.AEItemStack; import appeng.util.prioritylist.IPartitionList; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.objects.Object2LongLinkedOpenHashMap; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.inventory.Container; @@ -71,6 +70,7 @@ public class PacketJEIRecipe extends AppEngPacket { private List recipe; private List output; + private boolean shouldCondense; static ItemStack[] emptyArray = {ItemStack.EMPTY}; @@ -80,6 +80,7 @@ public class PacketJEIRecipe extends AppEngPacket { bytes.skip(stream.readerIndex()); final NBTTagCompound comp = CompressedStreamTools.readCompressed(bytes); if (comp != null) { + this.shouldCondense = comp.getBoolean("condense"); this.recipe = new ArrayList<>(); for (int x = 0; x < comp.getKeySet().size(); x++) { @@ -104,7 +105,6 @@ public class PacketJEIRecipe extends AppEngPacket { } } } - } // api @@ -127,11 +127,10 @@ public class PacketJEIRecipe extends AppEngPacket { final EntityPlayerMP pmp = (EntityPlayerMP) player; final Container con = pmp.openContainer; - if (!(con instanceof IContainerCraftingPacket)) { + if (!(con instanceof IContainerCraftingPacket cct)) { return; } - final IContainerCraftingPacket cct = (IContainerCraftingPacket) con; final IGridNode node = cct.getNetworkNode(); if (node == null) { @@ -150,15 +149,28 @@ public class PacketJEIRecipe extends AppEngPacket { final IItemHandler craftMatrix = cct.getInventoryByName("crafting"); final IItemHandler playerInventory = cct.getInventoryByName("player"); + final Object2LongLinkedOpenHashMap condensedBuffer; + if (this.shouldCondense) { + condensedBuffer = new Object2LongLinkedOpenHashMap<>(); + } else { + condensedBuffer = null; + } + if (inv != null && this.recipe != null && security != null) { final IMEMonitor storage = inv.getInventory(AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class)); final IPartitionList filter = ItemViewCell.createFilter(cct.getViewCells()); - for (int x = 0; x < craftMatrix.getSlots(); x++) { - ItemStack currentItem = craftMatrix.getStackInSlot(x); + for (int x = 0; x < recipe.size(); x++) { + ItemStack currentItem; - if (x >= this.recipe.size()) { + if (x < craftMatrix.getSlots()) { + currentItem = craftMatrix.getStackInSlot(x); + } else if (this.shouldCondense) { + // If the inputs should be condensed, we can read past the current grid. currentItem = ItemStack.EMPTY; + } else { + // Otherwise break. + break; } // prepare slots @@ -251,7 +263,32 @@ public class PacketJEIRecipe extends AppEngPacket { } } } - ItemHandlerUtil.setStackInSlot(craftMatrix, x, currentItem); + + if (condensedBuffer != null) { + var aeItemStack = AEItemStack.fromItemStack(currentItem); + if (aeItemStack != null) { + condensedBuffer.compute(aeItemStack, (k, v) -> (v == null ? 0 : v) + k.getStackSize()); + } + } else { + ItemHandlerUtil.setStackInSlot(craftMatrix, x, currentItem); + } + } + + if (condensedBuffer != null) { + var slotIndex = 0; + for (var entry : condensedBuffer.entrySet()) { + if (slotIndex >= craftMatrix.getSlots()) { + break; + } + + ItemHandlerUtil.setStackInSlot(craftMatrix, slotIndex, + entry.getKey().copy().setStackSize(entry.getValue()).createItemStack()); + slotIndex++; + } + + for (var i = slotIndex; i < craftMatrix.getSlots(); i++) { + ItemHandlerUtil.setStackInSlot(craftMatrix, i, ItemStack.EMPTY); + } } con.onCraftMatrixChanged(new WrapperInvItemHandler(craftMatrix)); diff --git a/src/main/java/appeng/integration/modules/jei/JEIMissingItem.java b/src/main/java/appeng/integration/modules/jei/JEIMissingItem.java index b5326dc31..c832a5799 100644 --- a/src/main/java/appeng/integration/modules/jei/JEIMissingItem.java +++ b/src/main/java/appeng/integration/modules/jei/JEIMissingItem.java @@ -16,9 +16,9 @@ import mezz.jei.gui.TooltipRenderer; import mezz.jei.gui.recipes.RecipeLayout; import mezz.jei.gui.recipes.RecipeTransferButton; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; import net.minecraft.inventory.Container; import net.minecraft.item.ItemStack; -import net.minecraft.util.text.translation.I18n; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.wrapper.PlayerMainInvWrapper; @@ -26,12 +26,14 @@ import javax.annotation.Nonnull; import java.awt.*; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.List; import static mezz.jei.api.recipe.transfer.IRecipeTransferError.Type.USER_FACING; +import static net.minecraft.client.resources.I18n.format; public class JEIMissingItem implements IRecipeTransferError { - + private final IRecipeLayout recipeLayout; private boolean errored; public long lastUpdate; private final List craftableSlots = new ArrayList<>(); @@ -40,8 +42,11 @@ public class JEIMissingItem implements IRecipeTransferError { IItemList available = AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class).createList(); IItemList used = AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class).createList(); + private boolean foundAny; JEIMissingItem(Container container, @Nonnull IRecipeLayout recipeLayout) { + this.recipeLayout = recipeLayout; + if (container instanceof ContainerMEMonitorable) { IItemList ir = ((ContainerMEMonitorable) container).items; @@ -57,8 +62,7 @@ public class JEIMissingItem implements IRecipeTransferError { if (i.isInput() && !i.getAllIngredients().isEmpty()) { List allIngredients = i.getAllIngredients(); for (Object allIngredient : allIngredients) { - if (allIngredient instanceof ItemStack) { - ItemStack stack = (ItemStack) allIngredient; + if (allIngredient instanceof ItemStack stack) { if (!stack.isEmpty()) { IAEItemStack search = AEItemStack.fromItemStack(stack); if (stack.getItem().isDamageable() || Platform.isGTDamageableItem(stack.getItem())) { @@ -93,7 +97,8 @@ public class JEIMissingItem implements IRecipeTransferError { } if (!found) { this.errored = true; - break; + } else{ + this.foundAny = true; } } } @@ -103,6 +108,13 @@ public class JEIMissingItem implements IRecipeTransferError { @Nonnull @Override public Type getType() { + // Workaround. Re-enable the button if not errored. + if (this.errored && this.foundAny && this.recipeLayout instanceof RecipeLayout castedRecipeLayout) { + var recipeTransferButton = castedRecipeLayout.getRecipeTransferButton(); + if (recipeTransferButton != null) { + recipeTransferButton.enabled = true; + } + } return USER_FACING; } @@ -111,12 +123,15 @@ public class JEIMissingItem implements IRecipeTransferError { Container c = minecraft.player.openContainer; if (c instanceof ContainerMEMonitorable container) { IItemList ir = ((ContainerMEMonitorable) c).items; - boolean found = false; - boolean foundAny = false; - boolean craftable = false; - boolean foundAnyCraftable = false; + boolean found; + boolean craftable; + + int foundMissing = 0; + int foundCraftables = 0; int currentSlot = 0; + this.errored = false; + this.foundAny = false; if (System.currentTimeMillis() - lastUpdate > 1000) { lastUpdate = System.currentTimeMillis(); @@ -144,6 +159,7 @@ public class JEIMissingItem implements IRecipeTransferError { found = false; craftable = false; IItemList valid = AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class).createList(); + if (i.isInput()) { List allIngredients = i.getAllIngredients(); for (Object allIngredient : allIngredients) { @@ -209,11 +225,12 @@ public class JEIMissingItem implements IRecipeTransferError { i.drawHighlight(minecraft, new Color(0.0f, 0.0f, 1.0f, 0.4f), recipeX, recipeY); this.craftableSlots.add(currentSlot); recipeLayout.getItemStacks().set(currentSlot, validStacks); - foundAnyCraftable = true; + foundCraftables++; } else { i.drawHighlight(minecraft, new Color(1.0f, 0.0f, 0.0f, 0.4f), recipeX, recipeY); } this.errored = true; + foundMissing++; } else { foundAny = true; this.foundSlots.add(currentSlot); @@ -227,17 +244,39 @@ public class JEIMissingItem implements IRecipeTransferError { List tooltipLines = new ArrayList<>(); b.init(c, minecraft.player); if (errored && foundAny) { - tooltipLines.add(I18n.translateToLocal("gui.tooltips.appliedenergistics2.PartialTransfer")); + tooltipLines.add(format("gui.tooltips.appliedenergistics2.PartialTransfer")); b.enabled = true; b.visible = true; + + tooltipLines.add(format("gui.tooltips.appliedenergistics2.CondenseItems")); } - if (errored) { - tooltipLines.add(I18n.translateToLocal("gui.tooltips.appliedenergistics2.MissingItem")); + + if (foundMissing > 0) { + tooltipLines.add(format("gui.tooltips.appliedenergistics2.MissingItem", String.valueOf(foundMissing))); } - if (foundAnyCraftable) { - tooltipLines.add(I18n.translateToLocal("gui.tooltips.appliedenergistics2.CraftableItem")); + if (foundCraftables > 0) { + tooltipLines.add(format("gui.tooltips.appliedenergistics2.CraftableItem", foundCraftables)); } - TooltipRenderer.drawHoveringText(minecraft, tooltipLines, mouseX, mouseY); + + var longestStringWidth = minecraft.fontRenderer.getStringWidth(tooltipLines.stream() + .max(Comparator.comparingInt(String::length)).get()); + + var background = ((RecipeLayout) recipeLayout).getRecipeCategory().getBackground(); + var scaledresolution = new ScaledResolution(minecraft); + + // Mostly reverse-engineered Minecraft code. + final int offset; + if (mouseX + longestStringWidth + 4 + 12 > scaledresolution.getScaledWidth()) { + // The tooltip will appear to the left of the mouse cursor. + // Need to offset Y so that the tooltip doesn't block the ingredients. + offset = background.getHeight() + recipeY + + (minecraft.fontRenderer.FONT_HEIGHT * tooltipLines.size() + + 2 * (tooltipLines.size() - 1)) / 2 + + 4; + } else { + offset = mouseY; + } + TooltipRenderer.drawHoveringText(minecraft, tooltipLines, mouseX, offset); } } } diff --git a/src/main/java/appeng/integration/modules/jei/JEITransferInfo.java b/src/main/java/appeng/integration/modules/jei/JEITransferInfo.java new file mode 100644 index 000000000..04e037f10 --- /dev/null +++ b/src/main/java/appeng/integration/modules/jei/JEITransferInfo.java @@ -0,0 +1,44 @@ +package appeng.integration.modules.jei; + +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import mezz.jei.gui.TooltipRenderer; +import mezz.jei.gui.recipes.RecipeLayout; +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.I18n; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; + +public class JEITransferInfo implements IRecipeTransferError { + + public static final JEITransferInfo INSTANCE = new JEITransferInfo(); + private RecipeLayout recipeLayout; + + + private JEITransferInfo() {} + + @Override + public @NotNull Type getType() { + // Workaround. Re-enable the button. + if (recipeLayout != null) { + var recipeTransferButton = recipeLayout.getRecipeTransferButton(); + if (recipeTransferButton != null) { + recipeTransferButton.enabled = true; + } + } + return Type.USER_FACING; + } + + public void setRecipeLayout(RecipeLayout recipeLayout) { + this.recipeLayout = recipeLayout; + } + + @Override + public void showError(@NotNull Minecraft minecraft, int mouseX, int mouseY, @NotNull IRecipeLayout recipeLayout, int recipeX, int recipeY) { + var tooltipLines = new ArrayList(); + tooltipLines.add(I18n.format("gui.tooltips.appliedenergistics2.PartialTransfer")); + tooltipLines.add(I18n.format("gui.tooltips.appliedenergistics2.CondenseItems")); + TooltipRenderer.drawHoveringText(minecraft, tooltipLines, mouseX, mouseY); + } +} diff --git a/src/main/java/appeng/integration/modules/jei/RecipeTransferHandler.java b/src/main/java/appeng/integration/modules/jei/RecipeTransferHandler.java index e3e39e904..e67c60785 100644 --- a/src/main/java/appeng/integration/modules/jei/RecipeTransferHandler.java +++ b/src/main/java/appeng/integration/modules/jei/RecipeTransferHandler.java @@ -22,8 +22,6 @@ package appeng.integration.modules.jei; import appeng.container.implementations.ContainerCraftingTerm; import appeng.container.implementations.ContainerPatternEncoder; import appeng.container.implementations.ContainerWirelessCraftingTerminal; -import appeng.container.slot.SlotCraftingMatrix; -import appeng.container.slot.SlotFakeCraftingMatrix; import appeng.core.AELog; import appeng.core.sync.network.NetworkHandler; import appeng.core.sync.packets.PacketJEIRecipe; @@ -34,13 +32,14 @@ import mezz.jei.api.gui.IRecipeLayout; import mezz.jei.api.recipe.VanillaRecipeCategoryUid; import mezz.jei.api.recipe.transfer.IRecipeTransferError; import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; +import mezz.jei.gui.recipes.RecipeLayout; import mezz.jei.transfer.RecipeTransferErrorInternal; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.Container; -import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; +import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -61,7 +60,7 @@ class RecipeTransferHandler implements IRecipeTransferHandl } @Override - public Class getContainerClass() { + public @NotNull Class getContainerClass() { return this.containerClass; } @@ -77,19 +76,24 @@ class RecipeTransferHandler implements IRecipeTransferHandl if (!doTransfer) { if (recipeType.equals(VanillaRecipeCategoryUid.CRAFTING) && (container instanceof ContainerCraftingTerm || container instanceof ContainerWirelessCraftingTerminal)) { JEIMissingItem error = new JEIMissingItem(container, recipeLayout); + if (error.errored()) return error; } - return null; + + if (container instanceof ContainerPatternEncoder || container instanceof ContainerCraftingTerm) { + JEITransferInfo.INSTANCE.setRecipeLayout((RecipeLayout) recipeLayout); + return JEITransferInfo.INSTANCE; + } } if (container instanceof ContainerPatternEncoder) { try { - if (!((ContainerPatternEncoder) container).isCraftingMode()) { + if (!((ContainerPatternEncoder) container).isCraftingMode() && !maxTransfer) { if (recipeType.equals(VanillaRecipeCategoryUid.CRAFTING)) { NetworkHandler.instance().sendToServer(new PacketValueConfig("PatternTerminal.CraftMode", "1")); } - } else if (!recipeType.equals(VanillaRecipeCategoryUid.CRAFTING)) { + } else if (!recipeType.equals(VanillaRecipeCategoryUid.CRAFTING) || maxTransfer) { NetworkHandler.instance().sendToServer(new PacketValueConfig("PatternTerminal.CraftMode", "0")); } @@ -104,8 +108,7 @@ class RecipeTransferHandler implements IRecipeTransferHandl final NBTTagCompound recipe = new NBTTagCompound(); final NBTTagList outputs = new NBTTagList(); - - int slotIndex = 0; + var slotIndex = 0; for (Map.Entry> ingredientEntry : ingredients.entrySet()) { IGuiIngredient ingredient = ingredientEntry.getValue(); if (!ingredient.isInput()) { @@ -117,45 +120,39 @@ class RecipeTransferHandler implements IRecipeTransferHandl continue; } - for (final Slot slot : container.inventorySlots) { - if (slot instanceof SlotCraftingMatrix || slot instanceof SlotFakeCraftingMatrix) { - if (slot.getSlotIndex() == slotIndex) { - final NBTTagList tags = new NBTTagList(); - final List list = new ArrayList<>(); - final ItemStack displayed = ingredient.getDisplayedIngredient(); + final NBTTagList tags = new NBTTagList(); + final List list = new ArrayList<>(); + final ItemStack displayed = ingredient.getDisplayedIngredient(); - // prefer currently displayed item - if (displayed != null && !displayed.isEmpty()) { - list.add(displayed); - } + // prefer currently displayed item + if (displayed != null && !displayed.isEmpty()) { + list.add(displayed); + } - // prefer pure crystals. - for (ItemStack stack : ingredient.getAllIngredients()) { - if (stack == null) { - continue; - } - if (Platform.isRecipePrioritized(stack)) { - list.add(0, stack); - } else { - list.add(stack); - } - } - - for (final ItemStack is : list) { - final NBTTagCompound tag = stackToNBT(is); - tags.appendTag(tag); - } - - recipe.setTag("#" + slot.getSlotIndex(), tags); - break; - } + // prefer pure crystals. + for (ItemStack stack : ingredient.getAllIngredients()) { + if (stack == null) { + continue; + } + if (Platform.isRecipePrioritized(stack)) { + list.add(0, stack); + } else { + list.add(stack); } } + for (final ItemStack is : list) { + final NBTTagCompound tag = stackToNBT(is); + tags.appendTag(tag); + } + + recipe.setTag("#" + slotIndex, tags); slotIndex++; } + recipe.setTag("outputs", outputs); + recipe.setBoolean("condense", maxTransfer); try { NetworkHandler.instance().sendToServer(new PacketJEIRecipe(recipe)); diff --git a/src/main/resources/assets/appliedenergistics2/lang/en_us.lang b/src/main/resources/assets/appliedenergistics2/lang/en_us.lang index 0710950fd..054400b98 100644 --- a/src/main/resources/assets/appliedenergistics2/lang/en_us.lang +++ b/src/main/resources/assets/appliedenergistics2/lang/en_us.lang @@ -397,8 +397,9 @@ gui.tooltips.appliedenergistics2.SearchFieldInputs=Recipe Inputs gui.tooltips.appliedenergistics2.SearchFieldOutputs=Recipe Outputs gui.tooltips.appliedenergistics2.SearchFieldNames=Interface Names gui.tooltips.appliedenergistics2.PartialTransfer=Move stored items into the crafting grid -gui.tooltips.appliedenergistics2.MissingItem=§cMissing item -gui.tooltips.appliedenergistics2.CraftableItem=§9Craftable item +gui.tooltips.appliedenergistics2.MissingItem=§cMissing %s items +gui.tooltips.appliedenergistics2.CraftableItem=§9Found %s craftable items +gui.tooltips.appliedenergistics2.CondenseItems=§5- hold Shift to condense items // Units gui.appliedenergistics2.units.appliedenergstics=AE