Show missing indicators in JEI (#2582)

* port #2330 to 1.15

* remove unecessary reflection

* rewrite JEI show missing and craftable items so that:
- clicking transfer button still works
- it is also shown for processing recipes
- also for fluids

* add missing method

* send Fluids with the ItemGridUpdateMessage

* port #2330 to 1.15

* remove unecessary reflection

* rewrite JEI show missing and craftable items so that:
- clicking transfer button still works
- it is also shown for processing recipes
- also for fluids

* add missing method

* send Fluids with the ItemGridUpdateMessage

* fix accidental lang duplication

* move lang key to appropriate location

* remove accidental changes to lang file

* port to 1.16 and cleanup

* remove fluids

* update translation to newer method.

* add being able to request autocrafting.

* remove unused variable

* formatting

* fix requested changes

Co-authored-by: Michael <mcpower@users.noreply.github.com>
This commit is contained in:
Darkere
2020-09-25 20:32:20 +02:00
committed by GitHub
parent 1b4ebd3d20
commit 2c4ce13551
8 changed files with 297 additions and 46 deletions

View File

@@ -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;
}

View File

@@ -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,12 +38,69 @@ public class GridRecipeTransferHandler implements IRecipeTransferHandler<GridCon
@Override
public IRecipeTransferError transferRecipe(@Nonnull GridContainer container, Object recipe, @Nonnull IRecipeLayout recipeLayout, @Nonnull PlayerEntity player, boolean maxTransfer, boolean doTransfer) {
IGrid grid = container.getGrid();
IngredientTracker tracker = trackItems(container, recipeLayout, player);
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 {
return new RecipeTransferGridError(tracker);
}
}
return null;
}
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<IGridStack> 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();
if (doTransfer) {
LAST_TRANSFER_TIME = System.currentTimeMillis();
if (grid.getGridType() == GridType.PATTERN && !isCraftingRecipe(recipeLayout.getRecipeCategory())) {
if (grid.getGridType() == GridType.PATTERN && !recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) {
List<ItemStack> inputs = new LinkedList<>();
List<ItemStack> outputs = new LinkedList<>();
@@ -73,15 +135,10 @@ public class GridRecipeTransferHandler implements IRecipeTransferHandler<GridCon
} else {
RS.NETWORK_HANDLER.sendToServer(new GridTransferMessage(
recipeLayout.getItemStacks().getGuiIngredients(),
container.inventorySlots.stream().filter(s -> s.inventory instanceof CraftingInventory).collect(Collectors.toList())
gridContainer.inventorySlots.stream().filter(s -> s.inventory instanceof CraftingInventory).collect(Collectors.toList())
));
}
}
return null;
}
private boolean isCraftingRecipe(IRecipeCategory<?> recipeCategory) {
return recipeCategory.getUid().equals(VanillaRecipeCategoryUid.CRAFTING);
}
}

View File

@@ -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<ItemStack> guiIngredient;
private UUID craftStackId;
private int required;
private int fulfilled;
public Ingredient(IGuiIngredient<ItemStack> 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<ItemStack> getGuiIngredient() {
return guiIngredient;
}
public UUID getCraftStackId() {
return craftStackId;
}
public void setCraftStackId(UUID craftStackId) {
this.craftStackId = craftStackId;
}
public void fulfill(int amount) {
fulfilled += amount;
}
}

View File

@@ -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<Ingredient> ingredients = new ArrayList<>();
public IngredientTracker(IRecipeLayout recipeLayout) {
for (IGuiIngredient<ItemStack> guiIngredient : recipeLayout.getItemStacks().getGuiIngredients().values()) {
if (guiIngredient.isInput() && !guiIngredient.getAllIngredients().isEmpty()) {
ingredients.add(new Ingredient(guiIngredient));
}
}
}
public Collection<Ingredient> 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<UUID, Integer> getCraftingRequests() {
Map<UUID, Integer> toRequest = new HashMap<>();
for (Ingredient ingredient : ingredients) {
if (!ingredient.isAvailable() && ingredient.isCraftable()) {
toRequest.merge(ingredient.getCraftStackId(), ingredient.getMissingAmount(), Integer::sum);
}
}
return toRequest;
}
}

View File

@@ -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<ITextComponent> 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<ITextComponent> drawIngredientHighlights(MatrixStack stack, IngredientTracker tracker, int recipeX, int recipeY) {
List<ITextComponent> 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;
}
}

View File

@@ -32,6 +32,11 @@ public abstract class BaseGridView implements IGridView {
return stacks;
}
@Override
public Collection<IGridStack> getAllStacks() {
return map.values();
}
@Nullable
@Override
public IGridStack get(UUID id) {

View File

@@ -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<IGridStack> getAllStacks();
void setStacks(List<IGridStack> stacks);
void postChange(IGridStack stack, int delta);

View File

@@ -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.",