From d4680d715f4bf515d092851e33313a43ab53f251 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Wed, 16 Oct 2024 17:33:19 +0200 Subject: [PATCH 1/4] 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 From f28ca5da43d02864f77a7b9b50fa821217abcbc1 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Mon, 21 Oct 2024 17:54:54 +0200 Subject: [PATCH 2/4] Add dedicated support for EMI Closes CyclopsMC/IntegratedTerminals#120 Closes CyclopsMC/IntegratedDynamics#1384 --- build.gradle | 16 ++ gradle.properties | 1 + .../modcompat/common/RecipeInputSlot.java | 12 + .../common/RecipeTransferHelpers.java | 212 +++++++++++++++++ .../RecipeTransferResult.java} | 54 ++--- ...uttonItemStackCraftingGridSearchSync.java} | 27 ++- .../emi/EmiIntegratedTerminalsConfig.java | 109 +++++++++ .../RecipeInputSlotEmiIngredient.java | 41 ++++ .../RecipeInputSlotEmiSlotWidget.java | 43 ++++ .../TerminalStorageEmiRecipeHandler.java | 167 +++++++++++++ .../TerminalStorageEmiStackProvider.java | 43 ++++ .../jei/JEIIntegratedTerminalsConfig.java | 33 +-- .../modcompat/jei/RecipeInputSlotJei.java | 44 ++++ .../RecipeTransferErrorTransferResult.java | 46 ++++ .../TerminalStorageRecipeTransferHandler.java | 219 +++--------------- .../integratedterminalscompat/lang/en_us.json | 5 +- 16 files changed, 823 insertions(+), 249 deletions(-) create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeInputSlot.java create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeTransferHelpers.java rename src/main/java/org/cyclops/integratedterminalscompat/modcompat/{jei/RecipeTransferErrorColored.java => common/RecipeTransferResult.java} (53%) rename src/main/java/org/cyclops/integratedterminalscompat/modcompat/{jei/terminalstorage/button/TerminalButtonItemStackCraftingGridJeiSearchSync.java => common/button/TerminalButtonItemStackCraftingGridSearchSync.java} (76%) create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/EmiIntegratedTerminalsConfig.java create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/RecipeInputSlotEmiIngredient.java create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/RecipeInputSlotEmiSlotWidget.java create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/TerminalStorageEmiRecipeHandler.java create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/TerminalStorageEmiStackProvider.java create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeInputSlotJei.java create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeTransferErrorTransferResult.java diff --git a/build.gradle b/build.gradle index d57e357..478b45a 100644 --- a/build.gradle +++ b/build.gradle @@ -89,6 +89,10 @@ repositories { name "Curios" url "https://maven.theillusivec4.top/" } + maven { + name "EMI" + url "https://maven.terraformersmc.com/" + } } dependencies { @@ -139,6 +143,8 @@ dependencies { runtimeOnly fg.deobf("top.theillusivec4.curios:curios-forge:${project.curios_version}") // https://maven.theillusivec4.top/top/theillusivec4/curios/curios-forge/ compileOnly fg.deobf("top.theillusivec4.curios:curios-forge:${project.curios_version}:api") + + implementation fg.deobf("dev.emi:emi-forge:${emi_version}") } minecraft { @@ -149,6 +155,11 @@ minecraft { workingDirectory project.file('run') //property 'forge.logging.markers', 'REGISTRIES,REGISTRYDUMP' property 'forge.logging.console.level', 'debug' + + // Required for EMI + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" + mods { integratedterminalscompat { source sourceSets.main @@ -163,6 +174,11 @@ minecraft { server { workingDirectory project.file('run') property 'forge.logging.console.level', 'debug' + + // Required for EMI + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" + mods { integratedterminalscompat { source sourceSets.main diff --git a/gradle.properties b/gradle.properties index 19951ef..9d3e8e8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,7 @@ 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 +emi_version=1.1.16+1.19.2 # Workaround for Spotless bug # https://github.com/diffplug/spotless/issues/834 diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeInputSlot.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeInputSlot.java new file mode 100644 index 0000000..dd225c6 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeInputSlot.java @@ -0,0 +1,12 @@ +package org.cyclops.integratedterminalscompat.modcompat.common; + +import net.minecraft.world.item.ItemStack; + +/** + * @author rubensworks + */ +public interface RecipeInputSlot extends Iterable { + + public boolean isEmpty(); + +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeTransferHelpers.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeTransferHelpers.java new file mode 100644 index 0000000..e910a39 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeTransferHelpers.java @@ -0,0 +1,212 @@ +package org.cyclops.integratedterminalscompat.modcompat.common; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Streams; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.items.wrapper.InvWrapper; +import org.apache.commons.lang3.tuple.Pair; +import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent; +import org.cyclops.commoncapabilities.ingredient.storage.IngredientComponentStorageWrapperHandlerItemStack; +import org.cyclops.cyclopscore.ingredient.collection.IIngredientCollectionMutable; +import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionHelpers; +import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionPrototypeMap; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabCommon; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentClient; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentItemStackCrafting; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentItemStackCraftingCommon; +import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageBase; +import org.cyclops.integratedterminalscompat.IntegratedTerminalsCompat; +import org.cyclops.integratedterminalscompat.network.packet.TerminalStorageIngredientItemStackCraftingGridSetRecipe; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * Common helpers for JEI/EMI/REI + * @author rubensworks + */ +public class RecipeTransferHelpers { + + // The amount of seconds recipeErrors will be cached for transferRecipe + private static final long RECIPE_ERROR_CACHE_TIME = 60; + private static final Cache>> recipeErrorCache = CacheBuilder.newBuilder() + .expireAfterAccess(RECIPE_ERROR_CACHE_TIME, TimeUnit.SECONDS) + .build(); + + public static Optional>> getTabs(ContainerTerminalStorageBase container) { + if (Objects.equals(container.getSelectedTab(), TerminalStorageTabIngredientComponentItemStackCrafting.NAME.toString())) { + ITerminalStorageTabCommon tabCommon = container.getTabCommon(container.getSelectedTab()); + TerminalStorageTabIngredientComponentItemStackCraftingCommon tabCommonCrafting = + (TerminalStorageTabIngredientComponentItemStackCraftingCommon) tabCommon; + TerminalStorageTabIngredientComponentClient tabClient = (TerminalStorageTabIngredientComponentClient) + container.getTabClient(container.getSelectedTab()); + return Optional.of(Pair.of(tabCommonCrafting, tabClient)); + } + return Optional.empty(); + } + + public static Optional> getMissingItems(Object cacheKey, ContainerTerminalStorageBase container, Iterable recipeInputSlots, Player player, TerminalStorageTabIngredientComponentItemStackCraftingCommon tabCommonCrafting, TerminalStorageTabIngredientComponentClient tabClient, Function itemStackToMatchCondition, Supplier getId, Consumer onChangeId) { + Callable>> missingItemsSupplier = + () -> getMissingItemsUncached(container, recipeInputSlots, player, tabCommonCrafting, itemStackToMatchCondition, onChangeId); + if (getId.get() != tabClient.getLastChangeId()) { + // Clear cache when storage contents changed + recipeErrorCache.invalidateAll(); + } + try { + return recipeErrorCache.get(cacheKey, (Callable) missingItemsSupplier); + } catch (ExecutionException e) { + // Throw exceptions from missingItemsSupplier + throw new RuntimeException(e); + } + } + + public static Optional> getMissingItemsUncached(ContainerTerminalStorageBase container, Iterable recipeInputSlots, Player player, TerminalStorageTabIngredientComponentItemStackCraftingCommon tabCommonCrafting, Function itemStackToMatchCondition, Consumer onChangeId) { + TerminalStorageTabIngredientComponentClient tabClient = (TerminalStorageTabIngredientComponentClient) + container.getTabClient(container.getSelectedTab()); + + // Check in the player inventory and local client view if the required recipe ingredients are available + + // Build crafting grid index + IIngredientCollectionMutable hayStackCraftingGrid = new IngredientCollectionPrototypeMap<>(IngredientComponent.ITEMSTACK); + for (int slot = 0; slot < tabCommonCrafting.getInventoryCrafting().getContainerSize(); slot++) { + hayStackCraftingGrid.add(tabCommonCrafting.getInventoryCrafting().getItem(slot)); + } + + // Build player inventory index + IIngredientCollectionMutable hayStackPlayer = new IngredientCollectionPrototypeMap<>(IngredientComponent.ITEMSTACK); + hayStackPlayer.addAll(player.getInventory().items); + + // Build local client view of storage + 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 (T slot : recipeInputSlots) { + if (!slot.isEmpty()) { + boolean found = false; + boolean craftable = false; + for (ItemStack itemStack : slot) { + int matchCondition = itemStackToMatchCondition.apply(itemStack); + + // First check in the crafting grid + if (hayStackCraftingGrid.contains(itemStack, matchCondition)) { + hayStackPlayer.remove(itemStack); + found = true; + break; + } + + // Then check in player inventory + if (hayStackPlayer.contains(itemStack, matchCondition)) { + hayStackPlayer.remove(itemStack); + found = true; + break; + } + + // Then check the storage + if (hayStack.contains(itemStack, matchCondition)) { + hayStack.remove(itemStack); + found = true; + break; + } + + // Also check if this item could be craftable + if (hayStackCraftable.contains(itemStack, matchCondition)) { + craftable = true; + } + } + + if (!found) { + if (craftable) { + slotsMissingCraftableItems.add(slot); + } else { + slotsMissingItems.add(slot); + } + } + } + } + + onChangeId.accept(tabClient.getLastChangeId()); + if (!slotsMissingItems.isEmpty() || !slotsMissingCraftableItems.isEmpty()) { + return Optional.of(new RecipeTransferResult(slotsMissingItems, slotsMissingCraftableItems)); + } + + return Optional.empty(); + } + + public static void transferRecipe(ContainerTerminalStorageBase container, Iterable recipeInputSlots, Player player, TerminalStorageTabIngredientComponentItemStackCraftingCommon tabCommonCrafting, Function itemStackToMatchCondition, boolean maxTransfer) { + IngredientComponentStorageWrapperHandlerItemStack.ComponentStorageWrapper playerInventory = + new IngredientComponentStorageWrapperHandlerItemStack.ComponentStorageWrapper(IngredientComponent.ITEMSTACK, new InvWrapper(player.getInventory())); + + // Send a packet to the server if the recipe effectively needs to be applied to the grid + Map> slottedIngredientsFromPlayer = Maps.newHashMap(); + Map>> slottedIngredientsFromStorage = Maps.newHashMap(); + int slotOffset = tabCommonCrafting.getSlotCrafting().index; + int slotId = 0; + for (T recipeSlot : recipeInputSlots) { + if (!recipeSlot.isEmpty()) { + boolean found = false; + + // First check if we can transfer from the player inventory + // No need to check the crafting grid, as the server will first clear the grid into the storage in TerminalStorageIngredientItemStackCraftingGridSetRecipe + for (ItemStack itemStack : recipeSlot) { + int matchCondition = itemStackToMatchCondition.apply(itemStack); + + if (!playerInventory.extract(itemStack, matchCondition, true).isEmpty()) { + found = true; + + // Move from player to crafting grid + ItemStack extracted = playerInventory.extract(itemStack, matchCondition, false); + Slot slot = container.getSlot(slotId + slotOffset); + slot.set(extracted); + + // Do the exact same thing server-side + slottedIngredientsFromPlayer.put(slotId, Pair.of(itemStack, itemStackToMatchCondition.apply(itemStack))); + + break; + } + } + + if (!found) { + // Otherwise, request them from the storage + slottedIngredientsFromStorage.put(slotId, Streams.stream(recipeSlot) + .map(itemStack -> Pair.of(itemStack, itemStackToMatchCondition.apply(itemStack))) + .collect(Collectors.toList())); + } + } + slotId++; + } + + IntegratedTerminalsCompat._instance.getPacketHandler().sendToServer( + new TerminalStorageIngredientItemStackCraftingGridSetRecipe(container.getSelectedTab(), + container.getSelectedChannel(), maxTransfer, slottedIngredientsFromPlayer, slottedIngredientsFromStorage, AbstractContainerScreen.hasControlDown())); + } + +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeTransferErrorColored.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeTransferResult.java similarity index 53% rename from src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeTransferErrorColored.java rename to src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeTransferResult.java index d98f436..e93a406 100644 --- a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeTransferErrorColored.java +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeTransferResult.java @@ -1,11 +1,6 @@ -package org.cyclops.integratedterminalscompat.modcompat.jei; +package org.cyclops.integratedterminalscompat.modcompat.common; -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; @@ -14,10 +9,11 @@ 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 class RecipeTransferResult { + + public static final boolean HAS_CMD = System.getProperty("os.name").equals("Mac OS X"); 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); @@ -27,15 +23,16 @@ public class RecipeTransferErrorColored implements IRecipeTransferError { 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 Collection slotsMissing; + private final Collection slotsCraftable; private final int color; - public RecipeTransferErrorColored(Collection slotsMissing, Collection slotsCraftable) { - this.message.add(Component.translatable("jei.tooltip.transfer")); + public RecipeTransferResult(Collection slotsMissing, Collection slotsCraftable) { this.slotsMissing = slotsMissing; this.slotsCraftable = slotsCraftable; - if (slotsMissing.isEmpty()) { + if (slotsCraftable.isEmpty() && slotsMissing.isEmpty()) { + this.color = HIGHLIGHT_COLOR_CRAFTABLE; + } else 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)); @@ -43,7 +40,7 @@ public RecipeTransferErrorColored(Collection slotsMissing, Coll } 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.message.add(Component.translatable(HAS_CMD ? "gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info_cmd" : "gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info").withStyle(ChatFormatting.ITALIC)); this.color = HIGHLIGHT_COLOR_CRAFTABLE_PARTIAL; } else { // Missing items, and none are craftable @@ -52,27 +49,20 @@ public RecipeTransferErrorColored(Collection slotsMissing, Coll } } - @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(); + public List getMessage() { + return message; + } + + public Collection getSlotsCraftable() { + return slotsCraftable; } + + public Collection getSlotsMissing() { + return slotsMissing; + } + } diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/terminalstorage/button/TerminalButtonItemStackCraftingGridJeiSearchSync.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/button/TerminalButtonItemStackCraftingGridSearchSync.java similarity index 76% rename from src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/terminalstorage/button/TerminalButtonItemStackCraftingGridJeiSearchSync.java rename to src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/button/TerminalButtonItemStackCraftingGridSearchSync.java index f242604..9ceedbb 100644 --- a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/terminalstorage/button/TerminalButtonItemStackCraftingGridJeiSearchSync.java +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/button/TerminalButtonItemStackCraftingGridSearchSync.java @@ -1,4 +1,4 @@ -package org.cyclops.integratedterminalscompat.modcompat.jei.terminalstorage.button; +package org.cyclops.integratedterminalscompat.modcompat.common.button; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; @@ -19,22 +19,24 @@ import java.util.List; /** - * A button for toggling JEI search box sync. + * A button for toggling JEI/EMI search box sync. * @author rubensworks */ -public class TerminalButtonItemStackCraftingGridJeiSearchSync +public class TerminalButtonItemStackCraftingGridSearchSync implements ITerminalButton, TerminalStorageTabIngredientComponentCommon, ButtonImage> { + private final String mod; private final TerminalStorageState state; private final String buttonName; private final ITerminalStorageTabClient clientTab; private boolean active; - public TerminalButtonItemStackCraftingGridJeiSearchSync(TerminalStorageState state, ITerminalStorageTabClient clientTab) { + public TerminalButtonItemStackCraftingGridSearchSync(String mod, TerminalStorageState state, ITerminalStorageTabClient clientTab) { + this.mod = mod; this.state = state; - this.buttonName = "itemstack_grid_jeisearchsync"; + this.buttonName = "itemstack_grid_" + mod + "searchsync"; this.clientTab = clientTab; reloadFromState(); @@ -54,7 +56,7 @@ public void reloadFromState() { @OnlyIn(Dist.CLIENT) public ButtonImage createButton(int x, int y) { return new ButtonImage(x, y, - Component.translatable("gui.integratedterminalscompat.terminal_storage.craftinggrid.jeisync"), + Component.translatable("gui.integratedterminalscompat.terminal_storage.craftinggrid." + mod + "sync"), (b) -> {}, active ? Images.BUTTON_BACKGROUND_ACTIVE : Images.BUTTON_BACKGROUND_INACTIVE, Images.BUTTON_MIDDLE_JEI_SYNC); @@ -71,13 +73,13 @@ public void onClick(TerminalStorageTabIngredientComponentClient clientTab, @Override public String getTranslationKey() { - return "gui.integratedterminalscompat.terminal_storage.craftinggrid.jeisync"; + return "gui.integratedterminalscompat.terminal_storage.craftinggrid." + mod + "sync"; } @Override @OnlyIn(Dist.CLIENT) public void getTooltip(Player player, TooltipFlag tooltipFlag, List lines) { - lines.add(Component.translatable("gui.integratedterminalscompat.terminal_storage.craftinggrid.jeisync.info").withStyle(ChatFormatting.GRAY)); + lines.add(Component.translatable("gui.integratedterminalscompat.terminal_storage.craftinggrid." + mod + "sync.info").withStyle(ChatFormatting.GRAY)); lines.add(Component.translatable( active ? "general.cyclopscore.info.enabled" : "general.cyclopscore.info.disabled") .withStyle(ChatFormatting.ITALIC)); @@ -86,4 +88,13 @@ public void getTooltip(Player player, TooltipFlag tooltipFlag, List l public boolean isActive() { return active; } + + public static boolean isSearchSynced(ITerminalStorageTabClient clientTab) { + for (ITerminalButton button : clientTab.getButtons()) { + if (button instanceof TerminalButtonItemStackCraftingGridSearchSync) { + return ((TerminalButtonItemStackCraftingGridSearchSync) button).isActive(); + } + } + return false; + } } diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/EmiIntegratedTerminalsConfig.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/EmiIntegratedTerminalsConfig.java new file mode 100644 index 0000000..2456316 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/EmiIntegratedTerminalsConfig.java @@ -0,0 +1,109 @@ +package org.cyclops.integratedterminalscompat.modcompat.emi; + +import dev.emi.emi.api.EmiEntrypoint; +import dev.emi.emi.api.EmiPlugin; +import dev.emi.emi.api.EmiRegistry; +import dev.emi.emi.api.stack.Comparison; +import dev.emi.emi.registry.EmiComparisonDefaults; +import dev.emi.emi.screen.EmiScreenManager; +import net.minecraft.client.Minecraft; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.client.event.ScreenEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import org.cyclops.commoncapabilities.api.capability.itemhandler.ItemMatch; +import org.cyclops.cyclopscore.client.gui.component.input.WidgetTextFieldExtended; +import org.cyclops.integratedterminals.RegistryEntries; +import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageScreenSizeEvent; +import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientLoadButtonsEvent; +import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientSearchFieldUpdateEvent; +import org.cyclops.integratedterminals.client.gui.container.ContainerScreenTerminalStorage; +import org.cyclops.integratedterminalscompat.modcompat.common.button.TerminalButtonItemStackCraftingGridSearchSync; +import org.cyclops.integratedterminalscompat.modcompat.emi.terminalstorage.TerminalStorageEmiRecipeHandler; +import org.cyclops.integratedterminalscompat.modcompat.emi.terminalstorage.TerminalStorageEmiStackProvider; + +/** + * @author rubensworks + */ +@EmiEntrypoint +public class EmiIntegratedTerminalsConfig implements EmiPlugin { + + private boolean loaded = false; + private boolean wasEmiVisible = false; + + public EmiIntegratedTerminalsConfig() { + MinecraftForge.EVENT_BUS.register(this); + } + + public static int getItemStackMatchCondition(ItemStack itemStack) { + Comparison comparison = EmiComparisonDefaults.get(itemStack.getItem()); + return comparison == Comparison.DEFAULT_COMPARISON ? ItemMatch.ITEM : ItemMatch.ITEM | ItemMatch.TAG; + } + + @Override + public void register(EmiRegistry emiRegistry) { + loaded = true; + + emiRegistry.addRecipeHandler(RegistryEntries.CONTAINER_PART_TERMINAL_STORAGE_PART, new TerminalStorageEmiRecipeHandler<>()); + emiRegistry.addRecipeHandler(RegistryEntries.CONTAINER_PART_TERMINAL_STORAGE_ITEM, new TerminalStorageEmiRecipeHandler<>()); + emiRegistry.addGenericStackProvider(new TerminalStorageEmiStackProvider()); + } + + @SubscribeEvent + public void onTerminalStorageButtons(TerminalStorageTabClientLoadButtonsEvent event) { + if (this.loaded && !event.getButtons().stream() + .anyMatch((button) -> button instanceof TerminalButtonItemStackCraftingGridSearchSync)) { + event.getButtons().add(new TerminalButtonItemStackCraftingGridSearchSync( + "emi", event.getContainer().getGuiState(), event.getClientTab())); + } + } + + @SubscribeEvent + public void onTerminalStorageScreenSize(TerminalStorageScreenSizeEvent event) { + if (this.loaded) { + try { + boolean isOpen = !EmiScreenManager.isDisabled(); + boolean wasJeiVisiblePrevious = wasEmiVisible; + if (isOpen) { + wasEmiVisible = true; + event.setHeight(event.getHeight() - 20); + event.setWidth(event.getWidth() - 170); + } else { + wasEmiVisible = false; + } + + // Re-init screen if JEI was just made (in)visible + if (wasJeiVisiblePrevious != wasEmiVisible) { + ((ContainerScreenTerminalStorage) Minecraft.getInstance().screen).init(); + } + } catch (NoClassDefFoundError | ClassCastException e) { + // Do nothing when we detect some JEI API issues + } + } + } + + @SubscribeEvent + public void onSearchFieldUpdated(TerminalStorageTabClientSearchFieldUpdateEvent event) { + // Copy the terminal search box contents into the JEI search box. + if (!EmiScreenManager.isDisabled() && TerminalButtonItemStackCraftingGridSearchSync.isSearchSynced(event.getClientTab())) { + EmiScreenManager.search.setValue(event.getSearchString() + ""); + } + } + + @SubscribeEvent + public void onKeyTyped(ScreenEvent.KeyReleased.Post event) { + // Copy the JEI search box contents into the terminal search box. + if (event.getScreen() instanceof ContainerScreenTerminalStorage) { + ContainerScreenTerminalStorage gui = ((ContainerScreenTerminalStorage) event.getScreen()); + if (!EmiScreenManager.isDisabled() && EmiScreenManager.search.isFocused()) { + gui.getSelectedClientTab().ifPresent(tab -> { + if (TerminalButtonItemStackCraftingGridSearchSync.isSearchSynced(tab)) { + WidgetTextFieldExtended fieldSearch = gui.getFieldSearch(); + fieldSearch.setValue(EmiScreenManager.search.getValue()); + tab.setInstanceFilter(gui.getMenu().getSelectedChannel(), fieldSearch.getValue() + ""); + } + }); + } + } + } +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/RecipeInputSlotEmiIngredient.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/RecipeInputSlotEmiIngredient.java new file mode 100644 index 0000000..c459af8 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/RecipeInputSlotEmiIngredient.java @@ -0,0 +1,41 @@ +package org.cyclops.integratedterminalscompat.modcompat.emi.terminalstorage; + +import com.google.common.collect.Lists; +import dev.emi.emi.api.stack.EmiIngredient; +import dev.emi.emi.api.stack.EmiStack; +import net.minecraft.world.item.ItemStack; +import org.cyclops.integratedterminalscompat.modcompat.common.RecipeInputSlot; +import org.jetbrains.annotations.NotNull; + +import java.util.Iterator; + +/** + * @author rubensworks + */ +public class RecipeInputSlotEmiIngredient implements RecipeInputSlot { + + private final boolean input; + private final EmiIngredient emiIngredient; + + public RecipeInputSlotEmiIngredient(EmiIngredient emiIngredient, boolean input) { + this.emiIngredient = emiIngredient; + this.input = input; + } + + @NotNull + @Override + public Iterator iterator() { + if (!this.input) { + return Lists.newArrayList().iterator(); + } + return emiIngredient.getEmiStacks().stream() + .map(EmiStack::getItemStack) + .filter(stack -> !stack.isEmpty()) + .iterator(); + } + + @Override + public boolean isEmpty() { + return !this.input || emiIngredient.isEmpty(); + } +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/RecipeInputSlotEmiSlotWidget.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/RecipeInputSlotEmiSlotWidget.java new file mode 100644 index 0000000..de829dd --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/RecipeInputSlotEmiSlotWidget.java @@ -0,0 +1,43 @@ +package org.cyclops.integratedterminalscompat.modcompat.emi.terminalstorage; + +import com.google.common.collect.Lists; +import dev.emi.emi.api.stack.EmiStack; +import dev.emi.emi.api.widget.SlotWidget; +import net.minecraft.world.item.ItemStack; +import org.cyclops.integratedterminalscompat.modcompat.common.RecipeInputSlot; +import org.jetbrains.annotations.NotNull; + +import java.util.Iterator; + +/** + * @author rubensworks + */ +public class RecipeInputSlotEmiSlotWidget implements RecipeInputSlot { + + private final SlotWidget slotWidget; + + public RecipeInputSlotEmiSlotWidget(SlotWidget slotWidget) { + this.slotWidget = slotWidget; + } + + public SlotWidget getSlotWidget() { + return slotWidget; + } + + @NotNull + @Override + public Iterator iterator() { + if (slotWidget.getRecipe() != null) { + return Lists.newArrayList().iterator(); + } + return slotWidget.getStack().getEmiStacks().stream() + .map(EmiStack::getItemStack) + .filter(stack -> !stack.isEmpty()) + .iterator(); + } + + @Override + public boolean isEmpty() { + return slotWidget.getRecipe() != null || slotWidget.getStack().isEmpty(); + } +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/TerminalStorageEmiRecipeHandler.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/TerminalStorageEmiRecipeHandler.java new file mode 100644 index 0000000..bb71bf1 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/TerminalStorageEmiRecipeHandler.java @@ -0,0 +1,167 @@ +package org.cyclops.integratedterminalscompat.modcompat.emi.terminalstorage; + +import com.google.common.collect.Lists; +import com.mojang.blaze3d.vertex.PoseStack; +import dev.emi.emi.api.recipe.EmiCraftingRecipe; +import dev.emi.emi.api.recipe.EmiPlayerInventory; +import dev.emi.emi.api.recipe.EmiRecipe; +import dev.emi.emi.api.recipe.handler.EmiCraftContext; +import dev.emi.emi.api.recipe.handler.EmiRecipeHandler; +import dev.emi.emi.api.stack.EmiIngredient; +import dev.emi.emi.api.stack.EmiStack; +import dev.emi.emi.api.widget.Bounds; +import dev.emi.emi.api.widget.RecipeFillButtonWidget; +import dev.emi.emi.api.widget.SlotWidget; +import dev.emi.emi.api.widget.Widget; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.world.item.ItemStack; +import org.apache.commons.lang3.tuple.Pair; +import org.cyclops.cyclopscore.datastructure.Wrapper; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentClient; +import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageBase; +import org.cyclops.integratedterminalscompat.modcompat.common.RecipeTransferHelpers; +import org.cyclops.integratedterminalscompat.modcompat.common.RecipeTransferResult; +import org.cyclops.integratedterminalscompat.modcompat.emi.EmiIntegratedTerminalsConfig; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author rubensworks + */ +public class TerminalStorageEmiRecipeHandler> implements EmiRecipeHandler { + + private int previousChangeId; + + @Override + public EmiPlayerInventory getInventory(AbstractContainerScreen screen) { + T container = screen.getMenu(); + return RecipeTransferHelpers.getTabs(container) + .map(tabs -> { + List> unfilteredIngredients = tabs.getValue() + .getUnfilteredIngredientsView(container.getSelectedChannel()); + return new EmiPlayerInventory(unfilteredIngredients.stream() + .filter(ingredient -> ingredient.getCraftingOption() == null) + .map(TerminalStorageTabIngredientComponentClient.InstanceWithMetadata::getInstance) + .map(EmiStack::of) + .collect(Collectors.toList())); + }) + .orElseGet(() -> new EmiPlayerInventory(Lists.newArrayList())); + } + + @Override + public boolean supportsRecipe(EmiRecipe emiRecipe) { + return emiRecipe instanceof EmiCraftingRecipe; + } + + @Override + public boolean canCraft(EmiRecipe emiRecipe, EmiCraftContext emiCraftContext) { + return true; + } + + @Override + public boolean craft(EmiRecipe recipe, EmiCraftContext context) { + T container = context.getScreen().getMenu(); + RecipeTransferHelpers.getTabs(container) + .ifPresent(tabs -> { + // Determine input slots + Collection recipeInputSlots = Lists.newArrayList(); + for (EmiIngredient input : recipe.getOutputs()) { + recipeInputSlots.add(new RecipeInputSlotEmiIngredient(input, false)); + } + for (EmiIngredient input : recipe.getInputs()) { + recipeInputSlots.add(new RecipeInputSlotEmiIngredient(input, true)); + } + + // Transfer recipe + RecipeTransferHelpers.transferRecipe( + container, + recipeInputSlots, + Minecraft.getInstance().player, + tabs.getLeft(), + EmiIntegratedTerminalsConfig::getItemStackMatchCondition, + false + ); + }); + return true; + } + + @Override + public List getTooltip(EmiRecipe recipe, EmiCraftContext context) { + T container = context.getScreen().getMenu(); + return RecipeTransferHelpers.getTabs(container) + .map(tabs -> { + // Determine input slots + Collection recipeInputSlots = Lists.newArrayList(); + for (EmiIngredient input : recipe.getInputs()) { + recipeInputSlots.add(new RecipeInputSlotEmiIngredient(input, true)); + } + + // Calculate missing items + return RecipeTransferHelpers.getMissingItems( + Pair.of("tooltip", recipe), + container, + recipeInputSlots, + Minecraft.getInstance().player, + tabs.getLeft(), + tabs.getRight(), + EmiIntegratedTerminalsConfig::getItemStackMatchCondition, + () -> previousChangeId, + id -> previousChangeId = id + ) + .map(result -> result.getMessage().stream().map(c -> ClientTooltipComponent.create(c.getVisualOrderText())).collect(Collectors.toList())) + .orElseGet(List::of); + }) + .orElseGet(List::of); + } + + @Override + public void render(EmiRecipe recipe, EmiCraftContext context, List widgets, PoseStack matrices) { + T container = context.getScreen().getMenu(); + RecipeTransferHelpers.getTabs(container) + .ifPresent(tabs -> { + // Determine input slots + Collection recipeInputSlots = Lists.newArrayList(); + Wrapper fillButton = new Wrapper<>(); + for (Widget widget : widgets) { + if (widget instanceof SlotWidget slotWidget) { + recipeInputSlots.add(new RecipeInputSlotEmiSlotWidget(slotWidget)); + } + if (widget instanceof RecipeFillButtonWidget fillButtonWidget) { + fillButton.set(fillButtonWidget); + } + } + + // Calculate missing items + RecipeTransferHelpers.getMissingItems( + Pair.of("render", recipe), + container, + recipeInputSlots, + Minecraft.getInstance().player, + tabs.getLeft(), + tabs.getRight(), + EmiIntegratedTerminalsConfig::getItemStackMatchCondition, + () -> previousChangeId, + id -> previousChangeId = id + ).ifPresent(transferResult -> { + // Render overlay on slots + for (RecipeInputSlotEmiSlotWidget slot : transferResult.getSlotsMissing()) { + Bounds bounds = slot.getSlotWidget().getBounds(); + GuiComponent.fill(matrices, bounds.x(), bounds.y(), bounds.x() + bounds.width(), bounds.y() + bounds.height(), RecipeTransferResult.SLOT_COLOR_MISSING); + } + for (RecipeInputSlotEmiSlotWidget slot : transferResult.getSlotsCraftable()) { + Bounds bounds = slot.getSlotWidget().getBounds(); + GuiComponent.fill(matrices, bounds.x(), bounds.y(), bounds.x() + bounds.width(), bounds.y() + bounds.height(), RecipeTransferResult.SLOT_COLOR_CRAFTABLE); + } + + // Render overlay on button + Bounds bounds = fillButton.get().getBounds(); + GuiComponent.fill(matrices, bounds.x(), bounds.y(), bounds.x() + bounds.width(), bounds.y() + bounds.height(), transferResult.getButtonHighlightColor()); + }); + }); + } +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/TerminalStorageEmiStackProvider.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/TerminalStorageEmiStackProvider.java new file mode 100644 index 0000000..4227cb9 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/terminalstorage/TerminalStorageEmiStackProvider.java @@ -0,0 +1,43 @@ +package org.cyclops.integratedterminalscompat.modcompat.emi.terminalstorage; + +import dev.emi.emi.api.EmiStackProvider; +import dev.emi.emi.api.stack.EmiStack; +import dev.emi.emi.api.stack.EmiStackInteraction; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import org.cyclops.integratedterminals.client.gui.container.ContainerScreenTerminalStorage; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentClient; + +import java.util.Optional; + +/** + * @author rubensworks + */ +public class TerminalStorageEmiStackProvider implements EmiStackProvider { + @Override + public EmiStackInteraction getStackAt(Screen screen, int mouseX, int mouseY) { + if (screen instanceof ContainerScreenTerminalStorage containerScreen) { + return createClickableIngredient(containerScreen, mouseX, mouseY); + } + return EmiStackInteraction.EMPTY; + } + + private EmiStackInteraction createClickableIngredient( + ContainerScreenTerminalStorage containerScreen, int mouseX, int mouseY) { + int slotIndex = containerScreen.getStorageSlotIndexAtPosition(mouseX, mouseY); + @SuppressWarnings("unchecked") // Cast is safe due to filter + Optional> tabOptional = containerScreen.getSelectedClientTab() + .filter(TerminalStorageTabIngredientComponentClient.class::isInstance) + .map(TerminalStorageTabIngredientComponentClient.class::cast); + if(slotIndex >= 0 && tabOptional.isPresent()) { + TerminalStorageTabIngredientComponentClient tab = tabOptional.get(); + int channel = containerScreen.getMenu().getSelectedChannel(); + Optional instanceOptional = tab.getSlotInstance(channel, slotIndex); + return instanceOptional + .map(instance -> new EmiStackInteraction(instance instanceof ItemStack itemStack ? EmiStack.of(itemStack) : (instance instanceof FluidStack fluidStack ? EmiStack.of(fluidStack.getFluid(), fluidStack.getAmount()) : EmiStack.EMPTY), null, false)) + .orElse(EmiStackInteraction.EMPTY); + } + return EmiStackInteraction.EMPTY; + } +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/JEIIntegratedTerminalsConfig.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/JEIIntegratedTerminalsConfig.java index 8d1160a..63839c0 100644 --- a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/JEIIntegratedTerminalsConfig.java +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/JEIIntegratedTerminalsConfig.java @@ -21,8 +21,6 @@ import org.cyclops.commoncapabilities.api.capability.itemhandler.ItemMatch; import org.cyclops.cyclopscore.client.gui.component.input.WidgetTextFieldExtended; import org.cyclops.integratedterminals.RegistryEntries; -import org.cyclops.integratedterminals.api.terminalstorage.ITerminalButton; -import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabClient; import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageScreenSizeEvent; import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientLoadButtonsEvent; import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientSearchFieldUpdateEvent; @@ -31,9 +29,9 @@ import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStoragePart; import org.cyclops.integratedterminals.part.PartTypes; import org.cyclops.integratedterminalscompat.Reference; +import org.cyclops.integratedterminalscompat.modcompat.common.button.TerminalButtonItemStackCraftingGridSearchSync; import org.cyclops.integratedterminalscompat.modcompat.jei.terminalstorage.TerminalStorageGuiHandler; import org.cyclops.integratedterminalscompat.modcompat.jei.terminalstorage.TerminalStorageRecipeTransferHandler; -import org.cyclops.integratedterminalscompat.modcompat.jei.terminalstorage.button.TerminalButtonItemStackCraftingGridJeiSearchSync; /** * Helper for registering JEI manager. @@ -72,11 +70,9 @@ public void registerIngredients(IModIngredientRegistration registration) { @Override public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration) { registration.addUniversalRecipeTransferHandler( - new TerminalStorageRecipeTransferHandler<>(registration.getTransferHelper(), - ContainerTerminalStoragePart.class, RegistryEntries.CONTAINER_PART_TERMINAL_STORAGE_PART)); + new TerminalStorageRecipeTransferHandler<>(ContainerTerminalStoragePart.class, RegistryEntries.CONTAINER_PART_TERMINAL_STORAGE_PART)); registration.addUniversalRecipeTransferHandler( - new TerminalStorageRecipeTransferHandler<>(registration.getTransferHelper(), - ContainerTerminalStorageItem.class, RegistryEntries.CONTAINER_PART_TERMINAL_STORAGE_ITEM)); + new TerminalStorageRecipeTransferHandler<>(ContainerTerminalStorageItem.class, RegistryEntries.CONTAINER_PART_TERMINAL_STORAGE_ITEM)); } @Override @@ -104,10 +100,10 @@ public void onRuntimeAvailable(IJeiRuntime jeiRuntime) { @SubscribeEvent public void onTerminalStorageButtons(TerminalStorageTabClientLoadButtonsEvent event) { - if (!event.getButtons().stream() - .anyMatch((button) -> button instanceof TerminalButtonItemStackCraftingGridJeiSearchSync)) { - event.getButtons().add(new TerminalButtonItemStackCraftingGridJeiSearchSync( - event.getContainer().getGuiState(), event.getClientTab())); + if (jeiRuntime != null && !event.getButtons().stream() + .anyMatch((button) -> button instanceof TerminalButtonItemStackCraftingGridSearchSync)) { + event.getButtons().add(new TerminalButtonItemStackCraftingGridSearchSync( + "jei", event.getContainer().getGuiState(), event.getClientTab())); } } @@ -134,19 +130,10 @@ public void onTerminalStorageScreenSize(TerminalStorageScreenSizeEvent event) { } } - protected boolean isSearchSynced(ITerminalStorageTabClient clientTab) { - for (ITerminalButton button : clientTab.getButtons()) { - if (button instanceof TerminalButtonItemStackCraftingGridJeiSearchSync) { - return ((TerminalButtonItemStackCraftingGridJeiSearchSync) button).isActive(); - } - } - return false; - } - @SubscribeEvent public void onSearchFieldUpdated(TerminalStorageTabClientSearchFieldUpdateEvent event) { // Copy the terminal search box contents into the JEI search box. - if (isSearchSynced(event.getClientTab())) { + if (jeiRuntime != null && TerminalButtonItemStackCraftingGridSearchSync.isSearchSynced(event.getClientTab())) { jeiRuntime.getIngredientFilter().setFilterText(event.getSearchString() + ""); } } @@ -156,9 +143,9 @@ public void onKeyTyped(ScreenEvent.KeyReleased.Post event) { // Copy the JEI search box contents into the terminal search box. if (event.getScreen() instanceof ContainerScreenTerminalStorage) { ContainerScreenTerminalStorage gui = ((ContainerScreenTerminalStorage) event.getScreen()); - if (jeiRuntime.getIngredientListOverlay().hasKeyboardFocus()) { + if (jeiRuntime != null && jeiRuntime.getIngredientListOverlay().hasKeyboardFocus()) { gui.getSelectedClientTab().ifPresent(tab -> { - if (isSearchSynced(tab)) { + if (TerminalButtonItemStackCraftingGridSearchSync.isSearchSynced(tab)) { WidgetTextFieldExtended fieldSearch = gui.getFieldSearch(); fieldSearch.setValue(jeiRuntime.getIngredientFilter().getFilterText()); tab.setInstanceFilter(gui.getMenu().getSelectedChannel(), fieldSearch.getValue() + ""); diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeInputSlotJei.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeInputSlotJei.java new file mode 100644 index 0000000..71b6d01 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeInputSlotJei.java @@ -0,0 +1,44 @@ +package org.cyclops.integratedterminalscompat.modcompat.jei; + +import com.google.common.collect.Lists; +import mezz.jei.api.gui.ingredient.IRecipeSlotView; +import mezz.jei.api.ingredients.ITypedIngredient; +import mezz.jei.api.recipe.RecipeIngredientRole; +import net.minecraft.world.item.ItemStack; +import org.cyclops.integratedterminalscompat.modcompat.common.RecipeInputSlot; +import org.jetbrains.annotations.NotNull; + +import java.util.Iterator; +import java.util.stream.Stream; + +/** + * @author rubensworks + */ +public class RecipeInputSlotJei implements RecipeInputSlot { + + private final IRecipeSlotView slotView; + + public RecipeInputSlotJei(IRecipeSlotView slotView) { + this.slotView = slotView; + } + + @Override + public boolean isEmpty() { + return this.slotView.getRole() != RecipeIngredientRole.INPUT || this.slotView.isEmpty(); + } + + public IRecipeSlotView getSlotView() { + return slotView; + } + + @NotNull + @Override + public Iterator iterator() { + if (this.slotView.getRole() != RecipeIngredientRole.INPUT) { + return Lists.newArrayList().iterator(); + } + return ((Stream>) (Stream) slotView.getAllIngredients()) + .map(ITypedIngredient::getIngredient) + .iterator(); + } +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeTransferErrorTransferResult.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeTransferErrorTransferResult.java new file mode 100644 index 0000000..d788645 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/RecipeTransferErrorTransferResult.java @@ -0,0 +1,46 @@ +package org.cyclops.integratedterminalscompat.modcompat.jei; + +import com.mojang.blaze3d.vertex.PoseStack; +import mezz.jei.api.gui.ingredient.IRecipeSlotsView; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; +import org.cyclops.integratedterminalscompat.modcompat.common.RecipeTransferResult; + +import java.util.stream.Stream; + +/** + * @author rubensworks + */ +public class RecipeTransferErrorTransferResult implements IRecipeTransferError { + + private final RecipeTransferResult result; + + public RecipeTransferErrorTransferResult(RecipeTransferResult result) { + this.result = result; + } + + @Override + public Type getType() { + return Type.COSMETIC; + } + + @Override + public int getButtonHighlightColor() { + return this.result.getButtonHighlightColor(); + } + + @Override + public void showError(PoseStack poseStack, int mouseX, int mouseY, IRecipeSlotsView recipeSlotsView, int recipeX, int recipeY) { + Minecraft.getInstance().screen.renderComponentTooltip(poseStack, Stream.concat(Stream.of(Component.translatable("jei.tooltip.transfer")), this.result.getMessage().stream()).toList(), mouseX, mouseY); + poseStack.pushPose(); + poseStack.translate(recipeX, recipeY, 0.0); + for (RecipeInputSlotJei slot : this.result.getSlotsMissing()) { + slot.getSlotView().drawHighlight(poseStack, RecipeTransferResult.SLOT_COLOR_MISSING); + } + for (RecipeInputSlotJei slot : this.result.getSlotsCraftable()) { + slot.getSlotView().drawHighlight(poseStack, RecipeTransferResult.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 a79ca4b..c6db7f3 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 @@ -1,74 +1,43 @@ package org.cyclops.integratedterminalscompat.modcompat.jei.terminalstorage; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.mojang.blaze3d.vertex.PoseStack; import mezz.jei.api.constants.RecipeTypes; import mezz.jei.api.constants.VanillaTypes; import mezz.jei.api.gui.ingredient.IRecipeSlotView; import mezz.jei.api.gui.ingredient.IRecipeSlotsView; -import mezz.jei.api.ingredients.ITypedIngredient; import mezz.jei.api.recipe.RecipeIngredientRole; import mezz.jei.api.recipe.RecipeType; import mezz.jei.api.recipe.transfer.IRecipeTransferError; import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; -import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper; -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; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.CraftingRecipe; -import net.minecraftforge.items.wrapper.InvWrapper; -import org.apache.commons.lang3.tuple.Pair; -import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent; -import org.cyclops.commoncapabilities.ingredient.storage.IngredientComponentStorageWrapperHandlerItemStack; -import org.cyclops.cyclopscore.ingredient.collection.IIngredientCollectionMutable; -import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionHelpers; -import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionPrototypeMap; import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabCommon; import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentClient; import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentItemStackCrafting; import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentItemStackCraftingCommon; import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageBase; -import org.cyclops.integratedterminalscompat.IntegratedTerminalsCompat; +import org.cyclops.integratedterminalscompat.modcompat.common.RecipeTransferHelpers; import org.cyclops.integratedterminalscompat.modcompat.jei.JEIIntegratedTerminalsConfig; -import org.cyclops.integratedterminalscompat.modcompat.jei.RecipeTransferErrorColored; -import org.cyclops.integratedterminalscompat.network.packet.TerminalStorageIngredientItemStackCraftingGridSetRecipe; +import org.cyclops.integratedterminalscompat.modcompat.jei.RecipeInputSlotJei; +import org.cyclops.integratedterminalscompat.modcompat.jei.RecipeTransferErrorTransferResult; import javax.annotation.Nullable; -import java.util.List; -import java.util.Map; +import java.util.Collection; import java.util.Objects; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * Handles recipe clicking from JEI. * @author rubensworks */ public class TerminalStorageRecipeTransferHandler> implements IRecipeTransferHandler { - // The amount of seconds recipeErrors will be cached for transferRecipe - private static final long RECIPE_ERROR_CACHE_TIME = 60; - private final IRecipeTransferHandlerHelper recipeTransferHandlerHelper; private final Class clazz; private final MenuType menuType; + private int previousChangeId; - private long previousChangeId; - private final Cache> recipeErrorCache = CacheBuilder.newBuilder() - .expireAfterAccess(RECIPE_ERROR_CACHE_TIME, TimeUnit.SECONDS) - .build(); - - - public TerminalStorageRecipeTransferHandler(IRecipeTransferHandlerHelper recipeTransferHandlerHelper, Class clazz, MenuType menuType) { - this.recipeTransferHandlerHelper = recipeTransferHandlerHelper; + public TerminalStorageRecipeTransferHandler(Class clazz, MenuType menuType) { this.clazz = clazz; this.menuType = menuType; } @@ -100,71 +69,17 @@ public IRecipeTransferError transferRecipe(T container, CraftingRecipe recipe, I if (!doTransfer) { TerminalStorageTabIngredientComponentClient tabClient = (TerminalStorageTabIngredientComponentClient) container.getTabClient(container.getSelectedTab()); - Callable> missingItemsSupplier = - () -> getMissingItems(container, recipe, recipeLayout, player, tabCommonCrafting); - if (previousChangeId != tabClient.getLastChangeId()) { - // Clear cache when storage contents changed - recipeErrorCache.invalidateAll(); - } - try { - return recipeErrorCache.get(recipe, missingItemsSupplier).orElse(null); - } catch (ExecutionException e) { - // Throw exceptions from missingItemsSupplier - throw new RuntimeException(e); - } + return getMissingItems(container, recipe, recipeLayout, player, tabCommonCrafting, tabClient) + .orElse(null); } else { - IngredientComponentStorageWrapperHandlerItemStack.ComponentStorageWrapper playerInventory = - new IngredientComponentStorageWrapperHandlerItemStack.ComponentStorageWrapper(IngredientComponent.ITEMSTACK, new InvWrapper(player.getInventory())); - - // Send a packet to the server if the recipe effectively needs to be applied to the grid - Map> slottedIngredientsFromPlayer = Maps.newHashMap(); - Map>> slottedIngredientsFromStorage = Maps.newHashMap(); - int slotOffset = tabCommonCrafting.getSlotCrafting().index; - int slotId = 0; - 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; - - // First check if we can transfer from the player inventory - // No need to check the crafting grid, as the server will first clear the grid into the storage in TerminalStorageIngredientItemStackCraftingGridSetRecipe - Set allIngredients = ((Stream>) (Stream) slotView.getAllIngredients()) - .map(ITypedIngredient::getIngredient) - .collect(Collectors.toSet()); - for (ItemStack itemStack : allIngredients) { - int matchCondition = JEIIntegratedTerminalsConfig.getItemStackMatchCondition(itemStack); - - if (!playerInventory.extract(itemStack, matchCondition, true).isEmpty()) { - found = true; - - // Move from player to crafting grid - ItemStack extracted = playerInventory.extract(itemStack, matchCondition, false); - Slot slot = container.getSlot(slotId + slotOffset); - slot.set(extracted); - - // Do the exact same thing server-side - slottedIngredientsFromPlayer.put(slotId, Pair.of(itemStack, JEIIntegratedTerminalsConfig.getItemStackMatchCondition(itemStack))); - - break; - } - } - - if (!found) { - // Otherwise, request them from the storage - slottedIngredientsFromStorage.put(slotId, allIngredients - .stream() - .map(itemStack -> Pair.of(itemStack, JEIIntegratedTerminalsConfig.getItemStackMatchCondition(itemStack))) - .collect(Collectors.toList())); - } - } - } - slotId++; - } - - IntegratedTerminalsCompat._instance.getPacketHandler().sendToServer( - new TerminalStorageIngredientItemStackCraftingGridSetRecipe(container.getSelectedTab(), - container.getSelectedChannel(), maxTransfer, slottedIngredientsFromPlayer, slottedIngredientsFromStorage, AbstractContainerScreen.hasControlDown())); + RecipeTransferHelpers.transferRecipe( + container, + getRecipeInputSlots(recipeLayout), + player, + tabCommonCrafting, + JEIIntegratedTerminalsConfig::getItemStackMatchCondition, + maxTransfer + ); return null; } } @@ -172,94 +87,28 @@ public IRecipeTransferError transferRecipe(T container, CraftingRecipe recipe, I return new TransferError(); } - private Optional getMissingItems(T container, CraftingRecipe recipe, IRecipeSlotsView recipeLayout, Player player, TerminalStorageTabIngredientComponentItemStackCraftingCommon tabCommonCrafting) { - TerminalStorageTabIngredientComponentClient tabClient = (TerminalStorageTabIngredientComponentClient) - container.getTabClient(container.getSelectedTab()); - - // Check in the player inventory and local client view if the required recipe ingredients are available - - // Build crafting grid index - IIngredientCollectionMutable hayStackCraftingGrid = new IngredientCollectionPrototypeMap<>(IngredientComponent.ITEMSTACK); - for (int slot = 0; slot < tabCommonCrafting.getInventoryCrafting().getContainerSize(); slot++) { - hayStackCraftingGrid.add(tabCommonCrafting.getInventoryCrafting().getItem(slot)); - } - - // Build player inventory index - IIngredientCollectionMutable hayStackPlayer = new IngredientCollectionPrototypeMap<>(IngredientComponent.ITEMSTACK); - hayStackPlayer.addAll(player.getInventory().items); - - // Build local client view of storage - 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(); + private Collection getRecipeInputSlots(IRecipeSlotsView recipeLayout) { + Collection recipeInputSlots = 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())) { - int matchCondition = JEIIntegratedTerminalsConfig.getItemStackMatchCondition(itemStack); - - // First check in the crafting grid - if (hayStackCraftingGrid.contains(itemStack, matchCondition)) { - hayStackPlayer.remove(itemStack); - found = true; - break; - } - - // Then check in player inventory - if (hayStackPlayer.contains(itemStack, matchCondition)) { - hayStackPlayer.remove(itemStack); - found = true; - break; - } - - // Then check the storage - if (hayStack.contains(itemStack, matchCondition)) { - hayStack.remove(itemStack); - found = true; - break; - } - - // Also check if this item could be craftable - if (hayStackCraftable.contains(itemStack, matchCondition)) { - craftable = true; - } - } - if (!found) { - if (craftable) { - slotsMissingCraftableItems.add(slotView); - } else { - slotsMissingItems.add(slotView); - } - } - } + if (slotView.getRole() != RecipeIngredientRole.INPUT || slotView.isEmpty() || slotView.getAllIngredients().findFirst().get().getType() == VanillaTypes.ITEM_STACK) { + recipeInputSlots.add(new RecipeInputSlotJei(slotView)); } } + return recipeInputSlots; + } - previousChangeId = tabClient.getLastChangeId(); - if (!slotsMissingItems.isEmpty() || !slotsMissingCraftableItems.isEmpty()) { - return Optional.of(new RecipeTransferErrorColored(slotsMissingItems, slotsMissingCraftableItems)); - } - - return Optional.empty(); + private Optional getMissingItems(T container, CraftingRecipe recipe, IRecipeSlotsView recipeLayout, Player player, TerminalStorageTabIngredientComponentItemStackCraftingCommon tabCommonCrafting, TerminalStorageTabIngredientComponentClient tabClient) { + return RecipeTransferHelpers.getMissingItems( + recipe, + container, + getRecipeInputSlots(recipeLayout), + player, + tabCommonCrafting, + tabClient, + JEIIntegratedTerminalsConfig::getItemStackMatchCondition, + () -> previousChangeId, + id -> previousChangeId = id + ).map(RecipeTransferErrorTransferResult::new); } public static class TransferError implements IRecipeTransferError { diff --git a/src/main/resources/assets/integratedterminalscompat/lang/en_us.json b/src/main/resources/assets/integratedterminalscompat/lang/en_us.json index 9e00f73..b671ea3 100644 --- a/src/main/resources/assets/integratedterminalscompat/lang/en_us.json +++ b/src/main/resources/assets/integratedterminalscompat/lang/en_us.json @@ -2,8 +2,11 @@ "_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.emisync": "EMI Search Sync", + "gui.integratedterminalscompat.terminal_storage.craftinggrid.emisync.info": "Synchronize search box with EMI.", "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" + "gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info": "Ctrl+click to trigger auto-crafting", + "gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info_cmd": "Cmd+click to trigger auto-crafting" } \ No newline at end of file From 048a631cd5072aac23bcf07097bd7df83170330a Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Tue, 22 Oct 2024 19:25:21 +0200 Subject: [PATCH 3/4] Add REI support --- build.gradle | 10 +- gradle.properties | 5 +- .../common/RecipeTransferResult.java | 2 +- .../modcompat/rei/RecipeInputSlotRei.java | 45 +++++++ .../rei/ReiIntegratedTerminalsConfig.java | 121 ++++++++++++++++++ ...erminalStorageReiFocusedStackProvider.java | 45 +++++++ .../TerminalStorageReiTransferHandler.java | 120 +++++++++++++++++ .../integratedterminalscompat/lang/en_us.json | 2 + 8 files changed, 347 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/RecipeInputSlotRei.java create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/ReiIntegratedTerminalsConfig.java create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/terminalstorage/TerminalStorageReiFocusedStackProvider.java create mode 100644 src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/terminalstorage/TerminalStorageReiTransferHandler.java diff --git a/build.gradle b/build.gradle index 478b45a..487b000 100644 --- a/build.gradle +++ b/build.gradle @@ -93,6 +93,10 @@ repositories { name "EMI" url "https://maven.terraformersmc.com/" } + maven { + name "REI" + url "https://maven.shedaniel.me/" + } } dependencies { @@ -144,7 +148,11 @@ dependencies { runtimeOnly fg.deobf("top.theillusivec4.curios:curios-forge:${project.curios_version}") // https://maven.theillusivec4.top/top/theillusivec4/curios/curios-forge/ compileOnly fg.deobf("top.theillusivec4.curios:curios-forge:${project.curios_version}:api") - implementation fg.deobf("dev.emi:emi-forge:${emi_version}") + compileOnly fg.deobf("dev.emi:emi-forge:${emi_version}") // https://maven.terraformersmc.com/ + + compileOnly fg.deobf("me.shedaniel:RoughlyEnoughItems-forge:${rei_version}") // https://maven.shedaniel.me/me/shedaniel/RoughlyEnoughItems-forge/ + implementation fg.deobf("me.shedaniel.cloth:cloth-config-forge:$cloth_config_version"); + implementation fg.deobf("dev.architectury:architectury-forge:$architectury_version"); } minecraft { diff --git a/gradle.properties b/gradle.properties index 9d3e8e8..d2af01b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ mod_version=1.0.0 minecraft_version=1.19.2 -forge_version=43.0.8 +forge_version=43.4.4 cyclopscore_version=1.19.4-495 release_type=release fingerprint=bd0353b3e8a2810d60dd584e256e364bc3bedd44 @@ -11,6 +11,9 @@ 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 emi_version=1.1.16+1.19.2 +rei_version=9.2.779 +cloth_config_version=8.3.134 +architectury_version=6.6.92 # Workaround for Spotless bug # https://github.com/diffplug/spotless/issues/834 diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeTransferResult.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeTransferResult.java index e93a406..2266188 100644 --- a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeTransferResult.java +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/RecipeTransferResult.java @@ -35,7 +35,7 @@ public RecipeTransferResult(Collection slotsMissing, Collection slotsCraft } else 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.message.add(Component.translatable(HAS_CMD ? "gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info_cmd" : "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 diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/RecipeInputSlotRei.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/RecipeInputSlotRei.java new file mode 100644 index 0000000..28ff67c --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/RecipeInputSlotRei.java @@ -0,0 +1,45 @@ +package org.cyclops.integratedterminalscompat.modcompat.rei; + +import com.google.common.collect.Lists; +import me.shedaniel.rei.api.common.entry.EntryIngredient; +import net.minecraft.world.item.ItemStack; +import org.cyclops.integratedterminalscompat.modcompat.common.RecipeInputSlot; +import org.jetbrains.annotations.NotNull; + +import java.util.Iterator; + +/** + * @author rubensworks + */ +public class RecipeInputSlotRei implements RecipeInputSlot { + + private final EntryIngredient ingredient; + private final boolean input; + private final int index; + + public RecipeInputSlotRei(EntryIngredient ingredient, boolean input, int index) { + this.ingredient = ingredient; + this.input = input; + this.index = index; + } + + public int getIndex() { + return index; + } + + @Override + public boolean isEmpty() { + return !this.input || this.ingredient.isEmpty(); + } + + @NotNull + @Override + public Iterator iterator() { + if (!this.input) { + return Lists.newArrayList().iterator(); + } + return this.ingredient.stream() + .map(i -> i.castValue()) + .iterator(); + } +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/ReiIntegratedTerminalsConfig.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/ReiIntegratedTerminalsConfig.java new file mode 100644 index 0000000..0acb982 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/ReiIntegratedTerminalsConfig.java @@ -0,0 +1,121 @@ +package org.cyclops.integratedterminalscompat.modcompat.rei; + +import me.shedaniel.rei.api.client.REIRuntime; +import me.shedaniel.rei.api.client.plugins.REIClientPlugin; +import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry; +import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRegistry; +import me.shedaniel.rei.api.common.entry.comparison.ItemComparatorRegistry; +import me.shedaniel.rei.forge.REIPluginClient; +import net.minecraft.client.Minecraft; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.client.event.ScreenEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import org.cyclops.commoncapabilities.api.capability.itemhandler.ItemMatch; +import org.cyclops.cyclopscore.client.gui.component.input.WidgetTextFieldExtended; +import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageScreenSizeEvent; +import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientLoadButtonsEvent; +import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientSearchFieldUpdateEvent; +import org.cyclops.integratedterminals.client.gui.container.ContainerScreenTerminalStorage; +import org.cyclops.integratedterminalscompat.modcompat.common.button.TerminalButtonItemStackCraftingGridSearchSync; +import org.cyclops.integratedterminalscompat.modcompat.rei.terminalstorage.TerminalStorageReiFocusedStackProvider; +import org.cyclops.integratedterminalscompat.modcompat.rei.terminalstorage.TerminalStorageReiTransferHandler; + +/** + * @author rubensworks + */ +@REIPluginClient +public class ReiIntegratedTerminalsConfig implements REIClientPlugin { + + private static ItemComparatorRegistry itemComparatorRegistry; + + public static int getItemStackMatchCondition(ItemStack itemStack) { + // By default, REI ignores NBT when matching items, unless sub type info is set. + + // Ideally, we would have to do a plain item extraction, and filter the items that match the subtype string, + // but just using the heuristic that the existence of sub type info implies NBT matching seems to work out so far. + // So if we would run into problems with this, this filtering is what we'd need to do. + + return !itemComparatorRegistry.containsComparator(itemStack.getItem()) ? ItemMatch.ITEM : ItemMatch.ITEM | ItemMatch.TAG; + } + + private boolean loaded = false; + private boolean wasReiVisible = false; + + public ReiIntegratedTerminalsConfig() { + MinecraftForge.EVENT_BUS.register(this); + } + + @Override + public void registerItemComparators(ItemComparatorRegistry registry) { + itemComparatorRegistry = registry; + } + + @Override + public void registerScreens(ScreenRegistry registry) { + loaded = true; + registry.registerFocusedStack(new TerminalStorageReiFocusedStackProvider()); + } + + @Override + public void registerTransferHandlers(TransferHandlerRegistry registry) { + registry.register(new TerminalStorageReiTransferHandler()); + } + + @SubscribeEvent + public void onTerminalStorageButtons(TerminalStorageTabClientLoadButtonsEvent event) { + if (this.loaded && !event.getButtons().stream() + .anyMatch((button) -> button instanceof TerminalButtonItemStackCraftingGridSearchSync)) { + event.getButtons().add(new TerminalButtonItemStackCraftingGridSearchSync( + "rei", event.getContainer().getGuiState(), event.getClientTab())); + } + } + + @SubscribeEvent + public void onTerminalStorageScreenSize(TerminalStorageScreenSizeEvent event) { + if (this.loaded) { + try { + boolean isOpen = REIRuntime.getInstance().getOverlay().isPresent(); + boolean wasJeiVisiblePrevious = wasReiVisible; + if (isOpen) { + wasReiVisible = true; + event.setWidth(event.getWidth() - 170); + } else { + wasReiVisible = false; + } + + // Re-init screen if JEI was just made (in)visible + if (wasJeiVisiblePrevious != wasReiVisible) { + ((ContainerScreenTerminalStorage) Minecraft.getInstance().screen).init(); + } + } catch (NoClassDefFoundError | ClassCastException e) { + // Do nothing when we detect some JEI API issues + } + } + } + + @SubscribeEvent + public void onSearchFieldUpdated(TerminalStorageTabClientSearchFieldUpdateEvent event) { + // Copy the terminal search box contents into the JEI search box. + if (REIRuntime.getInstance().getOverlay().isPresent() && TerminalButtonItemStackCraftingGridSearchSync.isSearchSynced(event.getClientTab())) { + REIRuntime.getInstance().getSearchTextField().setText(event.getSearchString() + ""); + } + } + + @SubscribeEvent + public void onKeyTyped(ScreenEvent.KeyReleased.Post event) { + // Copy the JEI search box contents into the terminal search box. + if (event.getScreen() instanceof ContainerScreenTerminalStorage) { + ContainerScreenTerminalStorage gui = ((ContainerScreenTerminalStorage) event.getScreen()); + if (REIRuntime.getInstance().getOverlay().isPresent() && REIRuntime.getInstance().getSearchTextField().isFocused()) { + gui.getSelectedClientTab().ifPresent(tab -> { + if (TerminalButtonItemStackCraftingGridSearchSync.isSearchSynced(tab)) { + WidgetTextFieldExtended fieldSearch = gui.getFieldSearch(); + fieldSearch.setValue(REIRuntime.getInstance().getSearchTextField().getText()); + tab.setInstanceFilter(gui.getMenu().getSelectedChannel(), fieldSearch.getValue() + ""); + } + }); + } + } + } +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/terminalstorage/TerminalStorageReiFocusedStackProvider.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/terminalstorage/TerminalStorageReiFocusedStackProvider.java new file mode 100644 index 0000000..914baff --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/terminalstorage/TerminalStorageReiFocusedStackProvider.java @@ -0,0 +1,45 @@ +package org.cyclops.integratedterminalscompat.modcompat.rei.terminalstorage; + +import dev.architectury.event.CompoundEventResult; +import me.shedaniel.math.Point; +import me.shedaniel.rei.api.client.registry.screen.FocusedStackProvider; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.util.EntryStacks; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import org.cyclops.integratedterminals.client.gui.container.ContainerScreenTerminalStorage; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentClient; + +import java.util.Optional; + +/** + * @author rubensworks + */ +public class TerminalStorageReiFocusedStackProvider implements FocusedStackProvider { + @Override + public CompoundEventResult> provide(Screen screen, Point point) { + if (screen instanceof ContainerScreenTerminalStorage containerScreen) { + return createClickableIngredient(containerScreen, point.x, point.y); + } + return CompoundEventResult.pass(); + } + + private CompoundEventResult> createClickableIngredient( + ContainerScreenTerminalStorage containerScreen, int mouseX, int mouseY) { + int slotIndex = containerScreen.getStorageSlotIndexAtPosition(mouseX, mouseY); + @SuppressWarnings("unchecked") // Cast is safe due to filter + Optional> tabOptional = containerScreen.getSelectedClientTab() + .filter(TerminalStorageTabIngredientComponentClient.class::isInstance) + .map(TerminalStorageTabIngredientComponentClient.class::cast); + if(slotIndex >= 0 && tabOptional.isPresent()) { + TerminalStorageTabIngredientComponentClient tab = tabOptional.get(); + int channel = containerScreen.getMenu().getSelectedChannel(); + Optional instanceOptional = tab.getSlotInstance(channel, slotIndex); + return instanceOptional + .map(instance -> CompoundEventResult.interruptTrue(instance instanceof ItemStack itemStack ? EntryStacks.of(itemStack) : (instance instanceof FluidStack fluidStack ? EntryStacks.of(fluidStack.getFluid(), fluidStack.getAmount()) : EntryStack.empty()))) + .orElse(CompoundEventResult.pass()); + } + return CompoundEventResult.pass(); + } +} diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/terminalstorage/TerminalStorageReiTransferHandler.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/terminalstorage/TerminalStorageReiTransferHandler.java new file mode 100644 index 0000000..c36441a --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/terminalstorage/TerminalStorageReiTransferHandler.java @@ -0,0 +1,120 @@ +package org.cyclops.integratedterminalscompat.modcompat.rei.terminalstorage; + +import com.google.common.collect.Lists; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.gui.widgets.Slot; +import me.shedaniel.rei.api.client.gui.widgets.Widget; +import me.shedaniel.rei.api.client.registry.transfer.TransferHandler; +import me.shedaniel.rei.api.common.entry.EntryIngredient; +import me.shedaniel.rei.plugin.common.BuiltinPlugin; +import me.shedaniel.rei.plugin.common.displays.crafting.DefaultCraftingDisplay; +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.network.chat.Component; +import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabCommon; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentClient; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentItemStackCrafting; +import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentItemStackCraftingCommon; +import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageBase; +import org.cyclops.integratedterminalscompat.modcompat.common.RecipeTransferHelpers; +import org.cyclops.integratedterminalscompat.modcompat.common.RecipeTransferResult; +import org.cyclops.integratedterminalscompat.modcompat.rei.RecipeInputSlotRei; +import org.cyclops.integratedterminalscompat.modcompat.rei.ReiIntegratedTerminalsConfig; + +import java.util.Collection; +import java.util.Objects; + +/** + * @author rubensworks + */ +public class TerminalStorageReiTransferHandler implements TransferHandler { + + private int previousChangeId; + + @Override + public Result handle(Context context) { + if (context.getDisplay().getCategoryIdentifier().equals(BuiltinPlugin.CRAFTING) && + context.getDisplay() instanceof DefaultCraftingDisplay displayCrafting && + context.getMenu() instanceof ContainerTerminalStorageBase container && + Objects.equals(container.getSelectedTab(), TerminalStorageTabIngredientComponentItemStackCrafting.NAME.toString())) { + ITerminalStorageTabCommon tabCommon = container.getTabCommon(container.getSelectedTab()); + TerminalStorageTabIngredientComponentItemStackCraftingCommon tabCommonCrafting = + (TerminalStorageTabIngredientComponentItemStackCraftingCommon) tabCommon; + + if (context.isActuallyCrafting()) { + RecipeTransferHelpers.transferRecipe( + container, + getRecipeInputSlots(displayCrafting), + context.getMinecraft().player, + tabCommonCrafting, + ReiIntegratedTerminalsConfig::getItemStackMatchCondition, + context.isStackedCrafting() + ); + return Result.createSuccessful().blocksFurtherHandling(); + } else { + TerminalStorageTabIngredientComponentClient tabClient = (TerminalStorageTabIngredientComponentClient) + container.getTabClient(container.getSelectedTab()); + return RecipeTransferHelpers.getMissingItems( + displayCrafting.getOptionalRecipe().orElse(null), + container, + getRecipeInputSlots(displayCrafting), + context.getMinecraft().player, + tabCommonCrafting, + tabClient, + ReiIntegratedTerminalsConfig::getItemStackMatchCondition, + () -> previousChangeId, + id -> previousChangeId = id + ).map(transferResult -> Result.createSuccessful() + .color(transferResult.getButtonHighlightColor()) + .tooltip(transferResult.getMessage().stream().reduce(Component.empty(), (c1, c2) -> c1.copy().append("\n").append(c2))) + .renderer((poseStack, mouseX, mouseY, v, widgets, rectangle, display) -> { + int index = 0; + for (Widget widget : widgets) { + if (widget instanceof Slot widgetSlot && widgetSlot.getNoticeMark() == Slot.INPUT) { + // Determine if this slot requires any highlighting + int color = -1; + for (RecipeInputSlotRei slot : transferResult.getSlotsMissing()) { + if (slot.getIndex() == index) { + color = RecipeTransferResult.SLOT_COLOR_MISSING; + break; + } + } + for (RecipeInputSlotRei slot : transferResult.getSlotsCraftable()) { + if (slot.getIndex() == index) { + color = RecipeTransferResult.SLOT_COLOR_CRAFTABLE; + break; + } + } + + // Draw the highlight + if (color != -1) { + Rectangle bounds = widgetSlot.getInnerBounds(); + poseStack.pushPose(); + poseStack.translate(0, 0, 20); + GuiComponent.fill(poseStack, bounds.x, bounds.y, bounds.getMaxX(), bounds.getMaxY(), color); + poseStack.popPose(); + } + + index++; + } + } + + }) + .blocksFurtherHandling()) + .orElse(Result.createSuccessful().blocksFurtherHandling()); + } + } + return Result.createNotApplicable(); + } + + private Collection getRecipeInputSlots(DefaultCraftingDisplay context) { + Collection recipeInputSlots = Lists.newArrayList(); + for (EntryIngredient outputEntry : context.getOutputEntries()) { + recipeInputSlots.add(new RecipeInputSlotRei(outputEntry, false, 0)); + } + int i = 0; + for (EntryIngredient outputEntry : context.getOrganisedInputEntries(3, 3)) { + recipeInputSlots.add(new RecipeInputSlotRei(outputEntry, true, i++)); + } + return recipeInputSlots; + } +} diff --git a/src/main/resources/assets/integratedterminalscompat/lang/en_us.json b/src/main/resources/assets/integratedterminalscompat/lang/en_us.json index b671ea3..97eee07 100644 --- a/src/main/resources/assets/integratedterminalscompat/lang/en_us.json +++ b/src/main/resources/assets/integratedterminalscompat/lang/en_us.json @@ -4,6 +4,8 @@ "gui.integratedterminalscompat.terminal_storage.craftinggrid.jeisync.info": "Synchronize search box with JEI.", "gui.integratedterminalscompat.terminal_storage.craftinggrid.emisync": "EMI Search Sync", "gui.integratedterminalscompat.terminal_storage.craftinggrid.emisync.info": "Synchronize search box with EMI.", + "gui.integratedterminalscompat.terminal_storage.craftinggrid.reisync": "REI Search Sync", + "gui.integratedterminalscompat.terminal_storage.craftinggrid.reisync.info": "Synchronize search box with REI.", "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", From 7da284fa8f66cb0f09e88be9e7762211ee86287c Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Wed, 23 Oct 2024 17:15:14 +0200 Subject: [PATCH 4/4] Add dedicated buttons for EMI and REI --- gradle.properties | 2 +- .../TerminalButtonItemStackCraftingGridSearchSync.java | 7 +++++-- .../modcompat/emi/EmiIntegratedTerminalsConfig.java | 3 ++- .../modcompat/jei/JEIIntegratedTerminalsConfig.java | 3 ++- .../modcompat/rei/ReiIntegratedTerminalsConfig.java | 3 ++- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/gradle.properties b/gradle.properties index d2af01b..e64ec53 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ release_type=release fingerprint=bd0353b3e8a2810d60dd584e256e364bc3bedd44 integrateddynamics_version=1.19.2-1.22.0-726 -integratedterminals_version=1.19.2-1.5.1-385 +integratedterminals_version=1.19.2-1.5.1-386 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/common/button/TerminalButtonItemStackCraftingGridSearchSync.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/button/TerminalButtonItemStackCraftingGridSearchSync.java index 9ceedbb..68bbf04 100644 --- a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/button/TerminalButtonItemStackCraftingGridSearchSync.java +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/common/button/TerminalButtonItemStackCraftingGridSearchSync.java @@ -8,6 +8,7 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import org.cyclops.cyclopscore.client.gui.component.button.ButtonImage; +import org.cyclops.cyclopscore.client.gui.image.Image; import org.cyclops.integratedterminals.api.terminalstorage.ITerminalButton; import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabClient; import org.cyclops.integratedterminals.client.gui.image.Images; @@ -30,14 +31,16 @@ public class TerminalButtonItemStackCraftingGridSearchSync private final TerminalStorageState state; private final String buttonName; private final ITerminalStorageTabClient clientTab; + private final Image image; private boolean active; - public TerminalButtonItemStackCraftingGridSearchSync(String mod, TerminalStorageState state, ITerminalStorageTabClient clientTab) { + public TerminalButtonItemStackCraftingGridSearchSync(String mod, TerminalStorageState state, ITerminalStorageTabClient clientTab, Image image) { this.mod = mod; this.state = state; this.buttonName = "itemstack_grid_" + mod + "searchsync"; this.clientTab = clientTab; + this.image = image; reloadFromState(); } @@ -59,7 +62,7 @@ public ButtonImage createButton(int x, int y) { Component.translatable("gui.integratedterminalscompat.terminal_storage.craftinggrid." + mod + "sync"), (b) -> {}, active ? Images.BUTTON_BACKGROUND_ACTIVE : Images.BUTTON_BACKGROUND_INACTIVE, - Images.BUTTON_MIDDLE_JEI_SYNC); + this.image); } @Override diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/EmiIntegratedTerminalsConfig.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/EmiIntegratedTerminalsConfig.java index 2456316..01e0eb4 100644 --- a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/EmiIntegratedTerminalsConfig.java +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/emi/EmiIntegratedTerminalsConfig.java @@ -18,6 +18,7 @@ import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientLoadButtonsEvent; import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientSearchFieldUpdateEvent; import org.cyclops.integratedterminals.client.gui.container.ContainerScreenTerminalStorage; +import org.cyclops.integratedterminals.client.gui.image.Images; import org.cyclops.integratedterminalscompat.modcompat.common.button.TerminalButtonItemStackCraftingGridSearchSync; import org.cyclops.integratedterminalscompat.modcompat.emi.terminalstorage.TerminalStorageEmiRecipeHandler; import org.cyclops.integratedterminalscompat.modcompat.emi.terminalstorage.TerminalStorageEmiStackProvider; @@ -54,7 +55,7 @@ public void onTerminalStorageButtons(TerminalStorageTabClientLoadButtonsEvent ev if (this.loaded && !event.getButtons().stream() .anyMatch((button) -> button instanceof TerminalButtonItemStackCraftingGridSearchSync)) { event.getButtons().add(new TerminalButtonItemStackCraftingGridSearchSync( - "emi", event.getContainer().getGuiState(), event.getClientTab())); + "emi", event.getContainer().getGuiState(), event.getClientTab(), Images.BUTTON_MIDDLE_EMI_SYNC)); } } diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/JEIIntegratedTerminalsConfig.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/JEIIntegratedTerminalsConfig.java index 63839c0..fd2221e 100644 --- a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/JEIIntegratedTerminalsConfig.java +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/jei/JEIIntegratedTerminalsConfig.java @@ -25,6 +25,7 @@ import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientLoadButtonsEvent; import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientSearchFieldUpdateEvent; import org.cyclops.integratedterminals.client.gui.container.ContainerScreenTerminalStorage; +import org.cyclops.integratedterminals.client.gui.image.Images; import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageItem; import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStoragePart; import org.cyclops.integratedterminals.part.PartTypes; @@ -103,7 +104,7 @@ public void onTerminalStorageButtons(TerminalStorageTabClientLoadButtonsEvent ev if (jeiRuntime != null && !event.getButtons().stream() .anyMatch((button) -> button instanceof TerminalButtonItemStackCraftingGridSearchSync)) { event.getButtons().add(new TerminalButtonItemStackCraftingGridSearchSync( - "jei", event.getContainer().getGuiState(), event.getClientTab())); + "jei", event.getContainer().getGuiState(), event.getClientTab(), Images.BUTTON_MIDDLE_JEI_SYNC)); } } diff --git a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/ReiIntegratedTerminalsConfig.java b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/ReiIntegratedTerminalsConfig.java index 0acb982..464d185 100644 --- a/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/ReiIntegratedTerminalsConfig.java +++ b/src/main/java/org/cyclops/integratedterminalscompat/modcompat/rei/ReiIntegratedTerminalsConfig.java @@ -17,6 +17,7 @@ import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientLoadButtonsEvent; import org.cyclops.integratedterminals.api.terminalstorage.event.TerminalStorageTabClientSearchFieldUpdateEvent; import org.cyclops.integratedterminals.client.gui.container.ContainerScreenTerminalStorage; +import org.cyclops.integratedterminals.client.gui.image.Images; import org.cyclops.integratedterminalscompat.modcompat.common.button.TerminalButtonItemStackCraftingGridSearchSync; import org.cyclops.integratedterminalscompat.modcompat.rei.terminalstorage.TerminalStorageReiFocusedStackProvider; import org.cyclops.integratedterminalscompat.modcompat.rei.terminalstorage.TerminalStorageReiTransferHandler; @@ -67,7 +68,7 @@ public void onTerminalStorageButtons(TerminalStorageTabClientLoadButtonsEvent ev if (this.loaded && !event.getButtons().stream() .anyMatch((button) -> button instanceof TerminalButtonItemStackCraftingGridSearchSync)) { event.getButtons().add(new TerminalButtonItemStackCraftingGridSearchSync( - "rei", event.getContainer().getGuiState(), event.getClientTab())); + "rei", event.getContainer().getGuiState(), event.getClientTab(), Images.BUTTON_MIDDLE_REI_SYNC)); } }