From d4680d715f4bf515d092851e33313a43ab53f251 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Wed, 16 Oct 2024 17:33:19 +0200 Subject: [PATCH] Trigger crafting jobs from JEI by Ctrl-clicking recipes Closes CyclopsMC/IntegratedTerminals#127 --- gradle.properties | 6 +- .../jei/RecipeTransferErrorColored.java | 78 +++++++++++++++++++ .../TerminalStorageRecipeTransferHandler.java | 29 +++++-- ...redientItemStackCraftingGridSetRecipe.java | 41 +++++++++- .../integratedterminalscompat/lang/en_us.json | 6 +- 5 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeTransferErrorColored.java diff --git a/gradle.properties b/gradle.properties index 8006efe..19951ef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,12 @@ mod_version=1.0.0 minecraft_version=1.19.2 forge_version=43.0.8 -cyclopscore_version=1.18.0-294 +cyclopscore_version=1.19.4-495 release_type=release fingerprint=bd0353b3e8a2810d60dd584e256e364bc3bedd44 -integrateddynamics_version=1.19.2-1.15.2-518 -integratedterminals_version=1.19.2-1.4.6-270 +integrateddynamics_version=1.19.2-1.22.0-726 +integratedterminals_version=1.19.2-1.5.1-385 commoncapabilities_version=1.19.2-2.9.0-88 curios_version=1.19.2-5.1.1.0 jei_version=1.19.2-forge:11.5.2.1007 diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeTransferErrorColored.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeTransferErrorColored.java new file mode 100644 index 0000000..d98f436 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeTransferErrorColored.java @@ -0,0 +1,78 @@ +package org.cyclops.integratedterminalscompat.modcompat.jei; + +import com.mojang.blaze3d.vertex.PoseStack; +import mezz.jei.api.gui.ingredient.IRecipeSlotView; +import mezz.jei.api.gui.ingredient.IRecipeSlotsView; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; +import org.cyclops.cyclopscore.helper.Helpers; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * A transfer error object that changes appearance based on presence of storage and craftable items. + * @author rubensworks + */ +public class RecipeTransferErrorColored implements IRecipeTransferError { + + public static final int SLOT_COLOR_MISSING = Helpers.RGBAToInt(255, 0, 0, 100); + public static final int SLOT_COLOR_CRAFTABLE = Helpers.RGBAToInt(0, 0, 255, 100); + + public static final int HIGHLIGHT_COLOR_FAIL = Helpers.RGBAToInt(255, 0, 0, 100); + public static final int HIGHLIGHT_COLOR_CRAFTABLE = Helpers.RGBAToInt(0, 0, 255, 100); + public static final int HIGHLIGHT_COLOR_CRAFTABLE_PARTIAL = Helpers.RGBAToInt(255, 125, 0, 100); + + private final List message = new ArrayList<>(); + private final Collection slotsMissing; + private final Collection slotsCraftable; + private final int color; + + public RecipeTransferErrorColored(Collection slotsMissing, Collection slotsCraftable) { + this.message.add(Component.translatable("jei.tooltip.transfer")); + this.slotsMissing = slotsMissing; + this.slotsCraftable = slotsCraftable; + if (slotsMissing.isEmpty()) { + // Missing items, but they are all craftable + this.message.add(Component.translatable("gui.integratedterminalscompat.terminal_storage.jei.transfer.craftable").withStyle(ChatFormatting.RED)); + this.message.add(Component.translatable("gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info").withStyle(ChatFormatting.ITALIC)); + this.color = HIGHLIGHT_COLOR_CRAFTABLE; + } else if (!slotsCraftable.isEmpty()) { + // Missing items, but only some of them are craftable + this.message.add(Component.translatable("gui.integratedterminalscompat.terminal_storage.jei.transfer.craftable_partial").withStyle(ChatFormatting.RED)); + this.message.add(Component.translatable("gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info").withStyle(ChatFormatting.ITALIC)); + this.color = HIGHLIGHT_COLOR_CRAFTABLE_PARTIAL; + } else { + // Missing items, and none are craftable + this.message.add(Component.translatable("gui.integratedterminalscompat.terminal_storage.jei.transfer.missing").withStyle(ChatFormatting.ITALIC)); + this.color = HIGHLIGHT_COLOR_FAIL; + } + } + + @Override + public Type getType() { + return this.slotsCraftable.isEmpty() ? Type.USER_FACING : Type.COSMETIC; + } + + @Override + public int getButtonHighlightColor() { + return this.color; + } + + @Override + public void showError(PoseStack poseStack, int mouseX, int mouseY, IRecipeSlotsView recipeSlotsView, int recipeX, int recipeY) { + Minecraft.getInstance().screen.renderComponentTooltip(poseStack, this.message, mouseX, mouseY); + poseStack.pushPose(); + poseStack.translate(recipeX, recipeY, 0.0); + for (IRecipeSlotView slot : this.slotsMissing) { + slot.drawHighlight(poseStack, SLOT_COLOR_MISSING); + } + for (IRecipeSlotView slot : this.slotsCraftable) { + slot.drawHighlight(poseStack, SLOT_COLOR_CRAFTABLE); + } + poseStack.popPose(); + } +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/terminalstorage/TerminalStorageRecipeTransferHandler.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/terminalstorage/TerminalStorageRecipeTransferHandler.java index 74e0807..a79ca4b 100644 --- a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/terminalstorage/TerminalStorageRecipeTransferHandler.java +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/terminalstorage/TerminalStorageRecipeTransferHandler.java @@ -15,7 +15,7 @@ import mezz.jei.api.recipe.transfer.IRecipeTransferError; import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper; -import net.minecraft.network.chat.Component; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.Slot; @@ -35,6 +35,7 @@ import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageBase; import org.cyclops.integratedterminalscompat.IntegratedTerminalsCompat; import org.cyclops.integratedterminalscompat.modcompat.jei.JEIIntegratedTerminalsConfig; +import org.cyclops.integratedterminalscompat.modcompat.jei.RecipeTransferErrorColored; import org.cyclops.integratedterminalscompat.network.packet.TerminalStorageIngredientItemStackCraftingGridSetRecipe; import javax.annotation.Nullable; @@ -163,7 +164,7 @@ public IRecipeTransferError transferRecipe(T container, CraftingRecipe recipe, I IntegratedTerminalsCompat._instance.getPacketHandler().sendToServer( new TerminalStorageIngredientItemStackCraftingGridSetRecipe(container.getSelectedTab(), - container.getSelectedChannel(), maxTransfer, slottedIngredientsFromPlayer, slottedIngredientsFromStorage)); + container.getSelectedChannel(), maxTransfer, slottedIngredientsFromPlayer, slottedIngredientsFromStorage, AbstractContainerScreen.hasControlDown())); return null; } } @@ -191,18 +192,26 @@ private Optional getMissingItems(T container, CraftingReci List> unfilteredIngredients = tabClient .getUnfilteredIngredientsView(container.getSelectedChannel()); IIngredientCollectionMutable hayStack = IngredientCollectionHelpers.createCollapsedCollection(IngredientComponent.ITEMSTACK); + IIngredientCollectionMutable hayStackCraftable = IngredientCollectionHelpers.createCollapsedCollection(IngredientComponent.ITEMSTACK); hayStack.addAll(unfilteredIngredients .stream() .filter(i -> i.getCraftingOption() == null) .map(TerminalStorageTabIngredientComponentClient.InstanceWithMetadata::getInstance) .collect(Collectors.toList())); + hayStackCraftable.addAll(unfilteredIngredients + .stream() + .filter(i -> i.getCraftingOption() != null) + .map(TerminalStorageTabIngredientComponentClient.InstanceWithMetadata::getInstance) + .collect(Collectors.toList())); List slotsMissingItems = Lists.newArrayList(); + List slotsMissingCraftableItems = Lists.newArrayList(); for (IRecipeSlotView slotView : recipeLayout.getSlotViews()) { if (!slotView.isEmpty() && slotView.getRole() == RecipeIngredientRole.INPUT) { ITypedIngredient typedIngredient = slotView.getAllIngredients().findFirst().get(); if (typedIngredient.getType() == VanillaTypes.ITEM_STACK) { boolean found = false; + boolean craftable = false; for (ItemStack itemStack : ((Stream>) (Stream) slotView.getAllIngredients()) .map(ITypedIngredient::getIngredient) .collect(Collectors.toSet())) { @@ -228,18 +237,26 @@ private Optional getMissingItems(T container, CraftingReci found = true; break; } + + // Also check if this item could be craftable + if (hayStackCraftable.contains(itemStack, matchCondition)) { + craftable = true; + } } if (!found) { - slotsMissingItems.add(slotView); + if (craftable) { + slotsMissingCraftableItems.add(slotView); + } else { + slotsMissingItems.add(slotView); + } } } } } previousChangeId = tabClient.getLastChangeId(); - if (!slotsMissingItems.isEmpty()) { - Component message = Component.translatable("jei.tooltip.error.recipe.transfer.missing"); - return Optional.of(recipeTransferHandlerHelper.createUserErrorForMissingSlots(message, slotsMissingItems)); + if (!slotsMissingItems.isEmpty() || !slotsMissingCraftableItems.isEmpty()) { + return Optional.of(new RecipeTransferErrorColored(slotsMissingItems, slotsMissingCraftableItems)); } return Optional.empty(); diff --git a/src/main/java/org/cyclops/integratedterminalscompat/network/packet/TerminalStorageIngredientItemStackCraftingGridSetRecipe.java b/src/main/java/org/cyclops/integratedterminalscompat/network/packet/TerminalStorageIngredientItemStackCraftingGridSetRecipe.java index bfa395b..53258dd 100644 --- a/src/main/java/org/cyclops/integratedterminalscompat/network/packet/TerminalStorageIngredientItemStackCraftingGridSetRecipe.java +++ b/src/main/java/org/cyclops/integratedterminalscompat/network/packet/TerminalStorageIngredientItemStackCraftingGridSetRecipe.java @@ -2,11 +2,11 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import net.minecraft.world.entity.player.Player; +import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; -import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.level.Level; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -18,11 +18,17 @@ import org.cyclops.cyclopscore.network.CodecField; import org.cyclops.cyclopscore.network.PacketCodec; import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabCommon; +import org.cyclops.integratedterminals.api.terminalstorage.crafting.CraftingJobStartException; +import org.cyclops.integratedterminals.api.terminalstorage.crafting.ITerminalCraftingOption; +import org.cyclops.integratedterminals.api.terminalstorage.crafting.ITerminalCraftingPlan; +import org.cyclops.integratedterminals.api.terminalstorage.crafting.ITerminalStorageTabIngredientCraftingHandler; import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentItemStackCraftingCommon; import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentServer; +import org.cyclops.integratedterminals.core.terminalstorage.crafting.TerminalStorageTabIngredientCraftingHandlers; import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageBase; import org.cyclops.integratedterminals.network.packet.TerminalStorageIngredientItemStackCraftingGridClear; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -41,6 +47,8 @@ public class TerminalStorageIngredientItemStackCraftingGridSetRecipe extends Pac private boolean maxTransfer; private Map> slottedIngredientsFromPlayer; private Map>> slottedIngredientsFromStorage; + @CodecField + private boolean triggerCraftingJobs; public TerminalStorageIngredientItemStackCraftingGridSetRecipe() { @@ -48,12 +56,14 @@ public TerminalStorageIngredientItemStackCraftingGridSetRecipe() { public TerminalStorageIngredientItemStackCraftingGridSetRecipe(String tabId, int channel, boolean maxTransfer, Map> slottedIngredientsFromPlayer, - Map>> slottedIngredientsFromStorage) { + Map>> slottedIngredientsFromStorage, + boolean triggerCraftingJobs) { this.tabId = tabId; this.channel = channel; this.maxTransfer = maxTransfer; this.slottedIngredientsFromPlayer = slottedIngredientsFromPlayer; this.slottedIngredientsFromStorage = slottedIngredientsFromStorage; + this.triggerCraftingJobs = triggerCraftingJobs; } @Override @@ -160,6 +170,31 @@ public void actionServer(Level world, ServerPlayer player) { } if (!extracted.isEmpty()) { slot.set(extracted); + } else if (this.triggerCraftingJobs) { + // Trigger crafting job if enabled + boolean startedJob = false; + for (ITerminalStorageTabIngredientCraftingHandler handler : TerminalStorageTabIngredientCraftingHandlers.REGISTRY.getHandlers()) { + for (Pair stackEntry : entry.getValue()) { + for (ITerminalCraftingOption craftingOption : (Collection>) handler.getCraftingOptionsWithOutput(tabServerCrafting, channel, stackEntry.getLeft(), stackEntry.getRight())) { + ITerminalCraftingPlan craftingPlan = handler.calculateCraftingPlan(tabServerCrafting.getNetwork(), channel, craftingOption, 1); + if (craftingPlan.getStatus().isValid()) { + try { + handler.startCraftingJob(tabServerCrafting.getNetwork(), channel, craftingPlan, player); + startedJob = true; + break; + } catch (CraftingJobStartException e) { + // Ignore jobs that could not start + } + } + } + if (startedJob) { + break; + } + } + if (startedJob) { + break; + } + } } } } diff --git a/src/main/resources/assets/integratedterminalscompat/lang/en_us.json b/src/main/resources/assets/integratedterminalscompat/lang/en_us.json index 0316784..9e00f73 100644 --- a/src/main/resources/assets/integratedterminalscompat/lang/en_us.json +++ b/src/main/resources/assets/integratedterminalscompat/lang/en_us.json @@ -1,5 +1,9 @@ { "_comment": "Gui", "gui.integratedterminalscompat.terminal_storage.craftinggrid.jeisync": "JEI Search Sync", - "gui.integratedterminalscompat.terminal_storage.craftinggrid.jeisync.info": "Synchronize search box with JEI." + "gui.integratedterminalscompat.terminal_storage.craftinggrid.jeisync.info": "Synchronize search box with JEI.", + "gui.integratedterminalscompat.terminal_storage.jei.transfer.craftable": "Auto-craftable items", + "gui.integratedterminalscompat.terminal_storage.jei.transfer.craftable_partial": "Partially auto-craftable items", + "gui.integratedterminalscompat.terminal_storage.jei.transfer.missing": "Missing items", + "gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info": "Ctrl+click to trigger auto-crafting" } \ No newline at end of file