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