├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── org │ └── rusherhack │ └── bookmod │ ├── ArchiverCommand.java │ ├── BookMod.java │ └── util │ ├── BetterScreenShotHelper.java │ ├── BookBuffer.java │ └── BookInfo.java └── resources └── mcmod.info /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | 117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 118 | !gradle-wrapper.jar 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **The 2b2t Signed Book & Collectibles Network - AutoArchiverBot:** 2 | 3 | 4 | This mod was created specifically for The 2b2t Signed Book & Collectibles Network Discord Server, which aims to archive and store valuable items, including books. You can join this discord server through the following link: https://discord.gg/s4pYpwr. 5 | 6 | This mod was designed for 1.12.2 Forge and eases the process of archiving books by seamlessly automatically copying the title and author of signed books upon hovering over them. It also takes screenshots upon hovering which can be used to post in #signedbooks_archive. We plan on integrating this mod with the discord server soon, and for your security, the code is open-source. 7 | 8 | # **Usage:** 9 | - To begin automatically listing and screenshotting books, simply execute the command ```/archiver start``` in game, and then proceed to hover over all the books you want to record and screenshot. Once you are done, type ```/archiver stop``` and the book info will be printed to a text file, with a folder containing all the screenshots. The list will be formatted in a way so that you can copy and paste the name and title without any effort. You can find the folder where things are saved by executing the command ```/archiver folder```. 10 | 11 | # **Other:** 12 | - Should you have any problems or questions, feel free to contact Crystallising#1380. Special thanks to John200410 for coding the bot. 13 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url = 'https://maven.minecraftforge.net' } 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '4.1.+', changing: true 8 | } 9 | } 10 | 11 | apply plugin: 'net.minecraftforge.gradle' 12 | 13 | group = 'org.rusherhack' 14 | version = '1.0-SNAPSHOT' 15 | archivesBaseName = 'autobookarchiver' 16 | 17 | java.toolchain.languageVersion = JavaLanguageVersion.of(8) // Mojang ships Java 8 to end users, so your mod should target Java 8. 18 | 19 | minecraft { 20 | // The mappings can be changed at any time, and must be in the following format. 21 | // Channel: Version: 22 | // snapshot YYYYMMDD Snapshot are built nightly. 23 | // stable # Stables are built at the discretion of the MCP team. 24 | // official MCVersion Official field/method names from Mojang mapping files 25 | // 26 | // You must be aware of the Mojang license when using the 'official' mappings. 27 | // See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md 28 | // 29 | // Use non-default mappings at your own risk. they may not always work. 30 | // Simply re-run your setup task after changing the mappings to update your workspace. 31 | mappings channel: 'stable', version: '39-1.12' 32 | // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. 33 | 34 | // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') 35 | 36 | // Default run configurations. 37 | // These can be tweaked, removed, or duplicated as needed. 38 | runs { 39 | client { 40 | workingDirectory project.file('run') 41 | 42 | // Recommended logging data for a userdev environment 43 | // The markers can be changed as needed. 44 | // "SCAN": For mods scan. 45 | // "REGISTRIES": For firing of registry events. 46 | // "REGISTRYDUMP": For getting the contents of all registries. 47 | property 'forge.logging.markers', 'REGISTRIES' 48 | 49 | // Recommended logging level for the console 50 | // You can set various levels here. 51 | // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels 52 | property 'forge.logging.console.level', 'debug' 53 | 54 | mods { 55 | bookmod { 56 | source sourceSets.main 57 | } 58 | } 59 | } 60 | 61 | server { 62 | workingDirectory project.file('run') 63 | 64 | // Recommended logging data for a userdev environment 65 | // The markers can be changed as needed. 66 | // "SCAN": For mods scan. 67 | // "REGISTRIES": For firing of registry events. 68 | // "REGISTRYDUMP": For getting the contents of all registries. 69 | property 'forge.logging.markers', 'REGISTRIES' 70 | 71 | // Recommended logging level for the console 72 | // You can set various levels here. 73 | // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels 74 | property 'forge.logging.console.level', 'debug' 75 | 76 | mods { 77 | bookmod { 78 | source sourceSets.main 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | // Include resources generated by data generators. 86 | sourceSets.main.resources { srcDir 'src/generated/resources' } 87 | 88 | dependencies { 89 | // Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed 90 | // that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied. 91 | // The userdev artifact is a special name and will get all sorts of transformations applied to it. 92 | minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2855' 93 | 94 | // You may put jars on which you depend on in ./libs or you may define them like so.. 95 | // compile "some.group:artifact:version:classifier" 96 | // compile "some.group:artifact:version" 97 | 98 | // Real examples 99 | // compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env 100 | // compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env 101 | 102 | // The 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime. 103 | // provided 'com.mod-buildcraft:buildcraft:6.0.8:dev' 104 | 105 | // These dependencies get remapped to your current MCP mappings 106 | // deobf 'com.mod-buildcraft:buildcraft:6.0.8:dev' 107 | 108 | // For more info... 109 | // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html 110 | // http://www.gradle.org/docs/current/userguide/dependency_management.html 111 | } 112 | 113 | // Example for how to get properties into the manifest for reading by the runtime.. 114 | jar { 115 | manifest { 116 | attributes([ 117 | "Specification-Title" : "autobookarchiver", 118 | "Specification-Vendor" : "John200410", 119 | "Specification-Version" : "1", // We are version 1 of ourselves 120 | "Implementation-Title" : project.name, 121 | "Implementation-Version" : project.version, 122 | "Implementation-Vendor" : "John200410", 123 | "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") 124 | ]) 125 | } 126 | } 127 | 128 | jar.finalizedBy('reobfJar') 129 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx3G 2 | org.gradle.daemon=false 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/John200410/BookMod/490d37d9f6a7e4b9b9295392e6dce2ca776851f2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'bookmod' 2 | -------------------------------------------------------------------------------- /src/main/java/org/rusherhack/bookmod/ArchiverCommand.java: -------------------------------------------------------------------------------- 1 | package org.rusherhack.bookmod; 2 | 3 | import net.minecraft.client.renderer.OpenGlHelper; 4 | import net.minecraft.command.CommandBase; 5 | import net.minecraft.command.ICommandSender; 6 | import net.minecraft.server.MinecraftServer; 7 | import net.minecraft.util.text.TextComponentString; 8 | 9 | /** 10 | * Our command to start and stop storing book info 11 | * 12 | * @author John200410 5/15/2021 for bookmod 13 | */ 14 | public class ArchiverCommand extends CommandBase { 15 | 16 | @Override 17 | public String getName() { 18 | return "archiver"; 19 | } 20 | 21 | @Override 22 | public String getUsage(ICommandSender sender) { 23 | return this.getName() + " "; 24 | } 25 | 26 | @Override 27 | public void execute(MinecraftServer server, ICommandSender sender, String[] args) { 28 | //if there are no arguments, just print whatever is in the book buffer 29 | if(args.length == 0) { 30 | sender.sendMessage(new TextComponentString(BookMod.PREFIX + " " + BookMod.BOOK_BUFFER)); 31 | } else { 32 | switch (args[0].toLowerCase()) { 33 | case "togglesc": 34 | BookMod.screenshot = !BookMod.screenshot; 35 | sender.sendMessage(new TextComponentString(BookMod.PREFIX + "Screenshots have been toggled " + (BookMod.screenshot ? "on" : "off"))); 36 | break; 37 | case "start": 38 | sender.sendMessage(new TextComponentString(BookMod.PREFIX + " " + (BookMod.BOOK_BUFFER.start() ? "Started listening" : "Buffer already active! Run '/archiver stop' first"))); 39 | break; 40 | case "stop": 41 | try { 42 | sender.sendMessage(new TextComponentString(BookMod.PREFIX + " " + (BookMod.BOOK_BUFFER.finish() ? "Saved books" : "Buffer not active!"))); 43 | } catch (Throwable t) { //some i/o exception 44 | t.printStackTrace(); 45 | sender.sendMessage(new TextComponentString(BookMod.PREFIX + " " + "Failed to save books! Check log for more details")); 46 | } 47 | break; 48 | case "folder": 49 | case "openfolder": 50 | OpenGlHelper.openFile(BookMod.PATH); 51 | break; 52 | default: 53 | sender.sendMessage(new TextComponentString(BookMod.PREFIX + " " + "Invalid Arguments!")); 54 | } 55 | } 56 | } 57 | 58 | @Override 59 | public boolean checkPermission(MinecraftServer server, ICommandSender sender) { 60 | return true; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/rusherhack/bookmod/BookMod.java: -------------------------------------------------------------------------------- 1 | package org.rusherhack.bookmod; 2 | 3 | import net.minecraft.util.text.TextFormatting; 4 | import net.minecraftforge.client.ClientCommandHandler; 5 | import net.minecraftforge.fml.common.Mod; 6 | import net.minecraftforge.fml.common.event.FMLInitializationEvent; 7 | import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; 8 | import org.rusherhack.bookmod.util.BookBuffer; 9 | 10 | import java.io.File; 11 | import java.text.SimpleDateFormat; 12 | 13 | /** 14 | * This is a mod that was commissioned by Crystallising. 15 | *

16 | * The goal of this mod is to make it easier to submit written books for archival. 17 | * 18 | * @author John200410 19 | */ 20 | @Mod(modid = BookMod.MOD_ID, name = BookMod.MOD_NAME, version = BookMod.VERSION) 21 | public class BookMod { 22 | 23 | /** 24 | * Constants 25 | */ 26 | public static final String MOD_ID = "autobookarchiver"; 27 | public static final String MOD_NAME = "AutoBookArchiver"; 28 | public static final String VERSION = "1.2.1"; 29 | public static final File PATH = new File(MOD_NAME); 30 | public static final String PREFIX = String.format("%s[%s]", TextFormatting.DARK_AQUA, MOD_NAME); 31 | public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MM-yyyy HH mm ss"); 32 | public static final BookBuffer BOOK_BUFFER = new BookBuffer(); 33 | 34 | /** This is the instance of your mod as created by Forge. It will never be null. */ 35 | @Mod.Instance(MOD_ID) 36 | public static BookMod INSTANCE; 37 | 38 | public static boolean screenshot = true; 39 | 40 | /** 41 | * This is the first initialization event. 42 | */ 43 | @Mod.EventHandler 44 | public void preInit(FMLPreInitializationEvent event) { 45 | //create path to store book files, if it doesn't exist. 46 | if(!PATH.isDirectory()) { 47 | PATH.mkdir(); 48 | } 49 | } 50 | 51 | /** 52 | * This is the second initialization event. Register custom recipes 53 | */ 54 | @Mod.EventHandler 55 | public void init(FMLInitializationEvent event) { 56 | //register our bookmod command 57 | ClientCommandHandler.instance.registerCommand(new ArchiverCommand()); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/rusherhack/bookmod/util/BetterScreenShotHelper.java: -------------------------------------------------------------------------------- 1 | package org.rusherhack.bookmod.util; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.client.renderer.GlStateManager; 5 | import net.minecraft.client.renderer.texture.TextureUtil; 6 | import org.lwjgl.BufferUtils; 7 | 8 | import java.awt.image.BufferedImage; 9 | import java.nio.IntBuffer; 10 | 11 | /** 12 | * Copied stuff from ScreenShotHelper.class and modified it lul 13 | * 14 | * @author John200410 7/24/2021 for bookmod 15 | */ 16 | public class BetterScreenShotHelper { 17 | 18 | private static IntBuffer pixelBuffer; 19 | private static int[] pixelValues; 20 | 21 | public static BufferedImage createScreenshot(int x, int y, int width, int height) { 22 | int i = width * height; 23 | 24 | if(pixelBuffer == null || pixelBuffer.capacity() < i) { 25 | pixelBuffer = BufferUtils.createIntBuffer(i); 26 | pixelValues = new int[i]; 27 | } 28 | 29 | GlStateManager.glPixelStorei(3333, 1); 30 | GlStateManager.glPixelStorei(3317, 1); 31 | pixelBuffer.clear(); 32 | 33 | GlStateManager.glReadPixels(x, Minecraft.getMinecraft().displayHeight - y - height, width, height, 32993, 33639, pixelBuffer); 34 | 35 | pixelBuffer.get(pixelValues); 36 | TextureUtil.processPixelValues(pixelValues, width, height); 37 | BufferedImage bufferedimage = new BufferedImage(width, height, 1); 38 | bufferedimage.setRGB(0, 0, width, height, pixelValues, 0, width); 39 | return bufferedimage; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/rusherhack/bookmod/util/BookBuffer.java: -------------------------------------------------------------------------------- 1 | package org.rusherhack.bookmod.util; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.client.gui.ScaledResolution; 5 | import net.minecraft.init.Items; 6 | import net.minecraft.item.ItemStack; 7 | import net.minecraft.item.ItemWrittenBook; 8 | import net.minecraft.nbt.NBTTagCompound; 9 | import net.minecraft.util.Tuple; 10 | import net.minecraftforge.client.event.RenderTooltipEvent; 11 | import net.minecraftforge.common.MinecraftForge; 12 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 13 | import org.rusherhack.bookmod.BookMod; 14 | 15 | import javax.imageio.ImageIO; 16 | import java.awt.image.BufferedImage; 17 | import java.io.File; 18 | import java.io.FileWriter; 19 | import java.io.IOException; 20 | import java.io.UnsupportedEncodingException; 21 | import java.net.URLEncoder; 22 | import java.util.Date; 23 | import java.util.HashSet; 24 | 25 | /** 26 | * Used to store book information and write it to a file once finished 27 | * 28 | * @author John200410 5/15/2021 for bookmod 29 | */ 30 | public class BookBuffer { 31 | 32 | /** 33 | * Set of book information in this buffer 34 | */ 35 | private final HashSet books = new HashSet<>(); 36 | 37 | /** 38 | * Set of buffered images which will be written once this buffer has stopped 39 | *

40 | * Key = Name of book 41 | * Value = Screenshot 42 | */ 43 | private final HashSet> screenshots = new HashSet<>(); 44 | 45 | /** 46 | * State of this buffer 47 | */ 48 | private boolean active = false; 49 | 50 | /** 51 | * Folder to save book info and screenshots to 52 | */ 53 | private File dataFolder; 54 | 55 | /** 56 | * Last item that the cursor has hovered over. 57 | *

58 | * We have this because we don't want to try adding the same book over and over every frame. 59 | * There is probably a better way to do this but i cba 60 | */ 61 | private ItemStack lastHoveredStack = null; 62 | 63 | /** 64 | * Start tooltip listeners 65 | * 66 | * @return false if this buffer is already active 67 | */ 68 | public boolean start() { 69 | if(!this.active) { 70 | MinecraftForge.EVENT_BUS.register(this); 71 | this.dataFolder = new File(BookMod.PATH, BookMod.DATE_FORMAT.format(new Date())); 72 | this.dataFolder.mkdirs(); 73 | return this.active = true; 74 | } else { 75 | return false; 76 | } 77 | } 78 | 79 | /** 80 | * Will write all of the books in this buffer to a file and clear the buffer. 81 | * 82 | * @return false if this buffer isn't active 83 | */ 84 | public boolean finish() throws IOException { 85 | 86 | //check if this buffer is active 87 | if(this.active) { 88 | 89 | /************************************************************** 90 | * Save book text file 91 | *************************************************************/ 92 | 93 | //file that the data in this buffer will be saved to 94 | final File storingFile = new File(this.dataFolder, "books.txt"); 95 | 96 | //write data 97 | final FileWriter fileWriter = new FileWriter(storingFile); 98 | for (BookInfo book : this.books) { 99 | fileWriter.write("s!addbook " + book.toString() + System.lineSeparator()); 100 | } 101 | fileWriter.close(); 102 | 103 | //clear the buffer 104 | this.books.clear(); 105 | 106 | /*************************************************************/ 107 | 108 | /************************************************************** 109 | * Save screenshots 110 | *************************************************************/ 111 | 112 | final File screenshotsFolder = new File(this.dataFolder, "screenshots"); 113 | screenshotsFolder.mkdirs(); 114 | 115 | for (Tuple screenshot : this.screenshots) { 116 | final File file = new File(screenshotsFolder, screenshot.getFirst() + ".png").getCanonicalFile(); 117 | ImageIO.write(screenshot.getSecond(), "png", file); 118 | } 119 | 120 | //clear screenshots 121 | this.screenshots.clear(); 122 | 123 | /*************************************************************/ 124 | 125 | //reset state 126 | this.active = false; 127 | this.dataFolder = null; 128 | 129 | //unregister event listener 130 | this.lastHoveredStack = null; 131 | MinecraftForge.EVENT_BUS.unregister(this); 132 | 133 | return true; 134 | } else { 135 | return false; 136 | } 137 | } 138 | 139 | /** 140 | * Will try adding this book to the buffer and will take a screenshot 141 | * 142 | * @param title title of book 143 | * @param author author of book 144 | * @param x horizontal start position 145 | * @param y vertical start position 146 | */ 147 | public void addBook(String title, String author, int x, int y, int width, int height) { 148 | if(this.active) { 149 | //add book, and if this book wasn't already there then take a screenshot 150 | final BookInfo bookInfo = new BookInfo(title, author); 151 | if(this.books.add(bookInfo)) { 152 | final Minecraft mc = Minecraft.getMinecraft(); 153 | String name; 154 | 155 | //encode to make sure file can save properly if the file has special characters 156 | try { 157 | name = URLEncoder.encode(bookInfo.toString(), "UTF-8"); 158 | } catch (UnsupportedEncodingException e) { //this shouldn't happen, BUT if it does then fallback to just author + epoch time 159 | e.printStackTrace(); 160 | name = bookInfo.getAuthor() + System.currentTimeMillis(); 161 | } 162 | 163 | //add screenshot 164 | if(BookMod.screenshot) { 165 | final int scaleFactor = new ScaledResolution(mc).getScaleFactor(); 166 | this.screenshots.add(new Tuple<>(name, BetterScreenShotHelper.createScreenshot(Math.max(x * scaleFactor - 20, 0), Math.max(y * scaleFactor - 20, 0), Math.min(width * scaleFactor + 40, mc.displayWidth), Math.min(height * scaleFactor + 40, mc.displayHeight)))); 167 | } 168 | } 169 | } 170 | } 171 | 172 | @Override 173 | public String toString() { 174 | return (this.active ? "ACTIVE " : "IDLE ") + this.books + (this.dataFolder != null ? " " + this.dataFolder.getName() : ""); 175 | } 176 | 177 | /************************************************** 178 | * EVENTS 179 | *************************************************/ 180 | 181 | /** 182 | * This is called every frame you are hovering over an item 183 | */ 184 | @SubscribeEvent 185 | public void onHover(RenderTooltipEvent.PostText event) { 186 | final ItemStack stack = event.getStack(); 187 | 188 | //we aren't interested if this item isn't a written book 189 | if(!stack.getItem().equals(Items.WRITTEN_BOOK)) { 190 | return; 191 | } 192 | 193 | //check if this is the last book we have hovered over 194 | if(this.lastHoveredStack == null || !this.lastHoveredStack.equals(stack)) { 195 | 196 | //nbt of this itemstack 197 | final NBTTagCompound nbt = stack.getTagCompound(); 198 | 199 | //verify that this is a real book 200 | if(ItemWrittenBook.validBookTagContents(nbt)) { 201 | //get book info from NBT 202 | final String title = nbt.getString("title"); 203 | final String author = nbt.getString("author"); 204 | 205 | //add book 206 | this.addBook(title, author, event.getX(), event.getY(), event.getWidth(), event.getHeight()); 207 | } 208 | } 209 | 210 | this.lastHoveredStack = stack; 211 | } 212 | 213 | /*************************************************/ 214 | 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/org/rusherhack/bookmod/util/BookInfo.java: -------------------------------------------------------------------------------- 1 | package org.rusherhack.bookmod.util; 2 | 3 | /** 4 | * Object used to store a book's name and author 5 | * 6 | * @author John200410 5/15/2021 for bookmod 7 | */ 8 | public class BookInfo { 9 | 10 | /** 11 | * Title of the book 12 | */ 13 | private final String title; 14 | 15 | /** 16 | * Book author 17 | */ 18 | private final String author; 19 | 20 | public BookInfo(String title, String author) { 21 | this.title = title; 22 | this.author = author; 23 | } 24 | 25 | public String getTitle() { 26 | return title; 27 | } 28 | 29 | public String getAuthor() { 30 | return author; 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | return this.title.hashCode() + this.author.hashCode(); 36 | } 37 | 38 | @Override 39 | public boolean equals(Object obj) { 40 | if(!(obj instanceof BookInfo)) { 41 | return false; 42 | } else { 43 | final BookInfo altObj = (BookInfo) obj; 44 | return this.title.equals(altObj.title) && this.author.equals(altObj.author); 45 | } 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | final boolean useQuotes = this.title.contains(" "); 51 | 52 | final String title = useQuotes ? String.format("\"%s\"", this.title) : this.title; 53 | return title + " " + this.author; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "autobookarchiver", 4 | "name": "AutoBookArchiver", 5 | "description": "Mod made for Crystallising", 6 | "version": "v1.2.1", 7 | "mcversion": "1.12.2", 8 | "url": "", 9 | "updateUrl": "", 10 | "authorList": [ 11 | "John200410" 12 | ], 13 | "credits": "", 14 | "logoFile": "", 15 | "screenshots": [], 16 | "dependencies": [] 17 | } 18 | ] 19 | --------------------------------------------------------------------------------