diff --git a/src/main/java/com/refinedmods/refinedstorage/container/GridContainer.java b/src/main/java/com/refinedmods/refinedstorage/container/GridContainer.java index ad393239f..b3d939e30 100644 --- a/src/main/java/com/refinedmods/refinedstorage/container/GridContainer.java +++ b/src/main/java/com/refinedmods/refinedstorage/container/GridContainer.java @@ -47,6 +47,10 @@ public class GridContainer extends BaseContainer implements ICraftingGridListene grid.addCraftingListener(this); } + public IScreenInfoProvider getScreenInfoProvider() { + return screenInfoProvider; + } + public void setScreenInfoProvider(IScreenInfoProvider screenInfoProvider) { this.screenInfoProvider = screenInfoProvider; } diff --git a/src/main/java/com/refinedmods/refinedstorage/integration/jei/GridRecipeTransferHandler.java b/src/main/java/com/refinedmods/refinedstorage/integration/jei/GridRecipeTransferHandler.java index c777052a3..c7155d315 100644 --- a/src/main/java/com/refinedmods/refinedstorage/integration/jei/GridRecipeTransferHandler.java +++ b/src/main/java/com/refinedmods/refinedstorage/integration/jei/GridRecipeTransferHandler.java @@ -4,20 +4,25 @@ import com.refinedmods.refinedstorage.RS; import com.refinedmods.refinedstorage.api.network.grid.GridType; import com.refinedmods.refinedstorage.api.network.grid.IGrid; import com.refinedmods.refinedstorage.container.GridContainer; +import com.refinedmods.refinedstorage.network.grid.GridCraftingPreviewRequestMessage; import com.refinedmods.refinedstorage.network.grid.GridProcessingTransferMessage; import com.refinedmods.refinedstorage.network.grid.GridTransferMessage; +import com.refinedmods.refinedstorage.screen.grid.GridScreen; +import com.refinedmods.refinedstorage.screen.grid.stack.IGridStack; +import com.refinedmods.refinedstorage.screen.grid.stack.ItemGridStack; import mezz.jei.api.constants.VanillaRecipeCategoryUid; import mezz.jei.api.gui.IRecipeLayout; import mezz.jei.api.gui.ingredient.IGuiIngredient; -import mezz.jei.api.recipe.category.IRecipeCategory; import mezz.jei.api.recipe.transfer.IRecipeTransferError; import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; +import net.minecraft.client.gui.screen.Screen; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.inventory.CraftingInventory; import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.FluidStack; import javax.annotation.Nonnull; +import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; @@ -33,55 +38,107 @@ public class GridRecipeTransferHandler implements IRecipeTransferHandler inputs = new LinkedList<>(); - List outputs = new LinkedList<>(); - - List fluidInputs = new LinkedList<>(); - List fluidOutputs = new LinkedList<>(); - - for (IGuiIngredient guiIngredient : recipeLayout.getItemStacks().getGuiIngredients().values()) { - if (guiIngredient != null && guiIngredient.getDisplayedIngredient() != null) { - ItemStack ingredient = guiIngredient.getDisplayedIngredient().copy(); - - if (guiIngredient.isInput()) { - inputs.add(ingredient); - } else { - outputs.add(ingredient); - } - } - } - - for (IGuiIngredient guiIngredient : recipeLayout.getFluidStacks().getGuiIngredients().values()) { - if (guiIngredient != null && guiIngredient.getDisplayedIngredient() != null) { - FluidStack ingredient = guiIngredient.getDisplayedIngredient().copy(); - - if (guiIngredient.isInput()) { - fluidInputs.add(ingredient); - } else { - fluidOutputs.add(ingredient); - } - } - } - - RS.NETWORK_HANDLER.sendToServer(new GridProcessingTransferMessage(inputs, outputs, fluidInputs, fluidOutputs)); + if (tracker.hasMissing()) { + if (doTransfer && Screen.hasControlDown()) { + tracker.getCraftingRequests().forEach((id, count) -> { + RS.NETWORK_HANDLER.sendToServer(new GridCraftingPreviewRequestMessage(id, count, Screen.hasShiftDown(), false)); + }); + } else if (doTransfer) { + moveItems(container, recipeLayout); } else { - RS.NETWORK_HANDLER.sendToServer(new GridTransferMessage( - recipeLayout.getItemStacks().getGuiIngredients(), - container.inventorySlots.stream().filter(s -> s.inventory instanceof CraftingInventory).collect(Collectors.toList()) - )); + return new RecipeTransferGridError(tracker); } } return null; } - private boolean isCraftingRecipe(IRecipeCategory recipeCategory) { - return recipeCategory.getUid().equals(VanillaRecipeCategoryUid.CRAFTING); + private IngredientTracker trackItems(GridContainer container, IRecipeLayout recipeLayout, PlayerEntity player) { + IngredientTracker tracker = new IngredientTracker(recipeLayout); + if (!(container.getScreenInfoProvider() instanceof GridScreen)) { + return tracker; + } + + // Using IGridView#getStacks will return a *filtered* list of items in the view, + // which will cause problems - especially if the user uses JEI synchronised searching. + // Instead, we will use IGridView#getAllStacks which provides an unordered view of all GridStacks. + Collection gridStacks = ((GridScreen) container.getScreenInfoProvider()).getView().getAllStacks(); + + // Check grid + for (IGridStack gridStack : gridStacks) { + if (gridStack instanceof ItemGridStack) { + tracker.addAvailableStack(((ItemGridStack) gridStack).getStack(), gridStack); + } + } + + // Check inventory + for (int inventorySlot = 0; inventorySlot < player.inventory.getSizeInventory(); inventorySlot++) { + if (!player.inventory.getStackInSlot(inventorySlot).isEmpty()) { + tracker.addAvailableStack(player.inventory.getStackInSlot(inventorySlot), null); + } + } + + // Check grid crafting slots + if (container.getGrid().getGridType().equals(GridType.CRAFTING)) { + CraftingInventory craftingMatrix = container.getGrid().getCraftingMatrix(); + if (craftingMatrix != null) { + for (int matrixSlot = 0; matrixSlot < craftingMatrix.getSizeInventory(); matrixSlot++) { + if (!craftingMatrix.getStackInSlot(matrixSlot).isEmpty()) { + tracker.addAvailableStack(craftingMatrix.getStackInSlot(matrixSlot), null); + } + } + } + } + + return tracker; } + + private void moveItems(GridContainer gridContainer, IRecipeLayout recipeLayout) { + IGrid grid = gridContainer.getGrid(); + + LAST_TRANSFER_TIME = System.currentTimeMillis(); + + if (grid.getGridType() == GridType.PATTERN && !recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) { + List inputs = new LinkedList<>(); + List outputs = new LinkedList<>(); + + List fluidInputs = new LinkedList<>(); + List fluidOutputs = new LinkedList<>(); + + for (IGuiIngredient guiIngredient : recipeLayout.getItemStacks().getGuiIngredients().values()) { + if (guiIngredient != null && guiIngredient.getDisplayedIngredient() != null) { + ItemStack ingredient = guiIngredient.getDisplayedIngredient().copy(); + + if (guiIngredient.isInput()) { + inputs.add(ingredient); + } else { + outputs.add(ingredient); + } + } + } + + for (IGuiIngredient guiIngredient : recipeLayout.getFluidStacks().getGuiIngredients().values()) { + if (guiIngredient != null && guiIngredient.getDisplayedIngredient() != null) { + FluidStack ingredient = guiIngredient.getDisplayedIngredient().copy(); + + if (guiIngredient.isInput()) { + fluidInputs.add(ingredient); + } else { + fluidOutputs.add(ingredient); + } + } + } + + RS.NETWORK_HANDLER.sendToServer(new GridProcessingTransferMessage(inputs, outputs, fluidInputs, fluidOutputs)); + } else { + RS.NETWORK_HANDLER.sendToServer(new GridTransferMessage( + recipeLayout.getItemStacks().getGuiIngredients(), + gridContainer.inventorySlots.stream().filter(s -> s.inventory instanceof CraftingInventory).collect(Collectors.toList()) + )); + } + } + } + diff --git a/src/main/java/com/refinedmods/refinedstorage/integration/jei/Ingredient.java b/src/main/java/com/refinedmods/refinedstorage/integration/jei/Ingredient.java new file mode 100644 index 000000000..96fc5faa5 --- /dev/null +++ b/src/main/java/com/refinedmods/refinedstorage/integration/jei/Ingredient.java @@ -0,0 +1,46 @@ +package com.refinedmods.refinedstorage.integration.jei; + +import mezz.jei.api.gui.ingredient.IGuiIngredient; +import net.minecraft.item.ItemStack; + +import java.util.UUID; + +class Ingredient { + private IGuiIngredient guiIngredient; + private UUID craftStackId; + private int required; + private int fulfilled; + + public Ingredient(IGuiIngredient guiIngredient) { + this.guiIngredient = guiIngredient; + this.required = guiIngredient.getAllIngredients().get(0).getCount(); + } + + public boolean isAvailable() { + return getMissingAmount() == 0; + } + + public int getMissingAmount() { + return required - fulfilled; + } + + public boolean isCraftable() { + return craftStackId != null; + } + + public IGuiIngredient getGuiIngredient() { + return guiIngredient; + } + + public UUID getCraftStackId() { + return craftStackId; + } + + public void setCraftStackId(UUID craftStackId) { + this.craftStackId = craftStackId; + } + + public void fulfill(int amount) { + fulfilled += amount; + } +} diff --git a/src/main/java/com/refinedmods/refinedstorage/integration/jei/IngredientTracker.java b/src/main/java/com/refinedmods/refinedstorage/integration/jei/IngredientTracker.java new file mode 100644 index 000000000..67faacba6 --- /dev/null +++ b/src/main/java/com/refinedmods/refinedstorage/integration/jei/IngredientTracker.java @@ -0,0 +1,71 @@ +package com.refinedmods.refinedstorage.integration.jei; + +import com.refinedmods.refinedstorage.api.util.IComparer; +import com.refinedmods.refinedstorage.apiimpl.API; +import com.refinedmods.refinedstorage.screen.grid.stack.IGridStack; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.gui.ingredient.IGuiIngredient; +import net.minecraft.item.ItemStack; + +import java.util.*; + +public class IngredientTracker { + private final List ingredients = new ArrayList<>(); + + public IngredientTracker(IRecipeLayout recipeLayout) { + for (IGuiIngredient guiIngredient : recipeLayout.getItemStacks().getGuiIngredients().values()) { + if (guiIngredient.isInput() && !guiIngredient.getAllIngredients().isEmpty()) { + ingredients.add(new Ingredient(guiIngredient)); + } + } + } + + public Collection getIngredients() { + return ingredients; + } + + public void addAvailableStack(ItemStack stack, IGridStack gridStack) { + int available = stack.getCount(); + + for (Ingredient ingredient : ingredients) { + if (available == 0) { + return; + } + + if (ingredient.isAvailable()) { + continue; + } + + Optional match = ingredient.getGuiIngredient().getAllIngredients().stream().filter((ItemStack matchingStack) -> API.instance().getComparer().isEqual(stack, matchingStack, IComparer.COMPARE_NBT)).findFirst(); + if (match.isPresent()) { + //craftables and non-craftables are 2 different gridstacks + //as such we need to ignore craftable stacks as they are not actual items + if (gridStack != null && gridStack.isCraftable()) { + ingredient.setCraftStackId(gridStack.getId()); + } else { + int needed = ingredient.getMissingAmount(); + int used = Math.min(available, needed); + ingredient.fulfill(used); + available -= used; + } + } + } + } + + public boolean hasMissing() { + return ingredients.stream().anyMatch(ingredient -> !ingredient.isAvailable()); + } + + public Map getCraftingRequests() { + Map toRequest = new HashMap<>(); + + for (Ingredient ingredient : ingredients) { + if (!ingredient.isAvailable() && ingredient.isCraftable()) { + toRequest.merge(ingredient.getCraftStackId(), ingredient.getMissingAmount(), Integer::sum); + } + } + + return toRequest; + } + +} diff --git a/src/main/java/com/refinedmods/refinedstorage/integration/jei/RecipeTransferGridError.java b/src/main/java/com/refinedmods/refinedstorage/integration/jei/RecipeTransferGridError.java new file mode 100644 index 000000000..d63110c26 --- /dev/null +++ b/src/main/java/com/refinedmods/refinedstorage/integration/jei/RecipeTransferGridError.java @@ -0,0 +1,67 @@ +package com.refinedmods.refinedstorage.integration.jei; + +import com.mojang.blaze3d.matrix.MatrixStack; +import com.refinedmods.refinedstorage.util.TemporaryPortingUtils; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.TranslationTextComponent; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class RecipeTransferGridError implements IRecipeTransferError { + private static final Color HIGHLIGHT_COLOR = new Color(1.0f, 0.0f, 0.0f, 0.4f); + private static final Color HIGHLIGHT_AUTOCRAFT_COLOR = new Color(0.0f, 0.0f, 1.0f, 0.4f); + IngredientTracker tracker; + + public RecipeTransferGridError(IngredientTracker tracker) { + this.tracker = tracker; + } + + @Override + public Type getType() { + return Type.COSMETIC; + } + + @Override + public void showError(MatrixStack stack, int mouseX, int mouseY, IRecipeLayout recipeLayout, int recipeX, int recipeY) { + List message = drawIngredientHighlights(stack, tracker, recipeX, recipeY); + Screen currentScreen = Minecraft.getInstance().currentScreen; + TemporaryPortingUtils.drawHoveringText(ItemStack.EMPTY, stack, message, mouseX, mouseY, currentScreen.width, currentScreen.height, 150, Minecraft.getInstance().fontRenderer); + } + + private List drawIngredientHighlights(MatrixStack stack, IngredientTracker tracker, int recipeX, int recipeY) { + List message = new ArrayList<>(); + message.add(new TranslationTextComponent("jei.tooltip.transfer")); + boolean craftMessage = false; + boolean missingMessage = false; + + for (Ingredient ingredient : tracker.getIngredients()) { + if (!ingredient.isAvailable()) { + if (ingredient.isCraftable()) { + ingredient.getGuiIngredient().drawHighlight(stack, HIGHLIGHT_AUTOCRAFT_COLOR.getRGB(), recipeX, recipeY); + craftMessage = true; + } else { + ingredient.getGuiIngredient().drawHighlight(stack, HIGHLIGHT_COLOR.getRGB(), recipeX, recipeY); + missingMessage = true; + } + } + } + + if (missingMessage) { + message.add(new TranslationTextComponent("jei.tooltip.error.recipe.transfer.missing").mergeStyle(TextFormatting.RED)); + } + + if (craftMessage) { + message.add(new TranslationTextComponent("gui.refinedstorage.jei.tooltip.error.recipe.transfer.missing.autocraft").mergeStyle(TextFormatting.BLUE)); + } + + return message; + } +} diff --git a/src/main/java/com/refinedmods/refinedstorage/screen/grid/view/BaseGridView.java b/src/main/java/com/refinedmods/refinedstorage/screen/grid/view/BaseGridView.java index 96a66b12d..0599f8e47 100644 --- a/src/main/java/com/refinedmods/refinedstorage/screen/grid/view/BaseGridView.java +++ b/src/main/java/com/refinedmods/refinedstorage/screen/grid/view/BaseGridView.java @@ -32,6 +32,11 @@ public abstract class BaseGridView implements IGridView { return stacks; } + @Override + public Collection getAllStacks() { + return map.values(); + } + @Nullable @Override public IGridStack get(UUID id) { diff --git a/src/main/java/com/refinedmods/refinedstorage/screen/grid/view/IGridView.java b/src/main/java/com/refinedmods/refinedstorage/screen/grid/view/IGridView.java index 6814b4c0c..535843397 100644 --- a/src/main/java/com/refinedmods/refinedstorage/screen/grid/view/IGridView.java +++ b/src/main/java/com/refinedmods/refinedstorage/screen/grid/view/IGridView.java @@ -3,6 +3,7 @@ package com.refinedmods.refinedstorage.screen.grid.view; import com.refinedmods.refinedstorage.screen.grid.stack.IGridStack; import javax.annotation.Nullable; +import java.util.Collection; import java.util.List; import java.util.UUID; @@ -12,6 +13,8 @@ public interface IGridView { @Nullable IGridStack get(UUID id); + Collection getAllStacks(); + void setStacks(List stacks); void postChange(IGridStack stack, int delta); diff --git a/src/main/resources/assets/refinedstorage/lang/en_us.json b/src/main/resources/assets/refinedstorage/lang/en_us.json index fbe986de5..a15b796bc 100644 --- a/src/main/resources/assets/refinedstorage/lang/en_us.json +++ b/src/main/resources/assets/refinedstorage/lang/en_us.json @@ -88,17 +88,15 @@ "gui.refinedstorage.crafter_manager": "Crafter Manager", "gui.refinedstorage.alternatives": "Alternatives", "gui.refinedstorage.alternatives.apply": "Apply", - + "gui.refinedstorage.jei.tooltip.error.recipe.transfer.missing.autocraft": "CTRL + CLICK to request autocrafting", "misc.refinedstorage.energy_stored": "%d / %d FE", "misc.refinedstorage.energy_usage": "Usage: %d FE/t", "misc.refinedstorage.energy_usage_minimal": "%d FE/t", - "misc.refinedstorage.storage.stored": "Stored: %s", "misc.refinedstorage.storage.stored_capacity": "Stored: %s / %s", "misc.refinedstorage.storage.stored_minimal": "%s", "misc.refinedstorage.storage.stored_capacity_minimal": "%s / %s", "misc.refinedstorage.storage.full": "%d%% full", - "misc.refinedstorage.network_item.tooltip": "Linked to %d, %d, %d.", "misc.refinedstorage.network_item.out_of_range": "There is no Wireless Transmitter in range.", "misc.refinedstorage.network_item.not_found": "Network not found.",