├── src └── main │ ├── resources │ ├── pack.mcmeta │ ├── icon.png │ ├── toomanyrecipeviewers.mixins.json │ ├── META-INF │ │ └── neoforge.mods.toml │ └── LICENSE_emi │ └── java │ └── dev │ └── nolij │ └── toomanyrecipeviewers │ ├── util │ ├── FluidRendererParameters.java │ ├── IItemStackish.java │ ├── IFluidStackish.java │ ├── IStackish.java │ ├── FormattedTextConsumer.java │ ├── ComponentFormattedCharSink.java │ └── ResourceLocationHolderComparator.java │ ├── impl │ ├── jei │ │ ├── forge │ │ │ └── JustEnoughItems.java │ │ ├── library │ │ │ └── config │ │ │ │ └── ModIDFormatConfig.java │ │ ├── api │ │ │ ├── runtime │ │ │ │ ├── config │ │ │ │ │ └── JEIConfigManager.java │ │ │ │ ├── JEIRuntime.java │ │ │ │ └── JEIKeyMappings.java │ │ │ ├── gui │ │ │ │ ├── drawable │ │ │ │ │ └── OffsetDrawable.java │ │ │ │ ├── ingredient │ │ │ │ │ ├── ITMRVSlotWidget.java │ │ │ │ │ ├── ITMRVRecipeSlotDrawable.java │ │ │ │ │ ├── TMRVTankWidget.java │ │ │ │ │ └── TMRVSlotWidget.java │ │ │ │ ├── builder │ │ │ │ │ ├── RecipeLayoutBuilder.java │ │ │ │ │ ├── TMRVTooltipBuilder.java │ │ │ │ │ ├── TMRVIngredientCollector.java │ │ │ │ │ └── RecipeSlotBuilder.java │ │ │ │ └── widgets │ │ │ │ │ └── ScrollGridWidget.java │ │ │ ├── recipe │ │ │ │ └── advanced │ │ │ │ │ └── RecipeManagerPluginHelper.java │ │ │ └── registration │ │ │ │ ├── RecipeRegistration.java │ │ │ │ └── RuntimeRegistration.java │ │ └── common │ │ │ ├── config │ │ │ ├── JEIClientConfigs.java │ │ │ ├── IngredientGridConfig.java │ │ │ ├── IngredientFilterConfig.java │ │ │ └── ClientConfig.java │ │ │ └── network │ │ │ └── ConnectionToServer.java │ ├── widget │ │ ├── FillingFlameWidget.java │ │ ├── DeferredPlaceableWidget.java │ │ ├── DrawableWidget.java │ │ ├── ButtonWidget.java │ │ ├── TextWidget.java │ │ └── ScrollBarWidget.java │ ├── ingredient │ │ ├── ErrorIngredient.java │ │ └── ErrorEmiStack.java │ └── recipe │ │ └── ExtendedSmithingRecipe.java │ ├── mixin │ ├── PacketRecipeTransferAccessor.java │ ├── EmiAgnosMixin.java │ ├── SmithingRecipeCategoryAccessor.java │ ├── JemiStackMixin.java │ ├── NormalizedTypedItemMixin.java │ ├── EmiReloadManager_ReloadWorkerMixin.java │ ├── NormalizedTypedItemStackMixin.java │ ├── FullTypedItemStackMixin.java │ ├── VanillaPluginMixin.java │ ├── TypedItemStackMixin.java │ ├── TypedIngredientMixin.java │ ├── ItemEmiStackMixin.java │ ├── JemiUtilMixin.java │ └── FluidEmiStackMixin.java │ ├── TooManyRecipeViewersMod.java │ └── TooManyRecipeViewers.java ├── jei-api └── src │ └── main │ ├── resources │ ├── pack.mcmeta │ ├── META-INF │ │ └── neoforge.mods.toml │ └── LICENSE_jei │ └── java │ └── mezz │ └── jei │ ├── common │ ├── platform │ │ ├── JEIAPIStub.java │ │ └── Services.java │ └── config │ │ └── DebugConfig.java │ └── library │ └── config │ └── ColorNameConfig.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── CHANGELOG.md ├── versions ├── 21.1-neoforge │ └── gradle.properties └── 20.1-lexforge │ └── gradle.properties ├── .github └── workflows │ ├── pull_request.yml │ ├── release_pre.yml │ ├── release_rc.yml │ ├── release_dev.yml │ ├── release.yml │ ├── build.yml │ └── dco.yml ├── settings.gradle.kts ├── gradle.properties ├── .gitignore ├── gradlew.bat ├── stonecutter.gradle.kts ├── README.md ├── README_MODRINTH.md ├── BENCHMARKS.md ├── README_CURSEFORGE.md └── gradlew /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "TMRV", 4 | "pack_format": 6 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nolij/TooManyRecipeViewers/HEAD/src/main/resources/icon.png -------------------------------------------------------------------------------- /jei-api/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "JEI API", 4 | "pack_format": 6 5 | } 6 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nolij/TooManyRecipeViewers/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | - further improvements to overall system stability and other minor adjustments have been made to enhance the user experience 2 | -------------------------------------------------------------------------------- /jei-api/src/main/java/mezz/jei/common/platform/JEIAPIStub.java: -------------------------------------------------------------------------------- 1 | package mezz.jei.common.platform; 2 | 3 | //? if <21.1 { 4 | /*import net.neoforged.fml.common.Mod; 5 | 6 | @Mod("jei_api") 7 | public class JEIAPIStub { 8 | } 9 | */ -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/util/FluidRendererParameters.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.util; 2 | 3 | public record FluidRendererParameters(long capacity, boolean showCapacity, int width, int height) {} 4 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/forge/JustEnoughItems.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.forge; 2 | 3 | //? if <21.1 { 4 | /*import net.neoforged.fml.common.Mod; 5 | 6 | @Mod("jei") 7 | public class JustEnoughItems { 8 | } 9 | */ -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /jei-api/src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "[1,)" 3 | license = "MIT" 4 | issueTrackerURL = "${issue_url}" 5 | 6 | [[mods]] 7 | modId = "jei_api" 8 | version = "${jei_version}" 9 | displayName = "JEI API Stub" 10 | authors = "JEI Authors" 11 | description = "JEI API" -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/util/IItemStackish.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.util; 2 | 3 | import net.minecraft.world.item.Item; 4 | 5 | public interface IItemStackish extends IStackish { 6 | 7 | Item tmrv$getItem(); 8 | 9 | @Override 10 | default long tmrv$getAmount() { 11 | return 1; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/util/IFluidStackish.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.util; 2 | 3 | import net.minecraft.world.level.material.Fluid; 4 | 5 | public interface IFluidStackish extends IStackish { 6 | 7 | Fluid tmrv$getFluid(); 8 | 9 | @Override 10 | default long tmrv$getAmount() { 11 | return 1000L; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/PacketRecipeTransferAccessor.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | import mezz.jei.common.network.packets.PacketRecipeTransfer; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(PacketRecipeTransfer.class) 8 | public interface PacketRecipeTransferAccessor { 9 | @Accessor("maxTransfer") 10 | boolean tmrv$isMaxTransfer(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/util/IStackish.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.util; 2 | 3 | import net.minecraft.core.component.DataComponentPatch; 4 | 5 | public interface IStackish { 6 | 7 | default /*? if >=21.1 {*/ DataComponentPatch /*?} else {*/ /*CompoundTag *//*?}*/ tmrv$getDataComponentPatch() { 8 | return /*? if >=21.1 {*/ DataComponentPatch.EMPTY /*?} else {*/ /*null *//*?}*/; 9 | } 10 | 11 | long tmrv$getAmount(); 12 | 13 | T tmrv$normalize(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /jei-api/src/main/java/mezz/jei/common/platform/Services.java: -------------------------------------------------------------------------------- 1 | package mezz.jei.common.platform; 2 | 3 | import mezz.jei.neoforge.platform.PlatformHelper; 4 | 5 | public final class Services { 6 | 7 | public static final IPlatformHelper PLATFORM = new PlatformHelper(); 8 | 9 | public static T load(Class serviceClass) { 10 | if (serviceClass == IPlatformHelper.class) 11 | //noinspection unchecked 12 | return (T) PLATFORM; 13 | 14 | throw new UnsupportedOperationException(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /versions/21.1-neoforge/gradle.properties: -------------------------------------------------------------------------------- 1 | # https://projects.neoforged.net/neoforged/neoforge 2 | java_version = VERSION_21 3 | minecraft_version = 21.1 4 | minecraft_range = 1.21.1 5 | mod_loader = NEOFORGE 6 | mod_loader_version = 215 7 | mod_loader_range = [21.1.0-,21.2) 8 | parchment_version = 2024.11.17 9 | 10 | # https://maven.terraformersmc.com/releases/dev/emi/emi-neoforge 11 | emi_version = 1.1.22+1.21.1 12 | # https://maven.blamejared.com/mezz/jei/jei-1.21.1-neoforge/maven-metadata.xml 13 | jei_version = 19.25.1.328 -------------------------------------------------------------------------------- /versions/20.1-lexforge/gradle.properties: -------------------------------------------------------------------------------- 1 | # https://files.minecraftforge.net/net/minecraftforge/forge/index_1.20.1.html 2 | java_version = VERSION_17 3 | minecraft_version = 20.1 4 | minecraft_range = 1.20.1 5 | mod_loader = LEXFORGE 6 | mod_loader_version = 47.4.11 7 | mod_loader_range = [47.3.39,48) 8 | parchment_version = 2023.09.03 9 | 10 | # https://maven.terraformersmc.com/releases/dev/emi/emi-forge 11 | emi_version = 1.1.22+1.20.1 12 | # https://maven.blamejared.com/mezz/jei/jei-1.20.1-forge/maven-metadata.xml 13 | jei_version = 15.20.0.122 -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/library/config/ModIDFormatConfig.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.library.config; 2 | 3 | import mezz.jei.library.config.IModIdFormatConfig; 4 | import mezz.jei.library.config.ModIdFormatConfig; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public class ModIDFormatConfig implements IModIdFormatConfig { 8 | 9 | @Override 10 | public @NotNull String getModNameFormat() { 11 | return ModIdFormatConfig.MOD_NAME_FORMAT_CODE; 12 | } 13 | 14 | @Override 15 | public boolean isModNameFormatOverrideActive() { 16 | return false; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/runtime/config/JEIConfigManager.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime.config; 2 | 3 | import mezz.jei.api.runtime.config.IJeiConfigFile; 4 | import mezz.jei.api.runtime.config.IJeiConfigManager; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Unmodifiable; 7 | 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | public class JEIConfigManager implements IJeiConfigManager { 12 | 13 | @Override 14 | public @Unmodifiable @NotNull Collection getConfigFiles() { 15 | return List.of(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /jei-api/src/main/java/mezz/jei/common/config/DebugConfig.java: -------------------------------------------------------------------------------- 1 | package mezz.jei.common.config; 2 | 3 | public final class DebugConfig { 4 | 5 | public static boolean isDebugModeEnabled() { 6 | return false; 7 | } 8 | 9 | public static boolean isDebugGuisEnabled() { 10 | return false; 11 | } 12 | 13 | public static boolean isDebugInputsEnabled() { 14 | return false; 15 | } 16 | 17 | public static boolean isDebugInfoTooltipsEnabled() { 18 | return false; 19 | } 20 | 21 | public static boolean isCrashingTestIngredientsEnabled() { 22 | return false; 23 | } 24 | 25 | public static boolean isLogSuffixTreeStatsEnabled() { 26 | return false; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/EmiAgnosMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 5 | import dev.emi.emi.platform.EmiAgnos; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | 8 | @Mixin(value = EmiAgnos.class, remap = false) 9 | public class EmiAgnosMixin { 10 | 11 | @WrapMethod(method = "isModLoaded") 12 | private static boolean tmrv$isModLoaded(String id, Operation original) { 13 | if (id.equals("jei")) 14 | return false; // suppress JEMI 15 | 16 | return original.call(id); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/SmithingRecipeCategoryAccessor.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | import mezz.jei.api.recipe.category.extensions.vanilla.smithing.ISmithingCategoryExtension; 4 | import mezz.jei.library.plugins.vanilla.anvil.SmithingRecipeCategory; 5 | import net.minecraft.world.item.crafting.SmithingRecipe; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Invoker; 8 | 9 | @Mixin(value = SmithingRecipeCategory.class, remap = false) 10 | public interface SmithingRecipeCategoryAccessor { 11 | 12 | @Invoker("getExtension") ISmithingCategoryExtension tmrv$getExtension(SmithingRecipe recipe); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/toomanyrecipeviewers.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "dev.nolij.toomanyrecipeviewers.mixin", 5 | "compatibilityLevel": "JAVA_${mixin_java_version}", 6 | "client": [ 7 | "EmiAgnosMixin", 8 | "EmiReloadManager_ReloadWorkerMixin", 9 | "FluidEmiStackMixin", 10 | "FullTypedItemStackMixin", 11 | "ItemEmiStackMixin", 12 | "JemiStackMixin", 13 | "JemiUtilMixin", 14 | "NormalizedTypedItemMixin", 15 | "NormalizedTypedItemStackMixin", 16 | "PacketRecipeTransferAccessor", 17 | "SmithingRecipeCategoryAccessor", 18 | "TypedIngredientMixin", 19 | "TypedItemStackMixin", 20 | "VanillaPluginMixin" 21 | ], 22 | "injectors": { 23 | "defaultRequire": 1 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/gui/drawable/OffsetDrawable.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.drawable; 2 | 3 | import mezz.jei.api.gui.drawable.IDrawable; 4 | import net.minecraft.client.gui.GuiGraphics; 5 | 6 | public record OffsetDrawable(IDrawable drawable, int xOffset, int yOffset) implements IDrawable { 7 | 8 | @Override 9 | public int getWidth() { 10 | return drawable.getWidth(); 11 | } 12 | 13 | @Override 14 | public int getHeight() { 15 | return drawable.getHeight(); 16 | } 17 | 18 | @Override 19 | public void draw(GuiGraphics guiGraphics, int x, int y) { 20 | drawable.draw(guiGraphics, x + xOffset, y + yOffset); 21 | } 22 | 23 | @Override 24 | public void draw(GuiGraphics guiGraphics) { 25 | drawable.draw(guiGraphics, xOffset, yOffset); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/recipe/advanced/RecipeManagerPluginHelper.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.recipe.advanced; 2 | 3 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.recipe.RecipeManager; 4 | import mezz.jei.api.recipe.IFocus; 5 | import mezz.jei.api.recipe.RecipeType; 6 | import mezz.jei.api.recipe.advanced.IRecipeManagerPluginHelper; 7 | 8 | public class RecipeManagerPluginHelper implements IRecipeManagerPluginHelper { 9 | 10 | private final RecipeManager recipeManager; 11 | 12 | public RecipeManagerPluginHelper(RecipeManager recipeManager) { 13 | this.recipeManager = recipeManager; 14 | } 15 | 16 | @Override 17 | public boolean isRecipeCatalyst(RecipeType recipeType, IFocus focus) { 18 | return recipeManager.isRecipeCatalyst(recipeType, focus); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/JemiStackMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | import dev.emi.emi.jemi.JemiStack; 4 | import mezz.jei.api.ingredients.IIngredientType; 5 | import mezz.jei.api.ingredients.ITypedIngredient; 6 | import org.spongepowered.asm.mixin.Final; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | 10 | @SuppressWarnings("NonExtendableApiUsage") 11 | @Mixin(value = JemiStack.class, remap = false) 12 | public class JemiStackMixin implements ITypedIngredient { 13 | 14 | @Shadow @Final private IIngredientType type; 15 | @Shadow @Final public T ingredient; 16 | 17 | @Override 18 | public IIngredientType getType() { 19 | return type; 20 | } 21 | 22 | @Override 23 | public T getIngredient() { 24 | return ingredient; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: pull_request 2 | on: [ pull_request ] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-22.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | - name: Install Packages 10 | run: sudo apt-get install -y advancecomp 11 | - uses: actions/setup-java@v4 12 | with: 13 | distribution: temurin 14 | java-version: 21 15 | - name: Setup Gradle 16 | uses: gradle/actions/setup-gradle@v4 17 | with: 18 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY_PR }} 19 | cache-read-only: true 20 | - name: Build 21 | run: ./gradlew build --no-daemon 22 | - name: Upload artifacts 23 | uses: actions/upload-artifact@v4 24 | with: 25 | name: toomanyrecipeviewers 26 | path: | 27 | **/toomanyrecipeviewers-*.jar 28 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/util/FormattedTextConsumer.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.util; 2 | 3 | import net.minecraft.network.chat.FormattedText; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.Optional; 7 | 8 | public class FormattedTextConsumer implements FormattedText.ContentConsumer { 9 | 10 | public static String fromFormattedText(FormattedText formattedText) { 11 | final var consumer = new FormattedTextConsumer(); 12 | formattedText.visit(consumer); 13 | return consumer.getString(); 14 | } 15 | 16 | private final StringBuilder builder = new StringBuilder(); 17 | 18 | @Override 19 | public @NotNull Optional accept(@NotNull String string) { 20 | builder.append(string); 21 | return Optional.empty(); 22 | } 23 | 24 | public String getString() { 25 | return builder.toString(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/NormalizedTypedItemMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | //? if <21.1 { 4 | /*import net.minecraft.core.Holder; 5 | import net.minecraft.world.item.Item; 6 | import org.spongepowered.asm.mixin.Final; 7 | import org.spongepowered.asm.mixin.Shadow; 8 | *///?} 9 | import dev.nolij.toomanyrecipeviewers.util.IItemStackish; 10 | import mezz.jei.library.ingredients.itemStacks.TypedItemStack; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | 13 | @Mixin(targets = "mezz/jei/library/ingredients/itemStacks/NormalizedTypedItem", remap = false) 14 | public abstract class NormalizedTypedItemMixin implements IItemStackish { 15 | 16 | //? if <21.1 { 17 | /*@Shadow @Final private Holder itemHolder; 18 | 19 | @Override 20 | public Item tmrv$getItem() { 21 | return itemHolder.value(); 22 | } 23 | *///?} 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/widget/FillingFlameWidget.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.widget; 2 | 3 | import dev.emi.emi.EmiRenderHelper; 4 | import dev.emi.emi.api.widget.AnimatedTextureWidget; 5 | import dev.emi.emi.runtime.EmiDrawContext; 6 | import net.minecraft.client.gui.GuiGraphics; 7 | 8 | public class FillingFlameWidget extends AnimatedTextureWidget { 9 | 10 | public FillingFlameWidget(int x, int y, int time) { 11 | super(EmiRenderHelper.WIDGETS, x, y, 14, 14, 68, 14, time, true, false, false); 12 | } 13 | 14 | @Override 15 | public void render(GuiGraphics draw, int mouseX, int mouseY, float delta) { 16 | EmiDrawContext context = EmiDrawContext.wrap(draw); 17 | context.drawTexture(this.texture, x, y, width, height, u, 0, regionWidth, regionHeight, textureWidth, textureHeight); 18 | super.render(context.raw(), mouseX, mouseY, delta); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/EmiReloadManager_ReloadWorkerMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | import dev.emi.emi.registry.EmiPluginContainer; 4 | import dev.nolij.toomanyrecipeviewers.EMIPlugin; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 9 | 10 | @Mixin(targets = "dev/emi/emi/runtime/EmiReloadManager$ReloadWorker", remap = false) 11 | public class EmiReloadManager_ReloadWorkerMixin { 12 | 13 | @Inject(method = "entrypointPriority", at = @At("HEAD"), cancellable = true) 14 | private static void tmrv$entrypointPriority$HEAD(EmiPluginContainer container, CallbackInfoReturnable cir) { 15 | if (container.plugin() instanceof EMIPlugin) 16 | cir.setReturnValue(2); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/release_pre.yml: -------------------------------------------------------------------------------- 1 | name: release_pre 2 | on: [ workflow_dispatch ] 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-22.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 0 11 | fetch-tags: true 12 | - name: Install Packages 13 | run: sudo apt-get install -y advancecomp 14 | - uses: actions/setup-java@v4 15 | with: 16 | distribution: temurin 17 | java-version: 21 18 | - name: Setup Gradle 19 | uses: gradle/actions/setup-gradle@v4 20 | with: 21 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 22 | cache-read-only: true 23 | - name: Build 24 | run: ./gradlew build -Prelease_channel=PRE_RELEASE 25 | - name: Publish 26 | run: ./gradlew taugradle_publish -Prelease_channel=PRE_RELEASE 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_DEV_WEBHOOK }} -------------------------------------------------------------------------------- /.github/workflows/release_rc.yml: -------------------------------------------------------------------------------- 1 | name: release_rc 2 | on: [ workflow_dispatch ] 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-22.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 0 11 | fetch-tags: true 12 | - name: Install Packages 13 | run: sudo apt-get install -y advancecomp 14 | - uses: actions/setup-java@v4 15 | with: 16 | distribution: temurin 17 | java-version: 21 18 | - name: Setup Gradle 19 | uses: gradle/actions/setup-gradle@v4 20 | with: 21 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 22 | cache-read-only: true 23 | - name: Build 24 | run: ./gradlew build -Prelease_channel=RELEASE_CANDIDATE 25 | - name: Publish 26 | run: ./gradlew taugradle_publish -Prelease_channel=RELEASE_CANDIDATE 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_DEV_WEBHOOK }} -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/util/ComponentFormattedCharSink.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.util; 2 | 3 | import net.minecraft.network.chat.Component; 4 | import net.minecraft.network.chat.MutableComponent; 5 | import net.minecraft.network.chat.Style; 6 | import net.minecraft.util.FormattedCharSequence; 7 | import net.minecraft.util.FormattedCharSink; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class ComponentFormattedCharSink implements FormattedCharSink { 11 | 12 | public static MutableComponent fromSequence(FormattedCharSequence sequence) { 13 | final var sink = new ComponentFormattedCharSink(); 14 | sequence.accept(sink); 15 | return sink.component; 16 | } 17 | 18 | public MutableComponent component = Component.empty(); 19 | 20 | @Override 21 | public boolean accept(int width, @NotNull Style style, int codePoint) { 22 | component.append(Component.literal(new String(Character.toChars(codePoint))).withStyle(style)); 23 | return true; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/release_dev.yml: -------------------------------------------------------------------------------- 1 | name: release_dev 2 | on: [ workflow_dispatch ] 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-22.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 0 11 | fetch-tags: true 12 | - name: Install Packages 13 | run: sudo apt-get install -y advancecomp 14 | - uses: actions/setup-java@v4 15 | with: 16 | distribution: temurin 17 | java-version: 21 18 | - name: Setup Gradle 19 | uses: gradle/actions/setup-gradle@v4 20 | with: 21 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 22 | cache-read-only: true 23 | - name: Build 24 | run: ./gradlew build -Prelease_channel=DEV_BUILD --stacktrace 25 | - name: Publish 26 | run: ./gradlew taugradle_publish -Prelease_channel=DEV_BUILD --stacktrace 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_DEV_WEBHOOK }} -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: [ workflow_dispatch ] 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-22.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 0 11 | fetch-tags: true 12 | - name: Install Packages 13 | run: sudo apt-get install -y advancecomp 14 | - uses: actions/setup-java@v4 15 | with: 16 | distribution: temurin 17 | java-version: 21 18 | - name: Setup Gradle 19 | uses: gradle/actions/setup-gradle@v4 20 | with: 21 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 22 | cache-read-only: true 23 | - name: Build 24 | run: ./gradlew build -Prelease_channel=RELEASE 25 | - name: Publish 26 | run: ./gradlew taugradle_publish -Prelease_channel=RELEASE 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} 30 | MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} 31 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/widget/DeferredPlaceableWidget.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.widget; 2 | 3 | import mezz.jei.api.gui.placement.IPlaceable; 4 | 5 | public class DeferredPlaceableWidget implements IPlaceable { 6 | 7 | @FunctionalInterface 8 | public interface PlaceableCallback { 9 | void setPosition(int x, int y); 10 | } 11 | 12 | private final PlaceableCallback callback; 13 | private final int width, height; 14 | 15 | private boolean placed = false; 16 | 17 | public DeferredPlaceableWidget(PlaceableCallback callback, int width, int height) { 18 | this.callback = callback; 19 | this.width = width; 20 | this.height = height; 21 | } 22 | 23 | @Override 24 | public DeferredPlaceableWidget setPosition(int x, int y) { 25 | if (placed) 26 | throw new IllegalStateException(); 27 | placed = true; 28 | 29 | callback.setPosition(x, y); 30 | return this; 31 | } 32 | 33 | @Override 34 | public int getWidth() { 35 | return width; 36 | } 37 | 38 | @Override 39 | public int getHeight() { 40 | return height; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/gui/ingredient/ITMRVSlotWidget.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.ingredient; 2 | 3 | import dev.emi.emi.api.widget.SlotWidget; 4 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.drawable.OffsetDrawable; 5 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.builder.TMRVIngredientCollector; 6 | import mezz.jei.api.gui.ingredient.IRecipeSlotRichTooltipCallback; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.util.List; 10 | 11 | @SuppressWarnings("UnusedReturnValue") 12 | public interface ITMRVSlotWidget { 13 | 14 | void setName(@Nullable String name); 15 | 16 | void setBackground(@Nullable OffsetDrawable background); 17 | 18 | void setOverlay(@Nullable OffsetDrawable overlay); 19 | 20 | void addTooltipCallbacks(List tooltipCallbacks); 21 | 22 | void setPosition(int x, int y); 23 | 24 | void setVisible(boolean visible); 25 | 26 | TMRVIngredientCollector getIngredientCollector(); 27 | 28 | SlotWidget drawBack(boolean drawBack); 29 | 30 | SlotWidget large(boolean large); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "[1,)" 3 | license = "OSL-3.0" 4 | issueTrackerURL = "${issue_url}" 5 | 6 | [[mods]] 7 | modId = "toomanyrecipeviewers" 8 | version = "${mod_version}" 9 | displayName = "${mod_name}" 10 | displayURL = "${mod_url}" 11 | logoFile = "icon.png" 12 | authors = "${authors}" 13 | description = """ 14 | ${mod_description} 15 | """ 16 | 17 | [[dependencies.toomanyrecipeviewers]] 18 | modId = "${mod_loader}" 19 | type = "required" 20 | mandatory = true 21 | versionRange = "${mod_loader_range}" 22 | ordering = "NONE" 23 | side = "CLIENT" 24 | 25 | [[dependencies.toomanyrecipeviewers]] 26 | modId = "minecraft" 27 | type = "required" 28 | mandatory = true 29 | versionRange = "${minecraft_range}" 30 | ordering = "NONE" 31 | side = "CLIENT" 32 | 33 | [[dependencies.toomanyrecipeviewers]] 34 | modId = "emi" 35 | type = "required" 36 | mandatory = true 37 | versionRange = "${emi_range}" 38 | ordering = "NONE" 39 | side = "CLIENT" 40 | 41 | [[mods]] 42 | modId = "jei" 43 | version = "${jei_version}" 44 | displayName = "TMRV JEI Stub" 45 | description = "Provides JEI for mods that require JEI" 46 | -------------------------------------------------------------------------------- /src/main/resources/LICENSE_emi: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Emi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /jei-api/src/main/resources/LICENSE_jei: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 mezz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [ push ] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-22.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | 10 | - name: Install Packages 11 | run: sudo apt-get install -y advancecomp 12 | 13 | # cache local gradle files, global ones will be taken care of by the setup-gradle action 14 | - uses: actions/cache@v4 15 | with: 16 | path: | 17 | **/.gradle/ 18 | **/build/ 19 | key: ${{ runner.os }}-gradlelocal-${{ github.ref }} 20 | 21 | - uses: actions/setup-java@v4 22 | with: 23 | distribution: temurin 24 | java-version: 21 25 | 26 | - name: Setup Gradle 27 | uses: gradle/actions/setup-gradle@v4 28 | with: 29 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 30 | 31 | - name: Build 32 | run: ./gradlew build --stacktrace --no-daemon 33 | 34 | - name: Upload artifacts 35 | if: always() 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: toomanyrecipeviewers 39 | path: | 40 | **/build/libs/toomanyrecipeviewers*.jar 41 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/common/config/JEIClientConfigs.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.common.config; 2 | 3 | import mezz.jei.common.config.IClientConfig; 4 | import mezz.jei.common.config.IIngredientFilterConfig; 5 | import mezz.jei.common.config.IIngredientGridConfig; 6 | import mezz.jei.common.config.IJeiClientConfigs; 7 | 8 | public class JEIClientConfigs implements IJeiClientConfigs { 9 | 10 | private final IClientConfig clientConfig = new ClientConfig(); 11 | private final IIngredientFilterConfig ingredientFilterConfig = new IngredientFilterConfig(); 12 | private final IIngredientGridConfig ingredientListConfig = new IngredientGridConfig(); 13 | private final IIngredientGridConfig bookmarkListConfig = new IngredientGridConfig(); 14 | 15 | @Override 16 | public IClientConfig getClientConfig() { 17 | return clientConfig; 18 | } 19 | 20 | @Override 21 | public IIngredientFilterConfig getIngredientFilterConfig() { 22 | return ingredientFilterConfig; 23 | } 24 | 25 | @Override 26 | public IIngredientGridConfig getIngredientListConfig() { 27 | return ingredientListConfig; 28 | } 29 | 30 | @Override 31 | public IIngredientGridConfig getBookmarkListConfig() { 32 | return bookmarkListConfig; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/NormalizedTypedItemStackMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | //? if <21.1 { 4 | /*import net.minecraft.core.Holder; 5 | import net.minecraft.world.item.Item; 6 | import org.jetbrains.annotations.Nullable; 7 | *///?} 8 | import dev.nolij.toomanyrecipeviewers.util.IItemStackish; 9 | import mezz.jei.library.ingredients.itemStacks.TypedItemStack; 10 | import net.minecraft.core.component.DataComponentPatch; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | 15 | @Mixin(targets = "mezz/jei/library/ingredients/itemStacks/NormalizedTypedItemStack", remap = false) 16 | public abstract class NormalizedTypedItemStackMixin implements IItemStackish { 17 | 18 | //? if <21.1 { 19 | /*@Shadow @Final private Holder itemHolder; 20 | 21 | @Override 22 | public Item tmrv$getItem() { 23 | return itemHolder.value(); 24 | } 25 | *///?} 26 | 27 | @Shadow(aliases = "tag") @Final private DataComponentPatch dataComponentPatch; 28 | 29 | @Override 30 | public /*? if >=21.1 {*/ DataComponentPatch /*?} else {*/ /*CompoundTag *//*?}*/ tmrv$getDataComponentPatch() { 31 | return dataComponentPatch; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/util/ResourceLocationHolderComparator.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.util; 2 | 3 | import net.minecraft.resources.ResourceLocation; 4 | 5 | import java.util.Comparator; 6 | import java.util.function.Function; 7 | 8 | public abstract class ResourceLocationHolderComparator implements Comparator { 9 | 10 | public static ResourceLocationHolderComparator create(Function getter) { 11 | return new ResourceLocationHolderComparator<>() { 12 | @Override 13 | public ResourceLocation getResourceLocation(T obj) { 14 | return getter.apply(obj); 15 | } 16 | }; 17 | } 18 | 19 | public abstract ResourceLocation getResourceLocation(T t); 20 | 21 | private static boolean isVanillaNamespace(ResourceLocation location) { 22 | return location.getNamespace().equals("minecraft"); 23 | } 24 | 25 | @Override 26 | public int compare(T _left, T _right) { 27 | final var left = getResourceLocation(_left); 28 | final var right = getResourceLocation(_right); 29 | final var leftIsVanilla = isVanillaNamespace(left); 30 | final var rightIsVanilla = isVanillaNamespace(right); 31 | if (leftIsVanilla ^ rightIsVanilla) { 32 | if (leftIsVanilla) 33 | return -1; 34 | 35 | return 1; 36 | } 37 | 38 | return left.compareNamespaced(right); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/widget/DrawableWidget.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.widget; 2 | 3 | import dev.emi.emi.api.widget.Bounds; 4 | import dev.emi.emi.api.widget.Widget; 5 | import mezz.jei.api.gui.drawable.IDrawable; 6 | import mezz.jei.api.gui.placement.IPlaceable; 7 | import net.minecraft.client.gui.GuiGraphics; 8 | 9 | public class DrawableWidget extends Widget implements IPlaceable { 10 | 11 | private final IDrawable drawable; 12 | private int x; 13 | private int y; 14 | 15 | public DrawableWidget(IDrawable drawable, int x, int y) { 16 | this.drawable = drawable; 17 | this.x = x; 18 | this.y = y; 19 | } 20 | 21 | public DrawableWidget(IDrawable drawable) { 22 | this(drawable, 0, 0); 23 | } 24 | 25 | @Override 26 | public DrawableWidget setPosition(int x, int y) { 27 | this.x = x; 28 | this.y = y; 29 | return this; 30 | } 31 | 32 | @Override 33 | public int getWidth() { 34 | return drawable.getWidth(); 35 | } 36 | 37 | @Override 38 | public int getHeight() { 39 | return drawable.getHeight(); 40 | } 41 | 42 | @Override 43 | public Bounds getBounds() { 44 | return new Bounds(x, y, getWidth(), getHeight()); 45 | } 46 | 47 | @Override 48 | public void render(GuiGraphics draw, int mouseX, int mouseY, float delta) { 49 | drawable.draw(draw, x, y); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/FullTypedItemStackMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | //? if <21.1 { 4 | /*import net.minecraft.world.item.Item; 5 | import net.minecraft.core.Holder; 6 | import org.jetbrains.annotations.Nullable; 7 | *///?} 8 | import dev.nolij.toomanyrecipeviewers.util.IItemStackish; 9 | import mezz.jei.library.ingredients.itemStacks.TypedItemStack; 10 | import net.minecraft.core.component.DataComponentPatch; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | 15 | @Mixin(targets = "mezz/jei/library/ingredients/itemStacks/FullTypedItemStack", remap = false) 16 | public abstract class FullTypedItemStackMixin implements IItemStackish { 17 | 18 | //? if <21.1 { 19 | /*@Shadow @Final private Holder itemHolder; 20 | 21 | @Override 22 | public Item tmrv$getItem() { 23 | return itemHolder.value(); 24 | } 25 | *///?} 26 | 27 | @Shadow(aliases = "tag") @Final private DataComponentPatch dataComponentPatch; 28 | 29 | @Shadow @Final private int count; 30 | 31 | @Override 32 | public /*? if >=21.1 {*/ DataComponentPatch /*?} else {*/ /*CompoundTag *//*?}*/ tmrv$getDataComponentPatch() { 33 | return dataComponentPatch; 34 | } 35 | 36 | @Override 37 | public long tmrv$getAmount() { 38 | return count; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/common/config/IngredientGridConfig.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.common.config; 2 | 3 | //? if >=21.1 { 4 | import mezz.jei.api.gui.placement.HorizontalAlignment; 5 | import mezz.jei.api.gui.placement.VerticalAlignment; 6 | //?} else { 7 | /*import mezz.jei.common.util.HorizontalAlignment; 8 | import mezz.jei.common.util.VerticalAlignment; 9 | *///?} 10 | import mezz.jei.common.config.IIngredientGridConfig; 11 | import mezz.jei.common.util.NavigationVisibility; 12 | 13 | public class IngredientGridConfig implements IIngredientGridConfig { 14 | 15 | @Override 16 | public int getMaxColumns() { 17 | return 0; 18 | } 19 | 20 | @Override 21 | public int getMinColumns() { 22 | return 0; 23 | } 24 | 25 | @Override 26 | public int getMaxRows() { 27 | return 0; 28 | } 29 | 30 | @Override 31 | public int getMinRows() { 32 | return 0; 33 | } 34 | 35 | @Override 36 | public boolean drawBackground() { 37 | return false; 38 | } 39 | 40 | @Override 41 | public HorizontalAlignment getHorizontalAlignment() { 42 | return HorizontalAlignment.RIGHT; 43 | } 44 | 45 | @Override 46 | public VerticalAlignment getVerticalAlignment() { 47 | return VerticalAlignment.TOP; 48 | } 49 | 50 | @Override 51 | public NavigationVisibility getButtonNavigationVisibility() { 52 | return NavigationVisibility.DISABLED; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/common/config/IngredientFilterConfig.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.common.config; 2 | 3 | import mezz.jei.common.config.IIngredientFilterConfig; 4 | import mezz.jei.core.search.SearchMode; 5 | 6 | public class IngredientFilterConfig implements IIngredientFilterConfig { 7 | 8 | @Override 9 | public SearchMode getModNameSearchMode() { 10 | return SearchMode.DISABLED; 11 | } 12 | 13 | @Override 14 | public SearchMode getTooltipSearchMode() { 15 | return SearchMode.DISABLED; 16 | } 17 | 18 | @Override 19 | public SearchMode getTagSearchMode() { 20 | return SearchMode.DISABLED; 21 | } 22 | 23 | @Override 24 | public SearchMode getColorSearchMode() { 25 | return SearchMode.DISABLED; 26 | } 27 | 28 | @Override 29 | public SearchMode getResourceLocationSearchMode() { 30 | return SearchMode.DISABLED; 31 | } 32 | 33 | @Override 34 | public SearchMode getCreativeTabSearchMode() { 35 | return SearchMode.DISABLED; 36 | } 37 | 38 | @Override 39 | public boolean getSearchAdvancedTooltips() { 40 | return false; 41 | } 42 | 43 | @Override 44 | public boolean getSearchModIds() { 45 | return false; 46 | } 47 | 48 | //? if >=21.1 { 49 | @Override 50 | public boolean getSearchModAliases() { 51 | return false; 52 | } 53 | //?} 54 | 55 | @Override 56 | public boolean getSearchIngredientAliases() { 57 | return false; 58 | } 59 | 60 | @Override 61 | public boolean getSearchShortModNames() { 62 | return false; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/VanillaPluginMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | import com.llamalad7.mixinextras.sugar.Local; 4 | import dev.emi.emi.VanillaPlugin; 5 | import dev.emi.emi.api.EmiRegistry; 6 | import dev.emi.emi.api.recipe.EmiRecipe; 7 | import net.minecraft.world.item.crafting.CraftingRecipe; 8 | import net.minecraft.world.item.crafting.Recipe; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | import java.util.function.Supplier; 15 | 16 | import static dev.nolij.toomanyrecipeviewers.TooManyRecipeViewers.runtime; 17 | 18 | @Mixin(value = VanillaPlugin.class, remap = false) 19 | public class VanillaPluginMixin { 20 | 21 | @Inject(method = "addRecipeSafe(Ldev/emi/emi/api/EmiRegistry;Ljava/util/function/Supplier;Lnet/minecraft/world/item/crafting/Recipe;)V", at = @At("HEAD")) 22 | private static void tmrv$addRecipeSafe$HEAD(EmiRegistry registry, Supplier supplier, Recipe recipe, CallbackInfo ci) { 23 | runtime.ignoredRecipes.add(recipe); 24 | } 25 | 26 | @Inject(method = "register", at = @At(value = "INVOKE", target = "Ldev/emi/emi/VanillaPlugin;addRecipeSafe(Ldev/emi/emi/api/EmiRegistry;Ljava/util/function/Supplier;)V", ordinal = 0)) 27 | public void tmrv$register$addRecipeSafe(EmiRegistry registry, CallbackInfo ci, @Local CraftingRecipe recipe) { 28 | runtime.ignoredRecipes.add(recipe); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal { 4 | content { 5 | excludeGroup("org.apache.logging.log4j") 6 | } 7 | } 8 | mavenCentral() 9 | maven("https://maven.taumc.org/releases") 10 | maven("https://maven.kikugie.dev/releases") 11 | maven("https://maven.kikugie.dev/snapshots") 12 | maven("https://maven.wagyourtail.xyz/releases") 13 | maven("https://maven.wagyourtail.xyz/snapshots") 14 | mavenLocal() 15 | } 16 | 17 | plugins { 18 | operator fun String.invoke(): String = extra[this] as? String ?: error("Property $this not found") 19 | 20 | id("org.gradle.toolchains.foojay-resolver-convention") version("foojay_resolver_convention_version"()) 21 | id("dev.kikugie.stonecutter") version("stonecutter_version"()) 22 | id("com.gradleup.shadow") version("shadow_version"()) 23 | id("xyz.wagyourtail.unimined") version("unimined_version"()) 24 | id("xyz.wagyourtail.jvmdowngrader") version("jvmdg_version"()) 25 | id("com.github.gmazzo.buildconfig") version("buildconfig_version"()) 26 | id("org.taumc.gradle.versioning") version("taugradle_version"()) 27 | id("org.taumc.gradle.compression") version("taugradle_version"()) 28 | id("org.taumc.gradle.publishing") version("taugradle_version"()) 29 | } 30 | } 31 | 32 | plugins { 33 | id("org.gradle.toolchains.foojay-resolver-convention") 34 | id("dev.kikugie.stonecutter") 35 | } 36 | 37 | rootProject.name = "toomanyrecipeviewers" 38 | 39 | include(":jei-api") 40 | 41 | stonecutter.create(rootProject) { 42 | version("21.1-neoforge", "21.1") 43 | version("20.1-lexforge", "20.1") 44 | branch("jei-api") 45 | } -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/TypedItemStackMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | //? if >=21.1 { 4 | import net.minecraft.world.item.Item; 5 | //?} 6 | import dev.nolij.toomanyrecipeviewers.util.IItemStackish; 7 | import mezz.jei.library.ingredients.itemStacks.TypedItemStack; 8 | import mezz.jei.api.ingredients.ITypedIngredient; 9 | import net.minecraft.world.item.ItemStack; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 15 | 16 | @Mixin(value = TypedItemStack.class, remap = false) 17 | public abstract class TypedItemStackMixin implements IItemStackish { 18 | 19 | //? if >=21.1 { 20 | @Shadow protected abstract Item getItem(); 21 | 22 | @Override 23 | public Item tmrv$getItem() { 24 | return getItem(); 25 | } 26 | //?} 27 | 28 | @Shadow protected abstract TypedItemStack getNormalized(); 29 | 30 | @Inject(method = "normalize", at = @At("HEAD"), cancellable = true) 31 | private static void tmrv$normalize$HEAD(ITypedIngredient typedIngredient, CallbackInfoReturnable> cir) { 32 | if (typedIngredient instanceof IItemStackish itemStackish) { 33 | //noinspection unchecked 34 | cir.setReturnValue((ITypedIngredient) itemStackish.tmrv$normalize()); 35 | } 36 | } 37 | 38 | @Override 39 | public TypedItemStack tmrv$normalize() { 40 | return getNormalized(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/widget/ButtonWidget.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.widget; 2 | 3 | import dev.emi.emi.api.widget.Bounds; 4 | import dev.emi.emi.api.widget.Widget; 5 | import mezz.jei.common.Internal; 6 | import mezz.jei.common.util.ImmutableRect2i; 7 | import net.minecraft.client.gui.GuiGraphics; 8 | 9 | public class ButtonWidget extends Widget { 10 | 11 | private ImmutableRect2i rect; 12 | private boolean enabled; 13 | private final Runnable pressedAction; 14 | 15 | public ButtonWidget(ImmutableRect2i rect, boolean enabled, Runnable pressedAction) { 16 | this.rect = rect; 17 | this.enabled = enabled; 18 | this.pressedAction = pressedAction; 19 | } 20 | 21 | public void setRect(ImmutableRect2i rect) { 22 | this.rect = rect; 23 | } 24 | 25 | public void setPosition(int x, int y) { 26 | setRect(rect.setPosition(x, y)); 27 | } 28 | 29 | public void setEnabled(boolean enabled) { 30 | this.enabled = enabled; 31 | } 32 | 33 | @Override 34 | public Bounds getBounds() { 35 | return new Bounds(rect.x(), rect.y(), rect.width(), rect.height()); 36 | } 37 | 38 | @Override 39 | public void render(GuiGraphics draw, int mouseX, int mouseY, float delta) { 40 | final var hovering = rect.contains(mouseX, mouseY); 41 | final var texture = Internal.getTextures().getButtonForState(false, enabled, hovering); 42 | 43 | texture.draw(draw, rect); 44 | } 45 | 46 | @Override 47 | public boolean mouseClicked(int mouseX, int mouseY, int button) { 48 | if (enabled && button == 0 && rect.contains(mouseX, mouseY)) { 49 | pressedAction.run(); 50 | 51 | return true; 52 | } 53 | 54 | return false; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /.github/workflows/dco.yml: -------------------------------------------------------------------------------- 1 | name: "DCO Assistant" 2 | on: 3 | issue_comment: 4 | types: [created] 5 | pull_request_target: 6 | types: [opened, closed, synchronize] 7 | 8 | permissions: 9 | actions: write 10 | contents: write 11 | pull-requests: write 12 | statuses: write 13 | 14 | jobs: 15 | DCO: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: "DCO Assistant" 19 | if: ( 20 | github.event.comment.body == 'recheck' || 21 | github.event.comment.body == 'I have read and hereby affirm the entire contents of the Developer Certificate of Origin.' 22 | ) || github.event_name == 'pull_request_target' 23 | uses: cla-assistant/github-action@v2.3.0 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | path-to-signatures: "signatures.json" 28 | path-to-document: "https://github.com/Nolij/TooManyRecipeViewers/blob/dco/DCO.txt" 29 | branch: "dco" 30 | create-file-commit-message: "Create DCO signature list" 31 | signed-commit-message: "Add $contributorName to the DCO signature list" 32 | custom-notsigned-prcomment: 33 | "Before we can merge your submission, we require that you read and affirm the contents of the 34 | [Developer Certificate of Origin](https://github.com/Nolij/TooManyRecipeViewers/blob/dco/DCO.txt) by adding a comment containing the below text. 35 | Otherwise, please close this PR." 36 | custom-pr-sign-comment: "I have read and hereby affirm the entire contents of the Developer Certificate of Origin." 37 | custom-allsigned-prcomment: "All contributors have read and affirmed the entire contents of the Developer Certificate of Origin." 38 | use-dco-flag: true 39 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle 2 | org.gradle.jvmargs = -Xmx4G -XX:+EnableDynamicAgentLoading 3 | org.gradle.daemon = true 4 | org.gradle.parallel = true 5 | org.gradle.caching = true 6 | org.gradle.configuration-cache = false 7 | 8 | # Mod Properties 9 | maven_group = dev.nolij 10 | mod_id = toomanyrecipeviewers 11 | mod_version = 0.7 12 | mod_name = TooManyRecipeViewers 13 | mod_description = A compatibility layer for running JEI plugins with EMI written by Nolij 14 | authors = Nolij (@xdMatthewbx#1337) & the Craftoria team 15 | mod_url = https://legacy.curseforge.com/minecraft/mc-mods/tmrv 16 | repo_url = https://github.com/Nolij/TooManyRecipeViewers 17 | issue_url = https://github.com/Nolij/TooManyRecipeViewers/issues 18 | 19 | emi_range = [1.1.20,) 20 | 21 | # Dependencies 22 | # https://github.com/Nolij/LibNolij/releases/latest 23 | libnolij_version = 0.5.0 24 | # https://central.sonatype.com/artifact/org.ow2.asm/asm-tree 25 | asm_version = 9.9 26 | # https://central.sonatype.com/artifact/org.jetbrains/annotations 27 | jetbrains_annotations_version = 26.0.2-1 28 | # https://github.com/manifold-systems/manifold/tags 29 | manifold_version = 2025.1.27 30 | # https://github.com/LlamaLad7/MixinExtras/releases 31 | mixinextras_version = 0.5.0 32 | 33 | # Gradle Plugins 34 | # https://plugins.gradle.org/plugin/org.gradle.toolchains.foojay-resolver-convention 35 | foojay_resolver_convention_version = 1.0.0 36 | # https://maven.kikugie.dev/#/releases/dev/kikugie/stonecutter 37 | stonecutter_version = 0.7.11 38 | # https://git.taumc.org/TauMC/TauGradle/releases/latest 39 | taugradle_version = 0.3.38 40 | 41 | # https://github.com/GradleUp/shadow/blob/main/gradle/libs.versions.toml 42 | shadow_ant_version = 1.10.15 43 | # https://plugins.gradle.org/plugin/com.gradleup.shadow 44 | shadow_version = 8.3.7 45 | # https://github.com/unimined/unimined/releases/latest 46 | unimined_version = 1.3.15 47 | # https://plugins.gradle.org/plugin/com.github.gmazzo.buildconfig 48 | buildconfig_version = 5.6.7 49 | # https://github.com/unimined/JvmDowngrader/releases/latest 50 | jvmdg_version = 1.3.3 -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/gui/ingredient/ITMRVRecipeSlotDrawable.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.ingredient; 2 | 3 | import mezz.jei.api.gui.builder.ITooltipBuilder; 4 | import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable; 5 | import net.minecraft.client.gui.GuiGraphics; 6 | import net.minecraft.client.renderer.Rect2i; 7 | import net.minecraft.network.chat.Component; 8 | 9 | import java.util.List; 10 | 11 | @SuppressWarnings("NonExtendableApiUsage") 12 | public interface ITMRVRecipeSlotDrawable extends IRecipeSlotDrawable { 13 | 14 | @Override 15 | default void draw(GuiGraphics guiGraphics) { 16 | throw new UnsupportedOperationException(); 17 | } 18 | 19 | @Override 20 | default void drawHoverOverlays(GuiGraphics guiGraphics) { 21 | throw new UnsupportedOperationException(); 22 | } 23 | 24 | //? if >=21.1 25 | @SuppressWarnings("removal") 26 | @Override 27 | default List getTooltip() { 28 | // TODO 29 | throw new UnsupportedOperationException(); 30 | } 31 | 32 | //? if >=21.1 33 | @SuppressWarnings("removal") 34 | @Override 35 | default void getTooltip(ITooltipBuilder tooltipBuilder) { 36 | // TODO 37 | throw new UnsupportedOperationException(); 38 | } 39 | 40 | //? if >=21.1 { 41 | @Override 42 | default void drawTooltip(GuiGraphics guiGraphics, int i, int i1) { 43 | // TODO 44 | throw new UnsupportedOperationException(); 45 | } 46 | //?} 47 | 48 | @SuppressWarnings("removal") 49 | @Override 50 | default boolean isMouseOver(double x, double y) { 51 | return getRect().contains((int) x, (int) y); 52 | } 53 | 54 | @SuppressWarnings("removal") 55 | @Override 56 | default void addTooltipCallback(mezz.jei.api.gui.ingredient.IRecipeSlotTooltipCallback tooltipCallback) { 57 | // TODO 58 | throw new UnsupportedOperationException(); 59 | } 60 | 61 | @SuppressWarnings("removal") 62 | @Override 63 | default Rect2i getAreaIncludingBackground() { 64 | return getRect(); 65 | } 66 | 67 | @Override 68 | default void drawHighlight(GuiGraphics guiGraphics, int colour) { 69 | // TODO 70 | throw new UnsupportedOperationException(); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/TypedIngredientMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | //? if <21.1 { 4 | /*import java.util.Optional; 5 | *///?} 6 | import dev.nolij.toomanyrecipeviewers.impl.ingredient.ErrorIngredient; 7 | import dev.nolij.toomanyrecipeviewers.util.IStackish; 8 | import mezz.jei.api.ingredients.ITypedIngredient; 9 | import mezz.jei.api.runtime.IIngredientManager; 10 | import mezz.jei.library.ingredients.TypedIngredient; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 15 | 16 | @Mixin(value = TypedIngredient.class, remap = false) 17 | public class TypedIngredientMixin { 18 | 19 | // somehow increases memory usage?? 20 | // @Inject(method = "normalize", at = @At("HEAD"), cancellable = true) 21 | // private static void tmrv$normalize$HEAD(ITypedIngredient typedIngredient, IIngredientHelper ingredientHelper, CallbackInfoReturnable> cir) { 22 | // if (typedIngredient instanceof IStackish stackish) { 23 | // //noinspection unchecked 24 | // cir.setReturnValue((ITypedIngredient) stackish.tmrv$normalize()); 25 | // } 26 | // } 27 | 28 | //? if >=21.1 { 29 | @Inject(method = "defensivelyCopyTypedIngredientFromApi", at = @At("HEAD"), cancellable = true) 30 | private static void tmrv$defensivelyCopyTypedIngredientFromApi$HEAD(IIngredientManager ingredientManager, ITypedIngredient value, CallbackInfoReturnable> cir) { 31 | if (value instanceof IStackish || value == ErrorIngredient.TYPED_INSTANCE) { 32 | cir.setReturnValue(value); 33 | } 34 | } 35 | //?} else { 36 | /*@Inject(method = "deepCopy", at = @At("HEAD"), cancellable = true) 37 | private static void tmrv$deepCopy$HEAD(IIngredientManager ingredientManager, ITypedIngredient value, CallbackInfoReturnable>> cir) { 38 | if (value instanceof IStackish || value == ErrorIngredient.TYPED_INSTANCE) { 39 | cir.setReturnValue(Optional.of(value)); 40 | } 41 | } 42 | *///?} 43 | 44 | } 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | 117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 118 | !gradle-wrapper.jar 119 | -------------------------------------------------------------------------------- /jei-api/src/main/java/mezz/jei/library/config/ColorNameConfig.java: -------------------------------------------------------------------------------- 1 | package mezz.jei.library.config; 2 | 3 | import mezz.jei.library.color.ColorName; 4 | import mezz.jei.library.color.ColorUtil; 5 | 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.function.Supplier; 9 | 10 | public final class ColorNameConfig { 11 | 12 | private static final List defaultColors = List.of( 13 | new ColorName("White", 0xEEEEEE), 14 | new ColorName("LightBlue", 0x7492cc), 15 | new ColorName("Cyan", 0x00EEEE), 16 | new ColorName("Blue", 0x2222dd), 17 | new ColorName("LapisBlue", 0x25418b), 18 | new ColorName("Teal", 0x008080), 19 | new ColorName("Yellow", 0xcacb58), 20 | new ColorName("GoldenYellow", 0xEED700), 21 | new ColorName("Orange", 0xd97634), 22 | new ColorName("Pink", 0xD1899D), 23 | new ColorName("HotPink", 0xFC0FC0), 24 | new ColorName("Magenta", 0xb24bbb), 25 | new ColorName("Purple", 0x813eb9), 26 | new ColorName("EvilPurple", 0x2e1649), 27 | new ColorName("Lavender", 0xB57EDC), 28 | new ColorName("Indigo", 0x480082), 29 | new ColorName("Sand", 0xdbd3a0), 30 | new ColorName("Tan", 0xbb9b63), 31 | new ColorName("LightBrown", 0xA0522D), 32 | new ColorName("Brown", 0x634b33), 33 | new ColorName("DarkBrown", 0x3a2d13), 34 | new ColorName("LimeGreen", 0x43b239), 35 | new ColorName("SlimeGreen", 0x83cb73), 36 | new ColorName("Green", 0x008000), 37 | new ColorName("DarkGreen", 0x224d22), 38 | new ColorName("GrassGreen", 0x548049), 39 | new ColorName("Red", 0x963430), 40 | new ColorName("BrickRed", 0xb0604b), 41 | new ColorName("NetherBrick", 0x2a1516), 42 | new ColorName("Redstone", 0xce3e36), 43 | new ColorName("Black", 0x181515), 44 | new ColorName("CharcoalGray", 0x464646), 45 | new ColorName("IronGray", 0x646464), 46 | new ColorName("Gray", 0x808080), 47 | new ColorName("Silver", 0xC0C0C0) 48 | ); 49 | 50 | private final Supplier> searchColors; 51 | 52 | public ColorNameConfig() { 53 | searchColors = () -> defaultColors; 54 | } 55 | 56 | public String getClosestColorName(int color) { 57 | List colorNames = searchColors.get(); 58 | if (colorNames.isEmpty()) { 59 | colorNames = defaultColors; 60 | } 61 | return colorNames 62 | .stream() 63 | .min(Comparator.comparing(entry -> { 64 | int namedColor = entry.color(); 65 | double distance = ColorUtil.slowPerceptualColorDistanceSquared(namedColor, color); 66 | return Math.abs(distance); 67 | })) 68 | .map(ColorName::name) 69 | .orElseThrow(); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/ItemEmiStackMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | //? if >=21.1 4 | import mezz.jei.api.ingredients.IIngredientTypeWithSubtypes; 5 | import dev.emi.emi.api.stack.EmiStack; 6 | import dev.emi.emi.api.stack.ItemEmiStack; 7 | import dev.nolij.toomanyrecipeviewers.util.IItemStackish; 8 | import mezz.jei.api.constants.VanillaTypes; 9 | import mezz.jei.api.ingredients.IIngredientType; 10 | import mezz.jei.api.ingredients.ITypedIngredient; 11 | import net.minecraft.core.component.DataComponentPatch; 12 | import net.minecraft.world.item.Item; 13 | import net.minecraft.world.item.ItemStack; 14 | import org.spongepowered.asm.mixin.Final; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.Shadow; 17 | 18 | import java.util.Optional; 19 | 20 | @SuppressWarnings({"UnstableApiUsage", "NonExtendableApiUsage"}) 21 | @Mixin(value = ItemEmiStack.class, remap = false) 22 | public abstract class ItemEmiStackMixin implements ITypedIngredient, IItemStackish { 23 | 24 | @Shadow @Final private Item item; 25 | 26 | //? if >=21.1 { 27 | @Shadow @Final private DataComponentPatch componentChanges; 28 | //?} else 29 | /*@Shadow @Final private CompoundTag nbt;*/ 30 | 31 | @Shadow public abstract EmiStack copy(); 32 | 33 | @Override 34 | public IIngredientType getType() { 35 | return VanillaTypes.ITEM_STACK; 36 | } 37 | 38 | @Override 39 | public ItemStack getIngredient() { 40 | return ((ItemEmiStack) (Object) this).getItemStack(); 41 | } 42 | 43 | //? if >=21.1 { 44 | @Override 45 | public B getBaseIngredient(IIngredientTypeWithSubtypes ingredientType) { 46 | if (ingredientType != VanillaTypes.ITEM_STACK) 47 | return ITypedIngredient.super.getBaseIngredient(ingredientType); 48 | 49 | //noinspection unchecked 50 | return (B) item; 51 | } 52 | //?} 53 | 54 | @Override 55 | public Optional getItemStack() { 56 | return Optional.of(getIngredient()); 57 | } 58 | 59 | @Override 60 | public Item tmrv$getItem() { 61 | return item; 62 | } 63 | 64 | @Override 65 | public /*? if >=21.1 {*/ DataComponentPatch /*?} else {*/ /*CompoundTag *//*?}*/ tmrv$getDataComponentPatch() { 66 | //? if >=21.1 { 67 | return componentChanges; 68 | //?} else 69 | /*return nbt;*/ 70 | } 71 | 72 | @Override 73 | public long tmrv$getAmount() { 74 | return ((ItemEmiStack) (Object) this).getAmount(); 75 | } 76 | 77 | @Override 78 | public EmiStack tmrv$normalize() { 79 | return copy().setAmount(1); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/ingredient/ErrorIngredient.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.ingredient; 2 | 3 | import mezz.jei.api.ingredients.IIngredientHelper; 4 | import mezz.jei.api.ingredients.IIngredientRenderer; 5 | import mezz.jei.api.ingredients.IIngredientType; 6 | import mezz.jei.api.ingredients.ITypedIngredient; 7 | import mezz.jei.api.ingredients.subtypes.UidContext; 8 | import mezz.jei.library.ingredients.TypedIngredient; 9 | import net.minecraft.client.gui.GuiGraphics; 10 | import net.minecraft.network.chat.Component; 11 | import net.minecraft.resources.ResourceLocation; 12 | import net.minecraft.world.item.TooltipFlag; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | import java.util.List; 16 | 17 | public class ErrorIngredient implements IIngredientType, IIngredientHelper, IIngredientRenderer { 18 | 19 | public static final ErrorIngredient INSTANCE = new ErrorIngredient(); 20 | public static final ITypedIngredient TYPED_INSTANCE = TypedIngredient.createUnvalidated(INSTANCE, INSTANCE); 21 | 22 | private ErrorIngredient() {} 23 | 24 | //region IIngredientType 25 | @Override 26 | public String getUid() { 27 | return ErrorEmiStack.INSTANCE.getId().toString(); 28 | } 29 | 30 | @Override 31 | public Class getIngredientClass() { 32 | return ErrorIngredient.class; 33 | } 34 | //endregion 35 | 36 | //region IIngredientHelper 37 | @Override 38 | public IIngredientType getIngredientType() { 39 | return this; 40 | } 41 | 42 | @Override 43 | public String getDisplayName(ErrorIngredient errorIngredient) { 44 | return "ERROR"; 45 | } 46 | 47 | @SuppressWarnings("removal") 48 | @Override 49 | public String getUniqueId(ErrorIngredient errorIngredient, UidContext uidContext) { 50 | return ErrorEmiStack.INSTANCE.getId().toString(); 51 | } 52 | 53 | @Override 54 | public ResourceLocation getResourceLocation(ErrorIngredient errorIngredient) { 55 | return ErrorEmiStack.INSTANCE.getId(); 56 | } 57 | 58 | @Override 59 | public ErrorIngredient copyIngredient(ErrorIngredient errorIngredient) { 60 | return this; 61 | } 62 | 63 | @Override 64 | public String getErrorInfo(@Nullable ErrorIngredient errorIngredient) { 65 | return ""; 66 | } 67 | //endregion 68 | 69 | //region IIngredientRenderer 70 | @Override 71 | public void render(GuiGraphics guiGraphics, ErrorIngredient errorIngredient) { 72 | ErrorEmiStack.render(guiGraphics); 73 | } 74 | 75 | @SuppressWarnings("removal") 76 | @Override 77 | public List getTooltip(ErrorIngredient errorIngredient, TooltipFlag tooltipFlag) { 78 | return ErrorEmiStack.INSTANCE.getTooltipText(); 79 | } 80 | //endregion 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/widget/TextWidget.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.widget; 2 | 3 | import dev.emi.emi.api.widget.Bounds; 4 | import dev.emi.emi.api.widget.Widget; 5 | import dev.emi.emi.runtime.EmiDrawContext; 6 | import mezz.jei.api.gui.placement.HorizontalAlignment; 7 | import mezz.jei.api.gui.placement.VerticalAlignment; 8 | import mezz.jei.api.gui.widgets.ITextWidget; 9 | import net.minecraft.client.gui.Font; 10 | import net.minecraft.client.gui.GuiGraphics; 11 | import net.minecraft.network.chat.FormattedText; 12 | 13 | import java.util.List; 14 | 15 | public class TextWidget extends Widget implements ITextWidget { 16 | 17 | private final mezz.jei.common.gui.elements.TextWidget delegate; 18 | 19 | public TextWidget(List lines, int maxWidth, int maxHeight) { 20 | this.delegate = new mezz.jei.common.gui.elements.TextWidget(lines, 0, 0, maxWidth, maxHeight); 21 | } 22 | 23 | @Override 24 | public ITextWidget setPosition(int x, int y) { 25 | delegate.setPosition(x, y); 26 | return this; 27 | } 28 | 29 | @Override 30 | public int getWidth() { 31 | return delegate.getWidth(); 32 | } 33 | 34 | @Override 35 | public int getHeight() { 36 | return delegate.getHeight(); 37 | } 38 | 39 | @Override 40 | public Bounds getBounds() { 41 | final var position = delegate.getPosition(); 42 | return new Bounds(position.x(), position.y(), getWidth(), getHeight()); 43 | } 44 | 45 | @Override 46 | public void render(GuiGraphics draw, int mouseX, int mouseY, float delta) { 47 | final var position = delegate.getPosition(); 48 | final var context = EmiDrawContext.wrap(draw); 49 | context.push(); 50 | context.matrices().translate(position.x(), position.y(), 0); 51 | delegate.drawWidget(context.raw(), mouseX, mouseY); 52 | context.pop(); 53 | } 54 | 55 | //region ITextWidget 56 | @Override 57 | public ITextWidget setFont(Font font) { 58 | delegate.setFont(font); 59 | return this; 60 | } 61 | 62 | @Override 63 | public ITextWidget setColor(int colour) { 64 | delegate.setColor(colour); 65 | return this; 66 | } 67 | 68 | @Override 69 | public ITextWidget setLineSpacing(int spacing) { 70 | delegate.setLineSpacing(spacing); 71 | return this; 72 | } 73 | 74 | @Override 75 | public ITextWidget setShadow(boolean shadow) { 76 | delegate.setShadow(shadow); 77 | return this; 78 | } 79 | 80 | @Override 81 | public ITextWidget setTextAlignment(HorizontalAlignment horizontalAlignment) { 82 | delegate.setTextAlignment(horizontalAlignment); 83 | return this; 84 | } 85 | 86 | @Override 87 | public ITextWidget setTextAlignment(VerticalAlignment verticalAlignment) { 88 | delegate.setTextAlignment(verticalAlignment); 89 | return this; 90 | } 91 | //endregion 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/JemiUtilMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | import dev.emi.emi.api.stack.EmiStack; 4 | import dev.emi.emi.jemi.JemiUtil; 5 | import dev.nolij.toomanyrecipeviewers.TooManyRecipeViewers; 6 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime.IngredientManager; 7 | import dev.nolij.toomanyrecipeviewers.impl.ingredient.ErrorEmiStack; 8 | import mezz.jei.api.ingredients.IIngredientType; 9 | import mezz.jei.api.ingredients.ITypedIngredient; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Unique; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 15 | 16 | import java.util.Optional; 17 | import java.util.function.Consumer; 18 | 19 | @Mixin(value = JemiUtil.class, remap = false) 20 | public class JemiUtilMixin { 21 | 22 | @Unique 23 | private static void tmrv$withIngredientManager(Consumer consumer, Runnable orElse) { 24 | final var runtime = TooManyRecipeViewers.runtime; 25 | if (runtime == null) { 26 | orElse.run(); 27 | return; 28 | } 29 | 30 | final var ingredientManager = runtime.ingredientManager; 31 | if (ingredientManager == null) { 32 | orElse.run(); 33 | return; 34 | } 35 | 36 | consumer.accept(ingredientManager); 37 | } 38 | 39 | @Inject(method = "getStack(Lmezz/jei/api/ingredients/ITypedIngredient;)Ldev/emi/emi/api/stack/EmiStack;", at = @At("HEAD"), cancellable = true) 40 | private static void tmrv$getStack_typedingredient$HEAD(ITypedIngredient typedIngredient, CallbackInfoReturnable cir) { 41 | tmrv$withIngredientManager( 42 | ingredientManager -> cir.setReturnValue(ingredientManager.getEMIStack(typedIngredient)), 43 | () -> cir.setReturnValue(ErrorEmiStack.INSTANCE) 44 | ); 45 | } 46 | 47 | @Inject(method = "getStack(Lmezz/jei/api/ingredients/IIngredientType;Ljava/lang/Object;)Ldev/emi/emi/api/stack/EmiStack;", at = @At("HEAD"), cancellable = true) 48 | private static void tmrv$getStack_type_ingredient$HEAD(IIngredientType type, T ingredient, CallbackInfoReturnable cir) { 49 | tmrv$withIngredientManager( 50 | ingredientManager -> cir.setReturnValue(ingredientManager.getEMIStack(type, ingredient)), 51 | () -> cir.setReturnValue(ErrorEmiStack.INSTANCE) 52 | ); 53 | } 54 | 55 | @Inject(method = "getTyped", at = @At("HEAD"), cancellable = true) 56 | private static void tmrv$getTyped$HEAD(EmiStack emiStack, CallbackInfoReturnable>> cir) { 57 | tmrv$withIngredientManager( 58 | ingredientManager -> cir.setReturnValue(ingredientManager.getTypedIngredient(emiStack)), 59 | () -> cir.setReturnValue(Optional.empty()) 60 | ); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/mixin/FluidEmiStackMixin.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.mixin; 2 | 3 | //? if >=21.1 { 4 | import net.minecraft.core.Holder; 5 | import mezz.jei.api.ingredients.IIngredientTypeWithSubtypes; 6 | //?} 7 | import dev.emi.emi.api.stack.EmiStack; 8 | import dev.emi.emi.api.stack.FluidEmiStack; 9 | import dev.nolij.toomanyrecipeviewers.util.IFluidStackish; 10 | import mezz.jei.api.ingredients.IIngredientType; 11 | import mezz.jei.api.ingredients.ITypedIngredient; 12 | import net.minecraft.core.component.DataComponentPatch; 13 | import net.minecraft.world.level.material.Fluid; 14 | import net.neoforged.neoforge.fluids.FluidStack; 15 | import org.spongepowered.asm.mixin.Final; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Shadow; 18 | 19 | import static dev.nolij.toomanyrecipeviewers.TooManyRecipeViewers.fluidHelper; 20 | 21 | @SuppressWarnings({"UnstableApiUsage", "NonExtendableApiUsage"}) 22 | @Mixin(value = FluidEmiStack.class, remap = false) 23 | public abstract class FluidEmiStackMixin implements ITypedIngredient, IFluidStackish { 24 | 25 | @Shadow @Final private Fluid fluid; 26 | 27 | //? if >=21.1 { 28 | @Shadow @Final private DataComponentPatch componentChanges; 29 | //?} else 30 | /*@Shadow @Final private CompoundTag nbt;*/ 31 | 32 | @Shadow public abstract EmiStack copy(); 33 | 34 | @Override 35 | public IIngredientType getType() { 36 | //noinspection unchecked 37 | return (IIngredientType) fluidHelper.getFluidIngredientType(); 38 | } 39 | 40 | @Override 41 | public FluidStack getIngredient() { 42 | //? if >=21.1 { 43 | return (FluidStack) fluidHelper.create(Holder.direct(fluid), tmrv$getAmount(), componentChanges); 44 | //?} else 45 | /*return (FluidStack) fluidHelper.create(fluid, tmrv$getAmount(), nbt);*/ 46 | } 47 | 48 | //? if >=21.1 { 49 | @Override 50 | public B getBaseIngredient(IIngredientTypeWithSubtypes ingredientType) { 51 | if (ingredientType != getType()) 52 | return ITypedIngredient.super.getBaseIngredient(ingredientType); 53 | 54 | //noinspection unchecked 55 | return (B) fluid; 56 | } 57 | //?} 58 | 59 | @Override 60 | public Fluid tmrv$getFluid() { 61 | return fluid; 62 | } 63 | 64 | @Override 65 | public /*? if >=21.1 {*/ DataComponentPatch /*?} else {*/ /*CompoundTag *//*?}*/ tmrv$getDataComponentPatch() { 66 | //? if >=21.1 { 67 | return componentChanges; 68 | //?} else 69 | /*return nbt;*/ 70 | } 71 | 72 | @Override 73 | public long tmrv$getAmount() { 74 | final var amount = ((FluidEmiStack) (Object) this).getAmount(); 75 | if (amount == 0L) 76 | return 1000L; 77 | 78 | return amount; 79 | } 80 | 81 | @Override 82 | public EmiStack tmrv$normalize() { 83 | return copy().setAmount(0L); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/registration/RecipeRegistration.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.registration; 2 | 3 | import mezz.jei.api.constants.RecipeTypes; 4 | import mezz.jei.api.helpers.IJeiHelpers; 5 | import mezz.jei.api.ingredients.IIngredientType; 6 | import mezz.jei.api.recipe.IRecipeManager; 7 | import mezz.jei.api.recipe.RecipeType; 8 | import mezz.jei.api.recipe.vanilla.IJeiIngredientInfoRecipe; 9 | import mezz.jei.api.recipe.vanilla.IVanillaRecipeFactory; 10 | import mezz.jei.api.registration.IRecipeRegistration; 11 | import mezz.jei.api.runtime.IIngredientManager; 12 | import mezz.jei.common.util.ErrorUtil; 13 | import mezz.jei.library.plugins.jei.info.IngredientInfoRecipe; 14 | import net.minecraft.network.chat.Component; 15 | 16 | import java.util.List; 17 | 18 | public class RecipeRegistration implements IRecipeRegistration { 19 | 20 | private final IJeiHelpers jeiHelpers; 21 | private final IIngredientManager ingredientManager; 22 | private final IRecipeManager recipeManager; 23 | 24 | public RecipeRegistration(IJeiHelpers jeiHelpers, IIngredientManager ingredientManager, IRecipeManager recipeManager) { 25 | this.jeiHelpers = jeiHelpers; 26 | this.ingredientManager = ingredientManager; 27 | this.recipeManager = recipeManager; 28 | } 29 | 30 | public IJeiHelpers getJeiHelpers() { 31 | return this.jeiHelpers; 32 | } 33 | 34 | public IIngredientManager getIngredientManager() { 35 | return this.ingredientManager; 36 | } 37 | 38 | public IVanillaRecipeFactory getVanillaRecipeFactory() { 39 | return this.jeiHelpers.getVanillaRecipeFactory(); 40 | } 41 | 42 | public void addRecipes(RecipeType recipeType, List recipes) { 43 | ErrorUtil.checkNotNull(recipeType, "recipeType"); 44 | ErrorUtil.checkNotNull(recipes, "recipes"); 45 | this.recipeManager.addRecipes(recipeType, recipes); 46 | } 47 | 48 | public void addIngredientInfo(T ingredient, IIngredientType ingredientType, Component... descriptionComponents) { 49 | ErrorUtil.checkNotNull(ingredient, "ingredient"); 50 | ErrorUtil.checkNotNull(ingredientType, "ingredientType"); 51 | ErrorUtil.checkNotEmpty(descriptionComponents, "descriptionComponents"); 52 | this.addIngredientInfo(List.of(ingredient), ingredientType, descriptionComponents); 53 | } 54 | 55 | public void addIngredientInfo(List ingredients, IIngredientType ingredientType, Component... descriptionComponents) { 56 | ErrorUtil.checkNotEmpty(ingredients, "ingredients"); 57 | ErrorUtil.checkNotNull(ingredientType, "ingredientType"); 58 | ErrorUtil.checkNotEmpty(descriptionComponents, "descriptionComponents"); 59 | IJeiIngredientInfoRecipe recipe = IngredientInfoRecipe.create(this.ingredientManager, ingredients, ingredientType, descriptionComponents); 60 | this.addRecipes(RecipeTypes.INFORMATION, List.of(recipe)); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/gui/builder/RecipeLayoutBuilder.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.builder; 2 | 3 | import dev.emi.emi.api.stack.EmiIngredient; 4 | import dev.emi.emi.api.stack.EmiStack; 5 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime.IngredientManager; 6 | import mezz.jei.api.gui.builder.IIngredientAcceptor; 7 | import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; 8 | import mezz.jei.api.gui.builder.IRecipeSlotBuilder; 9 | import mezz.jei.api.recipe.RecipeIngredientRole; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class RecipeLayoutBuilder implements IRecipeLayoutBuilder { 15 | 16 | public record ExtractedEMIRecipeData( 17 | List inputs, 18 | List catalysts, 19 | List outputs, 20 | boolean shapeless, 21 | boolean supportsRecipeTree 22 | ) {} 23 | 24 | private final IngredientManager ingredientManager; 25 | 26 | private final List slots = new ArrayList<>(); 27 | private boolean shapeless = false; 28 | 29 | public RecipeLayoutBuilder(IngredientManager ingredientManager) { 30 | this.ingredientManager = ingredientManager; 31 | } 32 | 33 | public ExtractedEMIRecipeData extractEMIRecipeData() { 34 | final var outputSlots = slots.stream().filter(x -> x.role == RecipeIngredientRole.OUTPUT).toList(); 35 | final var outputs = new ArrayList(); 36 | 37 | var supportsRecipeTree = true; 38 | for (final var outputSlot : outputSlots) { 39 | final var stacks = outputSlot.getEMIStacks(); 40 | outputs.addAll(stacks); 41 | if (supportsRecipeTree && stacks.size() > 1) { 42 | supportsRecipeTree = false; 43 | } 44 | } 45 | 46 | return new ExtractedEMIRecipeData( 47 | slots.stream() 48 | .filter(x -> x.role == RecipeIngredientRole.INPUT) 49 | .map(RecipeSlotBuilder::getEMIIngredient) 50 | .toList(), 51 | slots.stream() 52 | .filter(x -> x.role == RecipeIngredientRole.CATALYST) 53 | .map(RecipeSlotBuilder::getEMIIngredient) 54 | .toList(), 55 | outputs, 56 | shapeless, 57 | supportsRecipeTree 58 | ); 59 | } 60 | 61 | public List getSlots() { 62 | return slots; 63 | } 64 | 65 | @Override 66 | public IRecipeSlotBuilder addSlot(RecipeIngredientRole role) { 67 | final var slot = new RecipeSlotBuilder(ingredientManager, role); 68 | slots.add(slot); 69 | return slot; 70 | } 71 | 72 | @SuppressWarnings("removal") 73 | @Override 74 | public IRecipeSlotBuilder addSlotToWidget(RecipeIngredientRole role, mezz.jei.api.gui.widgets.ISlottedWidgetFactory widgetFactory) { 75 | return addSlot(role); 76 | } 77 | 78 | @Override 79 | public IIngredientAcceptor addInvisibleIngredients(RecipeIngredientRole role) { 80 | return ((RecipeSlotBuilder) addSlot(role)).setInvisible(); 81 | } 82 | 83 | @Override 84 | public void moveRecipeTransferButton(int x, int y) { 85 | // TODO 86 | } 87 | 88 | @Override 89 | public void setShapeless() { 90 | shapeless = true; 91 | } 92 | 93 | @Override 94 | public void setShapeless(int x, int y) { 95 | shapeless = true; 96 | } 97 | 98 | @Override 99 | public void createFocusLink(IIngredientAcceptor... ingredientAcceptors) { 100 | 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/gui/widgets/ScrollGridWidget.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.widgets; 2 | 3 | import dev.emi.emi.api.widget.WidgetHolder; 4 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.ingredient.ITMRVSlotWidget; 5 | import dev.nolij.toomanyrecipeviewers.impl.widget.ScrollBarWidget; 6 | import mezz.jei.api.gui.ingredient.IRecipeSlotDrawable; 7 | import mezz.jei.api.gui.inputs.RecipeSlotUnderMouse; 8 | import mezz.jei.api.gui.widgets.IScrollGridWidget; 9 | import mezz.jei.common.util.ImmutableRect2i; 10 | import net.minecraft.client.gui.navigation.ScreenPosition; 11 | import net.minecraft.client.gui.navigation.ScreenRectangle; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Optional; 17 | 18 | public class ScrollGridWidget implements IScrollGridWidget { 19 | 20 | private static final int SLOT_SIZE = 18; 21 | private static final int SCROLL_BAR_WIDTH = 10; 22 | 23 | private final List slots = new ArrayList<>(); 24 | private final int columns; 25 | private final int visibleRows; 26 | 27 | private ImmutableRect2i rect; 28 | 29 | private final @Nullable ScrollBarWidget scrollBar; 30 | 31 | public ScrollGridWidget(WidgetHolder widgets, List slots, int columns, int visibleRows) { 32 | for (final var slot : slots) { 33 | if (slot instanceof ITMRVSlotWidget tmrvSlotWidget) { 34 | tmrvSlotWidget.drawBack(true); 35 | this.slots.add(tmrvSlotWidget); 36 | } else { 37 | throw new IllegalStateException(); 38 | } 39 | } 40 | 41 | this.columns = columns; 42 | final var rows = (int) Math.ceil(((double) this.slots.size()) / (double) columns); 43 | this.visibleRows = visibleRows; 44 | 45 | if (rows > visibleRows) 46 | this.scrollBar = widgets.add(new ScrollBarWidget(ImmutableRect2i.EMPTY, rows, visibleRows, this::updateGrid)); 47 | else 48 | this.scrollBar = null; 49 | this.rect = new ImmutableRect2i(0, 0, SLOT_SIZE * columns + (scrollBar == null ? 0 : SCROLL_BAR_WIDTH), SLOT_SIZE * visibleRows); 50 | updateGrid(); 51 | } 52 | 53 | private void updateGrid() { 54 | for (var i = 0; i < slots.size(); i++) { 55 | final var slot = slots.get(i); 56 | final var column = i % columns; 57 | final var row = i / columns; 58 | final var renderRow = scrollBar == null ? row : row - scrollBar.getScroll(); 59 | if (renderRow < 0 || renderRow >= visibleRows) { 60 | slot.setVisible(false); 61 | } else { 62 | slot.setVisible(true); 63 | slot.setPosition(rect.x() + column * SLOT_SIZE, rect.y() + renderRow * SLOT_SIZE); 64 | } 65 | } 66 | } 67 | 68 | @Override 69 | public ScreenRectangle getScreenRectangle() { 70 | return rect.toScreenRectangle(); 71 | } 72 | 73 | @Override 74 | public IScrollGridWidget setPosition(int x, int y) { 75 | rect = rect.setPosition(x + 1, y); 76 | 77 | if (this.scrollBar != null) 78 | scrollBar.setRect(rect.keepRight(SCROLL_BAR_WIDTH).addOffset(0, -1)); 79 | 80 | updateGrid(); 81 | return this; 82 | } 83 | 84 | @Override 85 | public int getWidth() { 86 | return rect.width(); 87 | } 88 | 89 | @Override 90 | public int getHeight() { 91 | return rect.height(); 92 | } 93 | 94 | @Override 95 | public Optional getSlotUnderMouse(double x, double y) { 96 | // TODO 97 | return Optional.empty(); 98 | } 99 | 100 | @Override 101 | public ScreenPosition getPosition() { 102 | return rect.getScreenPosition(); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/ingredient/ErrorEmiStack.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.ingredient; 2 | 3 | import dev.emi.emi.api.stack.EmiStack; 4 | import dev.emi.emi.runtime.EmiDrawContext; 5 | import net.minecraft.ChatFormatting; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.gui.Font; 8 | import net.minecraft.client.gui.GuiGraphics; 9 | import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; 10 | import net.minecraft.core.component.DataComponentPatch; 11 | import net.minecraft.network.chat.Component; 12 | import net.minecraft.resources.ResourceLocation; 13 | 14 | import java.util.List; 15 | 16 | import static dev.nolij.toomanyrecipeviewers.TooManyRecipeViewersConstants.MOD_ID; 17 | 18 | public class ErrorEmiStack extends EmiStack { 19 | 20 | private static final Font FONT = Minecraft.getInstance().font; 21 | private static final int Y_OFFSET = 8 - (FONT.lineHeight / 2); 22 | private static final int BACKGROUND_COLOR = 0xFFAA0000; 23 | private static final int TEXT_COLOUR = 0xFFDDDD33; 24 | 25 | static void render(GuiGraphics draw) { 26 | draw.fill(0, 0, 16, 16, BACKGROUND_COLOR); 27 | draw.drawCenteredString(FONT, Component.literal("!?!").withStyle(ChatFormatting.OBFUSCATED), 8, Y_OFFSET, TEXT_COLOUR); 28 | } 29 | 30 | public static final ErrorEmiStack INSTANCE = new ErrorEmiStack(); 31 | 32 | private ErrorEmiStack() {} 33 | 34 | @Override 35 | public EmiStack copy() { 36 | return this; 37 | } 38 | 39 | @Override 40 | public void render(GuiGraphics draw, int x, int y, float delta, int flags) { 41 | final var context = EmiDrawContext.wrap(draw); 42 | 43 | context.push(); 44 | context.matrices().translate(x, y, 0); 45 | 46 | render(context.raw()); 47 | 48 | context.pop(); 49 | } 50 | 51 | @Override 52 | public boolean isEmpty() { 53 | return false; 54 | } 55 | 56 | @Override 57 | //? if >=21.1 { 58 | public DataComponentPatch getComponentChanges() { 59 | return DataComponentPatch.EMPTY; 60 | } 61 | //?} else { 62 | /*public CompoundTag getNbt() { 63 | return null; 64 | } 65 | *///?} 66 | 67 | @Override 68 | public Object getKey() { 69 | return ErrorEmiStack.class; 70 | } 71 | 72 | @Override 73 | public ResourceLocation getId() { 74 | return ResourceLocation.fromNamespaceAndPath(MOD_ID, "error"); 75 | } 76 | 77 | @Override 78 | public List getTooltipText() { 79 | return List.of( 80 | Component.literal("ERROR") 81 | .withStyle(ChatFormatting.RED) 82 | .withStyle(ChatFormatting.BOLD), 83 | Component.literal("This shouldn't be here. It only shows when TooManyRecipeViewers has encountered an error. Please report this via a GitHub issue.\n") 84 | .withStyle(ChatFormatting.DARK_RED), 85 | Component.literal("PLEASE INCLUDE WITH YOUR REPORT:") 86 | .withStyle(ChatFormatting.WHITE) 87 | .withStyle(ChatFormatting.BOLD) 88 | .withStyle(ChatFormatting.UNDERLINE), 89 | Component.literal(""" 90 | - Screenshots (WITHOUT this tooltip; it might cover useful information!) 91 | - What recipe you were trying to view 92 | - Which mod adds it (including version) 93 | - TooManyRecipeViewers version 94 | - Any other potentially useful information""") 95 | ); 96 | } 97 | 98 | @Override 99 | public List getTooltip() { 100 | return getTooltipText().stream() 101 | .map(Component::getVisualOrderText) 102 | .map(ClientTooltipComponent::create) 103 | .toList(); 104 | } 105 | 106 | @Override 107 | public Component getName() { 108 | return Component.literal("ERROR"); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/widget/ScrollBarWidget.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.widget; 2 | 3 | import dev.emi.emi.api.widget.Bounds; 4 | import dev.emi.emi.api.widget.Widget; 5 | import mezz.jei.api.gui.placement.IPlaceable; 6 | import mezz.jei.common.Internal; 7 | import mezz.jei.common.util.ImmutableRect2i; 8 | import net.minecraft.client.gui.GuiGraphics; 9 | import net.minecraft.util.Mth; 10 | 11 | public class ScrollBarWidget extends Widget implements IPlaceable { 12 | 13 | private ImmutableRect2i rect; 14 | private ImmutableRect2i upRect; 15 | private ImmutableRect2i downRect; 16 | private ImmutableRect2i midRect; 17 | private ImmutableRect2i scrollRect; 18 | private ImmutableRect2i dragRect; 19 | 20 | private int scroll = 0; 21 | private final int maxScroll; 22 | private final Runnable onScroll; 23 | 24 | public ScrollBarWidget(ImmutableRect2i rect, int rows, int visibleRows, Runnable onScroll) { 25 | setRect(rect); 26 | 27 | this.maxScroll = rows - visibleRows; 28 | this.onScroll = onScroll; 29 | } 30 | 31 | private boolean canScroll(int newScroll) { 32 | return scroll != Mth.clamp(newScroll, 0, maxScroll); 33 | } 34 | 35 | private boolean scroll(int newScroll) { 36 | final var clamped = Mth.clamp(newScroll, 0, maxScroll); 37 | 38 | if (scroll == clamped) 39 | return false; 40 | 41 | scroll = clamped; 42 | updateDragRect(); 43 | onScroll.run(); 44 | return true; 45 | } 46 | 47 | private void updateDragRect() { 48 | if (maxScroll == 0) 49 | return; 50 | 51 | final var increment = scrollRect.height() / ((double) maxScroll + 1); 52 | this.dragRect = scrollRect 53 | .keepTop((int) increment) 54 | .addOffset(0, (int) (increment * (double) scroll)); 55 | } 56 | 57 | public int getScroll() { 58 | return scroll; 59 | } 60 | 61 | public void setRect(ImmutableRect2i rect) { 62 | this.rect = rect; 63 | 64 | upRect = rect.keepTop(rect.width()); 65 | downRect = rect.keepBottom(rect.width()); 66 | midRect = rect.cropTop(upRect.height()).cropBottom(downRect.height()); 67 | scrollRect = midRect.insetBy(1); 68 | updateDragRect(); 69 | } 70 | 71 | @Override 72 | public ScrollBarWidget setPosition(int x, int y) { 73 | setRect(rect.setPosition(x, y)); 74 | return this; 75 | } 76 | 77 | @Override 78 | public int getWidth() { 79 | return rect.width(); 80 | } 81 | 82 | @Override 83 | public int getHeight() { 84 | return rect.height(); 85 | } 86 | 87 | @Override 88 | public Bounds getBounds() { 89 | return new Bounds(rect.x(), rect.y(), rect.width(), rect.height()); 90 | } 91 | 92 | @Override 93 | public void render(GuiGraphics draw, int mouseX, int mouseY, float delta) { 94 | Internal.getTextures().getButtonForState(false, canScroll(scroll - 1), upRect.contains(mouseX, mouseY)).draw(draw, upRect); 95 | Internal.getTextures().getButtonForState(false, canScroll(scroll + 1), downRect.contains(mouseX, mouseY)).draw(draw, downRect); 96 | Internal.getTextures().getButtonForState(false, false, false).draw(draw, midRect); 97 | Internal.getTextures().getButtonForState(false, true, dragRect.contains(mouseX, mouseY)).draw(draw, dragRect); 98 | } 99 | 100 | @Override 101 | public boolean mouseClicked(int mouseX, int mouseY, int button) { 102 | if (!rect.contains(mouseX, mouseY)) 103 | return false; 104 | 105 | if (upRect.contains(mouseX, mouseY)) { 106 | return scroll(scroll - 1); 107 | } else if (downRect.contains(mouseX, mouseY)) { 108 | return scroll(scroll + 1); 109 | } else if (midRect.contains(mouseX, mouseY)) { 110 | return scroll((int) ((mouseY - scrollRect.y()) / (double) scrollRect.height() * ((double) maxScroll + 1))); 111 | } 112 | 113 | return false; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/runtime/JEIRuntime.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime; 2 | 3 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.registration.RuntimeRegistration; 4 | import mezz.jei.api.helpers.IJeiHelpers; 5 | import mezz.jei.api.recipe.IRecipeManager; 6 | import mezz.jei.api.recipe.transfer.IRecipeTransferManager; 7 | import mezz.jei.api.runtime.IBookmarkOverlay; 8 | import mezz.jei.api.runtime.IEditModeConfig; 9 | import mezz.jei.api.runtime.IIngredientFilter; 10 | import mezz.jei.api.runtime.IIngredientListOverlay; 11 | import mezz.jei.api.runtime.IIngredientManager; 12 | import mezz.jei.api.runtime.IJeiKeyMappings; 13 | import mezz.jei.api.runtime.IJeiRuntime; 14 | import mezz.jei.api.runtime.IRecipesGui; 15 | import mezz.jei.api.runtime.IScreenHelper; 16 | import mezz.jei.api.runtime.config.IJeiConfigManager; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | public class JEIRuntime implements IJeiRuntime { 20 | 21 | private final IRecipeManager recipeManager; 22 | private final IRecipesGui recipesGUI; 23 | private final IIngredientFilter ingredientFilter; 24 | private final IIngredientListOverlay ingredientListOverlay; 25 | private final IBookmarkOverlay bookmarkOverlay; 26 | private final IJeiHelpers jeiHelpers; 27 | private final IIngredientManager ingredientManager; 28 | private final IJeiKeyMappings jeiKeyMappings; 29 | private final IScreenHelper screenHelper; 30 | private final IRecipeTransferManager recipeTransferManager; 31 | private final IEditModeConfig editModeConfig; 32 | private final IJeiConfigManager jeiConfigManager; 33 | 34 | public JEIRuntime(RuntimeRegistration registration, IJeiKeyMappings jeiKeyMappings, IJeiConfigManager jeiConfigManager) { 35 | this.recipeManager = registration.getRecipeManager(); 36 | this.recipesGUI = registration.getRecipesGui(); 37 | this.ingredientFilter = registration.getIngredientFilter(); 38 | this.ingredientListOverlay = registration.getIngredientListOverlay(); 39 | this.bookmarkOverlay = registration.getBookmarkOverlay(); 40 | this.jeiHelpers = registration.getJeiHelpers(); 41 | this.ingredientManager = registration.getIngredientManager(); 42 | this.jeiKeyMappings = jeiKeyMappings; 43 | this.screenHelper = registration.getScreenHelper(); 44 | this.recipeTransferManager = registration.getRecipeTransferManager(); 45 | this.editModeConfig = registration.getEditModeConfig(); 46 | this.jeiConfigManager = jeiConfigManager; 47 | } 48 | 49 | @Override 50 | public @NotNull IRecipeManager getRecipeManager() { 51 | return recipeManager; 52 | } 53 | 54 | @Override 55 | public @NotNull IRecipesGui getRecipesGui() { 56 | return recipesGUI; 57 | } 58 | 59 | @Override 60 | public @NotNull IIngredientFilter getIngredientFilter() { 61 | return ingredientFilter; 62 | } 63 | 64 | @Override 65 | public @NotNull IIngredientListOverlay getIngredientListOverlay() { 66 | return ingredientListOverlay; 67 | } 68 | 69 | @Override 70 | public @NotNull IBookmarkOverlay getBookmarkOverlay() { 71 | return bookmarkOverlay; 72 | } 73 | 74 | @Override 75 | public @NotNull IJeiHelpers getJeiHelpers() { 76 | return jeiHelpers; 77 | } 78 | 79 | @Override 80 | public @NotNull IIngredientManager getIngredientManager() { 81 | return ingredientManager; 82 | } 83 | 84 | @Override 85 | public @NotNull IJeiKeyMappings getKeyMappings() { 86 | return jeiKeyMappings; 87 | } 88 | 89 | @Override 90 | public @NotNull IScreenHelper getScreenHelper() { 91 | return screenHelper; 92 | } 93 | 94 | @Override 95 | public @NotNull IRecipeTransferManager getRecipeTransferManager() { 96 | return recipeTransferManager; 97 | } 98 | 99 | @Override 100 | public @NotNull IEditModeConfig getEditModeConfig() { 101 | return editModeConfig; 102 | } 103 | 104 | @Override 105 | public @NotNull IJeiConfigManager getConfigManager() { 106 | return jeiConfigManager; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/TooManyRecipeViewersMod.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers; 2 | 3 | //? if >=21.1 { 4 | import net.neoforged.fml.common.Mod; 5 | import mezz.jei.common.network.packets.PacketRecipeTransfer; 6 | import net.neoforged.api.distmarker.Dist; 7 | import net.neoforged.bus.api.IEventBus; 8 | import net.neoforged.neoforge.common.NeoForge; 9 | import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; 10 | import net.neoforged.neoforge.network.registration.HandlerThread; 11 | //?} else { 12 | /*import net.minecraftforge.common.MinecraftForge; 13 | import net.neoforged.fml.common.Mod; 14 | import net.neoforged.fml.javafmlmod.FMLJavaModLoadingContext; 15 | import net.neoforged.fml.loading.FMLEnvironment; 16 | *///?} 17 | import dev.emi.emi.jemi.JemiPlugin; 18 | import dev.nolij.libnolij.refraction.Refraction; 19 | import dev.nolij.toomanyrecipeviewers.impl.jei.common.config.JEIClientConfigs; 20 | import dev.nolij.toomanyrecipeviewers.impl.jei.common.network.ConnectionToServer; 21 | import mezz.jei.common.Internal; 22 | import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; 23 | import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; 24 | import org.apache.logging.log4j.LogManager; 25 | import org.apache.logging.log4j.Logger; 26 | 27 | import java.lang.invoke.MethodHandles; 28 | import java.util.stream.Collectors; 29 | 30 | import static dev.nolij.toomanyrecipeviewers.TooManyRecipeViewers.*; 31 | import static dev.nolij.toomanyrecipeviewers.TooManyRecipeViewersConstants.*; 32 | 33 | @Mod(value = MOD_ID/*? if >=21.1 {*/, dist = Dist.CLIENT /*?}*/) 34 | public class TooManyRecipeViewersMod { 35 | 36 | public static final Logger LOGGER = LogManager.getLogger(MOD_ID); 37 | public static final Refraction REFRACTION = new Refraction(MethodHandles.lookup()); 38 | 39 | public TooManyRecipeViewersMod(/*? if >=21.1 {*/IEventBus modEventBus/*?}*/) { 40 | //? if <21.1 { 41 | /*if (!FMLEnvironment.dist.isClient()) 42 | return; 43 | 44 | @SuppressWarnings("removal") final var modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); 45 | *///?} 46 | 47 | Internal.setKeyMappings(jeiKeyMappings); 48 | JemiPlugin.runtime = staticJEIRuntime; 49 | 50 | Internal.setJeiClientConfigs(new JEIClientConfigs()); 51 | 52 | LOGGER.info("Loading JEI Plugins: [{}]", JEIPlugins.allPlugins.stream().map(x -> x.getPluginUid().toString()).collect(Collectors.joining(", "))); 53 | JEIPlugins.onConfigManagerAvailable(jeiConfigManager); 54 | 55 | modEventBus.addListener(this::onRegisterClientReloadListeners); 56 | //? if >=21.1 { 57 | NeoForge 58 | //?} else 59 | /*MinecraftForge*/ 60 | .EVENT_BUS.addListener(this::onLoggingOut); 61 | 62 | //? if >=21.1 63 | modEventBus.addListener(this::onRegisterPayloadHandlersEvent); 64 | } 65 | 66 | private void onLoggingOut(ClientPlayerNetworkEvent.LoggingOut event) { 67 | EMIPlugin.onRuntimeUnavailable(); 68 | } 69 | 70 | private void onRegisterClientReloadListeners(RegisterClientReloadListenersEvent event) { 71 | event.registerReloadListener(Internal.getTextures().getSpriteUploader()); 72 | } 73 | 74 | //? if >=21.1 { 75 | private void onRegisterPayloadHandlersEvent(RegisterPayloadHandlersEvent event) { 76 | event.registrar("3") 77 | .executesOn(HandlerThread.MAIN) 78 | .optional() 79 | .playToServer(PacketRecipeTransfer.TYPE, PacketRecipeTransfer.STREAM_CODEC, (recipeTransferPacket, context) -> { 80 | // We never process this packet. On a multiplayer server, TMRV is never loaded, so there are two possibilities: 81 | // - JEI is installed. In this case JEI will register a handler for this packet on the server. 82 | // - EMI is installed. In this case TMRV is not loaded, so the server will not support this packet, 83 | // and TMRV will fall back to the EMI packet. 84 | // We special-case singleplayer and never send this packet (since we don't have logic for handling it), 85 | // so nothing needs to be processed here. 86 | }); 87 | } 88 | //?} 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/registration/RuntimeRegistration.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.registration; 2 | 3 | import dev.emi.emi.jemi.runtime.JemiBookmarkOverlay; 4 | import dev.emi.emi.jemi.runtime.JemiIngredientFilter; 5 | import dev.emi.emi.jemi.runtime.JemiIngredientListOverlay; 6 | import dev.emi.emi.jemi.runtime.JemiRecipesGui; 7 | import dev.nolij.toomanyrecipeviewers.TooManyRecipeViewers; 8 | import mezz.jei.api.helpers.IJeiHelpers; 9 | import mezz.jei.api.recipe.IRecipeManager; 10 | import mezz.jei.api.recipe.transfer.IRecipeTransferManager; 11 | import mezz.jei.api.registration.IRuntimeRegistration; 12 | import mezz.jei.api.runtime.IBookmarkOverlay; 13 | import mezz.jei.api.runtime.IEditModeConfig; 14 | import mezz.jei.api.runtime.IIngredientFilter; 15 | import mezz.jei.api.runtime.IIngredientListOverlay; 16 | import mezz.jei.api.runtime.IIngredientManager; 17 | import mezz.jei.api.runtime.IRecipesGui; 18 | import mezz.jei.api.runtime.IScreenHelper; 19 | import org.jetbrains.annotations.NotNull; 20 | 21 | import static dev.nolij.toomanyrecipeviewers.TooManyRecipeViewersMod.LOGGER; 22 | 23 | public class RuntimeRegistration implements IRuntimeRegistration { 24 | 25 | private final IRecipeManager recipeManager; 26 | private final IJeiHelpers jeiHelpers; 27 | private final IEditModeConfig editModeConfig; 28 | private final IIngredientManager ingredientManager; 29 | private final IRecipeTransferManager recipeTransferManager; 30 | private final IScreenHelper screenHelper; 31 | 32 | public RuntimeRegistration(TooManyRecipeViewers runtime) { 33 | this.recipeManager = runtime.recipeManager; 34 | this.jeiHelpers = runtime.jeiHelpers; 35 | this.editModeConfig = runtime.editModeConfig; 36 | this.ingredientManager = runtime.ingredientManager; 37 | this.recipeTransferManager = runtime.recipeTransferManager; 38 | this.screenHelper = runtime.screenHelper; 39 | } 40 | 41 | private final IIngredientListOverlay ingredientListOverlay = new JemiIngredientListOverlay(); 42 | private final IBookmarkOverlay bookmarkOverlay = new JemiBookmarkOverlay(); 43 | private final IRecipesGui recipesGui = new JemiRecipesGui(); 44 | private final IIngredientFilter ingredientFilter = new JemiIngredientFilter(); 45 | 46 | @Override 47 | public void setIngredientListOverlay(@NotNull IIngredientListOverlay ingredientListOverlay) { 48 | LOGGER.error(new UnsupportedOperationException()); 49 | } 50 | 51 | @Override 52 | public void setBookmarkOverlay(@NotNull IBookmarkOverlay bookmarkOverlay) { 53 | LOGGER.error(new UnsupportedOperationException()); 54 | } 55 | 56 | @Override 57 | public void setRecipesGui(@NotNull IRecipesGui recipesGui) { 58 | LOGGER.error(new UnsupportedOperationException()); 59 | } 60 | 61 | @Override 62 | public void setIngredientFilter(@NotNull IIngredientFilter ingredientFilter) { 63 | LOGGER.error(new UnsupportedOperationException()); 64 | } 65 | 66 | @Override 67 | public @NotNull IRecipeManager getRecipeManager() { 68 | return this.recipeManager; 69 | } 70 | 71 | @Override 72 | public @NotNull IJeiHelpers getJeiHelpers() { 73 | return this.jeiHelpers; 74 | } 75 | 76 | @Override 77 | public @NotNull IIngredientManager getIngredientManager() { 78 | return this.ingredientManager; 79 | } 80 | 81 | @Override 82 | public @NotNull IScreenHelper getScreenHelper() { 83 | return this.screenHelper; 84 | } 85 | 86 | @Override 87 | public @NotNull IRecipeTransferManager getRecipeTransferManager() { 88 | return this.recipeTransferManager; 89 | } 90 | 91 | @Override 92 | public @NotNull IEditModeConfig getEditModeConfig() { 93 | return this.editModeConfig; 94 | } 95 | 96 | public IIngredientListOverlay getIngredientListOverlay() { 97 | return ingredientListOverlay; 98 | } 99 | 100 | public IBookmarkOverlay getBookmarkOverlay() { 101 | return bookmarkOverlay; 102 | } 103 | 104 | public IRecipesGui getRecipesGui() { 105 | return recipesGui; 106 | } 107 | 108 | public IIngredientFilter getIngredientFilter() { 109 | return this.ingredientFilter; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /stonecutter.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.taumc.gradle.publishing.api.PublishChannel 2 | import org.taumc.gradle.publishing.publishing 3 | 4 | plugins { 5 | id("idea") 6 | id("dev.kikugie.stonecutter") 7 | id("org.taumc.gradle.versioning") 8 | id("org.taumc.gradle.publishing") 9 | } 10 | stonecutter active "21.1-neoforge" /* [SC] DO NOT EDIT */ 11 | 12 | operator fun String.invoke(): String = project.properties[this] as? String ?: error("Property $this not found") 13 | 14 | idea.module { 15 | isDownloadJavadoc = true 16 | isDownloadSources = true 17 | } 18 | 19 | project.group = "maven_group"() 20 | project.version = tau.versioning.version("mod_version"(), project.properties["release_channel"]) 21 | 22 | tasks.register("runClientActive") { 23 | group = "project" 24 | dependsOn("${stonecutter.current?.project}:runClient") 25 | } 26 | 27 | tau.publishing.publish { 28 | useTauGradleVersioning() 29 | changelog = rootProject.file("CHANGELOG.md").readText() 30 | 31 | github { 32 | supportAllChannels() 33 | 34 | accessToken = providers.environmentVariable("GITHUB_TOKEN") 35 | repository = "Nolij/TooManyRecipeViewers" 36 | tagName = tau.versioning.releaseTag 37 | } 38 | 39 | curseforge { 40 | supportChannels(PublishChannel.RELEASE) 41 | 42 | accessToken = providers.environmentVariable("CURSEFORGE_TOKEN") 43 | projectID = 1194921 44 | projectSlug = "tmrv" 45 | } 46 | 47 | modrinth { 48 | supportChannels(PublishChannel.RELEASE) 49 | 50 | accessToken = providers.environmentVariable("MODRINTH_TOKEN") 51 | projectID = "yFypjcfd" 52 | projectSlug = "tmrv" 53 | } 54 | 55 | val iconURL = "https://github.com/Nolij/TooManyRecipeViewers/raw/master/src/main/resources/icon.png" 56 | 57 | discord { 58 | supportAllChannelsExcluding(PublishChannel.RELEASE) 59 | 60 | webhookURL = providers.environmentVariable("DISCORD_WEBHOOK") 61 | avatarURL = iconURL 62 | 63 | testBuildPreset(modName = "TooManyRecipeViewers", repoURL = "https://github.com/Nolij/TooManyRecipeViewers") 64 | } 65 | 66 | discord { 67 | supportChannels(PublishChannel.RELEASE) 68 | 69 | webhookURL = providers.environmentVariable("DISCORD_WEBHOOK") 70 | avatarURL = iconURL 71 | 72 | releasePreset(modName = "TooManyRecipeViewers") 73 | } 74 | } 75 | 76 | stonecutter parameters { 77 | replacements { 78 | string { 79 | direction = eval(current.version, ">=21.1") 80 | replace("mezz.jei.forge", "mezz.jei.neoforge") 81 | } 82 | string { 83 | direction = eval(current.version, ">=21.1") 84 | replace("mezz.jei.api.forge", "mezz.jei.api.neoforge") 85 | } 86 | string { 87 | direction = eval(current.version, ">=21.1") 88 | replace("net.minecraftforge.client.event", "net.neoforged.neoforge.client.event") 89 | } 90 | string { 91 | direction = eval(current.version, ">=21.1") 92 | replace("net.minecraftforge.fml", "net.neoforged.fml") 93 | } 94 | string { 95 | direction = eval(current.version, ">=21.1") 96 | replace("net.minecraftforge.network", "net.neoforged.neoforge.network") 97 | } 98 | string { 99 | direction = eval(current.version, ">=21.1") 100 | replace("net.minecraftforge.fluids", "net.neoforged.neoforge.fluids") 101 | } 102 | string { 103 | direction = eval(current.version, ">=21.1") 104 | replace("@Nullable CompoundTag dataComponentPatch", "DataComponentPatch dataComponentPatch") 105 | } 106 | string { 107 | direction = eval(current.version, ">=21.1") 108 | replace("(dataComponentPatch == null || dataComponentPatch.isEmpty())", "(dataComponentPatch.isEmpty())") 109 | } 110 | string { 111 | direction = eval(current.version, ">=21.1") 112 | replace("import net.minecraft.nbt.CompoundTag;", "import net.minecraft.core.component.DataComponentPatch;") 113 | } 114 | string { 115 | direction = eval(current.version, ">=21.1") 116 | replace("TypedIngredient.deepCopy", "TypedIngredient.defensivelyCopyTypedIngredientFromApi") 117 | } 118 | string { 119 | direction = eval(current.version, ">=21.1") 120 | replace(".getNbt()", ".getComponentChanges()") 121 | } 122 | string { 123 | direction = eval(current.version, ">=21.1") 124 | replace(".getTag()", ".getComponentsPatch()") 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/common/config/ClientConfig.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.common.config; 2 | 3 | import mezz.jei.common.config.BookmarkTooltipFeature; 4 | import mezz.jei.common.config.GiveMode; 5 | import mezz.jei.common.config.HistoryDisplaySide; 6 | import mezz.jei.common.config.IClientConfig; 7 | import mezz.jei.common.config.IngredientSortStage; 8 | import mezz.jei.common.config.RecipeSorterStage; 9 | import mezz.jei.common.config.file.IConfigListener; 10 | 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | public class ClientConfig implements IClientConfig { 15 | 16 | @Override 17 | public boolean isCenterSearchBarEnabled() { 18 | return false; 19 | } 20 | 21 | @Override 22 | public boolean isLowMemorySlowSearchEnabled() { 23 | return false; 24 | } 25 | 26 | @Override 27 | public boolean isCatchRenderErrorsEnabled() { 28 | return false; 29 | } 30 | 31 | @Override 32 | public boolean isCheatToHotbarUsingHotkeysEnabled() { 33 | return false; 34 | } 35 | 36 | @Override 37 | public boolean isAddingBookmarksToFrontEnabled() { 38 | return false; 39 | } 40 | 41 | @Override 42 | public boolean isLookupFluidContentsEnabled() { 43 | return false; 44 | } 45 | 46 | @Override 47 | public boolean isLookupBlockTagsEnabled() { 48 | return false; 49 | } 50 | 51 | @Override 52 | public GiveMode getGiveMode() { 53 | return GiveMode.defaultGiveMode; 54 | } 55 | 56 | @Override 57 | //? if >=21.1 { 58 | public boolean getShowHiddenIngredients() { 59 | //?} else 60 | /*public boolean isShowHiddenItemsEnabled() {*/ 61 | return true; 62 | } 63 | 64 | @Override 65 | public List getBookmarkTooltipFeatures() { 66 | return List.of(); 67 | } 68 | 69 | @Override 70 | public boolean isHoldShiftToShowBookmarkTooltipFeaturesEnabled() { 71 | return false; 72 | } 73 | 74 | @Override 75 | public boolean isDragToRearrangeBookmarksEnabled() { 76 | return false; 77 | } 78 | 79 | @Override 80 | public int getDragDelayMs() { 81 | return 0; 82 | } 83 | 84 | @Override 85 | public int getSmoothScrollRate() { 86 | return 0; 87 | } 88 | 89 | @Override 90 | public int getMaxRecipeGuiHeight() { 91 | return 0; 92 | } 93 | 94 | @Override 95 | public List getIngredientSorterStages() { 96 | return List.of(); 97 | } 98 | 99 | @Override 100 | public Set getRecipeSorterStages() { 101 | return Set.of(); 102 | } 103 | 104 | @Override 105 | public void enableRecipeSorterStage(RecipeSorterStage recipeSorterStage) { 106 | 107 | } 108 | 109 | @Override 110 | public void disableRecipeSorterStage(RecipeSorterStage recipeSorterStage) { 111 | 112 | } 113 | 114 | @Override 115 | public boolean isTagContentTooltipEnabled() { 116 | return false; 117 | } 118 | 119 | @Override 120 | //? if >=21.1 { 121 | public boolean getHideSingleTagContentTooltipEnabled() { 122 | //?} else 123 | /*public boolean isHideSingleIngredientTagsEnabled() {*/ 124 | return false; 125 | } 126 | 127 | @Override 128 | public boolean isShowTagRecipesEnabled() { 129 | return false; 130 | } 131 | 132 | @Override 133 | public boolean isShowCreativeTabNamesEnabled() { 134 | return false; 135 | } 136 | 137 | @Override 138 | public boolean isLookupHistoryEnabled() { 139 | return false; 140 | } 141 | 142 | @Override 143 | public void setLookupHistoryEnabled(boolean b) { 144 | 145 | } 146 | 147 | @Override 148 | public void addLookupHistoryEnabledListener(IConfigListener iConfigListener) { 149 | 150 | } 151 | 152 | @Override 153 | public void addLookupHistoryDisplaySideListener(IConfigListener iConfigListener) { 154 | 155 | } 156 | 157 | @Override 158 | public int getMaxLookupHistoryRows() { 159 | return 0; 160 | } 161 | 162 | @Override 163 | public int getMaxLookupHistoryIngredients() { 164 | return 0; 165 | } 166 | 167 | @Override 168 | public HistoryDisplaySide getLookupHistoryDisplaySide() { 169 | return null; 170 | } 171 | 172 | //? if >=21.1 { 173 | @Override 174 | public boolean isIngredientsSummaryEnabled() { 175 | return false; 176 | } 177 | //?} 178 | 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/recipe/ExtendedSmithingRecipe.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.recipe; 2 | 3 | import dev.emi.emi.api.recipe.EmiRecipe; 4 | import dev.emi.emi.api.recipe.EmiRecipeCategory; 5 | import dev.emi.emi.api.recipe.VanillaEmiRecipeCategories; 6 | import dev.emi.emi.api.render.EmiTexture; 7 | import dev.emi.emi.api.stack.EmiIngredient; 8 | import dev.emi.emi.api.stack.EmiStack; 9 | import dev.emi.emi.api.widget.WidgetHolder; 10 | import dev.nolij.toomanyrecipeviewers.TooManyRecipeViewers; 11 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.builder.TMRVIngredientCollector; 12 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.ingredient.TMRVSlotWidget; 13 | import mezz.jei.api.gui.builder.IIngredientAcceptor; 14 | import mezz.jei.api.recipe.RecipeIngredientRole; 15 | import mezz.jei.api.recipe.category.extensions.vanilla.smithing.ISmithingCategoryExtension; 16 | import mezz.jei.common.util.ImmutableRect2i; 17 | import mezz.jei.library.focus.FocusGroup; 18 | import net.minecraft.resources.ResourceLocation; 19 | import net.minecraft.world.item.crafting.SmithingRecipe; 20 | import org.jetbrains.annotations.Nullable; 21 | 22 | import java.util.List; 23 | import java.util.function.Consumer; 24 | 25 | @SuppressWarnings("NonExtendableApiUsage") 26 | public class ExtendedSmithingRecipe implements EmiRecipe { 27 | 28 | private final TooManyRecipeViewers runtime; 29 | private final R backingRecipe; 30 | private final ISmithingCategoryExtension extension; 31 | private final ResourceLocation id; 32 | 33 | public ExtendedSmithingRecipe(TooManyRecipeViewers runtime, R backingRecipe, ISmithingCategoryExtension extension, ResourceLocation id) { 34 | this.runtime = runtime; 35 | this.backingRecipe = backingRecipe; 36 | this.extension = extension; 37 | this.id = id; 38 | } 39 | 40 | @Override 41 | public EmiRecipeCategory getCategory() { 42 | return VanillaEmiRecipeCategories.SMITHING; 43 | } 44 | 45 | @Override 46 | public @Nullable ResourceLocation getId() { 47 | return id; 48 | } 49 | 50 | @Override 51 | public List getInputs() { 52 | return List.of( 53 | collect(extension::setTemplate).getEMIIngredient(), 54 | collect(extension::setBase).getEMIIngredient(), 55 | collect(extension::setAddition).getEMIIngredient() 56 | ); 57 | } 58 | 59 | @Override 60 | public List getOutputs() { 61 | return collect(extension::setOutput).getEMIStacks(); 62 | } 63 | 64 | @Override 65 | public int getDisplayWidth() { 66 | return 112; 67 | } 68 | 69 | @Override 70 | public int getDisplayHeight() { 71 | return 18; 72 | } 73 | 74 | @FunctionalInterface 75 | private interface SetMethod { 76 | > void set(R recipe, T ingredientAcceptor); 77 | } 78 | 79 | private TMRVIngredientCollector collect(SetMethod setMethod) { 80 | final var collector = new TMRVIngredientCollector(runtime.ingredientManager); 81 | setMethod.set(backingRecipe, collector); 82 | 83 | return collector; 84 | } 85 | 86 | private TMRVSlotWidget addSlot(WidgetHolder widgets, SetMethod setMethod, ImmutableRect2i rect, RecipeIngredientRole role) { 87 | final var slot = new TMRVSlotWidget(runtime.ingredientManager, role, rect); 88 | setMethod.set(backingRecipe, slot.getIngredientCollector()); 89 | return widgets.add(slot); 90 | } 91 | 92 | @Override 93 | public void addWidgets(WidgetHolder widgets) { 94 | widgets.addTexture(EmiTexture.EMPTY_ARROW, 62, 1); 95 | final var origin = new ImmutableRect2i(1, 1, 16, 16); 96 | final var templateSlot = addSlot(widgets, extension::setTemplate, origin, RecipeIngredientRole.INPUT); 97 | final var baseSlot = addSlot(widgets, extension::setBase, origin.addOffset(18, 0), RecipeIngredientRole.INPUT); 98 | final var additionSlot = addSlot(widgets, extension::setAddition, origin.addOffset(36, 0), RecipeIngredientRole.INPUT); 99 | final var outputSlot = addSlot(widgets, extension::setOutput, origin.addOffset(94, 0), RecipeIngredientRole.OUTPUT); 100 | outputSlot.recipeContext(this); 101 | extension.onDisplayedIngredientsUpdate(backingRecipe, templateSlot, baseSlot, additionSlot, outputSlot, FocusGroup.EMPTY); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/gui/builder/TMRVTooltipBuilder.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.builder; 2 | 3 | import com.mojang.datafixers.util.Either; 4 | import dev.emi.emi.mixin.accessor.OrderedTextTooltipComponentAccessor; 5 | import dev.nolij.toomanyrecipeviewers.util.ComponentFormattedCharSink; 6 | import dev.nolij.toomanyrecipeviewers.util.FormattedTextConsumer; 7 | import mezz.jei.api.gui.builder.ITooltipBuilder; 8 | import mezz.jei.api.ingredients.ITypedIngredient; 9 | import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; 10 | import net.minecraft.network.chat.Component; 11 | import net.minecraft.network.chat.FormattedText; 12 | import net.minecraft.util.FormattedCharSequence; 13 | import net.minecraft.world.inventory.tooltip.TooltipComponent; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Collection; 17 | import java.util.HashSet; 18 | import java.util.List; 19 | import java.util.Objects; 20 | 21 | public class TMRVTooltipBuilder implements ITooltipBuilder { 22 | 23 | private static Component getComponent(Object object) { 24 | return switch (object) { 25 | case Component component -> component; 26 | case FormattedCharSequence sequence -> ComponentFormattedCharSink.fromSequence(sequence); 27 | case OrderedTextTooltipComponentAccessor accessor -> ComponentFormattedCharSink.fromSequence(accessor.getText()); 28 | default -> null; 29 | }; 30 | } 31 | 32 | private final List lines = new ArrayList<>(); 33 | 34 | public TMRVTooltipBuilder(List lines) { 35 | this.lines.addAll(lines); 36 | } 37 | 38 | @Override 39 | public void add(FormattedText formattedText) { 40 | lines.add(formattedText); 41 | } 42 | 43 | @Override 44 | public void addAll(Collection collection) { 45 | collection.forEach(this::add); 46 | } 47 | 48 | @Override 49 | public void add(TooltipComponent tooltipComponent) { 50 | lines.add(tooltipComponent); 51 | } 52 | 53 | @Override 54 | public void setIngredient(ITypedIngredient typedIngredient) { 55 | 56 | } 57 | 58 | //? if >=21.1 { 59 | @Override 60 | public void clear() { 61 | lines.clear(); 62 | } 63 | 64 | @Override 65 | public void clearIngredient() { 66 | setIngredient(null); 67 | } 68 | 69 | @Override 70 | public List> getLines() { 71 | return lines.stream() 72 | .map(x -> (x instanceof FormattedText || x instanceof TooltipComponent) ? x : getComponent(x)) 73 | .filter(Objects::nonNull) 74 | .>map(line -> switch (line) { 75 | case FormattedText x -> Either.left(x); 76 | case TooltipComponent x -> Either.right(x); 77 | default -> throw new IllegalStateException(); 78 | }) 79 | .toList(); 80 | } 81 | //?} 82 | 83 | @SuppressWarnings("removal") 84 | @Override 85 | public List toLegacyToComponents() { 86 | return lines.stream() 87 | .map(TMRVTooltipBuilder::getComponent) 88 | .filter(Objects::nonNull) 89 | .toList(); 90 | } 91 | 92 | @SuppressWarnings("removal") 93 | @Override 94 | public void removeAll(List list) { 95 | final var toRemove = new HashSet<>(list); 96 | for (final var iterator = lines.iterator(); iterator.hasNext(); ) { 97 | final var line = iterator.next(); 98 | final var component = getComponent(line); 99 | if (component != null && toRemove.contains(component)) { 100 | iterator.remove(); 101 | toRemove.remove(component); 102 | } 103 | } 104 | } 105 | 106 | public List getClientTooltipComponents() { 107 | return lines.stream() 108 | .map(line -> switch (line) { 109 | case ClientTooltipComponent clientTooltipComponent -> clientTooltipComponent; 110 | case Component component -> ClientTooltipComponent.create(component.getVisualOrderText()); 111 | case FormattedText formattedText -> 112 | ClientTooltipComponent.create( 113 | Component.literal( 114 | FormattedTextConsumer.fromFormattedText(formattedText) 115 | ).getVisualOrderText() 116 | ); 117 | case TooltipComponent tooltipComponent -> ClientTooltipComponent.create(tooltipComponent); 118 | default -> null; 119 | }) 120 | .filter(Objects::nonNull) 121 | .toList(); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/gui/ingredient/TMRVTankWidget.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.ingredient; 2 | 3 | import dev.emi.emi.api.stack.EmiIngredient; 4 | import dev.emi.emi.api.widget.Bounds; 5 | import dev.emi.emi.api.widget.TankWidget; 6 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.drawable.OffsetDrawable; 7 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.builder.TMRVIngredientCollector; 8 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime.IngredientManager; 9 | import dev.nolij.toomanyrecipeviewers.impl.ingredient.ErrorEmiStack; 10 | import dev.nolij.toomanyrecipeviewers.util.FluidRendererParameters; 11 | import mezz.jei.api.gui.builder.IIngredientConsumer; 12 | import mezz.jei.api.gui.ingredient.IRecipeSlotRichTooltipCallback; 13 | import mezz.jei.api.ingredients.ITypedIngredient; 14 | import mezz.jei.api.recipe.RecipeIngredientRole; 15 | import mezz.jei.common.util.ImmutableRect2i; 16 | import net.minecraft.client.gui.GuiGraphics; 17 | import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; 18 | import net.minecraft.client.renderer.Rect2i; 19 | import org.jetbrains.annotations.Nullable; 20 | import org.jetbrains.annotations.Unmodifiable; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.Optional; 25 | import java.util.stream.Stream; 26 | 27 | public class TMRVTankWidget extends TankWidget implements ITMRVRecipeSlotDrawable, ITMRVSlotWidget { 28 | 29 | private final IngredientManager ingredientManager; 30 | private final RecipeIngredientRole role; 31 | private ImmutableRect2i rect; 32 | private boolean visible = true; 33 | 34 | private final TMRVIngredientCollector ingredientCollector; 35 | private @Nullable TMRVIngredientCollector overrideIngredientCollector = null; 36 | 37 | private @Nullable String name = null; 38 | 39 | private @Nullable OffsetDrawable background = null; 40 | private @Nullable OffsetDrawable overlay = null; 41 | 42 | private final List tooltipCallbacks = new ArrayList<>(); 43 | 44 | public TMRVTankWidget(IngredientManager ingredientManager, RecipeIngredientRole role, FluidRendererParameters fluidRendererParameters, ImmutableRect2i rect) { 45 | super(ErrorEmiStack.INSTANCE, rect.x(), rect.y(), rect.width(), rect.height(), fluidRendererParameters.capacity()); 46 | this.ingredientManager = ingredientManager; 47 | this.role = role; 48 | this.rect = rect; 49 | this.ingredientCollector = new TMRVIngredientCollector(ingredientManager); 50 | } 51 | 52 | private TMRVIngredientCollector getActiveIngredientCollector() { 53 | return overrideIngredientCollector == null ? ingredientCollector : overrideIngredientCollector; 54 | } 55 | 56 | @Override 57 | public TMRVIngredientCollector getIngredientCollector() { 58 | return ingredientCollector; 59 | } 60 | 61 | @Override 62 | public void setName(@Nullable String name) { 63 | this.name = name; 64 | } 65 | 66 | @Override 67 | public void setBackground(@Nullable OffsetDrawable background) { 68 | this.background = background; 69 | } 70 | 71 | @Override 72 | public void setOverlay(@Nullable OffsetDrawable overlay) { 73 | this.overlay = overlay; 74 | } 75 | 76 | @Override 77 | public void addTooltipCallbacks(List tooltipCallbacks) { 78 | this.tooltipCallbacks.addAll(tooltipCallbacks); 79 | } 80 | 81 | @Override 82 | public void setVisible(boolean visible) { 83 | this.visible = visible; 84 | } 85 | 86 | //region SlotWidget 87 | @Override 88 | public void render(GuiGraphics draw, int mouseX, int mouseY, float delta) { 89 | if (!visible) 90 | return; 91 | 92 | super.render(draw, mouseX, mouseY, delta); 93 | } 94 | 95 | @Override 96 | public Bounds getBounds() { 97 | if (!visible) 98 | return Bounds.EMPTY; 99 | 100 | final var rect = this.rect.expandBy(this.output ? 6 : 1); 101 | return new Bounds(rect.x(), rect.y(), rect.width(), rect.height()); 102 | } 103 | 104 | @Override 105 | public void drawBackground(GuiGraphics draw, int mouseX, int mouseY, float delta) { 106 | TMRVSlotWidget.drawJEIBackground(background, draw, rect.x(), rect.y()); 107 | 108 | super.drawBackground(draw, mouseX, mouseY, delta); 109 | } 110 | 111 | @Override 112 | public void drawOverlay(GuiGraphics draw, int mouseX, int mouseY, float delta) { 113 | TMRVSlotWidget.drawJEIOverlay(overlay, draw, rect.x(), rect.y()); 114 | 115 | super.drawOverlay(draw, mouseX, mouseY, delta); 116 | } 117 | 118 | @Override 119 | public EmiIngredient getStack() { 120 | return getActiveIngredientCollector().getEMIIngredient(); 121 | } 122 | 123 | @Override 124 | protected void addSlotTooltip(List list) { 125 | TMRVSlotWidget.applyTooltipCallbacks(list, tooltipCallbacks, this); 126 | 127 | super.addSlotTooltip(list); 128 | } 129 | //endregion 130 | 131 | //region ITMRVRecipeSlotDrawable 132 | @Override 133 | public IIngredientConsumer createDisplayOverrides() { 134 | if (overrideIngredientCollector == null) 135 | overrideIngredientCollector = new TMRVIngredientCollector(ingredientManager); 136 | 137 | return overrideIngredientCollector; 138 | } 139 | 140 | @Override 141 | public void clearDisplayOverrides() { 142 | overrideIngredientCollector = null; 143 | } 144 | 145 | @SuppressWarnings("removal") 146 | @Override 147 | public Rect2i getRect() { 148 | return rect.toMutable(); 149 | } 150 | 151 | @Override 152 | public void setPosition(int x, int y) { 153 | this.rect = new ImmutableRect2i(x, y, rect.width(), rect.height()); 154 | } 155 | 156 | @Override 157 | public Stream> getAllIngredients() { 158 | return getActiveIngredientCollector().stream(); 159 | } 160 | 161 | //? if >=21.1 { 162 | @Override 163 | public @Unmodifiable List<@Nullable ITypedIngredient> getAllIngredientsList() { 164 | return getActiveIngredientCollector().getCollectedIngredients(); 165 | } 166 | //?} 167 | 168 | @Override 169 | public Optional> getDisplayedIngredient() { 170 | return getAllIngredients().findFirst(); 171 | } 172 | 173 | @Override 174 | public RecipeIngredientRole getRole() { 175 | return this.role; 176 | } 177 | 178 | @Override 179 | public boolean isEmpty() { 180 | return getActiveIngredientCollector().isEmpty(); 181 | } 182 | 183 | @Override 184 | public Optional getSlotName() { 185 | return Optional.ofNullable(name); 186 | } 187 | //endregion 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/runtime/JEIKeyMappings.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime; 2 | 3 | import com.mojang.blaze3d.platform.InputConstants; 4 | import dev.emi.emi.config.EmiConfig; 5 | import dev.emi.emi.input.EmiBind; 6 | import mezz.jei.api.runtime.IJeiKeyMapping; 7 | import mezz.jei.common.input.IInternalKeyMappings; 8 | import net.minecraft.client.KeyMapping; 9 | import net.minecraft.network.chat.Component; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.function.Supplier; 13 | 14 | public class JEIKeyMappings implements IInternalKeyMappings { 15 | 16 | private record EMIDelegatedJEIKeyMapping(Supplier delegateGetter) implements IJeiKeyMapping { 17 | 18 | @Override 19 | public boolean isActiveAndMatches(InputConstants.@NotNull Key key) { 20 | return switch (key.getType()) { 21 | case KEYSYM -> delegateGetter.get().matchesKey(key.getValue(), 0); 22 | case SCANCODE -> delegateGetter.get().matchesKey(0, key.getValue()); 23 | case MOUSE -> delegateGetter.get().matchesMouse(key.getValue()); 24 | }; 25 | } 26 | 27 | @Override 28 | public boolean isUnbound() { 29 | return !delegateGetter.get().isBound(); 30 | } 31 | 32 | @Override 33 | public @NotNull Component getTranslatedKeyMessage() { 34 | return delegateGetter.get().getBindText(); 35 | } 36 | 37 | } 38 | 39 | private record MinecraftDelegatedJEIKeyMapping(KeyMapping delegate) implements IJeiKeyMapping { 40 | 41 | @Override 42 | public boolean isActiveAndMatches(InputConstants.@NotNull Key key) { 43 | return delegate.isActiveAndMatches(key); 44 | } 45 | 46 | @Override 47 | public boolean isUnbound() { 48 | return delegate.isUnbound(); 49 | } 50 | 51 | @Override 52 | public @NotNull Component getTranslatedKeyMessage() { 53 | return delegate.getTranslatedKeyMessage(); 54 | } 55 | 56 | } 57 | 58 | private static class DummyJEIKeyMapping implements IJeiKeyMapping { 59 | 60 | @Override 61 | public boolean isActiveAndMatches(InputConstants.@NotNull Key key) { 62 | return false; 63 | } 64 | 65 | @Override 66 | public boolean isUnbound() { 67 | return true; 68 | } 69 | 70 | @Override 71 | public @NotNull Component getTranslatedKeyMessage() { 72 | return Component.translatable("key.keyboard.unknown"); 73 | } 74 | 75 | } 76 | 77 | private final IJeiKeyMapping dummy = new DummyJEIKeyMapping(); 78 | 79 | private final IJeiKeyMapping toggleVisibility = new EMIDelegatedJEIKeyMapping(() -> EmiConfig.toggleVisibility); 80 | private final IJeiKeyMapping focusSearch = new EMIDelegatedJEIKeyMapping(() -> EmiConfig.focusSearch); 81 | private final IJeiKeyMapping back = new EMIDelegatedJEIKeyMapping(() -> EmiConfig.back); 82 | private final IJeiKeyMapping viewRecipes = new EMIDelegatedJEIKeyMapping(() -> EmiConfig.viewRecipes); 83 | private final IJeiKeyMapping viewUses = new EMIDelegatedJEIKeyMapping(() -> EmiConfig.viewUses); 84 | private final IJeiKeyMapping cheatOneToInventory = new EMIDelegatedJEIKeyMapping(() -> EmiConfig.cheatOneToInventory); 85 | private final IJeiKeyMapping cheatStackToInventory = new EMIDelegatedJEIKeyMapping(() -> EmiConfig.cheatStackToInventory); 86 | 87 | @Override 88 | public IJeiKeyMapping getToggleOverlay() { 89 | return toggleVisibility; 90 | } 91 | 92 | @Override 93 | public IJeiKeyMapping getFocusSearch() { 94 | return focusSearch; 95 | } 96 | 97 | @Override 98 | public IJeiKeyMapping getToggleCheatMode() { 99 | return dummy; 100 | } 101 | 102 | @Override 103 | public IJeiKeyMapping getToggleEditMode() { 104 | return dummy; 105 | } 106 | 107 | @Override 108 | public IJeiKeyMapping getToggleCheatModeConfigButton() { 109 | return dummy; 110 | } 111 | 112 | @Override 113 | public IJeiKeyMapping getRecipeBack() { 114 | return back; 115 | } 116 | 117 | @Override 118 | public IJeiKeyMapping getPreviousCategory() { 119 | return dummy; 120 | } 121 | 122 | @Override 123 | public IJeiKeyMapping getNextCategory() { 124 | return dummy; 125 | } 126 | 127 | @Override 128 | public IJeiKeyMapping getPreviousRecipePage() { 129 | return dummy; 130 | } 131 | 132 | @Override 133 | public IJeiKeyMapping getNextRecipePage() { 134 | return dummy; 135 | } 136 | 137 | @Override 138 | public IJeiKeyMapping getPreviousPage() { 139 | return dummy; 140 | } 141 | 142 | @Override 143 | public IJeiKeyMapping getNextPage() { 144 | return dummy; 145 | } 146 | 147 | @Override 148 | public IJeiKeyMapping getCloseRecipeGui() { 149 | return dummy; 150 | } 151 | 152 | @Override 153 | public IJeiKeyMapping getBookmark() { 154 | return dummy; 155 | } 156 | 157 | @Override 158 | public IJeiKeyMapping getToggleBookmarkOverlay() { 159 | return dummy; 160 | } 161 | 162 | @Override 163 | public @NotNull IJeiKeyMapping getShowRecipe() { 164 | return viewRecipes; 165 | } 166 | 167 | @Override 168 | public @NotNull IJeiKeyMapping getShowUses() { 169 | return viewUses; 170 | } 171 | 172 | @Override 173 | public IJeiKeyMapping getTransferRecipeBookmark() { 174 | return dummy; 175 | } 176 | 177 | @Override 178 | public IJeiKeyMapping getMaxTransferRecipeBookmark() { 179 | return dummy; 180 | } 181 | 182 | @Override 183 | public IJeiKeyMapping getCheatOneItem() { 184 | return cheatOneToInventory; 185 | } 186 | 187 | @Override 188 | public IJeiKeyMapping getCheatItemStack() { 189 | return cheatStackToInventory; 190 | } 191 | 192 | @Override 193 | public IJeiKeyMapping getToggleHideIngredient() { 194 | return dummy; 195 | } 196 | 197 | @Override 198 | public IJeiKeyMapping getToggleWildcardHideIngredient() { 199 | return dummy; 200 | } 201 | 202 | @Override 203 | public IJeiKeyMapping getHoveredClearSearchBar() { 204 | return dummy; 205 | } 206 | 207 | @Override 208 | public IJeiKeyMapping getPreviousSearch() { 209 | return dummy; 210 | } 211 | 212 | @Override 213 | public IJeiKeyMapping getNextSearch() { 214 | return dummy; 215 | } 216 | 217 | @Override 218 | public IJeiKeyMapping getCopyRecipeId() { 219 | return dummy; 220 | } 221 | 222 | @Override 223 | public IJeiKeyMapping getEscapeKey() { 224 | return dummy; // TODO 225 | } 226 | 227 | @Override 228 | public IJeiKeyMapping getLeftClick() { 229 | return dummy; // TODO 230 | } 231 | 232 | @Override 233 | public IJeiKeyMapping getRightClick() { 234 | return dummy; // TODO 235 | } 236 | 237 | @Override 238 | public IJeiKeyMapping getEnterKey() { 239 | return dummy; // TODO 240 | } 241 | 242 | } 243 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/gui/builder/TMRVIngredientCollector.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.builder; 2 | 3 | import dev.emi.emi.api.stack.EmiIngredient; 4 | import dev.emi.emi.api.stack.EmiStack; 5 | import dev.emi.emi.api.stack.FluidEmiStack; 6 | import dev.emi.emi.api.stack.ItemEmiStack; 7 | import dev.emi.emi.jemi.JemiStack; 8 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime.IngredientManager; 9 | import dev.nolij.toomanyrecipeviewers.impl.ingredient.ErrorIngredient; 10 | import dev.nolij.toomanyrecipeviewers.util.IStackish; 11 | import mezz.jei.api.constants.VanillaTypes; 12 | import mezz.jei.api.gui.builder.IIngredientAcceptor; 13 | import mezz.jei.api.gui.builder.IIngredientConsumer; 14 | import mezz.jei.api.ingredients.IIngredientRenderer; 15 | import mezz.jei.api.ingredients.IIngredientType; 16 | import mezz.jei.api.ingredients.ITypedIngredient; 17 | import mezz.jei.library.ingredients.TypedIngredient; 18 | import mezz.jei.library.ingredients.itemStacks.TypedItemStack; 19 | import net.minecraft.core.component.DataComponentPatch; 20 | import net.minecraft.world.item.ItemStack; 21 | import net.minecraft.world.level.ItemLike; 22 | import net.minecraft.world.level.material.Fluid; 23 | import net.neoforged.neoforge.fluids.FluidStack; 24 | import org.jetbrains.annotations.Nullable; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.Optional; 30 | import java.util.stream.Stream; 31 | 32 | @SuppressWarnings({"NonExtendableApiUsage", "unchecked", "UnstableApiUsage"}) 33 | public class TMRVIngredientCollector implements IIngredientAcceptor { 34 | 35 | private final IngredientManager ingredientManager; 36 | private final ArrayList> collectedIngredients = new ArrayList<>(); 37 | 38 | public TMRVIngredientCollector(IngredientManager ingredientManager) { 39 | this.ingredientManager = ingredientManager; 40 | } 41 | 42 | public Stream> stream() { 43 | return collectedIngredients.stream(); 44 | } 45 | 46 | public List> getCollectedIngredients() { 47 | return stream().toList(); 48 | } 49 | 50 | public boolean isEmpty() { 51 | return collectedIngredients.isEmpty(); 52 | } 53 | 54 | public List getEMIStacks() { 55 | return collectedIngredients.stream().map(ingredientManager::getEMIStack).toList(); 56 | } 57 | 58 | public EmiIngredient getEMIIngredient() { 59 | return EmiIngredient.of(getEMIStacks()); 60 | } 61 | 62 | public EmiIngredient getEMIIngredient(Map, IIngredientRenderer> rendererOverrides) { 63 | return EmiIngredient.of(collectedIngredients.stream() 64 | .map(typedIngredient -> { 65 | final var type = typedIngredient.getType(); 66 | if (!rendererOverrides.containsKey(type)) 67 | return ingredientManager.getEMIStack(typedIngredient); 68 | 69 | //noinspection rawtypes 70 | return new JemiStack(type, ingredientManager.getIngredientHelper(type), rendererOverrides.get(type), typedIngredient.getIngredient()); 71 | }) 72 | .toList()); 73 | } 74 | 75 | public void copy(TMRVIngredientCollector other) { 76 | collectedIngredients.addAll(other.collectedIngredients); 77 | } 78 | 79 | //region IIngredientAcceptor 80 | @Override 81 | public TMRVIngredientCollector addIngredient(IIngredientType type, I ingredient) { 82 | if (type == VanillaTypes.ITEM_STACK) { 83 | if (ingredient instanceof ItemStack itemStack) 84 | collectedIngredients.add(TypedItemStack.create(itemStack)); 85 | else 86 | collectedIngredients.add(ErrorIngredient.TYPED_INSTANCE); 87 | } else { 88 | final var typedIngredient = TypedIngredient.createAndFilterInvalid(ingredientManager, type, ingredient, false) 89 | //? if <21.1 90 | /*.orElse(null)*/ 91 | ; 92 | collectedIngredients.add(typedIngredient != null ? typedIngredient : ErrorIngredient.TYPED_INSTANCE); 93 | } 94 | 95 | return this; 96 | } 97 | 98 | @Override 99 | public TMRVIngredientCollector addIngredients(IIngredientType type, List<@Nullable I> ingredients) { 100 | for (final var ingredient : ingredients) { 101 | addIngredient(type, ingredient); 102 | } 103 | 104 | return this; 105 | } 106 | 107 | @Override 108 | public TMRVIngredientCollector addIngredientsUnsafe(List ingredients) { 109 | for (final var ingredient : ingredients) { 110 | final var type = ingredientManager.getIngredientType(ingredient); 111 | if (type != null) 112 | collectedIngredients.add(TypedIngredient.createUnvalidated(type, ingredient)); 113 | else 114 | collectedIngredients.add(ErrorIngredient.TYPED_INSTANCE); 115 | } 116 | 117 | return this; 118 | } 119 | 120 | @Override 121 | public TMRVIngredientCollector addTypedIngredient(ITypedIngredient typedIngredient) { 122 | if (typedIngredient instanceof IStackish) { 123 | collectedIngredients.add(typedIngredient); 124 | } else { 125 | final var copy = TypedIngredient.defensivelyCopyTypedIngredientFromApi(ingredientManager, typedIngredient) 126 | //? if <21.1 127 | /*.orElse(null)*/ 128 | ; 129 | collectedIngredients.add(copy != null ? copy : ErrorIngredient.TYPED_INSTANCE); 130 | } 131 | 132 | return this; 133 | } 134 | 135 | @Override 136 | public TMRVIngredientCollector addTypedIngredients(List> typedIngredients) { 137 | for (final var typedIngredient : typedIngredients) { 138 | addTypedIngredient(typedIngredient); 139 | } 140 | 141 | return this; 142 | } 143 | 144 | @Override 145 | public TMRVIngredientCollector addOptionalTypedIngredients(List>> typedIngredients) { 146 | typedIngredients.stream() 147 | .map(x -> x.orElse(null)) 148 | .forEach(this::addTypedIngredient); 149 | 150 | return this; 151 | } 152 | 153 | @Override 154 | public IIngredientConsumer addItemLike(ItemLike itemLike) { 155 | return addTypedIngredient((ITypedIngredient) ItemEmiStack.of(itemLike)); 156 | } 157 | 158 | @Override 159 | public TMRVIngredientCollector addFluidStack(Fluid fluid) { 160 | return addTypedIngredient((ITypedIngredient) FluidEmiStack.of(fluid)); 161 | } 162 | 163 | @Override 164 | public TMRVIngredientCollector addFluidStack(Fluid fluid, long amount) { 165 | return addTypedIngredient((ITypedIngredient) FluidEmiStack.of(fluid, amount)); 166 | } 167 | 168 | @Override 169 | public TMRVIngredientCollector addFluidStack(Fluid fluid, long amount, DataComponentPatch dataComponentPatch) { 170 | return addTypedIngredient((ITypedIngredient) FluidEmiStack.of(fluid, dataComponentPatch, amount)); 171 | } 172 | //endregion 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/common/network/ConnectionToServer.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.common.network; 2 | 3 | //? if >=21.1 { 4 | import mezz.jei.common.network.packets.PlayToServerPacket; 5 | import org.jetbrains.annotations.NotNull; 6 | //?} else { 7 | /*import mezz.jei.common.network.packets.PacketJei; 8 | import mezz.jei.common.Constants; 9 | import net.neoforged.neoforge.network.NetworkDirection; 10 | import net.neoforged.neoforge.network.NetworkHooks; 11 | *///?} 12 | import dev.emi.emi.api.recipe.EmiRecipeCategory; 13 | import dev.emi.emi.api.stack.EmiIngredient; 14 | import dev.emi.emi.api.stack.EmiStack; 15 | import dev.emi.emi.api.widget.WidgetHolder; 16 | import dev.emi.emi.api.EmiApi; 17 | import dev.emi.emi.api.recipe.EmiRecipe; 18 | import dev.emi.emi.api.recipe.handler.EmiCraftContext; 19 | import dev.emi.emi.api.recipe.handler.StandardRecipeHandler; 20 | import dev.emi.emi.network.EmiNetwork; 21 | import dev.emi.emi.network.FillRecipeC2SPacket; 22 | import dev.emi.emi.platform.EmiClient; 23 | import dev.emi.emi.registry.EmiRecipeFiller; 24 | import dev.nolij.toomanyrecipeviewers.mixin.PacketRecipeTransferAccessor; 25 | import mezz.jei.common.network.IConnectionToServer; 26 | import mezz.jei.common.network.packets.PacketRecipeTransfer; 27 | import mezz.jei.common.transfer.TransferOperation; 28 | import net.minecraft.client.Minecraft; 29 | import net.minecraft.resources.ResourceLocation; 30 | import net.minecraft.world.inventory.AbstractContainerMenu; 31 | import net.minecraft.world.inventory.Slot; 32 | import net.minecraft.world.item.ItemStack; 33 | import net.neoforged.neoforge.network.PacketDistributor; 34 | import org.jetbrains.annotations.Nullable; 35 | 36 | import java.util.ArrayList; 37 | import java.util.List; 38 | import java.util.Optional; 39 | 40 | public class ConnectionToServer implements IConnectionToServer { 41 | 42 | private final boolean serverHasJEI; 43 | 44 | public ConnectionToServer() { 45 | final var clientPacketListener = Minecraft.getInstance().getConnection(); 46 | if (clientPacketListener == null || 47 | clientPacketListener.getConnection().isMemoryConnection()) { 48 | serverHasJEI = false; 49 | } else { 50 | //? if >=21.1 { 51 | serverHasJEI = clientPacketListener.hasChannel(PacketRecipeTransfer.TYPE); 52 | //?} else { 53 | /*final var connection = clientPacketListener.getConnection(); 54 | final var connectionData = NetworkHooks.getConnectionData(connection); 55 | if (connectionData == null) { 56 | serverHasJEI = false; 57 | } else { 58 | final var channels = connectionData.getChannels(); 59 | serverHasJEI = channels.containsKey(Constants.NETWORK_CHANNEL_ID); 60 | } 61 | *///?} 62 | } 63 | } 64 | 65 | @Override 66 | public boolean isJeiOnServer() { 67 | return true; 68 | } 69 | 70 | @Override 71 | //? if >=21.1 { 72 | public > void sendPacketToServer(@NotNull T packet) { 73 | //?} else 74 | /*public void sendPacketToServer(PacketJei packet) {*/ 75 | if (!(packet instanceof PacketRecipeTransfer recipeTransferPacket)) 76 | return; 77 | 78 | if (serverHasJEI) { 79 | //? if >=21.1 { 80 | PacketDistributor.sendToServer(recipeTransferPacket); 81 | //?} else { 82 | /*final var packetData = packet.getPacketData(); 83 | final var payload = NetworkDirection.PLAY_TO_SERVER.buildPacket(packetData, Constants.NETWORK_CHANNEL_ID); 84 | PacketDistributor.SERVER.noArg().send(payload.getThis()); 85 | *///?} 86 | return; 87 | } 88 | 89 | handle(recipeTransferPacket); 90 | } 91 | 92 | public static void handle(PacketRecipeTransfer recipeTransferPacket) { 93 | final var containerScreen = EmiApi.getHandledScreen(); 94 | if (containerScreen == null) { 95 | return; 96 | } 97 | 98 | final var containerMenu = containerScreen.getMenu(); 99 | final var inventorySlots = recipeTransferPacket.inventorySlots.stream() 100 | //? if <21.1 101 | /*.map(x -> x.index)*/ 102 | .map(containerMenu::getSlot).toList(); 103 | final var craftingSlots = recipeTransferPacket.craftingSlots.stream() 104 | //? if <21.1 105 | /*.map(x -> x.index)*/ 106 | .map(containerMenu::getSlot).toList(); 107 | 108 | final var craftingSlotIndex = craftingSlots.stream().mapToInt(s -> s.index).toArray(); 109 | 110 | final var transferOperationIndex = new ArrayList>(craftingSlots.size()); 111 | for (final var craftingSlotId : craftingSlotIndex) { 112 | transferOperationIndex.add(recipeTransferPacket.transferOperations 113 | .stream().filter(x -> x.craftingSlotId() == craftingSlotId) 114 | .findFirst()); 115 | } 116 | 117 | // We need to compute the desired number of items to place in each slot 118 | 119 | //noinspection rawtypes 120 | var fakeRecipeHandler = new StandardRecipeHandler() { 121 | @Override 122 | public List getInputSources(AbstractContainerMenu handler) { 123 | return inventorySlots; 124 | } 125 | 126 | @Override 127 | public List getCraftingSlots(AbstractContainerMenu handler) { 128 | return craftingSlots; 129 | } 130 | 131 | @Override 132 | public boolean supportsRecipe(EmiRecipe recipe) { 133 | return true; 134 | } 135 | }; 136 | 137 | var fakeIngredients = transferOperationIndex.stream() 138 | .map(o -> o.map(t -> containerMenu.getSlot(t.inventorySlotId()))) 139 | .map(o -> o.map(s -> s.getItem().copyWithCount(1))) 140 | .map(o -> o.map(EmiStack::of).orElse(EmiStack.EMPTY)) 141 | .map(EmiIngredient.class::cast) 142 | .toList(); 143 | 144 | var fakeRecipe = new EmiRecipe() { 145 | @Override 146 | public EmiRecipeCategory getCategory() { 147 | throw new UnsupportedOperationException(); 148 | } 149 | 150 | @Override 151 | public @Nullable ResourceLocation getId() { 152 | throw new UnsupportedOperationException(); 153 | } 154 | 155 | @Override 156 | public List getInputs() { 157 | return fakeIngredients; 158 | } 159 | 160 | @Override 161 | public List getOutputs() { 162 | return List.of(); 163 | } 164 | 165 | @Override 166 | public int getDisplayWidth() { 167 | throw new UnsupportedOperationException(); 168 | } 169 | 170 | @Override 171 | public int getDisplayHeight() { 172 | throw new UnsupportedOperationException(); 173 | } 174 | 175 | @Override 176 | public void addWidgets(WidgetHolder widgets) { 177 | throw new UnsupportedOperationException(); 178 | } 179 | }; 180 | 181 | //noinspection unchecked 182 | List stacks = EmiRecipeFiller.getStacks(fakeRecipeHandler, fakeRecipe, containerScreen, ((PacketRecipeTransferAccessor)recipeTransferPacket).tmrv$isMaxTransfer() ? Integer.MAX_VALUE : 1); 183 | 184 | if (EmiClient.onServer) { 185 | EmiNetwork.sendToServer(new FillRecipeC2SPacket(containerMenu, 0, inventorySlots, craftingSlots, null, stacks)); 186 | } else { 187 | //noinspection unchecked 188 | EmiRecipeFiller.clientFill(fakeRecipeHandler, null, containerScreen, stacks, EmiCraftContext.Destination.NONE); 189 | } 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/gui/ingredient/TMRVSlotWidget.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.ingredient; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import dev.emi.emi.api.stack.EmiIngredient; 5 | import dev.emi.emi.api.widget.Bounds; 6 | import dev.emi.emi.api.widget.SlotWidget; 7 | import dev.emi.emi.runtime.EmiDrawContext; 8 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.builder.TMRVTooltipBuilder; 9 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.drawable.OffsetDrawable; 10 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.builder.TMRVIngredientCollector; 11 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime.IngredientManager; 12 | import dev.nolij.toomanyrecipeviewers.impl.ingredient.ErrorEmiStack; 13 | import mezz.jei.api.gui.builder.IIngredientConsumer; 14 | import mezz.jei.api.gui.ingredient.IRecipeSlotRichTooltipCallback; 15 | import mezz.jei.api.gui.ingredient.IRecipeSlotView; 16 | import mezz.jei.api.ingredients.IIngredientRenderer; 17 | import mezz.jei.api.ingredients.IIngredientType; 18 | import mezz.jei.api.ingredients.ITypedIngredient; 19 | import mezz.jei.api.recipe.RecipeIngredientRole; 20 | import mezz.jei.common.util.ImmutableRect2i; 21 | import net.minecraft.client.gui.GuiGraphics; 22 | import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; 23 | import net.minecraft.client.renderer.Rect2i; 24 | import org.jetbrains.annotations.Nullable; 25 | import org.jetbrains.annotations.Unmodifiable; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.Optional; 31 | import java.util.stream.Stream; 32 | 33 | import static dev.nolij.toomanyrecipeviewers.TooManyRecipeViewersMod.LOGGER; 34 | 35 | public class TMRVSlotWidget extends SlotWidget implements ITMRVRecipeSlotDrawable, ITMRVSlotWidget { 36 | 37 | static void drawJEIBackground(@Nullable OffsetDrawable background, GuiGraphics draw, int x, int y) { 38 | if (background == null) 39 | return; 40 | 41 | background.draw(draw, x, y); 42 | } 43 | 44 | static void drawJEIOverlay(@Nullable OffsetDrawable overlay, GuiGraphics draw, int x, int y) { 45 | if (overlay == null) 46 | return; 47 | 48 | final var context = EmiDrawContext.wrap(draw); 49 | 50 | RenderSystem.enableBlend(); 51 | context.push(); 52 | context.matrices().translate(0.0F, 0.0F, 200.0F); 53 | overlay.draw(context.raw(), x, y); 54 | context.pop(); 55 | } 56 | 57 | static void applyTooltipCallbacks(List list, List tooltipCallbacks, IRecipeSlotView slotView) { 58 | final var builder = new TMRVTooltipBuilder(list); 59 | 60 | for (final var tooltipCallback : tooltipCallbacks) { 61 | try { 62 | tooltipCallback.onRichTooltip(slotView, builder); 63 | } catch (Throwable t) { 64 | LOGGER.error("Error invoking JEI tooltip callback: ", t); 65 | } 66 | } 67 | 68 | list.clear(); 69 | list.addAll(builder.getClientTooltipComponents()); 70 | } 71 | 72 | private final IngredientManager ingredientManager; 73 | private final RecipeIngredientRole role; 74 | private ImmutableRect2i rect; 75 | private final Map, IIngredientRenderer> rendererOverrides; 76 | private boolean visible = true; 77 | 78 | private final TMRVIngredientCollector ingredientCollector; 79 | private @Nullable TMRVIngredientCollector overrideIngredientCollector = null; 80 | 81 | private @Nullable String name = null; 82 | 83 | private @Nullable OffsetDrawable background = null; 84 | private @Nullable OffsetDrawable overlay = null; 85 | 86 | private final List tooltipCallbacks = new ArrayList<>(); 87 | 88 | public TMRVSlotWidget(IngredientManager ingredientManager, RecipeIngredientRole role, ImmutableRect2i rect, Map, IIngredientRenderer> rendererOverrides) { 89 | super(ErrorEmiStack.INSTANCE, rect.x(), rect.y()); 90 | this.ingredientManager = ingredientManager; 91 | this.role = role; 92 | this.rect = rect; 93 | this.rendererOverrides = rendererOverrides; 94 | this.ingredientCollector = new TMRVIngredientCollector(ingredientManager); 95 | } 96 | 97 | public TMRVSlotWidget(IngredientManager ingredientManager, RecipeIngredientRole role, ImmutableRect2i rect) { 98 | this(ingredientManager, role, rect, Map.of()); 99 | } 100 | 101 | private TMRVIngredientCollector getActiveIngredientCollector() { 102 | return overrideIngredientCollector == null ? ingredientCollector : overrideIngredientCollector; 103 | } 104 | 105 | @Override 106 | public TMRVIngredientCollector getIngredientCollector() { 107 | return ingredientCollector; 108 | } 109 | 110 | @Override 111 | public void setName(@Nullable String name) { 112 | this.name = name; 113 | } 114 | 115 | @Override 116 | public void setBackground(@Nullable OffsetDrawable background) { 117 | this.background = background; 118 | } 119 | 120 | @Override 121 | public void setOverlay(@Nullable OffsetDrawable overlay) { 122 | this.overlay = overlay; 123 | } 124 | 125 | @Override 126 | public void addTooltipCallbacks(List tooltipCallbacks) { 127 | this.tooltipCallbacks.addAll(tooltipCallbacks); 128 | } 129 | 130 | @Override 131 | public void setVisible(boolean visible) { 132 | this.visible = visible; 133 | } 134 | 135 | //region SlotWidget 136 | @Override 137 | public void render(GuiGraphics draw, int mouseX, int mouseY, float delta) { 138 | if (!visible) 139 | return; 140 | 141 | super.render(draw, mouseX, mouseY, delta); 142 | } 143 | 144 | @Override 145 | public Bounds getBounds() { 146 | if (!visible) 147 | return Bounds.EMPTY; 148 | 149 | final var rect = this.rect.expandBy(this.output ? 6 : 1); 150 | return new Bounds(rect.x(), rect.y(), rect.width(), rect.height()); 151 | } 152 | 153 | @Override 154 | public void drawBackground(GuiGraphics draw, int mouseX, int mouseY, float delta) { 155 | drawJEIBackground(background, draw, rect.x(), rect.y()); 156 | 157 | super.drawBackground(draw, mouseX, mouseY, delta); 158 | } 159 | 160 | @Override 161 | public void drawOverlay(GuiGraphics draw, int mouseX, int mouseY, float delta) { 162 | drawJEIOverlay(overlay, draw, rect.x(), rect.y()); 163 | 164 | super.drawOverlay(draw, mouseX, mouseY, delta); 165 | } 166 | 167 | @Override 168 | public EmiIngredient getStack() { 169 | return getActiveIngredientCollector().getEMIIngredient(rendererOverrides); 170 | } 171 | 172 | @Override 173 | protected void addSlotTooltip(List list) { 174 | applyTooltipCallbacks(list, tooltipCallbacks, this); 175 | 176 | super.addSlotTooltip(list); 177 | } 178 | //endregion 179 | 180 | //region ITMRVRecipeSlotDrawable 181 | @Override 182 | public IIngredientConsumer createDisplayOverrides() { 183 | if (overrideIngredientCollector == null) 184 | overrideIngredientCollector = new TMRVIngredientCollector(ingredientManager); 185 | 186 | return overrideIngredientCollector; 187 | } 188 | 189 | @Override 190 | public void clearDisplayOverrides() { 191 | overrideIngredientCollector = null; 192 | } 193 | 194 | @SuppressWarnings("removal") 195 | @Override 196 | public Rect2i getRect() { 197 | return rect.toMutable(); 198 | } 199 | 200 | @Override 201 | public void setPosition(int x, int y) { 202 | this.rect = new ImmutableRect2i(x, y, rect.width(), rect.height()); 203 | } 204 | 205 | @Override 206 | public Stream> getAllIngredients() { 207 | return getActiveIngredientCollector().stream(); 208 | } 209 | 210 | //? if >=21.1 { 211 | @Override 212 | public @Unmodifiable List<@Nullable ITypedIngredient> getAllIngredientsList() { 213 | return getActiveIngredientCollector().getCollectedIngredients(); 214 | } 215 | //?} 216 | 217 | @Override 218 | public Optional> getDisplayedIngredient() { 219 | return getAllIngredients().findFirst(); 220 | } 221 | 222 | @Override 223 | public RecipeIngredientRole getRole() { 224 | return this.role; 225 | } 226 | 227 | @Override 228 | public boolean isEmpty() { 229 | return getActiveIngredientCollector().isEmpty(); 230 | } 231 | 232 | @Override 233 | public Optional getSlotName() { 234 | return Optional.ofNullable(name); 235 | } 236 | //endregion 237 | 238 | } 239 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IMPORTANT LICENSE NOTICE 2 | 3 | By using this project in any form, you hereby give your "express assent" for the terms of the license of this project (see [License](#license)), and acknowledge that I (the author of this project) have fulfilled my obligation under the license to "make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License". 4 | 5 | # TooManyRecipeViewers 6 | 7 | **T**oo**M**any**R**ecipe**V**iewers (or TMRV) is a compatibility layer for running [JEI](https://github.com/mezz/JustEnoughItems) plugins with [EMI](https://github.com/emilyploszaj/emi) **without** having to install [JEI](https://github.com/mezz/JustEnoughItems), written by Nolij. 8 | 9 | You'll need EMI installed to use this mod. 10 | 11 | # Why use TMRV over EMI+JEI (AKA JEMI)? 12 | 13 | JEMI is a compatibility layer built-in to EMI to get JEI plugins _mostly_ working with EMI by heavily relying on the JEI internals. It is designed to be as simplistic as possible and relies on JEI to process recipe data first before it is imported to EMI. 14 | 15 | TMRV is not like JEMI - it aims to completely replace the JEI API (NOTE: TMRV does contain some unmodified JEI internals - see [JEI Code Re-Use](#jei-code-re-use)). TMRV (where feasible) replaces JEI APIs with direct mappers to EMI APIs instead of loading the whole JEI registry and querying it after the fact. This has several advantages, including more efficient use of system resources, but is also a tradeoff, as TMRV's approach makes maintenance far more involved than what JEMI requires. JEMI is intentionally designed this way to allow EMI development to be focused on improving EMI itself, which is an approach I fully support. 16 | 17 | TL;DR: **TMRV is much more efficient than JEMI** at the cost of taking **much more effort** to maintain than JEMI does, and **nobody should expect EMI to put this amount of effort** in to supporting an API that was intentionally not referenced during design. Be happy EMI can even load JEI plugins out of the box to begin with - it still took a fair amount of effort. 18 | 19 | That being said, TMRV has two primary advantages over JEMI: 20 | 21 | ### 1. Plugin Compatibility 22 | 23 | TMRV has better coverage of JEI's API than JEMI does (with one exception - see [Known API Limitations](#known-api-limitations)). As of writing, this includes: 24 | 25 | - Better conversion of built-in recipe types (JEMI only supports crafting and info recipe types; TMRV supports all built-in JEI recipe types) 26 | - Ingredient/search alias support 27 | - `createRecipeExtras` support 28 | 29 | ### 2. Efficiency 30 | 31 | With TMRV, you will always load in to the world faster than with JEMI. This is because JEI plugin initialization blocks world load - you can't start playing until all JEI plugins are initialized. EMI loads plugins asynchronously _after_ the world is loaded. 32 | 33 | This means that even if TMRV loaded JEI plugins _slower_ than JEI does (not the case, it loads them measurably faster - see [benchmarks](#benchmarks)), worlds will _always_ load faster with TMRV than with JEMI. 34 | 35 | As already mentioned, TMRV replaces much of the JEI APIs with mappers to the corresponding EMI APIs - this means entire parts of the JEI internals can be outright removed. There's no need to initialize and store a whole JEI recipe registry - TMRV just converts JEI API calls to EMI ones, and converts the responses to the JEI format. Think of TMRV like Wine or Proton, and JEMI like a VM. JEMI uses real JEI, so there will be some scenarios where TMRV will error where JEMI won't (note that this doesn't necessarily mean that JEMI properly supports a scenario, it just means it looks like it does), but TMRV will generally be more efficient than JEMI. 36 | 37 | # Benchmarks 38 | 39 | The full results and steps followed to obtain them are documented in [BENCHMARKS.md](BENCHMARKS.md). These results were not cherry-picked. The instructions were followed exactly as documented in that file. I encourage the community to verify them. 40 | 41 | ### Load Times 42 | 43 | | | TMRV | JEMI | Comparison | 44 | |-----------|-----------------------------------------------------------|--------------------------------------------------------------|------------------------------------------------------------------| 45 | | Craftoria | 3201ms (2ms before world load, 3199ms after world load) | 8277ms (6751ms before world load, 1526ms after world load) | -5076ms (-6749ms before world load, +1673ms after world load) | 46 | | ATM10 | 7484ms (2ms before world load, 7482ms after world load) | 18658ms (14500ms before world load, 4158ms after world load) | -11174ms (-14498ms before world load, +3324ms after world load) | 47 | | ATM9 | 32392ms (2ms before world load, 32390ms after world load) | 49409ms (42590ms before world load, 6819ms after world load) | -17017ms (-42588ms before world load, +25571ms after world load) | 48 | 49 | ### Memory Usage 50 | 51 | | | TMRV | JEMI | Comparison | 52 | |-----------|-----------|-----------|--------------------------| 53 | | Craftoria | 2.722 GiB | 2.872 GiB | -153.6 MiB (approximate) | 54 | | ATM10 | 3.580 GiB | 4.491 GiB | -932.9 MiB (approximate) | 55 | | ATM9 | 4.345 GiB | 5.939 GiB | -1.594 GiB | 56 | 57 | # Known API Limitations 58 | 59 | ### JEI Config Files 60 | 61 | `.minecraft/config/jei/blacklist.json` is the only JEI config file that TMRV even reads. This file _should_ work fine for vanilla ingredient types and for modded ingredient types added by a JEI plugin (this does not include mods that support both JEI and EMI natively, such as Mekanism). This is meant to be a stop-gap for packs switching over from JEI. JEMI had a similar flaw. EMI has its own config for hiding ingredients - please use that instead. All other JEI config files are completely ignored by TMRV, and there are no plans to support them. 62 | 63 | ### Recipe Manager Plugins 64 | 65 | The JEI API supports "Recipe Manager Plugins". These plugins allow mods to control their own recipe registries and handle recipe lookups themselves at runtime. 66 | 67 | TMRV will attempt to extract recipes from these plugins, but this does not work for most plugins, and by no means provides proper support for the feature. Support beyond this is not planned. Recipe Manager plugins are an outdated concept that very few plugins still use, and they aren't possible to properly support without very invasive EMI mixins - something I do not intend to use in this project. 68 | 69 | ### Vanilla Recipe Category Extensions 70 | 71 | The JEI API supports "extensions" to the vanilla Crafting and Smithing recipe categories. Sufficient inspection (that would be necessary to determine feasibility of adding support) of how this part of the JEI API works is yet to be done. For now, though, these are unsupported by TMRV. 72 | 73 | ### Runtime Registry Changes 74 | 75 | The JEI API supports modifying the recipe and ingredient registries at runtime (ie after plugin registration is complete). This concept is not compatible with EMI, and it is not a practice I want to support. As such, after `IModPlugin.onRuntimeAvailable` has been invoked, all APIs for runtime registry modifications will throw an `IllegalStateException` if invoked to avoid potential confusion. 76 | 77 | # JEI Code Re-use 78 | 79 | The plan is to replace more of the JEI internals in future updates. However, some parts of JEI simply aren't worth re-implementing for various reasons. Regardless, enough of JEI has already been replaced in TMRV that I can confidently say: 80 | 81 | 1. It wouldn't be feasible to achieve the same improvements over JEMI with mixins (at least sanely), and 82 | 2. Enough of the JEI internals have been replaced or removed that I don't consider this unfair to JEI, especially given the fact that [JEI's license](https://github.com/mezz/JustEnoughItems/blob/d4ea796eb319efff2ff209f50c053c2a5a1dec05/LICENSE.txt) explicitly allows doing this. 83 | 84 | # License 85 | 86 | This project is licensed under OSL-3.0. For more information, see [LICENSE](LICENSE). 87 | 88 | Some code was copied from [EMI](https://github.com/emilyploszaj/emi) and [JEI](https://github.com/mezz/JustEnoughItems) in compliance with their copyright licenses. All modifications present in this project are licensed under the same license as the rest of this project, OSL-3.0. 89 | 90 | # ![YourKit](https://www.yourkit.com/images/yklogo.png) 91 | 92 | TooManyRecipeViewers uses [YourKit](https://www.yourkit.com) for ensuring code efficiency. 93 | 94 | YourKit supports open source projects with innovative and intelligent tools 95 | for monitoring and profiling Java and .NET applications. 96 | YourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), 97 | [YourKit .NET Profiler](https://www.yourkit.com/dotnet-profiler/), 98 | and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/). -------------------------------------------------------------------------------- /README_MODRINTH.md: -------------------------------------------------------------------------------- 1 | # IMPORTANT LICENSE NOTICE 2 | 3 | By using this project in any form, you hereby give your "express assent" for the terms of the license of this project (see [License](#license)), and acknowledge that I (the author of this project) have fulfilled my obligation under the license to "make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License". 4 | 5 | # TooManyRecipeViewers 6 | 7 | **T**oo**M**any**R**ecipe**V**iewers (or TMRV) is a compatibility layer for running [JEI](https://modrinth.com/mod/jei) plugins with [EMI](https://modrinth.com/mod/emi) **without** having to install [JEI](https://modrinth.com/mod/jei), written by Nolij. 8 | 9 | You'll need EMI installed to use this mod. 10 | 11 | # Why use TMRV over EMI+JEI (AKA JEMI)? 12 | 13 | JEMI is a compatibility layer built-in to EMI to get JEI plugins _mostly_ working with EMI by heavily relying on the JEI internals. It is designed to be as simplistic as possible and relies on JEI to process recipe data first before it is imported to EMI. 14 | 15 | TMRV is not like JEMI - it aims to completely replace the JEI API (NOTE: TMRV does contain some unmodified JEI internals - see [JEI Code Re-Use](#jei-code-re-use)). TMRV (where feasible) replaces JEI APIs with direct mappers to EMI APIs instead of loading the whole JEI registry and querying it after the fact. This has several advantages, including more efficient use of system resources, but is also a tradeoff, as TMRV's approach makes maintenance far more involved than what JEMI requires. JEMI is intentionally designed this way to allow EMI development to be focused on improving EMI itself, which is an approach I fully support. 16 | 17 | TL;DR: **TMRV is much more efficient than JEMI** at the cost of taking **much more effort** to maintain than JEMI does, and **nobody should expect EMI to put this amount of effort** in to supporting an API that was intentionally not referenced during design. Be happy EMI can even load JEI plugins out of the box to begin with - it still took a fair amount of effort. 18 | 19 | That being said, TMRV has two primary advantages over JEMI: 20 | 21 | ### 1. Plugin Compatibility 22 | 23 | TMRV has better coverage of JEI's API than JEMI does (with one exception - see [Known API Limitations](#known-api-limitations)). As of writing, this includes: 24 | 25 | - Better conversion of built-in recipe types (JEMI only supports crafting and info recipe types; TMRV supports all built-in JEI recipe types) 26 | - Ingredient/search alias support 27 | - `createRecipeExtras` support 28 | 29 | ### 2. Efficiency 30 | 31 | With TMRV, you will always load in to the world faster than with JEMI. This is because JEI plugin initialization blocks world load - you can't start playing until all JEI plugins are initialized. EMI loads plugins asynchronously _after_ the world is loaded. 32 | 33 | This means that even if TMRV loaded JEI plugins _slower_ than JEI does (not the case, it loads them measurably faster - see [benchmarks](#benchmarks)), worlds will _always_ load faster with TMRV than with JEMI. 34 | 35 | As already mentioned, TMRV replaces much of the JEI APIs with mappers to the corresponding EMI APIs - this means entire parts of the JEI internals can be outright removed. There's no need to initialize and store a whole JEI recipe registry - TMRV just converts JEI API calls to EMI ones, and converts the responses to the JEI format. Think of TMRV like Wine or Proton, and JEMI like a VM. JEMI uses real JEI, so there will be some scenarios where TMRV will error where JEMI won't (note that this doesn't necessarily mean that JEMI properly supports a scenario, it just means it looks like it does), but TMRV will generally be more efficient than JEMI. 36 | 37 | # Benchmarks 38 | 39 | The full results and steps followed to obtain them are documented in [BENCHMARKS.md](https://github.com/Nolij/TooManyRecipeViewers/raw/master/BENCHMARKS.md). These results were not cherry-picked. The instructions were followed exactly as documented in that file. I encourage the community to verify them. 40 | 41 | ### Load Times 42 | 43 | | | TMRV | JEMI | Comparison | 44 | |-----------------------------|-----------------------------------------------------------------------------|--------------------------------------------------------------------------------|------------------------------------------------------------------| 45 | | Craftoria    | 3201ms (2ms before world load, 3199ms after world load)    | 8277ms (6751ms before world load, 1526ms after world load)    | -5076ms (-6749ms before world load, +1673ms after world load) | 46 | | ATM10    | 7484ms (2ms before world load, 7482ms after world load)    | 18658ms (14500ms before world load, 4158ms after world load)    | -11174ms (-14498ms before world load, +3324ms after world load) | 47 | | ATM9    | 32392ms (2ms before world load, 32390ms after world load)    | 49409ms (42590ms before world load, 6819ms after world load)    | -17017ms (-42588ms before world load, +25571ms after world load) | 48 | 49 | ### Memory Usage 50 | 51 | | | TMRV | JEMI | Comparison | 52 | |-----------------------------|-----------------------------|-----------------------------|--------------------------| 53 | | Craftoria    | 2.722 GiB    | 2.872 GiB    | -153.6 MiB (approximate) | 54 | | ATM10    | 3.580 GiB    | 4.491 GiB    | -932.9 MiB (approximate) | 55 | | ATM9    | 4.345 GiB    | 5.939 GiB    | -1.594 GiB | 56 | 57 | # Known API Limitations 58 | 59 | ### JEI Config Files 60 | 61 | `.minecraft/config/jei/blacklist.json` is the only JEI config file that TMRV even reads. This file _should_ work fine for vanilla ingredient types and for modded ingredient types added by a JEI plugin (this does not include mods that support both JEI and EMI natively, such as Mekanism). This is meant to be a stop-gap for packs switching over from JEI. JEMI had a similar flaw. EMI has its own config for hiding ingredients - please use that instead. All other JEI config files are completely ignored by TMRV, and there are no plans to support them. 62 | 63 | ### Recipe Manager Plugins 64 | 65 | The JEI API supports "Recipe Manager Plugins". These plugins allow mods to control their own recipe registries and handle recipe lookups themselves at runtime. 66 | 67 | TMRV will attempt to extract recipes from these plugins, but this does not work for most plugins, and by no means provides proper support for the feature. Support beyond this is not planned. Recipe Manager plugins are an outdated concept that very few plugins still use, and they aren't possible to properly support without very invasive EMI mixins - something I do not intend to use in this project. 68 | 69 | ### Vanilla Recipe Category Extensions 70 | 71 | The JEI API supports "extensions" to the vanilla Crafting and Smithing recipe categories. Sufficient inspection (that would be necessary to determine feasibility of adding support) of how this part of the JEI API works is yet to be done. For now, though, these are unsupported by TMRV. 72 | 73 | ### Runtime Registry Changes 74 | 75 | The JEI API supports modifying the recipe and ingredient registries at runtime (ie after plugin registration is complete). This concept is not compatible with EMI, and it is not a practice I want to support. As such, after `IModPlugin.onRuntimeAvailable` has been invoked, all APIs for runtime registry modifications will throw an `IllegalStateException` if invoked to avoid potential confusion. 76 | 77 | # JEI Code Re-use 78 | 79 | The plan is to replace more of the JEI internals in future updates. However, some parts of JEI simply aren't worth re-implementing for various reasons. Regardless, enough of JEI has already been replaced in TMRV that I can confidently say: 80 | 81 | 1. It wouldn't be feasible to achieve the same improvements over JEMI with mixins (at least sanely), and 82 | 2. Enough of the JEI internals have been replaced or removed that I don't consider this unfair to JEI, especially given the fact that [JEI's license](https://github.com/mezz/JustEnoughItems/blob/d4ea796eb319efff2ff209f50c053c2a5a1dec05/LICENSE.txt) explicitly allows doing this. 83 | 84 | # License 85 | 86 | This project is licensed under OSL-3.0. For more information, see [LICENSE](https://github.com/Nolij/TooManyRecipeViewers/raw/master/LICENSE). 87 | 88 | Some code was copied from [EMI](https://github.com/emilyploszaj/emi) and [JEI](https://github.com/mezz/JustEnoughItems) in compliance with their copyright licenses. All modifications present in this project are licensed under the same license as the rest of this project, OSL-3.0. -------------------------------------------------------------------------------- /BENCHMARKS.md: -------------------------------------------------------------------------------- 1 | # System Details 2 | 3 | All benchmarks were performed in the following environment: 4 | 5 | ``` 6 | CPU: AMD Ryzen 9 5950x 7 | RAM: 128 GB DDR4-3600 8 | Disk: PCIe Gen 4 NVMe using BtrFS 9 | OS: Arch Linux (Linux 6.15.2-arch1-1) 10 | DE: KDE Plasma 6.3.5 (Wayland) 11 | GLFW: aur/glfw-wayland-minecraft-cursorfix 3.4-6 12 | JVM: aur/jdk21-temurin 21.0.7.u6-1 13 | Launcher: extra/prismlauncher 9.4-1 14 | JVM Flags: -XX:+UseShenandoahGC -XX:+UnlockExperimentalVMOptions -XX:+AlwaysPreTouch -XX:+UseStringDeduplication -Dfml.ignorePatchDiscrepancies=true -Dfml.ignoreInvalidMinecraftCertificates=true -XX:-OmitStackTraceInFastThrow -XX:+OptimizeStringConcat -Dfml.readTimeout=180 15 | ``` 16 | 17 | Three modpacks were tested: 18 | 19 | - [Craftoria 1.22.2](https://legacy.curseforge.com/minecraft/modpacks/craftoria/files/6707705) 20 | - [ATM10 4.2](https://legacy.curseforge.com/minecraft/modpacks/all-the-mods-10/files/6696915) 21 | - [ATM9 1.0.8](https://legacy.curseforge.com/minecraft/modpacks/all-the-mods-9/files/6451428) 22 | 23 | With the following modifications: 24 | 25 | - [JEI 19.21.2.313](https://legacy.curseforge.com/minecraft/mc-mods/jei/files/6614392) (latest applicable version as of writing) was added to Craftoria for the JEMI benchmark. 26 | - Included TMRV in Craftoria was replaced with latest dev build (for obvious reasons). 27 | - [EMI 1.1.22](https://legacy.curseforge.com/minecraft/mc-mods/emi/files/6420931) ([LexForge link](https://legacy.curseforge.com/minecraft/mc-mods/emi/files/6420945)) was added to ATM10 and ATM9 (because those packs don't include EMI). 28 | - `earlyWindowControl` was set to `false` in `.minecraft/config/fml.toml` for ATM9 to fix an issue with Wayland. 29 | - Sodium Dynamic Lights and Sodium Options API were removed from ATM9 due to ethical concerns. 30 | - [Zume](https://legacy.curseforge.com/minecraft/mc-mods/zume) was added to ATM10 and ATM9, and [Just Zoom](https://legacy.curseforge.com/minecraft/mc-mods/just-zoom) was disabled (because I felt like it). 31 | 32 | All tests were performed using [TMRV 0.6.0-rc.2](https://github.com/Nolij/TooManyRecipeViewers/releases/tag/release/0.6.0-rc.2). 33 | 34 | Craftoria and ATM10 were tested with an 8 GiB max heap size. ATM9 was tested with a 16 GiB max heap size. 35 | 36 | # Steps 37 | 38 | 1. Configure instance 39 | 1. Confirm EMI is enabled 40 | 2. For JEMI: Ensure JEI is enabled and TMRV is disabled 41 | 3. For TMRV: Ensure TMRV is enabled and JEI is disabled 42 | 2. Launch instance 43 | 3. Open world 44 | 4. Deselect Minecraft before world finishes loading (this will ensure that the game is paused immediately on load, which is necessary for reliably getting a comparable heap size, as the heap size constantly changes when the game is running) 45 | 5. Wait for EMI to finish loading (`Baked recipes after reload in ...ms` and `Reloaded EMI in ...ms` are both in logs) 46 | 6. Get memory usage using following shell command: `jmap -histo:live "$MC_PID" | tail -1 | awk '{ print $3; }' | numfmt --to=iec-i --suffix=B --format=%.3f;` 47 | 7. Quit game 48 | 8. Collect relevant logs using following RegEx: `/\[ModernFix\/\]: (Game took \d+\.\d+ seconds to start|Time from main menu to in\-game was \d+\.\d+ seconds|Total time to load game and open world was \d+\.\d+ seconds)|Starting JEI took \d+\.\d+ s|\[EMI\] ((Reloaded|Initialized) plugin from (jemi|toomanyrecipeviewers) in \d+ms|Baked \d+ recipes in \d+ms|Reloaded EMI in \d+ms|Baked recipes after reload in \d+ms)/g` 49 | 9. Determine load times from logs 50 | - For JEMI: 51 | - Pre-world-load time: JEI start time (`Starting JEI took {} s`) + `jemi` EMI plugin initialize time (`Reloaded plugin from jemi in {}ms`) 52 | - Post-world-load time: `jemi` EMI plugin register time (`Reloaded plugin from jemi in {}ms`) 53 | - For TMRV: 54 | - Pre-world-load time: `toomanyrecipeviewers` EMI plugin initialize time (`Initialized plugin from toomanyrecipeviewers in {}ms`) 55 | - Post-world-load time: `toomanyrecipeviewers` EMI plugin register time (`Reloaded plugin from toomanyrecipeviewers in {}ms`) 56 | 57 | # Results 58 | 59 | ## Craftoria 60 | 61 | #### TMRV 62 | 63 | Load time: 3201ms (2ms before world load, 3199ms after world load) 64 | Heap size: 2.722 GiB 65 | 66 | Relevant logs: 67 | ``` 68 | [Render thread/WARN] [ModernFix/]: Game took 93.907 seconds to start 69 | [Thread-108/INFO] [EMI/]: [EMI] Initialized plugin from toomanyrecipeviewers in 2ms 70 | [Render thread/WARN] [ModernFix/]: Time from main menu to in-game was 26.39167 seconds 71 | [Render thread/WARN] [ModernFix/]: Total time to load game and open world was 120.29867 seconds 72 | [Thread-108/INFO] [EMI/]: [EMI] Reloaded plugin from toomanyrecipeviewers in 3199ms 73 | [Thread-108/INFO] [EMI/]: [EMI] Baked 246939 recipes in 2784ms 74 | [Thread-126/INFO] [EMI/]: [EMI] Baked recipes after reload in 2054ms 75 | [Thread-108/INFO] [EMI/]: [EMI] Reloaded EMI in 27539ms 76 | ``` 77 | 78 | #### JEMI 79 | 80 | Load time: 8277ms (6751ms before world load, 1526ms after world load) 81 | Heap size: 2.872 GiB 82 | 83 | Relevant logs: 84 | ``` 85 | [Render thread/WARN] [ModernFix/]: Game took 93.449 seconds to start 86 | [Render thread/INFO] [mezz.jei.core.util.LoggedTimer/]: Starting JEI took 6.751 seconds 87 | [Thread-113/INFO] [EMI/]: [EMI] Initialized plugin from jemi in 0ms 88 | [Render thread/WARN] [ModernFix/]: Time from main menu to in-game was 30.508913 seconds 89 | [Render thread/WARN] [ModernFix/]: Total time to load game and open world was 123.95791 seconds 90 | [Thread-113/INFO] [EMI/]: [EMI] Reloaded plugin from jemi in 1526ms 91 | [Thread-113/INFO] [EMI/]: [EMI] Baked 243195 recipes in 2499ms 92 | [Thread-124/INFO] [EMI/]: [EMI] Baked recipes after reload in 2419ms 93 | [Thread-113/INFO] [EMI/]: [EMI] Reloaded EMI in 24017ms 94 | ``` 95 | 96 | ## ATM10 97 | 98 | #### TMRV 99 | 100 | Load time: 7484ms (2ms before world load, 7482ms after world load) 101 | Heap size: 3.580 GiB 102 | 103 | Relevant logs: 104 | ``` 105 | [Render thread/WARN] [ModernFix/]: Game took 99.155 seconds to start 106 | [Thread-43/INFO] [EMI/]: [EMI] Initialized plugin from toomanyrecipeviewers in 2ms 107 | [Render thread/WARN] [ModernFix/]: Time from main menu to in-game was 35.92763 seconds 108 | [Render thread/WARN] [ModernFix/]: Total time to load game and open world was 135.08263 seconds 109 | [Thread-43/INFO] [EMI/]: [EMI] Reloaded plugin from toomanyrecipeviewers in 7482ms 110 | [Thread-43/INFO] [EMI/]: [EMI] Baked 379846 recipes in 5611ms 111 | [Thread-49/INFO] [EMI/]: [EMI] Baked recipes after reload in 5819ms 112 | [Thread-43/INFO] [EMI/]: [EMI] Reloaded EMI in 61201ms 113 | ``` 114 | 115 | #### JEMI 116 | 117 | Load time: 18658ms (14500ms before world load, 4158ms after world load) 118 | Heap size: 4.491 GiB 119 | 120 | Relevant logs: 121 | ``` 122 | [Render thread/WARN] [ModernFix/]: Game took 100.864 seconds to start 123 | [Render thread/INFO] [mezz.jei.core.util.LoggedTimer/]: Starting JEI took 14.50 seconds 124 | [Thread-49/INFO] [EMI/]: [EMI] Initialized plugin from jemi in 0ms 125 | [Render thread/WARN] [ModernFix/]: Time from main menu to in-game was 49.368702 seconds 126 | [Render thread/WARN] [ModernFix/]: Total time to load game and open world was 150.2327 seconds 127 | [Thread-49/INFO] [EMI/]: [EMI] Reloaded plugin from jemi in 4158ms 128 | [Thread-49/INFO] [EMI/]: [EMI] Baked 424928 recipes in 7246ms 129 | [Thread-51/INFO] [EMI/]: [EMI] Baked recipes after reload in 9733ms 130 | [Thread-49/INFO] [EMI/]: [EMI] Reloaded EMI in 65663ms 131 | ``` 132 | 133 | ## ATM9 134 | 135 | #### TMRV 136 | 137 | Load time: 32392ms (2ms before world load, 32390ms after world load) 138 | Heap size: 4.345 GiB 139 | 140 | Relevant logs: 141 | ``` 142 | [Render thread/WARN] [ModernFix/]: Game took 145.889 seconds to start 143 | [Thread-40/INFO] [EMI/]: [EMI] Initialized plugin from toomanyrecipeviewers in 2ms 144 | [Render thread/WARN] [ModernFix/]: Time from main menu to in-game was 73.94757 seconds 145 | [Render thread/WARN] [ModernFix/]: Total time to load game and open world was 219.83658 seconds 146 | [Thread-40/INFO] [EMI/]: [EMI] Reloaded plugin from toomanyrecipeviewers in 32390ms 147 | [Thread-40/INFO] [EMI/]: [EMI] Baked 443238 recipes in 5453ms 148 | [Thread-46/INFO] [EMI/]: [EMI] Baked recipes after reload in 3926ms 149 | [Thread-40/INFO] [EMI/]: [EMI] Reloaded EMI in 91274ms 150 | ``` 151 | 152 | #### JEMI 153 | 154 | Load time: 49409ms (42590ms before world load, 6819ms after world load) 155 | Heap size: 5.939 GiB 156 | 157 | Relevant logs: 158 | ``` 159 | [Render thread/WARN] [ModernFix/]: Game took 142.23 seconds to start 160 | [Render thread/INFO] [mezz.jei.core.util.LoggedTimer/]: Starting JEI took 42.59 s 161 | [Thread-41/INFO] [EMI/]: [EMI] Initialized plugin from jemi in 0ms 162 | [Render thread/WARN] [ModernFix/]: Time from main menu to in-game was 113.406425 seconds 163 | [Render thread/WARN] [ModernFix/]: Total time to load game and open world was 255.63641 seconds 164 | [Thread-41/INFO] [EMI/]: [EMI] Reloaded plugin from jemi in 6819ms 165 | [Thread-41/INFO] [EMI/]: [EMI] Baked 469828 recipes in 15147ms 166 | [Thread-41/INFO] [EMI/]: [EMI] Reloaded EMI in 72860ms 167 | ``` 168 | -------------------------------------------------------------------------------- /README_CURSEFORGE.md: -------------------------------------------------------------------------------- 1 | # IMPORTANT LICENSE NOTICE 2 | 3 | By using this project in any form, you hereby give your "express assent" for the terms of the license of this project (see [License](#license)), and acknowledge that I (the author of this project) have fulfilled my obligation under the license to "make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License". 4 | 5 | # TooManyRecipeViewers 6 | 7 | **T**oo**M**any**R**ecipe**V**iewers (or TMRV) is a compatibility layer for running [JEI](https://legacy.curseforge.com/minecraft/mc-mods/jei) plugins with [EMI](https://legacy.curseforge.com/minecraft/mc-mods/emi) **without** having to install [JEI](https://legacy.curseforge.com/minecraft/mc-mods/jei), written by Nolij. 8 | 9 | You'll need EMI installed to use this mod. 10 | 11 | # Why use TMRV over EMI+JEI (AKA JEMI)? 12 | 13 | JEMI is a compatibility layer built-in to EMI to get JEI plugins _mostly_ working with EMI by heavily relying on the JEI internals. It is designed to be as simplistic as possible and relies on JEI to process recipe data first before it is imported to EMI. 14 | 15 | TMRV is not like JEMI - it aims to completely replace the JEI API (NOTE: TMRV does contain some unmodified JEI internals - see [JEI Code Re-Use](#jei-code-re-use)). TMRV (where feasible) replaces JEI APIs with direct mappers to EMI APIs instead of loading the whole JEI registry and querying it after the fact. This has several advantages, including more efficient use of system resources, but is also a tradeoff, as TMRV's approach makes maintenance far more involved than what JEMI requires. JEMI is intentionally designed this way to allow EMI development to be focused on improving EMI itself, which is an approach I fully support. 16 | 17 | TL;DR: **TMRV is much more efficient than JEMI** at the cost of taking **much more effort** to maintain than JEMI does, and **nobody should expect EMI to put this amount of effort** in to supporting an API that was intentionally not referenced during design. Be happy EMI can even load JEI plugins out of the box to begin with - it still took a fair amount of effort. 18 | 19 | That being said, TMRV has two primary advantages over JEMI: 20 | 21 | ### 1. Plugin Compatibility 22 | 23 | TMRV has better coverage of JEI's API than JEMI does (with one exception - see [Known API Limitations](#known-api-limitations)). As of writing, this includes: 24 | 25 | - Better conversion of built-in recipe types (JEMI only supports crafting and info recipe types; TMRV supports all built-in JEI recipe types) 26 | - Ingredient/search alias support 27 | - `createRecipeExtras` support 28 | 29 | ### 2. Efficiency 30 | 31 | With TMRV, you will always load in to the world faster than with JEMI. This is because JEI plugin initialization blocks world load - you can't start playing until all JEI plugins are initialized. EMI loads plugins asynchronously _after_ the world is loaded. 32 | 33 | This means that even if TMRV loaded JEI plugins _slower_ than JEI does (not the case, it loads them measurably faster - see [benchmarks](#benchmarks)), worlds will _always_ load faster with TMRV than with JEMI. 34 | 35 | As already mentioned, TMRV replaces much of the JEI APIs with mappers to the corresponding EMI APIs - this means entire parts of the JEI internals can be outright removed. There's no need to initialize and store a whole JEI recipe registry - TMRV just converts JEI API calls to EMI ones, and converts the responses to the JEI format. Think of TMRV like Wine or Proton, and JEMI like a VM. JEMI uses real JEI, so there will be some scenarios where TMRV will error where JEMI won't (note that this doesn't necessarily mean that JEMI properly supports a scenario, it just means it looks like it does), but TMRV will generally be more efficient than JEMI. 36 | 37 | # Benchmarks 38 | 39 | The full results and steps followed to obtain them are documented in [BENCHMARKS.md](https://github.com/Nolij/TooManyRecipeViewers/raw/master/BENCHMARKS.md). These results were not cherry-picked. The instructions were followed exactly as documented in that file. I encourage the community to verify them. 40 | 41 | ### Load Times 42 | 43 | | | TMRV | JEMI | Comparison | 44 | |-----------------------------|-----------------------------------------------------------------------------|--------------------------------------------------------------------------------|------------------------------------------------------------------| 45 | | Craftoria    | 3201ms (2ms before world load, 3199ms after world load)    | 8277ms (6751ms before world load, 1526ms after world load)    | -5076ms (-6749ms before world load, +1673ms after world load) | 46 | | ATM10    | 7484ms (2ms before world load, 7482ms after world load)    | 18658ms (14500ms before world load, 4158ms after world load)    | -11174ms (-14498ms before world load, +3324ms after world load) | 47 | | ATM9    | 32392ms (2ms before world load, 32390ms after world load)    | 49409ms (42590ms before world load, 6819ms after world load)    | -17017ms (-42588ms before world load, +25571ms after world load) | 48 | 49 | ### Memory Usage 50 | 51 | | | TMRV | JEMI | Comparison | 52 | |-----------------------------|-----------------------------|-----------------------------|--------------------------| 53 | | Craftoria    | 2.722 GiB    | 2.872 GiB    | -153.6 MiB (approximate) | 54 | | ATM10    | 3.580 GiB    | 4.491 GiB    | -932.9 MiB (approximate) | 55 | | ATM9    | 4.345 GiB    | 5.939 GiB    | -1.594 GiB | 56 | 57 | # Known API Limitations 58 | 59 | ### JEI Config Files 60 | 61 | `.minecraft/config/jei/blacklist.json` is the only JEI config file that TMRV even reads. This file _should_ work fine for vanilla ingredient types and for modded ingredient types added by a JEI plugin (this does not include mods that support both JEI and EMI natively, such as Mekanism). This is meant to be a stop-gap for packs switching over from JEI. JEMI had a similar flaw. EMI has its own config for hiding ingredients - please use that instead. All other JEI config files are completely ignored by TMRV, and there are no plans to support them. 62 | 63 | ### Recipe Manager Plugins 64 | 65 | The JEI API supports "Recipe Manager Plugins". These plugins allow mods to control their own recipe registries and handle recipe lookups themselves at runtime. 66 | 67 | TMRV will attempt to extract recipes from these plugins, but this does not work for most plugins, and by no means provides proper support for the feature. Support beyond this is not planned. Recipe Manager plugins are an outdated concept that very few plugins still use, and they aren't possible to properly support without very invasive EMI mixins - something I do not intend to use in this project. 68 | 69 | ### Vanilla Recipe Category Extensions 70 | 71 | The JEI API supports "extensions" to the vanilla Crafting and Smithing recipe categories. Sufficient inspection (that would be necessary to determine feasibility of adding support) of how this part of the JEI API works is yet to be done. For now, though, these are unsupported by TMRV. 72 | 73 | ### Runtime Registry Changes 74 | 75 | The JEI API supports modifying the recipe and ingredient registries at runtime (ie after plugin registration is complete). This concept is not compatible with EMI, and it is not a practice I want to support. As such, after `IModPlugin.onRuntimeAvailable` has been invoked, all APIs for runtime registry modifications will throw an `IllegalStateException` if invoked to avoid potential confusion. 76 | 77 | # JEI Code Re-use 78 | 79 | The plan is to replace more of the JEI internals in future updates. However, some parts of JEI simply aren't worth re-implementing for various reasons. Regardless, enough of JEI has already been replaced in TMRV that I can confidently say: 80 | 81 | 1. It wouldn't be feasible to achieve the same improvements over JEMI with mixins (at least sanely), and 82 | 2. Enough of the JEI internals have been replaced or removed that I don't consider this unfair to JEI, especially given the fact that [JEI's license](https://github.com/mezz/JustEnoughItems/blob/d4ea796eb319efff2ff209f50c053c2a5a1dec05/LICENSE.txt) explicitly allows doing this. 83 | 84 | # License 85 | 86 | This project is licensed under OSL-3.0. For more information, see [LICENSE](https://github.com/Nolij/TooManyRecipeViewers/raw/master/LICENSE). 87 | 88 | Some code was copied from [EMI](https://github.com/emilyploszaj/emi) and [JEI](https://github.com/mezz/JustEnoughItems) in compliance with their copyright licenses. All modifications present in this project are licensed under the same license as the rest of this project, OSL-3.0. -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/impl/jei/api/gui/builder/RecipeSlotBuilder.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.builder; 2 | 3 | import dev.emi.emi.api.stack.EmiIngredient; 4 | import dev.emi.emi.api.stack.EmiStack; 5 | import dev.emi.emi.api.widget.SlotWidget; 6 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.drawable.OffsetDrawable; 7 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.ingredient.ITMRVSlotWidget; 8 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.ingredient.TMRVSlotWidget; 9 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.gui.ingredient.TMRVTankWidget; 10 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime.IngredientManager; 11 | import dev.nolij.toomanyrecipeviewers.util.FluidRendererParameters; 12 | import mezz.jei.api.gui.builder.IRecipeSlotBuilder; 13 | import mezz.jei.api.gui.drawable.IDrawable; 14 | import mezz.jei.api.gui.ingredient.IRecipeSlotRichTooltipCallback; 15 | import mezz.jei.api.ingredients.IIngredientRenderer; 16 | import mezz.jei.api.ingredients.IIngredientType; 17 | import mezz.jei.api.ingredients.ITypedIngredient; 18 | import mezz.jei.api.recipe.RecipeIngredientRole; 19 | import mezz.jei.common.util.ImmutableRect2i; 20 | import mezz.jei.library.gui.recipes.layout.builder.LegacyTooltipCallbackAdapter; 21 | import net.minecraft.core.component.DataComponentPatch; 22 | import net.minecraft.world.level.material.Fluid; 23 | import org.jetbrains.annotations.NotNull; 24 | import org.jetbrains.annotations.Nullable; 25 | 26 | import java.util.ArrayList; 27 | import java.util.HashMap; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.Optional; 31 | 32 | import static dev.nolij.toomanyrecipeviewers.TooManyRecipeViewers.fluidHelper; 33 | 34 | @SuppressWarnings("NonExtendableApiUsage") 35 | public class RecipeSlotBuilder implements IRecipeSlotBuilder { 36 | 37 | private final IngredientManager ingredientManager; 38 | 39 | public final RecipeIngredientRole role; 40 | private final TMRVIngredientCollector ingredientCollector; 41 | 42 | private boolean visible = true; 43 | private @Nullable String name; 44 | private final List tooltipCallbacks = new ArrayList<>(); 45 | private boolean outputSlotBackground = false; 46 | private @Nullable OffsetDrawable background = null; 47 | private @Nullable OffsetDrawable overlay = null; 48 | private final Map, IIngredientRenderer> rendererOverrides = new HashMap<>(); 49 | private @Nullable FluidRendererParameters fluidRendererParameters = null; 50 | private ImmutableRect2i rect = new ImmutableRect2i(0, 0, 16, 16); 51 | 52 | RecipeSlotBuilder(IngredientManager ingredientManager, RecipeIngredientRole role) { 53 | this.ingredientManager = ingredientManager; 54 | this.role = role; 55 | this.ingredientCollector = new TMRVIngredientCollector(ingredientManager); 56 | } 57 | 58 | public boolean isVisible() { 59 | return visible; 60 | } 61 | 62 | public List> getCollectedIngredients() { 63 | return ingredientCollector.getCollectedIngredients(); 64 | } 65 | 66 | public List getEMIStacks() { 67 | return ingredientCollector.getEMIStacks(); 68 | } 69 | 70 | public EmiIngredient getEMIIngredient() { 71 | return ingredientCollector.getEMIIngredient(); 72 | } 73 | 74 | private @NotNull ITMRVSlotWidget getWidget() { 75 | final ITMRVSlotWidget widget; 76 | 77 | if (fluidRendererParameters == null) { 78 | widget = new TMRVSlotWidget(ingredientManager, role, rect, rendererOverrides); 79 | } else { 80 | for (final var ingredient : getCollectedIngredients()) { 81 | if (ingredient.getType() != fluidHelper.getFluidIngredientType()) 82 | throw new IllegalStateException("Mixed fluids and non-fluids"); 83 | } 84 | if (!rendererOverrides.isEmpty()) 85 | throw new IllegalStateException("Renderer override on fluid slot"); 86 | 87 | widget = new TMRVTankWidget(ingredientManager, role, fluidRendererParameters, rect); 88 | } 89 | 90 | return widget; 91 | } 92 | 93 | public SlotWidget build() { 94 | if (!visible) 95 | throw new UnsupportedOperationException(); 96 | 97 | final ITMRVSlotWidget widget = getWidget(); 98 | 99 | widget.drawBack(false); 100 | widget.large(outputSlotBackground); 101 | widget.setName(name); 102 | widget.addTooltipCallbacks(tooltipCallbacks); 103 | widget.setBackground(background); 104 | widget.setOverlay(overlay); 105 | widget.getIngredientCollector().copy(ingredientCollector); 106 | 107 | return (SlotWidget) widget; 108 | } 109 | 110 | public RecipeSlotBuilder setInvisible() { 111 | visible = false; 112 | return this; 113 | } 114 | 115 | //region IRecipeSlotBuilder 116 | @Override 117 | public IRecipeSlotBuilder addRichTooltipCallback(IRecipeSlotRichTooltipCallback tooltipCallback) { 118 | this.tooltipCallbacks.add(tooltipCallback); 119 | return this; 120 | } 121 | 122 | @SuppressWarnings("removal") 123 | @Override 124 | public IRecipeSlotBuilder addTooltipCallback(mezz.jei.api.gui.ingredient.IRecipeSlotTooltipCallback tooltipCallback) { 125 | return addRichTooltipCallback(new LegacyTooltipCallbackAdapter(tooltipCallback)); 126 | } 127 | 128 | @Override 129 | public IRecipeSlotBuilder setSlotName(String name) { 130 | this.name = name; 131 | return this; 132 | } 133 | 134 | @Override 135 | public IRecipeSlotBuilder setStandardSlotBackground() { 136 | outputSlotBackground = false; 137 | return this; 138 | } 139 | 140 | @Override 141 | public IRecipeSlotBuilder setOutputSlotBackground() { 142 | outputSlotBackground = true; 143 | return this; 144 | } 145 | 146 | @Override 147 | public IRecipeSlotBuilder setBackground(IDrawable background, int xOffset, int yOffset) { 148 | this.background = new OffsetDrawable(background, xOffset, yOffset); 149 | return this; 150 | } 151 | 152 | @Override 153 | public IRecipeSlotBuilder setOverlay(IDrawable overlay, int xOffset, int yOffset) { 154 | this.overlay = new OffsetDrawable(overlay, xOffset, yOffset); 155 | return this; 156 | } 157 | 158 | @Override 159 | public IRecipeSlotBuilder setCustomRenderer(IIngredientType type, IIngredientRenderer renderer) { 160 | synchronized (rendererOverrides) { 161 | if (rendererOverrides.isEmpty()) { 162 | rect = new ImmutableRect2i(rect.x(), rect.y(), renderer.getWidth(), renderer.getHeight()); 163 | } else if ( 164 | renderer.getWidth() != rect.width() || 165 | renderer.getHeight() != rect.height()) { 166 | throw new IllegalStateException("Size mismatch"); 167 | } 168 | 169 | this.rendererOverrides.put(type, renderer); 170 | 171 | return this; 172 | } 173 | } 174 | 175 | @Override 176 | public IRecipeSlotBuilder setFluidRenderer(long capacity, boolean showCapacity, int width, int height) { 177 | fluidRendererParameters = new FluidRendererParameters(capacity, showCapacity, width, height); 178 | rect = new ImmutableRect2i(rect.x(), rect.y(), width, height); 179 | return this; 180 | } 181 | //endregion 182 | 183 | //region IPlaceable 184 | @Override 185 | public IRecipeSlotBuilder setPosition(int x, int y) { 186 | this.rect = this.rect.setPosition(x, y); 187 | return this; 188 | } 189 | 190 | @Override 191 | public int getWidth() { 192 | return this.rect.width(); 193 | } 194 | 195 | @Override 196 | public int getHeight() { 197 | return this.rect.height(); 198 | } 199 | //endregion 200 | 201 | //region IIngredientAcceptor 202 | @Override 203 | public IRecipeSlotBuilder addIngredients(IIngredientType type, List<@Nullable I> ingredients) { 204 | ingredientCollector.addIngredients(type, ingredients); 205 | return this; 206 | } 207 | 208 | @Override 209 | public IRecipeSlotBuilder addIngredient(IIngredientType type, I ingredient) { 210 | ingredientCollector.addIngredient(type, ingredient); 211 | return this; 212 | } 213 | 214 | @Override 215 | public IRecipeSlotBuilder addIngredientsUnsafe(List ingredients) { 216 | ingredientCollector.addIngredientsUnsafe(ingredients); 217 | return this; 218 | } 219 | 220 | @Override 221 | public IRecipeSlotBuilder addTypedIngredients(List> ingredients) { 222 | ingredientCollector.addTypedIngredients(ingredients); 223 | return this; 224 | } 225 | 226 | @Override 227 | public IRecipeSlotBuilder addOptionalTypedIngredients(List>> ingredients) { 228 | ingredientCollector.addOptionalTypedIngredients(ingredients); 229 | return this; 230 | } 231 | 232 | @Override 233 | public IRecipeSlotBuilder addFluidStack(Fluid fluid) { 234 | ingredientCollector.addFluidStack(fluid); 235 | return this; 236 | } 237 | 238 | @Override 239 | public IRecipeSlotBuilder addFluidStack(Fluid fluid, long amount) { 240 | ingredientCollector.addFluidStack(fluid, amount); 241 | return this; 242 | } 243 | 244 | @Override 245 | public IRecipeSlotBuilder addFluidStack(Fluid fluid, long amount, DataComponentPatch dataComponentPatch) { 246 | ingredientCollector.addFluidStack(fluid, amount, dataComponentPatch); 247 | return this; 248 | } 249 | //endregion 250 | 251 | } 252 | -------------------------------------------------------------------------------- /src/main/java/dev/nolij/toomanyrecipeviewers/TooManyRecipeViewers.java: -------------------------------------------------------------------------------- 1 | package dev.nolij.toomanyrecipeviewers; 2 | 3 | //? if >=21.1 4 | import mezz.jei.api.helpers.ICodecHelper; 5 | import com.google.common.collect.ImmutableListMultimap; 6 | import com.google.common.collect.ImmutableSetMultimap; 7 | import dev.emi.emi.api.EmiRegistry; 8 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.recipe.RecipeManager; 9 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime.IngredientManager; 10 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime.JEIKeyMappings; 11 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime.JEIRuntime; 12 | import dev.nolij.toomanyrecipeviewers.impl.jei.api.runtime.config.JEIConfigManager; 13 | import mezz.jei.api.helpers.IColorHelper; 14 | import mezz.jei.api.helpers.IGuiHelper; 15 | import mezz.jei.api.helpers.IJeiHelpers; 16 | import mezz.jei.api.helpers.IModIdHelper; 17 | import mezz.jei.api.helpers.IPlatformFluidHelper; 18 | import mezz.jei.api.helpers.IStackHelper; 19 | import mezz.jei.api.ingredients.ITypedIngredient; 20 | import mezz.jei.api.recipe.IFocusFactory; 21 | import mezz.jei.api.recipe.IRecipeManager; 22 | import mezz.jei.api.recipe.RecipeType; 23 | import mezz.jei.api.recipe.category.IRecipeCategory; 24 | import mezz.jei.api.recipe.transfer.IRecipeTransferManager; 25 | import mezz.jei.api.recipe.vanilla.IVanillaRecipeFactory; 26 | import mezz.jei.api.runtime.IBookmarkOverlay; 27 | import mezz.jei.api.runtime.IEditModeConfig; 28 | import mezz.jei.api.runtime.IIngredientFilter; 29 | import mezz.jei.api.runtime.IIngredientListOverlay; 30 | import mezz.jei.api.runtime.IIngredientManager; 31 | import mezz.jei.api.runtime.IIngredientVisibility; 32 | import mezz.jei.api.runtime.IJeiKeyMappings; 33 | import mezz.jei.api.runtime.IJeiRuntime; 34 | import mezz.jei.api.runtime.IRecipesGui; 35 | import mezz.jei.api.runtime.IScreenHelper; 36 | import mezz.jei.api.runtime.config.IJeiConfigManager; 37 | import mezz.jei.common.config.IClientToggleState; 38 | import mezz.jei.common.input.IInternalKeyMappings; 39 | import mezz.jei.common.platform.IPlatformFluidHelperInternal; 40 | import mezz.jei.common.util.StackHelper; 41 | import mezz.jei.library.config.EditModeConfig; 42 | import mezz.jei.library.gui.helpers.GuiHelper; 43 | import mezz.jei.library.ingredients.IngredientBlacklistInternal; 44 | import mezz.jei.library.ingredients.subtypes.SubtypeManager; 45 | import mezz.jei.library.plugins.vanilla.anvil.SmithingRecipeCategory; 46 | import mezz.jei.library.plugins.vanilla.crafting.CraftingRecipeCategory; 47 | import mezz.jei.library.runtime.JeiHelpers; 48 | import mezz.jei.neoforge.platform.FluidHelper; 49 | import net.minecraft.resources.ResourceLocation; 50 | import org.jetbrains.annotations.Unmodifiable; 51 | 52 | import java.util.ArrayList; 53 | import java.util.Collections; 54 | import java.util.HashSet; 55 | import java.util.List; 56 | import java.util.Optional; 57 | import java.util.Set; 58 | import java.util.stream.Stream; 59 | 60 | public final class TooManyRecipeViewers { 61 | 62 | public static volatile TooManyRecipeViewers runtime = null; 63 | 64 | //region Storage 65 | public volatile EmiRegistry emiRegistry = null; 66 | public volatile SubtypeManager subtypeManager = null; 67 | public volatile StackHelper stackHelper = null; 68 | public volatile IColorHelper colorHelper = null; 69 | public volatile IngredientManager ingredientManager = null; 70 | public volatile GuiHelper guiHelper = null; 71 | public volatile IFocusFactory focusFactory = null; 72 | //? if >=21.1 73 | public volatile ICodecHelper codecHelper = null; 74 | public volatile IVanillaRecipeFactory vanillaRecipeFactory = null; 75 | public volatile IngredientBlacklistInternal blacklist = null; 76 | public volatile IClientToggleState clientToggleState = null; 77 | public volatile EditModeConfig editModeConfig = null; 78 | public volatile IIngredientVisibility ingredientVisibility = null; 79 | public volatile ImmutableSetMultimap modAliases = null; 80 | public volatile IModIdHelper modIdHelper = null; 81 | public volatile JeiHelpers jeiHelpers = null; 82 | public volatile CraftingRecipeCategory craftingCategory = null; 83 | public volatile SmithingRecipeCategory smithingCategory = null; 84 | public volatile @Unmodifiable List> recipeCategories = null; 85 | public volatile ImmutableListMultimap, ITypedIngredient> recipeCatalysts = null; 86 | public final Set ignoredRecipes = new HashSet<>(); 87 | public volatile RecipeManager recipeManager = null; 88 | public volatile IRecipeTransferManager recipeTransferManager = null; 89 | public volatile IScreenHelper screenHelper = null; 90 | public volatile JEIRuntime jeiRuntime = null; 91 | //endregion 92 | 93 | public interface ILockable { 94 | void lock() throws IllegalStateException; 95 | } 96 | 97 | private volatile boolean registrationLocked = false; 98 | private final List lockAfterRegistration = Collections.synchronizedList(new ArrayList<>()); 99 | 100 | public synchronized void lockAfterRegistration(ILockable lockable) throws IllegalStateException { 101 | if (registrationLocked) 102 | throw new IllegalStateException(); 103 | 104 | lockAfterRegistration.add(lockable); 105 | } 106 | 107 | public synchronized void lockRegistration() throws IllegalStateException { 108 | if (registrationLocked) 109 | throw new IllegalStateException(); 110 | registrationLocked = true; 111 | 112 | lockAfterRegistration.forEach(ILockable::lock); 113 | lockAfterRegistration.clear(); 114 | } 115 | 116 | //region Static Storage 117 | public static final JEIConfigManager jeiConfigManager = new JEIConfigManager(); 118 | public static final IPlatformFluidHelperInternal fluidHelper = new FluidHelper(); 119 | public static final IInternalKeyMappings jeiKeyMappings = new JEIKeyMappings(); 120 | 121 | public static final IJeiHelpers staticJEIHelpers = new IJeiHelpers() { 122 | @Override 123 | public IGuiHelper getGuiHelper() { 124 | return runtime.guiHelper; 125 | } 126 | 127 | @Override 128 | public IStackHelper getStackHelper() { 129 | return runtime.stackHelper; 130 | } 131 | 132 | @Override 133 | public IModIdHelper getModIdHelper() { 134 | return runtime.modIdHelper; 135 | } 136 | 137 | @Override 138 | public IFocusFactory getFocusFactory() { 139 | return runtime.focusFactory; 140 | } 141 | 142 | @Override 143 | public IColorHelper getColorHelper() { 144 | return runtime.colorHelper; 145 | } 146 | 147 | @Override 148 | public IPlatformFluidHelper getPlatformFluidHelper() { 149 | return fluidHelper; 150 | } 151 | 152 | @Override 153 | public Optional> getRecipeType(ResourceLocation recipeUid, Class recipeClass) { 154 | if (runtime == null || runtime.recipeManager == null) 155 | return Optional.empty(); 156 | 157 | return runtime.recipeManager.getRecipeType(recipeUid, recipeClass); 158 | } 159 | 160 | //? if <21.1 161 | /*@SuppressWarnings("removal")*/ 162 | @Override 163 | public Optional> getRecipeType(ResourceLocation recipeUid) { 164 | if (runtime == null || runtime.recipeManager == null) 165 | return Optional.empty(); 166 | 167 | return runtime.recipeManager.getRecipeType(recipeUid); 168 | } 169 | 170 | @Override 171 | public Stream> getAllRecipeTypes() { 172 | if (runtime == null || runtime.recipeManager == null) 173 | return Stream.empty(); 174 | 175 | return runtime.recipeManager.getAllRecipeTypes(); 176 | } 177 | 178 | @Override 179 | public IIngredientManager getIngredientManager() { 180 | return runtime.ingredientManager; 181 | } 182 | 183 | //? if >=21.1 { 184 | @Override 185 | public ICodecHelper getCodecHelper() { 186 | return runtime.codecHelper; 187 | } 188 | //?} 189 | 190 | @Override 191 | public IVanillaRecipeFactory getVanillaRecipeFactory() { 192 | return runtime.vanillaRecipeFactory; 193 | } 194 | 195 | @Override 196 | public IIngredientVisibility getIngredientVisibility() { 197 | return runtime.ingredientVisibility; 198 | } 199 | }; 200 | 201 | public static final IJeiRuntime staticJEIRuntime = new IJeiRuntime() { 202 | @Override 203 | public IRecipeManager getRecipeManager() { 204 | return runtime.recipeManager; 205 | } 206 | 207 | @Override 208 | public IRecipesGui getRecipesGui() { 209 | return runtime.jeiRuntime.getRecipesGui(); 210 | } 211 | 212 | @Override 213 | public IIngredientFilter getIngredientFilter() { 214 | return runtime.jeiRuntime.getIngredientFilter(); 215 | } 216 | 217 | @Override 218 | public IIngredientListOverlay getIngredientListOverlay() { 219 | return runtime.jeiRuntime.getIngredientListOverlay(); 220 | } 221 | 222 | @Override 223 | public IBookmarkOverlay getBookmarkOverlay() { 224 | return runtime.jeiRuntime.getBookmarkOverlay(); 225 | } 226 | 227 | @Override 228 | public IJeiHelpers getJeiHelpers() { 229 | return staticJEIHelpers; 230 | } 231 | 232 | @Override 233 | public IIngredientManager getIngredientManager() { 234 | return runtime.ingredientManager; 235 | } 236 | 237 | @Override 238 | public IJeiKeyMappings getKeyMappings() { 239 | return jeiKeyMappings; 240 | } 241 | 242 | @Override 243 | public IScreenHelper getScreenHelper() { 244 | return runtime.screenHelper; 245 | } 246 | 247 | @Override 248 | public IRecipeTransferManager getRecipeTransferManager() { 249 | return runtime.recipeTransferManager; 250 | } 251 | 252 | @Override 253 | public IEditModeConfig getEditModeConfig() { 254 | return runtime.editModeConfig; 255 | } 256 | 257 | @Override 258 | public IJeiConfigManager getConfigManager() { 259 | return jeiConfigManager; 260 | } 261 | }; 262 | //endregion 263 | 264 | } 265 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | --------------------------------------------------------------------------------