├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew └── src └── main ├── java └── com │ └── github │ └── vfyjxf │ └── jeiutilities │ ├── JEIUtilities.java │ ├── asm │ ├── JeiUtilitiesClassTransformer.java │ └── JeiUtilitiesLoadingPlugin.java │ ├── config │ ├── JeiUtilitiesConfig.java │ ├── JeiUtilitiesConfigGuiFactory.java │ ├── KeyBindings.java │ ├── RecordMode.java │ └── SplittingMode.java │ ├── gui │ ├── bookmark │ │ ├── AdvancedBookmarkOverlay.java │ │ ├── BookmarkInputHandler.java │ │ └── RecordConfigButton.java │ ├── common │ │ └── GuiInputHandler.java │ ├── history │ │ └── AdvancedIngredientGrid.java │ └── recipe │ │ ├── DraggableRecipeWidget.java │ │ └── RecipePreviewWidget.java │ ├── helper │ ├── IngredientHelper.java │ ├── RecipeHelper.java │ └── ReflectionUtils.java │ └── jei │ ├── JeiHooks.java │ ├── JeiUtilitiesPlugin.java │ ├── bookmark │ └── RecipeBookmarkList.java │ └── ingredient │ ├── CraftingRecipeInfo.java │ ├── RecipeInfo.java │ ├── RecipeInfoHelper.java │ └── RecipeInfoRenderer.java └── resources ├── assets └── jeiutilities │ ├── lang │ ├── en_us.lang │ ├── ja_jp.lang │ ├── pt_br.json │ ├── ru_ru.lang │ └── zh_cn.lang │ ├── meta │ └── logo.png │ └── textures │ └── gui │ ├── icon │ ├── bookmark_button_config_disable.png │ └── bookmark_button_config_enable.png │ └── preview_recipe_background.png ├── mcmod.info └── pack.mcmeta /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gradle 3 | .idea 4 | .settings 5 | .metadata 6 | bin 7 | logs 8 | build 9 | run 10 | minecraft 11 | eclipse 12 | .classpath 13 | .project 14 | .apt_generated 15 | .apt_generated_tests 16 | 17 | *.bat 18 | *.sh 19 | *.iml 20 | *.ipr 21 | *.iws 22 | *.launch 23 | .factorypath 24 | 25 | forge*changelog.txt 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 vfyjxf 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JEI-Utilities 2 | More enhancements for JEI 3 | 4 | ## Credits 5 | Thanks for the idea from NEI easy search and GTNH-NEI -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | maven { 5 | name = "forge" 6 | url = "https://maven.minecraftforge.net" 7 | } 8 | maven { 9 | name = "github" 10 | url = "https://github.com/juanmuscaria/maven/raw/master" 11 | } 12 | maven { 13 | name = "sponge" 14 | url = "https://repo.spongepowered.org/maven" 15 | } 16 | } 17 | dependencies { 18 | classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT' 19 | classpath 'org.spongepowered:mixingradle:0.6-SNAPSHOT' 20 | } 21 | } 22 | 23 | apply plugin: 'net.minecraftforge.gradle.forge' 24 | 25 | version = mod_version 26 | group = mod_group 27 | archivesBaseName = mod_archives_name + "-" + mc_version 28 | 29 | [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' 30 | sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' 31 | 32 | minecraft { 33 | version = "1.12.2-14.23.5.2847" 34 | mappings = "stable_39" 35 | runDir = "run" 36 | 37 | replace '@VERSION@', project.version 38 | 39 | def args = [ 40 | "-Dfml.coreMods.load=com.github.vfyjxf.jeiutilities.asm.JeiUtilitiesLoadingPlugin", 41 | "-Dmixin.hotSwap=true", 42 | "-Dmixin.checks.interfaces=true", 43 | "-Dmixin.debug.export=true" 44 | ] 45 | 46 | clientJvmArgs.addAll(args) 47 | serverJvmArgs.addAll(args) 48 | } 49 | 50 | processResources { 51 | 52 | from(sourceSets.main.resources.srcDirs) { 53 | include 'mcmod.info' 54 | expand 'version': project.version, 55 | 'mod_id': mod_id, 56 | 'mod_name': mod_name, 57 | 'mod_author': mod_author, 58 | 'mod_description': mod_description, 59 | 'mc_version': mc_version, 60 | 'mod_version': mod_version, 61 | 'mod_icon': mod_icon 62 | } 63 | 64 | from(sourceSets.main.resources.srcDirs) { 65 | exclude 'mcmod.info' 66 | } 67 | } 68 | 69 | repositories { 70 | maven { 71 | // location of the maven that hosts JEI files 72 | name = "Progwml6 maven" 73 | url = "https://dvs1.progwml6.com/files/maven/" 74 | } 75 | maven { 76 | // location of a maven mirror for JEI files, as a fallback 77 | name = "ModMaven" 78 | url = "https://modmaven.k-4u.nl" 79 | } 80 | maven { 81 | url = 'https://maven.blamejared.com' 82 | name = 'BlameJared Maven' 83 | } 84 | maven { 85 | name = "CurseMaven" 86 | url = "https://cursemaven.com" 87 | } 88 | maven { 89 | url "https://maven.cleanroommc.com" 90 | } 91 | } 92 | 93 | dependencies { 94 | deobfCompile "mezz.jei:jei_${mc_version}:${jei_version}" 95 | //big recipe test 96 | runtime "curse.maven:extended-crafting-268387:2777071" 97 | runtime "curse.maven:cucumber-272335:2645867" 98 | // deobfCompile("zone.rong:mixinbooter:4.2") 99 | } 100 | 101 | task devJar(type: Jar) { 102 | classifier = 'dev' 103 | from sourceSets.main.output 104 | } 105 | 106 | task sourcesJar(type: Jar) { 107 | classifier = 'sources' 108 | from sourceSets.main.allSource 109 | } 110 | 111 | runClient { 112 | args '--username', 'developer' 113 | } 114 | 115 | jar { 116 | manifest.attributes( 117 | 'FMLCorePlugin': 'com.github.vfyjxf.jeiutilities.asm.JeiUtilitiesLoadingPlugin', 118 | 'FMLCorePluginContainsFMLMod': 'true' 119 | ) 120 | } 121 | 122 | artifacts { 123 | archives devJar 124 | archives sourcesJar 125 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle Specifications 2 | org.gradle.jvmargs=-Xms256M -Xmx2G 3 | org.gradle.daemon=false 4 | 5 | # General Specifications 6 | mc_version=1.12.2 7 | mod_version=0.2.12 8 | forge_version=14.23.5.2847 9 | jei_version=4.16.1.302 10 | mod_group=com.github.vfyjxf.jeiutilities 11 | mod_id=jeiutilities 12 | mod_name=JEI Utilities 13 | mod_archives_name=JEI-Utilities 14 | mod_author=vfyjxf_ 15 | mod_icon=assets/jeiutilities/meta/logo.png 16 | mod_description=More enhancements for JEI 17 | mod_credits=Thanks for the idea from NEI easy search and GTNH-NEI 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vfyjxf/JEI-Utilities/2f1ab3be8f26dd15ca124aa046ada34b75ecb004/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/JEIUtilities.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities; 2 | 3 | import com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig; 4 | import com.github.vfyjxf.jeiutilities.config.KeyBindings; 5 | import com.github.vfyjxf.jeiutilities.gui.common.GuiInputHandler; 6 | import com.github.vfyjxf.jeiutilities.gui.bookmark.BookmarkInputHandler; 7 | import com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin; 8 | import mezz.jei.Internal; 9 | import net.minecraftforge.common.MinecraftForge; 10 | import net.minecraftforge.fml.common.Mod; 11 | import net.minecraftforge.fml.common.Mod.EventHandler; 12 | import net.minecraftforge.fml.common.ObfuscationReflectionHelper; 13 | import net.minecraftforge.fml.common.event.FMLInitializationEvent; 14 | import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent; 15 | import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; 16 | import org.apache.logging.log4j.LogManager; 17 | import org.apache.logging.log4j.Logger; 18 | 19 | @Mod(modid = JEIUtilities.MODID, 20 | name = JEIUtilities.NAME, 21 | version = JEIUtilities.VERSION, 22 | dependencies = JEIUtilities.DEPENDENCIES, 23 | guiFactory = JEIUtilities.GUI_FACTORY, 24 | clientSideOnly = true 25 | ) 26 | public class JEIUtilities { 27 | public static final String MODID = "jeiutilities"; 28 | public static final String NAME = "JEI Utilities"; 29 | public static final String VERSION = "@VERSION@"; 30 | public static final String DEPENDENCIES = "required-after:jei"; 31 | public static final String GUI_FACTORY = "com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfigGuiFactory"; 32 | 33 | public static final Logger logger = LogManager.getLogger(JEIUtilities.NAME); 34 | 35 | @EventHandler 36 | public void preInit(FMLPreInitializationEvent event) { 37 | JeiUtilitiesConfig.preInit(event); 38 | } 39 | 40 | @EventHandler 41 | public void init(FMLInitializationEvent event) { 42 | logger.info("JEI Utilities Initializing..."); 43 | if (JeiUtilitiesConfig.getRecordRecipes()) { 44 | MinecraftForge.EVENT_BUS.register(BookmarkInputHandler.getInstance()); 45 | } 46 | MinecraftForge.EVENT_BUS.register(GuiInputHandler.getInstance()); 47 | KeyBindings.registerKeyBindings(); 48 | } 49 | 50 | @EventHandler 51 | public void onLoadComplete(FMLLoadCompleteEvent event) { 52 | if (JeiUtilitiesConfig.getRecordRecipes() || JeiUtilitiesConfig.isEnableHistory()) { 53 | JeiUtilitiesPlugin.inputHandler = ObfuscationReflectionHelper.getPrivateValue(Internal.class, null, "inputHandler"); 54 | if (JeiUtilitiesConfig.getRecordRecipes()) { 55 | BookmarkInputHandler.getInstance().onInputHandlerSet(); 56 | } 57 | } 58 | logger.info("JEI Utilities Loading Complete..."); 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/asm/JeiUtilitiesClassTransformer.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.asm; 2 | 3 | import com.github.vfyjxf.jeiutilities.JEIUtilities; 4 | import com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig; 5 | import net.minecraft.launchwrapper.IClassTransformer; 6 | import org.objectweb.asm.ClassReader; 7 | import org.objectweb.asm.ClassWriter; 8 | import org.objectweb.asm.Opcodes; 9 | import org.objectweb.asm.tree.*; 10 | 11 | @SuppressWarnings("unused") 12 | public class JeiUtilitiesClassTransformer implements IClassTransformer { 13 | @Override 14 | public byte[] transform(String name, String transformedName, byte[] basicClass) { 15 | String internalName = toInternalClassName(transformedName); 16 | if ("mezz/jei/gui/recipes/RecipesGui".equals(internalName)) { 17 | if (JeiUtilitiesConfig.isEnableHistory()) { 18 | ClassNode classNode = new ClassNode(); 19 | ClassReader classReader = new ClassReader(basicClass); 20 | classReader.accept(classNode, 0); 21 | for (MethodNode methodNode : classNode.methods) { 22 | if ("show".equals(methodNode.name)) { 23 | JEIUtilities.logger.info("Transforming : " + internalName + ";" + methodNode.name + methodNode.desc); 24 | AbstractInsnNode target = methodNode.instructions.getFirst(); 25 | while (target.getOpcode() != Opcodes.RETURN) { 26 | target = target.getNext(); 27 | } 28 | InsnList insnList = new InsnList(); 29 | insnList.add(new VarInsnNode(Opcodes.ALOAD, 1)); 30 | insnList.add(new MethodInsnNode( 31 | Opcodes.INVOKESTATIC, 32 | "com/github/vfyjxf/jeiutilities/jei/JeiHooks", 33 | "onSetFocus", 34 | "(Lmezz/jei/api/recipe/IFocus;)V", 35 | false) 36 | ); 37 | 38 | methodNode.instructions.insertBefore(target, insnList); 39 | ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); 40 | classNode.accept(classWriter); 41 | return classWriter.toByteArray(); 42 | } 43 | } 44 | 45 | } 46 | } 47 | if ("mezz/jei/startup/JeiStarter".equals(internalName)) { 48 | ClassNode classNode = new ClassNode(); 49 | ClassReader classReader = new ClassReader(basicClass); 50 | classReader.accept(classNode, 0); 51 | for (MethodNode methodNode : classNode.methods) { 52 | if ("start".equals(methodNode.name)) { 53 | JEIUtilities.logger.info("Transforming : " + internalName + ";" + methodNode.name + methodNode.desc); 54 | //patch create method 55 | { 56 | AbstractInsnNode target = methodNode.instructions.getFirst(); 57 | for (AbstractInsnNode node : methodNode.instructions.toArray()) { 58 | if (node.getOpcode() == Opcodes.NEW && node instanceof TypeInsnNode) { 59 | TypeInsnNode typeInsnNode = (TypeInsnNode) node; 60 | if ("mezz/jei/gui/overlay/bookmarks/BookmarkOverlay".equals(typeInsnNode.desc)) { 61 | methodNode.instructions.remove(typeInsnNode.getNext()); 62 | methodNode.instructions.remove(typeInsnNode); 63 | } 64 | if ("mezz/jei/bookmarks/BookmarkList".equals(typeInsnNode.desc)) { 65 | methodNode.instructions.remove(typeInsnNode.getNext()); 66 | methodNode.instructions.remove(typeInsnNode); 67 | } 68 | } 69 | if (node.getOpcode() == Opcodes.INVOKESPECIAL && node instanceof MethodInsnNode) { 70 | MethodInsnNode methodInsnNode = (MethodInsnNode) node; 71 | if ("mezz/jei/gui/overlay/bookmarks/BookmarkOverlay".equals(methodInsnNode.owner) && "".equals(methodInsnNode.name)) { 72 | methodInsnNode.setOpcode(Opcodes.INVOKESTATIC); 73 | methodInsnNode.owner = "com/github/vfyjxf/jeiutilities/gui/bookmark/AdvancedBookmarkOverlay"; 74 | methodInsnNode.name = "create"; 75 | methodInsnNode.desc = "(Lmezz/jei/bookmarks/BookmarkList;Lmezz/jei/gui/GuiHelper;Lmezz/jei/gui/GuiScreenHelper;)Lmezz/jei/gui/overlay/bookmarks/BookmarkOverlay;"; 76 | } 77 | if ("mezz/jei/bookmarks/BookmarkList".equals(methodInsnNode.owner) && "".equals(methodInsnNode.name)) { 78 | methodInsnNode.setOpcode(Opcodes.INVOKESTATIC); 79 | methodInsnNode.owner = "com/github/vfyjxf/jeiutilities/jei/bookmark/RecipeBookmarkList"; 80 | methodInsnNode.name = "create"; 81 | methodInsnNode.desc = "(Lmezz/jei/ingredients/IngredientRegistry;)Lmezz/jei/bookmarks/BookmarkList;"; 82 | } 83 | } 84 | } 85 | } 86 | //patch set JeiUtilitiesPlugin.recipeRegistry 87 | { 88 | AbstractInsnNode target = null; 89 | for (AbstractInsnNode node : methodNode.instructions.toArray()) { 90 | if (node.getOpcode() == Opcodes.INVOKEVIRTUAL && node instanceof MethodInsnNode) { 91 | MethodInsnNode methodInsnNode = (MethodInsnNode) node; 92 | if ("mezz/jei/bookmarks/BookmarkList".equals(methodInsnNode.owner) && "loadBookmarks".equals(methodInsnNode.name)) { 93 | target = methodInsnNode; 94 | while (!(target instanceof LabelNode)) { 95 | target = target.getPrevious(); 96 | } 97 | break; 98 | } 99 | } 100 | } 101 | if (target != null) { 102 | InsnList insnList = new InsnList(); 103 | //load recipeRegistry 104 | insnList.add(new VarInsnNode(Opcodes.ALOAD, 14)); 105 | //set field 106 | insnList.add(new MethodInsnNode(Opcodes.PUTSTATIC, 107 | "com/github/vfyjxf/jeiutilities/jei/JeiUtilitiesPlugin", 108 | "recipeRegistry", 109 | "Lmezz/jei/api/IRecipeRegistry;", false)); 110 | methodNode.instructions.insertBefore(target, insnList); 111 | } 112 | } 113 | } 114 | } 115 | ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); 116 | classNode.accept(classWriter); 117 | return classWriter.toByteArray(); 118 | } 119 | /* 120 | if ("mezz/jei/startup/ForgeModIdHelper".equals(internalName)){ 121 | ClassNode classNode = new ClassNode(); 122 | ClassReader classReader = new ClassReader(basicClass); 123 | classReader.accept(classNode, 0); 124 | for (MethodNode methodNode : classNode.methods) { 125 | if ("addModNameToIngredientTooltip".equals(methodNode.name)){ 126 | JEIUtilities.logger.info("Transforming : " + internalName + ";" + methodNode.name + methodNode.desc); 127 | } 128 | } 129 | } 130 | */ 131 | return basicClass; 132 | } 133 | 134 | private String toInternalClassName(String className) { 135 | return className.replace('.', '/'); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/asm/JeiUtilitiesLoadingPlugin.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.asm; 2 | 3 | import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; 4 | 5 | import javax.annotation.Nullable; 6 | import java.util.Map; 7 | 8 | public class JeiUtilitiesLoadingPlugin implements IFMLLoadingPlugin { 9 | @Override 10 | public String[] getASMTransformerClass() { 11 | return new String[]{"com.github.vfyjxf.jeiutilities.asm.JeiUtilitiesClassTransformer"}; 12 | } 13 | 14 | @Override 15 | public String getModContainerClass() { 16 | return null; 17 | } 18 | 19 | @Nullable 20 | @Override 21 | public String getSetupClass() { 22 | return null; 23 | } 24 | 25 | @Override 26 | public void injectData(Map data) { 27 | 28 | } 29 | 30 | @Override 31 | public String getAccessTransformerClass() { 32 | return null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/config/JeiUtilitiesConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.config; 2 | 3 | import com.github.vfyjxf.jeiutilities.JEIUtilities; 4 | import net.minecraftforge.common.config.Configuration; 5 | import net.minecraftforge.fml.client.event.ConfigChangedEvent; 6 | import net.minecraftforge.fml.common.Mod; 7 | import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; 8 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 9 | 10 | import java.io.File; 11 | 12 | @Mod.EventBusSubscriber(modid = JEIUtilities.MODID) 13 | public class JeiUtilitiesConfig { 14 | 15 | private static final String CATEGORY_HISTORY = "history"; 16 | private static final String CATEGORY_BOOKMARK = "bookmark"; 17 | private static final String CATEGORY_RENDER = "render"; 18 | 19 | private static Configuration config; 20 | private static File modConfigFile; 21 | 22 | private static boolean enableHistory = true; 23 | private static int useRows = 2; 24 | private static boolean matchesNBTs = true; 25 | private static SplittingMode splittingMode = SplittingMode.DOTTED_LINE; 26 | private static int backgroundColour = 0xee555555; 27 | 28 | private static boolean recordRecipes = true; 29 | private static boolean showRecipeBookmarkReminders = true; 30 | private static RecordMode recordMode = RecordMode.ENABLE; 31 | 32 | private static boolean adaptiveRecipePreview = true; 33 | private static float recipePreviewScaling = 0.8F; 34 | 35 | 36 | public static void preInit(FMLPreInitializationEvent event) { 37 | File configDir = new File(event.getModConfigurationDirectory(), JEIUtilities.MODID); 38 | 39 | modConfigFile = new File(configDir, JEIUtilities.MODID + ".cfg"); 40 | config = new Configuration(modConfigFile); 41 | 42 | loadConfig(); 43 | 44 | } 45 | 46 | private static void loadConfig() { 47 | 48 | if (config == null) { 49 | return; 50 | } 51 | 52 | config.load(); 53 | 54 | { 55 | enableHistory = config.getBoolean("enableHistory", 56 | CATEGORY_HISTORY, 57 | enableHistory, 58 | "Enable browsing history feature" 59 | ); 60 | 61 | useRows = config.getInt("useRows", 62 | CATEGORY_HISTORY, 63 | useRows, 64 | 1, 65 | 6, 66 | "Number of rows to use for history" 67 | ); 68 | 69 | matchesNBTs = config.getBoolean("matchesNBTs", 70 | CATEGORY_HISTORY, 71 | matchesNBTs, 72 | "Add item with different nbt to the browsing history" 73 | ); 74 | 75 | try { 76 | splittingMode = SplittingMode.valueOf( 77 | config.getString("splittingMode", 78 | CATEGORY_HISTORY, 79 | SplittingMode.DOTTED_LINE.name(), 80 | "Splitting mode for the browsing history.\n" + 81 | "Mode : BACKGROUND, DOTTED_LINE" 82 | ) 83 | ); 84 | } catch (IllegalArgumentException | NullPointerException e) { 85 | //set default mode 86 | splittingMode = SplittingMode.DOTTED_LINE; 87 | } 88 | 89 | backgroundColour = config.getInt("backgroundColour", 90 | CATEGORY_HISTORY, 91 | backgroundColour, 92 | Integer.MIN_VALUE, 93 | Integer.MAX_VALUE, 94 | "Color of the history area display" 95 | ); 96 | } 97 | { 98 | recordRecipes = config.getBoolean("recordRecipes", 99 | CATEGORY_BOOKMARK, 100 | recordRecipes, 101 | "Record current recipe when add ingredient to bookmark in recipe screen" 102 | ); 103 | 104 | showRecipeBookmarkReminders = config.getBoolean("showRecipeBookmarkReminders", 105 | CATEGORY_BOOKMARK, 106 | showRecipeBookmarkReminders, 107 | "Display a letter \"R\" in the upper left corner of the recipe bookmark." 108 | ); 109 | 110 | recordMode = RecordMode.valueOf(config.getString( 111 | "recordMode", CATEGORY_BOOKMARK, recordMode.name(), 112 | "Current mode of recording recipes." + "\n" 113 | + "Enable: The opposite of RESTRICTED mode" + "\n" 114 | + "Disable: Don't record any recipes" + "\n" 115 | + "RESTRICTED: You need to hold down Shift to view the recorded recipe or record recipe.") 116 | ); 117 | 118 | } 119 | { 120 | adaptiveRecipePreview = config.getBoolean("adaptiveRecipePreview", 121 | CATEGORY_RENDER, 122 | adaptiveRecipePreview, 123 | "If true, then the recipe preview will automatically select the appropriate scaling based on the screen size." 124 | ); 125 | 126 | recipePreviewScaling = config.getFloat("recipePreviewScaling", 127 | CATEGORY_RENDER, 128 | recipePreviewScaling, 129 | 0.01F, 130 | 5.0F, 131 | "The scaling of the recipe preview.It is only used when adaptiveRecipePreview is false." 132 | ); 133 | 134 | } 135 | 136 | if (config.hasChanged()) { 137 | config.save(); 138 | } 139 | } 140 | 141 | public static Configuration getConfig() { 142 | return config; 143 | } 144 | 145 | public static File getModConfigFile() { 146 | return modConfigFile; 147 | } 148 | 149 | public static boolean isEnableHistory() { 150 | return enableHistory; 151 | } 152 | 153 | public static int getUseRows() { 154 | return useRows; 155 | } 156 | 157 | public static boolean isMatchesNBTs() { 158 | return matchesNBTs; 159 | } 160 | 161 | public static boolean getRecordRecipes() { 162 | return recordRecipes; 163 | } 164 | 165 | public static RecordMode getRecordMode() { 166 | return recordMode; 167 | } 168 | 169 | public static int getBackgroundColour() { 170 | return backgroundColour; 171 | } 172 | 173 | public static void setRecordMode(RecordMode mode) { 174 | JeiUtilitiesConfig.recordMode = mode; 175 | config.get(CATEGORY_BOOKMARK, "recordMode", recordMode.name(), "Current mode of recording recipes").set(mode.name()); 176 | config.save(); 177 | } 178 | 179 | public static boolean isShowRecipeBookmarkReminders() { 180 | return showRecipeBookmarkReminders; 181 | } 182 | 183 | public static SplittingMode getSplittingMode() { 184 | return splittingMode; 185 | } 186 | 187 | public static boolean isAdaptiveRecipePreview() { 188 | return adaptiveRecipePreview; 189 | } 190 | 191 | public static float getRecipePreviewScaling() { 192 | return recipePreviewScaling; 193 | } 194 | 195 | @SubscribeEvent 196 | public static void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event) { 197 | if (event.getModID().equals(JEIUtilities.MODID)) { 198 | if (config.hasChanged()) { 199 | config.save(); 200 | } 201 | loadConfig(); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/config/JeiUtilitiesConfigGuiFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.config; 2 | 3 | import com.github.vfyjxf.jeiutilities.JEIUtilities; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.gui.GuiScreen; 6 | import net.minecraftforge.common.config.ConfigElement; 7 | import net.minecraftforge.fml.client.IModGuiFactory; 8 | import net.minecraftforge.fml.client.config.GuiConfig; 9 | import net.minecraftforge.fml.client.config.IConfigElement; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Set; 14 | 15 | public class JeiUtilitiesConfigGuiFactory implements IModGuiFactory { 16 | @Override 17 | public void initialize(Minecraft minecraftInstance) { 18 | 19 | } 20 | 21 | @Override 22 | public boolean hasConfigGui() { 23 | return true; 24 | } 25 | 26 | @Override 27 | public GuiScreen createConfigGui(GuiScreen parentScreen) { 28 | return new GuiConfig(parentScreen, 29 | getConfigElements(), 30 | JEIUtilities.MODID, 31 | false, 32 | false, 33 | JEIUtilities.NAME 34 | ); 35 | } 36 | 37 | @Override 38 | public Set runtimeGuiCategories() { 39 | return null; 40 | } 41 | 42 | private static List getConfigElements() { 43 | List list = new ArrayList<>(); 44 | for (String name : JeiUtilitiesConfig.getConfig().getCategoryNames()) { 45 | list.add(new ConfigElement(JeiUtilitiesConfig.getConfig().getCategory(name))); 46 | } 47 | return list; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/config/KeyBindings.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.config; 2 | 3 | import com.github.vfyjxf.jeiutilities.JEIUtilities; 4 | import net.minecraft.client.settings.KeyBinding; 5 | import net.minecraftforge.client.settings.KeyConflictContext; 6 | import net.minecraftforge.client.settings.KeyModifier; 7 | import net.minecraftforge.fml.client.registry.ClientRegistry; 8 | import net.minecraftforge.fml.common.Mod; 9 | import net.minecraftforge.fml.relauncher.Side; 10 | import org.lwjgl.input.Keyboard; 11 | 12 | import javax.annotation.Nonnull; 13 | 14 | @Mod.EventBusSubscriber(modid = JEIUtilities.MODID, value = Side.CLIENT) 15 | public final class KeyBindings { 16 | 17 | public static final KeyBinding displayRecipe; 18 | public static final KeyBinding pickBookmark; 19 | public static final KeyBinding transferRecipe; 20 | public static final KeyBinding transferRecipeMax; 21 | 22 | static { 23 | displayRecipe = new KeyBinding("key.jeiutilities.displayRecipe", KeyConflictContext.GUI, Keyboard.KEY_LCONTROL, JEIUtilities.NAME); 24 | pickBookmark = new KeyBinding("key.jeiutilities.pickBookmark", KeyConflictContext.GUI, -98, JEIUtilities.NAME); 25 | transferRecipe = new KeyBinding("key.jeiutilities.transferRecipe", KeyConflictContext.GUI, KeyModifier.CONTROL, Keyboard.KEY_W, JEIUtilities.NAME); 26 | transferRecipeMax = new KeyBinding("key.jeiutilities.transferRecipeMax", KeyConflictContext.GUI, KeyModifier.CONTROL, Keyboard.KEY_T, JEIUtilities.NAME); 27 | } 28 | 29 | public static void registerKeyBindings() { 30 | ClientRegistry.registerKeyBinding(displayRecipe); 31 | ClientRegistry.registerKeyBinding(pickBookmark); 32 | ClientRegistry.registerKeyBinding(transferRecipe); 33 | ClientRegistry.registerKeyBinding(transferRecipeMax); 34 | } 35 | 36 | 37 | /** 38 | * @param keyBinding The key binding to check. 39 | * @param checkModifier Whether to check the modifier(Usually when the KeyModifier of your KeyBinding is set to {@link net.minecraftforge.client.settings.KeyModifier#NONE}, but you want to allow it to be pressed at the same time as several other KeyModifiers is set to false.). 40 | * @return true if the key is down. 41 | */ 42 | public static boolean isKeyDown(@Nonnull KeyBinding keyBinding, boolean checkModifier) { 43 | if (checkModifier) { 44 | return Keyboard.isKeyDown(keyBinding.getKeyCode()) && 45 | keyBinding.getKeyConflictContext().isActive() && 46 | keyBinding.getKeyModifier().isActive(keyBinding.getKeyConflictContext()); 47 | } else { 48 | return Keyboard.isKeyDown(keyBinding.getKeyCode()) && 49 | keyBinding.getKeyConflictContext().isActive(); 50 | } 51 | } 52 | 53 | public static boolean isKeyDown(@Nonnull KeyBinding keyBinding) { 54 | return isKeyDown(keyBinding, true); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/config/RecordMode.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.config; 2 | 3 | import net.minecraft.client.resources.I18n; 4 | 5 | import java.util.Locale; 6 | 7 | /** 8 | * DISABLE : The ability to completely disable recipe logging. 9 | * ENABLE : The opposite of RESTRICTED mode. 10 | * RESTRICTED : You need to hold down Shift to view the recorded recipe or record recipe. 11 | */ 12 | public enum RecordMode { 13 | DISABLE, 14 | ENABLE, 15 | RESTRICTED; 16 | 17 | public String getLocalizedName() { 18 | return I18n.format("jeiutilities.button.name." + this.name().toLowerCase(Locale.ROOT)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/config/SplittingMode.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.config; 2 | 3 | public enum SplittingMode { 4 | BACKGROUND, 5 | DOTTED_LINE, 6 | LINE 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/gui/bookmark/AdvancedBookmarkOverlay.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.gui.bookmark; 2 | 3 | import com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig; 4 | import com.github.vfyjxf.jeiutilities.config.KeyBindings; 5 | import com.github.vfyjxf.jeiutilities.gui.common.GuiInputHandler; 6 | import com.github.vfyjxf.jeiutilities.gui.recipe.RecipePreviewWidget; 7 | import com.github.vfyjxf.jeiutilities.jei.ingredient.RecipeInfo; 8 | import mezz.jei.api.ingredients.IIngredientRenderer; 9 | import mezz.jei.bookmarks.BookmarkList; 10 | import mezz.jei.gui.GuiHelper; 11 | import mezz.jei.gui.GuiScreenHelper; 12 | import mezz.jei.gui.elements.GuiIconToggleButton; 13 | import mezz.jei.gui.overlay.IngredientGridWithNavigation; 14 | import mezz.jei.gui.overlay.bookmarks.BookmarkOverlay; 15 | import net.minecraft.client.Minecraft; 16 | import net.minecraft.client.renderer.GlStateManager; 17 | import net.minecraftforge.fml.common.ObfuscationReflectionHelper; 18 | import org.lwjgl.input.Keyboard; 19 | 20 | import javax.annotation.Nonnull; 21 | import java.awt.*; 22 | import java.util.Set; 23 | 24 | import static com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin.ingredientListOverlay; 25 | import static mezz.jei.gui.overlay.IngredientGrid.INGREDIENT_HEIGHT; 26 | import static mezz.jei.gui.overlay.IngredientGrid.INGREDIENT_WIDTH; 27 | 28 | @SuppressWarnings("unused") 29 | public class AdvancedBookmarkOverlay extends BookmarkOverlay { 30 | 31 | private static final int BUTTON_SIZE = 20; 32 | 33 | private final IngredientGridWithNavigation contents; 34 | private final GuiIconToggleButton recordConfigButton; 35 | private final BookmarkInputHandler inputHandler; 36 | 37 | private RecipeInfo infoUnderMouse; 38 | private RecipePreviewWidget recipeLayout; 39 | 40 | public static BookmarkOverlay create(BookmarkList bookmarkList, GuiHelper guiHelper, GuiScreenHelper guiScreenHelper) { 41 | if (JeiUtilitiesConfig.getRecordRecipes()) { 42 | return new AdvancedBookmarkOverlay(bookmarkList, guiHelper, guiScreenHelper); 43 | } else { 44 | return new BookmarkOverlay(bookmarkList, guiHelper, guiScreenHelper); 45 | } 46 | } 47 | 48 | private AdvancedBookmarkOverlay(BookmarkList bookmarkList, GuiHelper guiHelper, GuiScreenHelper guiScreenHelper) { 49 | super(bookmarkList, guiHelper, guiScreenHelper); 50 | this.recordConfigButton = RecordConfigButton.create(this); 51 | this.contents = ObfuscationReflectionHelper.getPrivateValue(BookmarkOverlay.class, this, "contents"); 52 | this.inputHandler = BookmarkInputHandler.getInstance(); 53 | } 54 | 55 | @Override 56 | public void updateBounds(@Nonnull Rectangle area, @Nonnull Set guiExclusionAreas) { 57 | super.updateBounds(area, guiExclusionAreas); 58 | Rectangle rectangle = new Rectangle(area); 59 | rectangle.x = contents.getArea().x; 60 | rectangle.width = contents.getArea().width; 61 | this.recordConfigButton.updateBounds(new Rectangle( 62 | rectangle.x + BUTTON_SIZE + 2, 63 | (int) Math.floor(rectangle.getMaxY()) - BUTTON_SIZE - 2, 64 | BUTTON_SIZE, 65 | BUTTON_SIZE 66 | )); 67 | } 68 | 69 | @Override 70 | public void drawScreen(@Nonnull Minecraft minecraft, int mouseX, int mouseY, float partialTicks) { 71 | super.drawScreen(minecraft, mouseX, mouseY, partialTicks); 72 | this.recordConfigButton.draw(minecraft, mouseX, mouseY, partialTicks); 73 | } 74 | 75 | @Override 76 | @SuppressWarnings({"unchecked", "rawtypes"}) 77 | public void drawTooltips(@Nonnull Minecraft minecraft, int mouseX, int mouseY) { 78 | boolean renderRecipe = false; 79 | boolean shouldRenderRecipe = false; 80 | int eventKey = Keyboard.getEventKey(); 81 | boolean displayRecipe = KeyBindings.isKeyDown(KeyBindings.displayRecipe, false); 82 | boolean isTransferRecipe = KeyBindings.isKeyDown(KeyBindings.transferRecipe); 83 | boolean isTransferRecipeMax = KeyBindings.isKeyDown(KeyBindings.transferRecipeMax); 84 | if (displayRecipe | isTransferRecipe | isTransferRecipeMax) { 85 | Object ingredientUnderMouse = this.getIngredientUnderMouse(); 86 | if (ingredientUnderMouse instanceof RecipeInfo) { 87 | RecipeInfo recipeInfo = (RecipeInfo) ingredientUnderMouse; 88 | shouldRenderRecipe = true; 89 | RecipePreviewWidget recipeLayout; 90 | if (this.infoUnderMouse == recipeInfo) { 91 | recipeLayout = this.recipeLayout; 92 | } else { 93 | this.infoUnderMouse = recipeInfo; 94 | 95 | recipeLayout = RecipePreviewWidget.createLayout(recipeInfo, mouseX, mouseY); 96 | this.recipeLayout = recipeLayout; 97 | } 98 | 99 | if (recipeLayout != null && displayRecipe) { 100 | updatePosition(mouseX, mouseY); 101 | recipeLayout.drawRecipe(minecraft, mouseX, mouseY); 102 | renderRecipe = true; 103 | } 104 | 105 | } 106 | } 107 | 108 | if (!(GuiInputHandler.isContainerTextFieldFocused() || ingredientListOverlay.hasKeyboardFocus())) { 109 | if (isTransferRecipe || isTransferRecipeMax) { 110 | if (shouldRenderRecipe && recipeLayout != null && recipeLayout.getTransferError() != null) { 111 | if (!renderRecipe) { 112 | recipeLayout.drawRecipe(minecraft, mouseX, mouseY); 113 | renderRecipe = true; 114 | } 115 | recipeLayout.showError(minecraft, mouseX, mouseY); 116 | } 117 | } 118 | } 119 | 120 | if (!renderRecipe) { 121 | super.drawTooltips(minecraft, mouseX, mouseY); 122 | this.recordConfigButton.drawTooltips(minecraft, mouseX, mouseY); 123 | } 124 | 125 | if (inputHandler.getDraggedElement() != null) { 126 | GlStateManager.pushMatrix(); 127 | GlStateManager.translate(0.0F, 0.0F, 200.0F); 128 | IIngredientRenderer ingredientRenderer = inputHandler.getDraggedElement().getIngredientRenderer(); 129 | ingredientRenderer.render(minecraft, mouseX, mouseY, inputHandler.getDraggedElement().getIngredient()); 130 | GlStateManager.popMatrix(); 131 | } 132 | 133 | } 134 | 135 | private void updatePosition(int mouseX, int mouseY) { 136 | if (this.recipeLayout != null) { 137 | int x = this.recipeLayout.getPosX(); 138 | int y = this.recipeLayout.getPosY(); 139 | Rectangle area = new Rectangle(x - INGREDIENT_WIDTH, y - INGREDIENT_WIDTH, INGREDIENT_WIDTH * 2, INGREDIENT_HEIGHT * 2); 140 | if (!area.contains(mouseX, mouseY)) { 141 | this.recipeLayout.setPosition(mouseX, mouseY); 142 | } 143 | } 144 | } 145 | 146 | @Override 147 | public boolean handleMouseClicked(int mouseX, int mouseY, int mouseButton) { 148 | boolean result = super.handleMouseClicked(mouseX, mouseY, mouseButton); 149 | 150 | if (recordConfigButton.isMouseOver(mouseX, mouseY)) { 151 | return recordConfigButton.handleMouseClick(mouseX, mouseY); 152 | } 153 | 154 | return result; 155 | } 156 | 157 | public RecipeInfo getInfoUnderMouse() { 158 | return infoUnderMouse; 159 | } 160 | 161 | public RecipePreviewWidget getRecipeLayout() { 162 | return recipeLayout; 163 | } 164 | 165 | public void setInfoUnderMouse(RecipeInfo infoUnderMouse) { 166 | this.infoUnderMouse = infoUnderMouse; 167 | } 168 | 169 | public void setRecipeLayout(RecipePreviewWidget recipeLayout) { 170 | this.recipeLayout = recipeLayout; 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/gui/bookmark/BookmarkInputHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.gui.bookmark; 2 | 3 | import com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig; 4 | import com.github.vfyjxf.jeiutilities.config.RecordMode; 5 | import com.github.vfyjxf.jeiutilities.helper.IngredientHelper; 6 | import com.github.vfyjxf.jeiutilities.helper.ReflectionUtils; 7 | import com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin; 8 | import com.github.vfyjxf.jeiutilities.jei.ingredient.RecipeInfo; 9 | import it.unimi.dsi.fastutil.ints.IntArraySet; 10 | import it.unimi.dsi.fastutil.ints.IntSet; 11 | import mezz.jei.api.recipe.IFocus; 12 | import mezz.jei.api.recipe.IRecipeCategory; 13 | import mezz.jei.api.recipe.IRecipeWrapper; 14 | import mezz.jei.bookmarks.BookmarkList; 15 | import mezz.jei.config.Config; 16 | import mezz.jei.config.KeyBindings; 17 | import mezz.jei.gui.Focus; 18 | import mezz.jei.gui.ingredients.GuiIngredient; 19 | import mezz.jei.gui.ingredients.IIngredientListElement; 20 | import mezz.jei.gui.ingredients.IngredientLookupState; 21 | import mezz.jei.gui.overlay.IngredientGrid; 22 | import mezz.jei.gui.overlay.IngredientGridWithNavigation; 23 | import mezz.jei.gui.overlay.bookmarks.BookmarkOverlay; 24 | import mezz.jei.gui.overlay.bookmarks.LeftAreaDispatcher; 25 | import mezz.jei.gui.recipes.RecipeGuiLogic; 26 | import mezz.jei.gui.recipes.RecipeLayout; 27 | import mezz.jei.gui.recipes.RecipesGui; 28 | import mezz.jei.input.IClickedIngredient; 29 | import mezz.jei.input.InputHandler; 30 | import mezz.jei.input.MouseHelper; 31 | import mezz.jei.render.IngredientListBatchRenderer; 32 | import mezz.jei.render.IngredientListSlot; 33 | import mezz.jei.util.ReflectionUtil; 34 | import net.minecraft.client.Minecraft; 35 | import net.minecraft.client.gui.GuiScreen; 36 | import net.minecraft.client.gui.GuiTextField; 37 | import net.minecraft.client.gui.inventory.GuiContainer; 38 | import net.minecraftforge.client.event.GuiOpenEvent; 39 | import net.minecraftforge.client.event.GuiScreenEvent; 40 | import net.minecraftforge.fml.common.ObfuscationReflectionHelper; 41 | import net.minecraftforge.fml.common.eventhandler.EventPriority; 42 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 43 | import org.apache.commons.lang3.tuple.Pair; 44 | import org.lwjgl.input.Keyboard; 45 | import org.lwjgl.input.Mouse; 46 | 47 | import javax.annotation.Nonnull; 48 | import java.lang.reflect.InvocationTargetException; 49 | import java.util.LinkedList; 50 | import java.util.List; 51 | 52 | public class BookmarkInputHandler { 53 | 54 | private static BookmarkInputHandler instance; 55 | 56 | //recipe 57 | 58 | private RecipesGui recipesGui; 59 | private RecipeGuiLogic logic; 60 | 61 | //bookmark 62 | 63 | private BookmarkList bookmarkList; 64 | private LeftAreaDispatcher leftAreaDispatcher; 65 | private IngredientGrid bookmarkIngredientGrid; 66 | private IngredientGridWithNavigation bookmarkContents; 67 | private IngredientListBatchRenderer bookmarkIngredientSlots; 68 | 69 | private final IntSet clickHandled = new IntArraySet(); 70 | private IIngredientListElement draggedElement; 71 | 72 | public static BookmarkInputHandler getInstance() { 73 | if (instance == null) { 74 | instance = new BookmarkInputHandler(); 75 | } 76 | return instance; 77 | } 78 | 79 | @SubscribeEvent(priority = EventPriority.HIGH) 80 | public void onBookmarkListAdd(GuiScreenEvent.KeyboardInputEvent.Post event) { 81 | 82 | if (JeiUtilitiesConfig.getRecordMode() == RecordMode.DISABLE) { 83 | return; 84 | } 85 | 86 | if (isContainerTextFieldFocused()) { 87 | return; 88 | } 89 | 90 | final int eventKey = Keyboard.getEventKey(); 91 | 92 | if (isAddBookmark(eventKey)) { 93 | if (JeiUtilitiesConfig.getRecordMode() == RecordMode.ENABLE && GuiContainer.isShiftKeyDown()) { 94 | //handle shift + add bookmarks in ENABLE mode. 95 | IClickedIngredient clicked = recipesGui.getIngredientUnderMouse(MouseHelper.getX(), MouseHelper.getY()); 96 | if (clicked != null) { 97 | if (addOrRemoveBookmark(clicked.getValue())) { 98 | event.setCanceled(true); 99 | } 100 | } 101 | } else { 102 | //In RESTRICTED mode, player needs to press shift in order to mark the recipe. 103 | boolean withShift = JeiUtilitiesConfig.getRecordMode() == RecordMode.RESTRICTED; 104 | if (withShift && !GuiContainer.isShiftKeyDown()) { 105 | return; 106 | } 107 | 108 | Pair output = getOutputUnderMouse(); 109 | if (output != null) { 110 | IngredientLookupState state = ReflectionUtils.getFieldValue(RecipeGuiLogic.class, logic, "state"); 111 | if (state != null && state.getFocus() != null) { 112 | boolean isInputMode = state.getFocus().getMode() == IFocus.Mode.INPUT; 113 | String recipeCategoryUid = state.getRecipeCategories().get(state.getRecipeCategoryIndex()).getUid(); 114 | 115 | IRecipeWrapper recipeWrapper = ReflectionUtils.getFieldValue(RecipeLayout.class, output.getLeft(), "recipeWrapper"); 116 | RecipeInfo recipeInfo = RecipeInfo.create( 117 | IngredientHelper.getNormalize(state.getFocus().getValue()), 118 | IngredientHelper.getNormalize(output.getRight()), 119 | recipeCategoryUid, 120 | state.getRecipeIndex(), 121 | isInputMode, 122 | recipeWrapper 123 | ); 124 | 125 | if (addOrRemoveBookmark(recipeInfo)) { 126 | event.setCanceled(true); 127 | } 128 | 129 | } 130 | } 131 | } 132 | } 133 | 134 | } 135 | 136 | /** 137 | * open recorded recipe and handle bookmarks movement(Replace jei InputHandler). 138 | */ 139 | @SubscribeEvent(priority = EventPriority.HIGH) 140 | public void onMouseClicked(GuiScreenEvent.MouseInputEvent.Pre event) { 141 | 142 | final int eventButton = Mouse.getEventButton(); 143 | 144 | if (eventButton > -1) { 145 | if (Mouse.getEventButtonState()) { 146 | if (!clickHandled.contains(eventButton)) { 147 | 148 | if (handleBookmarkMove(eventButton)) { 149 | clickHandled.add(eventButton); 150 | event.setCanceled(true); 151 | return; 152 | } 153 | 154 | if (handleMouseClick(eventButton)) { 155 | clickHandled.add(eventButton); 156 | event.setCanceled(true); 157 | } 158 | 159 | 160 | } 161 | } else if (clickHandled.contains(eventButton)) { 162 | clickHandled.remove(eventButton); 163 | event.setCanceled(true); 164 | } 165 | } 166 | 167 | } 168 | 169 | /** 170 | * open recorded recipe(Replace jei InputHandler) 171 | */ 172 | @SubscribeEvent(priority = EventPriority.HIGH) 173 | public void onKeyPressed(GuiScreenEvent.KeyboardInputEvent.Post event) { 174 | 175 | char typedChar = Keyboard.getEventCharacter(); 176 | int eventKey = Keyboard.getEventKey(); 177 | boolean shouldHandleKey = (eventKey == 0 && typedChar >= 32) || Keyboard.getEventKeyState(); 178 | if (!shouldHandleKey) { 179 | return; 180 | } 181 | 182 | event.setCanceled(handleFocusKeybindings(eventKey)); 183 | 184 | } 185 | 186 | /** 187 | * Restore bookmarks to their initial state when a gui is closed/opened. 188 | */ 189 | @SubscribeEvent 190 | public void onGuiClosed(GuiOpenEvent event) { 191 | this.draggedElement = null; 192 | this.notifyListenersOfChange(); 193 | } 194 | 195 | public IIngredientListElement getDraggedElement() { 196 | return draggedElement; 197 | } 198 | 199 | private boolean handleMouseClick(int mouseButton) { 200 | 201 | if (this.draggedElement != null) { 202 | return false; 203 | } 204 | 205 | IClickedIngredient clicked = leftAreaDispatcher.getIngredientUnderMouse(MouseHelper.getX(), MouseHelper.getY()); 206 | if (clicked != null) { 207 | Object ingredient = clicked.getValue(); 208 | 209 | if (ingredient instanceof RecipeInfo) { 210 | RecipeInfo recipeInfo = (RecipeInfo) ingredient; 211 | 212 | if (mouseButton == 0) { 213 | 214 | if (JeiUtilitiesConfig.getRecordMode() == RecordMode.DISABLE) { 215 | recipesGui.show(new Focus<>(IFocus.Mode.OUTPUT, recipeInfo.getResult())); 216 | return true; 217 | } 218 | 219 | //Use to invert the operation when shift is pressed. 220 | if (handleInvert(recipeInfo)) { 221 | return true; 222 | } 223 | 224 | showRecipe(new Focus<>(recipeInfo.getMode(), recipeInfo.getIngredient())); 225 | JeiUtilitiesPlugin.getGrid().ifPresent(grid -> grid.addHistoryIngredient(recipeInfo.getResult())); 226 | IngredientLookupState state = ReflectionUtils.getFieldValue(RecipeGuiLogic.class, logic, "state"); 227 | if (state != null) { 228 | state.setRecipeCategoryIndex(getRecipeCategoryIndex(state, recipeInfo.getRecipeCategoryUid())); 229 | state.setRecipeIndex(recipeInfo.getRecipeIndex()); 230 | updateRecipes(); 231 | recipesGui.onStateChange(); 232 | clickHandled.add(mouseButton); 233 | return true; 234 | } 235 | } else if (mouseButton == 1) { 236 | recipesGui.show(new Focus<>(IFocus.Mode.INPUT, recipeInfo.getResult())); 237 | return true; 238 | } 239 | 240 | } 241 | 242 | } 243 | return false; 244 | } 245 | 246 | private boolean handleFocusKeybindings(int eventKey) { 247 | final boolean showRecipe = isShowRecipe(eventKey); 248 | final boolean showUses = KeyBindings.showUses.isActiveAndMatches(eventKey); 249 | 250 | if (showRecipe || showUses) { 251 | IClickedIngredient clicked = leftAreaDispatcher.getIngredientUnderMouse(MouseHelper.getX(), MouseHelper.getY()); 252 | if (clicked != null) { 253 | 254 | Object clickedIngredient = clicked.getValue(); 255 | if (clickedIngredient instanceof RecipeInfo) { 256 | RecipeInfo recipeInfo = (RecipeInfo) clickedIngredient; 257 | 258 | if (showRecipe) { 259 | 260 | if (JeiUtilitiesConfig.getRecordMode() == RecordMode.DISABLE) { 261 | recipesGui.show(new Focus<>(IFocus.Mode.OUTPUT, recipeInfo.getResult())); 262 | return true; 263 | } 264 | 265 | if (handleInvert(recipeInfo)) { 266 | return true; 267 | } 268 | 269 | showRecipe(new Focus<>(recipeInfo.getMode(), recipeInfo.getIngredient())); 270 | JeiUtilitiesPlugin.getGrid().ifPresent(grid -> grid.addHistoryIngredient(recipeInfo.getResult())); 271 | IngredientLookupState state = ReflectionUtils.getFieldValue(RecipeGuiLogic.class, logic, "state"); 272 | if (state != null) { 273 | state.setRecipeCategoryIndex(getRecipeCategoryIndex(state, recipeInfo.getRecipeCategoryUid())); 274 | state.setRecipeIndex(recipeInfo.getRecipeIndex()); 275 | updateRecipes(); 276 | recipesGui.onStateChange(); 277 | return true; 278 | } 279 | 280 | } else { 281 | recipesGui.show(new Focus<>(IFocus.Mode.INPUT, recipeInfo.getResult())); 282 | return true; 283 | } 284 | 285 | } 286 | 287 | } 288 | } 289 | 290 | return false; 291 | } 292 | 293 | private boolean handleBookmarkMove(int eventButton) { 294 | //Pick up the bookmark to the mouse. 295 | if (eventButton == 2 && draggedElement == null) { 296 | IIngredientListElement elementUnderMouse = bookmarkIngredientGrid.getElementUnderMouse(); 297 | if (elementUnderMouse != null) { 298 | pickUpElement(elementUnderMouse); 299 | return true; 300 | } 301 | } else if (eventButton == 0 || eventButton == 1 || eventButton == 2) { 302 | 303 | if (draggedElement == null) { 304 | return false; 305 | } 306 | 307 | IIngredientListElement replaceElement = null; 308 | if (eventButton == 2) { 309 | replaceElement = bookmarkIngredientGrid.getElementUnderMouse(); 310 | } 311 | 312 | int insertIndex = getInsertIndex(); 313 | 314 | if (insertIndex > -1) { 315 | if (bookmarkList.remove(draggedElement.getIngredient())) { 316 | addElement(insertIndex, draggedElement); 317 | } 318 | draggedElement = null; 319 | notifyListenersOfChange(); 320 | if (replaceElement != null) { 321 | pickUpElement(replaceElement); 322 | } 323 | return true; 324 | } 325 | 326 | } 327 | 328 | return false; 329 | } 330 | 331 | private Pair getOutputUnderMouse() { 332 | List recipeLayouts = ReflectionUtils.getFieldValue(RecipesGui.class, recipesGui, "recipeLayouts"); 333 | if (recipeLayouts != null) { 334 | for (RecipeLayout recipeLayout : recipeLayouts) { 335 | GuiIngredient clicked = recipeLayout.getGuiIngredientUnderMouse(MouseHelper.getX(), MouseHelper.getY()); 336 | if (clicked != null && !clicked.isInput()) { 337 | return Pair.of(recipeLayout, clicked.getDisplayedIngredient()); 338 | } 339 | } 340 | } 341 | return null; 342 | } 343 | 344 | private boolean addOrRemoveBookmark(Object value) { 345 | if (bookmarkList.remove(value)) { 346 | if (bookmarkList.isEmpty() && Config.isBookmarkOverlayEnabled()) { 347 | Config.toggleBookmarkEnabled(); 348 | } 349 | 350 | return true; 351 | } else { 352 | if (!Config.isBookmarkOverlayEnabled()) { 353 | Config.toggleBookmarkEnabled(); 354 | } 355 | return bookmarkList.add(value); 356 | } 357 | } 358 | 359 | private boolean handleInvert(RecipeInfo recipeInfo) { 360 | if (JeiUtilitiesConfig.getRecordMode() == RecordMode.RESTRICTED) { 361 | if (!GuiContainer.isShiftKeyDown()) { 362 | recipesGui.show(new Focus<>(IFocus.Mode.OUTPUT, recipeInfo.getResult())); 363 | return true; 364 | } 365 | } else { 366 | if (GuiContainer.isShiftKeyDown()) { 367 | recipesGui.show(new Focus<>(IFocus.Mode.OUTPUT, recipeInfo.getResult())); 368 | return true; 369 | } 370 | } 371 | return false; 372 | } 373 | 374 | private void updateRecipes() { 375 | try { 376 | ReflectionUtils.getMethod(RecipeGuiLogic.class, "updateRecipes", void.class).invoke(logic); 377 | } catch (IllegalAccessException | InvocationTargetException e) { 378 | e.printStackTrace(); 379 | } 380 | } 381 | 382 | private int getRecipeCategoryIndex(@Nonnull IngredientLookupState state, @Nonnull String recipeCategoryUid) { 383 | for (IRecipeCategory recipeCategory : state.getRecipeCategories()) { 384 | if (recipeCategory.getUid().equals(recipeCategoryUid)) { 385 | return state.getRecipeCategories().indexOf(recipeCategory); 386 | } 387 | } 388 | return 0; 389 | } 390 | 391 | private void showRecipe(IFocus focus) { 392 | focus = Focus.check(focus); 393 | 394 | if (logic.setFocus(focus)) { 395 | try { 396 | ReflectionUtils.getMethod(RecipesGui.class, "open", void.class).invoke(recipesGui); 397 | } catch (IllegalAccessException | InvocationTargetException e) { 398 | e.printStackTrace(); 399 | } 400 | } 401 | } 402 | 403 | private boolean isContainerTextFieldFocused() { 404 | GuiScreen gui = Minecraft.getMinecraft().currentScreen; 405 | if (gui == null) { 406 | return false; 407 | } 408 | GuiTextField textField = ReflectionUtil.getFieldWithClass(gui, GuiTextField.class); 409 | return textField != null && textField.getVisible() && textField.isFocused(); 410 | } 411 | 412 | private boolean isAddBookmark(int keycode) { 413 | return keycode != 0 && 414 | KeyBindings.bookmark.getKeyCode() == keycode 415 | && KeyBindings.bookmark.getKeyConflictContext().isActive(); 416 | } 417 | 418 | private boolean isShowRecipe(int keycode) { 419 | return keycode != 0 && 420 | KeyBindings.showRecipe.getKeyCode() == keycode && 421 | KeyBindings.showRecipe.getKeyConflictContext().isActive(); 422 | } 423 | 424 | @SuppressWarnings("rawtypes") 425 | private int getFirstItemIndex(List ingredientList) { 426 | int firstItemIndex = ReflectionUtils.getFieldValue( 427 | IngredientGridWithNavigation.class, 428 | bookmarkContents, 429 | "firstItemIndex" 430 | ); 431 | if (firstItemIndex >= ingredientList.size()) { 432 | firstItemIndex = 0; 433 | } 434 | return firstItemIndex; 435 | } 436 | 437 | @SuppressWarnings("rawtypes") 438 | private void pickUpElement(@Nonnull IIngredientListElement element) { 439 | List ingredientList = new LinkedList<>(bookmarkList.getIngredientList()); 440 | ingredientList.remove(element); 441 | int firstItemIndex = getFirstItemIndex(ingredientList); 442 | bookmarkIngredientSlots.set(firstItemIndex, ingredientList); 443 | this.draggedElement = element; 444 | } 445 | 446 | /** 447 | * @return The index of the slot into which the bookmark will be inserted. 448 | */ 449 | private int getInsertIndex() { 450 | int mouseX = MouseHelper.getX(); 451 | int mouseY = MouseHelper.getY(); 452 | IngredientListSlot slotUnderMouse = null; 453 | List allGuiIngredientSlots = bookmarkIngredientSlots.getAllGuiIngredientSlots(); 454 | for (IngredientListSlot slot : allGuiIngredientSlots) { 455 | if (slot.getArea().contains(mouseX, mouseY)) { 456 | if (slot.getIngredientRenderer() == null) { 457 | List list = ReflectionUtils.getFieldValue(BookmarkList.class, bookmarkList, "list"); 458 | return list.size() - 1; 459 | } else { 460 | slotUnderMouse = slot; 461 | break; 462 | } 463 | 464 | } 465 | } 466 | 467 | if (slotUnderMouse != null) { 468 | int halfX = slotUnderMouse.getArea().x + slotUnderMouse.getArea().width / 2; 469 | int slotIndex = bookmarkIngredientSlots.getAllGuiIngredientSlots().indexOf(slotUnderMouse); 470 | return mouseX <= halfX ? slotIndex : slotIndex + 1; 471 | } 472 | 473 | return -1; 474 | } 475 | 476 | private void addElement(int index, @Nonnull IIngredientListElement element) { 477 | 478 | for (IIngredientListElement existing : bookmarkList.getIngredientList()) { 479 | if (IngredientHelper.ingredientEquals(existing.getIngredient(), element.getIngredient())) { 480 | return; 481 | } 482 | } 483 | 484 | List list = ReflectionUtils.getFieldValue(BookmarkList.class, bookmarkList, "list"); 485 | list.add(index, element.getIngredient()); 486 | bookmarkList.getIngredientList().add(index, element); 487 | bookmarkList.saveBookmarks(); 488 | } 489 | 490 | private void notifyListenersOfChange() { 491 | try { 492 | ReflectionUtils.getMethod(BookmarkList.class, "notifyListenersOfChange", void.class).invoke(bookmarkList); 493 | } catch (IllegalAccessException | InvocationTargetException e) { 494 | throw new RuntimeException(e); 495 | } 496 | } 497 | 498 | public void onInputHandlerSet() { 499 | recipesGui = JeiUtilitiesPlugin.jeiRuntime.getRecipesGui(); 500 | logic = ObfuscationReflectionHelper.getPrivateValue(RecipesGui.class, recipesGui, "logic"); 501 | bookmarkList = ObfuscationReflectionHelper.getPrivateValue(BookmarkOverlay.class, (BookmarkOverlay) JeiUtilitiesPlugin.jeiRuntime.getBookmarkOverlay(), "bookmarkList"); 502 | leftAreaDispatcher = ObfuscationReflectionHelper.getPrivateValue(InputHandler.class, JeiUtilitiesPlugin.inputHandler, "leftAreaDispatcher"); 503 | bookmarkIngredientGrid = JeiUtilitiesPlugin.bookmarkIngredientGrid; 504 | bookmarkContents = JeiUtilitiesPlugin.bookmarkContents; 505 | bookmarkIngredientSlots = ObfuscationReflectionHelper.getPrivateValue(IngredientGrid.class, bookmarkIngredientGrid, "guiIngredientSlots"); 506 | } 507 | 508 | } 509 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/gui/bookmark/RecordConfigButton.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.gui.bookmark; 2 | 3 | import com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig; 4 | import com.github.vfyjxf.jeiutilities.config.KeyBindings; 5 | import com.github.vfyjxf.jeiutilities.config.RecordMode; 6 | import com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin; 7 | import mezz.jei.api.gui.IDrawable; 8 | import mezz.jei.gui.elements.GuiIconToggleButton; 9 | import mezz.jei.gui.overlay.bookmarks.BookmarkOverlay; 10 | import mezz.jei.util.Translator; 11 | import net.minecraft.util.ResourceLocation; 12 | import net.minecraft.util.text.TextFormatting; 13 | import org.lwjgl.input.Mouse; 14 | 15 | import javax.annotation.Nonnull; 16 | import java.util.List; 17 | 18 | public class RecordConfigButton extends GuiIconToggleButton { 19 | 20 | private RecordMode currentMode; 21 | private final BookmarkOverlay bookmarkOverlay; 22 | 23 | public static RecordConfigButton create(BookmarkOverlay bookmarkOverlay) { 24 | IDrawable offIcon = JeiUtilitiesPlugin.guiHelper.drawableBuilder(new ResourceLocation("jeiutilities:textures/gui/icon/bookmark_button_config_disable.png"), 0, 0, 16, 16).setTextureSize(16, 16).build(); 25 | IDrawable onIcon = JeiUtilitiesPlugin.guiHelper.drawableBuilder(new ResourceLocation("jeiutilities:textures/gui/icon/bookmark_button_config_enable.png"), 0, 0, 16, 16).setTextureSize(16, 16).build(); 26 | return new RecordConfigButton(offIcon, onIcon, bookmarkOverlay); 27 | } 28 | 29 | private RecordConfigButton(IDrawable offIcon, IDrawable onIcon, BookmarkOverlay bookmarkOverlay) { 30 | super(offIcon, onIcon); 31 | this.currentMode = JeiUtilitiesConfig.getRecordMode(); 32 | this.bookmarkOverlay = bookmarkOverlay; 33 | } 34 | 35 | public void setRecordMode(RecordMode mode) { 36 | this.currentMode = mode; 37 | } 38 | 39 | @Override 40 | public void getTooltips(@Nonnull List tooltip) { 41 | tooltip.add(Translator.translateToLocal("jeiutilities.tooltip.recording")); 42 | tooltip.add(TextFormatting.GRAY + Translator.translateToLocalFormatted("jeiutilities.tooltip.recording.mode", this.currentMode.getLocalizedName())); 43 | if (currentMode == RecordMode.RESTRICTED) { 44 | tooltip.add(TextFormatting.GRAY + Translator.translateToLocal("jeiutilities.tooltip.recording.description.restricted")); 45 | } 46 | if (currentMode == RecordMode.ENABLE) { 47 | tooltip.add(TextFormatting.GRAY + Translator.translateToLocal("jeiutilities.tooltip.recording.description.enable")); 48 | } 49 | if (currentMode != RecordMode.DISABLE) { 50 | tooltip.add(TextFormatting.GRAY + Translator.translateToLocalFormatted("jeiutilities.tooltip.recording.information_1", KeyBindings.displayRecipe.getDisplayName())); 51 | } 52 | } 53 | 54 | @Override 55 | public boolean isIconToggledOn() { 56 | return this.currentMode != RecordMode.DISABLE; 57 | } 58 | 59 | @Override 60 | public boolean onMouseClicked(int mouseX, int mouseY) { 61 | if (this.bookmarkOverlay.hasRoom()) { 62 | int ordinal = Mouse.getEventButton() != 2 ? this.currentMode.ordinal() + 1 : this.currentMode.ordinal() - 1; 63 | if (ordinal >= RecordMode.values().length) { 64 | ordinal = 0; 65 | } 66 | if (ordinal < 0) { 67 | ordinal = RecordMode.values().length - 1; 68 | } 69 | this.setRecordMode(RecordMode.values()[ordinal]); 70 | JeiUtilitiesConfig.setRecordMode(this.currentMode); 71 | return true; 72 | } 73 | return false; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/gui/common/GuiInputHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.gui.common; 2 | 3 | import com.github.vfyjxf.jeiutilities.config.KeyBindings; 4 | import com.github.vfyjxf.jeiutilities.gui.bookmark.AdvancedBookmarkOverlay; 5 | import com.github.vfyjxf.jeiutilities.gui.recipe.RecipePreviewWidget; 6 | import com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin; 7 | import com.github.vfyjxf.jeiutilities.jei.ingredient.RecipeInfo; 8 | import it.unimi.dsi.fastutil.ints.IntArraySet; 9 | import it.unimi.dsi.fastutil.ints.IntSet; 10 | import mezz.jei.api.recipe.transfer.IRecipeTransferError; 11 | import mezz.jei.input.MouseHelper; 12 | import mezz.jei.util.ReflectionUtil; 13 | import net.minecraft.client.Minecraft; 14 | import net.minecraft.client.gui.GuiScreen; 15 | import net.minecraft.client.gui.GuiTextField; 16 | import net.minecraft.client.gui.inventory.GuiContainer; 17 | import net.minecraft.inventory.Container; 18 | import net.minecraftforge.client.event.GuiScreenEvent; 19 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 20 | import org.lwjgl.input.Keyboard; 21 | 22 | import static com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin.ingredientListOverlay; 23 | 24 | public class GuiInputHandler { 25 | 26 | private static GuiInputHandler instance; 27 | 28 | public static GuiInputHandler getInstance() { 29 | if (instance == null) { 30 | instance = new GuiInputHandler(); 31 | } 32 | return instance; 33 | } 34 | 35 | private GuiInputHandler() { 36 | 37 | } 38 | 39 | private final IntSet pressedKeys = new IntArraySet(); 40 | 41 | @SubscribeEvent 42 | public void onKeyPressed(GuiScreenEvent.KeyboardInputEvent.Post event) { 43 | char typedChar = Keyboard.getEventCharacter(); 44 | int eventKey = Keyboard.getEventKey(); 45 | 46 | if (pressedKeys.contains(eventKey) && !Keyboard.isKeyDown(eventKey)) { 47 | pressedKeys.remove(eventKey); 48 | } 49 | 50 | boolean shouldHandleInput = (eventKey == 0 && typedChar >= 32) || Keyboard.getEventKeyState(); 51 | boolean shouldNotHandleKey = !shouldHandleInput || 52 | pressedKeys.contains(eventKey) || 53 | isContainerTextFieldFocused() || 54 | ingredientListOverlay.hasKeyboardFocus(); 55 | if (shouldNotHandleKey) { 56 | return; 57 | } 58 | 59 | boolean isTransferRecipe = KeyBindings.isKeyDown(KeyBindings.transferRecipe); 60 | boolean isTransferRecipeMax = KeyBindings.isKeyDown(KeyBindings.transferRecipeMax); 61 | if (isTransferRecipe || isTransferRecipeMax) { 62 | RecipePreviewWidget recipeLayout = getRecipeLayout(); 63 | if (recipeLayout != null) { 64 | Minecraft mc = event.getGui().mc; 65 | if (mc == null) { 66 | return; 67 | } 68 | if (mc.currentScreen instanceof GuiContainer) { 69 | Container container = ((GuiContainer) mc.currentScreen).inventorySlots; 70 | IRecipeTransferError error = recipeLayout.transferRecipe(container, mc.player, false, false); 71 | if (error == null) { 72 | recipeLayout.transferRecipe(container, mc.player, isTransferRecipeMax, true); 73 | event.setCanceled(true); 74 | } 75 | pressedKeys.add(eventKey); 76 | } 77 | } 78 | } 79 | } 80 | 81 | private RecipePreviewWidget getRecipeLayout() { 82 | if (JeiUtilitiesPlugin.bookmarkOverlay instanceof AdvancedBookmarkOverlay) { 83 | AdvancedBookmarkOverlay bookmarkOverlay = (AdvancedBookmarkOverlay) JeiUtilitiesPlugin.bookmarkOverlay; 84 | Object ingredient = bookmarkOverlay.getIngredientUnderMouse(); 85 | if (ingredient instanceof RecipeInfo) { 86 | RecipeInfo recipeInfo = (RecipeInfo) ingredient; 87 | if (recipeInfo == bookmarkOverlay.getInfoUnderMouse()) { 88 | return bookmarkOverlay.getRecipeLayout(); 89 | } else { 90 | RecipePreviewWidget recipeLayout = RecipePreviewWidget.createLayout(recipeInfo, MouseHelper.getX(), MouseHelper.getY()); 91 | if (recipeLayout != null) { 92 | bookmarkOverlay.setRecipeLayout(recipeLayout); 93 | bookmarkOverlay.setInfoUnderMouse(recipeInfo); 94 | return recipeLayout; 95 | } 96 | } 97 | 98 | } 99 | } 100 | return null; 101 | } 102 | 103 | public static boolean isContainerTextFieldFocused() { 104 | GuiScreen gui = Minecraft.getMinecraft().currentScreen; 105 | if (gui == null) { 106 | return false; 107 | } 108 | GuiTextField textField = ReflectionUtil.getFieldWithClass(gui, GuiTextField.class); 109 | return textField != null && textField.getVisible() && textField.isFocused(); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/gui/history/AdvancedIngredientGrid.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.gui.history; 2 | 3 | import com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig; 4 | import com.github.vfyjxf.jeiutilities.config.SplittingMode; 5 | import com.github.vfyjxf.jeiutilities.helper.IngredientHelper; 6 | import com.github.vfyjxf.jeiutilities.jei.ingredient.RecipeInfo; 7 | import mezz.jei.Internal; 8 | import mezz.jei.api.ingredients.IIngredientHelper; 9 | import mezz.jei.config.Config; 10 | import mezz.jei.gui.TooltipRenderer; 11 | import mezz.jei.gui.ingredients.IIngredientListElement; 12 | import mezz.jei.gui.overlay.GridAlignment; 13 | import mezz.jei.gui.overlay.IngredientGrid; 14 | import mezz.jei.ingredients.IngredientListElement; 15 | import mezz.jei.input.ClickedIngredient; 16 | import mezz.jei.input.IClickedIngredient; 17 | import mezz.jei.render.IngredientListBatchRenderer; 18 | import mezz.jei.render.IngredientListSlot; 19 | import mezz.jei.render.IngredientRenderer; 20 | import mezz.jei.runtime.JeiRuntime; 21 | import mezz.jei.startup.ForgeModIdHelper; 22 | import mezz.jei.util.GiveMode; 23 | import mezz.jei.util.MathUtil; 24 | import mezz.jei.util.Translator; 25 | import net.minecraft.client.Minecraft; 26 | import net.minecraft.client.renderer.GlStateManager; 27 | import net.minecraft.entity.player.EntityPlayer; 28 | import net.minecraft.item.ItemStack; 29 | import net.minecraftforge.fml.client.config.GuiUtils; 30 | import net.minecraftforge.items.ItemHandlerHelper; 31 | import org.lwjgl.opengl.GL11; 32 | 33 | import javax.annotation.Nonnull; 34 | import javax.annotation.Nullable; 35 | import java.awt.*; 36 | import java.util.ArrayList; 37 | import java.util.Collection; 38 | import java.util.List; 39 | 40 | import static com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin.ORDER_TRACKER; 41 | import static com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin.ingredientRegistry; 42 | 43 | /** 44 | * @author vfyjxf 45 | */ 46 | public class AdvancedIngredientGrid extends IngredientGrid { 47 | 48 | public static final int USE_ROWS = 2; 49 | public static final int MIN_ROWS = 6; 50 | 51 | private int historySize; 52 | private int columns; 53 | private final IngredientListBatchRenderer guiHistoryIngredientSlots; 54 | @SuppressWarnings("rawtypes") 55 | private final List historyIngredientElements = new ArrayList<>(); 56 | 57 | private boolean showHistory; 58 | 59 | public AdvancedIngredientGrid() { 60 | super(GridAlignment.LEFT); 61 | this.guiHistoryIngredientSlots = new IngredientListBatchRenderer(); 62 | } 63 | 64 | @Override 65 | public boolean updateBounds(@Nonnull Rectangle availableArea, int minWidth, @Nonnull Collection exclusionAreas) { 66 | final int columns = Math.min(availableArea.width / INGREDIENT_WIDTH, Config.getMaxColumns()); 67 | this.columns = columns; 68 | int rows = availableArea.height / INGREDIENT_HEIGHT; 69 | 70 | final int ingredientsWidth = columns * INGREDIENT_WIDTH; 71 | final int width = Math.max(ingredientsWidth, minWidth); 72 | final int height = rows * INGREDIENT_HEIGHT; 73 | final int x = availableArea.x + (availableArea.width - width); 74 | final int y = availableArea.y + (availableArea.height - height) / 2; 75 | final int xOffset = x + Math.max(0, (width - ingredientsWidth) / 2); 76 | final int useRows = JeiUtilitiesConfig.getUseRows(); 77 | 78 | this.getArea().setBounds(x, y, width, height); 79 | this.guiIngredientSlots.clear(); 80 | this.guiHistoryIngredientSlots.clear(); 81 | this.historySize = columns * useRows; 82 | 83 | if (rows == 0 || columns < Config.smallestNumColumns) { 84 | return false; 85 | } 86 | 87 | if (rows >= MIN_ROWS) { 88 | rows = rows - useRows; 89 | showHistory = true; 90 | } else { 91 | showHistory = false; 92 | } 93 | 94 | for (int row = 0; row < rows; row++) { 95 | int y1 = y + (row * INGREDIENT_HEIGHT); 96 | for (int column = 0; column < columns; column++) { 97 | int x1 = xOffset + (column * INGREDIENT_WIDTH); 98 | IngredientListSlot ingredientListSlot = new IngredientListSlot(x1, y1, 1); 99 | Rectangle stackArea = ingredientListSlot.getArea(); 100 | final boolean blocked = MathUtil.intersects(exclusionAreas, stackArea); 101 | ingredientListSlot.setBlocked(blocked); 102 | this.guiIngredientSlots.add(ingredientListSlot); 103 | } 104 | } 105 | 106 | if (showHistory) { 107 | for (int row = 0; row < useRows; row++) { 108 | int y1 = y + ((row + rows) * INGREDIENT_HEIGHT); 109 | for (int column = 0; column < columns; column++) { 110 | int x1 = xOffset + (column * INGREDIENT_WIDTH); 111 | IngredientListSlot ingredientListSlot = new IngredientListSlot(x1, y1, 1); 112 | Rectangle stackArea = ingredientListSlot.getArea(); 113 | final boolean blocked = MathUtil.intersects(exclusionAreas, stackArea); 114 | ingredientListSlot.setBlocked(blocked); 115 | this.guiHistoryIngredientSlots.add(ingredientListSlot); 116 | } 117 | } 118 | guiHistoryIngredientSlots.set(0, this.historyIngredientElements); 119 | } 120 | 121 | return true; 122 | } 123 | 124 | @SuppressWarnings("rawtypes") 125 | @Override 126 | public void draw(@Nonnull Minecraft minecraft, int mouseX, int mouseY) { 127 | GlStateManager.disableBlend(); 128 | 129 | guiIngredientSlots.render(minecraft); 130 | 131 | if (showHistory) { 132 | Rectangle firstRect = guiHistoryIngredientSlots.getAllGuiIngredientSlots().get(0).getArea(); 133 | 134 | if (JeiUtilitiesConfig.getSplittingMode() == SplittingMode.DOTTED_LINE) { 135 | drawSpillingArea(firstRect.x, firstRect.y, 136 | firstRect.width * this.columns, 137 | firstRect.height * JeiUtilitiesConfig.getUseRows(), 138 | JeiUtilitiesConfig.getBackgroundColour() 139 | ); 140 | } else { 141 | GuiUtils.drawGradientRect( 142 | 0, firstRect.x, firstRect.y, 143 | firstRect.x + firstRect.width * this.columns, 144 | firstRect.y + firstRect.height * JeiUtilitiesConfig.getUseRows(), 145 | JeiUtilitiesConfig.getBackgroundColour(), 146 | JeiUtilitiesConfig.getBackgroundColour() 147 | ); 148 | } 149 | 150 | guiHistoryIngredientSlots.render(minecraft); 151 | 152 | } 153 | 154 | if (!shouldDeleteItemOnClick(minecraft, mouseX, mouseY) && isMouseOver(mouseX, mouseY)) { 155 | IngredientRenderer hovered = guiIngredientSlots.getHovered(mouseX, mouseY); 156 | if (hovered != null) { 157 | hovered.drawHighlight(); 158 | } 159 | if (showHistory) { 160 | IngredientRenderer hoveredHistory = guiHistoryIngredientSlots.getHovered(mouseX, mouseY); 161 | if (hoveredHistory != null) { 162 | hoveredHistory.drawHighlight(); 163 | } 164 | } 165 | } 166 | 167 | GlStateManager.enableAlpha(); 168 | } 169 | 170 | @SuppressWarnings("rawtypes") 171 | @Override 172 | public void drawTooltips(@Nonnull Minecraft minecraft, int mouseX, int mouseY) { 173 | if (isMouseOver(mouseX, mouseY)) { 174 | if (shouldDeleteItemOnClick(minecraft, mouseX, mouseY)) { 175 | String deleteItem = Translator.translateToLocal("jei.tooltip.delete.item"); 176 | TooltipRenderer.drawHoveringText(minecraft, deleteItem, mouseX, mouseY); 177 | } else { 178 | IngredientRenderer hovered = guiIngredientSlots.getHovered(mouseX, mouseY); 179 | if (hovered != null) { 180 | hovered.drawTooltip(minecraft, mouseX, mouseY); 181 | } 182 | if (showHistory) { 183 | IngredientRenderer hoveredHistory = getGuiHistoryIngredientSlots().getHovered(mouseX, mouseY); 184 | if (hoveredHistory != null) { 185 | hoveredHistory.drawTooltip(minecraft, mouseX, mouseY); 186 | } 187 | } 188 | } 189 | } 190 | } 191 | 192 | @Nullable 193 | @Override 194 | public IClickedIngredient getIngredientUnderMouse(int mouseX, int mouseY) { 195 | IClickedIngredient clicked = super.getIngredientUnderMouse(mouseX, mouseY); 196 | if (clicked != null) { 197 | return clicked; 198 | } 199 | if (showHistory) { 200 | ClickedIngredient clickedHistory = this.guiHistoryIngredientSlots.getIngredientUnderMouse(mouseX, mouseY); 201 | if (clickedHistory != null) { 202 | clickedHistory.setAllowsCheating(); 203 | } 204 | return clickedHistory; 205 | } 206 | return null; 207 | } 208 | 209 | private boolean shouldDeleteItemOnClick(Minecraft minecraft, int mouseX, int mouseY) { 210 | if (Config.isDeleteItemsInCheatModeActive()) { 211 | EntityPlayer player = minecraft.player; 212 | if (player != null) { 213 | ItemStack itemStack = player.inventory.getItemStack(); 214 | if (!itemStack.isEmpty()) { 215 | JeiRuntime runtime = Internal.getRuntime(); 216 | if (runtime == null || !runtime.getRecipesGui().isOpen()) { 217 | GiveMode giveMode = Config.getGiveMode(); 218 | if (giveMode == GiveMode.MOUSE_PICKUP) { 219 | IClickedIngredient ingredientUnderMouse = getIngredientUnderMouse(mouseX, mouseY); 220 | if (ingredientUnderMouse != null && ingredientUnderMouse.getValue() instanceof ItemStack) { 221 | ItemStack value = (ItemStack) ingredientUnderMouse.getValue(); 222 | return !ItemHandlerHelper.canItemStacksStack(itemStack, value); 223 | } 224 | } 225 | return true; 226 | } 227 | } 228 | } 229 | } 230 | return false; 231 | } 232 | 233 | public IngredientListBatchRenderer getGuiHistoryIngredientSlots() { 234 | return guiHistoryIngredientSlots; 235 | } 236 | 237 | public void addHistoryIngredient(Object value) { 238 | if (value != null) { 239 | 240 | if (value instanceof RecipeInfo) { 241 | return; 242 | } 243 | 244 | Object normalized = IngredientHelper.getNormalize(value); 245 | IIngredientListElement ingredient = IngredientListElement.create( 246 | normalized, 247 | ingredientRegistry.getIngredientHelper(normalized), 248 | ingredientRegistry.getIngredientRenderer(normalized), 249 | ForgeModIdHelper.getInstance(), 250 | ORDER_TRACKER.getOrderIndex(normalized, ingredientRegistry.getIngredientHelper(normalized)) 251 | ); 252 | historyIngredientElements.removeIf(element -> areIngredientEqual(element.getIngredient(), normalized, JeiUtilitiesConfig.isMatchesNBTs())); 253 | historyIngredientElements.add(0, ingredient); 254 | if (historyIngredientElements.size() > this.historySize) { 255 | historyIngredientElements.remove(historyIngredientElements.size() - 1); 256 | } 257 | while (historyIngredientElements.size() > USE_ROWS * Config.largestNumColumns) { 258 | historyIngredientElements.remove(historyIngredientElements.size() - 1); 259 | } 260 | guiHistoryIngredientSlots.set(0, historyIngredientElements); 261 | } 262 | 263 | } 264 | 265 | public void removeElement(int index) { 266 | historyIngredientElements.remove(index); 267 | guiHistoryIngredientSlots.set(0, historyIngredientElements); 268 | } 269 | 270 | private boolean areIngredientEqual(@Nonnull Object ingredient1, @Nonnull Object ingredient2, boolean matchesNbt) { 271 | 272 | if (ingredient1 == ingredient2) { 273 | return true; 274 | } 275 | 276 | if (ingredient1.getClass() == ingredient2.getClass()) { 277 | IIngredientHelper ingredientHelper = ingredientRegistry.getIngredientHelper(ingredient1); 278 | if (matchesNbt) { 279 | return ingredientHelper.getUniqueId(ingredient1).equals(ingredientHelper.getUniqueId(ingredient2)); 280 | } 281 | return ingredientHelper.getWildcardId(ingredient1).equals(ingredientHelper.getWildcardId(ingredient2)); 282 | } 283 | 284 | return false; 285 | } 286 | 287 | private void drawSpillingArea(int x, int y, int width, int height, int color) { 288 | 289 | float alpha = (float) (color >> 24 & 255) / 255.0F; 290 | float red = (float) (color >> 16 & 255) / 255.0F; 291 | float green = (float) (color >> 8 & 255) / 255.0F; 292 | float blue = (float) (color & 255) / 255.0F; 293 | 294 | GlStateManager.pushMatrix(); 295 | 296 | GlStateManager.disableTexture2D(); 297 | GL11.glEnable(GL11.GL_LINE_STIPPLE); 298 | GlStateManager.color(red, green, blue, alpha); 299 | GL11.glLineWidth(2F); 300 | GL11.glLineStipple(2, (short) 0x00FF); 301 | 302 | GL11.glBegin(GL11.GL_LINE_LOOP); 303 | 304 | GL11.glVertex2i(x, y); 305 | GL11.glVertex2i(x + width, y); 306 | GL11.glVertex2i(x + width, y + height); 307 | GL11.glVertex2i(x, y + height); 308 | 309 | GL11.glEnd(); 310 | 311 | GL11.glLineStipple(2, (short) 0xFFFF); 312 | GL11.glLineWidth(2F); 313 | GL11.glDisable(GL11.GL_LINE_STIPPLE); 314 | GlStateManager.enableTexture2D(); 315 | GlStateManager.color(1F, 1F, 1F, 1F); 316 | 317 | GlStateManager.popMatrix(); 318 | 319 | } 320 | 321 | //TODO:implements it 322 | 323 | /** 324 | * An ingredient such as energy ingredient in ender:io and Multiblocked. 325 | * It should not be added to the browsing history because it is meaningless in itself 326 | * 327 | * @return Whether the current ingredient is an ignored ingredient 328 | */ 329 | private boolean isIgnoredIngredients() { 330 | return false; 331 | } 332 | 333 | 334 | } 335 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/gui/recipe/DraggableRecipeWidget.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.gui.recipe; 2 | 3 | import mezz.jei.api.gui.IGuiFluidStackGroup; 4 | import mezz.jei.api.gui.IGuiIngredientGroup; 5 | import mezz.jei.api.gui.IGuiItemStackGroup; 6 | import mezz.jei.api.gui.IRecipeLayoutDrawable; 7 | import mezz.jei.api.recipe.IFocus; 8 | import mezz.jei.api.recipe.IIngredientType; 9 | import mezz.jei.api.recipe.IRecipeCategory; 10 | import net.minecraft.client.Minecraft; 11 | 12 | import javax.annotation.Nonnull; 13 | import javax.annotation.Nullable; 14 | 15 | public class DraggableRecipeWidget implements IRecipeLayoutDrawable { 16 | @Override 17 | public void setPosition(int posX, int posY) { 18 | 19 | } 20 | 21 | @Override 22 | public void drawRecipe(@Nonnull Minecraft minecraft, int mouseX, int mouseY) { 23 | 24 | } 25 | 26 | @Override 27 | public void drawOverlays(@Nonnull Minecraft minecraft, int mouseX, int mouseY) { 28 | 29 | } 30 | 31 | @Override 32 | public boolean isMouseOver(int mouseX, int mouseY) { 33 | return false; 34 | } 35 | 36 | @Nullable 37 | @Override 38 | public Object getIngredientUnderMouse(int mouseX, int mouseY) { 39 | return null; 40 | } 41 | 42 | @Override 43 | public void draw(Minecraft minecraft, int mouseX, int mouseY) { 44 | 45 | } 46 | 47 | @Override 48 | public IGuiItemStackGroup getItemStacks() { 49 | return null; 50 | } 51 | 52 | @Override 53 | public IGuiFluidStackGroup getFluidStacks() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public IGuiIngredientGroup getIngredientsGroup(IIngredientType ingredientType) { 59 | return null; 60 | } 61 | 62 | @Nullable 63 | @Override 64 | public IFocus getFocus() { 65 | return null; 66 | } 67 | 68 | @Override 69 | public IRecipeCategory getRecipeCategory() { 70 | return null; 71 | } 72 | 73 | @Override 74 | public void setRecipeTransferButton(int posX, int posY) { 75 | 76 | } 77 | 78 | @Override 79 | public void setShapeless() { 80 | 81 | } 82 | 83 | @Override 84 | public IGuiIngredientGroup getIngredientsGroup(Class ingredientClass) { 85 | return null; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/gui/recipe/RecipePreviewWidget.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.gui.recipe; 2 | 3 | import com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig; 4 | import com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin; 5 | import com.github.vfyjxf.jeiutilities.jei.ingredient.RecipeInfo; 6 | import mezz.jei.Internal; 7 | import mezz.jei.api.gui.*; 8 | import mezz.jei.api.ingredients.IIngredientRegistry; 9 | import mezz.jei.api.ingredients.IIngredients; 10 | import mezz.jei.api.ingredients.VanillaTypes; 11 | import mezz.jei.api.recipe.IFocus; 12 | import mezz.jei.api.recipe.IIngredientType; 13 | import mezz.jei.api.recipe.IRecipeCategory; 14 | import mezz.jei.api.recipe.IRecipeWrapper; 15 | import mezz.jei.api.recipe.transfer.IRecipeTransferError; 16 | import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; 17 | import mezz.jei.gui.Focus; 18 | import mezz.jei.gui.elements.DrawableNineSliceTexture; 19 | import mezz.jei.gui.ingredients.GuiFluidStackGroup; 20 | import mezz.jei.gui.ingredients.GuiIngredient; 21 | import mezz.jei.gui.ingredients.GuiIngredientGroup; 22 | import mezz.jei.gui.ingredients.GuiItemStackGroup; 23 | import mezz.jei.gui.recipes.RecipeLayout; 24 | import mezz.jei.gui.recipes.ShapelessIcon; 25 | import mezz.jei.ingredients.Ingredients; 26 | import mezz.jei.recipes.RecipeRegistry; 27 | import mezz.jei.runtime.JeiRuntime; 28 | import mezz.jei.transfer.RecipeTransferErrorInternal; 29 | import mezz.jei.util.ErrorUtil; 30 | import mezz.jei.util.Log; 31 | import net.minecraft.client.Minecraft; 32 | import net.minecraft.client.gui.GuiScreen; 33 | import net.minecraft.client.renderer.GlStateManager; 34 | import net.minecraft.entity.player.EntityPlayer; 35 | import net.minecraft.inventory.Container; 36 | import net.minecraft.item.ItemStack; 37 | import net.minecraftforge.fluids.FluidStack; 38 | 39 | import javax.annotation.Nonnull; 40 | import javax.annotation.Nullable; 41 | import java.awt.*; 42 | import java.util.IdentityHashMap; 43 | import java.util.Map; 44 | 45 | import static com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig.isAdaptiveRecipePreview; 46 | 47 | /** 48 | * A lite version of {@link RecipeLayout}, used to display recipes in the bookmark area. 49 | * Base on the {@link RecipeLayout}, some changes were made to display the recipe more concisely. 50 | */ 51 | public class RecipePreviewWidget implements IRecipeLayoutDrawable { 52 | 53 | private static final int RECIPE_BORDER_PADDING = 4; 54 | 55 | private final int ingredientCycleOffset = (int) ((Math.random() * 10000) % Integer.MAX_VALUE); 56 | private final IRecipeCategory recipeCategory; 57 | private final Map guiIngredientGroups; 58 | private final IRecipeWrapper recipeWrapper; 59 | @Nullable 60 | private final IFocus focus; 61 | private final Color highlightColor = new Color(0x7FFFFFFF, true); 62 | @Nullable 63 | private ShapelessIcon shapelessIcon; 64 | 65 | private IRecipeTransferError transferError; 66 | 67 | private final DrawableNineSliceTexture recipeBorder; 68 | private int posX; 69 | private int posY; 70 | 71 | @SuppressWarnings("rawtypes") 72 | @Nullable 73 | public static RecipePreviewWidget create( 74 | IRecipeCategory recipeCategory, 75 | T recipeWrapper, 76 | @Nullable IFocus focus, 77 | int posX, 78 | int posY 79 | ) { 80 | RecipePreviewWidget recipeLayout = new RecipePreviewWidget(recipeCategory, recipeWrapper, focus, posX, posY); 81 | try { 82 | IIngredients ingredients = new Ingredients(); 83 | recipeWrapper.getIngredients(ingredients); 84 | recipeCategory.setRecipe(recipeLayout, recipeWrapper, ingredients); 85 | return recipeLayout; 86 | } catch (RuntimeException | LinkageError e) { 87 | Log.get().error("Error caught from Recipe Category: {}", recipeCategory.getClass().getCanonicalName(), e); 88 | } 89 | return null; 90 | } 91 | 92 | @SuppressWarnings("unchecked") 93 | public static RecipePreviewWidget createLayout(RecipeInfo recipeInfo, int posX, int posY) { 94 | return create(JeiUtilitiesPlugin.recipeRegistry.getRecipeCategory(recipeInfo.getRecipeCategoryUid()), 95 | recipeInfo.getRecipeWrapper(), 96 | new Focus<>(recipeInfo.getMode(), 97 | recipeInfo.getIngredient()), 98 | posX, posY); 99 | } 100 | 101 | private RecipePreviewWidget(IRecipeCategory recipeCategory, T recipeWrapper, @Nullable IFocus focus, int posX, int posY) { 102 | ErrorUtil.checkNotNull(recipeCategory, "recipeCategory"); 103 | ErrorUtil.checkNotNull(recipeWrapper, "recipeWrapper"); 104 | if (focus != null) { 105 | focus = Focus.check(focus); 106 | } 107 | this.recipeCategory = recipeCategory; 108 | this.focus = focus; 109 | 110 | IFocus itemStackFocus = null; 111 | IFocus fluidStackFocus = null; 112 | if (focus != null) { 113 | Object focusValue = focus.getValue(); 114 | if (focusValue instanceof ItemStack) { 115 | //noinspection unchecked 116 | itemStackFocus = (IFocus) focus; 117 | } else if (focusValue instanceof FluidStack) { 118 | //noinspection unchecked 119 | fluidStackFocus = (IFocus) focus; 120 | } 121 | } 122 | 123 | this.guiIngredientGroups = new IdentityHashMap<>(); 124 | 125 | GuiItemStackGroup itemStackGroup = new GuiItemStackGroup(itemStackFocus, ingredientCycleOffset) { 126 | @Override 127 | public void draw(@Nonnull Minecraft minecraft, int xOffset, int yOffset, @Nonnull Color highlightColor, int mouseX, int mouseY) { 128 | for (GuiIngredient ingredient : super.getGuiIngredients().values()) { 129 | ingredient.draw(minecraft, xOffset, yOffset); 130 | } 131 | } 132 | }; 133 | 134 | GuiFluidStackGroup fluidStackGroup = new GuiFluidStackGroup(fluidStackFocus, ingredientCycleOffset) { 135 | @Override 136 | public void draw(@Nonnull Minecraft minecraft, int xOffset, int yOffset, @Nonnull Color highlightColor, int mouseX, int mouseY) { 137 | for (GuiIngredient ingredient : super.getGuiIngredients().values()) { 138 | ingredient.draw(minecraft, xOffset, yOffset); 139 | } 140 | } 141 | }; 142 | 143 | this.guiIngredientGroups.put(VanillaTypes.ITEM, itemStackGroup); 144 | this.guiIngredientGroups.put(VanillaTypes.FLUID, fluidStackGroup); 145 | 146 | setPosition(posX, posY); 147 | 148 | this.recipeWrapper = recipeWrapper; 149 | this.recipeBorder = Internal.getHelpers().getGuiHelper().getRecipeBackground(); 150 | } 151 | 152 | 153 | @Override 154 | public void setPosition(int posX, int posY) { 155 | this.posX = posX; 156 | this.posY = posY; 157 | } 158 | 159 | @Override 160 | public void drawRecipe(@Nonnull Minecraft minecraft, int mouseX, int mouseY) { 161 | IDrawable background = recipeCategory.getBackground(); 162 | GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); 163 | GlStateManager.disableDepth(); 164 | GlStateManager.disableLighting(); 165 | GlStateManager.enableAlpha(); 166 | final int recipeMouseX = mouseX - posX; 167 | final int recipeMouseY = mouseY - posY; 168 | int width = background.getWidth() + (2 * RECIPE_BORDER_PADDING); 169 | int height = background.getHeight() + (2 * RECIPE_BORDER_PADDING); 170 | checkBounds(minecraft.currentScreen, height); 171 | float scaling = isAdaptiveRecipePreview() ? getScaling(minecraft.currentScreen) : JeiUtilitiesConfig.getRecipePreviewScaling(); 172 | if (scaling > 1.5F) { 173 | scaling = 1.5F;//prevent scaling too much. 174 | } 175 | GlStateManager.pushMatrix(); 176 | GlStateManager.translate(posX * scaling, posY * scaling, 0.0F); 177 | GlStateManager.scale(scaling, scaling, 1.0F); 178 | { 179 | recipeBorder.draw(minecraft, -RECIPE_BORDER_PADDING, -RECIPE_BORDER_PADDING, width, height); 180 | background.draw(minecraft); 181 | recipeCategory.drawExtras(minecraft); 182 | recipeWrapper.drawInfo(minecraft, background.getWidth(), background.getHeight(), recipeMouseX, recipeMouseY); 183 | // drawExtras and drawInfo often render text which messes with the color, this clears it 184 | GlStateManager.color(1, 1, 1, 1); 185 | if (shapelessIcon != null) { 186 | shapelessIcon.draw(minecraft, background.getWidth()); 187 | } 188 | } 189 | GlStateManager.popMatrix(); 190 | GlStateManager.enableDepth(); 191 | 192 | GlStateManager.pushMatrix(); 193 | GlStateManager.translate(0.0F, 0.0F, 200.0F); 194 | GlStateManager.scale(scaling, scaling, 1.0F); 195 | for (GuiIngredientGroup guiIngredientGroup : guiIngredientGroups.values()) { 196 | guiIngredientGroup.draw(minecraft, posX, posY, highlightColor, mouseX, mouseY); 197 | } 198 | GlStateManager.popMatrix(); 199 | 200 | GlStateManager.disableBlend(); 201 | GlStateManager.disableLighting(); 202 | GlStateManager.disableAlpha(); 203 | } 204 | 205 | private float getScaling(GuiScreen guiScreen) { 206 | if (guiScreen != null) { 207 | IDrawable categoryBackground = recipeCategory.getBackground(); 208 | int backgroundWidth = categoryBackground.getWidth() + (2 * RECIPE_BORDER_PADDING); 209 | int backgroundHeight = categoryBackground.getHeight() + (2 * RECIPE_BORDER_PADDING); 210 | int maxWidth = guiScreen.width / 2; 211 | int maxHeight = guiScreen.height / 2; 212 | //when backgroundWidth > maxWidth and backgroundHeight > maxHeight,Scaling by width. 213 | if (backgroundWidth > maxWidth && backgroundHeight > maxHeight) { 214 | return (float) maxWidth / backgroundWidth + 0.1F; 215 | } 216 | if (backgroundWidth < maxWidth * 0.5 && backgroundHeight < maxHeight * 0.5) { 217 | return (float) maxWidth / backgroundWidth - 0.2F; 218 | } 219 | if (backgroundWidth < maxWidth && backgroundHeight > maxHeight) { 220 | return (float) maxHeight / backgroundHeight + 0.2F; 221 | } 222 | if (backgroundWidth >= maxWidth && backgroundHeight < maxHeight) { 223 | return 1.0F; 224 | } 225 | return 0.95F; 226 | } 227 | return JeiUtilitiesConfig.getRecipePreviewScaling(); 228 | } 229 | 230 | private void checkBounds(GuiScreen guiScreen, int height) { 231 | if (guiScreen != null) { 232 | if (posX < 6) { 233 | posX += 6; 234 | } 235 | if (posY < 6) { 236 | posY += 6; 237 | } 238 | if (posY + height > guiScreen.height) { 239 | posY -= posY + height - guiScreen.height; 240 | } 241 | } 242 | } 243 | 244 | @Override 245 | public void drawOverlays(@Nonnull Minecraft minecraft, int mouseX, int mouseY) { 246 | 247 | } 248 | 249 | @Override 250 | public boolean isMouseOver(int mouseX, int mouseY) { 251 | final IDrawable background = recipeCategory.getBackground(); 252 | final Rectangle backgroundRect = new Rectangle(posX, posY, background.getWidth(), background.getHeight()); 253 | return backgroundRect.contains(mouseX, mouseY); 254 | } 255 | 256 | @Nullable 257 | @Override 258 | public Object getIngredientUnderMouse(int mouseX, int mouseY) { 259 | return null; 260 | } 261 | 262 | @Override 263 | public void draw(@Nonnull Minecraft minecraft, int mouseX, int mouseY) { 264 | drawRecipe(minecraft, mouseX, mouseY); 265 | drawOverlays(minecraft, mouseX, mouseY); 266 | } 267 | 268 | public IRecipeTransferError getTransferError() { 269 | return transferError; 270 | } 271 | 272 | @SuppressWarnings("rawtypes") 273 | public IRecipeTransferError transferRecipe(Container container, EntityPlayer player, boolean maxTransfer, boolean doTransfer) { 274 | final JeiRuntime runtime = Internal.getRuntime(); 275 | if (runtime == null) { 276 | return RecipeTransferErrorInternal.INSTANCE; 277 | } 278 | final RecipeRegistry recipeRegistry = runtime.getRecipeRegistry(); 279 | 280 | final IRecipeTransferHandler transferHandler = recipeRegistry.getRecipeTransferHandler(container, this.recipeCategory); 281 | if (transferHandler == null) { 282 | if (doTransfer) { 283 | Log.get().error("No Recipe Transfer handler for container {}", container.getClass()); 284 | } 285 | return RecipeTransferErrorInternal.INSTANCE; 286 | } 287 | 288 | //noinspection unchecked 289 | transferError = transferHandler.transferRecipe(container, this, player, maxTransfer, doTransfer); 290 | return transferError; 291 | } 292 | 293 | public void showError(Minecraft minecraft, int mouseX, int mouseY) { 294 | if (transferError != null) { 295 | final float scaling = isAdaptiveRecipePreview() ? getScaling(minecraft.currentScreen) : JeiUtilitiesConfig.getRecipePreviewScaling(); 296 | GlStateManager.pushMatrix(); 297 | GlStateManager.scale(scaling, scaling, 1.0F); 298 | transferError.showError(minecraft, mouseX, mouseY, this, posX, posY); 299 | GlStateManager.popMatrix(); 300 | } 301 | } 302 | 303 | 304 | @Nonnull 305 | @Override 306 | public IGuiItemStackGroup getItemStacks() { 307 | return (IGuiItemStackGroup) this.guiIngredientGroups.get(VanillaTypes.ITEM); 308 | } 309 | 310 | @Nonnull 311 | @Override 312 | public IGuiFluidStackGroup getFluidStacks() { 313 | return (IGuiFluidStackGroup) this.guiIngredientGroups.get(VanillaTypes.FLUID); 314 | } 315 | 316 | @Nonnull 317 | @Override 318 | public IGuiIngredientGroup getIngredientsGroup(@Nonnull IIngredientType ingredientType) { 319 | @SuppressWarnings("unchecked") 320 | GuiIngredientGroup guiIngredientGroup = guiIngredientGroups.get(ingredientType); 321 | if (guiIngredientGroup == null) { 322 | IFocus focus = null; 323 | if (this.focus != null) { 324 | Object focusValue = this.focus.getValue(); 325 | if (ingredientType.getIngredientClass().isInstance(focusValue)) { 326 | //noinspection unchecked 327 | focus = (IFocus) this.focus; 328 | } 329 | } 330 | guiIngredientGroup = new GuiIngredientGroup(ingredientType, focus, ingredientCycleOffset) { 331 | @Override 332 | public void draw(@Nonnull Minecraft minecraft, int xOffset, int yOffset, @Nonnull Color highlightColor, int mouseX, int mouseY) { 333 | for (GuiIngredient ingredient : super.getGuiIngredients().values()) { 334 | ingredient.draw(minecraft, xOffset, yOffset); 335 | } 336 | } 337 | }; 338 | guiIngredientGroups.put(ingredientType, guiIngredientGroup); 339 | } 340 | return guiIngredientGroup; 341 | } 342 | 343 | @Nonnull 344 | @Override 345 | public IGuiIngredientGroup getIngredientsGroup(@Nonnull Class ingredientClass) { 346 | IIngredientRegistry ingredientRegistry = Internal.getIngredientRegistry(); 347 | IIngredientType ingredientType = ingredientRegistry.getIngredientType(ingredientClass); 348 | return getIngredientsGroup(ingredientType); 349 | } 350 | 351 | @Nullable 352 | @Override 353 | public IFocus getFocus() { 354 | return focus; 355 | } 356 | 357 | @Nonnull 358 | @Override 359 | public IRecipeCategory getRecipeCategory() { 360 | return recipeCategory; 361 | } 362 | 363 | public int getPosX() { 364 | return posX; 365 | } 366 | 367 | public int getPosY() { 368 | return posY; 369 | } 370 | 371 | @Override 372 | public void setRecipeTransferButton(int posX, int posY) { 373 | //:P 374 | } 375 | 376 | @Override 377 | public void setShapeless() { 378 | this.shapelessIcon = new ShapelessIcon(); 379 | } 380 | 381 | } 382 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/helper/IngredientHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.helper; 2 | 3 | import com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin; 4 | import com.github.vfyjxf.jeiutilities.jei.ingredient.RecipeInfo; 5 | import mezz.jei.api.ingredients.IIngredientHelper; 6 | import mezz.jei.api.ingredients.VanillaTypes; 7 | import mezz.jei.api.recipe.IIngredientType; 8 | import mezz.jei.util.LegacyUtil; 9 | import net.minecraft.item.ItemStack; 10 | import net.minecraft.nbt.JsonToNBT; 11 | import net.minecraft.nbt.NBTException; 12 | import net.minecraft.nbt.NBTTagCompound; 13 | import net.minecraftforge.fluids.FluidStack; 14 | 15 | import javax.annotation.Nonnull; 16 | import java.util.List; 17 | 18 | import static com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin.ingredientRegistry; 19 | 20 | public class IngredientHelper { 21 | public static String getUniqueId(V ingredient) { 22 | 23 | if (ingredient == null) return RecipeInfo.NONE_MARK; 24 | 25 | if (ingredient instanceof ItemStack) { 26 | return ((ItemStack) ingredient).writeToNBT(new NBTTagCompound()).toString(); 27 | } 28 | 29 | IIngredientHelper ingredientHelper = JeiUtilitiesPlugin.ingredientRegistry.getIngredientHelper(ingredient); 30 | return ingredientHelper.getUniqueId(ingredient); 31 | } 32 | 33 | public static IIngredientType getIngredientType(List ingredientUidList) { 34 | 35 | for (String ingredientUid : ingredientUidList) { 36 | //First check if the type corresponding to the uid is ItemStack. 37 | if (!getItemStackFromUid(ingredientUid).isEmpty()) { 38 | return VanillaTypes.ITEM; 39 | } 40 | 41 | for (IIngredientType ingredientType : ingredientRegistry.getRegisteredIngredientTypes()) { 42 | if (ingredientRegistry.getIngredientByUid(ingredientType, ingredientUid) != null) { 43 | return ingredientType; 44 | } 45 | } 46 | } 47 | 48 | return null; 49 | } 50 | 51 | public static T getNormalize(@Nonnull T ingredient) { 52 | IIngredientHelper ingredientHelper = ingredientRegistry.getIngredientHelper(ingredient); 53 | T copy = LegacyUtil.getIngredientCopy(ingredient, ingredientHelper); 54 | if (copy instanceof ItemStack) { 55 | ((ItemStack) copy).setCount(1); 56 | } else if (copy instanceof FluidStack) { 57 | ((FluidStack) copy).amount = 1000; 58 | } 59 | return copy; 60 | } 61 | 62 | public static boolean ingredientEquals(Object ingredient1, Object ingredient2) { 63 | 64 | if (ingredient1 == ingredient2){ 65 | return true; 66 | } 67 | 68 | if (ingredient1 == null || ingredient2 == null){ 69 | return false; 70 | } 71 | 72 | if (ingredient1.getClass() == ingredient2.getClass()){ 73 | IIngredientHelper ingredientHelper = ingredientRegistry.getIngredientHelper(ingredient1); 74 | return ingredientHelper.getUniqueId(ingredient1).equals(ingredientHelper.getUniqueId(ingredient2)); 75 | } 76 | 77 | return false; 78 | } 79 | 80 | private static ItemStack getItemStackFromUid(String uid) { 81 | try { 82 | NBTTagCompound itemStackAsNbt = JsonToNBT.getTagFromJson(uid); 83 | return new ItemStack(itemStackAsNbt); 84 | } catch (NBTException ignored) { 85 | //:p 86 | } 87 | return ItemStack.EMPTY; 88 | } 89 | 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/helper/RecipeHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.helper; 2 | 3 | import com.github.vfyjxf.jeiutilities.jei.ingredient.RecipeInfo; 4 | import mezz.jei.api.ingredients.IIngredientHelper; 5 | import mezz.jei.api.recipe.*; 6 | import mezz.jei.api.recipe.wrapper.ICraftingRecipeWrapper; 7 | import mezz.jei.gui.Focus; 8 | import mezz.jei.ingredients.Ingredients; 9 | import net.minecraft.util.ResourceLocation; 10 | import org.apache.commons.lang3.tuple.Pair; 11 | 12 | import javax.annotation.Nonnull; 13 | import java.util.*; 14 | import java.util.stream.Collectors; 15 | 16 | import static com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin.ingredientRegistry; 17 | import static com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin.recipeRegistry; 18 | 19 | public class RecipeHelper { 20 | 21 | @SuppressWarnings({"rawtypes", "unchecked"}) 22 | public static Pair getRecipeWrapperAndIndex( 23 | @Nonnull Map> recipeUidMap, 24 | @Nonnull String recipeCategoryUid, 25 | @Nonnull T recipeOutput, 26 | @Nonnull IFocus focus 27 | ) { 28 | IRecipeCategory recipeCategory = recipeRegistry.getRecipeCategory(recipeCategoryUid); 29 | if (recipeCategory != null) { 30 | IIngredientHelper ingredientHelper = ingredientRegistry.getIngredientHelper(focus.getValue()); 31 | IFocus translatedFocus = ingredientHelper.translateFocus(focus, Focus::new); 32 | List recipes = recipeRegistry.getRecipeWrappers(recipeCategory, translatedFocus); 33 | IIngredientType outputType = ingredientRegistry.getIngredientType(recipeOutput); 34 | String outputUid = IngredientHelper.getUniqueId(recipeOutput); 35 | List outputMatchRecipes = recipes.parallelStream() 36 | .filter(recipe -> { 37 | Ingredients outputIngredients = new Ingredients(); 38 | recipe.getIngredients(outputIngredients); 39 | List> outputSlots = outputIngredients.getOutputs(outputType); 40 | return outputSlots.parallelStream() 41 | .flatMap(Collection::stream) 42 | .map(t -> IngredientHelper.getUniqueId(IngredientHelper.getNormalize(t))) 43 | .anyMatch(outputIngredientUid -> outputIngredientUid.equals(outputUid)); 44 | } 45 | ).collect(Collectors.toList()); 46 | return outputMatchRecipes.parallelStream() 47 | .filter(recipe -> isRecipeMatch(recipe, recipeUidMap)) 48 | .findAny() 49 | .map(recipe -> Pair.of(recipe, recipes.indexOf(recipe))) 50 | .orElse(null); 51 | } 52 | return null; 53 | } 54 | 55 | @SuppressWarnings({"unchecked"}) 56 | public static Pair getCraftingRecipeWrapperAndIndex( 57 | @Nonnull ResourceLocation registerName, 58 | @Nonnull String recipeCategoryUid, 59 | @Nonnull T recipeOutput, 60 | @Nonnull IFocus focus 61 | ) { 62 | IRecipeCategory recipeCategory = recipeRegistry.getRecipeCategory(recipeCategoryUid); 63 | if (recipeCategory != null && recipeCategory.getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) { 64 | IIngredientHelper ingredientHelper = ingredientRegistry.getIngredientHelper(focus.getValue()); 65 | IFocus translatedFocus = ingredientHelper.translateFocus(focus, Focus::new); 66 | //In fact, we know that the returned RecipeWrapper instanceof ICraftingRecipeWrapper. 67 | //OK,it seems to be that not all RecipeWrapper is ICraftingRecipeWrapper. 68 | //But we only care ICraftingRecipeWrapper. 69 | List craftingRecipes = recipeRegistry.getRecipeWrappers(recipeCategory, translatedFocus) 70 | .stream() 71 | .filter(ICraftingRecipeWrapper.class::isInstance) 72 | .map(ICraftingRecipeWrapper.class::cast) 73 | .collect(Collectors.toList()); 74 | 75 | List registerNameMatchRecipes = craftingRecipes.parallelStream() 76 | .filter(recipe -> registerName.equals(recipe.getRegistryName())) 77 | .collect(Collectors.toList()); 78 | 79 | return registerNameMatchRecipes.parallelStream() 80 | .filter(recipe -> { 81 | Ingredients outputIngredients = new Ingredients(); 82 | recipe.getIngredients(outputIngredients); 83 | List> outputSlots = outputIngredients.getOutputs(ingredientRegistry.getIngredientType(recipeOutput)); 84 | return outputSlots.parallelStream() 85 | .flatMap(Collection::stream) 86 | .map(t -> IngredientHelper.getUniqueId(IngredientHelper.getNormalize(t))) 87 | .anyMatch(outputIngredientUid -> outputIngredientUid.equals(IngredientHelper.getUniqueId(recipeOutput))); 88 | } 89 | ) 90 | .findAny() 91 | .map(recipe -> Pair.of(recipe, craftingRecipes.indexOf(recipe))) 92 | .orElse(null); 93 | 94 | } 95 | 96 | return null; 97 | } 98 | 99 | @SuppressWarnings({"rawtypes", "unchecked"}) 100 | private static boolean isRecipeMatch(IRecipeWrapper recipeWrapper, Map> recipeUidMap) { 101 | Ingredients recipeIngredients = new Ingredients(); 102 | recipeWrapper.getIngredients(recipeIngredients); 103 | Set ingredientTypes = ((Map>) ReflectionUtils.getFieldValue( 104 | Ingredients.class, 105 | recipeIngredients, 106 | "inputs" 107 | )).keySet(); 108 | 109 | Set inputTypes = recipeUidMap.keySet(); 110 | 111 | if (ingredientTypes.size() != inputTypes.size()) { 112 | return false; 113 | } 114 | 115 | if (!ingredientTypes.containsAll(inputTypes)) { 116 | return false; 117 | } 118 | 119 | for (IIngredientType ingredientType : ingredientTypes) { 120 | List inputUid = recipeUidMap.get(ingredientType); 121 | List ingredientUid = getIngredientsUid(recipeIngredients, ingredientType); 122 | if (inputUid.size() != ingredientUid.size()) { 123 | return false; 124 | } 125 | 126 | for (int i = 0; i < inputUid.size(); i++) { 127 | if (!inputUid.get(i).equals(ingredientUid.get(i))) { 128 | return false; 129 | } 130 | } 131 | 132 | } 133 | 134 | return true; 135 | } 136 | 137 | private static List getIngredientsUid(Ingredients ingredients, IIngredientType ingredientType) { 138 | List ingredientsUid = new ArrayList<>(); 139 | List> ingredientList = ingredients.getInputs(ingredientType); 140 | for (List ingredientPerSlot : ingredientList) { 141 | V first = ingredientPerSlot.isEmpty() ? null : 142 | ingredientPerSlot.stream() 143 | .filter(Objects::nonNull) 144 | .findFirst() 145 | .orElse(null); 146 | if (first == null) { 147 | ingredientsUid.add(RecipeInfo.NONE_MARK); 148 | } else { 149 | ingredientsUid.add(IngredientHelper.getUniqueId(first)); 150 | } 151 | } 152 | return ingredientsUid; 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/helper/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.helper; 2 | 3 | import net.minecraftforge.fml.common.ObfuscationReflectionHelper; 4 | 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.Nullable; 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Method; 9 | import java.util.Arrays; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.Objects; 13 | 14 | public final class ReflectionUtils { 15 | 16 | private static final Map FIELD_CACHE = new HashMap<>(); 17 | private static final Map METHOD_CACHE = new HashMap<>(); 18 | 19 | public static Field findField(@Nonnull Class classToAccess, @Nonnull String fieldName) { 20 | FieldDescriptor fieldDescriptor = new FieldDescriptor(classToAccess, fieldName); 21 | Field field = FIELD_CACHE.get(fieldDescriptor); 22 | if (field == null) { 23 | field = ObfuscationReflectionHelper.findField(fieldDescriptor.clazz, fieldDescriptor.fieldName); 24 | if (!field.isAccessible()) { 25 | field.setAccessible(true); 26 | } 27 | FIELD_CACHE.put(fieldDescriptor, field); 28 | } 29 | return field; 30 | } 31 | 32 | public static T getFieldValue(@Nonnull Class classToAccess, @Nonnull E object, @Nonnull String fieldName) { 33 | return getFieldValue(object, new FieldDescriptor(classToAccess, fieldName, false)); 34 | } 35 | 36 | public static T getStaticFieldValue(@Nonnull Class classToAccess, @Nonnull String fieldName) { 37 | return getFieldValue(null, new FieldDescriptor(classToAccess, fieldName, true)); 38 | } 39 | 40 | public static void setPrivateValue(Class classToAccess, @Nullable T instance, @Nullable E value, String fieldName) { 41 | try { 42 | findField(classToAccess, fieldName).set(instance, value); 43 | } catch (IllegalAccessException e) { 44 | throw new RuntimeException(e); 45 | } 46 | } 47 | 48 | @SuppressWarnings("unchecked") 49 | private static T getFieldValue(@Nullable Object obj, @Nonnull FieldDescriptor fieldDescriptor) { 50 | try { 51 | return (T) (findField(fieldDescriptor.clazz, fieldDescriptor.fieldName).get(obj)); 52 | } catch (Exception e) { 53 | throw new RuntimeException(e); 54 | } 55 | } 56 | 57 | public static Method getMethod(@Nonnull Class clazz, @Nonnull String methodName, Class returnType, Class... parameterTypes) { 58 | MethodDescriptor methodDescriptor = new MethodDescriptor(clazz, methodName, parameterTypes); 59 | Method method = METHOD_CACHE.get(methodDescriptor); 60 | if (method == null) { 61 | method = ObfuscationReflectionHelper.findMethod(clazz, methodName, returnType, parameterTypes); 62 | METHOD_CACHE.put(methodDescriptor, method); 63 | } 64 | return method; 65 | } 66 | 67 | private static class FieldDescriptor { 68 | @Nonnull 69 | private final Class clazz; 70 | @Nonnull 71 | private final String fieldName; 72 | private final boolean isStatic; 73 | 74 | public FieldDescriptor(@Nonnull Class clazz, @Nonnull String fieldName, boolean isStatic) { 75 | this.clazz = clazz; 76 | this.fieldName = fieldName; 77 | this.isStatic = isStatic; 78 | } 79 | 80 | public FieldDescriptor(@Nonnull Class clazz, @Nonnull String fieldName) { 81 | this(clazz, fieldName, false); 82 | } 83 | 84 | @Override 85 | public boolean equals(Object o) { 86 | if (this == o) return true; 87 | if (o == null || getClass() != o.getClass()) return false; 88 | FieldDescriptor that = (FieldDescriptor) o; 89 | return isStatic == that.isStatic && clazz.equals(that.clazz) && fieldName.equals(that.fieldName); 90 | } 91 | 92 | @Override 93 | public int hashCode() { 94 | return Objects.hash(clazz, fieldName, isStatic); 95 | } 96 | 97 | } 98 | 99 | private static class MethodDescriptor { 100 | @Nonnull 101 | private final Class clazz; 102 | @Nonnull 103 | private final String methodName; 104 | @Nonnull 105 | private final Class[] parameterTypes; 106 | 107 | public MethodDescriptor(@Nonnull Class clazz, @Nonnull String methodName, @Nonnull Class[] parameterTypes) { 108 | this.clazz = clazz; 109 | this.methodName = methodName; 110 | this.parameterTypes = parameterTypes; 111 | } 112 | 113 | @Override 114 | public boolean equals(Object o) { 115 | if (this == o) return true; 116 | if (o == null || getClass() != o.getClass()) return false; 117 | MethodDescriptor that = (MethodDescriptor) o; 118 | return clazz.equals(that.clazz) && 119 | methodName.equals(that.methodName) && 120 | Arrays.equals(parameterTypes, that.parameterTypes); 121 | } 122 | 123 | @Override 124 | public int hashCode() { 125 | int result = Objects.hash(clazz, methodName); 126 | result = 31 * result + Arrays.hashCode(parameterTypes); 127 | return result; 128 | } 129 | 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/jei/JeiHooks.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.jei; 2 | 3 | import com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig; 4 | import mezz.jei.api.recipe.IFocus; 5 | 6 | @SuppressWarnings("unused") 7 | public final class JeiHooks { 8 | 9 | private JeiHooks() { 10 | } 11 | 12 | /** 13 | * Since using events to implement add history requires too much processing, we decided to use asm. 14 | */ 15 | public static void onSetFocus(IFocus focus) { 16 | if (JeiUtilitiesConfig.isEnableHistory()) { 17 | JeiUtilitiesPlugin.getGrid().ifPresent(grid -> grid.addHistoryIngredient(focus.getValue())); 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/jei/JeiUtilitiesPlugin.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.jei; 2 | 3 | import com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig; 4 | import com.github.vfyjxf.jeiutilities.gui.bookmark.AdvancedBookmarkOverlay; 5 | import com.github.vfyjxf.jeiutilities.gui.history.AdvancedIngredientGrid; 6 | import com.github.vfyjxf.jeiutilities.jei.ingredient.CraftingRecipeInfo; 7 | import com.github.vfyjxf.jeiutilities.jei.ingredient.RecipeInfo; 8 | import com.github.vfyjxf.jeiutilities.jei.ingredient.RecipeInfoHelper; 9 | import com.github.vfyjxf.jeiutilities.jei.ingredient.RecipeInfoRenderer; 10 | import mezz.jei.Internal; 11 | import mezz.jei.api.*; 12 | import mezz.jei.api.ingredients.IModIngredientRegistration; 13 | import mezz.jei.bookmarks.BookmarkList; 14 | import mezz.jei.gui.overlay.IngredientGrid; 15 | import mezz.jei.gui.overlay.IngredientGridWithNavigation; 16 | import mezz.jei.gui.overlay.IngredientListOverlay; 17 | import mezz.jei.gui.overlay.bookmarks.BookmarkOverlay; 18 | import mezz.jei.ingredients.IngredientListElementFactory; 19 | import mezz.jei.ingredients.IngredientOrderTracker; 20 | import mezz.jei.ingredients.IngredientRegistry; 21 | import mezz.jei.input.InputHandler; 22 | import mezz.jei.runtime.JeiRuntime; 23 | import net.minecraftforge.fml.common.ObfuscationReflectionHelper; 24 | 25 | import javax.annotation.Nonnull; 26 | import java.util.Collections; 27 | import java.util.Optional; 28 | 29 | import static net.minecraftforge.fml.common.ObfuscationReflectionHelper.getPrivateValue; 30 | 31 | /** 32 | * @author vfyjxf 33 | */ 34 | @JEIPlugin 35 | public class JeiUtilitiesPlugin implements IModPlugin { 36 | 37 | public static JeiRuntime jeiRuntime; 38 | public static InputHandler inputHandler; 39 | public static IModRegistry modRegistry; 40 | public static IngredientRegistry ingredientRegistry; 41 | public static IngredientOrderTracker ORDER_TRACKER; 42 | public static IGuiHelper guiHelper; 43 | public static BookmarkOverlay bookmarkOverlay; 44 | public static BookmarkList bookmarkList; 45 | public static IngredientListOverlay ingredientListOverlay; 46 | public static IngredientGrid bookmarkIngredientGrid; 47 | public static IngredientGridWithNavigation bookmarkContents; 48 | /** 49 | * This field is set by asm. 50 | */ 51 | public static IRecipeRegistry recipeRegistry; 52 | private static AdvancedIngredientGrid grid; 53 | 54 | @Override 55 | public void onRuntimeAvailable(@Nonnull IJeiRuntime jeiRuntime) { 56 | JeiUtilitiesPlugin.jeiRuntime = Internal.getRuntime(); 57 | ingredientListOverlay = (IngredientListOverlay) jeiRuntime.getIngredientListOverlay(); 58 | if (JeiUtilitiesConfig.isEnableHistory()) { 59 | ObfuscationReflectionHelper.setPrivateValue( 60 | IngredientGridWithNavigation.class, 61 | getPrivateValue(IngredientListOverlay.class, (IngredientListOverlay) jeiRuntime.getIngredientListOverlay(), "contents"), 62 | grid = new AdvancedIngredientGrid(), 63 | "ingredientGrid" 64 | ); 65 | ORDER_TRACKER = getPrivateValue(IngredientListElementFactory.class, null, "ORDER_TRACKER"); 66 | } 67 | if (JeiUtilitiesConfig.getRecordRecipes()) { 68 | bookmarkOverlay = (BookmarkOverlay) jeiRuntime.getBookmarkOverlay(); 69 | bookmarkList = getPrivateValue(BookmarkOverlay.class, bookmarkOverlay, "bookmarkList"); 70 | bookmarkContents = getPrivateValue(BookmarkOverlay.class, bookmarkOverlay, "contents"); 71 | bookmarkIngredientGrid = getPrivateValue(IngredientGridWithNavigation.class, 72 | bookmarkContents, 73 | "ingredientGrid"); 74 | } 75 | } 76 | 77 | @Override 78 | public void register(@Nonnull IModRegistry registry) { 79 | JeiUtilitiesPlugin.modRegistry = registry; 80 | ingredientRegistry = (IngredientRegistry) registry.getIngredientRegistry(); 81 | JeiUtilitiesPlugin.guiHelper = registry.getJeiHelpers().getGuiHelper(); 82 | } 83 | 84 | @SuppressWarnings({"unchecked", "rawtypes"}) 85 | @Override 86 | public void registerIngredients(@Nonnull IModIngredientRegistration registry) { 87 | RecipeInfoHelper helper = new RecipeInfoHelper<>(); 88 | RecipeInfoRenderer renderer = new RecipeInfoRenderer(); 89 | registry.register(RecipeInfo.RECIPE_INFO, Collections.emptyList(), helper, renderer); 90 | registry.register(CraftingRecipeInfo.CRAFTING_RECIPE_INFO, Collections.emptyList(), helper, renderer); 91 | } 92 | 93 | public static Optional getGrid() { 94 | return Optional.ofNullable(grid); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/jei/bookmark/RecipeBookmarkList.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.jei.bookmark; 2 | 3 | import com.github.vfyjxf.jeiutilities.JEIUtilities; 4 | import com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig; 5 | import com.github.vfyjxf.jeiutilities.helper.IngredientHelper; 6 | import com.github.vfyjxf.jeiutilities.helper.RecipeHelper; 7 | import com.github.vfyjxf.jeiutilities.helper.ReflectionUtils; 8 | import com.github.vfyjxf.jeiutilities.jei.ingredient.CraftingRecipeInfo; 9 | import com.github.vfyjxf.jeiutilities.jei.ingredient.RecipeInfo; 10 | import com.google.gson.*; 11 | import mezz.jei.api.ingredients.IIngredientHelper; 12 | import mezz.jei.api.ingredients.VanillaTypes; 13 | import mezz.jei.api.recipe.IFocus; 14 | import mezz.jei.api.recipe.IIngredientType; 15 | import mezz.jei.api.recipe.IRecipeWrapper; 16 | import mezz.jei.api.recipe.wrapper.ICraftingRecipeWrapper; 17 | import mezz.jei.bookmarks.BookmarkList; 18 | import mezz.jei.config.Config; 19 | import mezz.jei.gui.Focus; 20 | import mezz.jei.gui.ingredients.IIngredientListElement; 21 | import mezz.jei.gui.overlay.IIngredientGridSource; 22 | import mezz.jei.ingredients.IngredientListElementFactory; 23 | import mezz.jei.ingredients.IngredientRegistry; 24 | import mezz.jei.startup.ForgeModIdHelper; 25 | import mezz.jei.util.Log; 26 | import net.minecraft.item.ItemStack; 27 | import net.minecraft.nbt.JsonToNBT; 28 | import net.minecraft.nbt.NBTException; 29 | import net.minecraft.nbt.NBTTagCompound; 30 | import net.minecraft.util.ResourceLocation; 31 | import org.apache.commons.io.IOUtils; 32 | import org.apache.commons.lang3.tuple.Pair; 33 | 34 | import javax.annotation.Nullable; 35 | import java.io.File; 36 | import java.io.FileReader; 37 | import java.io.FileWriter; 38 | import java.io.IOException; 39 | import java.util.*; 40 | 41 | import static com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin.ingredientRegistry; 42 | 43 | @SuppressWarnings({"rawtypes", "unused"}) 44 | public class RecipeBookmarkList extends BookmarkList { 45 | 46 | public static final String MARKER_OTHER = "O:"; 47 | public static final String MARKER_STACK = "T:"; 48 | public static final String MARKER_RECIPE = "R:"; 49 | 50 | private final List list; 51 | private final List listeners; 52 | 53 | public static BookmarkList create(IngredientRegistry ingredientRegistry) { 54 | if (JeiUtilitiesConfig.getRecordRecipes()) { 55 | return new RecipeBookmarkList(ingredientRegistry); 56 | } else { 57 | return new BookmarkList(ingredientRegistry); 58 | } 59 | } 60 | 61 | public RecipeBookmarkList(IngredientRegistry ingredientRegistry) { 62 | super(ingredientRegistry); 63 | list = ReflectionUtils.getFieldValue(BookmarkList.class, this, "list"); 64 | listeners = ReflectionUtils.getFieldValue(BookmarkList.class, this, "listeners"); 65 | } 66 | 67 | @Override 68 | public void saveBookmarks() { 69 | List strings = new ArrayList<>(); 70 | for (IIngredientListElement element : this.getIngredientList()) { 71 | Object object = element.getIngredient(); 72 | if (object instanceof ItemStack) { 73 | strings.add(MARKER_STACK + ((ItemStack) object).writeToNBT(new NBTTagCompound())); 74 | } else if (object instanceof RecipeInfo) { 75 | strings.add(MARKER_RECIPE + object); 76 | } else { 77 | strings.add(MARKER_OTHER + getUid(element)); 78 | } 79 | } 80 | File file = Config.getBookmarkFile(); 81 | if (file != null) { 82 | try (FileWriter writer = new FileWriter(file)) { 83 | IOUtils.writeLines(strings, "\n", writer); 84 | } catch (IOException e) { 85 | Log.get().error("Failed to save bookmarks list to file {}", file, e); 86 | } 87 | } 88 | } 89 | 90 | @Override 91 | public void loadBookmarks() { 92 | File file = Config.getBookmarkFile(); 93 | if (file == null || !file.exists()) { 94 | return; 95 | } 96 | List ingredientJsonStrings; 97 | try (FileReader reader = new FileReader(file)) { 98 | ingredientJsonStrings = IOUtils.readLines(reader); 99 | } catch (IOException e) { 100 | Log.get().error("Failed to load bookmarks from file {}", file, e); 101 | return; 102 | } 103 | Collection otherIngredientTypes = new ArrayList<>(ingredientRegistry.getRegisteredIngredientTypes()); 104 | otherIngredientTypes.remove(VanillaTypes.ITEM); 105 | list.clear(); 106 | getIngredientList().clear(); 107 | 108 | for (String ingredientJsonString : ingredientJsonStrings) { 109 | if (ingredientJsonString.startsWith(MARKER_STACK)) { 110 | String itemStackAsJson = ingredientJsonString.substring(MARKER_STACK.length()); 111 | try { 112 | NBTTagCompound itemStackAsNbt = JsonToNBT.getTagFromJson(itemStackAsJson); 113 | ItemStack itemStack = new ItemStack(itemStackAsNbt); 114 | if (!itemStack.isEmpty()) { 115 | ItemStack normalized = normalize(itemStack); 116 | addToLists(normalized); 117 | } else { 118 | Log.get().warn("Failed to load bookmarked ItemStack from json string, the item no longer exists:\n{}", itemStackAsJson); 119 | } 120 | } catch (NBTException e) { 121 | Log.get().error("Failed to load bookmarked ItemStack from json string:\n{}", itemStackAsJson, e); 122 | } 123 | } else if (ingredientJsonString.startsWith(MARKER_RECIPE)) { 124 | String recipeInfoAsJson = ingredientJsonString.substring(MARKER_RECIPE.length()); 125 | RecipeInfo recipeInfo = loadInfoFromJson(recipeInfoAsJson, otherIngredientTypes); 126 | if (recipeInfo != null) { 127 | addToLists(recipeInfo); 128 | } 129 | } else if (ingredientJsonString.startsWith(MARKER_OTHER)) { 130 | String uid = ingredientJsonString.substring(MARKER_OTHER.length()); 131 | Object ingredient = getUnknownIngredientByUid(otherIngredientTypes, uid); 132 | if (ingredient != null) { 133 | Object normalized = normalize(ingredient); 134 | addToLists(normalized); 135 | } 136 | } else { 137 | Log.get().error("Failed to load unknown bookmarked ingredient:\n{}", ingredientJsonString); 138 | } 139 | } 140 | 141 | for (IIngredientGridSource.Listener listener : listeners) { 142 | listener.onChange(); 143 | } 144 | } 145 | 146 | private void addToLists(T ingredient) { 147 | IIngredientType ingredientType = ingredientRegistry.getIngredientType(ingredient); 148 | IIngredientListElement element = IngredientListElementFactory.createUnorderedElement(ingredientRegistry, ingredientType, ingredient, ForgeModIdHelper.getInstance()); 149 | if (element != null) { 150 | list.add(ingredient); 151 | getIngredientList().add(element); 152 | } 153 | } 154 | 155 | private static String getUid(IIngredientListElement element) { 156 | IIngredientHelper ingredientHelper = element.getIngredientHelper(); 157 | return ingredientHelper.getUniqueId(element.getIngredient()); 158 | } 159 | 160 | private RecipeInfo loadInfoFromJson(String recipeInfoString, Collection otherIngredientTypes) { 161 | JsonObject jsonObject; 162 | try { 163 | jsonObject = new JsonParser().parse(recipeInfoString).getAsJsonObject(); 164 | } catch (JsonSyntaxException e) { 165 | JEIUtilities.logger.error("Failed to parse recipe info string {}", recipeInfoString, e); 166 | return null; 167 | } 168 | String ingredientUid = jsonObject.get("ingredient").getAsString(); 169 | String resultUid = jsonObject.get("result").getAsString(); 170 | String recipeCategoryUid = jsonObject.get("recipeCategoryUid").getAsString(); 171 | 172 | Object ingredientObject = loadIngredientFromJson(ingredientUid, otherIngredientTypes); 173 | Object resultObject = loadIngredientFromJson(resultUid, otherIngredientTypes); 174 | 175 | if (ingredientObject == null || resultObject == null) { 176 | JEIUtilities.logger.error("Failed to load recipe info from json string:\n{}", recipeInfoString); 177 | return null; 178 | } 179 | 180 | boolean isInputMode = jsonObject.get("isInputMode").getAsBoolean(); 181 | IFocus.Mode mode = isInputMode ? IFocus.Mode.INPUT : IFocus.Mode.OUTPUT; 182 | 183 | if (jsonObject.has("registryName")) { 184 | if (ingredientObject instanceof ItemStack && resultObject instanceof ItemStack) { 185 | 186 | ResourceLocation registryName = new ResourceLocation(jsonObject.get("registryName").getAsString()); 187 | Pair recipePair = RecipeHelper.getCraftingRecipeWrapperAndIndex(registryName, recipeCategoryUid, resultObject, new Focus<>(mode, ingredientObject)); 188 | 189 | if (recipePair == null) { 190 | JEIUtilities.logger.error("Failed to find the corresponding recipe, found the invalid recipe record :\n{}", recipeInfoString); 191 | return null; 192 | } 193 | 194 | return new CraftingRecipeInfo((ItemStack) ingredientObject, 195 | (ItemStack) resultObject, 196 | recipeCategoryUid, 197 | recipePair.getRight(), 198 | isInputMode, 199 | recipePair.getLeft(), 200 | registryName 201 | ); 202 | 203 | } 204 | } 205 | 206 | if (jsonObject.has("inputs")) { 207 | JsonArray inputsArray = jsonObject.get("inputs").getAsJsonArray(); 208 | Map> recipeUidMap = getRecipeUidMap(inputsArray); 209 | 210 | Pair recipePair = RecipeHelper.getRecipeWrapperAndIndex(recipeUidMap, recipeCategoryUid, resultObject, new Focus<>(mode, ingredientObject)); 211 | 212 | if (recipePair == null) { 213 | JEIUtilities.logger.error("Failed to find the corresponding recipe, found the invalid recipe record :\n{}", recipeInfoString); 214 | return null; 215 | } 216 | 217 | return new RecipeInfo<>(ingredientObject, 218 | resultObject, 219 | recipeCategoryUid, 220 | recipePair.getRight(), 221 | isInputMode, 222 | recipePair.getLeft() 223 | ); 224 | } 225 | 226 | return null; 227 | } 228 | 229 | private Object loadIngredientFromJson(String ingredientString, Collection otherIngredientTypes) { 230 | if (ingredientString.startsWith(MARKER_STACK)) { 231 | String itemStackAsJson = ingredientString.substring(MARKER_STACK.length()); 232 | try { 233 | NBTTagCompound itemStackAsNbt = JsonToNBT.getTagFromJson(itemStackAsJson); 234 | ItemStack itemStack = new ItemStack(itemStackAsNbt); 235 | if (!itemStack.isEmpty()) { 236 | return IngredientHelper.getNormalize(itemStack); 237 | } else { 238 | JEIUtilities.logger.warn("Failed to load recipe info ItemStack from json string, the item no longer exists:\n{}", itemStackAsJson); 239 | } 240 | } catch (NBTException e) { 241 | JEIUtilities.logger.error("Failed to load bookmarked ItemStack from json string:\n{}", itemStackAsJson, e); 242 | } 243 | } else if (ingredientString.startsWith(MARKER_OTHER)) { 244 | String uid = ingredientString.substring(MARKER_OTHER.length()); 245 | Object ingredient = getUnknownIngredientByUid(otherIngredientTypes, uid); 246 | if (ingredient != null) { 247 | return IngredientHelper.getNormalize(ingredient); 248 | } 249 | } else { 250 | JEIUtilities.logger.error("Failed to load unknown recipe info:\n{}", ingredientString); 251 | } 252 | return null; 253 | } 254 | 255 | private Map> getRecipeUidMap(JsonArray inputsArray) { 256 | HashMap> recipeUidMap = new HashMap<>(inputsArray.size()); 257 | for (JsonElement element : inputsArray) { 258 | if (element.isJsonArray()) { 259 | List inputsUidListInner = new ArrayList<>(); 260 | for (JsonElement elementInner : element.getAsJsonArray()) { 261 | if (elementInner.isJsonPrimitive()) { 262 | inputsUidListInner.add(elementInner.getAsString()); 263 | } 264 | } 265 | recipeUidMap.put(IngredientHelper.getIngredientType(inputsUidListInner), inputsUidListInner); 266 | } 267 | } 268 | 269 | return recipeUidMap; 270 | } 271 | 272 | @Nullable 273 | @SuppressWarnings("rawtypes") 274 | private Object getUnknownIngredientByUid(Collection ingredientTypes, String uid) { 275 | for (IIngredientType ingredientType : ingredientTypes) { 276 | Object ingredient = ingredientRegistry.getIngredientByUid(ingredientType, uid); 277 | if (ingredient != null) { 278 | return ingredient; 279 | } 280 | } 281 | return null; 282 | } 283 | 284 | } 285 | 286 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/jei/ingredient/CraftingRecipeInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.jei.ingredient; 2 | 3 | 4 | import mezz.jei.api.recipe.IIngredientType; 5 | import mezz.jei.api.recipe.wrapper.ICraftingRecipeWrapper; 6 | import net.minecraft.item.ItemStack; 7 | import net.minecraft.util.ResourceLocation; 8 | 9 | import javax.annotation.Nonnull; 10 | 11 | /** 12 | * A special version of {@link RecipeInfo}, used only to describe CraftingRecipe, 13 | * because CraftingRecipe has the recipeName feature, 14 | * we can simplify its storage and improve the speed of loading it. 15 | */ 16 | public class CraftingRecipeInfo extends RecipeInfo { 17 | 18 | public static final IIngredientType CRAFTING_RECIPE_INFO = () -> CraftingRecipeInfo.class; 19 | @Nonnull 20 | private final ResourceLocation registryName; 21 | 22 | public CraftingRecipeInfo(@Nonnull ItemStack ingredient, 23 | @Nonnull ItemStack result, 24 | String recipeCategoryUid, 25 | int recipeIndex, 26 | boolean isInputMode, 27 | @Nonnull ICraftingRecipeWrapper recipeWrapper, 28 | @Nonnull ResourceLocation registryName 29 | ) { 30 | super(ingredient, result, recipeCategoryUid, recipeIndex, isInputMode, recipeWrapper); 31 | this.registryName = registryName; 32 | } 33 | 34 | @Override 35 | public RecipeInfo copy() { 36 | return new CraftingRecipeInfo( 37 | getIngredient(), 38 | getResult(), 39 | getRecipeCategoryUid(), 40 | getRecipeIndex(), 41 | isInputMode(), 42 | (ICraftingRecipeWrapper) getRecipeWrapper(), 43 | registryName 44 | ); 45 | } 46 | 47 | @Nonnull 48 | public ResourceLocation getRegistryName() { 49 | return registryName; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "{" + 55 | "\"ingredient\":\"" + getIngredientString(getIngredient()) + "\"," + 56 | "\"result\":\"" + getIngredientString(getResult()) + "\"," + 57 | "\"recipeCategoryUid\":\"" + getRecipeCategoryUid() + "\"," + 58 | "\"isInputMode\":" + isInputMode() + "," + 59 | "\"registryName\":\"" + getRegistryName() + "\"" + 60 | "}"; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/jei/ingredient/RecipeInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.jei.ingredient; 2 | 3 | import com.github.vfyjxf.jeiutilities.helper.IngredientHelper; 4 | import com.github.vfyjxf.jeiutilities.helper.ReflectionUtils; 5 | import com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin; 6 | import mezz.jei.api.ingredients.IIngredientRenderer; 7 | import mezz.jei.api.ingredients.IIngredients; 8 | import mezz.jei.api.recipe.IFocus; 9 | import mezz.jei.api.recipe.IIngredientType; 10 | import mezz.jei.api.recipe.IRecipeWrapper; 11 | import mezz.jei.api.recipe.wrapper.ICraftingRecipeWrapper; 12 | import mezz.jei.ingredients.Ingredients; 13 | import net.minecraft.item.ItemStack; 14 | import net.minecraft.nbt.NBTTagCompound; 15 | 16 | import javax.annotation.Nonnull; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | import static com.github.vfyjxf.jeiutilities.jei.bookmark.RecipeBookmarkList.MARKER_OTHER; 22 | import static com.github.vfyjxf.jeiutilities.jei.bookmark.RecipeBookmarkList.MARKER_STACK; 23 | 24 | /** 25 | * Wrap the recipe inside this class to allow jei bookmarks to accept the same ingredients as the elements in the bookmark. 26 | */ 27 | @SuppressWarnings("rawtypes") 28 | public class RecipeInfo { 29 | 30 | public static final IIngredientType RECIPE_INFO = () -> RecipeInfo.class; 31 | public static final String NONE_MARK = "none"; 32 | 33 | private final I ingredient; 34 | private final R result; 35 | private final String recipeCategoryUid; 36 | private final int recipeIndex; 37 | private final boolean isInputMode; 38 | /** 39 | * The runtime points to the saved recipe, and this object is used at runtime to compare recipes for equality. 40 | */ 41 | private final IRecipeWrapper recipeWrapper; 42 | 43 | public static RecipeInfo create( 44 | @Nonnull Object ingredient, 45 | @Nonnull Object result, 46 | String recipeCategoryUid, 47 | int recipeIndex, 48 | boolean isInputMode, 49 | @Nonnull IRecipeWrapper recipeWrapper 50 | ) { 51 | if (recipeWrapper instanceof ICraftingRecipeWrapper) { 52 | ICraftingRecipeWrapper craftingWrapper = (ICraftingRecipeWrapper) recipeWrapper; 53 | if (craftingWrapper.getRegistryName() != null && ingredient instanceof ItemStack && result instanceof ItemStack) { 54 | return new CraftingRecipeInfo( 55 | (ItemStack) ingredient, 56 | (ItemStack) result, 57 | recipeCategoryUid, 58 | recipeIndex, 59 | isInputMode, 60 | craftingWrapper, 61 | craftingWrapper.getRegistryName() 62 | ); 63 | } 64 | } 65 | 66 | return new RecipeInfo<>(ingredient, result, recipeCategoryUid, recipeIndex, isInputMode, recipeWrapper); 67 | } 68 | 69 | public RecipeInfo(@Nonnull I ingredient, 70 | @Nonnull R result, 71 | String recipeCategoryUid, 72 | int recipeIndex, 73 | boolean isInputMode, 74 | @Nonnull IRecipeWrapper recipeWrapper 75 | ) { 76 | this.ingredient = ingredient; 77 | this.result = result; 78 | this.recipeCategoryUid = recipeCategoryUid; 79 | this.recipeIndex = recipeIndex; 80 | this.isInputMode = isInputMode; 81 | this.recipeWrapper = recipeWrapper; 82 | } 83 | 84 | public I getIngredient() { 85 | return ingredient; 86 | } 87 | 88 | @Nonnull 89 | public R getResult() { 90 | return result; 91 | } 92 | 93 | public String getRecipeCategoryUid() { 94 | return recipeCategoryUid; 95 | } 96 | 97 | public int getRecipeIndex() { 98 | return recipeIndex; 99 | } 100 | 101 | public IFocus.Mode getMode() { 102 | return isInputMode ? IFocus.Mode.INPUT : IFocus.Mode.OUTPUT; 103 | } 104 | 105 | public boolean isInputMode() { 106 | return isInputMode; 107 | } 108 | 109 | public IIngredientRenderer getResultIngredientRenderer() { 110 | return JeiUtilitiesPlugin.ingredientRegistry.getIngredientRenderer(this.result); 111 | } 112 | 113 | public IRecipeWrapper getRecipeWrapper() { 114 | return recipeWrapper; 115 | } 116 | 117 | public RecipeInfo copy() { 118 | return new RecipeInfo<>( 119 | this.ingredient, 120 | this.result, 121 | this.recipeCategoryUid, 122 | this.recipeIndex, 123 | this.isInputMode, 124 | this.recipeWrapper 125 | ); 126 | } 127 | 128 | @Override 129 | public String toString() { 130 | return "{" + 131 | "\"ingredient\":\"" + getIngredientString(ingredient) + "\"," + 132 | "\"result\":\"" + getIngredientString(result) + "\"," + 133 | "\"recipeCategoryUid\":\"" + recipeCategoryUid + "\"," + 134 | "\"isInputMode\":" + isInputMode + "," + 135 | "\"inputs\":" + getInputsString() + 136 | "}"; 137 | } 138 | 139 | protected String getIngredientString(T ingredient) { 140 | if (ingredient instanceof ItemStack) { 141 | //replace " -> \" 142 | return MARKER_STACK + ((ItemStack) ingredient).writeToNBT(new NBTTagCompound()).toString().replace("\"", "\\\""); 143 | } else { 144 | return MARKER_OTHER + IngredientHelper.getUniqueId(ingredient).replace("\"", "\\\""); 145 | } 146 | } 147 | 148 | protected String getInputsString() { 149 | IIngredients recipeIngredients = new Ingredients(); 150 | recipeWrapper.getIngredients(recipeIngredients); 151 | Map> allInputs = ReflectionUtils.getFieldValue( 152 | Ingredients.class, 153 | recipeIngredients, 154 | "inputs" 155 | ); 156 | List> recipeIngredientUidList = new ArrayList<>(); 157 | 158 | for (List inputsPerType : allInputs.values()) { 159 | List ingredientUidList = new ArrayList<>(); 160 | for (List inputPerSlot : inputsPerType) { 161 | if (inputPerSlot.isEmpty()) { 162 | ingredientUidList.add(RecipeInfo.NONE_MARK); 163 | } else { 164 | ingredientUidList.add(IngredientHelper.getUniqueId(inputPerSlot.get(0))); 165 | } 166 | } 167 | recipeIngredientUidList.add(ingredientUidList); 168 | } 169 | 170 | recipeIngredientUidList.sort((o1, o2) -> Integer.compare(o2.size(), o1.size())); 171 | 172 | StringBuilder builder = new StringBuilder(); 173 | builder.append("["); 174 | for (List ingredientUidList : recipeIngredientUidList) { 175 | builder.append("["); 176 | for (String ingredientUid : ingredientUidList) { 177 | builder.append("\"") 178 | .append(ingredientUid.replace("\"", "\\\"")) 179 | .append("\","); 180 | } 181 | builder.deleteCharAt(builder.length() - 1); 182 | builder.append("],"); 183 | } 184 | builder.deleteCharAt(builder.length() - 1); 185 | builder.append("]"); 186 | 187 | return builder.toString(); 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/jei/ingredient/RecipeInfoHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.jei.ingredient; 2 | 3 | import com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin; 4 | import mezz.jei.api.ingredients.IIngredientHelper; 5 | import mezz.jei.api.recipe.IFocus; 6 | import net.minecraft.item.ItemStack; 7 | 8 | import javax.annotation.Nonnull; 9 | import javax.annotation.Nullable; 10 | import java.awt.*; 11 | import java.util.Collection; 12 | 13 | @SuppressWarnings({"rawtypes", "unchecked"}) 14 | public class RecipeInfoHelper implements IIngredientHelper { 15 | 16 | @Nullable 17 | @Override 18 | public T getMatch(@Nonnull Iterable ingredients, @Nonnull T ingredientToMatch) { 19 | return null; 20 | } 21 | 22 | @Override 23 | @Nonnull 24 | public String getDisplayName(@Nonnull T ingredient) { 25 | return getIngredientHelper(ingredient.getResult()).getDisplayName(ingredient.getResult()); 26 | } 27 | 28 | @Override 29 | @Nonnull 30 | public String getUniqueId(@Nonnull T ingredient) { 31 | return ingredient.toString(); 32 | } 33 | 34 | @Override 35 | @Nonnull 36 | public String getWildcardId(@Nonnull T ingredient) { 37 | return getIngredientHelper(ingredient.getResult()).getWildcardId(ingredient.getResult()); 38 | } 39 | 40 | @Override 41 | @Nonnull 42 | public String getModId(@Nonnull T ingredient) { 43 | return getIngredientHelper(ingredient.getResult()).getModId(ingredient.getResult()); 44 | } 45 | 46 | @Override 47 | @Nonnull 48 | public String getResourceId(@Nonnull T ingredient) { 49 | return getIngredientHelper(ingredient.getResult()).getResourceId(ingredient.getResult()); 50 | } 51 | 52 | @Override 53 | @Nonnull 54 | public RecipeInfo copyIngredient(@Nonnull RecipeInfo ingredient) { 55 | return ingredient.copy(); 56 | } 57 | 58 | @Override 59 | @Nonnull 60 | public String getErrorInfo(@Nullable RecipeInfo ingredient) { 61 | if (ingredient == null) { 62 | return "null"; 63 | } 64 | return getIngredientHelper(ingredient.getResult()).getErrorInfo(ingredient.getResult()); 65 | } 66 | 67 | private IIngredientHelper getIngredientHelper(E ingredient) { 68 | return JeiUtilitiesPlugin.ingredientRegistry.getIngredientHelper(ingredient); 69 | } 70 | 71 | @Nonnull 72 | @Override 73 | public IFocus translateFocus(@Nonnull IFocus focus, @Nonnull IFocusFactory focusFactory) { 74 | IFocus real = focusFactory.createFocus(focus.getMode(), focus.getValue().getResult()); 75 | return getIngredientHelper(focus.getValue().getResult()).translateFocus(real, focusFactory); 76 | } 77 | 78 | @Nonnull 79 | @Override 80 | public String getDisplayModId(@Nonnull T ingredient) { 81 | return getIngredientHelper(ingredient.getResult()).getDisplayModId(ingredient.getResult()); 82 | } 83 | 84 | @Nonnull 85 | @Override 86 | public Iterable getColors(@Nonnull T ingredient) { 87 | return getIngredientHelper(ingredient.getResult()).getColors(ingredient.getResult()); 88 | } 89 | 90 | @Nonnull 91 | @Override 92 | public ItemStack getCheatItemStack(@Nonnull T ingredient) { 93 | return getIngredientHelper(ingredient.getResult()).getCheatItemStack(ingredient.getResult()); 94 | } 95 | 96 | @Override 97 | public boolean isValidIngredient(@Nonnull T ingredient) { 98 | return getIngredientHelper(ingredient.getResult()).isValidIngredient(ingredient.getResult()); 99 | } 100 | 101 | @Override 102 | public boolean isIngredientOnServer(@Nonnull T ingredient) { 103 | return getIngredientHelper(ingredient.getResult()).isIngredientOnServer(ingredient.getResult()); 104 | } 105 | 106 | @Nonnull 107 | @Override 108 | public Collection getOreDictNames(@Nonnull T ingredient) { 109 | return getIngredientHelper(ingredient.getResult()).getOreDictNames(ingredient.getResult()); 110 | } 111 | 112 | @Nonnull 113 | @Override 114 | public Collection getCreativeTabNames(@Nonnull T ingredient) { 115 | return getIngredientHelper(ingredient.getResult()).getCreativeTabNames(ingredient.getResult()); 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /src/main/java/com/github/vfyjxf/jeiutilities/jei/ingredient/RecipeInfoRenderer.java: -------------------------------------------------------------------------------- 1 | package com.github.vfyjxf.jeiutilities.jei.ingredient; 2 | 3 | import com.github.vfyjxf.jeiutilities.config.JeiUtilitiesConfig; 4 | import com.github.vfyjxf.jeiutilities.jei.JeiUtilitiesPlugin; 5 | import mezz.jei.api.ingredients.IIngredientRenderer; 6 | import mezz.jei.api.recipe.IIngredientType; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.client.gui.FontRenderer; 9 | import net.minecraft.client.renderer.GlStateManager; 10 | import net.minecraft.client.util.ITooltipFlag; 11 | 12 | import javax.annotation.Nonnull; 13 | import javax.annotation.Nullable; 14 | import java.util.List; 15 | 16 | @SuppressWarnings("rawtypes") 17 | public class RecipeInfoRenderer implements IIngredientRenderer { 18 | 19 | @Override 20 | public void render(@Nonnull Minecraft minecraft, int xPosition, int yPosition, @Nullable RecipeInfo ingredient) { 21 | if (ingredient != null) { 22 | IIngredientType ingredientType = JeiUtilitiesPlugin.ingredientRegistry.getIngredientType(ingredient.getResult()); 23 | IIngredientRenderer ingredientRenderer = JeiUtilitiesPlugin.ingredientRegistry.getIngredientRenderer(ingredientType); 24 | ingredientRenderer.render(minecraft, xPosition, yPosition, ingredient.getResult()); 25 | if (JeiUtilitiesConfig.isShowRecipeBookmarkReminders()) { 26 | GlStateManager.disableDepth(); 27 | this.getFontRenderer(minecraft, ingredient).drawStringWithShadow("R", xPosition, yPosition - 2, 0xFF555555); 28 | GlStateManager.enableDepth(); 29 | } 30 | } 31 | } 32 | 33 | @SuppressWarnings("unchecked") 34 | @Nonnull 35 | @Override 36 | public List getTooltip(@Nonnull Minecraft minecraft, @Nonnull RecipeInfo ingredient, @Nonnull ITooltipFlag tooltipFlag) { 37 | return ingredient.getResultIngredientRenderer().getTooltip(minecraft, ingredient.getResult(), tooltipFlag); 38 | } 39 | 40 | @SuppressWarnings("unchecked") 41 | @Nonnull 42 | @Override 43 | public FontRenderer getFontRenderer(@Nonnull Minecraft minecraft, @Nonnull RecipeInfo ingredient) { 44 | return ingredient.getResultIngredientRenderer().getFontRenderer(minecraft, ingredient.getResult()); 45 | } 46 | 47 | @Nonnull 48 | @SuppressWarnings({"unchecked","deprecation"}) 49 | @Override 50 | public List getTooltip(@Nonnull Minecraft minecraft, @Nonnull T ingredient, boolean advanced) { 51 | return ingredient.getResultIngredientRenderer().getTooltip(minecraft, ingredient.getResult(), advanced); 52 | } 53 | 54 | @Nonnull 55 | @SuppressWarnings({"unchecked","deprecation"}) 56 | @Override 57 | public List getTooltip(@Nonnull Minecraft minecraft, @Nonnull T ingredient) { 58 | return ingredient.getResultIngredientRenderer().getTooltip(minecraft, ingredient.getResult()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/resources/assets/jeiutilities/lang/en_us.lang: -------------------------------------------------------------------------------- 1 | jeiutilities.tooltip.recording=Recipe Record Configuration 2 | jeiutilities.tooltip.recording.mode=Current Mode : %s 3 | jeiutilities.tooltip.recording.description.restricted=Open a recorded recipe or mark a recipe while holding down the Shift key. 4 | jeiutilities.tooltip.recording.description.enable=Recipes are not recorded or opened when Shift is pressed. 5 | jeiutilities.tooltip.recording.information_1=Hold down the %s key to display the recipe preview. 6 | 7 | jeiutilities.button.name.disable=Disable 8 | jeiutilities.button.name.enable=Enable 9 | jeiutilities.button.name.restricted=Restricted 10 | 11 | key.jeiutilities.displayRecipe=Display Recipe Preview 12 | key.jeiutilities.pickBookmark=Pick up bookmark 13 | key.jeiutilities.transferRecipe=Transfer Recipe(Single) 14 | key.jeiutilities.transferRecipeMax=Transfer Recipe(MAX) -------------------------------------------------------------------------------- /src/main/resources/assets/jeiutilities/lang/ja_jp.lang: -------------------------------------------------------------------------------- 1 | jeiutilities.tooltip.recording=JEI Utilities 2 | jeiutilities.tooltip.recording.mode=現在のモード: %s 3 | jeiutilities.tooltip.recording.description.restricted=Shiftキーを押している場合、レシピ確認が出来る方法で登録する。 4 | jeiutilities.tooltip.recording.description.enable=Shiftキーを押していない場合、レシピ確認が出来る方法で登録する。 5 | jeiutilities.tooltip.recording.information_1=%sキーを長押ししながら、レシピ確認が出来る方法で登録した物を表示する。 6 | 7 | jeiutilities.button.name.disable=無効化 8 | jeiutilities.button.name.enable=有効化 9 | jeiutilities.button.name.restricted=制限付き 10 | 11 | key.jeiutilities.displayRecipe=レシピのプレビューを表示 12 | key.jeiutilities.pickBookmark=ブックマークを掴む 13 | key.jeiutilities.transferRecipe=レシピを移動(Single) 14 | key.jeiutilities.transferRecipeMax=レシピを移動(MAX) -------------------------------------------------------------------------------- /src/main/resources/assets/jeiutilities/lang/pt_br.json: -------------------------------------------------------------------------------- 1 | jeiutilities.tooltip.recording=Configuração de registro de receita 2 | jeiutilities.tooltip.recording.mode=Modo atual : %s 3 | jeiutilities.tooltip.recording.description.restricted=Abra uma receita gravada ou marque uma receita enquanto mantém pressionada a tecla Shift. 4 | jeiutilities.tooltip.recording.description.enable=As receitas não são gravadas ou abertas quando a tecla Shift é pressionada. 5 | jeiutilities.tooltip.recording.information_1=Mantenha pressionada a tecla %s para exibir a visualização da receita. 6 | 7 | jeiutilities.button.name.disable=Desativar 8 | jeiutilities.button.name.enable=Ativar 9 | jeiutilities.button.name.restricted=Restrito 10 | 11 | key.jeiutilities.displayRecipe=Exibir visualização da receita 12 | key.jeiutilities.pickBookmark=Pegar marcador 13 | key.jeiutilities.transferRecipe=Receita de Transferência (Individual) 14 | key.jeiutilities.transferRecipeMax=Receita de transferência (MAX) -------------------------------------------------------------------------------- /src/main/resources/assets/jeiutilities/lang/ru_ru.lang: -------------------------------------------------------------------------------- 1 | jeiutilities.tooltip.recording=Конфигурация записи рецепта 2 | jeiutilities.tooltip.recording.mode=Текущий режим: %s 3 | jeiutilities.tooltip.recording.description.restricted=Рецепт откроется из истории при нажатой клавише Shift. 4 | jeiutilities.tooltip.recording.description.enable=Рецепты не записываются и не открываются при нажатии Shift. 5 | jeiutilities.tooltip.recording.information_1=Удерживайте нажатой клавишу %s, чтобы отобразить предварительный просмотр рецепта. 6 | 7 | jeiutilities.button.name.disable=Отключено 8 | jeiutilities.button.name.enable=Включено 9 | jeiutilities.button.name.restricted=Ограничено 10 | 11 | key.jeiutilities.displayRecipe=Показать предварительный просмотр рецепта 12 | key.jeiutilities.pickBookmark=Поднять закладку 13 | key.jeiutilities.transferRecipe=Рецепт передачи(Одиночный) 14 | key.jeiutilities.transferRecipeMax=Рецепт передачи(MAX) -------------------------------------------------------------------------------- /src/main/resources/assets/jeiutilities/lang/zh_cn.lang: -------------------------------------------------------------------------------- 1 | jeiutilities.tooltip.recording=配方记录设置 2 | jeiutilities.tooltip.recording.mode=当前模式:%s 3 | jeiutilities.tooltip.recording.description.restricted=按住Shift键时标记配方/打开记录的配方。 4 | jeiutilities.tooltip.recording.description.enable=按下Shift键时不会记录配方/打开记录的配方。 5 | jeiutilities.tooltip.recording.information_1=按住%s键以显示配方预览。 6 | 7 | jeiutilities.button.name.disable=关闭配方记录 8 | jeiutilities.button.name.enable=打开配方记录 9 | jeiutilities.button.name.restricted=限制模式 10 | 11 | key.jeiutilities.displayRecipe=显示配方预览 12 | key.jeiutilities.pickBookmark=拾起书签 13 | key.jeiutilities.transferRecipe=转移配方(单个) 14 | key.jeiutilities.transferRecipeMax=转移配方(最大) -------------------------------------------------------------------------------- /src/main/resources/assets/jeiutilities/meta/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vfyjxf/JEI-Utilities/2f1ab3be8f26dd15ca124aa046ada34b75ecb004/src/main/resources/assets/jeiutilities/meta/logo.png -------------------------------------------------------------------------------- /src/main/resources/assets/jeiutilities/textures/gui/icon/bookmark_button_config_disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vfyjxf/JEI-Utilities/2f1ab3be8f26dd15ca124aa046ada34b75ecb004/src/main/resources/assets/jeiutilities/textures/gui/icon/bookmark_button_config_disable.png -------------------------------------------------------------------------------- /src/main/resources/assets/jeiutilities/textures/gui/icon/bookmark_button_config_enable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vfyjxf/JEI-Utilities/2f1ab3be8f26dd15ca124aa046ada34b75ecb004/src/main/resources/assets/jeiutilities/textures/gui/icon/bookmark_button_config_enable.png -------------------------------------------------------------------------------- /src/main/resources/assets/jeiutilities/textures/gui/preview_recipe_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vfyjxf/JEI-Utilities/2f1ab3be8f26dd15ca124aa046ada34b75ecb004/src/main/resources/assets/jeiutilities/textures/gui/preview_recipe_background.png -------------------------------------------------------------------------------- /src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [{ 2 | "modid": "${mod_id}", 3 | "name": "${mod_name}", 4 | "description": "${mod_description}", 5 | "version": "${mod_version}", 6 | "mcversion": "${mc_version}", 7 | "url": "", 8 | "updateUrl": "", 9 | "authorList": ["${mod_author}"], 10 | "credits": "", 11 | "logoFile": "${mod_icon}", 12 | "screenshots": [], 13 | "dependencies": [] 14 | }] -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "", 4 | "pack_format": 3 5 | } 6 | } 7 | --------------------------------------------------------------------------------